Initial commit
This commit is contained in:
63
engines/glk/adrift/adrift.cpp
Normal file
63
engines/glk/adrift/adrift.cpp
Normal 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
|
||||
78
engines/glk/adrift/adrift.h
Normal file
78
engines/glk/adrift/adrift.h
Normal 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
|
||||
177
engines/glk/adrift/detection.cpp
Normal file
177
engines/glk/adrift/detection.cpp
Normal 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
|
||||
72
engines/glk/adrift/detection.h
Normal file
72
engines/glk/adrift/detection.h
Normal 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
|
||||
2517
engines/glk/adrift/detection_tables.h
Normal file
2517
engines/glk/adrift/detection_tables.h
Normal file
File diff suppressed because it is too large
Load Diff
3105
engines/glk/adrift/os_glk.cpp
Normal file
3105
engines/glk/adrift/os_glk.cpp
Normal file
File diff suppressed because it is too large
Load Diff
39
engines/glk/adrift/os_glk.h
Normal file
39
engines/glk/adrift/os_glk.h
Normal 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
188
engines/glk/adrift/scare.h
Normal 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
|
||||
2490
engines/glk/adrift/scdebug.cpp
Normal file
2490
engines/glk/adrift/scdebug.cpp
Normal file
File diff suppressed because it is too large
Load Diff
771
engines/glk/adrift/scevents.cpp
Normal file
771
engines/glk/adrift/scevents.cpp
Normal 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
|
||||
1543
engines/glk/adrift/scexpr.cpp
Normal file
1543
engines/glk/adrift/scexpr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1001
engines/glk/adrift/scgamest.cpp
Normal file
1001
engines/glk/adrift/scgamest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
188
engines/glk/adrift/scgamest.h
Normal file
188
engines/glk/adrift/scgamest.h
Normal 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
|
||||
1015
engines/glk/adrift/scinterf.cpp
Normal file
1015
engines/glk/adrift/scinterf.cpp
Normal file
File diff suppressed because it is too large
Load Diff
9654
engines/glk/adrift/sclibrar.cpp
Normal file
9654
engines/glk/adrift/sclibrar.cpp
Normal file
File diff suppressed because it is too large
Load Diff
515
engines/glk/adrift/sclocale.cpp
Normal file
515
engines/glk/adrift/sclocale.cpp
Normal 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
|
||||
570
engines/glk/adrift/scmemos.cpp
Normal file
570
engines/glk/adrift/scmemos.cpp
Normal 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
|
||||
581
engines/glk/adrift/scnpcs.cpp
Normal file
581
engines/glk/adrift/scnpcs.cpp
Normal 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
|
||||
928
engines/glk/adrift/scobjcts.cpp
Normal file
928
engines/glk/adrift/scobjcts.cpp
Normal 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
|
||||
1945
engines/glk/adrift/scparser.cpp
Normal file
1945
engines/glk/adrift/scparser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1427
engines/glk/adrift/scprintf.cpp
Normal file
1427
engines/glk/adrift/scprintf.cpp
Normal file
File diff suppressed because it is too large
Load Diff
966
engines/glk/adrift/scprops.cpp
Normal file
966
engines/glk/adrift/scprops.cpp
Normal 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
|
||||
770
engines/glk/adrift/scprotos.h
Normal file
770
engines/glk/adrift/scprotos.h
Normal 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
|
||||
315
engines/glk/adrift/scresour.cpp
Normal file
315
engines/glk/adrift/scresour.cpp
Normal 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
|
||||
1050
engines/glk/adrift/screstrs.cpp
Normal file
1050
engines/glk/adrift/screstrs.cpp
Normal file
File diff suppressed because it is too large
Load Diff
2094
engines/glk/adrift/scrunner.cpp
Normal file
2094
engines/glk/adrift/scrunner.cpp
Normal file
File diff suppressed because it is too large
Load Diff
732
engines/glk/adrift/sctaffil.cpp
Normal file
732
engines/glk/adrift/sctaffil.cpp
Normal 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
|
||||
3423
engines/glk/adrift/sctafpar.cpp
Normal file
3423
engines/glk/adrift/sctafpar.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1279
engines/glk/adrift/sctasks.cpp
Normal file
1279
engines/glk/adrift/sctasks.cpp
Normal file
File diff suppressed because it is too large
Load Diff
411
engines/glk/adrift/scutils.cpp
Normal file
411
engines/glk/adrift/scutils.cpp
Normal 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
|
||||
1685
engines/glk/adrift/scvars.cpp
Normal file
1685
engines/glk/adrift/scvars.cpp
Normal file
File diff suppressed because it is too large
Load Diff
494
engines/glk/adrift/serialization.cpp
Normal file
494
engines/glk/adrift/serialization.cpp
Normal 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
|
||||
145
engines/glk/adrift/serialization.h
Normal file
145
engines/glk/adrift/serialization.h
Normal 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
|
||||
173
engines/glk/adrift/sxfile.cpp
Normal file
173
engines/glk/adrift/sxfile.cpp
Normal 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
|
||||
292
engines/glk/adrift/sxglob.cpp
Normal file
292
engines/glk/adrift/sxglob.cpp
Normal 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
|
||||
86
engines/glk/adrift/sxprotos.h
Normal file
86
engines/glk/adrift/sxprotos.h
Normal 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
|
||||
567
engines/glk/adrift/sxscript.cpp
Normal file
567
engines/glk/adrift/sxscript.cpp
Normal 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
|
||||
373
engines/glk/adrift/sxstubs.cpp
Normal file
373
engines/glk/adrift/sxstubs.cpp
Normal 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
|
||||
245
engines/glk/adrift/sxutils.cpp
Normal file
245
engines/glk/adrift/sxutils.cpp
Normal 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
|
||||
Reference in New Issue
Block a user