Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/adrift.h"
#include "glk/adrift/os_glk.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/serialization.h"
namespace Glk {
namespace Adrift {
Adrift *g_vm = nullptr;
Adrift::Adrift(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
g_vm = this;
}
void Adrift::runGame() {
if (adrift_startup_code(&_gameFile))
adrift_main();
}
Common::Error Adrift::readSaveData(Common::SeekableReadStream *rs) {
LoadSerializer ser((sc_gameref_t)gsc_game, if_read_saved_game, rs);
return ser.load() ? Common::kNoError : Common::kReadingFailed;
}
Common::Error Adrift::writeGameData(Common::WriteStream *ws) {
SaveSerializer ser((sc_gameref_t)gsc_game, if_write_saved_game, ws);
ser.save();
return Common::kNoError;
}
sc_int Adrift::if_read_saved_game(void *opaque, sc_byte *buffer, sc_int length) {
Common::SeekableReadStream *rs = (Common::SeekableReadStream *)opaque;
return rs->read(buffer, length);
}
void Adrift::if_write_saved_game(void *opaque, const sc_byte *buffer, sc_int length) {
Common::WriteStream *ws = (Common::WriteStream *)opaque;
ws->write(buffer, length);
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,78 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* Based on Scare interpreter 1.3.10 */
#ifndef GLK_ADRIFT_ADRIFT
#define GLK_ADRIFT_ADRIFT
#include "common/scummsys.h"
#include "common/serializer.h"
#include "common/stack.h"
#include "glk/glk_api.h"
#include "glk/adrift/scare.h"
namespace Glk {
namespace Adrift {
/**
* Adrift game interpreter
*/
class Adrift : public GlkAPI {
private:
static void if_write_saved_game(void *opaque, const sc_byte *buffer, sc_int length);
static sc_int if_read_saved_game(void *opaque, sc_byte *buffer, sc_int length);
public:
/**
* Constructor
*/
Adrift(OSystem *syst, const GlkGameDescription &gameDesc);
/**
* Run the game
*/
void runGame() override;
/**
* Returns the running interpreter type
*/
InterpreterType getInterpreterType() const override {
return INTERPRETER_ADRIFT;
}
/**
* Load a savegame from the passed Quetzal file chunk stream
*/
Common::Error readSaveData(Common::SeekableReadStream *rs) override;
/**
* Save the game. The passed write stream represents access to the UMem chunk
* in the Quetzal save file that will be created
*/
Common::Error writeGameData(Common::WriteStream *ws) override;
};
extern Adrift *g_vm;
} // End of namespace Alan2
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,177 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/detection.h"
#include "glk/adrift/detection_tables.h"
#include "glk/adrift/scprotos.h"
#include "glk/blorb.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/md5.h"
#include "engines/game.h"
namespace Glk {
namespace Adrift {
enum {
VERSION_HEADER_SIZE = 14
};
/* Various version TAF file signatures. */
static const byte V500_SIGNATURE[VERSION_HEADER_SIZE] = {
0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87, 0xc2, 0xcf, 0x92, 0x45, 0x3e, 0x61, 0x30, 0x30
};
static const byte V500_SIGNATURE_2[VERSION_HEADER_SIZE] = {
0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87, 0xc2, 0xcf, 0x92, 0x45, 0x3e, 0x61, 0x51, 0x36
};
static const byte V400_SIGNATURE[VERSION_HEADER_SIZE] = {
0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87, 0xc2, 0xcf, 0x93, 0x45, 0x3e, 0x61, 0x39, 0xfa
};
static const byte V390_SIGNATURE[VERSION_HEADER_SIZE] = {
0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87, 0xc2, 0xcf, 0x94, 0x45, 0x37, 0x61, 0x39, 0xfa
};
static const byte V380_SIGNATURE[VERSION_HEADER_SIZE] = {
0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87, 0xc2, 0xcf, 0x94, 0x45, 0x36, 0x61, 0x39, 0xfa
};
void AdriftMetaEngine::getSupportedGames(PlainGameList &games) {
for (const PlainGameDescriptor *pd = ADRIFT_GAME_LIST; pd->gameId; ++pd) {
games.push_back(*pd);
}
for (const PlainGameDescriptor *pd = ADRIFT5_GAME_LIST; pd->gameId; ++pd) {
games.push_back(*pd);
}
}
const GlkDetectionEntry* AdriftMetaEngine::getDetectionEntries() {
return ADRIFT_GAMES;
}
GameDescriptor AdriftMetaEngine::findGame(const char *gameId) {
for (const PlainGameDescriptor *pd = ADRIFT_GAME_LIST; pd->gameId; ++pd) {
if (!strcmp(gameId, pd->gameId))
return *pd;
}
for (const PlainGameDescriptor *pd = ADRIFT5_GAME_LIST; pd->gameId; ++pd) {
if (!strcmp(gameId, pd->gameId)) {
GameDescriptor gd = *pd;
gd._supportLevel = kUnstableGame;
return gd;
}
}
return PlainGameDescriptor::empty();
}
bool AdriftMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
int version;
byte header[VERSION_HEADER_SIZE];
// Loop through the files of the folder
for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
// Check for a recognised filename
if (file->isDirectory())
continue;
Common::String filename = file->getName();
bool hasExt = Blorb::hasBlorbExt(filename) || filename.hasSuffixIgnoreCase(".taf");
if (!hasExt)
continue;
// Open up the file and calculate the md5
Common::File gameFile;
if (!gameFile.open(*file))
continue;
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
size_t filesize = gameFile.size();
gameFile.seek(0);
gameFile.read(header, VERSION_HEADER_SIZE);
gameFile.seek(0);
bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI);
gameFile.close();
if (!isBlorb && Blorb::hasBlorbExt(filename))
continue;
// Check for known games
const GlkDetectionEntry *p = ADRIFT_GAMES;
while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
++p;
if (p->_gameId) {
PlainGameDescriptor gameDesc = findGame(p->_gameId);
gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language));
} else {
version = isBlorb ? 0 : detectGameVersion(header);
if (isBlorb || version != TAF_VERSION_NONE) {
const PlainGameDescriptor &desc = ADRIFT_GAME_LIST[0];
gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
}
}
}
return !gameList.empty();
}
void AdriftMetaEngine::detectClashes(Common::StringMap &map) {
for (const PlainGameDescriptor *pd = ADRIFT_GAME_LIST; pd->gameId; ++pd) {
if (map.contains(pd->gameId))
error("Duplicate game Id found - %s", pd->gameId);
map[pd->gameId] = "";
}
for (const PlainGameDescriptor *pd = ADRIFT5_GAME_LIST; pd->gameId; ++pd) {
if (map.contains(pd->gameId))
error("Duplicate game Id found - %s", pd->gameId);
map[pd->gameId] = "";
}
}
int AdriftMetaEngine::detectGameVersion(const byte *header) {
if (memcmp(header, V500_SIGNATURE, VERSION_HEADER_SIZE) == 0 ||
memcmp(header, V500_SIGNATURE_2, VERSION_HEADER_SIZE) == 0) {
return TAF_VERSION_500;
} else if (memcmp(header, V400_SIGNATURE, VERSION_HEADER_SIZE) == 0) {
return TAF_VERSION_400;
} else if (memcmp(header, V390_SIGNATURE, VERSION_HEADER_SIZE) == 0) {
return TAF_VERSION_390;
} else if (memcmp(header, V380_SIGNATURE, VERSION_HEADER_SIZE) == 0) {
return TAF_VERSION_380;
} else {
return TAF_VERSION_NONE;
}
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,72 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ADRIFT_DETECTION
#define GLK_ADRIFT_DETECTION
#include "common/fs.h"
#include "common/hash-str.h"
#include "engines/game.h"
#include "glk/detection.h"
namespace Glk {
namespace Adrift {
/**
* Meta engine for Adrift interpreter
*/
class AdriftMetaEngine {
public:
/**
* Get a list of supported games
*/
static void getSupportedGames(PlainGameList &games);
/**
* Get the detection entries
*/
static const GlkDetectionEntry* getDetectionEntries();
/**
* Returns a game description for the given game Id, if it's supported
*/
static GameDescriptor findGame(const char *gameId);
/**
* Detect supported games
*/
static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList);
/**
* Check for game Id clashes with other sub-engines
*/
static void detectClashes(Common::StringMap &map);
/**
* Detect the game version
*/
static int detectGameVersion(const byte *header);
};
} // End of namespace Adrift
} // End of namespace Glk
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ADRIFT_OS_GLK_H
#define ADRIFT_OS_GLK_H
#include "common/scummsys.h"
#include "glk/adrift/scare.h"
namespace Glk {
namespace Adrift {
extern sc_game gsc_game;
extern bool adrift_startup_code(Common::SeekableReadStream *gameFile);
extern void adrift_main();
} // End of namespace Adrift
} // End of namespace Glk
#endif

188
engines/glk/adrift/scare.h Normal file
View File

@@ -0,0 +1,188 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ADRIFT_ADRIFT_H
#define ADRIFT_ADRIFT_H
#include "common/scummsys.h"
#include "common/stream.h"
#include "glk/jumps.h"
namespace Glk {
namespace Adrift {
/*
* Base type definitions. SCARE integer types need to be at least 32 bits,
* so using long here is a good bet for almost all ANSI C implementations for
* 32 and 64 bit platforms; maybe also for any 16 bit ones. For 64 bit
* platforms configured for LP64, SCARE integer types will consume more space
* in data structures. Values won't wrap identically to 32 bit ones, but
* games shouldn't be relying on wrapping anyway. One final note -- in several
* places, SCARE allocates 32 bytes into which it will sprintf() a long; this
* is fine for both standard 32 bit and LP64 64 bit platforms, but is unsafe
* should SCARE ever be configured for 128 bit definitions of sc_[u]int.
*/
typedef char sc_char;
typedef unsigned char sc_byte;
typedef long sc_int;
typedef unsigned long sc_uint;
typedef int sc_bool;
enum { BYTE_MAX = 0xff };
enum { INTEGER_MAX = 0x7fff };
/* Enumerated confirmation types, passed to os_confirm(). */
enum {
SC_CONF_QUIT = 0,
SC_CONF_RESTART, SC_CONF_SAVE, SC_CONF_RESTORE, SC_CONF_VIEW_HINTS
};
/* HTML-like tag enumerated values, passed to os_print_tag(). */
enum {
SC_TAG_UNKNOWN = 0, SC_TAG_ITALICS, SC_TAG_ENDITALICS, SC_TAG_BOLD,
SC_TAG_ENDBOLD, SC_TAG_UNDERLINE, SC_TAG_ENDUNDERLINE, SC_TAG_COLOR,
SC_TAG_ENDCOLOR, SC_TAG_FONT, SC_TAG_ENDFONT, SC_TAG_BGCOLOR, SC_TAG_CENTER,
SC_TAG_ENDCENTER, SC_TAG_RIGHT, SC_TAG_ENDRIGHT, SC_TAG_WAIT, SC_TAG_WAITKEY,
SC_TAG_CLS,
/* British spelling equivalents. */
SC_TAG_COLOUR = SC_TAG_COLOR,
SC_TAG_ENDCOLOUR = SC_TAG_ENDCOLOR,
SC_TAG_BGCOLOUR = SC_TAG_BGCOLOR,
SC_TAG_CENTRE = SC_TAG_CENTER,
SC_TAG_ENDCENTRE = SC_TAG_ENDCENTER
};
/* OS interface function prototypes; interpreters must define these. */
typedef void *sc_game;
extern void os_print_string(const sc_char *string);
extern void os_print_tag(sc_int tag, const sc_char *argument);
extern void os_play_sound(const sc_char *filepath,
sc_int offset, sc_int length, sc_bool is_looping);
extern void os_stop_sound();
extern void os_show_graphic(const sc_char *filepath,
sc_int offset, sc_int length);
extern sc_bool os_read_line(sc_char *buffer, sc_int length);
extern sc_bool os_confirm(sc_int type);
extern void *os_open_file(sc_bool is_save);
extern void os_write_file(void *opaque, const sc_byte *buffer, sc_int length);
extern sc_int os_read_file(void *opaque, sc_byte *buffer, sc_int length);
extern void os_close_file(void *opaque);
extern void os_display_hints(sc_game game);
extern void os_print_string_debug(const sc_char *string);
extern sc_bool os_read_line_debug(sc_char *buffer, sc_int length);
/* Interpreter trace flag bits, passed to sc_set_trace_flags(). */
enum {
SC_TRACE_PARSE = 1, SC_TRACE_PROPERTIES = 2, SC_TRACE_VARIABLES = 4,
SC_TRACE_PARSER = 8, SC_TRACE_LIBRARY = 16, SC_TRACE_EVENTS = 32,
SC_TRACE_NPCS = 64, SC_TRACE_OBJECTS = 128, SC_TRACE_TASKS = 256,
SC_TRACE_PRINTFILTER = 512,
SC_DUMP_TAF = 1024, SC_DUMP_PROPERTIES = 2048, SC_DUMP_VARIABLES = 4096,
SC_DUMP_PARSER_TREES = 8192, SC_DUMP_LOCALE_TABLES = 16384
};
/* Module-wide trace control function prototype. */
extern void sc_set_trace_flags(sc_uint trace_flags);
/* Interpreter interface function prototypes. */
extern sc_game sc_game_from_filename(const sc_char *filename);
extern sc_game sc_game_from_stream(Common::SeekableReadStream *stream);
extern sc_game sc_game_from_callback(sc_int(*callback)
(void *, sc_byte *, sc_int),
void *opaque);
extern void sc_interpret_game(CONTEXT, sc_game game);
extern void sc_restart_game(CONTEXT, sc_game game);
extern sc_bool sc_save_game(sc_game game);
extern sc_bool sc_load_game(sc_game game);
extern sc_bool sc_undo_game_turn(CONTEXT, sc_game game);
extern void sc_quit_game(sc_game game);
extern sc_bool sc_save_game_to_filename(sc_game game, const sc_char *filename);
extern void sc_save_game_to_stream(sc_game game, Common::SeekableReadStream *stream);
extern void sc_save_game_to_callback(sc_game game,
void (*callback)
(void *, const sc_byte *, sc_int),
void *opaque);
extern sc_bool sc_load_game_from_filename(sc_game game,
const sc_char *filename);
extern sc_bool sc_load_game_from_stream(sc_game game, Common::SeekableReadStream *stream);
extern sc_bool sc_load_game_from_callback(sc_game game,
sc_int(*callback)
(void *, sc_byte *, sc_int),
void *opaque);
extern void sc_free_game(sc_game game);
extern sc_bool sc_is_game_running(sc_game game);
extern const sc_char *sc_get_game_name(sc_game game);
extern const sc_char *sc_get_game_author(sc_game game);
extern const sc_char *sc_get_game_compile_date(sc_game game);
extern sc_int sc_get_game_turns(sc_game game);
extern sc_int sc_get_game_score(sc_game game);
extern sc_int sc_get_game_max_score(sc_game game);
extern const sc_char *sc_get_game_room(sc_game game);
extern const sc_char *sc_get_game_status_line(sc_game game);
extern const sc_char *sc_get_game_preferred_font(sc_game game);
extern sc_bool sc_get_game_bold_room_names(sc_game game);
extern sc_bool sc_get_game_verbose(sc_game game);
extern sc_bool sc_get_game_notify_score_change(sc_game game);
extern sc_bool sc_has_game_completed(sc_game game);
extern sc_bool sc_is_game_undo_available(sc_game game);
extern void sc_set_game_bold_room_names(sc_game game, sc_bool flag);
extern void sc_set_game_verbose(sc_game game, sc_bool flag);
extern void sc_set_game_notify_score_change(sc_game game, sc_bool flag);
extern sc_bool sc_does_game_use_sounds(sc_game);
extern sc_bool sc_does_game_use_graphics(sc_game);
typedef void *sc_game_hint;
extern sc_game_hint sc_get_first_game_hint(sc_game game);
extern sc_game_hint sc_get_next_game_hint(sc_game game, sc_game_hint hint);
extern const sc_char *sc_get_game_hint_question(sc_game game,
sc_game_hint hint);
extern const sc_char *sc_get_game_subtle_hint(sc_game game,
sc_game_hint hint);
extern const sc_char *sc_get_game_unsubtle_hint(sc_game game,
sc_game_hint hint);
extern void sc_set_game_debugger_enabled(sc_game game, sc_bool flag);
extern sc_bool sc_get_game_debugger_enabled(sc_game game);
extern sc_bool sc_run_game_debugger_command(sc_game game,
const sc_char *debug_command);
extern void sc_set_portable_random(sc_bool flag);
extern void sc_reseed_random_sequence(sc_uint new_seed);
/* Locale control and query functions. */
extern sc_bool sc_set_locale(const sc_char *name);
extern const sc_char *sc_get_locale();
/* A few possibly useful utilities. */
extern sc_int sc_strncasecmp(const sc_char *s1, const sc_char *s2, sc_int n);
extern sc_int sc_strcasecmp(const sc_char *s1, const sc_char *s2);
extern const sc_char *sc_scare_version();
extern sc_int sc_scare_emulation();
extern char *adrift_fgets(char *buf, int max, Common::SeekableReadStream *s);
} // End of namespace Adrift
} // End of namespace Glk
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,771 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/scgamest.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Event pause and resume tasks need more testing.
*/
/* Trace flag, set before running. */
static sc_bool evt_trace = FALSE;
/*
* evt_any_task_in_state()
*
* Return TRUE if any task at all matches the given completion state.
*/
static sc_bool evt_any_task_in_state(sc_gameref_t game, sc_bool state) {
sc_int task;
/* Scan tasks for any whose completion matches input. */
for (task = 0; task < gs_task_count(game); task++) {
if (gs_task_done(game, task) == state)
return TRUE;
}
/* No tasks matched. */
return FALSE;
}
/*
* evt_can_see_event()
*
* Return TRUE if player is in the right room for event text.
*/
sc_bool evt_can_see_event(sc_gameref_t game, sc_int event) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[5];
sc_int type;
/* Check room list for the event and return it. */
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "Where";
vt_key[3].string = "Type";
type = prop_get_integer(bundle, "I<-siss", vt_key);
switch (type) {
case ROOMLIST_NO_ROOMS:
return FALSE;
case ROOMLIST_ALL_ROOMS:
return TRUE;
case ROOMLIST_ONE_ROOM:
vt_key[3].string = "Room";
return prop_get_integer(bundle, "I<-siss", vt_key)
== gs_playerroom(game);
case ROOMLIST_SOME_ROOMS:
vt_key[3].string = "Rooms";
vt_key[4].integer = gs_playerroom(game);
return prop_get_boolean(bundle, "B<-sissi", vt_key);
default:
sc_fatal("evt_can_see_event: invalid type, %ld\n", type);
return FALSE;
}
}
/*
* evt_move_object()
*
* Move an object from within an event.
*/
static void evt_move_object(sc_gameref_t game, sc_int object, sc_int destination) {
/* Ignore negative values of object. */
if (object >= 0) {
if (evt_trace) {
sc_trace("Event: moving object %ld to room %ld\n",
object, destination);
}
/* Move object depending on destination. */
switch (destination) {
case -1: /* Hidden. */
gs_object_make_hidden(game, object);
break;
case 0: /* Held by player. */
gs_object_player_get(game, object);
break;
case 1: /* Same room as player. */
gs_object_to_room(game, object, gs_playerroom(game));
break;
default:
if (destination < gs_room_count(game) + 2)
gs_object_to_room(game, object, destination - 2);
else {
sc_int roomgroup, room;
roomgroup = destination - gs_room_count(game) - 2;
room = lib_random_roomgroup_member(game, roomgroup);
gs_object_to_room(game, object, room);
}
break;
}
/*
* If static, mark as no longer unmoved.
*
* TODO Is this the only place static objects can be moved? And just
* how static is a static object if it's moveable, anyway?
*/
if (obj_is_static(game, object))
gs_set_object_static_unmoved(game, object, FALSE);
}
}
/*
* evt_fixup_v390_v380_immediate_restart()
*
* Versions 3.9 and 3.8 differ from version 4.0 on immediate restart; they
* "miss" the event start actions and move one step into the event without
* comment. It's arguable if this is a feature or a bug; nevertheless, we
* can do the same thing here, though it's ugly.
*/
static sc_bool evt_fixup_v390_v380_immediate_restart(sc_gameref_t game, sc_int event) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int version;
vt_key[0].string = "Version";
version = prop_get_integer(bundle, "I<-s", vt_key);
if (version < TAF_VERSION_400) {
sc_int time1, time2;
if (evt_trace)
sc_trace("Event: applying 3.9/3.8 restart fixup\n");
/* Set to running state. */
gs_set_event_state(game, event, ES_RUNNING);
/* Set up event time to be one less than a proper start. */
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "Time1";
time1 = prop_get_integer(bundle, "I<-sis", vt_key);
vt_key[2].string = "Time2";
time2 = prop_get_integer(bundle, "I<-sis", vt_key);
gs_set_event_time(game, event, sc_randomint(time1, time2) - 1);
}
/* Return TRUE if we applied the fixup. */
return version < TAF_VERSION_400;
}
/*
* evt_start_event()
*
* Change an event from WAITING to RUNNING.
*/
static void evt_start_event(sc_gameref_t game, sc_int event) {
const sc_filterref_t filter = gs_get_filter(game);
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[4];
sc_int time1, time2, obj1, obj1dest;
if (evt_trace)
sc_trace("Event: starting event %ld\n", event);
/* If event is visible, print its start text. */
if (evt_can_see_event(game, event)) {
const sc_char *starttext;
/* Get and print start text. */
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "StartText";
starttext = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(starttext)) {
pf_buffer_string(filter, starttext);
pf_buffer_character(filter, '\n');
}
/* Handle any associated resource. */
vt_key[2].string = "Res";
vt_key[3].integer = 0;
res_handle_resource(game, "sisi", vt_key);
}
/* Move event object to destination. */
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "Obj1";
obj1 = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
vt_key[2].string = "Obj1Dest";
obj1dest = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
evt_move_object(game, obj1, obj1dest);
/* Set the event's state and time. */
gs_set_event_state(game, event, ES_RUNNING);
vt_key[2].string = "Time1";
time1 = prop_get_integer(bundle, "I<-sis", vt_key);
vt_key[2].string = "Time2";
time2 = prop_get_integer(bundle, "I<-sis", vt_key);
gs_set_event_time(game, event, sc_randomint(time1, time2));
if (evt_trace)
sc_trace("Event: start event handling done, %ld\n", event);
}
/*
* evt_get_starter_type()
*
* Return the starter type for an event.
*/
static sc_int evt_get_starter_type(sc_gameref_t game, sc_int event) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int startertype;
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "StarterType";
startertype = prop_get_integer(bundle, "I<-sis", vt_key);
return startertype;
}
/*
* evt_finish_event()
*
* Move an event to FINISHED, or restart it.
*/
static void evt_finish_event(sc_gameref_t game, sc_int event) {
const sc_filterref_t filter = gs_get_filter(game);
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[4];
sc_int obj2, obj2dest, obj3, obj3dest;
sc_int task, startertype, restarttype;
sc_bool taskdir;
if (evt_trace)
sc_trace("Event: finishing event %ld\n", event);
/* Set up invariant parts of the key. */
vt_key[0].string = "Events";
vt_key[1].integer = event;
/* If event is visible, print its finish text. */
if (evt_can_see_event(game, event)) {
const sc_char *finishtext;
/* Get and print finish text. */
vt_key[2].string = "FinishText";
finishtext = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(finishtext)) {
pf_buffer_string(filter, finishtext);
pf_buffer_character(filter, '\n');
}
/* Handle any associated resource. */
vt_key[2].string = "Res";
vt_key[3].integer = 4;
res_handle_resource(game, "sisi", vt_key);
}
/* Move event objects to destination. */
vt_key[2].string = "Obj2";
obj2 = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
vt_key[2].string = "Obj2Dest";
obj2dest = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
evt_move_object(game, obj2, obj2dest);
vt_key[2].string = "Obj3";
obj3 = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
vt_key[2].string = "Obj3Dest";
obj3dest = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
evt_move_object(game, obj3, obj3dest);
/* See if there is an affected task. */
vt_key[2].string = "TaskAffected";
task = prop_get_integer(bundle, "I<-sis", vt_key) - 1;
if (task >= 0) {
vt_key[2].string = "TaskFinished";
taskdir = !prop_get_boolean(bundle, "B<-sis", vt_key);
if (task_can_run_task_directional(game, task, taskdir)) {
if (evt_trace) {
sc_trace("Event: event running task %ld, %s\n",
task, taskdir ? "forwards" : "backwards");
}
task_run_task(game, task, taskdir);
} else {
if (evt_trace)
sc_trace("Event: event can't run task %ld\n", task);
}
}
/* Handle possible restart. */
vt_key[2].string = "RestartType";
restarttype = prop_get_integer(bundle, "I<-sis", vt_key);
switch (restarttype) {
case 0: /* Don't restart. */
startertype = evt_get_starter_type(game, event);
switch (startertype) {
case 1: /* Immediate. */
case 2: /* Random delay. */
case 3: /* After task. */
gs_set_event_state(game, event, ES_FINISHED);
gs_set_event_time(game, event, 0);
break;
default:
sc_fatal("evt_finish_event:"
" unknown value for starter type, %ld\n", startertype);
}
break;
case 1: /* Restart immediately. */
if (evt_fixup_v390_v380_immediate_restart(game, event))
break;
else
evt_start_event(game, event);
break;
case 2: /* Restart after delay. */
startertype = evt_get_starter_type(game, event);
switch (startertype) {
case 1: /* Immediate. */
if (evt_fixup_v390_v380_immediate_restart(game, event))
break;
else
evt_start_event(game, event);
break;
case 2: { /* Random delay. */
sc_int start, end;
gs_set_event_state(game, event, ES_WAITING);
vt_key[2].string = "StartTime";
start = prop_get_integer(bundle, "I<-sis", vt_key);
vt_key[2].string = "EndTime";
end = prop_get_integer(bundle, "I<-sis", vt_key);
gs_set_event_time(game, event, sc_randomint(start, end));
break;
}
case 3: /* After task. */
gs_set_event_state(game, event, ES_AWAITING);
gs_set_event_time(game, event, 0);
break;
default:
sc_fatal("evt_finish_event: unknown StarterType\n");
}
break;
default:
sc_fatal("evt_finish_event: unknown RestartType\n");
}
if (evt_trace)
sc_trace("Event: finish event handling done, %ld\n", event);
}
/*
* evt_has_starter_task()
* evt_starter_task_is_complete()
* evt_pauser_task_is_complete()
* evt_resumer_task_is_complete()
*
* Return the status of start, pause and resume states of an event.
*/
static sc_bool evt_has_starter_task(sc_gameref_t game, sc_int event) {
sc_int startertype;
startertype = evt_get_starter_type(game, event);
return startertype == 3;
}
static sc_bool evt_starter_task_is_complete(sc_gameref_t game, sc_int event) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int task;
sc_bool start;
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "TaskNum";
task = prop_get_integer(bundle, "I<-sis", vt_key);
start = FALSE;
if (task == 0) {
if (evt_any_task_in_state(game, TRUE))
start = TRUE;
} else if (task > 0) {
if (gs_task_done(game, task - 1))
start = TRUE;
}
return start;
}
static sc_bool evt_pauser_task_is_complete(sc_gameref_t game, sc_int event) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int pausetask;
sc_bool completed, pause;
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "PauseTask";
pausetask = prop_get_integer(bundle, "I<-sis", vt_key);
vt_key[2].string = "PauserCompleted";
completed = !prop_get_boolean(bundle, "B<-sis", vt_key);
pause = FALSE;
if (pausetask == 1) {
if (evt_any_task_in_state(game, completed))
pause = TRUE;
} else if (pausetask > 1) {
if (completed == gs_task_done(game, pausetask - 2))
pause = TRUE;
}
return pause;
}
static sc_bool evt_resumer_task_is_complete(sc_gameref_t game, sc_int event) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int resumetask;
sc_bool completed, resume;
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "ResumeTask";
resumetask = prop_get_integer(bundle, "I<-sis", vt_key);
vt_key[2].string = "ResumerCompleted";
completed = !prop_get_boolean(bundle, "B<-sis", vt_key);
resume = FALSE;
if (resumetask == 1) {
if (evt_any_task_in_state(game, completed))
resume = TRUE;
} else if (resumetask > 1) {
if (completed == gs_task_done(game, resumetask - 2))
resume = TRUE;
}
return resume;
}
/*
* evt_handle_preftime_notifications()
*
* Print messages and handle resources for the event where we're in mid-event
* and getting close to some number of turns from its end.
*/
static void evt_handle_preftime_notifications(sc_gameref_t game, sc_int event) {
const sc_filterref_t filter = gs_get_filter(game);
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[4];
sc_int preftime1, preftime2;
const sc_char *preftext;
vt_key[0].string = "Events";
vt_key[1].integer = event;
vt_key[2].string = "PrefTime1";
preftime1 = prop_get_integer(bundle, "I<-sis", vt_key);
if (preftime1 == gs_event_time(game, event)) {
vt_key[2].string = "PrefText1";
preftext = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(preftext)) {
pf_buffer_string(filter, preftext);
pf_buffer_character(filter, '\n');
}
vt_key[2].string = "Res";
vt_key[3].integer = 2;
res_handle_resource(game, "sisi", vt_key);
}
vt_key[2].string = "PrefTime2";
preftime2 = prop_get_integer(bundle, "I<-sis", vt_key);
if (preftime2 == gs_event_time(game, event)) {
vt_key[2].string = "PrefText2";
preftext = prop_get_string(bundle, "S<-sis", vt_key);
if (!sc_strempty(preftext)) {
pf_buffer_string(filter, preftext);
pf_buffer_character(filter, '\n');
}
vt_key[2].string = "Res";
vt_key[3].integer = 3;
res_handle_resource(game, "sisi", vt_key);
}
}
/*
* evt_tick_event()
*
* Attempt to advance an event by one turn.
*/
static void evt_tick_event(sc_gameref_t game, sc_int event) {
if (evt_trace) {
sc_trace("Event: ticking event %ld: state %ld, time %ld\n", event,
gs_event_state(game, event), gs_event_time(game, event));
}
/* Handle call based on current event state. */
switch (gs_event_state(game, event)) {
case ES_WAITING: {
if (evt_trace)
sc_trace("Event: ticking waiting event %ld\n", event);
/*
* Because we also tick an event that goes from waiting to running,
* events started here will tick through RUNNING too, and have their
* time decremented. To get around this, so that the timer for one-
* shot events doesn't look one lower than it should after this
* transition, we need to set the initial time for events that start
* as soon as the game starts to one greater than that set by
* evt_start_time(). Here's the hack to do that; if the event starts
* immediately, its time will already be zero, even before decrement,
* which is how we tell which events to apply this hack to.
*
* TODO This seems to work, but also seems very dodgy.
*/
if (gs_event_time(game, event) == 0) {
evt_start_event(game, event);
/* If the event time was set to zero, finish immediately. */
if (gs_event_time(game, event) <= 0)
evt_finish_event(game, event);
else
gs_set_event_time(game, event, gs_event_time(game, event) + 1);
break;
}
/*
* Decrement the event's time, and if it goes to zero, start running
* the event.
*/
gs_decrement_event_time(game, event);
if (gs_event_time(game, event) <= 0) {
evt_start_event(game, event);
/* If the event time was set to zero, finish immediately. */
if (gs_event_time(game, event) <= 0)
evt_finish_event(game, event);
}
}
break;
case ES_RUNNING: {
if (evt_trace)
sc_trace("Event: ticking running event %ld\n", event);
/*
* Re-check the starter task; if it's no longer completed, we need
* to set the event back to waiting on task.
*/
if (evt_has_starter_task(game, event)) {
if (!evt_starter_task_is_complete(game, event)) {
if (evt_trace)
sc_trace("Event: starter task not complete\n");
gs_set_event_state(game, event, ES_AWAITING);
gs_set_event_time(game, event, 0);
break;
}
}
/* If the pauser has completed, but resumer not, pause this event. */
if (evt_pauser_task_is_complete(game, event)
&& !evt_resumer_task_is_complete(game, event)) {
if (evt_trace)
sc_trace("Event: pause complete\n");
gs_set_event_state(game, event, ES_PAUSED);
break;
}
/*
* Decrement the event's time, and print any notifications for a set
* number of turns from the event end.
*/
gs_decrement_event_time(game, event);
if (evt_can_see_event(game, event))
evt_handle_preftime_notifications(game, event);
/* If the time goes to zero, finish running the event. */
if (gs_event_time(game, event) <= 0)
evt_finish_event(game, event);
}
break;
case ES_AWAITING: {
if (evt_trace)
sc_trace("Event: ticking awaiting event %ld\n", event);
/*
* Check the starter task. If it's completed, start running the
* event.
*/
if (evt_starter_task_is_complete(game, event)) {
evt_start_event(game, event);
/* If the event time was set to zero, finish immediately. */
if (gs_event_time(game, event) <= 0)
evt_finish_event(game, event);
else {
/*
* If the pauser has completed, but resumer not, immediately
* also pause this event.
*/
if (evt_pauser_task_is_complete(game, event)
&& !evt_resumer_task_is_complete(game, event)) {
if (evt_trace)
sc_trace("Event: pause complete, immediate pause\n");
gs_set_event_state(game, event, ES_PAUSED);
}
}
}
}
break;
case ES_FINISHED: {
if (evt_trace)
sc_trace("Event: ticking finished event %ld\n", event);
/*
* Check the starter task; if it's not completed, we need to set the
* event back to waiting on task.
*
* A completed event needs to go back to waiting on its task, but we
* don't want to set it there as soon as the event finishes. We need
* to wait for the starter task to first become undone, otherwise the
* event just cycles endlessly, and they don't in Adrift itself. Here
* is where we wait for starter tasks to become undone.
*/
if (evt_has_starter_task(game, event)) {
if (!evt_starter_task_is_complete(game, event)) {
if (evt_trace)
sc_trace("Event: starter task not complete\n");
gs_set_event_state(game, event, ES_AWAITING);
gs_set_event_time(game, event, 0);
break;
}
}
}
break;
case ES_PAUSED: {
if (evt_trace)
sc_trace("Event: ticking paused event %ld\n", event);
/* If the resumer has completed, resume this event. */
if (evt_resumer_task_is_complete(game, event)) {
if (evt_trace)
sc_trace("Event: resume complete\n");
gs_set_event_state(game, event, ES_RUNNING);
break;
}
}
break;
default:
sc_fatal("evt_tick: invalid event state\n");
}
if (evt_trace) {
sc_trace("Event: after ticking event %ld: state %ld, time %ld\n", event,
gs_event_state(game, event), gs_event_time(game, event));
}
}
/*
* evt_tick_events()
*
* Attempt to advance each event by one turn.
*/
void evt_tick_events(sc_gameref_t game) {
sc_int event;
/*
* Tick all events. If an event transitions into a running state from a
* paused or waiting state, tick that event again.
*/
for (event = 0; event < gs_event_count(game); event++) {
sc_int prior_state, state;
/* Note current state, and tick event forwards. */
prior_state = gs_event_state(game, event);
evt_tick_event(game, event);
/*
* If the event went from paused or waiting to running, tick again.
* This looks dodgy, and probably is, but it does keep timers correct
* by only re-ticking events that have transitioned from non-running
* states to a running one, and not already-running events. This is
* in effect just adding a bit of turn processing to a tick that would
* otherwise change state alone; a bit of laziness, in other words.
*/
state = gs_event_state(game, event);
if (state == ES_RUNNING
&& (prior_state == ES_PAUSED || prior_state == ES_WAITING))
evt_tick_event(game, event);
}
}
/*
* evt_debug_trace()
*
* Set event tracing on/off.
*/
void evt_debug_trace(sc_bool flag) {
evt_trace = flag;
}
} // End of namespace Adrift
} // End of namespace Glk

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,188 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#ifndef ADRIFT_GAMESTATE_H
#define ADRIFT_GAMESTATE_H
namespace Glk {
namespace Adrift {
/* Room state structure, tracks rooms visited by the player. */
struct sc_roomstate_s {
sc_bool visited;
};
typedef sc_roomstate_s sc_roomstate_t;
/*
* Object state structure, tracks object movement, position, parent, openness
* for openable objects, state for stateful objects, and whether seen or not
* by the player. The enumerations are values assigned to position when the
* object is other than just "in a room"; otherwise position contains the
* room number + 1.
*/
enum {
OBJ_HIDDEN = -1, OBJ_HELD_PLAYER = 0, OBJ_HELD_NPC = -200, OBJ_WORN_PLAYER = -100,
OBJ_WORN_NPC = -300, OBJ_PART_PLAYER = -30, OBJ_PART_NPC = -30, OBJ_ON_OBJECT = -20,
OBJ_IN_OBJECT = -10
};
struct sc_objectstate_s {
sc_int position;
sc_int parent;
sc_int openness;
sc_int state;
sc_bool seen;
sc_bool unmoved;
sc_bool static_unmoved;
};
typedef sc_objectstate_s sc_objectstate_t;
/* Task state structure, tracks task done, and if task scored. */
struct sc_taskstate_s {
sc_bool done;
sc_bool scored;
};
typedef sc_taskstate_s sc_taskstate_t;
/* Event state structure, holds event state, and timing information. */
enum {
ES_WAITING = 1,
ES_RUNNING = 2, ES_AWAITING = 3, ES_FINISHED = 4, ES_PAUSED = 5
};
struct sc_eventstate_s {
sc_int state;
sc_int time;
};
typedef sc_eventstate_s sc_eventstate_t;
/*
* NPC state structure, tracks the NPC location and position, any parent
* object, whether the NPC seen, and if the NPC walks, the count of walk
* steps and a steps array sized to this count.
*/
struct sc_npcstate_s {
sc_int location;
sc_int position;
sc_int parent;
sc_int walkstep_count;
sc_int *walksteps;
sc_bool seen;
};
typedef sc_npcstate_s sc_npcstate_t;
/*
* Resource tracking structure, holds the resource name, including any
* trailing "##" for looping sounds, its offset into the game file, and its
* length. Two resources are held -- active, and requested. The game main
* loop compares the two, and notifies the interface on a change.
*/
struct sc_resource_s {
const sc_char *name;
sc_int offset;
sc_int length;
};
typedef sc_resource_s sc_resource_t;
/*
* Overall game state structure. Arrays are malloc'ed for the appropriate
* number of each of the above state structures.
*/
struct sc_game_s {
sc_uint magic;
/* References to assorted helper subsystems. */
sc_var_setref_t vars;
sc_prop_setref_t bundle;
sc_filterref_t filter;
sc_memo_setref_t memento;
sc_debuggerref_t debugger;
/* Undo information, also used by the debugger. */
struct sc_game_s *temporary;
struct sc_game_s *undo;
sc_bool undo_available;
/* Basic game state -- rooms, objects, and so on. */
sc_int room_count;
sc_roomstate_t *rooms;
sc_int object_count;
sc_objectstate_t *objects;
sc_int task_count;
sc_taskstate_t *tasks;
sc_int event_count;
sc_eventstate_t *events;
sc_int npc_count;
sc_npcstate_t *npcs;
sc_int playerroom;
sc_int playerposition;
sc_int playerparent;
sc_int turns;
sc_int score;
sc_bool bold_room_names;
sc_bool verbose;
sc_bool notify_score_change;
sc_char *current_room_name;
sc_char *status_line;
sc_char *title;
sc_char *author;
sc_char *hint_text;
/* Resource management data. */
sc_resource_t requested_sound;
sc_resource_t requested_graphic;
sc_bool stop_sound;
sc_bool sound_active;
sc_resource_t playing_sound;
sc_resource_t displayed_graphic;
/* Game running and game completed flags. */
sc_bool is_running;
sc_bool has_completed;
/* Player's setting for waitturns; overrides the game's. */
sc_int waitturns;
/* Miscellaneous library and main loop conveniences. */
sc_int waitcounter;
sc_bool has_notified;
sc_bool is_admin;
sc_bool do_again;
sc_int redo_sequence;
sc_bool do_restart;
sc_bool do_restore;
sc_bool *object_references;
sc_bool *multiple_references;
sc_bool *npc_references;
sc_int it_object;
sc_int him_npc;
sc_int her_npc;
sc_int it_npc;
};
typedef sc_game_s sc_game_t;
} // End of namespace Adrift
} // End of namespace Glk
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,515 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Standard libc ctype.h functions vary their results according to the
* currently set locale. We want consistent Windows codepage 1252 or
* codepage 1251 (WinLatin1 or WinCyrillic) results. To get this, then,
* we have to define the needed functions internally to SCARE.
*/
/*
* All the ctype boolean and character tables contain 256 elements, one for
* each possible sc_char value. This is used to size arrays and to verify
* that range setting functions do not overrun array boundaries.
*/
enum { TABLE_SIZE = 256 };
/*
* loc_setrange_bool()
* loc_setranges_bool()
*
* Helpers for building ctype tables. Sets all elements from start to end
* inclusive to TRUE, and iterate this on a ranges array.
*/
static void loc_setrange_bool(sc_int start, sc_int end, sc_bool table[]) {
sc_int index_;
for (index_ = start; index_ <= end; index_++) {
assert(index_ > -1 && index_ < TABLE_SIZE);
table[index_] = TRUE;
}
}
static void loc_setranges_bool(const sc_int ranges[], sc_bool table[]) {
sc_int index_;
for (index_ = 0; ranges[index_] > -1; index_ += 2) {
assert(ranges[index_] <= ranges[index_ + 1]);
loc_setrange_bool(ranges[index_], ranges[index_ + 1], table);
}
}
/*
* loc_setrange_char()
* loc_setranges_char()
*
* Helpers for building ctype conversion tables. Sets all elements from start
* to end inclusive to their index value plus the given offset, and iterate
* this on a ranges array.
*/
static void loc_setrange_char(sc_int start, sc_int end, sc_int offset, sc_char table[]) {
sc_int index_;
for (index_ = start; index_ <= end; index_++) {
assert(index_ > -1 && index_ < TABLE_SIZE);
assert(index_ + offset > -1 && index_ + offset < TABLE_SIZE);
table[index_] = index_ + offset;
}
}
static void loc_setranges_char(const sc_int ranges[], sc_char table[]) {
sc_int index_;
for (index_ = 0; ranges[index_] > -1; index_ += 3) {
assert(ranges[index_] <= ranges[index_ + 1]);
loc_setrange_char(ranges[index_],
ranges[index_ + 1], ranges[index_ + 2], table);
}
}
/*
* A locale consists of a name, ranges for each table, and signatures for
* autodetection based on the game's compilation date. This is the static
* data portion of a locale.
*/
enum { RANGES_LENGTH = 32 };
enum { SIGNATURE_COUNT = 24, SIGNATURE_LENGTH = 3 };
struct sc_locale_t {
const sc_char *const name;
const sc_int isspace_ranges[RANGES_LENGTH];
const sc_int isdigit_ranges[RANGES_LENGTH];
const sc_int isalpha_ranges[RANGES_LENGTH];
const sc_int toupper_ranges[RANGES_LENGTH];
const sc_int tolower_ranges[RANGES_LENGTH];
const sc_byte signature[SIGNATURE_COUNT][SIGNATURE_LENGTH];
};
/*
* The locale table set is built from a locale using its ranges. There is one
* table for each function, and a pointer to the locale used to construct the
* table, for synchronization with changed locales. This is the dynamic data
* portion of a locale.
*/
struct sc_locale_table_t {
const sc_locale_t *locale;
sc_bool isspace[TABLE_SIZE];
sc_bool isdigit[TABLE_SIZE];
sc_bool isalpha[TABLE_SIZE];
sc_char toupper[TABLE_SIZE];
sc_char tolower[TABLE_SIZE];
};
/*
* Define a single static locale table set. This set re-initializes if it
* detects a locale change.
*/
static sc_locale_table_t loc_locale_tables = {nullptr, {0}, {0}, {0}, {0}, {0}};
/*
* loc_synchronize_tables()
* loc_check_tables_synchronized()
*
* Initialize tables for a locale. And compare the locale tables to a locale
* and if not for the same locale, (re-)initialize.
*/
static void loc_synchronize_tables(const sc_locale_t *locale) {
/* Clear all tables and the locale pointer. */
memset(&loc_locale_tables, 0, sizeof(loc_locale_tables));
/* Set ranges and attach the new locale. */
loc_setranges_bool(locale->isspace_ranges, loc_locale_tables.isspace);
loc_setranges_bool(locale->isdigit_ranges, loc_locale_tables.isdigit);
loc_setranges_bool(locale->isalpha_ranges, loc_locale_tables.isalpha);
loc_setranges_char(locale->toupper_ranges, loc_locale_tables.toupper);
loc_setranges_char(locale->tolower_ranges, loc_locale_tables.tolower);
loc_locale_tables.locale = locale;
}
static void loc_check_tables_synchronized(const sc_locale_t *locale) {
if (locale != loc_locale_tables.locale)
loc_synchronize_tables(locale);
}
/*
* Locale for Latin1. The signatures in this locale are null since it is the
* default locale; no matching required. Also, none may be practical, as this
* locale works for a large number of Western European languages (though in
* practice, it seems that only English and French Adrift Latin1 games exist).
*/
static const sc_locale_t LATIN1_LOCALE = {
"Latin1",
{9, 13, 32, 32, 160, 160, -1},
{48, 57, -1},
{
65, 90, 97, 122, 192, 214, 216, 246, 248, 255, 138, 138, 140, 140,
142, 142, 154, 154, 156, 156, 158, 158, 159, 159, -1
},
{
0, TABLE_SIZE - 1, 0, 97, 122, -32, 224, 246, -32, 248, 254, -32, 154, 154, -16,
156, 156, -16, 158, 158, -16, 255, 255, -96, -1
},
{
0, TABLE_SIZE - 1, 0, 65, 90, 32, 192, 214, 32, 216, 222, 32, 138, 138, 16,
140, 140, 16, 142, 142, 16, 159, 159, 96, -1
},
{{0}}
};
/*
* Locale for Cyrillic. The signatures in this locale are month names in
* both mixed case and lowercase Russian Cyrillic.
*/
static const sc_locale_t CYRILLIC_LOCALE = {
"Cyrillic",
{9, 13, 32, 32, 160, 160, -1},
{48, 57, -1},
{
65, 90, 97, 122, 168, 168, 184, 184, 175, 175, 191, 191, 178, 179,
192, 255, -1
},
{
0, TABLE_SIZE - 1, 0, 97, 122, -32, 184, 184, -16, 191, 191, -16, 179, 179, -1,
224, 255, -32, -1
},
{
0, TABLE_SIZE - 1, 0, 65, 90, 32, 168, 168, 16, 175, 175, 16, 178, 178, 1,
192, 223, 32, -1
},
{ {223, 237, 226}, {212, 229, 226}, {204, 224, 240}, {192, 239, 240},
{204, 224, 233}, {200, 254, 237}, {200, 254, 235}, {192, 226, 227},
{209, 229, 237}, {206, 234, 242}, {205, 238, 255}, {196, 229, 234},
{255, 237, 226}, {244, 229, 226}, {236, 224, 240}, {224, 239, 240},
{236, 224, 233}, {232, 254, 237}, {232, 254, 235}, {224, 226, 227},
{241, 229, 237}, {238, 234, 242}, {237, 238, 255}, {228, 229, 234}
}
};
/* List of pointers to supported and available locales, NULL terminated. */
static const sc_locale_t *const AVAILABLE_LOCALES[] = {
&LATIN1_LOCALE,
&CYRILLIC_LOCALE,
nullptr
};
/*
* The locale for the game, set below explicitly or on game startup, and
* a flag to note if it's been set explicitly, to prevent autodetection from
* overwriting a manual setting.
*/
static const sc_locale_t *loc_locale = &LATIN1_LOCALE;
static sc_bool loc_is_autodetect_enabled = TRUE;
/*
* loc_locate_signature_in_date()
*
* Checks the format of the input date to ensure it matches the format
* "dd [Mm]mm yyyy". Returns the address of the month part of the string, or
* NULL if it doesn't match the expected format.
*/
static const sc_char *loc_locate_signature_in_date(const sc_char *date) {
sc_int day, year, converted;
sc_char signature[SIGNATURE_LENGTH + 1];
/* Clear signature, and convert using a scanf format. */
memset(signature, 0, sizeof(signature));
converted = sscanf(date, "%2ld %3[^ 0-9] %4ld", &day, signature, &year);
/* Valid if we converted three values, and month has three characters. */
if (converted == 3 && strlen(signature) == SIGNATURE_LENGTH)
return strstr(date, signature);
else
return nullptr;
}
/*
* loc_compare_locale_signatures()
*
* Search a locale's signatures for a match with the signature passed in.
* Returns TRUE if a match found, FALSE otherwise. Uses memcmp rather than
* any strcasecmp() variant because the signatures are in the locale's
* codepage, but the locale is not yet (by definition) set.
*/
static sc_bool loc_compare_locale_signatures(const char *signature, const sc_locale_t *locale) {
sc_int index_;
sc_bool is_matched;
/* Compare signatures, stopping on the first match found. */
is_matched = FALSE;
for (index_ = 0; index_ < SIGNATURE_COUNT; index_++) {
if (memcmp(locale->signature[index_],
signature, sizeof(locale->signature[0])) == 0) {
is_matched = TRUE;
break;
}
}
return is_matched;
}
/*
* loc_find_matching_locale()
*
* Look at the incoming date, expected to be in the format "dd [Mm]mm yyyy",
* where "[Mm]mm" is a standard month abbreviation of the locale in which the
* Generator was run. Match this with locale signatures, and return the
* first locale that matches, or NULL if none match.
*/
static const sc_locale_t *loc_find_matching_locale(const sc_char *date,
const sc_locale_t *const *locales) {
const sc_char *signature;
const sc_locale_t *matched = nullptr;
/* Get the month part of date, and if valid, search locale signatures. */
signature = loc_locate_signature_in_date(date);
if (signature) {
const sc_locale_t *const *iterator;
/* Search for this signature in the locale's signatures. */
for (iterator = locales; *iterator; iterator++) {
if (loc_compare_locale_signatures(signature, *iterator)) {
matched = *iterator;
break;
}
}
}
/* Return the matching locale, NULL if none matched. */
return matched;
}
/*
* loc_detect_game_locale()
*
* Set an autodetected value for the locale based on looking at a game's
* compilation date.
*/
void loc_detect_game_locale(sc_prop_setref_t bundle) {
assert(bundle);
/* If an explicit locale has already been set, ignore the call. */
if (loc_is_autodetect_enabled) {
sc_vartype_t vt_key[1];
const sc_char *compile_date;
const sc_locale_t *matched;
/* Read the game's compilation date from the properties. */
vt_key[0].string = "CompileDate";
compile_date = prop_get_string(bundle, "S<-s", vt_key);
/* Search for a matching locale based on the game compilation date. */
matched = loc_find_matching_locale(compile_date, AVAILABLE_LOCALES);
/* If a locale matched, set the global locale to it. */
if (matched)
loc_locale = matched;
}
}
/*
* loc_ascii_tolower()
* loc_ascii_strncasecmp()
*
* The standard sc_strncasecmp() calls sc_tolower(), which is locale specific.
* This isn't a particular problem because it will set the Latin1 locale
* automatically before continuing. However, since locale names should always
* be in ascii anyway, it's slightly safer to just use an ascii-only version
* of this function.
*/
static sc_char loc_ascii_tolower(sc_char ch) {
return (ch >= 'A' && ch <= 'Z') ? ch - 'A' + 'a' : ch;
}
static sc_int loc_ascii_strncasecmp(const sc_char *s1, const sc_char *s2, sc_int n) {
sc_int index_;
for (index_ = 0; index_ < n; index_++) {
sc_int diff;
diff = loc_ascii_tolower(s1[index_]) - loc_ascii_tolower(s2[index_]);
if (diff < 0 || diff > 0)
return diff < 0 ? -1 : 1;
}
return 0;
}
/*
* loc_set_locale()
* loc_get_locale()
*
* Set a locale explicitly from the name passed in, returning TRUE if a locale
* matched the name. Get the current locale, which may be the default locale
* if none yet set.
*/
sc_bool loc_set_locale(const sc_char *name) {
const sc_locale_t *matched = nullptr;
const sc_locale_t *const *iterator;
assert(name);
/*
* Search locales for a matching name, abbreviated if necessary. Stop on
* the first match found.
*/
for (iterator = AVAILABLE_LOCALES; *iterator; iterator++) {
const sc_locale_t *const locale = *iterator;
if (loc_ascii_strncasecmp(name, locale->name, strlen(name)) == 0) {
matched = locale;
break;
}
}
/* If matched, set the global locale, and lock out future autodetection. */
if (matched) {
loc_locale = matched;
loc_is_autodetect_enabled = FALSE;
}
return matched ? TRUE : FALSE;
}
const sc_char *loc_get_locale(void) {
return loc_locale->name;
}
/*
* loc_debug_dump_new_line()
* loc_debug_dump_bool_table()
* loc_debug_dump_char_table()
* loc_debug_dump()
*
* Print out locale tables.
*/
static int loc_debug_dump_new_line(sc_int index_, sc_int count) {
return index_ < TABLE_SIZE - 1 && index_ % count == count - 1;
}
static void loc_debug_dump_bool_table(const sc_char *label, sc_int count, const sc_bool table[]) {
sc_int index_;
sc_trace("loc_locale_tables.%s = {\n ", label);
for (index_ = 0; index_ < TABLE_SIZE; index_++) {
sc_trace("%s%s", table[index_] ? "T" : "F",
loc_debug_dump_new_line(index_, count) ? "\n " : "");
}
sc_trace("\n}\n");
}
static void loc_debug_dump_char_table(const sc_char *label, sc_int count, const sc_char table[]) {
sc_int index_;
sc_trace("loc_locale_tables.%s = {\n ", label);
for (index_ = 0; index_ < TABLE_SIZE; index_++) {
sc_trace("%02lx%s", (sc_int)(sc_byte) table[index_],
loc_debug_dump_new_line(index_, count) ? "\n " : " ");
}
sc_trace("\n}\n");
}
void loc_debug_dump(void) {
sc_trace("Locale: debug dump follows...\n");
loc_check_tables_synchronized(loc_locale);
sc_trace("loc_locale_tables"
".locale->name = %s\n", loc_locale_tables.locale->name);
loc_debug_dump_bool_table("isspace", 64, loc_locale_tables.isspace);
loc_debug_dump_bool_table("isdigit", 64, loc_locale_tables.isdigit);
loc_debug_dump_bool_table("isalpha", 64, loc_locale_tables.isalpha);
loc_debug_dump_char_table("toupper", 16, loc_locale_tables.toupper);
loc_debug_dump_char_table("tolower", 16, loc_locale_tables.tolower);
}
/*
* loc_bool_template()
* loc_char_template()
*
* "Template" functions for locale variant ctype functions. Synchronize
* tables to the currently set locale, and return the value from the table.
*/
static sc_bool loc_bool_template(sc_char character, const sc_bool table[]) {
loc_check_tables_synchronized(loc_locale);
return table[(sc_byte) character];
}
static sc_char loc_char_template(sc_char character, const sc_char table[]) {
loc_check_tables_synchronized(loc_locale);
return table[(sc_byte) character];
}
/*
* sc_isspace()
* sc_isalpha()
* sc_isdigit()
* sc_tolower()
* sc_toupper()
*
* Public entry points into locale variant ctype functions.
*/
sc_bool sc_isspace(sc_char character) {
return loc_bool_template(character, loc_locale_tables.isspace);
}
sc_bool sc_isalpha(sc_char character) {
return loc_bool_template(character, loc_locale_tables.isalpha);
}
sc_bool sc_isdigit(sc_char character) {
return loc_bool_template(character, loc_locale_tables.isdigit);
}
sc_char sc_toupper(sc_char character) {
return loc_char_template(character, loc_locale_tables.toupper);
}
sc_char sc_tolower(sc_char character) {
return loc_char_template(character, loc_locale_tables.tolower);
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,570 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/serialization.h"
namespace Glk {
namespace Adrift {
/* Assorted definitions and constants. */
static const sc_uint MEMENTO_MAGIC = 0x9fd33d1d;
enum { MEMO_ALLOCATION_BLOCK = 32 };
/*
* Game memo structure, saves a serialized game. Allocation is preserved so
* that structures can be reused without requiring reallocation.
*/
struct sc_memo_s {
sc_byte *serialized_game;
sc_int allocation;
sc_int length;
};
typedef sc_memo_s sc_memo_t;
typedef sc_memo_t *sc_memoref_t;
/*
* Game command history structure, similar to a memo. Saves a player input
* command to create a history, reusing allocation where possible.
*/
struct sc_history_s {
sc_char *command;
sc_int sequence;
sc_int timestamp;
sc_int turns;
sc_int allocation;
sc_int length;
};
typedef sc_history_s sc_history_t;
typedef sc_history_t *sc_historyref_t;
/*
* Memo set structure. This reserves space for a predetermined number of
* serialized games, and an indicator cursor showing where additions are
* placed. The structure is a ring, with old elements being overwritten by
* newer arrivals. Also tacked onto this structure is a set of strings
* used to hold a command history that operates in a somewhat csh-like way,
* also a ring with limited capacity.
*/
enum { MEMO_UNDO_TABLE_SIZE = 16, MEMO_HISTORY_TABLE_SIZE = 64 };
struct sc_memo_set_s {
sc_uint magic;
sc_memo_t memo[MEMO_UNDO_TABLE_SIZE];
sc_int memo_cursor;
sc_history_t history[MEMO_HISTORY_TABLE_SIZE];
sc_int history_count;
sc_int current_history;
sc_bool is_at_start;
};
typedef sc_memo_set_s sc_memo_set_t;
/*
* memo_is_valid()
*
* Return TRUE if pointer is a valid memo set, FALSE otherwise.
*/
static sc_bool memo_is_valid(sc_memo_setref_t memento) {
return memento && memento->magic == MEMENTO_MAGIC;
}
/*
* memo_round_up()
*
* Round up an allocation in bytes to the next allocation block.
*/
static sc_int memo_round_up(sc_int allocation) {
sc_int extended;
extended = allocation + MEMO_ALLOCATION_BLOCK - 1;
return (extended / MEMO_ALLOCATION_BLOCK) * MEMO_ALLOCATION_BLOCK;
}
/*
* memo_create()
*
* Create and return a new set of memos.
*/
sc_memo_setref_t memo_create(void) {
sc_memo_setref_t memento;
/* Create and initialize a clean set of memos. */
memento = (sc_memo_setref_t)sc_malloc(sizeof(*memento));
memento->magic = MEMENTO_MAGIC;
memset(memento->memo, 0, sizeof(memento->memo));
memento->memo_cursor = 0;
memset(memento->history, 0, sizeof(memento->history));
memento->history_count = 0;
memento->current_history = 0;
memento->is_at_start = FALSE;
return memento;
}
/*
* memo_destroy()
*
* Destroy a memo set, and free its heap memory.
*/
void memo_destroy(sc_memo_setref_t memento) {
sc_int index_;
assert(memo_is_valid(memento));
/* Free the content of any used memo and any used history. */
for (index_ = 0; index_ < MEMO_UNDO_TABLE_SIZE; index_++) {
sc_memoref_t memo;
memo = memento->memo + index_;
sc_free(memo->serialized_game);
}
for (index_ = 0; index_ < MEMO_HISTORY_TABLE_SIZE; index_++) {
sc_historyref_t history;
history = memento->history + index_;
sc_free(history->command);
}
/* Poison and free the memo set itself. */
memset(memento, 0xaa, sizeof(*memento));
sc_free(memento);
}
/*
* memo_save_game_callback()
*
* Callback function for game serialization. Appends the data passed in to
* that already stored in the memo.
*/
static void memo_save_game_callback(void *opaque, const sc_byte *buffer, sc_int length) {
sc_memoref_t memo = (sc_memoref_t)opaque;
sc_int required;
assert(opaque && buffer && length > 0);
/*
* If necessary, increase the allocation for this memo. Serialized games
* tend to grow slightly as the game progresses, so we add a bit of extra
* to the actual allocation.
*/
required = memo->length + length;
if (required > memo->allocation) {
required = memo_round_up(required + 2 * MEMO_ALLOCATION_BLOCK);
memo->serialized_game = (sc_byte *)sc_realloc(memo->serialized_game, required);
memo->allocation = required;
}
/* Add this block of data to the buffer. */
memcpy(memo->serialized_game + memo->length, buffer, length);
memo->length += length;
}
/*
* memo_save_game()
*
* Store a game in the next memo slot.
*/
void memo_save_game(sc_memo_setref_t memento, sc_gameref_t game) {
sc_memoref_t memo;
assert(memo_is_valid(memento));
/*
* If the current slot is in use, we can re-use its allocation. Saved
* games will tend to be of roughly equal sizes, so it's worth doing.
*/
memo = memento->memo + memento->memo_cursor;
memo->length = 0;
/* Serialize the given game into this memo. */
SaveSerializer ser(game, memo_save_game_callback, memo);
ser.save();
/*
* If serialization worked (failure would be a surprise), advance the
* current memo cursor.
*/
if (memo->length > 0) {
memento->memo_cursor++;
memento->memo_cursor %= MEMO_UNDO_TABLE_SIZE;
} else
sc_error("memo_save_game: warning: game save failed\n");
}
/*
* memo_load_game_callback()
*
* Callback function for game deserialization. Returns data from the memo
* until it's drained.
*/
static sc_int memo_load_game_callback(void *opaque, sc_byte *buffer, sc_int length) {
sc_memoref_t memo = (sc_memoref_t)opaque;
sc_int bytes;
assert(opaque && buffer && length > 0);
/* Send back either all the bytes, or as many as the buffer allows. */
bytes = (memo->length < length) ? memo->length : length;
/* Read and remove the first block of data (or all if less than length). */
memcpy(buffer, memo->serialized_game, bytes);
memmove(memo->serialized_game,
memo->serialized_game + bytes, memo->length - bytes);
memo->length -= bytes;
/* Return the count of bytes placed in the buffer. */
return bytes;
}
/*
* memo_load_game()
*
* Restore a game from the last memo slot used, if possible.
*/
sc_bool memo_load_game(sc_memo_setref_t memento, sc_gameref_t game) {
sc_int cursor;
sc_memoref_t memo;
assert(memo_is_valid(memento));
/* Look back one from the current memo cursor. */
cursor = (memento->memo_cursor == 0)
? MEMO_UNDO_TABLE_SIZE - 1 : memento->memo_cursor - 1;
memo = memento->memo + cursor;
/* If this slot is not empty, restore the serialized game held in it. */
if (memo->length > 0) {
sc_bool status;
/*
* Deserialize the given game from this memo; failure would be somewhat
* of a surprise here.
*/
LoadSerializer ser(game, memo_load_game_callback, memo);
status = ser.load();
if (!status)
sc_error("memo_load_game: warning: game load failed\n");
/*
* This should have drained the memo of all data, but to be sure that
* there's no chance of trying to restore from this slot again, we'll
* force it anyway.
*/
if (memo->length > 0) {
sc_error("memo_load_game: warning: data remains after loading\n");
memo->length = 0;
}
/* Regress current memo, and return TRUE if we restored a memo. */
memento->memo_cursor = cursor;
return status;
}
/* There are no more memos to restore. */
return FALSE;
}
/*
* memo_is_load_available()
*
* Returns TRUE if a memo restore is likely to succeed if called, FALSE
* otherwise.
*/
sc_bool memo_is_load_available(sc_memo_setref_t memento) {
sc_int cursor;
sc_memoref_t memo;
assert(memo_is_valid(memento));
/*
* Look back one from the current memo cursor. Return TRUE if this slot
* contains a serialized game.
*/
cursor = (memento->memo_cursor == 0)
? MEMO_UNDO_TABLE_SIZE - 1 : memento->memo_cursor - 1;
memo = memento->memo + cursor;
return memo->length > 0;
}
/*
* memo_clear_games()
*
* Forget the memos of saved games.
*/
void memo_clear_games(sc_memo_setref_t memento) {
sc_int index_;
assert(memo_is_valid(memento));
/* Deallocate every entry. */
for (index_ = 0; index_ < MEMO_UNDO_TABLE_SIZE; index_++) {
sc_memoref_t memo;
memo = memento->memo + index_;
sc_free(memo->serialized_game);
}
/* Reset all entries and the cursor. */
memset(memento->memo, 0, sizeof(memento->memo));
memento->memo_cursor = 0;
}
/*
* memo_save_command()
*
* Store a player command in the command history, evicting any least recently
* used item if necessary.
*/
void memo_save_command(sc_memo_setref_t memento, const sc_char *command, sc_int timestamp, sc_int turns) {
sc_historyref_t history;
sc_int length;
assert(memo_is_valid(memento));
/* As with memos, reuse the allocation of the next slot if it has one. */
history = memento->history
+ memento->history_count % MEMO_HISTORY_TABLE_SIZE;
/*
* Resize the allocation for this slot if required. Strings tend to be
* short, so round up to a block to avoid too many reallocs.
*/
length = strlen(command) + 1;
if (history->allocation < length) {
sc_int required;
required = memo_round_up(length);
history->command = (sc_char *)sc_realloc(history->command, required);
history->allocation = required;
}
/* Save the string into this slot, and normalize it for neatness. */
Common::strcpy_s(history->command, history->allocation, command);
sc_normalize_string(history->command);
history->sequence = memento->history_count + 1;
history->timestamp = timestamp;
history->turns = turns;
history->length = length;
/* Increment the count of histories handled. */
memento->history_count++;
}
/*
* memo_unsave_command()
*
* Remove the last saved command. This is special functionality for the
* history lister. To keep synchronized with the runner main loop, it needs
* to "invent" a history item at the end of the list before listing, then
* remove it again as the main runner loop will add the real thing.
*/
void memo_unsave_command(sc_memo_setref_t memento) {
assert(memo_is_valid(memento));
/* Do nothing if for some reason there's no history to unsave. */
if (memento->history_count > 0) {
sc_historyref_t history;
/* Decrement the count of histories handled, erase the prior entry. */
memento->history_count--;
history = memento->history
+ memento->history_count % MEMO_HISTORY_TABLE_SIZE;
history->sequence = 0;
history->timestamp = 0;
history->turns = 0;
history->length = 0;
}
}
/*
* memo_get_command_count()
*
* Return a count of available saved commands.
*/
sc_int memo_get_command_count(sc_memo_setref_t memento) {
assert(memo_is_valid(memento));
/* Return the lesser of the history count and the history table size. */
if (memento->history_count < MEMO_HISTORY_TABLE_SIZE)
return memento->history_count;
else
return MEMO_HISTORY_TABLE_SIZE;
}
/*
* memo_first_command()
*
* Iterator rewind function, reset current location to the first command.
*/
void memo_first_command(sc_memo_setref_t memento) {
sc_int cursor;
sc_historyref_t history;
assert(memo_is_valid(memento));
/*
* If the buffer has cycled, we have the full complement of saved commands,
* so start iterating at the current cursor. Otherwise, start from index 0.
* Detect cycling by looking at the current slot; if it's filled, we've
* been here before. Set at_start flag to indicate the special case for
* circular buffers.
*/
cursor = memento->history_count % MEMO_HISTORY_TABLE_SIZE;
history = memento->history + cursor;
memento->current_history = (history->length > 0) ? cursor : 0;
memento->is_at_start = TRUE;
}
/*
* memo_next_command()
*
* Iterator function, return the next saved command and its sequence id
* starting at 1, and the timestamp and turns when the command was saved.
*/
void memo_next_command(sc_memo_setref_t memento, const sc_char **command,
sc_int *sequence, sc_int *timestamp, sc_int *turns) {
assert(memo_is_valid(memento));
/* If valid, return the current command and advance. */
if (memo_more_commands(memento)) {
sc_historyref_t history;
/* Note the current history, and advance its index. */
history = memento->history + memento->current_history;
memento->current_history++;
memento->current_history %= MEMO_HISTORY_TABLE_SIZE;
memento->is_at_start = FALSE;
/* Return details from the history noted above. */
*command = history->command;
*sequence = history->sequence;
*timestamp = history->timestamp;
*turns = history->turns;
} else {
/* Return NULL and zeroes if no more commands available. */
*command = nullptr;
*sequence = 0;
*timestamp = 0;
*turns = 0;
}
}
/*
* memo_more_commands()
*
* Iterator end function, returns TRUE if more commands are readable.
*/
sc_bool memo_more_commands(sc_memo_setref_t memento) {
sc_int cursor;
sc_historyref_t history;
assert(memo_is_valid(memento));
/* Get the current effective write position, and the current history. */
cursor = memento->history_count % MEMO_HISTORY_TABLE_SIZE;
history = memento->history + memento->current_history;
/*
* More data if the current history is behind the write position and is
* occupied, or if it matches and is occupied and we're at the start of
* iteration (circular buffer special case).
*/
if (memento->current_history == cursor)
return (memento->is_at_start) ? history->length > 0 : FALSE;
else
return history->length > 0;
}
/*
* memo_find_command()
*
* Find and return the command string for the given sequence number (-ve
* indicates an offset from the last defined), or NULL if not found.
*/
const sc_char *memo_find_command(sc_memo_setref_t memento, sc_int sequence) {
sc_int target, index_;
sc_historyref_t matched;
assert(memo_is_valid(memento));
/* Decide on a search target, depending on the sign of sequence. */
target = (sequence < 0) ? memento->history_count + sequence + 1 : sequence;
/*
* A backwards search starting at the write position would probably be more
* efficient here, but this is a rarely called function so we'll do it the
* simpler way.
*/
matched = nullptr;
for (index_ = 0; index_ < MEMO_HISTORY_TABLE_SIZE; index_++) {
sc_historyref_t history;
history = memento->history + index_;
if (history->sequence == target) {
matched = history;
break;
}
}
/*
* Return the command or NULL. If sequence passed in was zero, and the
* history was not full, this will still return NULL as it should, since
* this unused history's command found by the search above will be NULL.
*/
return matched ? matched->command : nullptr;
}
/*
* memo_clear_commands()
*
* Forget all saved commands.
*/
void memo_clear_commands(sc_memo_setref_t memento) {
sc_int index_;
assert(memo_is_valid(memento));
/* Deallocate every entry. */
for (index_ = 0; index_ < MEMO_HISTORY_TABLE_SIZE; index_++) {
sc_historyref_t history;
history = memento->history + index_;
sc_free(history->command);
}
/* Reset all entries, the count, and the iteration variables. */
memset(memento->history, 0, sizeof(memento->history));
memento->history_count = 0;
memento->current_history = 0;
memento->is_at_start = FALSE;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,581 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/scgamest.h"
namespace Glk {
namespace Adrift {
/* Trace flag, set before running. */
static sc_bool npc_trace = FALSE;
/*
* npc_in_room()
*
* Return TRUE if a given NPC is currently in a given room.
*/
sc_bool npc_in_room(sc_gameref_t game, sc_int npc, sc_int room) {
if (npc_trace) {
sc_trace("NPC: checking NPC %ld in room %ld (NPC is in %ld)\n",
npc, room, gs_npc_location(game, npc));
}
return gs_npc_location(game, npc) - 1 == room;
}
/*
* npc_count_in_room()
*
* Return the count of characters in the room, including the player.
*/
sc_int npc_count_in_room(sc_gameref_t game, sc_int room) {
sc_int count, npc;
/* Start with the player. */
count = gs_player_in_room(game, room) ? 1 : 0;
/* Total up other NPCs inhabiting the room. */
for (npc = 0; npc < gs_npc_count(game); npc++) {
if (gs_npc_location(game, npc) - 1 == room)
count++;
}
return count;
}
/*
* npc_start_npc_walk()
*
* Start the given walk for the given NPC.
*/
void npc_start_npc_walk(sc_gameref_t game, sc_int npc, sc_int walk) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[6];
sc_int movetime;
/* Retrieve movetime 0 for the NPC walk. */
vt_key[0].string = "NPCs";
vt_key[1].integer = npc;
vt_key[2].string = "Walks";
vt_key[3].integer = walk;
vt_key[4].string = "MoveTimes";
vt_key[5].integer = 0;
movetime = prop_get_integer(bundle, "I<-sisisi", vt_key) + 1;
/* Set up walkstep. */
gs_set_npc_walkstep(game, npc, walk, movetime);
}
/*
* npc_turn_update()
* npc_setup_initial()
*
* Set initial values for NPC states, and update on turns.
*/
void npc_turn_update(sc_gameref_t game) {
sc_int index_;
/* Set current values for NPC seen states. */
for (index_ = 0; index_ < gs_npc_count(game); index_++) {
if (!gs_npc_seen(game, index_)
&& npc_in_room(game, index_, gs_playerroom(game)))
gs_set_npc_seen(game, index_, TRUE);
}
}
void npc_setup_initial(sc_gameref_t game) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[5];
sc_int index_;
/* Start any walks that do not depend on a StartTask */
vt_key[0].string = "NPCs";
for (index_ = 0; index_ < gs_npc_count(game); index_++) {
sc_int walk;
/* Set up invariant parts of the properties key. */
vt_key[1].integer = index_;
vt_key[2].string = "Walks";
/* Process each walk, starting at the last and working backwards. */
for (walk = gs_npc_walkstep_count(game, index_) - 1; walk >= 0; walk--) {
sc_int starttask;
/* If StartTask is zero, start walk at game start. */
vt_key[3].integer = walk;
vt_key[4].string = "StartTask";
starttask = prop_get_integer(bundle, "I<-sisis", vt_key);
if (starttask == 0)
npc_start_npc_walk(game, index_, walk);
}
}
/* Update seen flags for initial states. */
npc_turn_update(game);
}
/*
* npc_room_in_roomgroup()
*
* Return TRUE if a given room is in a given group.
*/
static sc_bool npc_room_in_roomgroup(sc_gameref_t game, sc_int room, sc_int group) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[4];
sc_int member;
/* Check roomgroup membership. */
vt_key[0].string = "RoomGroups";
vt_key[1].integer = group;
vt_key[2].string = "List";
vt_key[3].integer = room;
member = prop_get_integer(bundle, "I<-sisi", vt_key);
return member != 0;
}
/* List of direction names, for printing entry/exit messages. */
static const sc_char *const DIRNAMES_4[] = {
"the north", "the east", "the south", "the west", "above", "below",
"inside", "outside",
nullptr
};
static const sc_char *const DIRNAMES_8[] = {
"the north", "the east", "the south", "the west", "above", "below",
"inside", "outside",
"the north-east", "the south-east", "the south-west", "the north-west",
nullptr
};
/*
* npc_random_adjacent_roomgroup_member()
*
* Return a random member of group adjacent to given room.
*/
static sc_int npc_random_adjacent_roomgroup_member(sc_gameref_t game, sc_int room, sc_int group) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[5];
sc_bool eightpointcompass;
sc_int roomlist[12], count, length, index_;
/* If given room is "hidden", return nothing. */
if (room == -1)
return -1;
/* How many exits to consider? */
vt_key[0].string = "Globals";
vt_key[1].string = "EightPointCompass";
eightpointcompass = prop_get_boolean(bundle, "B<-ss", vt_key);
if (eightpointcompass)
length = sizeof(DIRNAMES_8) / sizeof(DIRNAMES_8[0]) - 1;
else
length = sizeof(DIRNAMES_4) / sizeof(DIRNAMES_4[0]) - 1;
/* Poll adjacent rooms. */
vt_key[0].string = "Rooms";
vt_key[1].integer = room;
vt_key[2].string = "Exits";
count = 0;
for (index_ = 0; index_ < length; index_++) {
sc_int adjacent;
vt_key[3].integer = index_;
vt_key[4].string = "Dest";
adjacent = prop_get_child_count(bundle, "I<-sisis", vt_key);
if (adjacent > 0 && npc_room_in_roomgroup(game, adjacent - 1, group)) {
roomlist[count] = adjacent - 1;
count++;
}
}
/* Return a random adjacent room, or -1 if nothing is adjacent. */
return (count > 0) ? roomlist[sc_randomint(0, count - 1)] : -1;
}
/*
* npc_announce()
*
* Helper for npc_tick_npc().
*/
static void npc_announce(sc_gameref_t game, sc_int npc, sc_int room, sc_bool is_exit, sc_int npc_room) {
const sc_filterref_t filter = gs_get_filter(game);
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[5], vt_rvalue;
const sc_char *text, *name, *const *dirnames;
sc_int dir, dir_match;
sc_bool eightpointcompass, showenterexit, found;
/* If no announcement required, return immediately. */
vt_key[0].string = "NPCs";
vt_key[1].integer = npc;
vt_key[2].string = "ShowEnterExit";
showenterexit = prop_get_boolean(bundle, "B<-sis", vt_key);
if (!showenterexit)
return;
/* Get exit or entry text, and NPC name. */
vt_key[2].string = is_exit ? "ExitText" : "EnterText";
text = prop_get_string(bundle, "S<-sis", vt_key);
vt_key[2].string = "Name";
name = prop_get_string(bundle, "S<-sis", vt_key);
/* Decide on four or eight point compass names list. */
vt_key[0].string = "Globals";
vt_key[1].string = "EightPointCompass";
eightpointcompass = prop_get_boolean(bundle, "B<-ss", vt_key);
dirnames = eightpointcompass ? DIRNAMES_8 : DIRNAMES_4;
/* Set invariant key for room exit search. */
vt_key[0].string = "Rooms";
vt_key[1].integer = room;
vt_key[2].string = "Exits";
/* Find the room exit that matches the NPC room. */
found = FALSE;
dir_match = 0;
for (dir = 0; dirnames[dir]; dir++) {
vt_key[3].integer = dir;
if (prop_get(bundle, "I<-sisi", &vt_rvalue, vt_key)) {
sc_int dest;
/* Get room's direction destination, and compare. */
vt_key[4].string = "Dest";
dest = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if (dest == npc_room) {
dir_match = dir;
found = TRUE;
break;
}
}
}
/* Print NPC exit/entry details. */
pf_buffer_character(filter, '\n');
pf_new_sentence(filter);
pf_buffer_string(filter, name);
pf_buffer_character(filter, ' ');
pf_buffer_string(filter, text);
if (found) {
pf_buffer_string(filter, is_exit ? " to " : " from ");
pf_buffer_string(filter, dirnames[dir_match]);
}
pf_buffer_string(filter, ".\n");
/* Handle any associated resource. */
vt_key[0].string = "NPCs";
vt_key[1].integer = npc;
vt_key[2].string = "Res";
vt_key[3].integer = is_exit ? 3 : 2;
res_handle_resource(game, "sisi", vt_key);
}
/*
* npc_tick_npc_walk()
*
* Helper for npc_tick_npc().
*/
static void npc_tick_npc_walk(sc_gameref_t game, sc_int npc, sc_int walk) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[6];
sc_int roomgroups, movetimes, walkstep, start, dest, destnum;
sc_int chartask, objecttask;
if (npc_trace) {
sc_trace("NPC: ticking NPC %ld, walk %ld: step %ld\n",
npc, walk, gs_npc_walkstep(game, npc, walk));
}
/* Count roomgroups for later use. */
vt_key[0].string = "RoomGroups";
roomgroups = prop_get_child_count(bundle, "I<-s", vt_key);
/* Get move times array length. */
vt_key[0].string = "NPCs";
vt_key[1].integer = npc;
vt_key[2].string = "Walks";
vt_key[3].integer = walk;
vt_key[4].string = "MoveTimes";
movetimes = prop_get_child_count(bundle, "I<-sisis", vt_key);
/* Find a step to match the movetime. */
for (walkstep = 0; walkstep < movetimes - 1; walkstep++) {
sc_int movetime;
vt_key[5].integer = walkstep + 1;
movetime = prop_get_integer(bundle, "I<-sisisi", vt_key);
if (gs_npc_walkstep(game, npc, walk) > movetime)
break;
}
/* Sort out a destination. */
dest = start = gs_npc_location(game, npc) - 1;
vt_key[4].string = "Rooms";
vt_key[5].integer = walkstep;
destnum = prop_get_integer(bundle, "I<-sisisi", vt_key);
if (destnum == 0) /* Hidden. */
dest = -1;
else if (destnum == 1) /* Follow player. */
dest = gs_playerroom(game);
else if (destnum < gs_room_count(game) + 2)
dest = destnum - 2; /* To room. */
else if (destnum < gs_room_count(game) + 2 + roomgroups) {
sc_int initial;
/* For roomgroup walks, move only if walksteps has just refreshed. */
vt_key[4].string = "MoveTimes";
vt_key[5].integer = 0;
initial = prop_get_integer(bundle, "I<-sisisi", vt_key);
if (gs_npc_walkstep(game, npc, walk) == initial) {
sc_int group;
group = destnum - 2 - gs_room_count(game);
dest = npc_random_adjacent_roomgroup_member(game, start, group);
if (dest == -1)
dest = lib_random_roomgroup_member(game, group);
}
}
/* See if the NPC actually moved. */
if (start != dest) {
if (npc_trace)
sc_trace("NPC: walking NPC %ld moved to %ld\n", npc, dest);
/* Move NPC to destination. */
gs_set_npc_location(game, npc, dest + 1);
/* Announce NPC movements, and handle meeting characters and objects. */
if (gs_player_in_room(game, start))
npc_announce(game, npc, start, TRUE, dest);
else if (gs_player_in_room(game, dest))
npc_announce(game, npc, dest, FALSE, start);
}
/* Handle meeting characters and objects. */
vt_key[4].string = "CharTask";
chartask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if (chartask >= 0) {
sc_int meetchar;
/* Run meetchar task if appropriate. */
vt_key[4].string = "MeetChar";
meetchar = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if ((meetchar == -1 && gs_player_in_room(game, dest))
|| (meetchar >= 0 && dest == gs_npc_location(game, meetchar) - 1)) {
if (task_can_run_task(game, chartask))
task_run_task(game, chartask, TRUE);
}
}
vt_key[4].string = "ObjectTask";
objecttask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if (objecttask >= 0) {
sc_int meetobject;
/* Run meetobject task if appropriate. */
vt_key[4].string = "MeetObject";
meetobject = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if (meetobject >= 0 && obj_directly_in_room(game, meetobject, dest)) {
if (task_can_run_task(game, objecttask))
task_run_task(game, objecttask, TRUE);
}
}
}
/*
* npc_tick_npc()
*
* Move an NPC one step along current walk.
*/
static void npc_tick_npc(sc_gameref_t game, sc_int npc) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[6];
sc_int walk;
sc_bool has_moved = FALSE;
if (npc_trace)
sc_trace("NPC: ticking NPC %ld\n", npc);
/* Set up invariant key parts. */
vt_key[0].string = "NPCs";
vt_key[1].integer = npc;
vt_key[2].string = "Walks";
/* Find active walk, and if any found, make a step along it. */
for (walk = gs_npc_walkstep_count(game, npc) - 1; walk >= 0; walk--) {
sc_int starttask, stoppingtask;
/* Ignore finished walks. */
if (gs_npc_walkstep(game, npc, walk) <= 0)
continue;
/* Get start task. */
vt_key[3].integer = walk;
vt_key[4].string = "StartTask";
starttask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
/*
* Check that the starter is still complete, and if not, stop walk.
* Then keep on looking for an active walk.
*/
if (starttask >= 0 && !gs_task_done(game, starttask)) {
if (npc_trace)
sc_trace("NPC: stopping NPC %ld walk, start task undone\n", npc);
gs_set_npc_walkstep(game, npc, walk, -1);
continue;
}
/* Get stopping task. */
vt_key[4].string = "StoppingTask";
stoppingtask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
/*
* If any stopping task has completed, ignore this walk but don't
* actually finish it; more like an event pauser, then.
*
* TODO Is this right?
*/
if (stoppingtask >= 0 && gs_task_done(game, stoppingtask)) {
if (npc_trace)
sc_trace("NPC: ignoring NPC %ld walk, stop task done\n", npc);
continue;
}
/* Decrement steps. */
gs_decrement_npc_walkstep(game, npc, walk);
/* If we just hit a walk end, loop if called for. */
if (gs_npc_walkstep(game, npc, walk) == 0) {
sc_bool is_loop;
/* If walk is a loop, restart it. */
vt_key[4].string = "Loop";
is_loop = prop_get_boolean(bundle, "B<-sisis", vt_key);
if (is_loop) {
vt_key[4].string = "MoveTimes";
vt_key[5].integer = 0;
gs_set_npc_walkstep(game, npc, walk,
prop_get_integer(bundle,
"I<-sisisi", vt_key));
} else
gs_set_npc_walkstep(game, npc, walk, -1);
}
/*
* If not yet made a move on this walk, make one, and once made, make
* no other
*/
if (!has_moved) {
npc_tick_npc_walk(game, npc, walk);
has_moved = TRUE;
}
}
}
/*
* npc_tick_npcs()
*
* Move each NPC one step along current walk.
*/
void npc_tick_npcs(sc_gameref_t game) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
const sc_gameref_t undo = game->undo;
sc_int npc;
/*
* Compare the player location to last turn, to see if the player has moved
* this turn. If moved, look for meetings with NPCs.
*
* TODO Is this the right place to do this. After ticking each NPC, rather
* than before, seems more appropriate. But the messages come out in the
* right order by putting it here.
*
* Also, note that we take the shortcut of using the undo gamestate here,
* rather than properly recording the prior location of the player, and
* perhaps also NPCs, in the live gamestate.
*/
if (undo && !gs_player_in_room(undo, gs_playerroom(game))) {
for (npc = 0; npc < gs_npc_count(game); npc++) {
sc_int walk;
/* Iterate each NPC's walks. */
for (walk = gs_npc_walkstep_count(game, npc) - 1; walk >= 0; walk--) {
sc_vartype_t vt_key[5];
sc_int chartask;
/* Ignore finished walks. */
if (gs_npc_walkstep(game, npc, walk) <= 0)
continue;
/* Retrieve any character meeting task for the NPC. */
vt_key[0].string = "NPCs";
vt_key[1].integer = npc;
vt_key[2].string = "Walks";
vt_key[3].integer = walk;
vt_key[4].string = "CharTask";
chartask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if (chartask >= 0) {
sc_int meetchar;
/* Run meetchar task if appropriate. */
vt_key[4].string = "MeetChar";
meetchar = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if (meetchar == -1 &&
gs_player_in_room(game, gs_npc_location(game, npc) - 1)) {
if (task_can_run_task(game, chartask))
task_run_task(game, chartask, TRUE);
}
}
}
}
}
/* Iterate and tick each individual NPC. */
for (npc = 0; npc < gs_npc_count(game); npc++)
npc_tick_npc(game, npc);
}
/*
* npc_debug_trace()
*
* Set NPC tracing on/off.
*/
void npc_debug_trace(sc_bool flag) {
npc_trace = flag;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,928 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/scgamest.h"
namespace Glk {
namespace Adrift {
/* Assorted definitions and constants. */
static const sc_char NUL = '\0';
/* Trace flag, set before running. */
static sc_bool obj_trace = FALSE;
/*
* obj_is_static()
* obj_is_surface()
* obj_is_container()
*
* Convenience functions to return TRUE for given object attributes.
*/
sc_bool obj_is_static(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_bool bstatic;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Static";
bstatic = prop_get_boolean(bundle, "B<-sis", vt_key);
return bstatic;
}
sc_bool obj_is_container(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_bool is_container;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Container";
is_container = prop_get_boolean(bundle, "B<-sis", vt_key);
return is_container;
}
sc_bool obj_is_surface(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_bool is_surface;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Surface";
is_surface = prop_get_boolean(bundle, "B<-sis", vt_key);
return is_surface;
}
/*
* obj_container_object()
*
* Return the index of the n'th container object found.
*/
sc_int obj_container_object(sc_gameref_t game, sc_int n) {
sc_int object, count;
/* Progress through objects until n containers found. */
count = n;
for (object = 0; object < gs_object_count(game) && count >= 0; object++) {
if (obj_is_container(game, object))
count--;
}
return object - 1;
}
/*
* obj_container_index()
*
* Return index such that obj_container_object(index) == objnum.
*/
sc_int obj_container_index(sc_gameref_t game, sc_int objnum) {
sc_int object, count;
/* Progress through objects up to objnum. */
count = 0;
for (object = 0; object < objnum; object++) {
if (obj_is_container(game, object))
count++;
}
return count;
}
/*
* obj_surface_object()
*
* Return the index of the n'th surface object found.
*/
sc_int obj_surface_object(sc_gameref_t game, sc_int n) {
sc_int object, count;
/* Progress through objects until n surfaces found. */
count = n;
for (object = 0; object < gs_object_count(game) && count >= 0; object++) {
if (obj_is_surface(game, object))
count--;
}
return object - 1;
}
/*
* obj_surface_index()
*
* Return index such that obj_surface_object(index) == objnum.
*/
sc_int obj_surface_index(sc_gameref_t game, sc_int objnum) {
sc_int object, count;
/* Progress through objects up to objnum. */
count = 0;
for (object = 0; object < objnum; object++) {
if (obj_is_surface(game, object))
count++;
}
return count;
}
/*
* obj_stateful_object()
*
* Return the index of the n'th openable or statussed object found.
*/
sc_int obj_stateful_object(sc_gameref_t game, sc_int n) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_int object, count;
/* Progress through objects until n matches found. */
count = n;
for (object = 0; object < gs_object_count(game) && count >= 0; object++) {
sc_vartype_t vt_key[3];
sc_bool is_openable, is_statussed;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Openable";
is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
vt_key[2].string = "CurrentState";
is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
if (is_openable || is_statussed)
count--;
}
return object - 1;
}
/*
* obj_stateful_index()
*
* Return index such that obj_stateful_object(index) == objnum.
*/
sc_int obj_stateful_index(sc_gameref_t game, sc_int objnum) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_int object, count;
/* Progress through objects up to objnum. */
count = 0;
for (object = 0; object < objnum; object++) {
sc_vartype_t vt_key[3];
sc_bool is_openable, is_statussed;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Openable";
is_openable = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
vt_key[2].string = "CurrentState";
is_statussed = prop_get_integer(bundle, "I<-sis", vt_key) != 0;
if (is_openable || is_statussed)
count++;
}
return count;
}
/*
* obj_state_name()
*
* Return the string name of the state of a given stateful object. The
* string is malloc'ed, and needs to be freed by the caller. Returns NULL
* if no valid state string found.
*/
sc_char *obj_state_name(sc_gameref_t game, sc_int objnum) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
const sc_char *states;
sc_int length, state, count, first, last;
sc_char *string;
/* Get the list of state strings for the object. */
vt_key[0].string = "Objects";
vt_key[1].integer = objnum;
vt_key[2].string = "States";
states = prop_get_string(bundle, "S<-sis", vt_key);
/* Find the start of the element for the current state. */
state = gs_object_state(game, objnum);
length = strlen(states);
for (first = 0, count = state; first < length && count > 1; first++) {
if (states[first] == '|')
count--;
}
if (count != 1)
return nullptr;
/* Find the end of the state string. */
for (last = first; last < length; last++) {
if (states[last] == '|')
break;
}
/* Allocate and take a copy of the state string. */
string = (sc_char *)sc_malloc(last - first + 1);
memcpy(string, states + first, last - first);
string[last - first] = NUL;
return string;
}
/*
* obj_dynamic_object()
*
* Return the index of the n'th non-static object found.
*/
sc_int obj_dynamic_object(sc_gameref_t game, sc_int n) {
sc_int object, count;
/* Progress through objects until n matches found. */
count = n;
for (object = 0; object < gs_object_count(game) && count >= 0; object++) {
if (!obj_is_static(game, object))
count--;
}
return object - 1;
}
/*
* obj_wearable_object()
*
* Return the index of the n'th wearable object found.
*/
sc_int obj_wearable_object(sc_gameref_t game, sc_int n) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_int object, count;
/* Progress through objects until n matches found. */
count = n;
for (object = 0; object < gs_object_count(game) && count >= 0; object++) {
if (!obj_is_static(game, object)) {
sc_vartype_t vt_key[3];
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Wearable";
if (prop_get_boolean(bundle, "B<-sis", vt_key))
count--;
}
}
return object - 1;
}
/*
* Size is held in the ten's digit of SizeWeight, and weight in the units.
* Size and weight are multipliers -- the relative size and weight of objects
* rises by a factor of three for each incremental multiplier. These factors
* are also used for the maximum size of object that can fit in a container,
* and the number of these that fit.
*/
enum {
OBJ_DIMENSION_DIVISOR = 10,
OBJ_DIMENSION_MULTIPLE = 3
};
/*
* obj_get_size()
* obj_get_weight()
*
* Return the relative size and weight of an object. For containers, the
* weight includes the weight of each contained object.
*
* TODO It's possible to have static objects in the player inventory, moved
* by events -- how should these be handled, as they have no SizeWeight?
*/
sc_int obj_get_size(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int size, count;
/* TODO For now, give static objects no size. */
if (obj_is_static(game, object))
return 0;
/* Size is the 'tens' component of SizeWeight. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "SizeWeight";
count = prop_get_integer(bundle, "I<-sis", vt_key) / OBJ_DIMENSION_DIVISOR;
/*
* Calculate base object size. Unlike weights below, we take this as simply
* being the maximum size; that is, when a container carries other objects
* its weight increases by the sum of objects carried, but its size remains
* constant.
*/
size = 1;
for (; count > 0; count--)
size *= OBJ_DIMENSION_MULTIPLE;
if (obj_trace)
sc_trace("Object: object %ld is size %ld\n", object, size);
/* Return total size. */
return size;
}
sc_int obj_get_weight(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int weight, count;
/* TODO For now, give static objects no weight. */
if (obj_is_static(game, object))
return 0;
/* Weight is the 'units' component of SizeWeight. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "SizeWeight";
count = prop_get_integer(bundle, "I<-sis", vt_key) % OBJ_DIMENSION_DIVISOR;
/* Calculate base object weight. */
weight = 1;
for (; count > 0; count--)
weight *= OBJ_DIMENSION_MULTIPLE;
/* If this is a container or a surface, add weights of parented objects. */
if (obj_is_container(game, object) || obj_is_surface(game, object)) {
sc_int other;
/* Find and add contained or surface objects. */
for (other = 0; other < gs_object_count(game); other++) {
if ((gs_object_position(game, other) == OBJ_IN_OBJECT
|| gs_object_position(game, other) == OBJ_ON_OBJECT)
&& gs_object_parent(game, other) == object) {
weight += obj_get_weight(game, other);
}
}
}
if (obj_trace)
sc_trace("Object: object %ld is weight %ld\n", object, weight);
/* Return total weight. */
return weight;
}
/*
* obj_convert_player_limit()
* obj_get_player_size_limit()
* obj_get_player_weight_limit()
*
* Return the limits set on the sizes and weights a player can handle. Not
* really object-related except that they deal with sizing multiples.
*/
static sc_int obj_convert_player_limit(sc_int value) {
sc_int retval, index_;
/* 'Tens' of value multiplied by 3 to the power 'units' of value. */
retval = value / OBJ_DIMENSION_DIVISOR;
for (index_ = 0; index_ < value % OBJ_DIMENSION_DIVISOR; index_++)
retval *= OBJ_DIMENSION_MULTIPLE;
return retval;
}
sc_int obj_get_player_size_limit(sc_gameref_t game) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[2];
sc_int max_size;
vt_key[0].string = "Globals";
vt_key[1].string = "MaxSize";
max_size = prop_get_integer(bundle, "I<-ss", vt_key);
return obj_convert_player_limit(max_size);
}
sc_int obj_get_player_weight_limit(sc_gameref_t game) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[2];
sc_int max_weight;
vt_key[0].string = "Globals";
vt_key[1].string = "MaxWt";
max_weight = prop_get_integer(bundle, "I<-ss", vt_key);
return obj_convert_player_limit(max_weight);
}
/*
* obj_get_container_maxsize()
* obj_get_container_capacity()
*
* Return the maximum size of an object that can be placed in a container,
* and the number that will fit.
*/
sc_int obj_get_container_maxsize(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int maxsize, count;
/* Maxsize is found from the 'units' component of Capacity. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Capacity";
count = prop_get_integer(bundle, "I<-sis", vt_key) % OBJ_DIMENSION_DIVISOR;
/* Calculate and return maximum size. */
maxsize = 1;
for (; count > 0; count--)
maxsize *= OBJ_DIMENSION_MULTIPLE;
if (obj_trace)
sc_trace("Object: object %ld has max size %ld\n", object, maxsize);
return maxsize;
}
sc_int obj_get_container_capacity(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int capacity;
/* The count of objects is in the 'tens' component of Capacity. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Capacity";
capacity = prop_get_integer(bundle, "I<-sis", vt_key)
/ OBJ_DIMENSION_DIVISOR;
if (obj_trace)
sc_trace("Object: object %ld has capacity %ld\n", object, capacity);
return capacity;
}
/* Sit/lie bit mask enumerations. */
enum {
OBJ_STANDABLE_MASK = 1 << 0,
OBJ_LIEABLE_MASK = 1 << 1
};
/*
* obj_standable_object()
*
* Return the index of the n'th standable object found.
*/
sc_int obj_standable_object(sc_gameref_t game, sc_int n) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_int object, count;
/* Progress through objects until n standable found. */
count = n;
for (object = 0; object < gs_object_count(game) && count >= 0; object++) {
sc_vartype_t vt_key[3];
sc_int sit_lie_flags;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "SitLie";
sit_lie_flags = prop_get_integer(bundle, "I<-sis", vt_key);
if (sit_lie_flags & OBJ_STANDABLE_MASK)
count--;
}
return object - 1;
}
/*
* obj_lieable_object()
*
* Return the index of the n'th lieable object found.
*/
sc_int obj_lieable_object(sc_gameref_t game, sc_int n) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_int object, count;
/* Progress through objects until n lieable found. */
count = n;
for (object = 0; object < gs_object_count(game) && count >= 0; object++) {
sc_vartype_t vt_key[3];
sc_int sit_lie_flags;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "SitLie";
sit_lie_flags = prop_get_integer(bundle, "I<-sis", vt_key);
if (sit_lie_flags & OBJ_LIEABLE_MASK)
count--;
}
return object - 1;
}
/*
* obj_appears_plural()
*
* Return TRUE if the object appears to be plural. Adrift makes a guess at
* this to produce "... is on.." or "... are on...". It's not clear how it
* does it, but it looks something like: singular if prefix is "a" or "an"
* or ""; plural if prefix is "the" or "some" and short name ends with 's'
* that is not preceded by 'u'.
*/
sc_bool obj_appears_plural(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
const sc_char *prefix, *name;
/* Check prefix for "a", "an", or empty. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Prefix";
prefix = prop_get_string(bundle, "S<-sis", vt_key);
if (!(sc_strempty(prefix)
|| sc_compare_word(prefix, "a", 1)
|| sc_compare_word(prefix, "an", 2))) {
sc_int length;
/* Check name for ending in 's', but not 'us'. */
vt_key[2].string = "Short";
name = prop_get_string(bundle, "S<-sis", vt_key);
length = strlen(name);
if (!sc_strempty(name)
&& sc_tolower(name[length - 1]) == 's'
&& (length < 2 || sc_tolower(name[length - 2]) != 'u'))
return TRUE;
}
/* Doesn't look plural. */
return FALSE;
}
/*
* obj_directly_in_room_internal()
* obj_directly_in_room()
*
* Return TRUE if a given object is currently on the floor of a given room.
*/
static sc_bool obj_directly_in_room_internal(sc_gameref_t game, sc_int object, sc_int room) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
/* See if the object is static or dynamic. */
if (obj_is_static(game, object)) {
sc_vartype_t vt_key[5];
sc_int type;
/* Static object moved to player or room by event? */
if (!gs_object_static_unmoved(game, object)) {
if (gs_object_position(game, object) == OBJ_HELD_PLAYER)
return FALSE;
else
return gs_object_position(game, object) - 1 == room;
}
/* Check and return the room list for the object. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Where";
vt_key[3].string = "Type";
type = prop_get_integer(bundle, "I<-siss", vt_key);
switch (type) {
case ROOMLIST_ALL_ROOMS:
return TRUE;
case ROOMLIST_NO_ROOMS:
case ROOMLIST_NPC_PART:
return FALSE;
case ROOMLIST_ONE_ROOM:
vt_key[3].string = "Room";
return prop_get_integer(bundle, "I<-siss", vt_key) == room + 1;
case ROOMLIST_SOME_ROOMS:
vt_key[3].string = "Rooms";
vt_key[4].integer = room + 1;
return prop_get_boolean(bundle, "B<-sissi", vt_key);
default:
sc_fatal("obj_directly_in_room_internal:"
" invalid type, %ld\n", type);
return FALSE;
}
} else
return gs_object_position(game, object) == room + 1;
}
sc_bool obj_directly_in_room(sc_gameref_t game, sc_int object, sc_int room) {
sc_bool result;
/* Check, trace result, and return. */
result = obj_directly_in_room_internal(game, object, room);
if (obj_trace) {
sc_trace("Object: checking for object %ld directly in room %ld, %s\n",
object, room, result ? "true" : "false");
}
return result;
}
/*
* obj_indirectly_in_room_internal()
* obj_indirectly_in_room()
*
* Return TRUE if a given object is currently in a given room, either
* directly, on an object indirectly, in an open object indirectly, or
* carried by an NPC in the room.
*/
static sc_bool obj_indirectly_in_room_internal(sc_gameref_t game, sc_int object, sc_int room) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
/* See if the object is static or dynamic. */
if (obj_is_static(game, object)) {
sc_vartype_t vt_key[5];
sc_int type;
/* Static object moved to player or room by event? */
if (!gs_object_static_unmoved(game, object)) {
if (gs_object_position(game, object) == OBJ_HELD_PLAYER)
return gs_player_in_room(game, room);
else
return gs_object_position(game, object) - 1 == room;
}
/* Check and return the room list for the object. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Where";
vt_key[3].string = "Type";
type = prop_get_integer(bundle, "I<-siss", vt_key);
switch (type) {
case ROOMLIST_ALL_ROOMS:
return TRUE;
case ROOMLIST_NO_ROOMS:
return FALSE;
case ROOMLIST_ONE_ROOM:
vt_key[3].string = "Room";
return prop_get_integer(bundle, "I<-siss", vt_key) == room + 1;
case ROOMLIST_SOME_ROOMS:
vt_key[3].string = "Rooms";
vt_key[4].integer = room + 1;
return prop_get_boolean(bundle, "B<-sissi", vt_key);
case ROOMLIST_NPC_PART: {
sc_int npc;
vt_key[2].string = "Parent";
npc = prop_get_integer(bundle, "I<-sis", vt_key);
if (npc == 0)
return gs_player_in_room(game, room);
else
return npc_in_room(game, npc - 1, room);
}
default:
sc_fatal("obj_indirectly_in_room_internal:"
" invalid type, %ld\n", type);
return FALSE;
}
} else {
sc_int parent, position;
/* Get dynamic object's parent and position. */
parent = gs_object_parent(game, object);
position = gs_object_position(game, object);
/* Decide depending on positioning. */
switch (position) {
case OBJ_HIDDEN: /* Hidden. */
return FALSE;
case OBJ_HELD_PLAYER: /* Held by player. */
case OBJ_WORN_PLAYER: /* Worn by player. */
return gs_player_in_room(game, room);
case OBJ_HELD_NPC: /* Held by NPC. */
case OBJ_WORN_NPC: /* Worn by NPC. */
return npc_in_room(game, parent, room);
case OBJ_IN_OBJECT: { /* In another object. */
sc_int openness;
openness = gs_object_openness(game, parent);
switch (openness) {
case OBJ_WONTCLOSE:
case OBJ_OPEN:
return obj_indirectly_in_room(game, parent, room);
default:
return FALSE;
}
}
case OBJ_ON_OBJECT: /* On another object. */
return obj_indirectly_in_room(game, parent, room);
default: /* Within a room. */
if (position > gs_room_count(game) + 1) {
sc_error("sc_object_indirectly_in_room:"
" position out of bounds, %ld\n", position);
}
return position - 1 == room;
}
}
}
sc_bool obj_indirectly_in_room(sc_gameref_t game, sc_int object, sc_int Room) {
sc_bool result;
/* Check, trace result, and return. */
result = obj_indirectly_in_room_internal(game, object, Room);
if (obj_trace) {
sc_trace("Object: checking for object %ld indirectly in room %ld, %s\n",
object, Room, result ? "true" : "false");
}
return result;
}
/*
* obj_indirectly_held_by_player_internal()
* obj_indirectly_held_by_player()
*
* Return TRUE if a given object is currently held by the player, either
* directly, on an object indirectly, or in an open object indirectly.
*/
static sc_bool obj_indirectly_held_by_player_internal(sc_gameref_t game, sc_int object) {
/* See if the object is static or dynamic. */
if (obj_is_static(game, object)) {
/* Static object moved to player or room by event? */
if (!gs_object_static_unmoved(game, object)) {
if (gs_object_position(game, object) == OBJ_HELD_PLAYER)
return TRUE;
else
return FALSE;
}
/* An unmoved static object is not held by the player. */
return FALSE;
} else {
sc_int parent, position;
/* Get dynamic object's parent and position. */
parent = gs_object_parent(game, object);
position = gs_object_position(game, object);
/* Decide depending on positioning. */
switch (position) {
case OBJ_HIDDEN: /* Hidden. */
return FALSE;
case OBJ_HELD_PLAYER: /* Held by player. */
case OBJ_WORN_PLAYER: /* Worn by player. */
return TRUE;
case OBJ_HELD_NPC: /* Held by NPC. */
case OBJ_WORN_NPC: /* Worn by NPC. */
return FALSE;
case OBJ_IN_OBJECT: { /* In another object. */
sc_int openness;
openness = gs_object_openness(game, parent);
switch (openness) {
case OBJ_WONTCLOSE:
case OBJ_OPEN:
return obj_indirectly_held_by_player(game, parent);
default:
return FALSE;
}
}
case OBJ_ON_OBJECT: /* On another object. */
return obj_indirectly_held_by_player(game, parent);
default: /* Within a room. */
return FALSE;
}
}
}
sc_bool obj_indirectly_held_by_player(sc_gameref_t game, sc_int object) {
sc_bool result;
/* Check, trace result, and return. */
result = obj_indirectly_held_by_player_internal(game, object);
if (obj_trace) {
sc_trace("Object: checking for object %ld indirectly"
" held by player, %s\n", object, result ? "true" : "false");
}
return result;
}
/*
* sc_obj_shows_initial_description()
*
* Return TRUE if this object should be listed as room content.
*/
sc_bool obj_shows_initial_description(sc_gameref_t game, sc_int object) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[3];
sc_int onlywhennotmoved;
/* Get only when moved property. */
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "OnlyWhenNotMoved";
onlywhennotmoved = prop_get_integer(bundle, "I<-sis", vt_key);
/* Combine this with game in mysterious ways. */
switch (onlywhennotmoved) {
case 0:
return TRUE;
case 1:
return gs_object_unmoved(game, object);
case 2: {
sc_int initialposition;
if (gs_object_unmoved(game, object))
return TRUE;
vt_key[2].string = "InitialPosition";
initialposition = prop_get_integer(bundle, "I<-sis", vt_key) - 3;
return gs_object_position(game, object) == initialposition;
}
default:
break;
}
/* What you talkin' 'bout, Willis? */
return FALSE;
}
/*
* obj_turn_update()
* obj_setup_initial()
*
* Set initial values for object states, and update after a turn.
*/
void obj_turn_update(sc_gameref_t game) {
sc_int index_;
/* Update object seen flag to current state. */
for (index_ = 0; index_ < gs_object_count(game); index_++) {
if (!gs_object_seen(game, index_)
&& obj_indirectly_in_room(game, index_, gs_playerroom(game)))
gs_set_object_seen(game, index_, TRUE);
}
}
void obj_setup_initial(sc_gameref_t game) {
/* Set initial seen states for objects. */
obj_turn_update(game);
}
/*
* obj_debug_trace()
*
* Set object tracing on/off.
*/
void obj_debug_trace(sc_bool flag) {
obj_trace = flag;
}
} // End of namespace Adrift
} // End of namespace Glk

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,966 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
namespace Glk {
namespace Adrift {
/* Assorted definitions and constants. */
static const sc_uint PROP_MAGIC = 0x7927b2e0;
enum {
PROP_GROW_INCREMENT = 32,
MAX_INTEGER_KEY = 65535,
NODE_POOL_CAPACITY = 512
};
static const sc_char NUL = '\0';
/* Properties trace flag. */
static sc_bool prop_trace = FALSE;
/*
* Property tree node definition, uses a child list representation for
* fast lookup on indexed nodes. Name is a variable type, as is property,
* which is also overloaded to contain the child count for internal nodes.
*/
struct sc_prop_node_s {
sc_vartype_t name;
sc_vartype_t property;
struct sc_prop_node_s **child_list;
};
typedef sc_prop_node_s sc_prop_node_t;
typedef sc_prop_node_t *sc_prop_noderef_t;
/*
* Properties set structure. This is a set of properties, on which the
* properties functions operate (a properties "object"). Node string
* names are held in a dictionary to help save space. To avoid excessive
* malloc'ing of nodes, new nodes are preallocated in pools.
*/
struct sc_prop_set_s {
sc_uint magic;
sc_int dictionary_length;
sc_char **dictionary;
sc_int node_pools_length;
sc_prop_noderef_t *node_pools;
sc_int node_count;
sc_int orphans_length;
void **orphans;
sc_bool is_readonly;
sc_prop_noderef_t root_node;
sc_tafref_t taf;
};
typedef sc_prop_set_s sc_prop_set_t;
/*
* prop_is_valid()
*
* Return TRUE if pointer is a valid properties set, FALSE otherwise.
*/
static sc_bool prop_is_valid(sc_prop_setref_t bundle) {
return bundle && bundle->magic == PROP_MAGIC;
}
/*
* prop_round_up()
*
* Round up a count of elements to the next block of grow increments.
*/
static sc_int prop_round_up(sc_int elements) {
sc_int extended;
extended = elements + PROP_GROW_INCREMENT - 1;
return (extended / PROP_GROW_INCREMENT) * PROP_GROW_INCREMENT;
}
/*
* prop_ensure_capacity()
*
* Ensure that capacity exists in a growable array for a given number of
* elements, growing if necessary.
*
* Some libc's allocate generously on realloc(), and some not. Those that
* don't will thrash badly if we realloc() on each grow, so here we try to
* realloc() in blocks of elements, and thus need to realloc() much less
* frequently.
*/
static void *prop_ensure_capacity(void *array, sc_int old_size, sc_int new_size, sc_int element_size) {
sc_int current, required;
/*
* See if there's any resize necessary, that is, does the new size round up
* to a larger number of elements than the old size.
*/
current = prop_round_up(old_size);
required = prop_round_up(new_size);
if (required > current) {
sc_byte *new_array, *start_clearing;
/* Grow array to the required size, and zero new elements. */
new_array = (sc_byte *)sc_realloc(array, required * element_size);
start_clearing = new_array + current * element_size;
memset(start_clearing, 0, (required - current) * element_size);
return new_array;
}
/* No resize necessary. */
return array;
}
/*
* prop_trim_capacity()
*
* Trim an array allocation back to the bare minimum required. This will
* "unblock" the allocations of prop_ensure_capacity(). Once trimmed,
* the array cannot ever be grown safely again.
*/
static void *prop_trim_capacity(void *array, sc_int size, sc_int element_size) {
if (prop_round_up(size) > size)
return sc_realloc(array, size * element_size);
else
return array;
}
/*
* prop_compare()
*
* String comparison routine for sorting and searching dictionary strings.
* The function has return type "int" to match the libc implementations of
* bsearch() and qsort().
*/
static int prop_compare(const void *string1, const void *string2) {
return strcmp(*(sc_char * const *) string1, *(sc_char * const *) string2);
}
/*
* prop_dictionary_lookup()
*
* Find a string in the dictionary. If the string is not present, the
* function will add it. The function returns the string's address, if
* either added or already present. Any new dictionary entry will
* contain a malloced copy of the string passed in.
*/
static const sc_char *prop_dictionary_lookup(sc_prop_setref_t bundle, const sc_char *string) {
sc_char *dict_string;
/*
* Search the existing dictionary for the string. Although not GNU libc,
* some libc's loop or crash when given a list of zero length, so we need to
* trap that here.
*/
if (bundle->dictionary_length > 0) {
const sc_char *const *dict_search;
dict_search = (const sc_char * const *)bsearch(&string, bundle->dictionary,
bundle->dictionary_length,
sizeof(bundle->dictionary[0]), prop_compare);
if (dict_search)
return *dict_search;
}
/* Not found, so copy the string for dictionary insertion. */
size_t ln = strlen(string) + 1;
dict_string = (sc_char *)sc_malloc(ln);
Common::strcpy_s(dict_string, ln, string);
/* Extend the dictionary if necessary. */
bundle->dictionary = (sc_char **)prop_ensure_capacity(bundle->dictionary,
bundle->dictionary_length,
bundle->dictionary_length + 1,
sizeof(bundle->dictionary[0]));
/* Add the new entry to the end of the dictionary array, and sort. */
bundle->dictionary[bundle->dictionary_length++] = dict_string;
qsort(bundle->dictionary,
bundle->dictionary_length,
sizeof(bundle->dictionary[0]), prop_compare);
/* Return the address of the new string. */
return dict_string;
}
/*
* prop_new_node()
*
* Return the address of the next free properties node from the node pool.
* Using a pool gives a performance boost; the number of properties nodes
* for even a small game is large, and preallocating pools avoids excessive
* malloc's of small individual nodes.
*/
static sc_prop_noderef_t prop_new_node(sc_prop_setref_t bundle) {
sc_int node_index;
sc_prop_noderef_t node;
/* See if we need to create a new node pool. */
node_index = bundle->node_count % NODE_POOL_CAPACITY;
if (node_index == 0) {
sc_int required;
/* Extend the node pools array if necessary. */
bundle->node_pools = (sc_prop_noderef_t *)prop_ensure_capacity(bundle->node_pools,
bundle->node_pools_length,
bundle->node_pools_length + 1,
sizeof(bundle->
node_pools[0]));
/* Create a new node pool, and increment the length. */
required = NODE_POOL_CAPACITY * sizeof(*bundle->node_pools[0]);
bundle->node_pools[bundle->node_pools_length] = (sc_prop_noderef_t)sc_malloc(required);
bundle->node_pools_length++;
}
/* Find the next node address, and increment the node counter. */
node = bundle->node_pools[bundle->node_pools_length - 1] + node_index;
bundle->node_count++;
/* Return the new node. */
return node;
}
/*
* prop_find_child()
*
* Find a child node of the given parent whose name matches that passed in.
*/
static sc_prop_noderef_t prop_find_child(sc_prop_noderef_t parent, sc_int type, sc_vartype_t name) {
/* See if this node has any children. */
if (parent->child_list) {
sc_int index_;
sc_prop_noderef_t child = nullptr;
/* Do the lookup based on name type. */
switch (type) {
case PROP_KEY_INTEGER:
/*
* As with adding a child below, here we'll range-check an integer
* key just to make sure nobody has any unreal expectations of us.
*/
if (name.integer < 0)
sc_fatal("prop_find_child: integer key cannot be negative\n");
else if (name.integer > MAX_INTEGER_KEY)
sc_fatal("prop_find_child: integer key is too large\n");
/*
* For integer lookups, return the child at the indexed offset
* directly, provided it exists.
*/
if (name.integer >= 0 && name.integer < parent->property.integer) {
child = parent->child_list[name.integer];
return child;
}
break;
case PROP_KEY_STRING:
/* Scan children for a string name match. */
for (index_ = 0; index_ < parent->property.integer; index_++) {
child = parent->child_list[index_];
if (strcmp(child->name.string, name.string) == 0)
break;
}
/* Return child if we found a match. */
if (index_ < parent->property.integer) {
/*
* Before returning the child, try to improve future scans by
* moving the matched entry to index_ 0 -- this gives a key set
* sorted by recent usage, helpful as the same string key is
* used repeatedly in loops.
*/
if (index_ > 0) {
memmove(parent->child_list + 1,
parent->child_list, index_ * sizeof(child));
parent->child_list[0] = child;
}
return child;
}
break;
default:
sc_fatal("prop_find_child: invalid key type\n");
}
}
/* No matching child found. */
return nullptr;
}
/*
* prop_add_child()
*
* Add a new child node to the given parent. Return its reference. Set
* needs to be passed so that string names can be added to the dictionary.
*/
static sc_prop_noderef_t prop_add_child(sc_prop_noderef_t parent, sc_int type,
sc_vartype_t name, sc_prop_setref_t bundle) {
sc_prop_noderef_t child;
/* Not possible if growable allocations have been trimmed. */
if (bundle->is_readonly)
sc_fatal("prop_add_child: can't add to readonly properties\n");
/* Create the new node. */
child = prop_new_node(bundle);
switch (type) {
case PROP_KEY_INTEGER:
child->name.integer = name.integer;
break;
case PROP_KEY_STRING:
child->name.string = prop_dictionary_lookup(bundle, name.string);
break;
default:
sc_fatal("prop_add_child: invalid key type\n");
}
/* Initialize property and child list to visible nulls. */
child->property.voidp = nullptr;
child->child_list = nullptr;
/* Make a brief check for obvious overwrites. */
if (!parent->child_list && parent->property.voidp)
sc_error("prop_add_child: node overwritten, probable data loss\n");
/* Add the child to the parent, position dependent on key type. */
switch (type) {
case PROP_KEY_INTEGER:
/*
* Range check on integer keys, must be >= 0 for direct indexing to work,
* and we'll also apply a reasonableness constraint, to try to catch
* errors where string pointers are passed in as integers, which would
* otherwise lead to some extreme malloc() attempts.
*/
if (name.integer < 0)
sc_fatal("prop_add_child: integer key cannot be negative\n");
else if (name.integer > MAX_INTEGER_KEY)
sc_fatal("prop_add_child: integer key is too large\n");
/* Resize the parent's child list if necessary. */
parent->child_list = (sc_prop_noderef_t *)prop_ensure_capacity(parent->child_list,
parent->property.integer,
name.integer + 1,
sizeof(*parent->child_list));
/* Update the child count if the new node increases it. */
if (parent->property.integer <= name.integer)
parent->property.integer = name.integer + 1;
/* Store the child in its indexed list location. */
parent->child_list[name.integer] = child;
break;
case PROP_KEY_STRING:
/* Add a single entry to the child list, and resize. */
parent->child_list = (sc_prop_noderef_t *)prop_ensure_capacity(parent->child_list,
parent->property.integer,
parent->property.integer + 1,
sizeof(*parent->child_list));
/* Store the child at the end of the list. */
parent->child_list[parent->property.integer++] = child;
break;
default:
sc_fatal("prop_add_child: invalid key type\n");
}
return child;
}
/*
* prop_put()
*
* Add a property to a properties set. Duplicate entries will replace
* prior ones.
*
* Stores a value of variable type as a property. The value type is one of
* 'I', 'B', or 'S', for integer, boolean, and string values, held in the
* first character of format. The next two characters of format are "->",
* and are syntactic sugar. The remainder of format shows the key makeup,
* with 'i' indicating integer, and 's' string key elements. Example format:
* "I->sssis", stores an integer, with a key composed of three strings, an
* integer, and another string.
*/
void prop_put(sc_prop_setref_t bundle, const sc_char *format,
sc_vartype_t vt_value, const sc_vartype_t vt_key[]) {
sc_prop_noderef_t node;
sc_int index_;
assert(prop_is_valid(bundle));
/* Format check. */
if (!format || format[0] == NUL
|| format[1] != '-' || format[2] != '>' || format[3] == NUL)
sc_fatal("prop_put: format error\n");
/* Trace property put. */
if (prop_trace) {
sc_trace("Property: put ");
switch (format[0]) {
case PROP_STRING:
sc_trace("\"%s\"", vt_value.string);
break;
case PROP_INTEGER:
sc_trace("%ld", vt_value.integer);
break;
case PROP_BOOLEAN:
sc_trace("%s", vt_value.boolean ? "true" : "false");
break;
default:
sc_trace("%p [invalid type]", vt_value.voidp);
break;
}
sc_trace(", key \"%s\" : ", format);
for (index_ = 0; format[index_ + 3] != NUL; index_++) {
sc_trace("%s", index_ > 0 ? "," : "");
switch (format[index_ + 3]) {
case PROP_KEY_STRING:
sc_trace("\"%s\"", vt_key[index_].string);
break;
case PROP_KEY_INTEGER:
sc_trace("%ld", vt_key[index_].integer);
break;
default:
sc_trace("%p [invalid type]", vt_key[index_].voidp);
break;
}
}
sc_trace("\n");
}
/*
* Iterate keys, finding matching child nodes at each level. If no matching
* child is found, insert one and continue.
*/
node = bundle->root_node;
for (index_ = 0; format[index_ + 3] != NUL; index_++) {
sc_prop_noderef_t child;
sc_int type;
/*
* Search this level for a name matching the key. If found, advance
* to that child node. Otherwise, add the node to the tree, including
* the set so that the dictionary can be extended.
*/
type = format[index_ + 3];
child = prop_find_child(node, type, vt_key[index_]);
if (child)
node = child;
else
node = prop_add_child(node, type, vt_key[index_], bundle);
}
/*
* Ensure that we're not about to overwrite an internal node child count.
*/
if (node->child_list)
sc_fatal("prop_put: overwrite of internal node\n");
/* Set our properties in the final node. */
switch (format[0]) {
case PROP_INTEGER:
node->property.integer = vt_value.integer;
break;
case PROP_BOOLEAN:
node->property.boolean = vt_value.boolean;
break;
case PROP_STRING:
node->property.string = vt_value.string;
break;
default:
sc_fatal("prop_put: invalid property type\n");
}
}
/*
* prop_get()
*
* Retrieve a property from a properties set. Format stuff as above, except
* with "->" replaced with "<-". Returns FALSE if no such property exists.
*/
sc_bool prop_get(sc_prop_setref_t bundle, const sc_char *format, sc_vartype_t *vt_rvalue,
const sc_vartype_t vt_key[]) {
sc_prop_noderef_t node;
sc_int index_;
assert(prop_is_valid(bundle));
/* Format check. */
if (!format || format[0] == NUL
|| format[1] != '<' || format[2] != '-' || format[3] == NUL)
sc_fatal("prop_get: format error\n");
/* Trace property get. */
if (prop_trace) {
sc_trace("Property: get, key \"%s\" : ", format);
for (index_ = 0; format[index_ + 3] != NUL; index_++) {
sc_trace("%s", index_ > 0 ? "," : "");
switch (format[index_ + 3]) {
case PROP_KEY_STRING:
sc_trace("\"%s\"", vt_key[index_].string);
break;
case PROP_KEY_INTEGER:
sc_trace("%ld", vt_key[index_].integer);
break;
default:
sc_trace("%p [invalid type]", vt_key[index_].voidp);
break;
}
}
sc_trace("\n");
}
/*
* Iterate keys, finding matching child nodes at each level. Stop if no
* matching child is found.
*/
node = bundle->root_node;
for (index_ = 0; format[index_ + 3] != NUL; index_++) {
sc_int type;
/* Move node down to the matching child, NULL if no match. */
type = format[index_ + 3 ];
node = prop_find_child(node, type, vt_key[index_]);
if (!node)
break;
}
/* If key iteration halted because no child was found, return FALSE. */
if (!node) {
if (prop_trace)
sc_trace("Property: ...get FAILED\n");
return FALSE;
}
/*
* Enforce integer-only queries on internal nodes, since this is the only
* type of query that makes sense -- any other type is probably a mistake.
*/
if (node->child_list && format[0] != PROP_INTEGER)
sc_fatal("prop_get: only integer gets on internal nodes\n");
/* Return the properties of the final node. */
switch (format[0]) {
case PROP_INTEGER:
vt_rvalue->integer = node->property.integer;
break;
case PROP_BOOLEAN:
vt_rvalue->boolean = node->property.boolean;
break;
case PROP_STRING:
vt_rvalue->string = node->property.string;
break;
default:
sc_fatal("prop_get: invalid property type\n");
}
/* Complete tracing property get. */
if (prop_trace) {
sc_trace("Property: ...get returned : ");
switch (format[0]) {
case PROP_STRING:
sc_trace("\"%s\"", vt_rvalue->string);
break;
case PROP_INTEGER:
sc_trace("%ld", vt_rvalue->integer);
break;
case PROP_BOOLEAN:
sc_trace("%s", vt_rvalue->boolean ? "true" : "false");
break;
default:
sc_trace("%p [invalid type]", vt_rvalue->voidp);
break;
}
sc_trace("\n");
}
return TRUE;
}
/*
* prop_trim_node()
* prop_solidify()
*
* Trim excess allocation from growable arrays, and fix the properties set
* so that no further property insertions are allowed.
*/
static void prop_trim_node(sc_prop_noderef_t node) {
/* End recursion on null or childless node. */
if (node && node->child_list) {
sc_int index_;
/* Recursively trim allocation on children. */
for (index_ = 0; index_ < node->property.integer; index_++)
prop_trim_node(node->child_list[index_]);
/* Trim allocation on this node. */
node->child_list = (sc_prop_noderef_t *)prop_trim_capacity(node->child_list,
node->property.integer,
sizeof(*node->child_list));
}
}
void prop_solidify(sc_prop_setref_t bundle) {
assert(prop_is_valid(bundle));
/*
* Trim back the dictionary, orphans, pools array, and every internal tree
* node. The one thing _not_ to trim is the final node pool -- there are
* references to nodes within it strewn all over the properties tree, and
* it's a large job to try to find and update them; instead, we just live
* with a little wasted heap memory.
*/
bundle->dictionary = (sc_char **)prop_trim_capacity(bundle->dictionary,
bundle->dictionary_length,
sizeof(bundle->dictionary[0]));
bundle->node_pools = (sc_prop_noderef_t *)prop_trim_capacity(bundle->node_pools,
bundle->node_pools_length,
sizeof(bundle->node_pools[0]));
bundle->orphans = (void **)prop_trim_capacity(bundle->orphans,
bundle->orphans_length,
sizeof(bundle->orphans[0]));
prop_trim_node(bundle->root_node);
/* Set the bundle so that no more properties can be added. */
bundle->is_readonly = TRUE;
}
/*
* prop_get_integer()
* prop_get_boolean()
* prop_get_string()
*
* Convenience functions to retrieve a property of a known type directly.
* It is an error for the property not to exist on retrieval.
*/
sc_int prop_get_integer(sc_prop_setref_t bundle, const sc_char *format, const sc_vartype_t vt_key[]) {
sc_vartype_t vt_rvalue;
assert(format[0] == PROP_INTEGER);
if (!prop_get(bundle, format, &vt_rvalue, vt_key))
sc_fatal("prop_get_integer: can't retrieve property\n");
return vt_rvalue.integer;
}
sc_bool prop_get_boolean(sc_prop_setref_t bundle, const sc_char *format, const sc_vartype_t vt_key[]) {
sc_vartype_t vt_rvalue;
assert(format[0] == PROP_BOOLEAN);
if (!prop_get(bundle, format, &vt_rvalue, vt_key))
sc_fatal("prop_get_boolean: can't retrieve property\n");
return vt_rvalue.boolean;
}
const sc_char *prop_get_string(sc_prop_setref_t bundle, const sc_char *format, const sc_vartype_t vt_key[]) {
sc_vartype_t vt_rvalue;
assert(format[0] == PROP_STRING);
if (!prop_get(bundle, format, &vt_rvalue, vt_key))
sc_fatal("prop_get_string: can't retrieve property\n");
return vt_rvalue.string;
}
/*
* prop_get_child_count()
*
* Convenience function to retrieve a count of child properties available
* for a given property. Returns zero if the property does not exist.
*/
sc_int prop_get_child_count(sc_prop_setref_t bundle, const sc_char *format, const sc_vartype_t vt_key[]) {
sc_vartype_t vt_rvalue;
assert(format[0] == PROP_INTEGER);
if (!prop_get(bundle, format, &vt_rvalue, vt_key))
return 0;
/* Return overloaded integer property value, the child count. */
return vt_rvalue.integer;
}
/*
* prop_create_empty()
*
* Create a new, empty properties set, and return it.
*/
static sc_prop_setref_t prop_create_empty() {
sc_prop_setref_t bundle;
/* Create a new, empty set. */
bundle = (sc_prop_setref_t)sc_malloc(sizeof(*bundle));
bundle->magic = PROP_MAGIC;
/* Begin with an empty strings dictionary. */
bundle->dictionary_length = 0;
bundle->dictionary = nullptr;
/* Begin with no allocated node pools. */
bundle->node_pools_length = 0;
bundle->node_pools = nullptr;
bundle->node_count = 0;
/* Begin with no adopted addresses. */
bundle->orphans_length = 0;
bundle->orphans = nullptr;
/* Leave open for insertions. */
bundle->is_readonly = FALSE;
/*
* Start the set off with a root node. This will also kick off node pools,
* ensuring that every set has at least one node and one allocated pool.
*/
bundle->root_node = prop_new_node(bundle);
bundle->root_node->child_list = nullptr;
bundle->root_node->name.string = "ROOT";
bundle->root_node->property.voidp = nullptr;
/* No taf is yet connected with this set. */
bundle->taf = nullptr;
return bundle;
}
/*
* prop_destroy_child_list()
* prop_destroy()
*
* Free set memory, and destroy a properties set structure.
*/
static void prop_destroy_child_list(sc_prop_noderef_t node) {
/* End recursion on null or childless node. */
if (node && node->child_list) {
sc_int index_;
/* Recursively destroy the children's child lists. */
for (index_ = 0; index_ < node->property.integer; index_++)
prop_destroy_child_list(node->child_list[index_]);
/* Free our own child list. */
sc_free(node->child_list);
}
}
void prop_destroy(sc_prop_setref_t bundle) {
sc_int index_;
assert(prop_is_valid(bundle));
/* Destroy the dictionary, and free it. */
for (index_ = 0; index_ < bundle->dictionary_length; index_++)
sc_free(bundle->dictionary[index_]);
bundle->dictionary_length = 0;
sc_free(bundle->dictionary);
bundle->dictionary = nullptr;
/* Free adopted addresses. */
for (index_ = 0; index_ < bundle->orphans_length; index_++)
sc_free(bundle->orphans[index_]);
bundle->orphans_length = 0;
sc_free(bundle->orphans);
bundle->orphans = nullptr;
/* Walk the tree, destroying the child list for each node found. */
prop_destroy_child_list(bundle->root_node);
bundle->root_node = nullptr;
/* Destroy each node pool. */
for (index_ = 0; index_ < bundle->node_pools_length; index_++)
sc_free(bundle->node_pools[index_]);
bundle->node_pools_length = 0;
sc_free(bundle->node_pools);
bundle->node_pools = nullptr;
/* Destroy any taf associated with the bundle. */
if (bundle->taf)
taf_destroy(bundle->taf);
/* Poison and free the bundle. */
memset(bundle, 0xaa, sizeof(*bundle));
sc_free(bundle);
}
/*
* prop_create()
*
* Create a new properties set based on a taf, and return it.
*/
sc_prop_setref_t prop_create(const sc_tafref_t taf) {
sc_prop_setref_t bundle;
/* Create a new, empty set. */
bundle = prop_create_empty();
/* Populate it with data parsed from the taf file. */
if (!parse_game(taf, bundle)) {
prop_destroy(bundle);
return nullptr;
}
/* Note the taf for destruction later, and return the new set. */
bundle->taf = taf;
return bundle;
}
/*
* prop_adopt()
*
* Adopt a memory address for free'ing on destroy.
*/
void prop_adopt(sc_prop_setref_t bundle, void *addr) {
assert(prop_is_valid(bundle));
/* Extend the orphans array if necessary. */
bundle->orphans = (void **)prop_ensure_capacity(bundle->orphans,
bundle->orphans_length,
bundle->orphans_length + 1,
sizeof(bundle->orphans[0]));
/* Add the new address to the end of the array. */
bundle->orphans[bundle->orphans_length++] = addr;
}
/*
* prop_debug_is_dictionary_string()
* prop_debug_dump_node()
* prop_debug_dump()
*
* Print out a complete properties set.
*/
static sc_bool prop_debug_is_dictionary_string(sc_prop_setref_t bundle, const sc_char *pointer) {
const sc_char *const pointer_ = pointer;
sc_int index_;
/* Compare by pointer directly, not by string value comparisons. */
for (index_ = 0; index_ < bundle->dictionary_length; index_++) {
if (bundle->dictionary[index_] == pointer_)
return TRUE;
}
return FALSE;
}
static void prop_debug_dump_node(sc_prop_setref_t bundle, sc_int depth,
sc_int child_index, sc_prop_noderef_t node) {
sc_int index_;
/* Write node preamble, indented two spaces for each depth count. */
for (index_ = 0; index_ < depth; index_++)
sc_trace(" ");
sc_trace("%ld : %p", child_index, (void *) node);
/* Write node, or just a newline if none. */
if (node) {
/* Print out the node's key, as hex and either string or decimal. */
sc_trace(", name %p", node->name.voidp);
if (node != bundle->root_node) {
if (prop_debug_is_dictionary_string(bundle, node->name.string))
sc_trace(" \"%s\"", node->name.string);
else
sc_trace(" %ld", node->name.integer);
}
if (node->child_list) {
/* Recursively dump children. */
sc_trace(", child count %ld\n", node->property.integer);
for (index_ = 0; index_ < node->property.integer; index_++) {
prop_debug_dump_node(bundle, depth + 1,
index_, node->child_list[index_]);
}
} else {
/* Print out the node's property, again hex and string or decimal. */
sc_trace(", property %p", node->property.voidp);
if (taf_debug_is_taf_string(bundle->taf, node->property.string))
sc_trace(" \"%s\"\n", node->property.string);
else
sc_trace(" %ld\n", node->property.integer);
}
} else
sc_trace("\n");
}
void prop_debug_dump(sc_prop_setref_t bundle) {
sc_int index_;
assert(prop_is_valid(bundle));
/* Dump complete structure. */
sc_trace("Property: debug dump follows...\n");
sc_trace("bundle->is_readonly = %s\n",
bundle->is_readonly ? "true" : "false");
sc_trace("bundle->dictionary_length = %ld\n", bundle->dictionary_length);
sc_trace("bundle->dictionary =\n");
for (index_ = 0; index_ < bundle->dictionary_length; index_++) {
sc_trace("%3ld : %p \"%s\"\n", index_,
(void *)bundle->dictionary[index_], bundle->dictionary[index_]);
}
sc_trace("bundle->node_pools_length = %ld\n", bundle->node_pools_length);
sc_trace("bundle->node_pools =\n");
for (index_ = 0; index_ < bundle->node_pools_length; index_++)
sc_trace("%3ld : %p\n", index_, (void *) bundle->node_pools[index_]);
sc_trace("bundle->node_count = %ld\n", bundle->node_count);
sc_trace("bundle->root_node = {\n");
prop_debug_dump_node(bundle, 0, 0, bundle->root_node);
sc_trace("}\nbundle->taf = %p\n", (void *) bundle->taf);
}
/*
* prop_debug_trace()
*
* Set property tracing on/off.
*/
void prop_debug_trace(sc_bool flag) {
prop_trace = flag;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,770 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/jumps.h"
namespace Glk {
namespace Adrift {
#ifndef ADRIFT_PROTOTYPES_H
#define ADRIFT_PROTOTYPES_H
/* Runtime version and emulated version, for %version% variable and so on. */
#ifndef SCARE_VERSION
# define SCARE_VERSION "1.3.10"
#endif
#ifndef SCARE_PATCH_LEVEL
# define SCARE_PATCH_LEVEL ""
#endif
#ifndef SCARE_EMULATION
# define SCARE_EMULATION 4046
#endif
/* True and false, unless already defined. */
#ifndef FALSE
# define FALSE 0
#endif
#ifndef TRUE
# define TRUE (!FALSE)
#endif
/* Vartype typedef, supports relaxed typing. */
typedef union {
sc_int integer;
sc_bool boolean;
const sc_char *string;
sc_char *mutable_string;
void *voidp;
} sc_vartype_t;
/* Standard reader and writer callback function typedefs. */
typedef sc_int(*sc_read_callbackref_t)(void *, sc_byte *, sc_int);
typedef void (*sc_write_callbackref_t)(void *, const sc_byte *, sc_int);
/*
* Small utility and wrapper functions.
*/
extern void sc_trace(MSVC_PRINTF const sc_char *format, ...) GCC_PRINTF(1, 2);
extern void sc_error(MSVC_PRINTF const sc_char *format, ...) GCC_PRINTF(1, 2);
extern void sc_fatal(MSVC_PRINTF const sc_char *format, ...) GCC_PRINTF(1, 2);
extern void *sc_malloc(size_t size);
extern void *sc_realloc(void *pointer, size_t size);
extern void sc_free(void *pointer);
extern void sc_set_congruential_random(void);
extern void sc_set_platform_random(void);
extern sc_bool sc_is_congruential_random(void);
extern void sc_seed_random(sc_uint new_seed);
extern sc_int sc_rand(void);
extern sc_int sc_randomint(sc_int low, sc_int high);
extern sc_bool sc_strempty(const sc_char *string);
extern sc_char *sc_trim_string(sc_char *string);
extern sc_char *sc_normalize_string(sc_char *string);
extern sc_bool sc_compare_word(const sc_char *string,
const sc_char *Word, sc_int length);
extern sc_uint sc_hash(const sc_char *string);
/* TAF file reader/decompressor enumerations, opaque typedef and functions. */
enum {
TAF_VERSION_NONE = 0,
TAF_VERSION_SAVE = 999,
TAF_VERSION_500 = 500,
TAF_VERSION_400 = 400,
TAF_VERSION_390 = 390,
TAF_VERSION_380 = 380
};
typedef struct sc_taf_s *sc_tafref_t;
extern void taf_destroy(sc_tafref_t taf);
extern sc_tafref_t taf_create(sc_read_callbackref_t callback, void *opaque);
extern sc_tafref_t taf_create_tas(sc_read_callbackref_t callback,
void *opaque);
extern void taf_first_line(sc_tafref_t taf);
extern const sc_char *taf_next_line(sc_tafref_t taf);
extern sc_bool taf_more_lines(sc_tafref_t taf);
extern sc_int taf_get_game_data_length(sc_tafref_t taf);
extern sc_int taf_get_version(sc_tafref_t taf);
extern sc_bool taf_debug_is_taf_string(sc_tafref_t taf, const void *addr);
extern void taf_debug_dump(sc_tafref_t taf);
/* Properties store enumerations, opaque typedef, and functions. */
enum {
PROP_KEY_STRING = 's',
PROP_KEY_INTEGER = 'i'
};
enum {
PROP_INTEGER = 'I',
PROP_BOOLEAN = 'B',
PROP_STRING = 'S'
};
typedef struct sc_prop_set_s *sc_prop_setref_t;
extern sc_prop_setref_t prop_create(const sc_tafref_t taf);
extern void prop_destroy(sc_prop_setref_t bundle);
extern void prop_put(sc_prop_setref_t bundle,
const sc_char *format, sc_vartype_t vt_value,
const sc_vartype_t vt_key[]);
extern sc_bool prop_get(sc_prop_setref_t bundle,
const sc_char *format, sc_vartype_t *vt_value,
const sc_vartype_t vt_key[]);
extern void prop_solidify(sc_prop_setref_t bundle);
extern sc_int prop_get_integer(sc_prop_setref_t bundle,
const sc_char *format,
const sc_vartype_t vt_key[]);
extern sc_bool prop_get_boolean(sc_prop_setref_t bundle,
const sc_char *format,
const sc_vartype_t vt_key[]);
extern const sc_char *prop_get_string(sc_prop_setref_t bundle,
const sc_char *format,
const sc_vartype_t vt_key[]);
extern sc_int prop_get_child_count(sc_prop_setref_t bundle,
const sc_char *format,
const sc_vartype_t vt_key[]);
extern void prop_adopt(sc_prop_setref_t bundle, void *addr);
extern void prop_debug_trace(sc_bool flag);
extern void prop_debug_dump(sc_prop_setref_t bundle);
/* Game parser enumeration and functions. */
enum {
ROOMLIST_NO_ROOMS = 0,
ROOMLIST_ONE_ROOM = 1,
ROOMLIST_SOME_ROOMS = 2,
ROOMLIST_ALL_ROOMS = 3,
ROOMLIST_NPC_PART = 4
};
extern sc_bool parse_game(sc_tafref_t taf, sc_prop_setref_t bundle);
extern void parse_debug_trace(sc_bool flag);
/* Game state structure for modules that use it. */
typedef struct sc_game_s *sc_gameref_t;
/* Hint type definition, a thinly disguised pointer to task entry. */
typedef struct sc_taskstate_s *sc_hintref_t;
/* Variables set enumerations, opaque typedef, and functions. */
enum {
TAFVAR_NUMERIC = 0,
TAFVAR_STRING = 1
};
enum {
VAR_INTEGER = 'I',
VAR_STRING = 'S'
};
typedef struct sc_var_set_s *sc_var_setref_t;
extern void var_put(sc_var_setref_t vars,
const sc_char *name, sc_int _type, sc_vartype_t vt_value);
extern sc_bool var_get(sc_var_setref_t vars,
const sc_char *name, sc_int *_type,
sc_vartype_t *vt_rvalue);
extern void var_put_integer(sc_var_setref_t vars,
const sc_char *name, sc_int value);
extern sc_int var_get_integer(sc_var_setref_t vars, const sc_char *name);
extern void var_put_string(sc_var_setref_t vars,
const sc_char *name, const sc_char *string);
extern const sc_char *var_get_string(sc_var_setref_t vars,
const sc_char *name);
extern sc_var_setref_t var_create(sc_prop_setref_t bundle);
extern void var_destroy(sc_var_setref_t vars);
extern void var_register_game(sc_var_setref_t vars, sc_gameref_t game);
extern void var_set_ref_character(sc_var_setref_t vars, sc_int character);
extern void var_set_ref_object(sc_var_setref_t vars, sc_int object);
extern void var_set_ref_number(sc_var_setref_t vars, sc_int number);
extern void var_set_ref_text(sc_var_setref_t vars, const sc_char *text);
extern sc_int var_get_ref_character(sc_var_setref_t vars);
extern sc_int var_get_ref_object(sc_var_setref_t vars);
extern sc_int var_get_ref_number(sc_var_setref_t vars);
extern const sc_char *var_get_ref_text(sc_var_setref_t vars);
extern sc_uint var_get_elapsed_seconds(sc_var_setref_t vars);
extern void var_set_elapsed_seconds(sc_var_setref_t vars, sc_uint seconds);
extern void var_debug_trace(sc_bool flag);
extern void var_debug_dump(sc_var_setref_t vars);
/* Expression evaluation functions. */
extern sc_bool expr_eval_numeric_expression(const sc_char *expression,
sc_var_setref_t vars,
sc_int *rvalue);
extern sc_bool expr_eval_string_expression(const sc_char *expression,
sc_var_setref_t vars,
sc_char **rvalue);
/* Print filtering opaque typedef and functions. */
typedef struct sc_filter_s *sc_filterref_t;
extern sc_filterref_t pf_create(void);
extern void pf_destroy(sc_filterref_t filter);
extern void pf_buffer_string(sc_filterref_t filter,
const sc_char *string);
extern void pf_buffer_character(sc_filterref_t filter,
sc_char character);
extern void pf_prepend_string(sc_filterref_t filter,
const sc_char *string);
extern void pf_new_sentence(sc_filterref_t filter);
extern void pf_mute(sc_filterref_t filter);
extern void pf_clear_mute(sc_filterref_t filter);
extern void pf_buffer_tag(sc_filterref_t filter, sc_int tag);
extern void pf_strip_tags(sc_char *string);
extern void pf_strip_tags_for_hints(sc_char *string);
extern sc_char *pf_filter(const sc_char *string,
sc_var_setref_t vars, sc_prop_setref_t bundle);
extern sc_char *pf_filter_for_info(const sc_char *string,
sc_var_setref_t vars);
extern void pf_flush(sc_filterref_t filter,
sc_var_setref_t vars, sc_prop_setref_t bundle);
extern void pf_checkpoint(sc_filterref_t filter,
sc_var_setref_t vars, sc_prop_setref_t bundle);
extern const sc_char *pf_get_buffer(sc_filterref_t filter);
extern sc_char *pf_transfer_buffer(sc_filterref_t filter);
extern void pf_empty(sc_filterref_t filter);
extern sc_char *pf_escape(const sc_char *string);
extern sc_char *pf_filter_input(const sc_char *string,
sc_prop_setref_t bundle);
extern void pf_debug_trace(sc_bool flag);
/* Game memo opaque typedef and functions. */
typedef struct sc_memo_set_s *sc_memo_setref_t;
extern sc_memo_setref_t memo_create(void);
extern void memo_destroy(sc_memo_setref_t memento);
extern void memo_save_game(sc_memo_setref_t memento, sc_gameref_t game);
extern sc_bool memo_load_game(sc_memo_setref_t memento, sc_gameref_t game);
extern sc_bool memo_is_load_available(sc_memo_setref_t memento);
extern void memo_clear_games(sc_memo_setref_t memento);
extern void memo_save_command(sc_memo_setref_t memento,
const sc_char *command, sc_int timestamp,
sc_int turns);
extern void memo_unsave_command(sc_memo_setref_t memento);
extern sc_int memo_get_command_count(sc_memo_setref_t memento);
extern void memo_first_command(sc_memo_setref_t memento);
extern void memo_next_command(sc_memo_setref_t memento,
const sc_char **command, sc_int *sequence,
sc_int *timestamp, sc_int *turns);
extern sc_bool memo_more_commands(sc_memo_setref_t memento);
extern const sc_char *memo_find_command(sc_memo_setref_t memento,
sc_int sequence);
extern void memo_clear_commands(sc_memo_setref_t memento);
/* Game state functions. */
extern sc_gameref_t gs_create(sc_var_setref_t vars, sc_prop_setref_t bundle,
sc_filterref_t filter);
extern sc_bool gs_is_game_valid(sc_gameref_t game);
extern void gs_copy(sc_gameref_t to, sc_gameref_t from);
extern void gs_destroy(sc_gameref_t game);
/* Game state accessors and mutators. */
extern void gs_move_player_to_room(sc_gameref_t game, sc_int Room);
extern sc_bool gs_player_in_room(sc_gameref_t game, sc_int room);
extern sc_var_setref_t gs_get_vars(sc_gameref_t gs);
extern sc_prop_setref_t gs_get_bundle(sc_gameref_t gs);
extern sc_filterref_t gs_get_filter(sc_gameref_t gs);
extern sc_memo_setref_t gs_get_memento(sc_gameref_t gs);
extern void gs_set_playerroom(sc_gameref_t gs, sc_int room);
extern void gs_set_playerposition(sc_gameref_t gs, sc_int position);
extern void gs_set_playerparent(sc_gameref_t gs, sc_int parent);
extern sc_int gs_playerroom(sc_gameref_t gs);
extern sc_int gs_playerposition(sc_gameref_t gs);
extern sc_int gs_playerparent(sc_gameref_t gs);
extern sc_int gs_event_count(sc_gameref_t gs);
extern void gs_set_event_state(sc_gameref_t gs, sc_int event, sc_int state);
extern void gs_set_event_time(sc_gameref_t gs, sc_int event, sc_int etime);
extern sc_int gs_event_state(sc_gameref_t gs, sc_int event);
extern sc_int gs_event_time(sc_gameref_t gs, sc_int event);
extern void gs_decrement_event_time(sc_gameref_t gs, sc_int event);
extern sc_int gs_room_count(sc_gameref_t gs);
extern void gs_set_room_seen(sc_gameref_t gs, sc_int Room, sc_bool seen);
extern sc_bool gs_room_seen(sc_gameref_t gs, sc_int Room);
extern sc_int gs_task_count(sc_gameref_t gs);
extern void gs_set_task_done(sc_gameref_t gs, sc_int task, sc_bool done);
extern void gs_set_task_scored(sc_gameref_t gs, sc_int task, sc_bool scored);
extern sc_bool gs_task_done(sc_gameref_t gs, sc_int task);
extern sc_bool gs_task_scored(sc_gameref_t gs, sc_int task);
extern sc_int gs_object_count(sc_gameref_t gs);
extern void gs_set_object_openness(sc_gameref_t gs,
sc_int object, sc_int openness);
extern void gs_set_object_state(sc_gameref_t gs, sc_int object, sc_int state);
extern void gs_set_object_seen(sc_gameref_t gs, sc_int object, sc_bool seen);
extern void gs_set_object_unmoved(sc_gameref_t gs,
sc_int object, sc_bool unmoved);
extern void gs_set_object_static_unmoved(sc_gameref_t gs,
sc_int _object, sc_bool unmoved);
extern sc_int gs_object_openness(sc_gameref_t gs, sc_int object);
extern sc_int gs_object_state(sc_gameref_t gs, sc_int _object);
extern sc_bool gs_object_seen(sc_gameref_t gs, sc_int _object);
extern sc_bool gs_object_unmoved(sc_gameref_t gs, sc_int _object);
extern sc_bool gs_object_static_unmoved(sc_gameref_t gs, sc_int _object);
extern sc_int gs_object_position(sc_gameref_t gs, sc_int _object);
extern sc_int gs_object_parent(sc_gameref_t gs, sc_int _object);
extern void gs_object_move_onto(sc_gameref_t gs, sc_int _object, sc_int onto);
extern void gs_object_move_into(sc_gameref_t gs, sc_int _object, sc_int into);
extern void gs_object_make_hidden(sc_gameref_t gs, sc_int _object);
extern void gs_object_player_get(sc_gameref_t gs, sc_int _object);
extern void gs_object_npc_get(sc_gameref_t gs, sc_int object, sc_int npc);
extern void gs_object_player_wear(sc_gameref_t gs, sc_int object);
extern void gs_object_npc_wear(sc_gameref_t gs, sc_int object, sc_int npc);
extern void gs_object_to_room(sc_gameref_t gs, sc_int _object, sc_int room);
extern sc_int gs_npc_count(sc_gameref_t gs);
extern void gs_set_npc_location(sc_gameref_t gs, sc_int npc, sc_int _location);
extern sc_int gs_npc_location(sc_gameref_t gs, sc_int npc);
extern void gs_set_npc_position(sc_gameref_t gs, sc_int npc, sc_int position);
extern sc_int gs_npc_position(sc_gameref_t gs, sc_int npc);
extern void gs_set_npc_parent(sc_gameref_t gs, sc_int npc, sc_int parent);
extern sc_int gs_npc_parent(sc_gameref_t gs, sc_int npc);
extern void gs_set_npc_seen(sc_gameref_t gs, sc_int npc, sc_bool seen);
extern sc_bool gs_npc_seen(sc_gameref_t gs, sc_int npc);
extern sc_int gs_npc_walkstep_count(sc_gameref_t gs, sc_int npc);
extern void gs_set_npc_walkstep(sc_gameref_t gs, sc_int npc,
sc_int walk, sc_int walkstep);
extern sc_int gs_npc_walkstep(sc_gameref_t gs, sc_int npc, sc_int walk);
extern void gs_decrement_npc_walkstep(sc_gameref_t gs,
sc_int npc, sc_int walkstep);
extern void gs_clear_npc_references(sc_gameref_t gs);
extern void gs_clear_object_references(sc_gameref_t gs);
extern void gs_set_multiple_references(sc_gameref_t gs);
extern void gs_clear_multiple_references(sc_gameref_t gs);
/* Pattern matching functions. */
extern sc_bool uip_match(const sc_char *pattern,
const sc_char *string, sc_gameref_t game);
extern sc_char *uip_replace_pronouns(sc_gameref_t game, const sc_char *string);
extern void uip_assign_pronouns(sc_gameref_t game, const sc_char *string);
extern void uip_debug_trace(sc_bool flag);
/* Library perspective enumeration and functions. */
enum {
LIB_FIRST_PERSON = 0,
LIB_SECOND_PERSON = 1,
LIB_THIRD_PERSON = 2
};
extern void lib_warn_battle_system(void);
extern sc_int lib_random_roomgroup_member(sc_gameref_t game, sc_int roomgroup);
extern const sc_char *lib_get_room_name(sc_gameref_t game, sc_int Room);
extern void lib_print_room_name(sc_gameref_t game, sc_int Room);
extern void lib_print_room_description(sc_gameref_t game, sc_int Room);
extern sc_bool lib_cmd_go_north(sc_gameref_t game);
extern sc_bool lib_cmd_go_east(sc_gameref_t game);
extern sc_bool lib_cmd_go_south(sc_gameref_t game);
extern sc_bool lib_cmd_go_west(sc_gameref_t game);
extern sc_bool lib_cmd_go_up(sc_gameref_t game);
extern sc_bool lib_cmd_go_down(sc_gameref_t game);
extern sc_bool lib_cmd_go_in(sc_gameref_t game);
extern sc_bool lib_cmd_go_out(sc_gameref_t game);
extern sc_bool lib_cmd_go_northeast(sc_gameref_t game);
extern sc_bool lib_cmd_go_southeast(sc_gameref_t game);
extern sc_bool lib_cmd_go_northwest(sc_gameref_t game);
extern sc_bool lib_cmd_go_southwest(sc_gameref_t game);
extern sc_bool lib_cmd_go_room(sc_gameref_t game);
extern sc_bool lib_cmd_verbose(sc_gameref_t game);
extern sc_bool lib_cmd_brief(sc_gameref_t game);
extern sc_bool lib_cmd_notify_on_off(sc_gameref_t game);
extern sc_bool lib_cmd_notify(sc_gameref_t game);
extern sc_bool lib_cmd_time(sc_gameref_t game);
extern sc_bool lib_cmd_date(sc_gameref_t game);
extern sc_bool lib_cmd_quit(sc_gameref_t game);
extern sc_bool lib_cmd_restart(sc_gameref_t game);
extern sc_bool lib_cmd_undo(sc_gameref_t game);
extern sc_bool lib_cmd_history(sc_gameref_t game);
extern sc_bool lib_cmd_history_number(sc_gameref_t game);
extern sc_bool lib_cmd_again(sc_gameref_t game);
extern sc_bool lib_cmd_redo_number(sc_gameref_t game);
extern sc_bool lib_cmd_redo_text(sc_gameref_t game);
extern sc_bool lib_cmd_redo_last(sc_gameref_t game);
extern sc_bool lib_cmd_hints(sc_gameref_t game);
extern sc_bool lib_cmd_help(sc_gameref_t game);
extern sc_bool lib_cmd_license(sc_gameref_t game);
extern sc_bool lib_cmd_information(sc_gameref_t game);
extern sc_bool lib_cmd_clear(sc_gameref_t game);
extern sc_bool lib_cmd_statusline(sc_gameref_t game);
extern sc_bool lib_cmd_version(sc_gameref_t game);
extern sc_bool lib_cmd_look(sc_gameref_t game);
extern sc_bool lib_cmd_print_room_exits(sc_gameref_t game);
extern sc_bool lib_cmd_wait(sc_gameref_t game);
extern sc_bool lib_cmd_wait_number(sc_gameref_t game);
extern sc_bool lib_cmd_examine_self(sc_gameref_t game);
extern sc_bool lib_cmd_examine_npc(sc_gameref_t game);
extern sc_bool lib_cmd_examine_object(sc_gameref_t game);
extern sc_bool lib_cmd_count(sc_gameref_t game);
extern sc_bool lib_cmd_take_all(sc_gameref_t game);
extern sc_bool lib_cmd_take_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_take_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_take_all_from(sc_gameref_t game);
extern sc_bool lib_cmd_take_from_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_take_from_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_take_all_from_npc(sc_gameref_t game);
extern sc_bool lib_cmd_take_from_npc_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_take_from_npc_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_take_npc(sc_gameref_t game);
extern sc_bool lib_cmd_drop_all(sc_gameref_t game);
extern sc_bool lib_cmd_drop_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_drop_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_wear_all(sc_gameref_t game);
extern sc_bool lib_cmd_wear_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_wear_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_remove_all(sc_gameref_t game);
extern sc_bool lib_cmd_remove_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_remove_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_kiss_npc(sc_gameref_t game);
extern sc_bool lib_cmd_kiss_object(sc_gameref_t game);
extern sc_bool lib_cmd_kiss_other(sc_gameref_t game);
extern sc_bool lib_cmd_kill_other(sc_gameref_t game);
extern sc_bool lib_cmd_eat_object(sc_gameref_t game);
extern sc_bool lib_cmd_give_object_npc(sc_gameref_t game);
extern sc_bool lib_cmd_inventory(sc_gameref_t game);
extern sc_bool lib_cmd_open_object(sc_gameref_t game);
extern sc_bool lib_cmd_close_object(sc_gameref_t game);
extern sc_bool lib_cmd_unlock_object_with(sc_gameref_t game);
extern sc_bool lib_cmd_lock_object_with(sc_gameref_t game);
extern sc_bool lib_cmd_unlock_object(sc_gameref_t game);
extern sc_bool lib_cmd_lock_object(sc_gameref_t game);
extern sc_bool lib_cmd_ask_npc_about(sc_gameref_t game);
extern sc_bool lib_cmd_put_all_in(sc_gameref_t game);
extern sc_bool lib_cmd_put_in_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_put_in_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_put_all_on(sc_gameref_t game);
extern sc_bool lib_cmd_put_on_except_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_put_on_multiple(sc_gameref_t game);
extern sc_bool lib_cmd_read_object(sc_gameref_t game);
extern sc_bool lib_cmd_read_other(sc_gameref_t game);
extern sc_bool lib_cmd_stand_on_object(sc_gameref_t game);
extern sc_bool lib_cmd_stand_on_floor(sc_gameref_t game);
extern sc_bool lib_cmd_attack_npc_with(sc_gameref_t game);
extern sc_bool lib_cmd_sit_on_object(sc_gameref_t game);
extern sc_bool lib_cmd_sit_on_floor(sc_gameref_t game);
extern sc_bool lib_cmd_lie_on_object(sc_gameref_t game);
extern sc_bool lib_cmd_lie_on_floor(sc_gameref_t game);
extern sc_bool lib_cmd_get_off_object(sc_gameref_t game);
extern sc_bool lib_cmd_get_off(sc_gameref_t game);
extern sc_bool lib_cmd_save(sc_gameref_t game);
extern sc_bool lib_cmd_restore(sc_gameref_t game);
extern sc_bool lib_cmd_locate_object(sc_gameref_t game);
extern sc_bool lib_cmd_locate_npc(sc_gameref_t game);
extern sc_bool lib_cmd_turns(sc_gameref_t game);
extern sc_bool lib_cmd_score(sc_gameref_t game);
extern sc_bool lib_cmd_get_what(sc_gameref_t game);
extern sc_bool lib_cmd_open_what(sc_gameref_t game);
extern sc_bool lib_cmd_close_other(sc_gameref_t game);
extern sc_bool lib_cmd_lock_other(sc_gameref_t game);
extern sc_bool lib_cmd_lock_what(sc_gameref_t game);
extern sc_bool lib_cmd_unlock_other(sc_gameref_t game);
extern sc_bool lib_cmd_unlock_what(sc_gameref_t game);
extern sc_bool lib_cmd_stand_other(sc_gameref_t game);
extern sc_bool lib_cmd_sit_other(sc_gameref_t game);
extern sc_bool lib_cmd_lie_other(sc_gameref_t game);
extern sc_bool lib_cmd_give_object(sc_gameref_t game);
extern sc_bool lib_cmd_give_what(sc_gameref_t game);
extern sc_bool lib_cmd_remove_what(sc_gameref_t game);
extern sc_bool lib_cmd_drop_what(sc_gameref_t game);
extern sc_bool lib_cmd_wear_what(sc_gameref_t game);
extern sc_bool lib_cmd_profanity(sc_gameref_t game);
extern sc_bool lib_cmd_examine_all(sc_gameref_t game);
extern sc_bool lib_cmd_examine_other(sc_gameref_t game);
extern sc_bool lib_cmd_locate_other(sc_gameref_t game);
extern sc_bool lib_cmd_unix_like(sc_gameref_t game);
extern sc_bool lib_cmd_dos_like(sc_gameref_t game);
extern sc_bool lib_cmd_ask_object(sc_gameref_t game);
extern sc_bool lib_cmd_ask_npc(sc_gameref_t game);
extern sc_bool lib_cmd_ask_other(sc_gameref_t game);
extern sc_bool lib_cmd_block_object(sc_gameref_t game);
extern sc_bool lib_cmd_block_other(sc_gameref_t game);
extern sc_bool lib_cmd_block_what(sc_gameref_t game);
extern sc_bool lib_cmd_break_object(sc_gameref_t game);
extern sc_bool lib_cmd_break_other(sc_gameref_t game);
extern sc_bool lib_cmd_break_what(sc_gameref_t game);
extern sc_bool lib_cmd_destroy_what(sc_gameref_t game);
extern sc_bool lib_cmd_smash_what(sc_gameref_t game);
extern sc_bool lib_cmd_buy_object(sc_gameref_t game);
extern sc_bool lib_cmd_buy_other(sc_gameref_t game);
extern sc_bool lib_cmd_buy_what(sc_gameref_t game);
extern sc_bool lib_cmd_clean_object(sc_gameref_t game);
extern sc_bool lib_cmd_clean_other(sc_gameref_t game);
extern sc_bool lib_cmd_clean_what(sc_gameref_t game);
extern sc_bool lib_cmd_climb_object(sc_gameref_t game);
extern sc_bool lib_cmd_climb_other(sc_gameref_t game);
extern sc_bool lib_cmd_climb_what(sc_gameref_t game);
extern sc_bool lib_cmd_cry(sc_gameref_t game);
extern sc_bool lib_cmd_cut_object(sc_gameref_t game);
extern sc_bool lib_cmd_cut_other(sc_gameref_t game);
extern sc_bool lib_cmd_cut_what(sc_gameref_t game);
extern sc_bool lib_cmd_drink_object(sc_gameref_t game);
extern sc_bool lib_cmd_drink_other(sc_gameref_t game);
extern sc_bool lib_cmd_drink_what(sc_gameref_t game);
extern sc_bool lib_cmd_dance(sc_gameref_t game);
extern sc_bool lib_cmd_eat_other(sc_gameref_t game);
extern sc_bool lib_cmd_feed(sc_gameref_t game);
extern sc_bool lib_cmd_fight(sc_gameref_t game);
extern sc_bool lib_cmd_feel(sc_gameref_t game);
extern sc_bool lib_cmd_fix_object(sc_gameref_t game);
extern sc_bool lib_cmd_fix_other(sc_gameref_t game);
extern sc_bool lib_cmd_fix_what(sc_gameref_t game);
extern sc_bool lib_cmd_fly(sc_gameref_t game);
extern sc_bool lib_cmd_hint(sc_gameref_t game);
extern sc_bool lib_cmd_attack_npc(sc_gameref_t game);
extern sc_bool lib_cmd_hit_object(sc_gameref_t game);
extern sc_bool lib_cmd_hit_other(sc_gameref_t game);
extern sc_bool lib_cmd_hit_what(sc_gameref_t game);
extern sc_bool lib_cmd_hum(sc_gameref_t game);
extern sc_bool lib_cmd_jump(sc_gameref_t game);
extern sc_bool lib_cmd_kick_object(sc_gameref_t game);
extern sc_bool lib_cmd_kick_other(sc_gameref_t game);
extern sc_bool lib_cmd_kick_what(sc_gameref_t game);
extern sc_bool lib_cmd_light_object(sc_gameref_t game);
extern sc_bool lib_cmd_light_other(sc_gameref_t game);
extern sc_bool lib_cmd_light_what(sc_gameref_t game);
extern sc_bool lib_cmd_lift_object(sc_gameref_t game);
extern sc_bool lib_cmd_lift_other(sc_gameref_t game);
extern sc_bool lib_cmd_lift_what(sc_gameref_t game);
extern sc_bool lib_cmd_listen(sc_gameref_t game);
extern sc_bool lib_cmd_mend_object(sc_gameref_t game);
extern sc_bool lib_cmd_mend_other(sc_gameref_t game);
extern sc_bool lib_cmd_mend_what(sc_gameref_t game);
extern sc_bool lib_cmd_move_object(sc_gameref_t game);
extern sc_bool lib_cmd_move_other(sc_gameref_t game);
extern sc_bool lib_cmd_move_what(sc_gameref_t game);
extern sc_bool lib_cmd_please(sc_gameref_t game);
extern sc_bool lib_cmd_press_object(sc_gameref_t game);
extern sc_bool lib_cmd_press_other(sc_gameref_t game);
extern sc_bool lib_cmd_press_what(sc_gameref_t game);
extern sc_bool lib_cmd_pull_object(sc_gameref_t game);
extern sc_bool lib_cmd_pull_other(sc_gameref_t game);
extern sc_bool lib_cmd_pull_what(sc_gameref_t game);
extern sc_bool lib_cmd_punch(sc_gameref_t game);
extern sc_bool lib_cmd_push_object(sc_gameref_t game);
extern sc_bool lib_cmd_push_other(sc_gameref_t game);
extern sc_bool lib_cmd_push_what(sc_gameref_t game);
extern sc_bool lib_cmd_repair_object(sc_gameref_t game);
extern sc_bool lib_cmd_repair_other(sc_gameref_t game);
extern sc_bool lib_cmd_repair_what(sc_gameref_t game);
extern sc_bool lib_cmd_rub_object(sc_gameref_t game);
extern sc_bool lib_cmd_rub_other(sc_gameref_t game);
extern sc_bool lib_cmd_rub_what(sc_gameref_t game);
extern sc_bool lib_cmd_run(sc_gameref_t game);
extern sc_bool lib_cmd_say(sc_gameref_t game);
extern sc_bool lib_cmd_sell_object(sc_gameref_t game);
extern sc_bool lib_cmd_sell_other(sc_gameref_t game);
extern sc_bool lib_cmd_sell_what(sc_gameref_t game);
extern sc_bool lib_cmd_shake_object(sc_gameref_t game);
extern sc_bool lib_cmd_shake_npc(sc_gameref_t game);
extern sc_bool lib_cmd_shake_other(sc_gameref_t game);
extern sc_bool lib_cmd_shake_what(sc_gameref_t game);
extern sc_bool lib_cmd_shout(sc_gameref_t game);
extern sc_bool lib_cmd_sing(sc_gameref_t game);
extern sc_bool lib_cmd_sleep(sc_gameref_t game);
extern sc_bool lib_cmd_smell_object(sc_gameref_t game);
extern sc_bool lib_cmd_smell_other(sc_gameref_t game);
extern sc_bool lib_cmd_stop_object(sc_gameref_t game);
extern sc_bool lib_cmd_stop_other(sc_gameref_t game);
extern sc_bool lib_cmd_stop_what(sc_gameref_t game);
extern sc_bool lib_cmd_suck_object(sc_gameref_t game);
extern sc_bool lib_cmd_suck_other(sc_gameref_t game);
extern sc_bool lib_cmd_suck_what(sc_gameref_t game);
extern sc_bool lib_cmd_talk(sc_gameref_t game);
extern sc_bool lib_cmd_thank(sc_gameref_t game);
extern sc_bool lib_cmd_touch_object(sc_gameref_t game);
extern sc_bool lib_cmd_touch_other(sc_gameref_t game);
extern sc_bool lib_cmd_touch_what(sc_gameref_t game);
extern sc_bool lib_cmd_turn_object(sc_gameref_t game);
extern sc_bool lib_cmd_turn_other(sc_gameref_t game);
extern sc_bool lib_cmd_turn_what(sc_gameref_t game);
extern sc_bool lib_cmd_unblock_object(sc_gameref_t game);
extern sc_bool lib_cmd_unblock_other(sc_gameref_t game);
extern sc_bool lib_cmd_unblock_what(sc_gameref_t game);
extern sc_bool lib_cmd_wash_object(sc_gameref_t game);
extern sc_bool lib_cmd_wash_other(sc_gameref_t game);
extern sc_bool lib_cmd_wash_what(sc_gameref_t game);
extern sc_bool lib_cmd_whistle(sc_gameref_t game);
extern sc_bool lib_cmd_interrogation(sc_gameref_t game);
extern sc_bool lib_cmd_xyzzy(sc_gameref_t game);
extern sc_bool lib_cmd_egotistic(sc_gameref_t game);
extern sc_bool lib_cmd_yes_or_no(sc_gameref_t game);
extern sc_bool lib_cmd_verb_object(sc_gameref_t game);
extern sc_bool lib_cmd_verb_npc(sc_gameref_t game);
extern void lib_debug_trace(sc_bool flag);
/* Resource opaque typedef and control functions. */
typedef struct sc_resource_s *sc_resourceref_t;
extern sc_bool res_has_sound(sc_gameref_t game);
extern sc_bool res_has_graphics(sc_gameref_t game);
extern void res_clear_resource(sc_resourceref_t resource);
extern sc_bool res_compare_resource(sc_resourceref_t from,
sc_resourceref_t with);
extern void res_handle_resource(sc_gameref_t game,
const sc_char *partial_format,
const sc_vartype_t vt_partial[]);
extern void res_sync_resources(sc_gameref_t game);
extern void res_cancel_resources(sc_gameref_t game);
/* Game runner functions. */
extern sc_bool run_game_task_commands(sc_gameref_t game, const sc_char *string);
extern sc_gameref_t run_create(sc_read_callbackref_t callback, void *opaque);
extern void run_interpret(CONTEXT, sc_gameref_t game);
extern void run_destroy(sc_gameref_t game);
extern void run_restart(CONTEXT, sc_gameref_t game);
extern void run_save(sc_gameref_t game, sc_write_callbackref_t callback, void *opaque);
extern sc_bool run_save_prompted(sc_gameref_t game);
extern sc_bool run_restore(CONTEXT, sc_gameref_t game, sc_read_callbackref_t callback, void *opaque);
extern sc_bool run_restore_prompted(CONTEXT, sc_gameref_t game);
extern sc_bool run_undo(CONTEXT, sc_gameref_t game);
extern void run_quit(CONTEXT, sc_gameref_t game);
extern sc_bool run_is_running(sc_gameref_t game);
extern sc_bool run_has_completed(sc_gameref_t game);
extern sc_bool run_is_undo_available(sc_gameref_t game);
extern void run_get_attributes(sc_gameref_t game,
const sc_char **game_name,
const sc_char **game_author,
const sc_char **game_compile_date,
sc_int *turns, sc_int *score,
sc_int *max_score,
const sc_char **current_room_name,
const sc_char **status_line,
const sc_char **preferred_font,
sc_bool *bold_room_names, sc_bool *verbose,
sc_bool *notify_score_change);
extern void run_set_attributes(sc_gameref_t game,
sc_bool bold_room_names, sc_bool verbose,
sc_bool notify_score_change);
extern sc_hintref_t run_hint_iterate(sc_gameref_t game, sc_hintref_t hint);
extern const sc_char *run_get_hint_question(sc_gameref_t game,
sc_hintref_t hint);
extern const sc_char *run_get_subtle_hint(sc_gameref_t game,
sc_hintref_t hint);
extern const sc_char *run_get_unsubtle_hint(sc_gameref_t game,
sc_hintref_t hint);
/* Event functions. */
extern sc_bool evt_can_see_event(sc_gameref_t game, sc_int event);
extern void evt_tick_events(sc_gameref_t game);
extern void evt_debug_trace(sc_bool flag);
/* Task functions. */
extern sc_bool task_has_hints(sc_gameref_t game, sc_int task);
extern const sc_char *task_get_hint_question(sc_gameref_t game, sc_int task);
extern const sc_char *task_get_hint_subtle(sc_gameref_t game, sc_int task);
extern const sc_char *task_get_hint_unsubtle(sc_gameref_t game, sc_int task);
extern sc_bool task_can_run_task_directional(sc_gameref_t game,
sc_int task, sc_bool forwards);
extern sc_bool task_can_run_task(sc_gameref_t game, sc_int task);
extern sc_bool task_run_task(sc_gameref_t game, sc_int task, sc_bool forwards);
extern void task_debug_trace(sc_bool flag);
/* Task restriction functions. */
extern sc_bool restr_pass_task_object_state(sc_gameref_t game,
sc_int var1, sc_int var2);
extern sc_bool restr_eval_task_restrictions(sc_gameref_t game,
sc_int task, sc_bool *pass,
const sc_char **fail_message);
extern void restr_debug_trace(sc_bool flag);
/* NPC gender enumeration and functions. */
enum {
NPC_MALE = 0,
NPC_FEMALE = 1,
NPC_NEUTER = 2
};
extern sc_bool npc_in_room(sc_gameref_t game, sc_int npc, sc_int Room);
extern sc_int npc_count_in_room(sc_gameref_t game, sc_int Room);
extern void npc_setup_initial(sc_gameref_t game);
extern void npc_start_npc_walk(sc_gameref_t game, sc_int npc, sc_int walk);
extern void npc_tick_npcs(sc_gameref_t game);
extern void npc_turn_update(sc_gameref_t game);
extern void npc_debug_trace(sc_bool flag);
/* Object open/closed state enumeration and functions. */
enum {
OBJ_WONTCLOSE = 0,
OBJ_OPEN = 5,
OBJ_CLOSED = 6,
OBJ_LOCKED = 7
};
extern sc_bool obj_is_static(sc_gameref_t game, sc_int object);
extern sc_bool obj_is_container(sc_gameref_t game, sc_int _object);
extern sc_bool obj_is_surface(sc_gameref_t game, sc_int object);
extern sc_int obj_container_object(sc_gameref_t game, sc_int n);
extern sc_int obj_surface_object(sc_gameref_t game, sc_int n);
extern sc_bool obj_indirectly_in_room(sc_gameref_t game,
sc_int _object, sc_int Room);
extern sc_bool obj_indirectly_held_by_player(sc_gameref_t game, sc_int object);
extern sc_bool obj_directly_in_room(sc_gameref_t game,
sc_int _object, sc_int Room);
extern sc_int obj_stateful_object(sc_gameref_t game, sc_int n);
extern sc_int obj_dynamic_object(sc_gameref_t game, sc_int n);
extern sc_int obj_wearable_object(sc_gameref_t game, sc_int n);
extern sc_int obj_standable_object(sc_gameref_t game, sc_int n);
extern sc_int obj_get_size(sc_gameref_t game, sc_int object);
extern sc_int obj_get_weight(sc_gameref_t game, sc_int _object);
extern sc_int obj_get_player_size_limit(sc_gameref_t game);
extern sc_int obj_get_player_weight_limit(sc_gameref_t game);
extern sc_int obj_get_container_maxsize(sc_gameref_t game, sc_int object);
extern sc_int obj_get_container_capacity(sc_gameref_t game, sc_int object);
extern sc_int obj_lieable_object(sc_gameref_t game, sc_int n);
extern sc_bool obj_appears_plural(sc_gameref_t game, sc_int _object);
extern void obj_setup_initial(sc_gameref_t game);
extern sc_int obj_container_index(sc_gameref_t game, sc_int object);
extern sc_int obj_surface_index(sc_gameref_t game, sc_int object);
extern sc_int obj_stateful_index(sc_gameref_t game, sc_int _object);
extern sc_char *obj_state_name(sc_gameref_t game, sc_int object);
extern sc_bool obj_shows_initial_description(sc_gameref_t game, sc_int object);
extern void obj_turn_update(sc_gameref_t game);
extern void obj_debug_trace(sc_bool flag);
/* Locale support, and locale-sensitive functions. */
extern void loc_detect_game_locale(sc_prop_setref_t bundle);
extern sc_bool loc_set_locale(const sc_char *name);
extern const sc_char *loc_get_locale(void);
extern sc_bool sc_isspace(sc_char character);
extern sc_bool sc_isdigit(sc_char character);
extern sc_bool sc_isalpha(sc_char character);
extern sc_char sc_toupper(sc_char character);
extern sc_char sc_tolower(sc_char character);
extern void loc_debug_dump(void);
/* Debugger interface. */
typedef struct sc_debugger_s *sc_debuggerref_t;
extern sc_bool debug_run_command(sc_gameref_t game,
const sc_char *debug_command);
extern sc_bool debug_cmd_debugger(sc_gameref_t game);
extern void debug_set_enabled(sc_gameref_t game, sc_bool enable);
extern sc_bool debug_get_enabled(sc_gameref_t game);
extern void debug_game_started(CONTEXT, sc_gameref_t game);
extern void debug_game_ended(CONTEXT, sc_gameref_t game);
extern void debug_turn_update(CONTEXT, sc_gameref_t game);
/* OS interface functions. */
extern sc_bool if_get_trace_flag(sc_uint bitmask);
extern void if_print_string(const sc_char *string);
extern void if_print_debug(const sc_char *string);
extern void if_print_character(sc_char character);
extern void if_print_debug_character(sc_char character);
extern void if_print_tag(sc_int tag, const sc_char *arg);
extern void if_read_line(sc_char *buffer, sc_int length);
extern void if_read_debug(sc_char *buffer, sc_int length);
extern sc_bool if_confirm(sc_int _type);
extern void *if_open_saved_game(sc_bool is_save);
extern void if_close_saved_game(void *opaque);
extern void if_display_hints(sc_gameref_t game);
extern void if_update_sound(const sc_char *filepath, sc_int sound_offset,
sc_int sound_length, sc_bool is_looping);
extern void if_update_graphic(const sc_char *filepath, sc_int graphic_offset, sc_int graphic_length);
#endif
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,315 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/scgamest.h"
namespace Glk {
namespace Adrift {
/* Assorted definitions and constants. */
static const sc_char NUL = '\0';
/*
* res_has_sound()
* res_has_graphics()
*
* Return TRUE if the game uses sound or graphics.
*/
sc_bool res_has_sound(sc_gameref_t game) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[2];
sc_bool has_sound;
assert(gs_is_game_valid(game));
vt_key[0].string = "Globals";
vt_key[1].string = "Sound";
has_sound = prop_get_boolean(bundle, "B<-ss", vt_key);
return has_sound;
}
sc_bool res_has_graphics(sc_gameref_t game) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[2];
sc_bool has_graphics;
assert(gs_is_game_valid(game));
vt_key[0].string = "Globals";
vt_key[1].string = "Graphics";
has_graphics = prop_get_boolean(bundle, "B<-ss", vt_key);
return has_graphics;
}
/*
* res_set_resource()
* res_clear_resource()
* res_compare_resource()
*
* Convenience functions to set, clear, and compare resource fields.
*/
static void res_set_resource(sc_resourceref_t resource, const sc_char *name,
sc_int offset, sc_int length) {
resource->name = name;
resource->offset = offset;
resource->length = length;
}
void res_clear_resource(sc_resourceref_t resource) {
res_set_resource(resource, "", 0, 0);
}
sc_bool res_compare_resource(sc_resourceref_t from, sc_resourceref_t with) {
return strcmp(from->name, with->name) == 0
&& from->offset == with->offset && from->length == with->length;
}
/*
* res_handle_resource()
*
* General helper for handling graphics and sound resources. Supplied with a
* partial key to the node containing resources, it identifies what resource
* is appropriate, and sets this as the requested resource in the game, for
* later use on sync'ing, using the handler appropriate for the game version.
*
* The partial format is something like "sis" (the bit to follow I<- or S<-
* in prop_get), and the partial key is guaranteed to contain at least
* strlen(partial_format) elements.
*/
void res_handle_resource(sc_gameref_t game, const sc_char *partial_format,
const sc_vartype_t vt_partial[]) {
const sc_prop_setref_t bundle = gs_get_bundle(game);
sc_vartype_t vt_key[2], *vt_full;
sc_int partial_length, resource_start_offset;
sc_bool embedded;
sc_char *format;
assert(gs_is_game_valid(game));
assert(partial_format && vt_partial);
/*
* Check for resources. If this game doesn't use any, exit now to avoid the
* overhead of pointless lookups and allocations.
*/
if (!(res_has_sound(game) || res_has_graphics(game)))
return;
/*
* Get the global offset for all resources. For version 3.9 games this
* should be zero. For version 4.0 games, it's the start of resource data
* in the TAF file where resources are embedded.
*/
vt_key[0].string = "ResourceOffset";
resource_start_offset = prop_get_integer(bundle, "I<-s", vt_key);
/*
* Get the flag that indicated embedded resources. For version 3.9 games
* this should be false. If not set, offset and length are forced to zero
* for interface functions.
*/
vt_key[0].string = "Globals";
vt_key[1].string = "Embedded";
embedded = prop_get_boolean(bundle, "B<-ss", vt_key);
/*
* Allocate a format for use with properties calls, five characters longer
* than the partial passed in. Build a key one element larger than the
* partial supplied, and copy over all supplied elements.
*/
partial_length = strlen(partial_format);
format = (sc_char *)sc_malloc(partial_length + 5);
vt_full = (sc_vartype_t *)sc_malloc((partial_length + 1) * sizeof(vt_partial[0]));
memcpy(vt_full, vt_partial, partial_length * sizeof(vt_partial[0]));
/* Search for sound resources, and offer if found. */
if (res_has_sound(game)) {
const sc_char *soundfile;
sc_int soundoffset, soundlen;
/* Get soundfile property from the node supplied. */
vt_full[partial_length].string = "SoundFile";
Common::strcpy_s(format, partial_length + 5, "S<-");
Common::strcat_s(format, partial_length + 5, partial_format);
Common::strcat_s(format, partial_length + 5, "s");
soundfile = prop_get_string(bundle, format, vt_full);
/* If a sound is defined, handle it. */
if (!sc_strempty(soundfile)) {
if (embedded) {
/* Retrieve offset and length. */
vt_full[partial_length].string = "SoundOffset";
Common::strcpy_s(format, partial_length + 5, "I<-");
Common::strcat_s(format, partial_length + 5, partial_format);
Common::strcat_s(format, partial_length + 5, "s");
soundoffset = prop_get_integer(bundle, format, vt_full)
+ resource_start_offset;
vt_full[partial_length].string = "SoundLen";
Common::strcpy_s(format, partial_length + 5, "I<-");
Common::strcat_s(format, partial_length + 5, partial_format);
Common::strcat_s(format, partial_length + 5, "s");
soundlen = prop_get_integer(bundle, format, vt_full);
} else {
/* Coerce offset and length to zero. */
soundoffset = 0;
soundlen = 0;
}
/*
* If the sound is the special "##", latch stop, otherwise note
* details to play on sync.
*/
if (!strcmp(soundfile, "##")) {
game->stop_sound = TRUE;
res_clear_resource(&game->requested_sound);
} else {
res_set_resource(&game->requested_sound,
soundfile, soundoffset, soundlen);
}
}
}
/* Now do the same thing for graphics resources. */
if (res_has_graphics(game)) {
const sc_char *graphicfile;
sc_int graphicoffset, graphiclen;
/* Get graphicfile property from the node supplied. */
vt_full[partial_length].string = "GraphicFile";
Common::strcpy_s(format, partial_length + 5, "S<-");
Common::strcat_s(format, partial_length + 5, partial_format);
Common::strcat_s(format, partial_length + 5, "s");
graphicfile = prop_get_string(bundle, format, vt_full);
/* If a graphic is defined, handle it. */
if (!sc_strempty(graphicfile)) {
if (embedded) {
/* Retrieve offset and length. */
vt_full[partial_length].string = "GraphicOffset";
Common::strcpy_s(format, partial_length + 5, "I<-");
Common::strcat_s(format, partial_length + 5, partial_format);
Common::strcat_s(format, partial_length + 5, "s");
graphicoffset = prop_get_integer(bundle, format, vt_full)
+ resource_start_offset;
vt_full[partial_length].string = "GraphicLen";
Common::strcpy_s(format, partial_length + 5, "I<-");
Common::strcat_s(format, partial_length + 5, partial_format);
Common::strcat_s(format, partial_length + 5, "s");
graphiclen = prop_get_integer(bundle, format, vt_full);
} else {
/* Coerce offset and length to zero. */
graphicoffset = 0;
graphiclen = 0;
}
/* Graphics resource retrieved, note to show on sync. */
res_set_resource(&game->requested_graphic,
graphicfile, graphicoffset, graphiclen);
}
}
/* Free allocated memory. */
sc_free(format);
sc_free(vt_full);
}
/*
* res_sync_resources()
*
* Bring resources into line with the game; called on undo, restart,
* restore, and so on.
*/
void res_sync_resources(sc_gameref_t game) {
assert(gs_is_game_valid(game));
/* Deal with any latched sound stop first. */
if (game->stop_sound) {
if (game->sound_active) {
if_update_sound("", 0, 0, FALSE);
game->sound_active = FALSE;
res_clear_resource(&game->playing_sound);
}
game->stop_sound = FALSE;
}
/* Look for a change of sound, and pass to interface on change. */
if (!res_compare_resource(&game->playing_sound,
&game->requested_sound)) {
const sc_char *name;
sc_char *clean_name;
sc_bool is_looping;
/* If the sound name ends '##', this is a looping sound. */
name = game->requested_sound.name;
is_looping = !strcmp(name + strlen(name) - 2, "##");
size_t ln = strlen(name) + 1;
clean_name = (sc_char *)sc_malloc(ln);
Common::strcpy_s(clean_name, ln, name);
if (is_looping)
clean_name[strlen(clean_name) - 2] = NUL;
if_update_sound(clean_name,
game->requested_sound.offset,
game->requested_sound.length, is_looping);
game->playing_sound = game->requested_sound;
game->sound_active = TRUE;
sc_free(clean_name);
}
/* Look for a change of graphic, and pass to interface on change. */
if (!res_compare_resource(&game->displayed_graphic,
&game->requested_graphic)) {
if_update_graphic(game->requested_graphic.name,
game->requested_graphic.offset,
game->requested_graphic.length);
game->displayed_graphic = game->requested_graphic;
}
}
/*
* res_cancel_resources()
*
* Turn off sound and graphics, and reset the game's tracking of resources in
* use to match. Called on game restart or restore.
*/
void res_cancel_resources(sc_gameref_t game) {
assert(gs_is_game_valid(game));
/* Request that everything stops and clears. */
game->stop_sound = FALSE;
res_clear_resource(&game->requested_sound);
res_clear_resource(&game->requested_graphic);
/* Synchronize to have the above take effect. */
res_sync_resources(game);
}
} // End of namespace Adrift
} // End of namespace Glk

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,732 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/detection.h"
#include "common/algorithm.h"
#include "common/compression/deflate.h"
#include "common/memstream.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Put integer and boolean read functions in here?
*/
/* Assorted definitions and constants. */
static const sc_uint TAF_MAGIC = 0x5bdcfa41;
enum {
VERSION_HEADER_SIZE = 14,
V400_HEADER_EXTRA = 8
};
enum {
OUT_BUFFER_SIZE = 31744,
IN_BUFFER_SIZE = 16384,
GROW_INCREMENT = 8
};
static const sc_char NEWLINE = '\n';
static const sc_char CARRIAGE_RETURN = '\r';
static const sc_char NUL = '\0';
/*
* Game TAF data structure. The game structure contains the original TAF
* file header, a growable array of "slab" descriptors, each of which holds
* metadata for a "slab" (around a decompression buffer full of TAF strings),
* the length of the descriptor array and elements allocated, and a current
* location for iteration.
*
* Saved game files (.TAS) are just like TAF files except that they lack the
* header. So for files of this type, the header is all zeroes.
*/
struct sc_slabdesc_t {
sc_byte *data;
sc_int size;
};
typedef sc_slabdesc_t *sc_slabdescref_t;
struct sc_taf_s {
sc_uint magic;
sc_byte header[VERSION_HEADER_SIZE + V400_HEADER_EXTRA];
sc_int version;
sc_int total_in_bytes;
sc_slabdescref_t slabs;
sc_int slab_count;
sc_int slabs_allocated;
sc_bool is_unterminated;
sc_int current_slab;
sc_int current_offset;
};
typedef sc_taf_s sc_taf_t;
/* Microsoft Visual Basic PRNG magic numbers, initial and current state. */
static const sc_int PRNG_CST1 = 0x43fd43fd,
PRNG_CST2 = 0x00c39ec3,
PRNG_CST3 = 0x00ffffff,
PRNG_INITIAL_STATE = 0x00a09e86;
static sc_int taf_random_state = 0x00a09e86;
/*
* taf_random()
* taf_random_reset()
*
* Version 3.9 and version 3.8 games are obfuscated by xor'ing each character
* with the PRNG in Visual Basic. So here we have to emulate that, to unob-
* fuscate data from such game files. The PRNG generates 0..prng_cst3, which
* we multiply by 255 and then divide by prng_cst3 + 1 to get output in the
* range 0..254. Thanks to Rik Snel for uncovering this obfuscation.
*/
static sc_byte taf_random(void) {
/* Generate and return the next pseudo-random number. */
taf_random_state = (taf_random_state * PRNG_CST1 + PRNG_CST2) & PRNG_CST3;
return (BYTE_MAX * (sc_uint) taf_random_state) / (sc_uint)(PRNG_CST3 + 1);
}
static void taf_random_reset(void) {
/* Reset PRNG to initial conditions. */
taf_random_state = PRNG_INITIAL_STATE;
}
/*
* taf_is_valid()
*
* Return TRUE if pointer is a valid TAF structure, FALSE otherwise.
*/
static sc_bool taf_is_valid(sc_tafref_t taf) {
return taf && taf->magic == TAF_MAGIC;
}
/*
* taf_create_empty()
*
* Allocate and return a new, empty TAF structure.
*/
static sc_tafref_t taf_create_empty(void) {
sc_tafref_t taf;
/* Create an empty TAF structure. */
taf = (sc_tafref_t)sc_malloc(sizeof(*taf));
taf->magic = TAF_MAGIC;
memset(taf->header, 0, sizeof(taf->header));
taf->version = TAF_VERSION_NONE;
taf->total_in_bytes = 0;
taf->slabs = nullptr;
taf->slab_count = 0;
taf->slabs_allocated = 0;
taf->is_unterminated = FALSE;
taf->current_slab = 0;
taf->current_offset = 0;
/* Return the new TAF structure. */
return taf;
}
/*
* taf_destroy()
*
* Free TAF memory, and destroy a TAF structure.
*/
void taf_destroy(sc_tafref_t taf) {
sc_int index_;
assert(taf_is_valid(taf));
/* First free each slab in the slabs array,... */
for (index_ = 0; index_ < taf->slab_count; index_++)
sc_free(taf->slabs[index_].data);
/*
* ...then free slabs growable array, and poison and free the TAF structure
* itself.
*/
sc_free(taf->slabs);
memset(taf, 0xaa, sizeof(*taf));
sc_free(taf);
}
/*
* taf_finalize_last_slab()
*
* Insert nul's into slab data so that it turns into a series of nul-terminated
* strings. Nul's are used to replace carriage return and newline pairs.
*/
static void taf_finalize_last_slab(sc_tafref_t taf) {
sc_slabdescref_t slab;
sc_int index_;
/* Locate the final slab in the slab descriptors array. */
assert(taf->slab_count > 0);
slab = taf->slabs + taf->slab_count - 1;
/*
* Replace carriage return and newline pairs with nuls, and individual
* carriage returns with a single newline.
*/
for (index_ = 0; index_ < slab->size; index_++) {
if (slab->data[index_] == CARRIAGE_RETURN) {
if (index_ < slab->size - 1 && slab->data[index_ + 1] == NEWLINE) {
slab->data[index_] = NUL;
slab->data[index_ + 1] = NUL;
index_++;
} else
slab->data[index_] = NEWLINE;
}
/* Also protect against unlikely incoming nul characters. */
else if (slab->data[index_] == NUL)
slab->data[index_] = NEWLINE;
}
}
/*
* taf_find_buffer_extent()
*
* Search backwards from the buffer end for a terminating carriage return and
* line feed. If none, found, return length and set is_unterminated to TRUE.
* Otherwise, return the count of usable bytes found in the buffer.
*/
static sc_int taf_find_buffer_extent(const sc_byte *buffer, sc_int length, sc_bool *is_unterminated) {
sc_int bytes;
/* Search backwards from the buffer end for the final line feed. */
for (bytes = length; bytes > 1; bytes--) {
if (buffer[bytes - 2] == CARRIAGE_RETURN && buffer[bytes - 1] == NEWLINE)
break;
}
if (bytes < 2) {
/* No carriage return and newline termination found. */
*is_unterminated = TRUE;
return length;
}
*is_unterminated = FALSE;
return bytes;
}
/*
* taf_append_buffer()
*
* Append a buffer of TAF lines to an existing TAF structure. Returns the
* number of characters consumed from the buffer.
*/
static sc_int taf_append_buffer(sc_tafref_t taf, const sc_byte *buffer, sc_int length) {
sc_int bytes;
sc_bool is_unterminated;
/* Locate the extent of appendable data in the buffer. */
bytes = taf_find_buffer_extent(buffer, length, &is_unterminated);
/* See if the last buffer handled contained at least one data line. */
if (!taf->is_unterminated) {
sc_slabdescref_t slab;
/* Extend the slabs array if we've reached the current allocation. */
if (taf->slab_count == taf->slabs_allocated) {
taf->slabs_allocated += GROW_INCREMENT;
taf->slabs = (sc_slabdescref_t)sc_realloc(taf->slabs,
taf->slabs_allocated * sizeof(*taf->slabs));
}
/* Advance to the next unused slab in the slab descriptors array. */
slab = taf->slabs + taf->slab_count;
taf->slab_count++;
/* Copy the input buffer into the new slab. */
slab->data = (sc_byte *)sc_malloc(bytes);
memcpy(slab->data, buffer, bytes);
slab->size = bytes;
} else {
sc_slabdescref_t slab;
/* Locate the final slab in the slab descriptors array. */
assert(taf->slab_count > 0);
slab = taf->slabs + taf->slab_count - 1;
/*
* The last buffer we saw had no line endings in it. In this case,
* append the input buffer to the end of the last slab's data, rather
* than creating a new slab. This may cause allocation to overflow
* the system limits on single allocated areas on some platforms.
*/
slab->data = (sc_byte *)sc_realloc(slab->data, slab->size + bytes);
memcpy(slab->data + slab->size, buffer, bytes);
slab->size += bytes;
/*
* Use a special case for the final carriage return and newline pairing
* that are split over two buffers; force correct termination of this
* slab.
*/
if (slab->size > 1
&& slab->data[slab->size - 2] == CARRIAGE_RETURN
&& slab->data[slab->size - 1] == NEWLINE)
is_unterminated = FALSE;
}
/*
* Note if this buffer requires that the next be coalesced with it. If it
* doesn't, finalize the last slab by breaking it into separate lines.
*/
taf->is_unterminated = is_unterminated;
if (!is_unterminated)
taf_finalize_last_slab(taf);
/* Return count of buffer bytes consumed. */
return bytes;
}
/*
* taf_unobfuscate()
*
* Unobfuscate a version 3.9 and version 3.8 TAF file from data read by
* repeated calls to the callback() function. Callback() should return the
* count of bytes placed in the buffer, or 0 if no more (end of file).
* Assumes that the file has been read past the header.
*/
static sc_bool taf_unobfuscate(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
sc_byte *buffer;
sc_int bytes, used_bytes, total_bytes, index_;
/* Reset the PRNG, and synchronize with the header already read. */
taf_random_reset();
for (index_ = 0; index_ < VERSION_HEADER_SIZE; index_++)
taf_random();
/*
* Malloc buffer, done to help systems with limited stacks, and initialize
* count of bytes read and used in the buffer to zero.
*/
buffer = (sc_byte *)sc_malloc(IN_BUFFER_SIZE);
used_bytes = 0;
total_bytes = 0;
/* Unobfuscate in buffer sized chunks. */
do {
/* Try to obtain more data. */
bytes = callback(opaque,
buffer + used_bytes, IN_BUFFER_SIZE - used_bytes);
/* Unobfuscate data read in. */
for (index_ = 0; index_ < bytes; index_++)
buffer[used_bytes + index_] ^= taf_random();
/*
* Add data read in and unobfuscated to buffer used data, and if
* unobfuscated data is available, add it to the TAF.
*/
used_bytes += bytes;
if (used_bytes > 0) {
sc_int consumed;
/* Add lines from this buffer to the TAF. */
consumed = taf_append_buffer(taf, buffer, used_bytes);
/* Move unused buffer data to buffer start. */
memmove(buffer, buffer + consumed, IN_BUFFER_SIZE - consumed);
/* Note counts of bytes consumed and remaining in the buffer. */
used_bytes -= consumed;
total_bytes += consumed;
}
} while (bytes > 0);
/*
* Unobfuscation completed, note the total bytes read. This value is
* actually not used for version 3.9 and version 3.8 games, but we maintain
* it just in case.
*/
taf->total_in_bytes = total_bytes;
if (is_gamefile)
taf->total_in_bytes += VERSION_HEADER_SIZE;
/* Check that we found the end of the input file as expected. */
if (used_bytes > 0) {
sc_error("taf_unobfuscate:"
" warning: %ld unhandled bytes in the buffer\n", used_bytes);
}
if (taf->is_unterminated)
sc_fatal("taf_unobfuscate: unterminated final data slab\n");
/* Return successfully. */
sc_free(buffer);
return TRUE;
}
#define BUFFER_SIZE 16384
/*
* taf_decompress()
*
* Decompress a version 4.0 TAF
*/
static sc_bool taf_decompress(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
Common::SeekableReadStream *src = (Common::SeekableReadStream *)opaque;
assert(src);
Common::MemoryWriteStreamDynamic dest(DisposeAfterUse::YES);
size_t startingPos = src->pos();
Common::SeekableReadStream *gzStream = wrapCompressedReadStream(src, DisposeAfterUse::NO);
if (!gzStream) {
return false;
}
dest.writeStream(gzStream);
if (!gzStream->eos() || gzStream->err()) {
delete gzStream;
return false;
}
delete gzStream;
// Iterate through pushing data out to the taf file
const byte *pTemp = dest.getData();
int bytesRemaining = dest.size();
while (bytesRemaining > 0) {
int consumed = taf_append_buffer(taf, pTemp, bytesRemaining);
bytesRemaining -= consumed;
}
taf->total_in_bytes = src->pos() - startingPos;
return true;
}
/*
* taf_read_raw()
*
* Read an uncompressed version 4.0 TAF save chunk used by ScummVM
*/
static sc_bool taf_read_raw(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
byte *buffer = new byte[BUFFER_SIZE];
size_t bytesRead, bytesLeft = 0;
size_t totalBytes, bytesWritten;
for (;;) {
bytesRead = callback(opaque, buffer + bytesLeft, BUFFER_SIZE - bytesLeft);
if ((bytesLeft + bytesRead) == 0)
break;
totalBytes = bytesLeft + bytesRead;
bytesWritten = taf_append_buffer(taf, buffer, totalBytes);
bytesLeft = totalBytes - bytesWritten;
if (bytesLeft)
Common::copy(buffer + bytesWritten, buffer + totalBytes, buffer);
}
delete[] buffer;
return true;
}
/*
* taf_create_from_callback()
*
* Create a TAF structure from data read in by repeated calls to the
* callback() function. Callback() should return the count of bytes placed
* in the buffer, or 0 if no more (end of file).
*/
static sc_tafref_t taf_create_from_callback(sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
sc_tafref_t taf;
sc_bool status = FALSE;
assert(callback);
/* Create an empty TAF structure. */
taf = taf_create_empty();
/*
* Determine the TAF file version in use. For saved games, we always use
* version 4.0 format. For others, it's determined from the header.
*/
if (is_gamefile) {
sc_int in_bytes;
/*
* Read in the ADRIFT header for game files. Start by reading in the
* shorter header common to all.
*/
in_bytes = callback(opaque, taf->header, VERSION_HEADER_SIZE);
if (in_bytes != VERSION_HEADER_SIZE) {
sc_error("taf_create: not enough data for standard TAF header\n");
taf_destroy(taf);
return nullptr;
}
/* Handle different TAF versions */
int version = AdriftMetaEngine::detectGameVersion(taf->header);
if (version == TAF_VERSION_500 || version == TAF_VERSION_390 ||
version == TAF_VERSION_380) {
taf->version = version;
} else if (version == TAF_VERSION_400) {
/* Read in the version 4.0 header extension. */
in_bytes = callback(opaque,
taf->header + VERSION_HEADER_SIZE,
V400_HEADER_EXTRA);
if (in_bytes != V400_HEADER_EXTRA) {
sc_error("taf_create:"
" not enough data for extended TAF header\n");
taf_destroy(taf);
return nullptr;
}
taf->version = TAF_VERSION_400;
} else {
taf_destroy(taf);
return nullptr;
}
} else {
/* Saved games are always considered to be for ScummVM, version 5.0. */
taf->version = TAF_VERSION_SAVE;
}
/*
* Call the appropriate game file reader function. For version 4.0 games,
* data is compressed with Zlib. For version 3.9 and version 3.8 games,
* it's obfuscated with the Visual Basic PRNG.
*/
switch (taf->version) {
case TAF_VERSION_SAVE:
status = taf_read_raw(taf, callback, opaque, is_gamefile);
break;
case TAF_VERSION_500:
sc_error("taf_create: ADRIFT 5 games are not yet supported");
break;
case TAF_VERSION_400:
status = taf_decompress(taf, callback, opaque, is_gamefile);
break;
case TAF_VERSION_390:
case TAF_VERSION_380:
status = taf_unobfuscate(taf, callback, opaque, is_gamefile);
break;
default:
sc_fatal("taf_create: invalid version\n");
}
if (!status) {
taf_destroy(taf);
return nullptr;
}
/* Return successfully. */
return taf;
}
/*
* taf_create()
* taf_create_tas()
*
* Public entry points for taf_create_from_callback(). Return a taf object
* constructed from either *.TAF (game) or *.TAS (saved game state) file data.
*/
sc_tafref_t taf_create(sc_read_callbackref_t callback, void *opaque) {
return taf_create_from_callback(callback, opaque, TRUE);
}
sc_tafref_t taf_create_tas(sc_read_callbackref_t callback, void *opaque) {
return taf_create_from_callback(callback, opaque, FALSE);
}
/*
* taf_first_line()
*
* Iterator rewind function, reset current slab location to TAF data start.
*/
void taf_first_line(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* Set current locations to TAF start. */
taf->current_slab = 0;
taf->current_offset = 0;
}
/*
* taf_next_line()
*
* Iterator function, return the next line of data from a TAF, or NULL
* if no more lines.
*/
const sc_char *taf_next_line(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* If there is a next line, return it and advance current. */
if (taf->current_slab < taf->slab_count) {
sc_char *line;
/* Get the effective address of the current line. */
line = (sc_char *) taf->slabs[taf->current_slab].data;
line += taf->current_offset;
/*
* Advance to the next line. The + 2 skips the NULs used to replace the
* carriage return and line feed.
*/
taf->current_offset += strlen(line) + 2;
if (taf->current_offset >= taf->slabs[taf->current_slab].size) {
taf->current_slab++;
taf->current_offset = 0;
}
return line;
}
/* No more lines, so return NULL. */
return nullptr;
}
/*
* taf_more_lines()
*
* Iterator end function, returns TRUE if more TAF lines are readable.
*/
sc_bool taf_more_lines(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* Return TRUE if not at TAF data end. */
return taf->current_slab < taf->slab_count;
}
/*
* taf_get_game_data_length()
*
* Returns the number of bytes read to decompress the game. Resources are
* appended to the TAF file after the game, so this value allows them to
* be located.
*/
sc_int taf_get_game_data_length(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/*
* Return the count of bytes inflated; this includes the TAF header length
* for TAF, rather than TAS, files. For TAS files, the count of file bytes
* read is irrelevant, and is never used.
*/
return taf->total_in_bytes;
}
/*
* taf_get_version()
*
* Return the version number of the TAF file, 400, 390, or 380.
*/
sc_int taf_get_version(sc_tafref_t taf) {
assert(taf_is_valid(taf));
assert(taf->version != TAF_VERSION_NONE);
return taf->version;
}
/*
* taf_debug_is_taf_string()
* taf_debug_dump()
*
* Print out a complete TAF structure. The first function is a helper for
* properties debugging, indicating if a given address is a string in a TAF
* slab, and therefore safe to print.
*/
sc_bool taf_debug_is_taf_string(sc_tafref_t taf, const void *addr) {
const sc_byte *const addr_ = (const sc_byte *)addr;
sc_int index_;
/*
* Compare pointer, by address directly, against all memory contained in
* the TAF slabs. Return TRUE if in range.
*/
for (index_ = 0; index_ < taf->slab_count; index_++) {
if (addr_ >= taf->slabs[index_].data
&& addr_ < taf->slabs[index_].data + taf->slabs[index_].size)
return TRUE;
}
return FALSE;
}
void taf_debug_dump(sc_tafref_t taf) {
sc_int index_, current_slab, current_offset;
assert(taf_is_valid(taf));
/* Dump complete structure. */
sc_trace("TAFfile: debug dump follows...\n");
sc_trace("taf->header =");
for (index_ = 0; index_ < (sc_int) sizeof(taf->header); index_++)
sc_trace(" %02x", taf->header[index_]);
sc_trace("\n");
sc_trace("taf->version = %s\n",
taf->version == TAF_VERSION_400 ? "4.00" :
taf->version == TAF_VERSION_390 ? "3.90" :
taf->version == TAF_VERSION_380 ? "3.80" : "[Unknown]");
sc_trace("taf->slabs = \n");
for (index_ = 0; index_ < taf->slab_count; index_++) {
sc_trace("%3ld : %p, %ld bytes\n", index_,
(void *)taf->slabs[index_].data, taf->slabs[index_].size);
}
sc_trace("taf->slab_count = %ld\n", taf->slab_count);
sc_trace("taf->slabs_allocated = %ld\n", taf->slabs_allocated);
sc_trace("taf->current_slab = %ld\n", taf->current_slab);
sc_trace("taf->current_offset = %ld\n", taf->current_offset);
/* Save current location. */
current_slab = taf->current_slab;
current_offset = taf->current_offset;
/* Print out taf lines using taf iterators. */
sc_trace("\ntaf iterators:\n");
taf_first_line(taf);
for (index_ = 0; taf_more_lines(taf); index_++)
sc_trace("%5ld %s\n", index_, taf_next_line(taf));
/* Restore current location. */
taf->current_slab = current_slab;
taf->current_offset = current_offset;
}
} // End of namespace Adrift
} // End of namespace Glk

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,411 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/glk.h"
#include "glk/events.h"
#include "common/debug.h"
#include "common/str.h"
#include "common/textconsole.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Implement smarter selective module tracing.
*/
/*
* sc_trace()
*
* Debugging trace function; printf wrapper that writes to stderr.
*/
void sc_trace(const sc_char *format, ...) {
va_list ap;
assert(format);
va_start(ap, format);
Common::String s = Common::String::format(format, ap);
va_end(ap);
debug("%s", s.c_str());
}
/*
* sc_error()
* sc_fatal()
*
* Error reporting functions. sc_error() prints a message and continues.
* sc_fatal() prints a message, then calls abort().
*/
void sc_error(const sc_char *format, ...) {
va_list ap;
assert(format);
va_start(ap, format);
Common::String s = Common::String::vformat(format, ap);
va_end(ap);
warning("%s", s.c_str());
}
void sc_fatal(const sc_char *format, ...) {
va_list ap;
assert(format);
va_start(ap, format);
Common::String s = Common::String::format(format, ap);
va_end(ap);
error("%s", s.c_str());
}
/* Unique non-heap address for zero size malloc() and realloc() requests. */
static void *sc_zero_allocation = &sc_zero_allocation;
/*
* sc_malloc()
* sc_realloc()
* sc_free()
*
* Non-failing wrappers around malloc functions. Newly allocated memory is
* cleared to zero. In ANSI/ISO C, zero byte allocations are implementation-
* defined, so we have to take special care to get predictable behavior.
*/
void *sc_malloc(size_t size) {
void *allocated;
if (size == 0)
return sc_zero_allocation;
allocated = malloc(size);
if (!allocated)
sc_fatal("sc_malloc: requested %lu bytes\n", (sc_uint) size);
else if (allocated == sc_zero_allocation)
sc_fatal("sc_malloc: zero-byte allocation address returned\n");
memset(allocated, 0, size);
return allocated;
}
void *sc_realloc(void *pointer, size_t size) {
void *allocated;
if (size == 0) {
sc_free(pointer);
return sc_zero_allocation;
}
if (pointer == sc_zero_allocation)
pointer = nullptr;
allocated = realloc(pointer, size);
if (!allocated)
sc_fatal("sc_realloc: requested %lu bytes\n", (sc_uint) size);
else if (allocated == sc_zero_allocation)
sc_fatal("sc_realloc: zero-byte allocation address returned\n");
if (!pointer)
memset(allocated, 0, size);
return allocated;
}
void sc_free(void *pointer) {
if (sc_zero_allocation != &sc_zero_allocation)
sc_fatal("sc_free: write to zero-byte allocation address detected\n");
if (pointer && pointer != sc_zero_allocation)
free(pointer);
}
/*
* sc_strncasecmp()
* sc_strcasecmp()
*
* Strncasecmp and strcasecmp are not ANSI functions, so here are local
* definitions to do the same jobs.
*/
sc_int sc_strncasecmp(const sc_char *s1, const sc_char *s2, sc_int n) {
sc_int index_;
assert(s1 && s2);
for (index_ = 0; index_ < n; index_++) {
sc_int diff;
diff = sc_tolower(s1[index_]) - sc_tolower(s2[index_]);
if (diff < 0 || diff > 0)
return diff < 0 ? -1 : 1;
}
return 0;
}
sc_int sc_strcasecmp(const sc_char *s1, const sc_char *s2) {
sc_int s1len, s2len, result;
assert(s1 && s2);
s1len = strlen(s1);
s2len = strlen(s2);
result = sc_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
if (result < 0 || result > 0)
return result;
else
return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
}
/*
* sc_platform_rand()
* sc_congruential_rand()
* sc_set_random_handler()
*
* Internal random number generation functions. We offer two: one is a self-
* seeding wrapper around the platform's rand(), which should generate good
* random numbers but with a sequence that is platform-dependent; the other
* is a linear congruential generator with a long period that is guaranteed
* to return the same sequence for all platforms. The default is the first,
* with the latter intended for predictability of game actions.
*/
static sc_int sc_platform_rand(sc_uint new_seed) {
static sc_bool is_seeded = FALSE;
/* If reseeding, seed with the value supplied, note seeded, and return 0. */
if (new_seed > 0) {
g_vm->setRandomNumberSeed(new_seed);
is_seeded = TRUE;
return 0;
} else {
/* If not explicitly seeded yet, generate a seed from time(). */
if (!is_seeded) {
//srand ((sc_uint) time (NULL));
is_seeded = TRUE;
}
/* Return the next rand() number in the sequence. */
return g_vm->getRandomNumber(0xffffff);
}
}
static sc_int sc_congruential_rand(sc_uint new_seed) {
static sc_bool is_seeded = FALSE;
static sc_uint rand_state = 1;
/* If reseeding, seed with the value supplied, and note seeded. */
if (new_seed > 0) {
rand_state = new_seed;
is_seeded = TRUE;
return 0;
} else {
/* If not explicitly seeded yet, generate a seed from time(). */
if (!is_seeded) {
rand_state = (sc_uint)g_vm->_events->getTotalPlayTicks();
is_seeded = TRUE;
}
/*
* Advance random state, using constants from Park & Miller (1988).
* To keep the values the same for both 32 and 64 bit longs, mask out
* any bits above the bottom 32.
*/
rand_state = (rand_state * 16807 + 2147483647) & 0xffffffff;
/*
* Discard the lowest bit as a way to map 32-bits unsigned to a 32-bit
* positive signed.
*/
return rand_state >> 1;
}
}
/* Function pointer for the actual random number generator in use. */
static sc_int(*sc_rand_function)(sc_uint) = sc_platform_rand;
/*
* sc_set_congruential_random()
* sc_set_platform_random()
* sc_is_congruential_random()
* sc_seed_random()
* sc_rand()
* sc_randomint()
*
* Public interface to random functions; control and reseed the random
* handler in use, generate a random number, and a convenience function to
* generate a random value within a given range.
*/
void sc_set_congruential_random(void) {
sc_rand_function = sc_congruential_rand;
}
void sc_set_platform_random(void) {
sc_rand_function = sc_platform_rand;
}
sc_bool sc_is_congruential_random(void) {
return sc_rand_function == sc_congruential_rand;
}
void sc_seed_random(sc_uint new_seed) {
/* Ignore zero values of new_seed by simply using 1 instead. */
sc_rand_function(new_seed > 0 ? new_seed : 1);
}
sc_int sc_rand(void) {
sc_int retval;
/* Passing zero indicates this is not a seed operation. */
retval = sc_rand_function(0);
return retval;
}
sc_int sc_randomint(sc_int low, sc_int high) {
/*
* If the range is invalid, just return the low value given. This mimics
* Adrift under the same conditions, and also guards against division by
* zero in the mod operation.
*/
return (high < low) ? low : low + sc_rand() % (high - low + 1);
}
/* Miscellaneous general ascii constants. */
static const sc_char NUL = '\0';
static const sc_char SPACE = ' ';
/*
* sc_strempty()
*
* Return TRUE if a string is either zero-length or contains only whitespace.
*/
sc_bool sc_strempty(const sc_char *string) {
sc_int index_;
assert(string);
/* Scan for any non-space character. */
for (index_ = 0; string[index_] != NUL; index_++) {
if (!sc_isspace(string[index_]))
return FALSE;
}
/* None found, so string is empty. */
return TRUE;
}
/*
* sc_trim_string()
*
* Trim leading and trailing whitespace from a string. Modifies the string
* in place, and returns the string address for convenience.
*/
sc_char *sc_trim_string(sc_char *string) {
sc_int index_;
assert(string);
for (index_ = strlen(string) - 1;
index_ >= 0 && sc_isspace(string[index_]); index_--)
string[index_] = NUL;
for (index_ = 0; sc_isspace(string[index_]);)
index_++;
memmove(string, string + index_, strlen(string) - index_ + 1);
return string;
}
/*
* sc_normalize_string()
*
* Trim a string, and set all runs of whitespace to a single space character.
* Modifies the string in place, and returns the string address for
* convenience.
*/
sc_char *sc_normalize_string(sc_char *string) {
sc_int index_;
assert(string);
/* Trim all leading and trailing spaces. */
string = sc_trim_string(string);
/* Compress multiple whitespace runs into a single space character. */
for (index_ = 0; string[index_] != NUL; index_++) {
if (sc_isspace(string[index_])) {
sc_int cursor;
string[index_] = SPACE;
for (cursor = index_ + 1; sc_isspace(string[cursor]);)
cursor++;
memmove(string + index_ + 1,
string + cursor, strlen(string + cursor) + 1);
}
}
return string;
}
/*
* sc_compare_word()
*
* Return TRUE if the first word in the string is word, case insensitive.
*/
sc_bool sc_compare_word(const sc_char *string, const sc_char *word, sc_int length) {
assert(string && word);
/* Return TRUE if string starts with word, then space or string end. */
return sc_strncasecmp(string, word, length) == 0
&& (string[length] == NUL || sc_isspace(string[length]));
}
/*
* sc_hash()
*
* Hash a string, hashpjw algorithm, from 'Compilers, principles, techniques,
* and tools', page 436, unmodulo'ed and somewhat restyled.
*/
sc_uint sc_hash(const sc_char *string) {
sc_int index_;
sc_uint hash;
assert(string);
hash = 0;
for (index_ = 0; string[index_] != NUL; index_++) {
sc_uint temp;
hash = (hash << 4) + string[index_];
temp = hash & 0xf0000000;
if (temp != 0) {
hash = hash ^ (temp >> 24);
hash = hash ^ temp;
}
}
return hash;
}
} // End of namespace Adrift
} // End of namespace Glk

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,494 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/serialization.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/scgamest.h"
namespace Glk {
namespace Adrift {
/* Assorted definitions and constants. */
static const sc_char NEWLINE = '\n';
static const sc_char CARRIAGE_RETURN = '\r';
// Unused static const sc_char NUL = '\0';
enum { BUFFER_SIZE = 4096 };
void SaveSerializer::save() {
const sc_var_setref_t vars = gs_get_vars(_game);
const sc_prop_setref_t bundle = gs_get_bundle(_game);
sc_vartype_t vt_key[3];
sc_int index_, var_count;
// Write the _game name
vt_key[0].string = "Globals";
vt_key[1].string = "GameName";
writeString(prop_get_string(bundle, "S<-ss", vt_key));
/* Write the counts of rooms, objects, etc. */
writeInt(gs_room_count(_game));
writeInt(gs_object_count(_game));
writeInt(gs_task_count(_game));
writeInt(gs_event_count(_game));
writeInt(gs_npc_count(_game));
/* Write the score and player information. */
writeInt(_game->score);
writeInt(gs_playerroom(_game) + 1);
writeInt(gs_playerparent(_game));
writeInt(gs_playerposition(_game));
/* Write player gender. */
vt_key[0].string = "Globals";
vt_key[1].string = "PlayerGender";
writeInt(prop_get_integer(bundle, "I<-ss", vt_key));
/*
* Write encumbrance details. The player limits are constant for a given
* _game, and can be extracted from properties. The current sizes and
* weights can also be recalculated from held objects, so we don't maintain
* them in the _game. We can write constants here, then, and ignore
* the values on restoring. Note however that if the Adrift Runner is
* relying on these values, this may give it problems with one of our saved
* games.
*/
writeInt(90);
writeInt(0);
writeInt(90);
writeInt(0);
/* Save rooms information. */
for (index_ = 0; index_ < gs_room_count(_game); index_++)
writeBool(gs_room_seen(_game, index_));
/* Save objects information. */
for (index_ = 0; index_ < gs_object_count(_game); index_++) {
writeInt(gs_object_position(_game, index_));
writeBool(gs_object_seen(_game, index_));
writeInt(gs_object_parent(_game, index_));
if (gs_object_openness(_game, index_) != 0)
writeInt(gs_object_openness(_game, index_));
if (gs_object_state(_game, index_) != 0)
writeInt(gs_object_state(_game, index_));
writeBool(gs_object_unmoved(_game, index_));
}
/* Save tasks information. */
for (index_ = 0; index_ < gs_task_count(_game); index_++) {
writeBool(gs_task_done(_game, index_));
writeBool(gs_task_scored(_game, index_));
}
/* Save events information. */
for (index_ = 0; index_ < gs_event_count(_game); index_++) {
sc_int startertype, task;
/* Get starter task, if any. */
vt_key[0].string = "Events";
vt_key[1].integer = index_;
vt_key[2].string = "StarterType";
startertype = prop_get_integer(bundle, "I<-sis", vt_key);
if (startertype == 3) {
vt_key[2].string = "TaskNum";
task = prop_get_integer(bundle, "I<-sis", vt_key);
}
else
task = 0;
/* Save event details. */
writeInt(gs_event_time(_game, index_));
writeInt(task);
writeInt(gs_event_state(_game, index_) - 1);
if (task > 0)
writeBool(gs_task_done(_game, task - 1));
else
writeBool(false);
}
/* Save NPCs information. */
for (index_ = 0; index_ < gs_npc_count(_game); index_++) {
sc_int walk;
writeInt(gs_npc_location(_game, index_));
writeBool(gs_npc_seen(_game, index_));
for (walk = 0; walk < gs_npc_walkstep_count(_game, index_); walk++)
writeIntSpecial(gs_npc_walkstep(_game, index_, walk));
}
/* Save each variable. */
vt_key[0].string = "Variables";
var_count = prop_get_child_count(bundle, "I<-s", vt_key);
for (index_ = 0; index_ < var_count; index_++) {
const sc_char *name;
sc_int var_type;
vt_key[1].integer = index_;
vt_key[2].string = "Name";
name = prop_get_string(bundle, "S<-sis", vt_key);
vt_key[2].string = "Type";
var_type = prop_get_integer(bundle, "I<-sis", vt_key);
switch (var_type) {
case TAFVAR_NUMERIC:
writeInt(var_get_integer(vars, name));
break;
case TAFVAR_STRING:
writeString(var_get_string(vars, name));
break;
default:
sc_fatal("ser_save_game: unknown variable type, %ld\n", var_type);
}
}
/* Save timing information. */
writeUint(var_get_elapsed_seconds(vars));
/* Save turns count. */
writeUint((sc_uint)_game->turns);
/*
* Flush the last buffer contents, and drop the callback and opaque
* references.
*/
flush(TRUE);
_callback = nullptr;
_opaque = nullptr;
}
void SaveSerializer::flush(sc_bool is_final) {
if (is_final) {
_callback(_opaque, _buffer.getData(), _buffer.size());
}
}
void SaveSerializer::writeChar(sc_char character) {
// Add to the buffer
_buffer.writeByte(character);
}
void SaveSerializer::write(const sc_char *buffer, sc_int length) {
// Add each character to the buffer
for (int idx = 0; idx < length; ++idx)
writeChar(buffer[idx]);
}
void SaveSerializer::writeString(const sc_char *string) {
// Write string, followed by DOS style end-of-line
write(string, strlen(string));
writeChar(CARRIAGE_RETURN);
writeChar(NEWLINE);
}
void SaveSerializer::writeInt(sc_int value) {
Common::String s = Common::String::format("%ld", value);
writeString(s.c_str());
}
void SaveSerializer::writeIntSpecial(sc_int value) {
Common::String s = Common::String::format("% ld ", value);
writeString(s.c_str());
}
void SaveSerializer::writeUint(sc_uint value) {
Common::String s = Common::String::format("%lu", value);
writeString(s.c_str());
}
void SaveSerializer::writeBool(sc_bool boolean) {
// Write a 1 for TRUE, 0 for false
writeString(boolean ? "1" : "0");
}
/*--------------------------------------------------------------------------*/
#define CHECK if (context._break) goto ser_tas_error
bool LoadSerializer::load() {
const sc_filterref_t filter = gs_get_filter(_game);
const sc_prop_setref_t bundle = gs_get_bundle(_game);
sc_vartype_t vt_key[3];
sc_int index_, var_count;
const sc_char *gamename;
sc_var_setref_t new_vars = nullptr;
sc_gameref_t new_game = nullptr;
Context context;
sc_int count = 0;
// Create a TAF (TAS) reference from callbacks, for reader functions
ser_tas = taf_create_tas(_callback, _opaque);
if (!ser_tas)
return false;
// Reset line counter for error messages.
ser_tasline = 1;
// Read the _game name, and compare with the one in the _game. Fail if they don't match exactly.
// A tighter check than this would perhaps be preferable, say, something based on the TAF file
// header, but this isn't in the save file format.
vt_key[0].string = "Globals";
vt_key[1].string = "GameName";
gamename = prop_get_string(bundle, "S<-ss", vt_key);
if (strcmp(readString(context), gamename) != 0 || context._break)
goto ser_tas_error;
// Read and verify the counts in the saved _game.
if ((readInt(context) != gs_room_count(_game) || context._break)
|| (readInt(context) != gs_object_count(_game) || context._break)
|| (readInt(context) != gs_task_count(_game) || context._break)
|| (readInt(context) != gs_event_count(_game) || context._break)
|| (readInt(context) != gs_npc_count(_game) || context._break))
goto ser_tas_error;
// Create a variables set and _game to restore into.
new_vars = var_create(bundle);
new_game = gs_create(new_vars, bundle, filter);
var_register_game(new_vars, new_game);
// All set to load TAF (TAS) data into the new _game.
// Restore the score and player information.
new_game->score = readInt(context); CHECK;
gs_set_playerroom(new_game, readInt(context) - 1); CHECK;
gs_set_playerparent(new_game, readInt(context)); CHECK;
gs_set_playerposition(new_game, readInt(context)); CHECK;
// Skip player gender.
(void)readInt(context); CHECK;
// Skip encumbrance details, not currently maintained by the _game.
(void)readInt(context); CHECK;
(void)readInt(context); CHECK;
(void)readInt(context); CHECK;
(void)readInt(context); CHECK;
// Restore rooms information
count = gs_room_count(new_game);
for (index_ = 0; index_ < count; ++index_) {
gs_set_room_seen(new_game, index_, readBool(context)); CHECK;
}
// Restore objects information
count = gs_object_count(new_game);
for (index_ = 0; index_ < count; ++index_) {
sc_int openable, currentstate;
// Bypass mutators for position and parent. Fix later?
new_game->objects[index_].position = readInt(context); CHECK;
gs_set_object_seen(new_game, index_, readBool(context)); CHECK;
new_game->objects[index_].parent = readInt(context); CHECK;
vt_key[0].string = "Objects";
vt_key[1].integer = index_;
vt_key[2].string = "Openable";
openable = prop_get_integer(bundle, "I<-sis", vt_key);
gs_set_object_openness(new_game, index_, openable != 0 ? readInt(context) : 0); CHECK;
vt_key[2].string = "CurrentState";
currentstate = prop_get_integer(bundle, "I<-sis", vt_key);
gs_set_object_state(new_game, index_,
currentstate != 0 ? readInt(context) : 0); CHECK;
gs_set_object_unmoved(new_game, index_, readBool(context)); CHECK;
}
// Restore tasks information.
for (index_ = 0; index_ < gs_task_count(new_game); index_++) {
gs_set_task_done(new_game, index_, readBool(context)); CHECK;
gs_set_task_scored(new_game, index_, readBool(context)); CHECK;
}
// Restore events information
count = gs_event_count(new_game);
for (index_ = 0; index_ < count; index_++) {
sc_int startertype, task;
// Restore first event details.
gs_set_event_time(new_game, index_, readInt(context)); CHECK;
task = readInt(context); CHECK;
gs_set_event_state(new_game, index_, readInt(context) + 1); CHECK;
// Verify and restore the starter task, if any.
if (task > 0) {
vt_key[0].string = "Events";
vt_key[1].integer = index_;
vt_key[2].string = "StarterType";
startertype = prop_get_integer(bundle, "I<-sis", vt_key);
if (startertype != 3)
goto ser_tas_error;
// Restore task state.
gs_set_task_done(new_game, task - 1, readBool(context)); CHECK;
} else {
(void)readBool(context); CHECK;
}
}
// Restore NPCs information
count = gs_npc_count(new_game);
for (index_ = 0; index_ < count; index_++) {
sc_int walk;
gs_set_npc_location(new_game, index_, readInt(context)); CHECK;
gs_set_npc_seen(new_game, index_, readBool(context)); CHECK;
for (walk = 0; walk < gs_npc_walkstep_count(new_game, index_); walk++) {
gs_set_npc_walkstep(new_game, index_, walk, readInt(context)); CHECK;
}
}
// Restore each variable.
vt_key[0].string = "Variables";
var_count = prop_get_child_count(bundle, "I<-s", vt_key);
for (index_ = 0; index_ < var_count; index_++) {
const sc_char *name;
sc_int var_type;
vt_key[1].integer = index_;
vt_key[2].string = "Name";
name = prop_get_string(bundle, "S<-sis", vt_key);
vt_key[2].string = "Type";
var_type = prop_get_integer(bundle, "I<-sis", vt_key);
switch (var_type) {
case TAFVAR_NUMERIC:
var_put_integer(new_vars, name, readInt(context)); CHECK;
break;
case TAFVAR_STRING:
var_put_string(new_vars, name, readString(context)); CHECK;
break;
default:
sc_fatal("ser_load_game: unknown variable type, %ld\n", var_type);
}
}
// Restore timing information.
var_set_elapsed_seconds(new_vars, readUint(context)); CHECK;
// Restore turns count.
new_game->turns = (sc_int)readUint(context); CHECK;
/* Resources tweak -- set requested to match those in the current _game so that they remain
* unchanged by the gs_copy() of new_game onto game. This way, both the requested and the
* active resources in the game are unchanged by restore.
*/
new_game->requested_sound = _game->requested_sound;
new_game->requested_graphic = _game->requested_graphic;
/* If we got this far, we successfully restored the _game from the file.
* As our final act, copy the new _game onto the old one.
*/
new_game->temporary = _game->temporary;
new_game->undo = _game->undo;
gs_copy(_game, new_game);
// Done with the temporary _game and variables.
gs_destroy(new_game);
var_destroy(new_vars);
// Done with TAF (TAS) file; destroy it and return successfully
taf_destroy(ser_tas);
return true;
ser_tas_error:
// Destroy any temporary _game and variables
if (new_game)
gs_destroy(new_game);
if (new_vars)
var_destroy(new_vars);
// Destroy the TAF (TAS) file and return fail status
taf_destroy(ser_tas);
return false;
}
const sc_char *LoadSerializer::readString(CONTEXT) {
const sc_char *string;
/* Get the next line, and complain if absent. */
string = taf_next_line(ser_tas);
if (!string) {
sc_error("readString: out of TAS data at line %ld\n", ser_tasline);
LONG_JUMP0
}
ser_tasline++;
return string;
}
sc_int LoadSerializer::readInt(CONTEXT) {
const sc_char *string;
sc_int value;
// Get line, and scan for a single integer; return it
R0FUNC0(readString, string)
if (sscanf(string, "%ld", &value) != 1) {
sc_error("readInt: invalid integer at line %ld\n", ser_tasline - 1);
LONG_JUMP0
}
return value;
}
sc_uint LoadSerializer::readUint(CONTEXT) {
const sc_char *string;
sc_uint value;
// Get line, and scan for a single integer; return it
R0FUNC0(readString, string)
if (sscanf(string, "%lu", &value) != 1) {
sc_error("readUint: invalid integer at line %ld\n", ser_tasline - 1);
LONG_JUMP0
}
return value;
}
sc_bool LoadSerializer::readBool(CONTEXT) {
const sc_char *string;
sc_uint value;
// Get line, and scan for a single integer; check it's a valid-looking flag, and return it.
R0FUNC0(readString, string)
if (sscanf(string, "%lu", &value) != 1) {
sc_error("readBool: invalid boolean at line %ld\n", ser_tasline - 1);
LONG_JUMP0
}
if (value != 0 && value != 1) {
sc_error("readBool: warning: suspect boolean at line %ld\n", ser_tasline - 1);
LONG_JUMP0
}
return value != 0;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,145 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ADRIFT_SERIALIZATION_H
#define ADRIFT_SERIALIZATION_H
#include "common/memstream.h"
#include "common/str.h"
#include "glk/adrift/scprotos.h"
#include "glk/jumps.h"
namespace Glk {
namespace Adrift {
/**
* Saving serializer class
*/
class SaveSerializer {
private:
sc_gameref_t _game;
sc_write_callbackref_t _callback;
void *_opaque;
Common::MemoryWriteStreamDynamic _buffer;
private:
/**
* Flush pending buffer contents
*/
void flush(sc_bool is_final);
/**
* add a character to the buffer.
*/
void writeChar(sc_char character);
/**
* Write a buffer
*/
void write(const sc_char *buffer, sc_int length);
/**
* Write a string
*/
void writeString(const sc_char *string);
/**
* Write an integer
*/
void writeInt(sc_int value);
/**
* Write a special/long integer
*/
void writeIntSpecial(sc_int value);
/**
* Write an unsigned integer
*/
void writeUint(sc_uint value);
/**
* Write a boolean
*/
void writeBool(sc_bool boolean);
public:
/**
* Constructor
*/
SaveSerializer(sc_gameref_t game, sc_write_callbackref_t callback, void *opaque) :
_game(game), _callback(callback), _opaque(opaque), _buffer(DisposeAfterUse::YES) {
assert(callback);
}
/**
* Save method
*/
void save();
};
/**
* Loading serializer class
*/
class LoadSerializer {
private:
sc_gameref_t _game;
sc_read_callbackref_t _callback;
void *_opaque;
sc_tafref_t ser_tas;
sc_int ser_tasline;
private:
/**
* Reads a string
*/
const sc_char *readString(CONTEXT);
/**
* Read a signed integer
*/
sc_int readInt(CONTEXT);
/**
* Read an unsigned integer
*/
sc_uint readUint(CONTEXT);
/**
* Read a boolean
*/
sc_bool readBool(CONTEXT);
public:
/**
* Constructor
*/
LoadSerializer(sc_gameref_t game, sc_read_callbackref_t callback, void *opaque) :
_game(game), _callback(callback), _opaque(opaque), ser_tas(nullptr), ser_tasline(0) {
assert(callback);
}
/**
* Does the loading
*/
bool load();
};
} // End of namespace Adrift
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,173 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/sxprotos.h"
#include "common/textconsole.h"
namespace Glk {
namespace Adrift {
/*
* Structure for representing a fake game save/restore file. Used to catch
* a serialized gamestate, and return it later on restore. For now we allow
* only one of these to exist.
*/
struct sx_scr_stream_t {
sc_byte *data;
sc_int length;
sc_bool is_open;
sc_bool is_writable;
};
static sx_scr_stream_t scr_serialization_stream = {nullptr, 0, FALSE, FALSE};
/*
* file_open_file_callback()
* file_read_file_callback()
* file_write_file_callback()
* file_close_file_callback()
*
* Fake a single gamestate save/restore file. Used to satisfy requests from
* the script to serialize and restore a gamestate. Only one "file" can
* exist, meaning that a script must restore a saved game before trying to
* save another.
*/
void *file_open_file_callback(sc_bool is_save) {
sx_scr_stream_t *const stream = &scr_serialization_stream;
/* Detect any problems due to scripting limitations. */
if (stream->is_open) {
error("File open error: %s",
"stream is in use (script limitation)");
return nullptr;
} else if (is_save && stream->data) {
error("File open error: %s",
"stream has not been read (script limitation)");
return nullptr;
}
/*
* Set up the stream for the requested mode. Act as if no such file if
* no data available for a read-only open.
*/
if (is_save) {
stream->data = nullptr;
stream->length = 0;
} else if (!stream->data)
return nullptr;
stream->is_open = TRUE;
stream->is_writable = is_save;
return stream;
}
sc_int file_read_file_callback(void *opaque, sc_byte *buffer, sc_int length) {
sx_scr_stream_t *const stream = (sx_scr_stream_t *)opaque;
sc_int bytes;
assert(opaque && buffer && length > 0);
/* Detect any problems with the callback parameters. */
if (stream != &scr_serialization_stream) {
error("File read error: %s", "stream is invalid");
return 0;
} else if (!stream->is_open) {
error("File read error: %s", "stream is not open");
return 0;
} else if (stream->is_writable) {
error("File read error: %s", "stream is not open for read");
return 0;
}
/* Read and remove the first block of data (or all if less than length). */
bytes = (stream->length < length) ? stream->length : length;
memcpy(buffer, stream->data, bytes);
memmove(stream->data, stream->data + bytes, stream->length - bytes);
stream->length -= bytes;
return bytes;
}
void file_write_file_callback(void *opaque, const sc_byte *buffer, sc_int length) {
sx_scr_stream_t *const stream = (sx_scr_stream_t *)opaque;
assert(opaque && buffer && length > 0);
/* Detect any problems with the callback parameters. */
if (stream != &scr_serialization_stream) {
error("File write error: %s", "stream is invalid");
return;
} else if (!stream->is_open) {
error("File write error: %s", "stream is not open");
return;
} else if (!stream->is_writable) {
error("File write error: %s", "stream is not open for write");
return;
}
/* Reallocate, then add this block of data to the buffer. */
stream->data = (sc_byte *)sx_realloc(stream->data, stream->length + length);
memcpy(stream->data + stream->length, buffer, length);
stream->length += length;
}
void file_close_file_callback(void *opaque) {
sx_scr_stream_t *const stream = (sx_scr_stream_t *)opaque;
assert(opaque);
/* Detect any problems with the callback parameters. */
if (stream != &scr_serialization_stream) {
error("File close error: %s", "stream is invalid");
return;
} else if (!stream->is_open) {
error("File close error: %s", "stream is not open");
return;
}
/*
* If closing after a read, free allocations, and return the stream to
* its empty state; if after write, leave the data for the later read.
*/
if (!stream->is_writable) {
sx_free(stream->data);
stream->data = nullptr;
stream->length = 0;
}
stream->is_writable = FALSE;
stream->is_open = FALSE;
}
/*
* file_cleanup()
*
* Free any pending allocations and clean up on completion of a script.
*/
void file_cleanup(void) {
sx_scr_stream_t *const stream = &scr_serialization_stream;
sx_free(stream->data);
stream->data = nullptr;
stream->length = 0;
stream->is_writable = FALSE;
stream->is_open = FALSE;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,292 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/sxprotos.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* The glob matching functions in this module are derived from an original
* (and somewhat hairy) glob.c posted by Arjan Kenter from the University
* of Twente, NL, in an assortment of minor variations between 1993 and 1997.
* The major modifications are:
*
* o Added checks to ensure that invalid range patterns such as "[a-" or
* "[-" don't cause the loops to walk off the end of the pattern string
* and (usually) result in SIGSEGV.
* o Moved from plain char to unsigned char to avoid signedness problems
* with range comparisons.
* o Skipped the leading '[' in the range checker; the original was treating
* it as a possible first value of 'r'.
* o Moved the range checker while() from the bottom of the loop to the top,
* to avoid problems with invalid ranges.
* o Gave 'l' in the range checker an initial value that ensures that it
* can never match until it's been re-assigned to 'r'.
* o Used a return value rather than multiple returns in the matcher, for
* better debugability.
* o Applied some const-correctness, and replaced some pointers by indexing.
* o Added scanf-like special cases, making ']' a valid part of a range if
* first, and '-' if last.
*
* This glob accepts * and ? wild cards, and [] ranges. It does not check
* whether the range string is valid (for example, terminates with ']'), but
* simply returns the best it can under those circumstances.
*
* Example call:
* glob_match ("a*b?c[A-Za-z_0-9]d*", some_string)
*/
/*
* glob_inrange_unsigned()
* glob_match_unsigned()
*
* Match a "[...]" character range, and match general glob wildcards. See
* above for notes on where these functions came from originally.
*/
static int glob_inrange_unsigned(const unsigned char **const pattern, unsigned char ch) {
const unsigned char *const pattern_ = *pattern;
int in_range = FALSE;
unsigned int l = 256, r = 0, index_;
/* Skip the leading '[' on entry to a range check. */
index_ = 1;
/* Special-case a range that has ']' as its first character. */
if (pattern_[index_] == ']') {
r = pattern_[index_++];
if (ch == r)
in_range = TRUE;
}
/*
* Check at the loop top, rather than the bottom, to avoid problems with
* invalid or uncompleted ranges.
*/
while (pattern_[index_] && pattern_[index_] != ']') {
r = pattern_[index_++];
if (r == '-') {
/* Special-case a range that has '-' as its last character. */
if (pattern_[index_] == ']' || !pattern_[index_]) {
if (ch == r)
in_range = TRUE;
break;
}
/* Break the loop on unterminated range ending with '-'. */
if (!pattern_[index_])
break;
r = pattern_[index_++];
if (l <= ch && ch <= r)
in_range = TRUE;
} else {
l = r;
if (ch == r)
in_range = TRUE;
}
}
/* Update pattern with characters consumed, return result. */
*pattern += index_;
return in_range;
}
static int glob_match_unsigned(const unsigned char *pattern, const unsigned char *string) {
int is_match = FALSE;
if (!*string) {
if (*pattern == '*')
is_match = glob_match_unsigned(pattern + 1, string);
else
is_match = !*pattern;
} else {
switch (*pattern) {
case '\0':
is_match = !*string;
break;
case '*':
if (glob_match_unsigned(pattern + 1, string))
is_match = TRUE;
else
is_match = glob_match_unsigned(pattern, string + 1);
break;
case '?':
is_match = glob_match_unsigned(pattern + 1, string + 1);
break;
case '[':
/*
* After a range check, we need to see if we hit the end of the
* pattern before recursively matching pattern + 1.
*/
is_match = glob_inrange_unsigned(&pattern, *string)
&& (!*pattern
|| glob_match_unsigned(pattern + 1, string + 1));
break;
default:
is_match = *pattern == *string
&& glob_match_unsigned(pattern + 1, string + 1);
break;
}
}
return is_match;
}
/* Structures and data for the self test function. */
struct sx_test_data_t {
const sc_char *const pattern;
const sc_char *const string;
};
static const sx_test_data_t SHOULD_MATCH[] = {
{"a", "a"}, {"abc", "abc"}, {"", ""},
{"*", ""}, {"*", "abc"}, {"*", "cba"},
{"*c", "c"}, {"*c", "abc"}, {"*c", "cbac"},
{"a*", "a"}, {"a*", "abc"}, {"a*", "abca"},
{"a*c", "ac"}, {"a*c", "abc"}, {"a*c", "abcbcbc"},
{"a**c", "ac"}, {"a**c", "abc"}, {"a**c", "abcbcbc"},
{"*b*", "b"}, {"*b*", "abc"}, {"*b*", "ab"}, {"*b*", "bc"},
{"?", "a"}, {"?", "z"}, {"?", "?"}, {"[?]", "?"},
{"a?", "aa"}, {"a?", "az"}, {"a?", "a?"},
{"?c", "ac"}, {"?c", "zc"}, {"?c", "?c"},
{"[abz]", "a"}, {"[abz]", "b"}, {"[abz]", "z"},
{"[a-c]", "a"}, {"[a-c]", "b"}, {"[a-c]", "c"},
{"[ac]b[ac]", "abc"}, {"[ac]b[ac]", "cba"},
{"[]]", "]"}, {"[]a-c]", "a"}, {"[]a-c]", "b"}, {"[]a-c]", "c"},
{"[?]", "?" }, {"[-]", "-"}, {"[z-]", "z"}, {"[z-]", "-"},
{"[][-]", "]"}, {"[][-]", "["}, {"[][-]", "-"},
{"[a-c-]", "a"}, {"[a-c-]", "b"}, {"[a-c-]", "c"}, {"[a-c-]", "-"},
{"*[a-z]*abc?xyz", "a<star>abcQxyz"}, {"*[a-z]*abc?xyz", "<star>aabcQxyz"},
{"*[a-z]*abc?xyz", "aabcQxyz"}, {"*[a-z]*abc?xyz", "<star>a<star>abcQxyz"},
{"???]", "abc]"}, {"[z-a]", "z"},
{"[a-z", "a"}, {"[a-", "a"}, {"[a", "a"}, {"[[", "["},
{nullptr, nullptr}
};
static const sx_test_data_t SHOULD_NOT_MATCH[] = {
{"a", "b"}, {"abc", "abd"}, {"a", ""}, {"", "a"},
{"*c", "a"}, {"*c", "ab"}, {"*c", "abca"},
{"a*", "c"}, {"a*", "cba"}, {"a*", "cbac"},
{"a*c", "ca"}, {"a*c", "cba"}, {"a*c", "cbababa"},
{"a**c", "ca"}, {"a**c", "cba"}, {"a**c", "cbababa"},
{"*b*", ""}, {"*b*", "z"}, {"*b*", "ac"}, {"*b*", "azc"},
{"?", ""}, {"?", "ab"}, {"?", "abc"}, {"[?]", "a"},
{"a?", "ca"}, {"a?", "cz"}, {"a?", "??"},
{"?c", "ab"}, {"?c", "zb"}, {"?c", "??"},
{"[bcy]", "a"}, {"[bcy]", "d"}, {"[bcy]", "z"},
{"[b-d]", "a"}, {"[b-d]", "e"}, {"[b-d]", ""}, {"[b-d]", "bc"},
{"[ac]b[ac]", "aaa"}, {"[ac]b[ac]", "bbb"}, {"[ac]b[ac]", "ccc"},
{"[]]", "["}, {"[]]", "a"}, {"[]a-c]", "z"},
{"[?]", "a" }, {"[-]", "a"}, {"[z-]", "a"},
{"[][-]", "a"}, {"[][-]", "z"},
{"[a-c-]", "z"},
{"*[a-z]*abc?xyz", "A<STAR>abcQxyz"}, {"*[a-z]*abc?xyz", "<STAR>AabcQxyz"},
{"*[a-z]*abc?xyz", "AabcQxyz"}, {"*[a-z]*abc?xyz", "aabcxyz"},
{"[z-a]", "a"}, {"[z-a]", "b"}, {"[", "a"}, {"[[", "a"},
{nullptr, nullptr}
};
/*
* glob_self_test()
*
* Sed quis custodiet ipsos custodes?
*/
static void glob_self_test(void) {
const sx_test_data_t *test;
sc_int errors;
/*
* Run each test case and compare against expected result. To avoid a lot
* of ugly casting, we use the main public glob_match() function.
*/
errors = 0;
for (test = SHOULD_MATCH; test->pattern; test++) {
if (!glob_match(test->pattern, test->string)) {
sx_error("glob_self_test: \"%s\", \"%s\""
" did not match, and should have matched\n",
test->pattern, test->string);
errors++;
}
}
for (test = SHOULD_NOT_MATCH; test->pattern; test++) {
if (glob_match(test->pattern, test->string)) {
sx_error("glob_self_test: \"%s\", \"%s\""
" matched, and should not have matched\n",
test->pattern, test->string);
errors++;
}
}
/*
* Abort if any error. As befits our distrustful nature, we won't even
* trust that sx_fatal() calls abort() (though it should).
*/
if (errors > 0) {
sx_fatal("glob_self_test: %ld self-test error%s found, aborting\n",
errors, (errors == 1) ? "" : "s");
}
}
/*
* glob_match()
*
* Adapter for the above globbing functions, presenting a more standard char-
* based interface. Here is where all the evil casting lives.
*/
sc_bool glob_match(const sc_char *pattern, const sc_char *string) {
static sc_bool initialized = FALSE;
const unsigned char *pattern_ = (const unsigned char *) pattern;
const unsigned char *string_ = (const unsigned char *) string;
sc_bool retval;
assert(pattern && string);
/* On the first call, run a self-test to verify basic glob matching. */
if (!initialized) {
/*
* To avoid lots of icky casting, the self-test uses the core public
* glob_match() that we're in right here to run its tests. So set
* initialized _before_ the test, to avoid infinite recursion.
*/
initialized = TRUE;
glob_self_test();
}
retval = glob_match_unsigned(pattern_, string_) != 0;
return retval;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,86 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCAREEXT_PROTOTYPES_H
#define SCAREEXT_PROTOTYPES_H
#include "glk/adrift/scare.h"
#include "common/stream.h"
namespace Glk {
namespace Adrift {
/* True and false, unless already defined. */
#ifndef FALSE
# define FALSE 0
#endif
#ifndef TRUE
# define TRUE (!FALSE)
#endif
/* Alias typedef for a test script. */
typedef Common::SeekableReadStream *sx_script;
/* Typedef representing a test descriptor. */
typedef struct sx_test_descriptor_s {
const sc_char *name;
sc_game game;
sx_script script;
} sx_test_descriptor_t;
/*
* Small utility and wrapper functions.
*/
extern void sx_trace(MSVC_PRINTF const sc_char *format, ...) GCC_PRINTF(1, 2);
extern void sx_error(MSVC_PRINTF const sc_char *format, ...) GCC_PRINTF(1, 2);
extern void sx_fatal(MSVC_PRINTF const sc_char *format, ...) GCC_PRINTF(1, 2);
extern void *sx_malloc(size_t size);
extern void *sx_realloc(void *pointer, size_t size);
extern void sx_free(void *pointer);
extern Common::SeekableReadStream *sx_fopen(const sc_char *name,
const sc_char *extension, const sc_char *mode);
extern sc_char *sx_trim_string(sc_char *string);
extern sc_char *sx_normalize_string(sc_char *string);
/* Test controller function. */
extern sc_int test_run_game_tests(const sx_test_descriptor_t tests[],
sc_int count, sc_bool is_verbose);
/* Globbing function. */
extern sc_bool glob_match(const sc_char *pattern, const sc_char *string);
/* Script running and checking functions. */
//extern void scr_test_failed(const sc_char *format, const sc_char *string);
//extern void scr_set_verbose(sc_bool flag);
/* Serialization helper for script running and checking. */
extern void *file_open_file_callback(sc_bool is_save);
extern sc_int file_read_file_callback(void *opaque,
sc_byte *buffer, sc_int length);
extern void file_write_file_callback(void *opaque,
const sc_byte *buffer, sc_int length);
extern void file_close_file_callback(void *opaque);
extern void file_cleanup(void);
} // End of namespace Adrift
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,567 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/sxprotos.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o The script file format is as follows. Lines beginning '#' are comments
* and empty lines are ignored, otherwise the file is composed of sections.
* The first section line is one that starts with either '>' or '~'. This
* is the next command. The following lines, up to the next '>' or '~'
* section start, are concatenated into the expectation for the command.
* Expectations are glob patterns. Commands starting with '>' are sent to
* the game; those starting with '~' are sent to the SCARE debugger. Before
* the game is running, debugger commands are valid. The first non-debugger
* command starts the game running. An empty debugger command ('~') that
* follows any introductory debugger commands both starts the game and sets
* an expectation for the game's introductory text. After the game has
* completed (or quit), only debugger commands are valid; others are ignored.
*
* o The script file structure is intentionally simple, but might be too
* simple for some purposes.
*/
/* Assorted definitions and constants. */
static const sc_int LINE_BUFFER_SIZE = 256;
static const sc_char NUL = '\0';
static const sc_char SCRIPT_COMMENT = '#';
static const sc_char GAME_COMMAND = '>';
static const sc_char DEBUG_COMMAND = '~';
/* Verbosity, and references to the game and script being processed. */
static sc_bool scr_is_verbose = FALSE;
static sc_game scr_game = NULL;
static sx_script scr_script = NULL;
/* Script line number, and count of errors registered for the script. */
static sc_int scr_line_number = 0;
static sc_int scr_errors = 0;
/*
* Current expected output, and game accumulated output, used by the
* expectation checking function.
*/
static sc_char *scr_expectation = NULL;
static sc_char *scr_game_output = NULL;
/*
* scr_set_verbose()
*
* Set error reporting for expectation errors detected in the script.
*/
void scr_set_verbose(sc_bool flag) {
scr_is_verbose = flag;
}
/*
* scr_test_message()
* scr_test_failed()
*
* Simple common message and test case failure handling functions. The second
* is used by the serialization helper, so is not static.
*/
static void scr_test_message(const sc_char *format, const sc_char *string) {
if (scr_is_verbose) {
sx_trace("--- ");
sx_trace(format, string);
sx_trace("\n");
}
}
void scr_test_failed(const sc_char *format, const sc_char *string) {
assert(format && string);
if (scr_is_verbose) {
if (scr_line_number > 0)
sx_trace("--- Near line %ld: ", scr_line_number);
else
sx_trace("--- ");
sx_trace(format, string);
sx_trace("\n");
}
scr_errors++;
}
/*
* scr_is_line_type()
* scr_is_line_comment_or_empty()
* scr_is_line_game_command()
* scr_is_line_debug_command()
* scr_is_line_command()
* scr_is_line_empty_debug_command()
*
* Line classifiers, return TRUE if line has the given type.
*/
static sc_bool scr_is_line_type(const sc_char *line, sc_char type) {
return line[0] == type;
}
static sc_bool scr_is_line_comment_or_empty(const sc_char *line) {
return scr_is_line_type(line, SCRIPT_COMMENT)
|| strspn(line, "\t\n\v\f\r ") == strlen(line);
}
static sc_bool scr_is_line_game_command(const sc_char *line) {
return scr_is_line_type(line, GAME_COMMAND);
}
static sc_bool scr_is_line_debug_command(const sc_char *line) {
return scr_is_line_type(line, DEBUG_COMMAND);
}
static sc_bool scr_is_line_command(const sc_char *line) {
return scr_is_line_game_command(line) || scr_is_line_debug_command(line);
}
static sc_bool scr_is_line_empty_debug_command(const sc_char *line) {
return scr_is_line_type(line, DEBUG_COMMAND) && line[1] == NUL;
}
/* Script location, a pair holding the file location and the line number. */
struct sx_scr_location_t {
size_t position;
sc_int line_number;
};
typedef sx_scr_location_t *sx_scr_locationref_t;
/*
* scr_save_location()
* scr_restore_location()
*
* Save and restore the script location in the given structure.
*/
static void scr_save_location(sx_script script, sx_scr_locationref_t location) {
location->position = script->pos();
location->line_number = scr_line_number;
}
static void scr_restore_location(sx_script script, sx_scr_locationref_t location) {
script->seek(location->position);
scr_line_number = location->line_number;
}
/*
* scr_get_next_line()
*
* Helper for scr_get_next_section(). Returns the next non-comment, non-empty
* line from the script. Returns NULL if no more lines, or on file error. The
* return string is allocated, and it's the caller's responsibility to free it.
*/
static sc_char *scr_get_next_line(sx_script script) {
sc_char *buffer, *line = NULL;
/* Allocate a buffer for line reads. */
buffer = (sc_char *)sx_malloc(LINE_BUFFER_SIZE);
/* Read until a significant line is found, or end of file or error. */
while (adrift_fgets(buffer, LINE_BUFFER_SIZE, script)) {
scr_line_number++;
if (!scr_is_line_comment_or_empty(buffer)) {
line = buffer;
break;
}
}
/* If no significant line read, free the read buffer. */
if (!line)
sx_free(buffer);
return line;
}
/*
* scr_concatenate()
*
* Helper for scr_get_next_section(). Builds a string formed by concatenating
* the second argument to the first. If the first is NULL, acts as strdup()
* instead.
*/
static sc_char *scr_concatenate(sc_char *string, const sc_char *buffer) {
/* If string is not null, concatenate buffer, otherwise duplicate. */
if (string) {
string = (sc_char *)sx_realloc(string,
strlen(string) + 1 + strlen(buffer) + 1);
strcat(string, " ");
strcat(string, buffer);
} else {
string = (sc_char *)sx_malloc(strlen(buffer) + 1);
strcpy(string, buffer);
}
return string;
}
/*
* scr_get_next_section()
*
* Retrieve the next command and any expectation from the script file.
* Returns TRUE if a line is returned, FALSE at end-of-file. Expectation may
* be NULL if this paragraph doesn't have one; command may not be (if TRUE is
* returned). Command and expectation are allocated, and the caller needs to
* free them.
*/
static sc_bool scr_get_next_section(sx_script script, sc_char **command, sc_char **expectation) {
sc_char *line, *first_line, *other_lines;
sx_scr_location_t location;
/* Clear initial line accumulation. */
first_line = other_lines = NULL;
/* Read the next significant line from the script. */
scr_save_location(script, &location);
line = scr_get_next_line(script);
while (line) {
/* If already a first line, this is other lines or section end. */
if (first_line) {
/*
* If we found the start of the next section, reset the script
* location that saved on the line read, and we're done.
*/
if (scr_is_line_command(line)) {
scr_restore_location(script, &location);
sx_free(line);
break;
} else
other_lines = scr_concatenate(other_lines, line);
} else
first_line = scr_concatenate(first_line, line);
sx_free(line);
/* Read the next significant line from the script. */
scr_save_location(script, &location);
line = scr_get_next_line(script);
}
/* Clean up and return nothing on file error. */
if (script->err()) {
scr_test_failed("Script error: Failed reading script input file", "");
sx_free(first_line);
sx_free(other_lines);
return FALSE;
}
/* Return the command and the matching expectation string, if any. */
if (first_line) {
*command = sx_normalize_string(first_line);
*expectation = other_lines ? sx_normalize_string(other_lines) : NULL;
return TRUE;
}
/* End of file, no command section read. */
return FALSE;
}
/*
* scr_expect()
* scr_verify_expectation()
*
* Set an expectation, and compare the expectation, if any, with the
* accumulated game output, using glob matching. scr_verify_expectation()
* increments the error count if the expectation isn't met, and reports the
* error if required. It then frees both the expectation and accumulated
* input.
*/
static void scr_expect(sc_char *expectation) {
/*
* Save the expectation, and set up collection of game output if needed.
* And if not needed, ensure expectation and game output are cleared.
*/
if (expectation) {
scr_expectation = (sc_char *)sx_malloc(strlen(expectation) + 1);
strcpy(scr_expectation, expectation);
scr_game_output = (sc_char *)sx_malloc(1);
strcpy(scr_game_output, "");
} else {
sx_free(scr_expectation);
scr_expectation = NULL;
sx_free(scr_game_output);
scr_game_output = NULL;
}
}
static void scr_verify_expectation(void) {
/* Compare expected with actual, and handle any error detected. */
if (scr_expectation && scr_game_output) {
scr_game_output = sx_normalize_string(scr_game_output);
if (!glob_match(scr_expectation, scr_game_output)) {
scr_test_failed("Expectation error:", "");
scr_test_message(" Expected: \"%s\"", scr_expectation);
scr_test_message(" Received: \"%s\"", scr_game_output);
}
}
/* Dispose of the expectation and accumulated game output. */
sx_free(scr_expectation);
scr_expectation = NULL;
sx_free(scr_game_output);
scr_game_output = NULL;
}
/*
* scr_execute_debugger_command()
*
* Convenience interface for immediate execution of debugger commands. This
* function directly calls the debugger interface, and because it's immediate,
* can also verify the expectation before returning to the caller.
*
* Also, it turns on the game debugger, and it's the caller's responsibility
* to turn it off when it's no longer needed.
*/
static void scr_execute_debugger_command(const sc_char *command, sc_char *expectation) {
sc_bool status;
/* Set up the expectation. */
scr_expect(expectation);
/*
* Execute the command via the debugger interface. The "+1" on command
* skips the leading '~' read in from the game script.
*/
sc_set_game_debugger_enabled(scr_game, TRUE);
status = sc_run_game_debugger_command(scr_game, command + 1);
if (!status) {
scr_test_failed("Script error:"
" Debug command \"%s\" is not valid", command);
}
/* Check expectations immediately. */
scr_verify_expectation();
}
/*
* scr_read_line_callback()
*
* Check any expectations set for the last line. Consult the script for the
* next line to feed to the game, and any expectation for the game output
* for that line. If there is an expectation, save it and set scr_game_output
* to "" so that accumulation begins. Then pass the next line of data back
* to the game.
*/
static sc_bool scr_read_line_callback(sc_char *buffer, sc_int length) {
sc_char *command, *expectation;
assert(buffer && length > 0);
/* Check pending expectation, and clear settings for the next line. */
scr_verify_expectation();
/* Get the next line-expectation pair from the script stream. */
if (scr_get_next_section(scr_script, &command, &expectation)) {
if (scr_is_line_debug_command(command)) {
/* The debugger persists where debug commands are adjacent. */
scr_execute_debugger_command(command, expectation);
sx_free(command);
sx_free(expectation);
/*
* Returning FALSE here causes the game to re-prompt. We could
* loop (or tail recurse) ourselves, but returning is simpler.
*/
return FALSE;
} else
sc_set_game_debugger_enabled(scr_game, FALSE);
if (scr_is_line_game_command(command)) {
/* Set up the expectation. */
scr_expect(expectation);
/* Copy out the line to the return buffer, and free the line. */
strncpy(buffer, command + 1, length);
buffer[length - 1] = NUL;
sx_free(command);
sx_free(expectation);
return TRUE;
}
/* Neither a '~' nor a '>' command. */
scr_test_failed("Script error:"
" Command \"%s\" is not valid, ignored", command);
sx_free(command);
sx_free(expectation);
return FALSE;
}
/* Ensure the game debugger is off after this section. */
sc_set_game_debugger_enabled(scr_game, FALSE);
/*
* We reached the end of the script without finding a "quit" command.
* Supply one here, then. In the unlikely even that this does not quit
* the game, we'll iterate on this.
*/
assert(length > 4);
strcpy(buffer, "quit");
return TRUE;
}
/*
* scr_print_string_callback()
*
* Handler function for game output. Accumulates strings received from the
* game into scr_game_output, unless no expectation is set, in which case
* the current game output will be NULL, and we can simply save the effort.
*/
static void scr_print_string_callback(const sc_char *string) {
assert(string);
if (scr_game_output) {
scr_game_output = (sc_char *)sx_realloc(scr_game_output,
strlen(scr_game_output)
+ strlen(string) + 1);
strcat(scr_game_output, string);
}
}
/*
* scr_start_script()
*
* Set up game monitoring so that each request for a line from the game
* enters this module. For each request, we grab the next "send" and
* "expect" pair from the script, satisfy the request with the send data,
* and match against the expectations on next request or on finalization.
*/
void scr_start_script(sc_game game, sx_script script) {
sc_char *command, *expectation;
sx_scr_location_t location;
assert(game && script);
/* Save the game and stream, and clear the line number and errors count. */
assert(!scr_game && !scr_script);
scr_game = game;
scr_script = script;
scr_line_number = 0;
scr_errors = 0;
/* Set up our callback functions to catch game i/o. */
stub_attach_handlers(scr_read_line_callback, scr_print_string_callback,
file_open_file_callback, file_read_file_callback,
file_write_file_callback, file_close_file_callback);
/*
* Handle any initial debugging commands, terminating on either a non-
* debugging one or an expectation for the game intro.
*/
scr_script->seek(0);
scr_save_location(scr_script, &location);
while (scr_get_next_section(scr_script, &command, &expectation)) {
if (scr_is_line_debug_command(command)) {
if (scr_is_line_empty_debug_command(command)) {
/* It's an intro expectation - set and break loop. */
scr_expect(expectation);
sx_free(command);
sx_free(expectation);
break;
} else {
/* It's a full debug command - execute it as one. */
scr_execute_debugger_command(command, expectation);
sx_free(command);
sx_free(expectation);
}
} else {
/*
* It's an ordinary section - rewind so that it's the first one
* handled in the callback, and break loop.
*/
scr_restore_location(scr_script, &location);
sx_free(command);
sx_free(expectation);
break;
}
/* Note script position before reading the next section. */
scr_save_location(scr_script, &location);
}
/* Ensure the game debugger is off after this section. */
sc_set_game_debugger_enabled(scr_game, FALSE);
}
/*
* scr_finalize_script()
*
* Match any final received string against a possible expectation, and then
* clear local records of the game, stream, and error count. Returns the
* count of errors detected during the script.
*/
sc_int scr_finalize_script(void) {
sc_char *command, *expectation;
sc_int errors;
/* Check pending expectation, and clear settings. */
scr_verify_expectation();
/* Drain the remainder of the script, ignoring non-debugging commands. */
while (scr_get_next_section(scr_script, &command, &expectation)) {
if (scr_is_line_debug_command(command)) {
scr_execute_debugger_command(command, expectation);
sx_free(command);
sx_free(expectation);
} else {
/* Complain about script entries ignored because the game ended. */
scr_test_failed("Script error:"
" Game completed, command \"%s\" ignored", command);
sx_free(command);
sx_free(expectation);
}
}
/* Ensure the game debugger is off after this section. */
sc_set_game_debugger_enabled(scr_game, FALSE);
/*
* Remove our callback functions from the stubs, and "close" any retained
* stream data from game save/load tests.
*/
stub_detach_handlers();
file_cleanup();
/* Clear local records of game stream, line number, and errors count. */
errors = scr_errors;
scr_game = NULL;
scr_script = NULL;
scr_line_number = 0;
scr_errors = 0;
return errors;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,373 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/sxprotos.h"
#include "common/str.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Better, or perhaps less strace-like, tracing might be more beneficial
* for game script debugging.
*/
/* Stubs trace flag. */
static sc_bool stub_trace = FALSE;
/*
* Input/output handler functions. If assigned, calls to os_* functions are
* routed here to allow the script runner to catch interpeter i/o.
*/
static sc_bool(*stub_read_line)(sc_char *, sc_int) = nullptr;
static void (*stub_print_string)(const sc_char *) = nullptr;
static void *(*stub_open_file)(sc_bool) = nullptr;
static sc_int(*stub_read_file)(void *, sc_byte *, sc_int) = nullptr;
static void (*stub_write_file)(void *, const sc_byte *, sc_int) = nullptr;
static void (*stub_close_file)(void *) = nullptr;
/* Flags for whether to report tags and resources via stub_print_string(). */
static sc_int stub_show_resources = 0;
static sc_int stub_show_tags = 0;
/*
* stub_attach_handlers()
* stub_detach_handlers()
*
* Attach input/output handler functions, and reset to NULLs.
*/
void
stub_attach_handlers(sc_bool(*read_line)(sc_char *, sc_int),
void (*print_string)(const sc_char *),
void *(*open_file)(sc_bool),
sc_int(*read_file)(void *, sc_byte *, sc_int),
void (*write_file)(void *, const sc_byte *, sc_int),
void (*close_file)(void *)) {
stub_read_line = read_line;
stub_print_string = print_string;
stub_open_file = open_file;
stub_read_file = read_file;
stub_write_file = write_file;
stub_close_file = close_file;
stub_show_resources = 0;
stub_show_tags = 0;
}
void
stub_detach_handlers(void) {
stub_read_line = nullptr;
stub_print_string = nullptr;
stub_open_file = nullptr;
stub_read_file = nullptr;
stub_write_file = nullptr;
stub_close_file = nullptr;
stub_show_resources = 0;
stub_show_tags = 0;
}
/*
* stub_adjust_test_control()
* stub_catch_test_control()
*
* Trap testing control tags from the game, incrementing or decrementing
* stub_show_resources or stub_show_tags if a testing control tag arrives.
* Returns TRUE if the tag trapped was one of our testing ones.
*/
static void
stub_adjust_test_control(sc_int *control, sc_bool is_begin) {
*control += is_begin ? 1 : (*control > 0 ? -1 : 0);
}
static sc_bool
stub_catch_test_control(sc_int tag, const sc_char *argument) {
if (tag == SC_TAG_UNKNOWN && argument) {
sc_bool is_begin;
const sc_char *name;
is_begin = !(argument[0] == '/');
name = is_begin ? argument : argument + 1;
if (sc_strcasecmp(name, "sxshowresources") == 0) {
stub_adjust_test_control(&stub_show_resources, is_begin);
return TRUE;
} else if (sc_strcasecmp(name, "sxshowtags") == 0) {
stub_adjust_test_control(&stub_show_tags, is_begin);
return TRUE;
}
}
return FALSE;
}
/*
* stub_notnull()
*
* Returns the string address passed in, or "(nil)" if NULL, for printf
* safety. Most libc's handle this themselves, but it's not defined by ANSI.
*/
static const sc_char *
stub_notnull(const sc_char *string) {
return string ? string : "(nil)";
}
/*
* os_*()
*
* Stub functions called by the interpreter core.
*/
void
os_print_tag(sc_int tag, const sc_char *argument) {
if (stub_trace)
sx_trace("os_print_tag (%ld, \"%s\")\n", tag, stub_notnull(argument));
if (!stub_catch_test_control(tag, argument)) {
if (stub_print_string) {
if (stub_show_tags > 0) {
stub_print_string("<<Tag: id=");
Common::String buffer = Common::String::format("%ld", tag);
stub_print_string(buffer.c_str());
stub_print_string(", argument=\"");
stub_print_string(stub_notnull(argument));
stub_print_string("\">>");
} else if (tag == SC_TAG_WAITKEY || tag == SC_TAG_CLS)
stub_print_string(" ");
}
}
}
void
os_print_string(const sc_char *string) {
if (stub_trace)
sx_trace("os_print_string (\"%s\")\n", stub_notnull(string));
if (stub_print_string)
stub_print_string(string);
}
void
os_print_string_debug(const sc_char *string) {
if (stub_trace)
sx_trace("os_print_string_debug (\"%s\")\n", stub_notnull(string));
if (stub_print_string)
stub_print_string(string);
}
void
os_play_sound(const sc_char *filepath,
sc_int offset, sc_int length, sc_bool is_looping) {
if (stub_trace)
sx_trace("os_play_sound (\"%s\", %ld, %ld, %s)\n",
stub_notnull(filepath), offset, length,
is_looping ? "true" : "false");
if (stub_print_string && stub_show_resources > 0) {
sc_char buffer[32];
stub_print_string("<<Sound: id=\"");
stub_print_string(stub_notnull(filepath));
stub_print_string("\", offset=");
Common::sprintf_s(buffer, "%ld", offset);
stub_print_string(buffer);
stub_print_string(", length=");
Common::sprintf_s(buffer, "%ld", length);
stub_print_string(buffer);
stub_print_string(", looping=");
stub_print_string(is_looping ? "true" : "false");
stub_print_string(">>");
}
}
void
os_stop_sound(void) {
if (stub_trace)
sx_trace("os_stop_sound ()\n");
if (stub_print_string && stub_show_resources > 0)
stub_print_string("<<Sound: stop>>");
}
void
os_show_graphic(const sc_char *filepath, sc_int offset, sc_int length) {
if (stub_trace)
sx_trace("os_show_graphic (\"%s\", %ld, %ld)\n",
stub_notnull(filepath), offset, length);
if (stub_print_string && stub_show_resources > 0) {
sc_char buffer[32];
stub_print_string("<<Graphic: id=\"");
stub_print_string(stub_notnull(filepath));
stub_print_string("\", offset=");
Common::sprintf_s(buffer, "%ld", offset);
stub_print_string(buffer);
stub_print_string(", length=");
Common::sprintf_s(buffer, "%ld", length);
stub_print_string(buffer);
stub_print_string(">>");
}
}
sc_bool
os_read_line(sc_char *buffer, sc_int length) {
sc_bool status;
if (stub_read_line)
status = stub_read_line(buffer, length);
else {
assert(buffer && length > 4);
Common::sprintf_s(buffer, "%s", "quit");
status = TRUE;
}
if (stub_trace) {
if (status)
sx_trace("os_read_line (\"%s\", %ld) -> true\n",
stub_notnull(buffer), length);
else
sx_trace("os_read_line (\"...\", %ld) -> false\n", length);
}
return status;
}
sc_bool
os_read_line_debug(sc_char *buffer, sc_int length) {
assert(buffer && length > 8);
Common::sprintf_s(buffer, "%s", "continue");
if (stub_trace)
sx_trace("os_read_line_debug (\"%s\", %ld) -> true\n", buffer, length);
return TRUE;
}
sc_bool
os_confirm(sc_int type) {
if (stub_trace)
sx_trace("os_confirm (%ld) -> true\n", type);
return TRUE;
}
void *
os_open_file(sc_bool is_save) {
void *opaque;
if (stub_open_file)
opaque = stub_open_file(is_save);
else
opaque = nullptr;
if (stub_trace) {
if (opaque)
sx_trace("os_open_file (%s) -> %p\n",
is_save ? "true" : "false", opaque);
else
sx_trace("os_open_file (%s) -> null\n", is_save ? "true" : "false");
}
return opaque;
}
sc_int
os_read_file(void *opaque, sc_byte *buffer, sc_int length) {
sc_int bytes;
if (stub_read_file)
bytes = stub_read_file(opaque, buffer, length);
else
bytes = 0;
if (stub_trace)
sx_trace("os_read_file (%p, %p, %ld) -> %ld\n",
opaque, buffer, length, bytes);
return bytes;
}
void
os_write_file(void *opaque, const sc_byte *buffer, sc_int length) {
if (stub_write_file)
stub_write_file(opaque, buffer, length);
if (stub_trace)
sx_trace("os_write_file (%p, %p, %ld)\n", opaque, buffer, length);
}
void
os_close_file(void *opaque) {
if (stub_close_file)
stub_close_file(opaque);
if (stub_trace)
sx_trace("os_close_file (%p)\n", opaque);
}
void
os_display_hints(sc_game game) {
if (stub_trace)
sx_trace("os_display_hints (%p)\n", game);
if (stub_print_string) {
sc_game_hint hint;
for (hint = sc_get_first_game_hint(game);
hint; hint = sc_get_next_game_hint(game, hint)) {
const sc_char *hint_text;
stub_print_string(sc_get_game_hint_question(game, hint));
stub_print_string("\n");
hint_text = sc_get_game_subtle_hint(game, hint);
if (hint_text) {
stub_print_string("- ");
stub_print_string(hint_text);
stub_print_string("\n");
}
hint_text = sc_get_game_unsubtle_hint(game, hint);
if (hint_text) {
stub_print_string("- ");
stub_print_string(hint_text);
stub_print_string("\n");
}
}
}
}
/*
* stub_debug_trace()
*
* Set stubs tracing on/off.
*/
void
stub_debug_trace(sc_bool flag) {
stub_trace = flag;
}
} // End of namespace Adrift
} // End of namespace Glk

View File

@@ -0,0 +1,245 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/sxprotos.h"
#include "common/debug.h"
#include "common/str.h"
#include "common/file.h"
#include "common/textconsole.h"
namespace Glk {
namespace Adrift {
/*
* sx_trace()
*
* Debugging trace function; printf wrapper that writes to stdout. Note that
* this differs from sc_trace(), which writes to stderr. We use stdout so
* that trace output is synchronized to test expectation failure messages.
*/
void sx_trace(const sc_char *format, ...) {
va_list ap;
assert(format);
va_start(ap, format);
Common::String line = Common::String::vformat(format, ap);
va_end(ap);
debug("%s", line.c_str());
}
/*
* sx_error()
* sx_fatal()
*
* Error reporting functions. sx_error() prints a message and continues.
* sx_fatal() prints a message, then calls abort().
*/
void sx_error(const sc_char *format, ...) {
va_list ap;
assert(format);
va_start(ap, format);
Common::String line = Common::String::vformat(format, ap);
va_end(ap);
warning("%s", line.c_str());
}
void sx_fatal(const sc_char *format, ...) {
va_list ap;
assert(format);
va_start(ap, format);
Common::String line = Common::String::vformat(format, ap);
va_end(ap);
error("%s", line.c_str());
}
/* Unique non-heap address for zero size malloc() and realloc() requests. */
static void *sx_zero_allocation = &sx_zero_allocation;
/*
* sx_malloc()
* sx_realloc()
* sx_free()
*
* Non-failing wrappers around malloc functions. Newly allocated memory is
* cleared to zero. In ANSI/ISO C, zero byte allocations are implementation-
* defined, so we have to take special care to get predictable behavior.
*/
void *sx_malloc(size_t size) {
void *allocated;
if (size == 0)
return sx_zero_allocation;
allocated = malloc(size);
if (!allocated)
sx_fatal("sx_malloc: requested %lu bytes\n", (sc_uint) size);
else if (allocated == sx_zero_allocation)
sx_fatal("sx_malloc: zero-byte allocation address returned\n");
memset(allocated, 0, size);
return allocated;
}
void *sx_realloc(void *pointer, size_t size) {
void *allocated;
if (size == 0) {
sx_free(pointer);
return sx_zero_allocation;
}
if (pointer == sx_zero_allocation)
pointer = nullptr;
allocated = realloc(pointer, size);
if (!allocated)
sx_fatal("sx_realloc: requested %lu bytes\n", (sc_uint) size);
else if (allocated == sx_zero_allocation)
sx_fatal("sx_realloc: zero-byte allocation address returned\n");
if (!pointer)
memset(allocated, 0, size);
return allocated;
}
void sx_free(void *pointer) {
if (sx_zero_allocation != &sx_zero_allocation)
sx_fatal("sx_free: write to zero-byte allocation address detected\n");
if (pointer && pointer != sx_zero_allocation)
free(pointer);
}
/*
* sx_fopen()
*
* Open a file for a given test name with the extension and mode supplied.
* Returns NULL if unsuccessful.
*/
Common::SeekableReadStream *sx_fopen(const sc_char *name, const sc_char *extension, const sc_char *mode) {
assert(name && extension && mode);
Common::Path filename(Common::String::format("%s.%s", name, extension));
Common::File *f = new Common::File();
if (f->open(filename))
return f;
delete f;
return nullptr;
}
/* Miscellaneous general ascii constants. */
static const sc_char NUL = '\0';
/*
* sx_isspace()
* sx_isprint()
*
* Built in replacements for locale-sensitive libc ctype.h functions.
*/
static sc_bool sx_isspace(sc_char character) {
static const sc_char *const WHITESPACE = "\t\n\v\f\r ";
return character != NUL && strchr(WHITESPACE, character) != nullptr;
}
static sc_bool sx_isprint(sc_char character) {
static const sc_int MIN_PRINTABLE = ' ', MAX_PRINTABLE = '~';
return character >= MIN_PRINTABLE && character <= MAX_PRINTABLE;
}
/*
* sx_trim_string()
*
* Trim leading and trailing whitespace from a string. Modifies the string
* in place, and returns the string address for convenience.
*/
sc_char *sx_trim_string(sc_char *string) {
sc_int index_;
assert(string);
for (index_ = strlen(string) - 1;
index_ >= 0 && sx_isspace(string[index_]); index_--)
string[index_] = NUL;
for (index_ = 0; sx_isspace(string[index_]);)
index_++;
memmove(string, string + index_, strlen(string) - index_ + 1);
return string;
}
/*
* sx_normalize_string()
*
* Trim a string, set all runs of whitespace to a single space character,
* and convert all non-printing characters to '?'. Modifies the string in
* place, and returns the string address for convenience.
*/
sc_char *sx_normalize_string(sc_char *string) {
sc_int index_;
assert(string);
string = sx_trim_string(string);
for (index_ = 0; string[index_] != NUL; index_++) {
if (sx_isspace(string[index_])) {
sc_int cursor;
string[index_] = ' ';
for (cursor = index_ + 1; sx_isspace(string[cursor]);)
cursor++;
memmove(string + index_ + 1,
string + cursor, strlen(string + cursor) + 1);
} else if (!sx_isprint(string[index_]))
string[index_] = '?';
}
return string;
}
char *adrift_fgets(char *buf, int max, Common::SeekableReadStream *s) {
char *ptr = buf;
char c;
while (s->pos() < s->size() && --max > 0) {
c = s->readByte();
if (c == '\n' || c == '\0')
break;
*ptr++ = c;
}
*ptr++ = '\0';
return buf;
}
} // End of namespace Adrift
} // End of namespace Glk