Initial commit
This commit is contained in:
123
engines/glk/advsys/advsys.cpp
Normal file
123
engines/glk/advsys/advsys.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/* 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/advsys/advsys.h"
|
||||
#include "common/translation.h"
|
||||
#include "common/config-manager.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
void AdvSys::runGame() {
|
||||
// Check for savegame
|
||||
_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
|
||||
|
||||
if (!initialize()) {
|
||||
GUIErrorMessage(_("Could not start AdvSys game"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Outer play loop - this loop re-iterates if a game is restarted
|
||||
while (!shouldQuit()) {
|
||||
// Run game startup
|
||||
execute(_initCodeOffset);
|
||||
|
||||
if (_saveSlot != -1) {
|
||||
Common::ErrorCode err = loadGameState(_saveSlot).getCode();
|
||||
_saveSlot = -1;
|
||||
if (err != Common::kNoError)
|
||||
print(_("Sorry, the savegame couldn't be restored"));
|
||||
else
|
||||
_pendingLine = "look"; // Do a look action after loading the savegame
|
||||
}
|
||||
|
||||
// Gameplay loop
|
||||
while (!shouldQuit() && !shouldRestart()) {
|
||||
// Run update code
|
||||
execute(_updateCodeOffset);
|
||||
|
||||
// Get and parse a single line
|
||||
if (getInput()) {
|
||||
if (singleAction()) {
|
||||
while (!shouldQuit() && nextCommand() && singleAction()) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinitialize();
|
||||
}
|
||||
|
||||
bool AdvSys::initialize() {
|
||||
// Create a Glk window for the game
|
||||
if (!GlkInterface::initialize())
|
||||
return false;
|
||||
|
||||
// Load the game's header
|
||||
if (!Game::init(&_gameFile))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdvSys::deinitialize() {
|
||||
}
|
||||
|
||||
bool AdvSys::singleAction() {
|
||||
// Do the before code
|
||||
switch (execute(_beforeOffset)) {
|
||||
case ABORT:
|
||||
// Script aborted
|
||||
return false;
|
||||
case CHAIN:
|
||||
// Execute the action handler
|
||||
if (execute(getActionField(getVariable(V_ACTION), A_CODE)) == ABORT)
|
||||
return false;
|
||||
|
||||
// fall through
|
||||
case FINISH:
|
||||
// Do the after code
|
||||
if (execute(_afterOffset) == ABORT)
|
||||
return false;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::Error AdvSys::readSaveData(Common::SeekableReadStream *rs) {
|
||||
if (rs->size() != (int)_saveSize)
|
||||
return Common::kReadingFailed;
|
||||
|
||||
rs->read(_saveArea, rs->size());
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
Common::Error AdvSys::writeGameData(Common::WriteStream *ws) {
|
||||
ws->write(_saveArea, _saveSize);
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
85
engines/glk/advsys/advsys.h
Normal file
85
engines/glk/advsys/advsys.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/* 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 AdvSys interpreter 1.2 */
|
||||
|
||||
#ifndef GLK_ADVSYS_ADVSYS
|
||||
#define GLK_ADVSYS_ADVSYS
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "glk/advsys/vm.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
/**
|
||||
* AdvSys game interpreter
|
||||
*/
|
||||
class AdvSys : public VM {
|
||||
private:
|
||||
/**
|
||||
* Engine initialization
|
||||
*/
|
||||
bool initialize();
|
||||
|
||||
/**
|
||||
* Engine cleanup
|
||||
*/
|
||||
void deinitialize();
|
||||
|
||||
/**
|
||||
* Handle a single action
|
||||
*/
|
||||
bool singleAction();
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
AdvSys(OSystem *syst, const GlkGameDescription &gameDesc) : VM(syst, gameDesc) {}
|
||||
|
||||
/**
|
||||
* Run the game
|
||||
*/
|
||||
void runGame() override;
|
||||
|
||||
/**
|
||||
* Returns the running interpreter type
|
||||
*/
|
||||
InterpreterType getInterpreterType() const override {
|
||||
return INTERPRETER_ADVSYS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
|
||||
#endif
|
||||
32
engines/glk/advsys/definitions.h
Normal file
32
engines/glk/advsys/definitions.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/* 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_ADVSYS_DEFINITIONS
|
||||
#define GLK_ADVSYS_DEFINITIONS
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
|
||||
#endif
|
||||
106
engines/glk/advsys/detection.cpp
Normal file
106
engines/glk/advsys/detection.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/* 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/advsys/detection.h"
|
||||
#include "glk/advsys/detection_tables.h"
|
||||
#include "glk/advsys/game.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/file.h"
|
||||
#include "common/md5.h"
|
||||
#include "engines/game.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
void AdvSysMetaEngine::getSupportedGames(PlainGameList &games) {
|
||||
for (const PlainGameDescriptor *pd = ADVSYS_GAME_LIST; pd->gameId; ++pd)
|
||||
games.push_back(*pd);
|
||||
}
|
||||
|
||||
const GlkDetectionEntry* AdvSysMetaEngine::getDetectionEntries() {
|
||||
return ADVSYS_GAMES;
|
||||
}
|
||||
|
||||
GameDescriptor AdvSysMetaEngine::findGame(const char *gameId) {
|
||||
for (const PlainGameDescriptor *pd = ADVSYS_GAME_LIST; pd->gameId; ++pd) {
|
||||
if (!strcmp(gameId, pd->gameId))
|
||||
return *pd;
|
||||
}
|
||||
|
||||
return GameDescriptor::empty();
|
||||
}
|
||||
|
||||
bool AdvSysMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
|
||||
const char *const EXTENSIONS[] = { ".dat", nullptr };
|
||||
|
||||
// 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 = false;
|
||||
for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext)
|
||||
hasExt = filename.hasSuffixIgnoreCase(*ext);
|
||||
if (!hasExt)
|
||||
continue;
|
||||
|
||||
Common::File gameFile;
|
||||
if (!gameFile.open(*file))
|
||||
continue;
|
||||
|
||||
Header hdr(&gameFile);
|
||||
if (!hdr._valid)
|
||||
continue;
|
||||
|
||||
gameFile.seek(0);
|
||||
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
|
||||
uint32 filesize = gameFile.size();
|
||||
|
||||
// Scan through the AdvSys game list for a match
|
||||
const GlkDetectionEntry *p = ADVSYS_GAMES;
|
||||
while (p->_md5 && p->_filesize != filesize && md5 != p->_md5)
|
||||
++p;
|
||||
|
||||
if (!p->_gameId) {
|
||||
const PlainGameDescriptor &desc = ADVSYS_GAME_LIST[0];
|
||||
gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
|
||||
} else {
|
||||
// Found a match
|
||||
PlainGameDescriptor gameDesc = findGame(p->_gameId);
|
||||
gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, filename));
|
||||
}
|
||||
}
|
||||
|
||||
return !gameList.empty();
|
||||
}
|
||||
|
||||
void AdvSysMetaEngine::detectClashes(Common::StringMap &map) {
|
||||
for (const PlainGameDescriptor *pd = ADVSYS_GAME_LIST; pd->gameId; ++pd) {
|
||||
if (map.contains(pd->gameId))
|
||||
error("Duplicate game Id found - %s", pd->gameId);
|
||||
map[pd->gameId] = "";
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
64
engines/glk/advsys/detection.h
Normal file
64
engines/glk/advsys/detection.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/* 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_ADVSYS_DETECTION
|
||||
#define GLK_ADVSYS_DETECTION
|
||||
|
||||
#include "common/fs.h"
|
||||
#include "common/hash-str.h"
|
||||
#include "engines/game.h"
|
||||
#include "glk/detection.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
class AdvSysMetaEngine {
|
||||
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);
|
||||
};
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
|
||||
#endif
|
||||
52
engines/glk/advsys/detection_tables.h
Normal file
52
engines/glk/advsys/detection_tables.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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 "engines/game.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
const PlainGameDescriptor ADVSYS_GAME_LIST[] = {
|
||||
{ "advsys", "AdvSys Game" },
|
||||
|
||||
{ "bustedadvsys", "Busted!" },
|
||||
{ "starshipcolumbus", "Starship Columbus" },
|
||||
{ "elves87", "Elves '87" },
|
||||
{ "keytotime", "The Key to Time" },
|
||||
{ "onehand", "The Sound of One Hand Clapping" },
|
||||
{ "pirating", "Pirating" },
|
||||
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
const GlkDetectionEntry ADVSYS_GAMES[] = {
|
||||
DT_ENTRY0("bustedadvsys", "2246a2686a07c714868680eaf980ece9", 79091),
|
||||
DT_ENTRY0("starshipcolumbus", "120d7041dfa000c9a313a8b0ae9cef33", 76032),
|
||||
DT_ENTRY0("elves87", "746963e82552f95b5e743fe24ecd1ec3", 77947),
|
||||
DT_ENTRY0("keytotime", "892217ab8d902a732e82c55efd22931d", 24941),
|
||||
DT_ENTRY0("onehand", "3a2a3cc24709ff3272f3a15d09b5e63e", 95762),
|
||||
DT_ENTRY0("pirating", "e55fff2ac51a8a16b979541e8d3210d8", 29529),
|
||||
|
||||
DT_END_MARKER
|
||||
};
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
388
engines/glk/advsys/game.cpp
Normal file
388
engines/glk/advsys/game.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
/* 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/advsys/game.h"
|
||||
#include "glk/advsys/definitions.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
void Decrypter::decrypt(byte *data, size_t size) {
|
||||
for (size_t idx = 0; idx < size; ++idx, ++data)
|
||||
*data = ~(*data + 30);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
#define HEADER_SIZE 62
|
||||
|
||||
bool Header::init(Common::SeekableReadStream *s) {
|
||||
_valid = false;
|
||||
byte data[HEADER_SIZE];
|
||||
|
||||
// Read in the data
|
||||
if (s->read(data, HEADER_SIZE) != HEADER_SIZE)
|
||||
return false;
|
||||
decrypt(data, HEADER_SIZE);
|
||||
Common::MemoryReadStream ms(data, HEADER_SIZE, DisposeAfterUse::NO);
|
||||
|
||||
// Validate the header
|
||||
_valid = !strncmp((const char *)data + 2, "ADVSYS", 6);
|
||||
if (!_valid)
|
||||
return false;
|
||||
|
||||
_size = ms.readUint16LE();
|
||||
ms.skip(6);
|
||||
_headerVersion = ms.readUint16LE();
|
||||
_name = Common::String((const char *)data + 10, (const char *)data + 28);
|
||||
ms.skip(18);
|
||||
_version = ms.readUint16LE();
|
||||
_wordTableOffset = ms.readUint16LE();
|
||||
_wordTypeTableOffset = ms.readUint16LE();
|
||||
_objectTableOffset = ms.readUint16LE();
|
||||
_actionTableOffset = ms.readUint16LE();
|
||||
_variableTableOffset = ms.readUint16LE();
|
||||
_dataSpaceOffset = ms.readUint16LE();
|
||||
_codeSpaceOffset = ms.readUint16LE();
|
||||
_dataBlockOffset = ms.readUint16LE();
|
||||
_messageBlockOffset = ms.readUint16LE();
|
||||
_initCodeOffset = ms.readUint16LE();
|
||||
_updateCodeOffset = ms.readUint16LE();
|
||||
_beforeOffset = ms.readUint16LE();
|
||||
_afterOffset = ms.readUint16LE();
|
||||
_errorHandlerOffset = ms.readUint16LE();
|
||||
_saveAreaOffset = ms.readUint16LE();
|
||||
_saveSize = ms.readUint16LE();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
#define MAX_VERSION 102
|
||||
#define WORD_SIZE 6
|
||||
|
||||
/**
|
||||
* Property flags
|
||||
*/
|
||||
enum PropertyFlag {
|
||||
P_CLASS = 0x8000
|
||||
};
|
||||
|
||||
/**
|
||||
* Link fields
|
||||
*/
|
||||
enum LinkField {
|
||||
L_DATA = 0,
|
||||
L_NEXT = 2,
|
||||
L_SIZE = 4
|
||||
};
|
||||
|
||||
Game::Game() : Header(), _stream(nullptr), _restartFlag(false), _residentOffset(0), _wordCount(0),
|
||||
_objectCount(0), _actionCount(0), _variableCount(0), _wordTable(nullptr), _wordTypeTable(nullptr),
|
||||
_objectTable(nullptr), _actionTable(nullptr), _variableTable(nullptr), _saveArea(nullptr),
|
||||
_msgBlockNum(-1), _msgBlockOffset(0) {
|
||||
_msgCache.resize(MESSAGE_CACHE_SIZE);
|
||||
for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx)
|
||||
_msgCache[idx] = new CacheEntry();
|
||||
}
|
||||
|
||||
Game::~Game() {
|
||||
for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx)
|
||||
delete _msgCache[idx];
|
||||
}
|
||||
|
||||
bool Game::init(Common::SeekableReadStream *s) {
|
||||
// Store a copy of the game file stream
|
||||
_stream = s;
|
||||
|
||||
// Load the header
|
||||
s->seek(0);
|
||||
if (!Header::init(s))
|
||||
return false;
|
||||
|
||||
if (_headerVersion < 101 || _headerVersion > MAX_VERSION)
|
||||
error("Wrong version number");
|
||||
|
||||
// Load the needed resident game data and decrypt it
|
||||
_residentOffset = _dataBlockOffset * 512;
|
||||
s->seek(_residentOffset);
|
||||
|
||||
_data.resize(_size);
|
||||
if (!s->read(&_data[0], _size))
|
||||
return false;
|
||||
decrypt(&_data[0], _size);
|
||||
|
||||
_wordTable = &_data[_wordTableOffset];
|
||||
_wordTypeTable = &_data[_wordTypeTableOffset - 1];
|
||||
_objectTable = &_data[_objectTableOffset];
|
||||
_actionTable = &_data[_actionTableOffset];
|
||||
_variableTable = &_data[_variableTableOffset];
|
||||
_saveArea = &_data[_saveAreaOffset];
|
||||
_dataSpace = &_data[_dataSpaceOffset];
|
||||
_codeSpace = &_data[_codeSpaceOffset];
|
||||
|
||||
_wordCount = READ_LE_UINT16(_wordTable);
|
||||
_objectCount = READ_LE_UINT16(_objectTable);
|
||||
_actionCount = READ_LE_UINT16(_actionTable);
|
||||
_variableCount = READ_LE_UINT16(_variableTable);
|
||||
|
||||
setVariable(V_OCOUNT, _objectCount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Game::restart() {
|
||||
_stream->seek(_residentOffset + _saveAreaOffset);
|
||||
_stream->read(_saveArea, _saveSize);
|
||||
decrypt(_saveArea, _saveSize);
|
||||
|
||||
setVariable(V_OCOUNT, _objectCount);
|
||||
_restartFlag = true;
|
||||
}
|
||||
|
||||
bool Game::shouldRestart() {
|
||||
bool result = _restartFlag;
|
||||
_restartFlag = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Game::saveGameData(Common::WriteStream &ws) {
|
||||
ws.write(_saveArea, _saveSize);
|
||||
}
|
||||
|
||||
void Game::loadGameData(Common::ReadStream &rs) {
|
||||
rs.read(_saveArea, _saveSize);
|
||||
}
|
||||
|
||||
int Game::findWord(const Common::String &word) const {
|
||||
// Limit the word to the maximum allowable size
|
||||
Common::String w(word.c_str(), MIN(word.size(), (uint)WORD_SIZE));
|
||||
|
||||
// Iterate over the dictionary for the word
|
||||
for (int idx = 1; idx <= _wordCount; ++idx) {
|
||||
int wordOffset = READ_LE_UINT16(_wordTable + idx * 2);
|
||||
if (w == (const char *)_dataSpace + wordOffset + 2)
|
||||
return readWord(wordOffset);
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
int Game::checkVerb(const Common::Array<int> &verbs) {
|
||||
// Iterate through the actions
|
||||
for (int idx = 1; idx <= _actionCount; ++idx) {
|
||||
if (hasVerb(idx, verbs))
|
||||
return idx;
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
int Game::findAction(const Common::Array<int> &verbs, int preposition, int flag) {
|
||||
// Iterate through the actions
|
||||
for (int idx = 1; idx <= _actionCount; ++idx) {
|
||||
if ((preposition && !hasPreposition(idx, preposition)) || !hasVerb(idx, verbs))
|
||||
continue;
|
||||
|
||||
int mask = ~getActionByte(idx, A_MASK);
|
||||
if ((flag & mask) == (getActionByte(idx, A_FLAG) & mask))
|
||||
return idx;
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
int Game::getObjectProperty(int obj, int prop) {
|
||||
int field;
|
||||
|
||||
for (; obj; obj = getObjectField(obj, O_CLASS)) {
|
||||
if ((field = findProperty(obj, prop)) != 0)
|
||||
return getObjectField(obj, field);
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
int Game::setObjectProperty(int obj, int prop, int val) {
|
||||
int field;
|
||||
|
||||
for (; obj; obj = getObjectField(obj, O_CLASS)) {
|
||||
if ((field = findProperty(obj, prop)) != 0)
|
||||
return setObjectField(obj, field, val);
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
int Game::getObjectLocation(int obj) const {
|
||||
if (obj < 1 || obj > _objectCount)
|
||||
error("Invalid object number %d", obj);
|
||||
|
||||
return READ_LE_UINT16(_objectTable + obj * 2);
|
||||
}
|
||||
|
||||
int Game::getActionLocation(int action) const {
|
||||
if (action < 1 || action > _actionCount)
|
||||
error("Invalid action number %d", action);
|
||||
|
||||
return READ_LE_UINT16(_actionTable + action * 2);
|
||||
}
|
||||
|
||||
int Game::getVariable(int variableNum) {
|
||||
if (variableNum < 1 || variableNum > _variableCount)
|
||||
error("Invalid ariable number %d", variableNum);
|
||||
|
||||
return READ_LE_UINT16(_variableTable + variableNum * 2);
|
||||
}
|
||||
|
||||
void Game::setVariable(int variableNum, int value) {
|
||||
if (variableNum < 1 || variableNum > _variableCount)
|
||||
error("Invalid ariable number %d", variableNum);
|
||||
|
||||
WRITE_LE_UINT16(_variableTable + variableNum * 2, value);
|
||||
}
|
||||
|
||||
int Game::findProperty(int obj, int prop) const {
|
||||
int nProp = getObjectField(obj, O_NPROPERTIES);
|
||||
|
||||
for (int idx = 0, p = 0; idx < nProp; ++idx, p += 4) {
|
||||
if ((getObjectField(obj, O_PROPERTIES + p) & ~P_CLASS) == prop)
|
||||
return O_PROPERTIES + p + 2;
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
bool Game::hasNoun(int obj, int noun) const {
|
||||
for (; obj; obj = getObjectField(obj, O_CLASS)) {
|
||||
if (inList(getObjectField(obj, O_NOUNS), noun))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Game::hasAdjective(int obj, int adjective) const {
|
||||
for (; obj; obj = getObjectField(obj, O_CLASS)) {
|
||||
if (inList(getObjectField(obj, O_ADJECTIVES), adjective))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Game::hasVerb(int act, const Common::Array<int> &verbs) const {
|
||||
// Get the list of verbs
|
||||
int link = getActionField(act, A_VERBS);
|
||||
|
||||
// Look for the verb
|
||||
for (; link; link = readWord(link + L_NEXT)) {
|
||||
Common::Array<int>::const_iterator verb = verbs.begin();
|
||||
int word = readWord(link + L_DATA);
|
||||
|
||||
for (; verb < verbs.end() && word; ++verb, word = readWord(word + L_NEXT)) {
|
||||
if (*verb != readWord(word + L_DATA))
|
||||
break;
|
||||
}
|
||||
|
||||
if (verb == verbs.end() && !word)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Game::inList(int link, int word) const {
|
||||
for (; link; link = readWord(link + L_NEXT)) {
|
||||
if (word == readWord(link + L_DATA))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::String Game::readString(int msg) {
|
||||
// Get the block to use, and ensure it's loaded
|
||||
_msgBlockNum = msg >> 7;
|
||||
_msgBlockOffset = (msg & 0x7f) << 2;
|
||||
readMsgBlock();
|
||||
|
||||
// Read the string
|
||||
Common::String result;
|
||||
char c;
|
||||
|
||||
while ((c = readMsgChar()) != '\0')
|
||||
result += c;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char Game::readMsgChar() {
|
||||
if (_msgBlockOffset >= MESSAGE_BLOCK_SIZE) {
|
||||
// Move to the next block
|
||||
++_msgBlockNum;
|
||||
_msgBlockOffset = 0;
|
||||
readMsgBlock();
|
||||
}
|
||||
|
||||
// Return next character
|
||||
return _msgCache[0]->_data[_msgBlockOffset++];
|
||||
}
|
||||
|
||||
void Game::readMsgBlock() {
|
||||
CacheEntry *ce;
|
||||
|
||||
// Check to see if the specified block is in the cache
|
||||
for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx) {
|
||||
if (_msgCache[idx]->_blockNum == _msgBlockNum) {
|
||||
// If it's not already at the top of the list, move it there to ensure
|
||||
// it'll be last to be unloaded as new blocks are loaded in
|
||||
if (idx != 0) {
|
||||
ce = _msgCache[idx];
|
||||
_msgCache.remove_at(idx);
|
||||
_msgCache.insert_at(0, ce);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we need to load a new block in. Discard the block at the end
|
||||
// and move it to the start for storing the new block to load
|
||||
ce = _msgCache.back();
|
||||
_msgCache.remove_at(_msgCache.size() - 1);
|
||||
_msgCache.insert_at(0, ce);
|
||||
|
||||
// Load the new block
|
||||
ce->_blockNum = _msgBlockNum;
|
||||
_stream->seek((_messageBlockOffset + _msgBlockNum) << 9);
|
||||
if (_stream->read(&ce->_data[0], MESSAGE_BLOCK_SIZE) != MESSAGE_BLOCK_SIZE)
|
||||
error("Error reading message block");
|
||||
|
||||
// Decode the loaded block
|
||||
for (int idx = 0; idx < MESSAGE_BLOCK_SIZE; ++idx)
|
||||
ce->_data[idx] = (ce->_data[idx] + 30) & 0xff;
|
||||
}
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
377
engines/glk/advsys/game.h
Normal file
377
engines/glk/advsys/game.h
Normal file
@@ -0,0 +1,377 @@
|
||||
/* 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_ADVSYS_GAME
|
||||
#define GLK_ADVSYS_GAME
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
#define NIL 0
|
||||
#define MESSAGE_CACHE_SIZE 8
|
||||
#define MESSAGE_BLOCK_SIZE 512
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
enum Action {
|
||||
A_VERBS = 0,
|
||||
A_PREPOSITIONS = 2,
|
||||
A_FLAG = 4,
|
||||
A_MASK = 5,
|
||||
A_CODE = 6,
|
||||
A_SIZE = 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Word types
|
||||
*/
|
||||
enum WordType {
|
||||
WT_UNKNOWN = 0,
|
||||
WT_VERB = 1,
|
||||
WT_NOUN = 2,
|
||||
WT_ADJECTIVE = 3,
|
||||
WT_PREPOSITION = 4,
|
||||
WT_CONJUNCTION = 5,
|
||||
WT_ARTICLE = 6
|
||||
};
|
||||
|
||||
/**
|
||||
* Object fields
|
||||
*/
|
||||
enum ObjectField {
|
||||
O_CLASS = 0,
|
||||
O_NOUNS = 2,
|
||||
O_ADJECTIVES = 4,
|
||||
O_NPROPERTIES = 6,
|
||||
O_PROPERTIES = 8,
|
||||
O_SIZE = 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Built-in variables
|
||||
*/
|
||||
enum Variable {
|
||||
V_ACTOR = 1, ///< Actor noun phrase number
|
||||
V_ACTION = 2, ///< Action from phrase
|
||||
V_DOBJECT = 3, ///< First direct object noun phrase number
|
||||
V_NDOBJECTS = 4, ///< Number of direct object noun phrases
|
||||
V_IOBJECT = 5, ///< Indirect object noun phrase number
|
||||
V_OCOUNT = 6 ///< Total object count
|
||||
};
|
||||
|
||||
/**
|
||||
* Data decryption
|
||||
*/
|
||||
class Decrypter {
|
||||
public:
|
||||
/**
|
||||
* Decrypt a data block
|
||||
*/
|
||||
static void decrypt(byte *data, size_t size);
|
||||
};
|
||||
|
||||
/**
|
||||
* AdvSys game header
|
||||
*/
|
||||
class Header : public Decrypter {
|
||||
public:
|
||||
bool _valid; ///< Signals whether header is valid
|
||||
size_t _size; ///< Resident size in bytes
|
||||
uint _headerVersion; ///< Header structure version
|
||||
Common::String _name; ///< Adventure name
|
||||
uint _version; ///< Adventure version
|
||||
uint _wordTableOffset; ///< Word table offset
|
||||
uint _wordTypeTableOffset; ///< Word type table offset
|
||||
uint _objectTableOffset; ///< Object table offset
|
||||
uint _actionTableOffset; ///< Action table offset
|
||||
uint _variableTableOffset; ///< Variable table offset
|
||||
uint _dataSpaceOffset; ///< Data space offset
|
||||
uint _codeSpaceOffset; ///< Code space offset
|
||||
uint _dataBlockOffset; ///< First data block offset
|
||||
uint _messageBlockOffset; ///< First message block offset
|
||||
uint _initCodeOffset; ///< Initialization code offset
|
||||
uint _updateCodeOffset; ///< Update code offset
|
||||
uint _beforeOffset; ///< Code offset before verb handler
|
||||
uint _afterOffset; ///< Code offset after verb handler
|
||||
uint _errorHandlerOffset; ///< Error handler code offset
|
||||
uint _saveAreaOffset; ///< Save area offset
|
||||
uint _saveSize; ///< Save area size
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Header() : _valid(false), _size(0), _headerVersion(0), _version(0), _wordTableOffset(0),
|
||||
_wordTypeTableOffset(0), _objectTableOffset(0), _actionTableOffset(0), _variableTableOffset(0),
|
||||
_dataSpaceOffset(0), _codeSpaceOffset(0), _dataBlockOffset(0), _messageBlockOffset(0),
|
||||
_initCodeOffset(0), _updateCodeOffset(0), _beforeOffset(0), _afterOffset(0),
|
||||
_errorHandlerOffset(0), _saveAreaOffset(0), _saveSize(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Header(Common::SeekableReadStream *s) {
|
||||
init(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* init the header
|
||||
*/
|
||||
bool init(Common::SeekableReadStream *s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Game abstraction class
|
||||
*/
|
||||
class Game : public Header {
|
||||
struct CacheEntry {
|
||||
int _blockNum;
|
||||
char _data[MESSAGE_BLOCK_SIZE];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
CacheEntry() : _blockNum(-1) {
|
||||
Common::fill(&_data[0], &_data[MESSAGE_BLOCK_SIZE], '\0');
|
||||
}
|
||||
};
|
||||
private:
|
||||
bool _restartFlag;
|
||||
Common::SeekableReadStream *_stream;
|
||||
Common::Array<CacheEntry *> _msgCache;
|
||||
int _msgBlockNum, _msgBlockOffset;
|
||||
private:
|
||||
/**
|
||||
* Find an object property field
|
||||
*/
|
||||
int findProperty(int obj, int prop) const;
|
||||
|
||||
/**
|
||||
* Returns true if an action has a given verb
|
||||
*/
|
||||
bool hasVerb(int act, const Common::Array<int> &verbs) const;
|
||||
|
||||
/**
|
||||
* Returns true if an action is in a given list
|
||||
*/
|
||||
bool hasPreposition(int act, int preposition) const {
|
||||
return inList(getActionField(act, A_PREPOSITIONS), preposition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a word is in an element of a given list
|
||||
*/
|
||||
bool inList(int link, int word) const;
|
||||
|
||||
/**
|
||||
* Reads in a message block from the game file
|
||||
*/
|
||||
void readMsgBlock();
|
||||
|
||||
/**
|
||||
* Read the next character for a string
|
||||
*/
|
||||
char readMsgChar();
|
||||
protected:
|
||||
/**
|
||||
* Returns true if an object has a given noun
|
||||
*/
|
||||
bool hasNoun(int obj, int noun) const;
|
||||
|
||||
/**
|
||||
* Returns true if an object has a given adjective
|
||||
*/
|
||||
bool hasAdjective(int obj, int adjective) const;
|
||||
public:
|
||||
Common::Array<byte> _data;
|
||||
int _residentOffset;
|
||||
int _wordCount;
|
||||
int _objectCount;
|
||||
int _actionCount;
|
||||
int _variableCount;
|
||||
|
||||
byte *_wordTable;
|
||||
byte *_wordTypeTable;
|
||||
byte *_objectTable;
|
||||
byte *_actionTable;
|
||||
byte *_variableTable;
|
||||
byte *_saveArea;
|
||||
byte *_dataSpace;
|
||||
byte *_codeSpace;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Game();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~Game();
|
||||
|
||||
/**
|
||||
* init data for the game
|
||||
*/
|
||||
bool init(Common::SeekableReadStream *s);
|
||||
|
||||
/**
|
||||
* Restore savegame data from the game to it's initial state
|
||||
*/
|
||||
void restart();
|
||||
|
||||
/**
|
||||
* Returns true if the game is restarting, and resets the flag
|
||||
*/
|
||||
bool shouldRestart();
|
||||
|
||||
/**
|
||||
* Save the game data to a savegame
|
||||
*/
|
||||
void saveGameData(Common::WriteStream &ws);
|
||||
|
||||
/**
|
||||
* Restore the game data from a savegame
|
||||
*/
|
||||
void loadGameData(Common::ReadStream &rs);
|
||||
|
||||
/**
|
||||
* Find a word in the dictionary
|
||||
*/
|
||||
int findWord(const Common::String &word) const;
|
||||
|
||||
/**
|
||||
* Return a word's type
|
||||
*/
|
||||
WordType getWordType(int word) const {
|
||||
return (WordType)_wordTypeTable[word];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this is a valid verb
|
||||
*/
|
||||
int checkVerb(const Common::Array<int> &verbs);
|
||||
|
||||
/**
|
||||
* Find an action matching a given description
|
||||
*/
|
||||
int findAction(const Common::Array<int> &verbs, int preposition, int flag);
|
||||
|
||||
/**
|
||||
* Get an object property
|
||||
*/
|
||||
int getObjectProperty(int obj, int prop);
|
||||
|
||||
/**
|
||||
* Sets an object property
|
||||
*/
|
||||
int setObjectProperty(int obj, int prop, int val);
|
||||
|
||||
/**
|
||||
* Gets a field from an object
|
||||
*/
|
||||
int getObjectField(int obj, int offset) const {
|
||||
return READ_LE_UINT16(_dataSpace + getObjectLocation(obj) + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a field in an object
|
||||
*/
|
||||
int setObjectField(int obj, int offset, int val) {
|
||||
WRITE_LE_UINT16(_dataSpace + getObjectLocation(obj) + offset, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field from an action
|
||||
*/
|
||||
int getActionField(int action, int offset) const {
|
||||
return READ_LE_UINT16(_dataSpace + getActionLocation(action) + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a byte field from an action
|
||||
*/
|
||||
int getActionByte(int action, int offset) const {
|
||||
return _dataSpace[getActionLocation(action) + offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the offset of an object from the object table
|
||||
*/
|
||||
int getObjectLocation(int obj) const;
|
||||
|
||||
/**
|
||||
* Gets the offset of an action from the action table
|
||||
*/
|
||||
int getActionLocation(int action) const;
|
||||
|
||||
/**
|
||||
* Get a variable value
|
||||
*/
|
||||
int getVariable(int variableNum);
|
||||
|
||||
/**
|
||||
* Set a variable value
|
||||
*/
|
||||
void setVariable(int variableNum, int value);
|
||||
|
||||
/**
|
||||
* Gets a code byte
|
||||
*/
|
||||
int getCodeByte(int offset) const {
|
||||
return _codeSpace[offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a code byte
|
||||
*/
|
||||
int getCodeWord(int offset) const {
|
||||
return READ_LE_UINT16(_codeSpace + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a word
|
||||
*/
|
||||
int readWord(int offset) const {
|
||||
return READ_LE_UINT16(_dataSpace + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a word
|
||||
*/
|
||||
void writeWord(int offset, int val) {
|
||||
WRITE_LE_UINT16(_dataSpace + offset, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string from the messages section
|
||||
*/
|
||||
Common::String readString(int msg);
|
||||
};
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
|
||||
#endif
|
||||
83
engines/glk/advsys/glk_interface.cpp
Normal file
83
engines/glk/advsys/glk_interface.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/* 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/advsys/glk_interface.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
bool GlkInterface::initialize() {
|
||||
_window = glk_window_open(nullptr, 0, 0, wintype_TextBuffer, 1);
|
||||
return _window != nullptr;
|
||||
}
|
||||
|
||||
void GlkInterface::print(const Common::String &msg) {
|
||||
// Don't print out text if loading a savegame directly from the launcher, since we don't
|
||||
// want any of the intro text displayed by the startup code to show
|
||||
if (_saveSlot == -1)
|
||||
glk_put_string_stream(glk_window_get_stream(_window), msg.c_str());
|
||||
}
|
||||
|
||||
void GlkInterface::print(const Common::U32String &msg) {
|
||||
// Don't print out text if loading a savegame directly from the launcher, since we don't
|
||||
// want any of the intro text displayed by the startup code to show
|
||||
if (_saveSlot == -1)
|
||||
glk_put_string_stream_uni(glk_window_get_stream(_window), msg.u32_str());
|
||||
}
|
||||
|
||||
void GlkInterface::print(int number) {
|
||||
Common::String s = Common::String::format("%d", number);
|
||||
print(s);
|
||||
}
|
||||
|
||||
Common::String GlkInterface::readLine() {
|
||||
event_t ev;
|
||||
char line[200];
|
||||
|
||||
print(": ");
|
||||
|
||||
if (!_pendingLine.empty()) {
|
||||
// The next input line has been manually provided, so return it
|
||||
print(_pendingLine);
|
||||
print("\n");
|
||||
|
||||
Common::String l = _pendingLine;
|
||||
_pendingLine = "";
|
||||
return l;
|
||||
}
|
||||
|
||||
glk_request_line_event(_window, line, 199, 0);
|
||||
|
||||
do {
|
||||
glk_select(&ev);
|
||||
if (ev.type == evtype_Quit)
|
||||
return "";
|
||||
else if (ev.type == evtype_LineInput) {
|
||||
line[ev.val1] = '\0';
|
||||
return Common::String(line);
|
||||
}
|
||||
} while (!shouldQuit() && ev.type != evtype_Quit);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
79
engines/glk/advsys/glk_interface.h
Normal file
79
engines/glk/advsys/glk_interface.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/* 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_ADVSYS_GLK_INTERFACE
|
||||
#define GLK_ADVSYS_GLK_INTERFACE
|
||||
|
||||
#include "glk/glk_api.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
/**
|
||||
* Interface class that sits between AdvSys and the GLK base, providing methods for
|
||||
* input and output
|
||||
*/
|
||||
class GlkInterface : public GlkAPI {
|
||||
private:
|
||||
winid_t _window;
|
||||
protected:
|
||||
int _saveSlot;
|
||||
Common::String _pendingLine;
|
||||
protected:
|
||||
/**
|
||||
* GLK initialization
|
||||
*/
|
||||
bool initialize();
|
||||
|
||||
/**
|
||||
* Print a string
|
||||
* @param msg String
|
||||
*/
|
||||
void print(const Common::String &msg);
|
||||
|
||||
/**
|
||||
* Print a unicode string
|
||||
* @param msg U32String
|
||||
*/
|
||||
void print(const Common::U32String &msg);
|
||||
|
||||
/**
|
||||
* Print a number
|
||||
* @param number Number to print
|
||||
*/
|
||||
void print(int number);
|
||||
|
||||
/**
|
||||
* Get an input line
|
||||
*/
|
||||
Common::String readLine();
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
GlkInterface(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
|
||||
_window(nullptr), _saveSlot(-1) {}
|
||||
};
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
|
||||
#endif
|
||||
701
engines/glk/advsys/vm.cpp
Normal file
701
engines/glk/advsys/vm.cpp
Normal file
@@ -0,0 +1,701 @@
|
||||
/* 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/advsys/vm.h"
|
||||
#include "common/translation.h"
|
||||
#include "common/ustr.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
#define TRUE -1
|
||||
|
||||
OpcodeMethod VM::_METHODS[0x34] = {
|
||||
&VM::opBRT,
|
||||
&VM::opBRF,
|
||||
&VM::opBR,
|
||||
&VM::opT,
|
||||
&VM::opNIL,
|
||||
&VM::opPUSH,
|
||||
&VM::opNOT,
|
||||
&VM::opADD,
|
||||
&VM::opSUB,
|
||||
&VM::opMUL,
|
||||
&VM::opDIV,
|
||||
&VM::opREM,
|
||||
&VM::opBAND,
|
||||
&VM::opBOR,
|
||||
&VM::opBNOT,
|
||||
&VM::opLT,
|
||||
&VM::opEQ,
|
||||
&VM::opGT,
|
||||
&VM::opLIT,
|
||||
&VM::opVAR,
|
||||
&VM::opGETP,
|
||||
&VM::opSETP,
|
||||
&VM::opSET,
|
||||
&VM::opPRINT,
|
||||
&VM::opTERPRI,
|
||||
&VM::opPNUMBER,
|
||||
&VM::opFINISH,
|
||||
&VM::opCHAIN,
|
||||
&VM::opABORT,
|
||||
&VM::opEXIT,
|
||||
&VM::opRETURN,
|
||||
&VM::opCALL,
|
||||
&VM::opSVAR,
|
||||
&VM::opSSET,
|
||||
&VM::opSPLIT,
|
||||
&VM::opSNLIT,
|
||||
&VM::opYORN,
|
||||
&VM::opSAVE,
|
||||
&VM::opRESTORE,
|
||||
&VM::opARG,
|
||||
&VM::opASET,
|
||||
&VM::opTMP,
|
||||
&VM::opTSET,
|
||||
&VM::opTSPACE,
|
||||
&VM::opCLASS,
|
||||
&VM::opMATCH,
|
||||
&VM::opPNOUN,
|
||||
&VM::opRESTART,
|
||||
&VM::opRAND,
|
||||
&VM::opRNDMIZE,
|
||||
&VM::opSEND,
|
||||
&VM::opVOWEL
|
||||
};
|
||||
|
||||
VM::VM(OSystem *syst, const GlkGameDescription &gameDesc) : GlkInterface(syst, gameDesc), Game(),
|
||||
_fp(_stack), _pc(0), _status(IN_PROGRESS), _actor(-1), _action(-1), _dObject(-1),
|
||||
_ndObjects(-1), _iObject(-1), _wordPtr(nullptr) {
|
||||
}
|
||||
|
||||
ExecutionResult VM::execute(int offset) {
|
||||
// Set the code pointer
|
||||
_pc = offset;
|
||||
|
||||
// Clear the stack
|
||||
_fp.clear();
|
||||
_stack.clear();
|
||||
|
||||
// Iterate through the script
|
||||
for (_status = IN_PROGRESS; !shouldQuit() && _status == IN_PROGRESS;)
|
||||
executeOpcode();
|
||||
|
||||
return _status;
|
||||
}
|
||||
|
||||
void VM::executeOpcode() {
|
||||
// Get next opcode
|
||||
uint opcode = readCodeByte();
|
||||
|
||||
if (gDebugLevel > 0) {
|
||||
Common::String s;
|
||||
for (int idx = (int)_stack.size() - 1; idx >= 0; --idx) s += Common::String::format(" %d", _stack[idx]);
|
||||
debugC(kDebugScripts, "%.4x - %.2x - %d%s", _pc - 1, opcode, _stack.size(), s.c_str());
|
||||
}
|
||||
|
||||
if (opcode >= OP_BRT && opcode <= OP_VOWEL) {
|
||||
(this->*_METHODS[(int)opcode - 1])();
|
||||
} else if (opcode >= OP_XVAR && opcode < OP_XSET) {
|
||||
_stack.top() = getVariable((int)opcode - OP_XVAR);
|
||||
} else if (opcode >= OP_XSET && opcode < OP_XPLIT) {
|
||||
setVariable((int)opcode - OP_XSET, _stack.top());
|
||||
} else if (opcode >= OP_XPLIT && opcode < OP_XNLIT) {
|
||||
_stack.top() = (int)opcode - OP_XPLIT;
|
||||
} else if (opcode >= OP_XNLIT && (int)opcode < 256) {
|
||||
_stack.top() = OP_XNLIT - opcode;
|
||||
} else {
|
||||
error("Unknown opcode %x at offset %d", opcode, _pc);
|
||||
}
|
||||
}
|
||||
void VM::opBRT() {
|
||||
_pc = _stack.top() ? readCodeWord() : _pc + 2;
|
||||
}
|
||||
|
||||
void VM::opBRF() {
|
||||
_pc = !_stack.top() ? readCodeWord() : _pc + 2;
|
||||
}
|
||||
|
||||
void VM::opBR() {
|
||||
_pc = readCodeWord();
|
||||
}
|
||||
|
||||
void VM::opT() {
|
||||
_stack.top() = TRUE;
|
||||
}
|
||||
|
||||
void VM::opNIL() {
|
||||
_stack.top() = NIL;
|
||||
}
|
||||
|
||||
void VM::opPUSH() {
|
||||
_stack.push(NIL);
|
||||
}
|
||||
|
||||
void VM::opNOT() {
|
||||
_stack.top() = _stack.top() ? NIL : TRUE;
|
||||
}
|
||||
|
||||
void VM::opADD() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() += v;
|
||||
}
|
||||
|
||||
void VM::opSUB() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() -= v;
|
||||
}
|
||||
|
||||
void VM::opMUL() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() *= v;
|
||||
}
|
||||
|
||||
void VM::opDIV() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() = (v == 0) ? 0 : _stack.top() / v;
|
||||
}
|
||||
|
||||
void VM::opREM() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() = (v == 0) ? 0 : _stack.top() % v;
|
||||
}
|
||||
|
||||
void VM::opBAND() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() &= v;
|
||||
}
|
||||
|
||||
void VM::opBOR() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() |= v;
|
||||
}
|
||||
|
||||
void VM::opBNOT() {
|
||||
_stack.top() = ~_stack.top();
|
||||
}
|
||||
|
||||
void VM::opLT() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() = (_stack.top() < v) ? TRUE : NIL;
|
||||
}
|
||||
|
||||
void VM::opEQ() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() = (_stack.top() == v) ? TRUE : NIL;
|
||||
}
|
||||
|
||||
void VM::opGT() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() = (_stack.top() > v) ? TRUE : NIL;
|
||||
}
|
||||
|
||||
void VM::opLIT() {
|
||||
_stack.top() = readCodeWord();
|
||||
}
|
||||
|
||||
void VM::opVAR() {
|
||||
_stack.top() = getVariable(readCodeWord());
|
||||
}
|
||||
|
||||
void VM::opGETP() {
|
||||
int v = _stack.pop();
|
||||
_stack.top() = getObjectProperty(_stack.top(), v);
|
||||
}
|
||||
|
||||
void VM::opSETP() {
|
||||
int v3 = _stack.pop();
|
||||
int v2 = _stack.pop();
|
||||
_stack.top() = setObjectProperty(_stack.top(), v2, v3);
|
||||
}
|
||||
|
||||
void VM::opSET() {
|
||||
setVariable(readCodeWord(), _stack.top());
|
||||
}
|
||||
|
||||
void VM::opPRINT() {
|
||||
Common::String msg = readString(_stack.top());
|
||||
print(msg);
|
||||
}
|
||||
|
||||
void VM::opTERPRI() {
|
||||
print("\n");
|
||||
}
|
||||
|
||||
void VM::opPNUMBER() {
|
||||
print(_stack.top());
|
||||
}
|
||||
|
||||
void VM::opFINISH() {
|
||||
_status = FINISH;
|
||||
}
|
||||
|
||||
void VM::opCHAIN() {
|
||||
_status = CHAIN;
|
||||
}
|
||||
|
||||
void VM::opABORT() {
|
||||
_status = ABORT;
|
||||
}
|
||||
|
||||
void VM::opEXIT() {
|
||||
quitGame();
|
||||
_status = ABORT;
|
||||
}
|
||||
|
||||
void VM::opRETURN() {
|
||||
if (_fp == 0) {
|
||||
_status = CHAIN;
|
||||
} else {
|
||||
int val = _stack.top();
|
||||
_stack.resize(_fp);
|
||||
_fp = _stack.pop();
|
||||
_pc = _stack.pop();
|
||||
|
||||
int argsSize = _stack.pop();
|
||||
_stack.resize(_stack.size() - argsSize);
|
||||
_stack.top() = val;
|
||||
}
|
||||
}
|
||||
|
||||
void VM::opCALL() {
|
||||
int argsSize = readCodeByte();
|
||||
|
||||
_stack.push(argsSize);
|
||||
_stack.push(_pc);
|
||||
_stack.push(_fp);
|
||||
|
||||
_fp.set();
|
||||
_pc = getActionField(_fp[_fp[FP_ARGS_SIZE] + FP_ARGS], A_CODE);
|
||||
}
|
||||
|
||||
void VM::opSVAR() {
|
||||
_stack.top() = getVariable(readCodeByte());
|
||||
}
|
||||
|
||||
void VM::opSSET() {
|
||||
setVariable(readCodeByte(), _stack.top());
|
||||
}
|
||||
|
||||
void VM::opSPLIT() {
|
||||
_stack.top() = readCodeByte();
|
||||
}
|
||||
|
||||
void VM::opSNLIT() {
|
||||
_stack.top() = readCodeByte();
|
||||
}
|
||||
|
||||
void VM::opYORN() {
|
||||
Common::String line = readLine();
|
||||
_stack.top() = !line.empty() && (line[0] == 'Y' || line[0] == 'y') ? TRUE : NIL;
|
||||
}
|
||||
|
||||
void VM::opSAVE() {
|
||||
if (saveGame().getCode() != Common::kNoError)
|
||||
print(_("Sorry, the savegame couldn't be created"));
|
||||
}
|
||||
|
||||
void VM::opRESTORE() {
|
||||
if (loadGame().getCode() != Common::kNoError)
|
||||
print(_("Sorry, the savegame couldn't be restored"));
|
||||
}
|
||||
|
||||
void VM::opARG() {
|
||||
int argNum = readCodeByte();
|
||||
if (argNum >= _fp[FP_ARGS_SIZE])
|
||||
error("Invalid argument number");
|
||||
_stack.top() = _fp[argNum + FP_ARGS];
|
||||
}
|
||||
|
||||
void VM::opASET() {
|
||||
int argNum = readCodeByte();
|
||||
if (argNum >= _fp[FP_ARGS_SIZE])
|
||||
error("Invalid argument number");
|
||||
_fp[argNum + FP_ARGS] = _stack.top();
|
||||
}
|
||||
|
||||
void VM::opTMP() {
|
||||
int val = readCodeByte();
|
||||
_stack.top() = _fp[-val - 1];
|
||||
}
|
||||
|
||||
void VM::opTSET() {
|
||||
int val = readCodeByte();
|
||||
_fp[-val - 1] = _stack.top();
|
||||
}
|
||||
|
||||
void VM::opTSPACE() {
|
||||
_stack.allocate(readCodeByte());
|
||||
}
|
||||
|
||||
void VM::opCLASS() {
|
||||
_stack.top() = getObjectField(_stack.top(), O_CLASS);
|
||||
}
|
||||
|
||||
void VM::opMATCH() {
|
||||
int idx = _stack.pop() - 1;
|
||||
if (idx < 0 || _nouns.size() == 0) {
|
||||
_stack.top() = NIL;
|
||||
} else {
|
||||
_stack.top() = match(_stack.top(), _nouns[idx]._noun, _nouns[idx]._adjective) ? TRUE : NIL;
|
||||
}
|
||||
}
|
||||
|
||||
void VM::opPNOUN() {
|
||||
int noun = _stack.top();
|
||||
Common::String str;
|
||||
|
||||
// Add the adjectives
|
||||
bool space = false;
|
||||
for (const AdjectiveEntry *aPtr = &_adjectiveList[noun - 1]; aPtr->_list; ++aPtr, space = true) {
|
||||
if (space)
|
||||
str += " ";
|
||||
str += _words[aPtr->_word]._text;
|
||||
}
|
||||
|
||||
// Add the noun
|
||||
if (space)
|
||||
str += " ";
|
||||
str += _words[_nouns[noun - 1]._num]._text;
|
||||
|
||||
print(str);
|
||||
}
|
||||
|
||||
void VM::opRESTART() {
|
||||
restart();
|
||||
}
|
||||
|
||||
void VM::opRAND() {
|
||||
_stack.top() = getRandomNumber(_stack.top());
|
||||
}
|
||||
|
||||
void VM::opRNDMIZE() {
|
||||
// No implementation
|
||||
}
|
||||
|
||||
void VM::opSEND() {
|
||||
int argsSize = readCodeByte();
|
||||
_stack.push(argsSize);
|
||||
_stack.push(_pc);
|
||||
_stack.push(_fp);
|
||||
_fp.set();
|
||||
|
||||
int val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS];
|
||||
if (val)
|
||||
val = getObjectField(val, O_CLASS);
|
||||
else
|
||||
val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 1];
|
||||
|
||||
if (val && (val = getObjectProperty(val, _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 2])) != 0) {
|
||||
_pc = getActionField(val, A_CODE);
|
||||
} else {
|
||||
// Return NIL if there's no action for the given message
|
||||
opRETURN();
|
||||
}
|
||||
}
|
||||
|
||||
void VM::opVOWEL() {
|
||||
// No implementation
|
||||
}
|
||||
|
||||
bool VM::getInput() {
|
||||
if (!parseInput())
|
||||
return false;
|
||||
|
||||
setVariable(V_ACTOR, _actor);
|
||||
setVariable(V_ACTION, _action);
|
||||
setVariable(V_DOBJECT, _dObject);
|
||||
setVariable(V_NDOBJECTS, _ndObjects);
|
||||
setVariable(V_IOBJECT, _iObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VM::nextCommand() {
|
||||
if (getVariable(V_NDOBJECTS) > 1) {
|
||||
setVariable(V_ACTOR, _actor);
|
||||
setVariable(V_ACTION, _action);
|
||||
setVariable(V_DOBJECT, getVariable(V_DOBJECT) + 1);
|
||||
setVariable(V_NDOBJECTS, getVariable(V_NDOBJECTS) - 1);
|
||||
setVariable(V_IOBJECT, _iObject);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool VM::parseInput() {
|
||||
int noun1 = 0, cnt1 = 0, noun2 = 0, cnt2 = 0;
|
||||
int preposition = 0, flags = 0;
|
||||
|
||||
// Initialize the parser result fields
|
||||
_actor = _action = _dObject = _iObject = 0;
|
||||
_ndObjects = 0;
|
||||
_nouns.clear();
|
||||
_adjectiveList.clear();
|
||||
_adjectiveList.reserve(20);
|
||||
|
||||
// Get the input line
|
||||
if (!getLine())
|
||||
return false;
|
||||
|
||||
// Check for actor
|
||||
WordType wordType = getWordType(*_wordPtr);
|
||||
if (wordType == WT_ADJECTIVE || wordType == WT_NOUN) {
|
||||
if (!(_actor = getNoun()))
|
||||
return false;
|
||||
flags |= A_ACTOR;
|
||||
}
|
||||
|
||||
// Check for a verb
|
||||
if (!getVerb())
|
||||
return false;
|
||||
|
||||
// Get direct object, preposition, and/or indirect object
|
||||
if (_wordPtr != _words.end()) {
|
||||
// Get any direct objects
|
||||
noun1 = _adjectiveList.size() + 1;
|
||||
for (;;) {
|
||||
// Get the next direct object
|
||||
if (!getNoun())
|
||||
return false;
|
||||
++cnt1;
|
||||
|
||||
// Check for more direct objects
|
||||
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
|
||||
break;
|
||||
++_wordPtr;
|
||||
}
|
||||
|
||||
// Get any reposition and indirect object
|
||||
if (_wordPtr != _words.end()) {
|
||||
// Get the preposition
|
||||
if (getWordType(*_wordPtr) == WT_PREPOSITION)
|
||||
preposition = *_wordPtr++;
|
||||
|
||||
// Get the indirect object
|
||||
noun2 = _adjectiveList.size() + 1;
|
||||
for (;;) {
|
||||
// Get the indirect object
|
||||
if (!getNoun())
|
||||
return false;
|
||||
++cnt2;
|
||||
|
||||
// Check for more objects
|
||||
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
|
||||
break;
|
||||
++_wordPtr;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we're at the end of the input line
|
||||
if (_wordPtr != _words.end()) {
|
||||
parseError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup resulting properties
|
||||
if (preposition) {
|
||||
if (cnt2 > 1) {
|
||||
parseError();
|
||||
return false;
|
||||
}
|
||||
|
||||
_dObject = noun1;
|
||||
_ndObjects = cnt1;
|
||||
_iObject = noun2;
|
||||
} else if (noun2) {
|
||||
if (cnt1 > 1) {
|
||||
parseError();
|
||||
return false;
|
||||
}
|
||||
|
||||
preposition = findWord("to");
|
||||
_dObject = noun2;
|
||||
_ndObjects = cnt2;
|
||||
_iObject = noun1;
|
||||
} else {
|
||||
_dObject = noun1;
|
||||
_ndObjects = cnt1;
|
||||
}
|
||||
|
||||
// Setup the flags for the action lookup
|
||||
if (_dObject)
|
||||
flags |= A_DOBJECT;
|
||||
if (_iObject)
|
||||
flags |= A_IOBJECT;
|
||||
|
||||
// Find the action
|
||||
if (!(_action = findAction(_verbs, preposition, flags))) {
|
||||
parseError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VM::getLine() {
|
||||
// Let the user type in an input line
|
||||
Common::String line = readLine();
|
||||
if (shouldQuit())
|
||||
return false;
|
||||
|
||||
skipSpaces(line);
|
||||
if (line.empty()) {
|
||||
print(_("Speak up! I can't hear you!\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the words of the line
|
||||
_words.clear();
|
||||
while (!line.empty()) {
|
||||
if (!getWord(line))
|
||||
return false;
|
||||
}
|
||||
|
||||
_wordPtr = _words.begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VM::getWord(Common::String &line) {
|
||||
// Find the end of the word
|
||||
const char *wordP = line.c_str();
|
||||
for (; *wordP && !isWhitespace(*wordP); ++wordP) {}
|
||||
|
||||
// Copy out the next word
|
||||
InputWord iw;
|
||||
iw._text = Common::String(line.c_str(), wordP);
|
||||
iw._text.toLowercase();
|
||||
|
||||
// Remove the word from the line
|
||||
line = Common::String(wordP);
|
||||
skipSpaces(line);
|
||||
|
||||
// Look up the word
|
||||
iw._number = findWord(iw._text);
|
||||
|
||||
if (iw._number) {
|
||||
_words.push_back(iw);
|
||||
return true;
|
||||
} else {
|
||||
Common::U32String msg = Common::U32String::format(_("I don't know the word \"%s\".\n"), iw._text.c_str());
|
||||
print(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint VM::getNoun() {
|
||||
// Skip over optional article if present
|
||||
if (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ARTICLE)
|
||||
++_wordPtr;
|
||||
|
||||
// Get optional adjectives
|
||||
uint alStart = _adjectiveList.size();
|
||||
while (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ADJECTIVE) {
|
||||
AdjectiveEntry ae;
|
||||
ae._list = *_wordPtr++;
|
||||
ae._word = _wordPtr - _words.begin() - 1;
|
||||
_adjectiveList.push_back(ae);
|
||||
}
|
||||
_adjectiveList.push_back(AdjectiveEntry());
|
||||
assert(_adjectiveList.size() <= 20);
|
||||
|
||||
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_NOUN) {
|
||||
parseError();
|
||||
return NIL;
|
||||
}
|
||||
|
||||
// Add a noun entry to the list
|
||||
Noun n;
|
||||
n._adjective = &_adjectiveList[alStart];
|
||||
n._noun = *_wordPtr++;
|
||||
n._num = _wordPtr - _words.begin() - 1;
|
||||
_nouns.push_back(n);
|
||||
|
||||
return _nouns.size();
|
||||
}
|
||||
|
||||
bool VM::getVerb() {
|
||||
_verbs.clear();
|
||||
|
||||
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_VERB) {
|
||||
parseError();
|
||||
return false;
|
||||
}
|
||||
|
||||
_verbs.push_back(*_wordPtr++);
|
||||
|
||||
// Check for a word following the verb
|
||||
if (_wordPtr < _words.end()) {
|
||||
_verbs.push_back(*_wordPtr);
|
||||
|
||||
if (checkVerb(_verbs)) {
|
||||
++_wordPtr;
|
||||
} else {
|
||||
_verbs.pop_back();
|
||||
_verbs.push_back(_words.back());
|
||||
|
||||
if (checkVerb(_verbs)) {
|
||||
_words.pop_back();
|
||||
} else {
|
||||
_verbs.pop_back();
|
||||
|
||||
if (!checkVerb(_verbs)) {
|
||||
parseError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VM::match(int obj, int noun, const VM::AdjectiveEntry *adjectives) {
|
||||
if (!hasNoun(obj, noun))
|
||||
return false;
|
||||
|
||||
for (const VM::AdjectiveEntry *adjPtr = adjectives; adjPtr->_list; ++adjPtr) {
|
||||
if (!hasAdjective(obj, adjPtr->_list))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VM::parseError() {
|
||||
print(_("I don't understand.\n"));
|
||||
}
|
||||
|
||||
bool VM::isWhitespace(char c) {
|
||||
return c == ' ' || c == ',' || c == '.';
|
||||
}
|
||||
|
||||
bool VM::skipSpaces(Common::String &str) {
|
||||
while (!str.empty() && isWhitespace(str[0]))
|
||||
str.deleteChar(0);
|
||||
|
||||
return !str.empty();
|
||||
}
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
391
engines/glk/advsys/vm.h
Normal file
391
engines/glk/advsys/vm.h
Normal file
@@ -0,0 +1,391 @@
|
||||
/* 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_ADVSYS_VM
|
||||
#define GLK_ADVSYS_VM
|
||||
|
||||
#include "glk/advsys/glk_interface.h"
|
||||
#include "glk/advsys/game.h"
|
||||
#include "common/stack.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace AdvSys {
|
||||
|
||||
/**
|
||||
* Execution result states
|
||||
*/
|
||||
enum ExecutionResult {
|
||||
IN_PROGRESS = 0, ///< Default state whilst script is in progress
|
||||
FINISH = 1, ///< Script was finished
|
||||
CHAIN = 2, ///< Another script is being chained to
|
||||
ABORT = 3 ///< Script was aborted
|
||||
};
|
||||
|
||||
/**
|
||||
* Opcode list
|
||||
*/
|
||||
enum Opcode {
|
||||
OP_BRT = 0x01, ///< Branch on true
|
||||
OP_BRF = 0x02, ///< Branch on false
|
||||
OP_BR = 0x03, ///< Branch unconditionally
|
||||
OP_T = 0x04, ///< Load top of stack with t
|
||||
OP_NIL = 0x05, ///< Load top of stack with nil
|
||||
OP_PUSH = 0x06, ///< Push nil onto stack
|
||||
OP_NOT = 0x07, ///< Logical negate top of stack
|
||||
OP_ADD = 0x08, ///< Add two numeric expressions
|
||||
OP_SUB = 0x09, ///< Subtract two numeric expressions
|
||||
OP_MUL = 0x0A, ///< Multiply two numeric expressions
|
||||
OP_DIV = 0x0B, ///< Divide two numeric expressions
|
||||
OP_REM = 0x0C, ///< Remainder of two numeric expressions
|
||||
OP_BAND = 0x0D, ///< Bitwise and of two numeric expressions
|
||||
OP_BOR = 0x0E, ///< Bitwise or of two numeric expressions
|
||||
OP_BNOT = 0x0F, ///< Bitwise not
|
||||
OP_LT = 0x10, ///< Less than
|
||||
OP_EQ = 0x11, ///< Equal to
|
||||
OP_GT = 0x12, ///< Greater than
|
||||
OP_LIT = 0x13, ///< Load literal
|
||||
OP_VAR = 0x14, ///< Load a variable value
|
||||
OP_GETP = 0x15, ///< Get the value of an object property
|
||||
OP_SETP = 0x16, ///< Set the value of an object property
|
||||
OP_SET = 0x17, ///< Set the value of a variable
|
||||
OP_PRINT = 0x18, ///< Print messages
|
||||
OP_TERPRI = 0x19, ///< Terminate the print line
|
||||
OP_PNUMBER = 0x1A, ///< Print a number
|
||||
OP_FINISH = 0x1B, ///< Finish handling this command
|
||||
OP_CHAIN = 0x1C, ///< Chain to the next handler
|
||||
OP_ABORT = 0x1D, ///< Abort this command
|
||||
OP_EXIT = 0x1E, ///< Exit the game
|
||||
OP_RETURN = 0x1F, ///< Return from function
|
||||
OP_CALL = 0x20, ///< Call subroutine
|
||||
OP_SVAR = 0x21, ///< Short load a variable
|
||||
OP_SSET = 0x22, ///< Short set a variable
|
||||
OP_SPLIT = 0x23, ///< Short load a positive literal
|
||||
OP_SNLIT = 0x24, ///< Short load a negative literal
|
||||
OP_YORN = 0x25, ///< Yes or No predicate
|
||||
OP_SAVE = 0x26, ///< Save data structures
|
||||
OP_RESTORE = 0x27, ///< Restore data structures
|
||||
OP_ARG = 0x28, ///< Load an argument value
|
||||
OP_ASET = 0x29, ///< Set an argument value
|
||||
OP_TMP = 0x2A, ///< Load a temporary variable value
|
||||
OP_TSET = 0x2B, ///< Set a temporary variable
|
||||
OP_TSPACE = 0x2C, ///< Allocate temporary variable space
|
||||
OP_CLASS = 0x2D, ///< Get the class of an object
|
||||
OP_MATCH = 0x2E, ///< Match a noun phrase with an object
|
||||
OP_PNOUN = 0x2F, ///< Print a noun phrase
|
||||
OP_RESTART = 0x30, ///< Restart the current game
|
||||
OP_RAND = 0x31, ///< Generate a random number
|
||||
OP_RNDMIZE = 0x32, ///< Seed the random number generator
|
||||
OP_SEND = 0x33, ///< Send a message to an object
|
||||
OP_VOWEL = 0x34, ///< Check for vowel beginning string
|
||||
|
||||
OP_XVAR = 0x40, ///< Extra short load a variable
|
||||
OP_XSET = 0x60, ///< Extra short set a variable
|
||||
OP_XPLIT = 0x80, ///< Extra short load a positive literal
|
||||
OP_XNLIT = 0xC0 ///< Extra short load a negative literal
|
||||
};
|
||||
|
||||
/**
|
||||
* Indexes useable in function pointer array offsets
|
||||
*/
|
||||
enum FPOffset {
|
||||
FP_FP = 0,
|
||||
FP_PC = 1,
|
||||
FP_ARGS_SIZE = 2,
|
||||
FP_ARGS = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* Action flags
|
||||
*/
|
||||
enum ActionFlag {
|
||||
A_ACTOR = 1, ///< Actor
|
||||
A_DOBJECT = 2, ///< Direct object
|
||||
A_IOBJECT = 4 ///< Indirect object
|
||||
};
|
||||
|
||||
|
||||
class VM;
|
||||
typedef void (VM::*OpcodeMethod)();
|
||||
|
||||
/**
|
||||
* Fixed stack
|
||||
*/
|
||||
class FixedStack : public Common::FixedStack<int, 500> {
|
||||
public:
|
||||
/**
|
||||
* Resize the stack
|
||||
*/
|
||||
void resize(size_t newSize) {
|
||||
assert(newSize <= 500);
|
||||
_size = newSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate extra space on the stack
|
||||
*/
|
||||
void allocate(size_t amount) {
|
||||
uint oldSize = _size;
|
||||
resize(_size + amount);
|
||||
Common::fill(&_stack[oldSize], &_stack[oldSize + amount], 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a function pointer reference into the stack. It also allows
|
||||
* positive array indexing to reference the following:
|
||||
* 0 = Previous function pointer
|
||||
* 1 = Return PC
|
||||
* 2 = Size of argument block
|
||||
* 3+ = Any function call arguments
|
||||
*/
|
||||
class FunctionPointer {
|
||||
private:
|
||||
FixedStack &_stack;
|
||||
int _index;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
FunctionPointer(FixedStack &s) : _stack(s), _index(0) {}
|
||||
|
||||
/**
|
||||
* Array indexing
|
||||
*/
|
||||
int &operator[](int idx) { return _stack[_index - idx - 1]; }
|
||||
|
||||
/**
|
||||
* Sets the index in the stack of the function pointer
|
||||
*/
|
||||
FunctionPointer &operator=(int index) {
|
||||
_index = index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the function pointer
|
||||
*/
|
||||
void clear() { _index = 0; }
|
||||
|
||||
/**
|
||||
* Returns the index in the stack of the function pointer
|
||||
*/
|
||||
operator int() const { return _index; }
|
||||
|
||||
/**
|
||||
* Sets the function pointer to the top of the stack
|
||||
*/
|
||||
void set() {
|
||||
_index = _stack.size();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Main VM for AdvSys
|
||||
*/
|
||||
class VM : public GlkInterface, public Game {
|
||||
struct InputWord {
|
||||
Common::String _text;
|
||||
int _number;
|
||||
|
||||
InputWord() : _number(0) {}
|
||||
operator int() const { return _number; }
|
||||
};
|
||||
struct AdjectiveEntry {
|
||||
int _list;
|
||||
int _word;
|
||||
|
||||
AdjectiveEntry() : _list(0), _word(0) {}
|
||||
};
|
||||
struct Noun {
|
||||
int _noun;
|
||||
int _num;
|
||||
AdjectiveEntry *_adjective;
|
||||
|
||||
Noun() : _noun(0), _num(0), _adjective(nullptr) {}
|
||||
};
|
||||
private:
|
||||
// Execution fields
|
||||
static OpcodeMethod _METHODS[0x34];
|
||||
int _pc;
|
||||
ExecutionResult _status;
|
||||
FixedStack _stack;
|
||||
FunctionPointer _fp;
|
||||
|
||||
// Parser fields
|
||||
int _actor;
|
||||
int _action;
|
||||
int _dObject;
|
||||
int _ndObjects;
|
||||
int _iObject;
|
||||
Common::Array<InputWord> _words;
|
||||
Common::Array<InputWord>::iterator _wordPtr;
|
||||
Common::Array<int> _verbs;
|
||||
Common::Array<AdjectiveEntry> _adjectiveList;
|
||||
Common::Array<Noun> _nouns;
|
||||
private:
|
||||
/**
|
||||
* Execute a single opcode within the script
|
||||
*/
|
||||
void executeOpcode();
|
||||
|
||||
/**
|
||||
* Get the next code byte and increments the PC counter
|
||||
*/
|
||||
int readCodeByte() {
|
||||
return getCodeByte(_pc++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next code word and increases the PC counter to after it
|
||||
*/
|
||||
int readCodeWord() {
|
||||
int v = getCodeWord(_pc);
|
||||
_pc += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an input line and parse it
|
||||
*/
|
||||
bool parseInput();
|
||||
|
||||
/**
|
||||
* Gets an input line and splits it up into the words array
|
||||
*/
|
||||
bool getLine();
|
||||
|
||||
/**
|
||||
* Get the next word of a passed input line
|
||||
* @param line Input line
|
||||
* @returns True if a valid word was extracted
|
||||
*/
|
||||
bool getWord(Common::String &line);
|
||||
|
||||
/**
|
||||
* Get a noun phrase and return the object it refers to
|
||||
*/
|
||||
uint getNoun();
|
||||
|
||||
/**
|
||||
* Get a verb phrase and return the action it refers to
|
||||
*/
|
||||
bool getVerb();
|
||||
|
||||
/**
|
||||
* Match an object against a name and list of adjectives
|
||||
*/
|
||||
bool match(int obj, int noun, const AdjectiveEntry *adjectives);
|
||||
|
||||
/**
|
||||
* Called when a parsing error occurs
|
||||
*/
|
||||
void parseError();
|
||||
|
||||
/**
|
||||
* Returns true if a passed character is a skippable whitespace
|
||||
*/
|
||||
static bool isWhitespace(char c);
|
||||
|
||||
/**
|
||||
* Skips over spaces in a passed string
|
||||
*/
|
||||
static bool skipSpaces(Common::String &str);
|
||||
private:
|
||||
void opBRT();
|
||||
void opBRF();
|
||||
void opBR();
|
||||
void opT();
|
||||
void opNIL();
|
||||
void opPUSH();
|
||||
void opNOT();
|
||||
void opADD();
|
||||
void opSUB();
|
||||
void opMUL();
|
||||
void opDIV();
|
||||
void opREM();
|
||||
void opBAND();
|
||||
void opBOR();
|
||||
void opBNOT();
|
||||
void opLT();
|
||||
void opEQ();
|
||||
void opGT();
|
||||
void opLIT();
|
||||
void opVAR();
|
||||
void opGETP();
|
||||
void opSETP();
|
||||
void opSET();
|
||||
void opPRINT();
|
||||
void opTERPRI();
|
||||
void opPNUMBER();
|
||||
void opFINISH();
|
||||
void opCHAIN();
|
||||
void opABORT();
|
||||
void opEXIT();
|
||||
void opRETURN();
|
||||
void opCALL();
|
||||
void opSVAR();
|
||||
void opSSET();
|
||||
void opSPLIT();
|
||||
void opSNLIT();
|
||||
void opYORN();
|
||||
void opSAVE();
|
||||
void opRESTORE();
|
||||
void opARG();
|
||||
void opASET();
|
||||
void opTMP();
|
||||
void opTSET();
|
||||
void opTSPACE();
|
||||
void opCLASS();
|
||||
void opMATCH();
|
||||
void opPNOUN();
|
||||
void opRESTART();
|
||||
void opRAND();
|
||||
void opRNDMIZE();
|
||||
void opSEND();
|
||||
void opVOWEL();
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
VM(OSystem *syst, const GlkGameDescription &gameDesc);
|
||||
|
||||
/**
|
||||
* Exeecute a script
|
||||
* @param offset Script offset
|
||||
* @returns Script result code
|
||||
*/
|
||||
ExecutionResult execute(int offset);
|
||||
|
||||
/**
|
||||
* Get an input line and parse it
|
||||
*/
|
||||
bool getInput();
|
||||
|
||||
/**
|
||||
* Get the next command (next direct object)
|
||||
*/
|
||||
bool nextCommand();
|
||||
};
|
||||
|
||||
} // End of namespace AdvSys
|
||||
} // End of namespace Glk
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user