Initial commit

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

View File

@@ -0,0 +1,87 @@
/* 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/comprehend/charset.h"
#include "common/file.h"
#include "common/md5.h"
#include "graphics/surface.h"
namespace Glk {
namespace Comprehend {
void FixedFont::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const {
assert(dst->format.bytesPerPixel == 4);
assert(chr >= 32 && chr < 128);
for (int yp = 0; yp < 8; ++yp) {
if ((y + yp) < 0 || (y + yp) >= dst->h)
continue;
uint32 *lineP = (uint32 *)dst->getBasePtr(x, y + yp);
byte bits = _data[chr - 32][yp];
for (int xp = x; xp < (x + 8); ++xp, ++lineP, bits >>= 1) {
if ((xp >= 0) && (xp < dst->w) && (bits & 1))
*lineP = color;
}
}
}
/*-------------------------------------------------------*/
CharSet::CharSet() : FixedFont() {
Common::File f;
if (!f.open("charset.gda"))
error("Could not open char set");
uint version = f.readUint16LE();
if (version != 0x1100)
error("Unknown char set version");
f.seek(4);
for (int idx = 0; idx < 128 - 32; ++idx)
f.read(&_data[idx][0], 8);
f.close();
}
/*-------------------------------------------------------*/
TalismanFont::TalismanFont() : FixedFont() {
// Extra strings are (annoyingly) stored in the game binary
Common::File f;
if (!f.open("novel.exe"))
error("novel.exe is a required file");
Common::String md5 = Common::computeStreamMD5AsString(f, 1024);
if (md5 == "0e7f002971acdb055f439020363512ce" || md5 == "2e18c88ce352ebea3e14177703a0485f") {
for (int idx = 0; idx < 128 - 32; ++idx)
f.read(&_data[idx][0], 8);
} else {
error("Unrecognised novel.exe encountered");
}
f.close();
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,103 @@
/* 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_COMPREHEND_CHARSET_H
#define GLK_COMPREHEND_CHARSET_H
#include "graphics/font.h"
namespace Glk {
namespace Comprehend {
/**
* Fixed width base font
*/
class FixedFont : public Graphics::Font {
protected:
byte _data[128 - 32][8];
public:
~FixedFont() override {}
/**
*/
int getFontHeight() const override {
return 8;
}
/**
* Query the maximum width of the font.
*/
int getMaxCharWidth() const override {
return 8;
}
/**
* Query the width of a specific character.
*/
int getCharWidth(uint32 chr) const override {
return 8;
}
/**
* Query the kerning offset between two characters.
*/
int getKerningOffset(uint32 left, uint32 right) const override {
return 0;
}
/**
* Calculate the bounding box of a character. It is assumed that
* the character shall be drawn at position (0, 0).
*/
Common::Rect getBoundingBox(uint32 chr) const override {
assert(chr < 127);
return Common::Rect(0, 0, 8, 8);
}
/**
* Draw a character at a specific point on a surface.
*/
void drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const override;
};
/**
* Font loaded from charset.gda
*/
class CharSet : public FixedFont {
public:
CharSet();
~CharSet() override {}
};
/**
* Talisman font directly from the executable
*/
class TalismanFont : public FixedFont {
public:
TalismanFont();
~TalismanFont() override {}
};
} // End of namespace Comprehend
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,296 @@
/* 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/comprehend/comprehend.h"
#include "glk/comprehend/debugger.h"
#include "glk/comprehend/draw_surface.h"
#include "glk/comprehend/game.h"
#include "glk/comprehend/game_cc.h"
#include "glk/comprehend/game_data.h"
#include "glk/comprehend/game_oo.h"
#include "glk/comprehend/game_tm.h"
#include "glk/comprehend/game_tr1.h"
#include "glk/comprehend/game_tr2.h"
#include "glk/comprehend/pics.h"
#include "glk/quetzal.h"
#include "common/config-manager.h"
#include "common/ustr.h"
#include "engines/util.h"
namespace Glk {
namespace Comprehend {
// Even with no ScummVM scaling, internally we do a 2x scaling to
// render on a 640x480 window, to allow for better looking text
#define SCALE_FACTOR 2
Comprehend *g_comprehend;
Comprehend::Comprehend(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
_topWindow(nullptr), _bottomWindow(nullptr), _roomDescWindow(nullptr),
_drawSurface(nullptr), _game(nullptr), _pics(nullptr), _saveSlot(-1),
_graphicsEnabled(true), _drawFlags(0), _disableSaves(false) {
g_comprehend = this;
}
Comprehend::~Comprehend() {
delete _drawSurface;
delete _game;
SearchMan.remove("Pics"); // This also deletes it
g_comprehend = nullptr;
}
void Comprehend::initGraphicsMode() {
Graphics::PixelFormat pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
initGraphics(640, 400, &pixelFormat);
}
void Comprehend::createConfiguration() {
GlkAPI::createConfiguration();
switchToWhiteOnBlack();
}
void Comprehend::runGame() {
initialize();
// Lookup game
createGame();
_game->loadGame();
_game->playGame();
deinitialize();
}
void Comprehend::initialize() {
_bottomWindow = (TextBufferWindow *)glk_window_open(nullptr, 0, 0, wintype_TextBuffer, 1);
glk_set_window(_bottomWindow);
showGraphics();
_topWindow->fillRect(0, Rect(0, 0, _topWindow->_w, _topWindow->_h));
// Initialize drawing surface, and the archive that abstracts
// the room and item graphics as as individual files
_drawSurface = new DrawSurface();
_pics = new Pics();
SearchMan.add("Pics", _pics, 99, true);
// Check for savegame to load
_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
}
void Comprehend::deinitialize() {
glk_window_close(_topWindow);
glk_window_close(_bottomWindow);
glk_window_close(_roomDescWindow);
}
void Comprehend::createDebugger() {
setDebugger(new Debugger());
}
void Comprehend::createGame() {
if (_gameDescription._gameId == "crimsoncrown")
_game = new CrimsonCrownGame();
else if (_gameDescription._gameId == "ootopos")
_game = new OOToposGame();
else if (_gameDescription._gameId == "talisman")
_game = new TalismanGame();
else if (_gameDescription._gameId == "transylvania")
_game = new TransylvaniaGame1();
else if (_gameDescription._gameId == "transylvaniav2")
_game = new TransylvaniaGame2();
else
error("Unknown game");
}
void Comprehend::print(const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
Common::String msg = Common::String::vformat(fmt, argp);
va_end(argp);
glk_put_string_stream(glk_window_get_stream(_bottomWindow), msg.c_str());
}
void Comprehend::print_u32_internal(const Common::U32String *fmt, ...) {
Common::U32String outputMsg;
va_list argp;
va_start(argp, fmt);
Common::U32String::vformat(fmt->begin(), fmt->end(), outputMsg, argp);
va_end(argp);
glk_put_string_stream_uni(glk_window_get_stream(_bottomWindow), outputMsg.u32_str());
}
void Comprehend::printRoomDesc(const Common::String &desc) {
if (_roomDescWindow) {
glk_window_clear(_roomDescWindow);
// Get the grid width and do a word wrap
uint width;
glk_window_get_size(_roomDescWindow, &width, nullptr);
Common::String str = desc;
str.wordWrap(width - 2);
str += '\n';
// Display the room description
while (!str.empty()) {
size_t idx = str.findFirstOf('\n');
Common::String line = Common::String::format(" %s", Common::String(str.c_str(), str.c_str() + idx + 1).c_str());
glk_put_string_stream(glk_window_get_stream(_roomDescWindow), line.c_str());
str = Common::String(str.c_str() + idx + 1);
}
}
}
void Comprehend::readLine(char *buffer, size_t maxLen) {
event_t ev;
glk_request_line_event(_bottomWindow, buffer, maxLen - 1, 0);
for (;;) {
glk_select(&ev);
if (ev.type == evtype_Quit) {
glk_cancel_line_event(_bottomWindow, &ev);
return;
} else if (ev.type == evtype_LineInput)
break;
}
buffer[ev.val1] = 0;
debug(1, "\n> %s", buffer);
}
int Comprehend::readChar() {
glk_request_char_event(_bottomWindow);
setDisableSaves(true);
event_t ev;
while (ev.type != evtype_CharInput) {
glk_select(&ev);
if (ev.type == evtype_Quit) {
glk_cancel_char_event(_bottomWindow);
return -1;
}
}
setDisableSaves(false);
return ev.val1;
}
Common::Error Comprehend::readSaveData(Common::SeekableReadStream *rs) {
Common::Serializer s(rs, nullptr);
_game->synchronizeSave(s);
_game->_updateFlags = UPDATE_ALL;
if (isInputLineActive()) {
// Restored game using GMM, so update grpahics and print room description
g_comprehend->print("\n");
_game->update();
g_comprehend->print("> ");
}
return Common::kNoError;
}
Common::Error Comprehend::writeGameData(Common::WriteStream *ws) {
Common::Serializer s(nullptr, ws);
_game->synchronizeSave(s);
return Common::kNoError;
}
bool Comprehend::loadLauncherSavegameIfNeeded() {
if (_saveSlot != -1) {
return loadGameState(_saveSlot).getCode() == Common::kNoError;
}
return false;
}
void Comprehend::drawPicture(uint pictureNum) {
if (_topWindow) {
// Clear the picture cache before each drawing in OO-Topos. Wearing the goggles
// can producing different versions of the same scene, so we can't cache it
if (_gameDescription._gameId == "ootopos")
_pictures->clear();
glk_image_draw_scaled(_topWindow, pictureNum,
20 * SCALE_FACTOR, 0, G_RENDER_WIDTH * SCALE_FACTOR, G_RENDER_HEIGHT * SCALE_FACTOR);
}
}
void Comprehend::drawLocationPicture(int pictureNum, bool clearBg) {
drawPicture(pictureNum + (clearBg ? LOCATIONS_OFFSET : LOCATIONS_NO_BG_OFFSET));
}
void Comprehend::drawItemPicture(int pictureNum) {
drawPicture(pictureNum + ITEMS_OFFSET);
}
void Comprehend::clearScreen(bool isBright) {
drawPicture(isBright ? BRIGHT_ROOM : DARK_ROOM);
}
bool Comprehend::toggleGraphics() {
if (_topWindow) {
// Remove the picture window
glk_window_close(_topWindow);
_topWindow = nullptr;
_graphicsEnabled = false;
// Add the room description window
_roomDescWindow = (TextGridWindow *)glk_window_open(_bottomWindow,
winmethod_Above | winmethod_Fixed, 5, wintype_TextGrid, 1);
return false;
} else {
glk_window_close(_roomDescWindow);
_roomDescWindow = nullptr;
// Create the window again
showGraphics();
return true;
}
}
void Comprehend::showGraphics() {
if (!_topWindow) {
_topWindow = (GraphicsWindow *)glk_window_open(_bottomWindow,
winmethod_Above | winmethod_Fixed,
160 * SCALE_FACTOR, wintype_Graphics, 2);
_graphicsEnabled = true;
}
}
bool Comprehend::isInputLineActive() const {
return _bottomWindow->_lineRequest || _bottomWindow->_lineRequestUni;
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,235 @@
/* 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_COMPREHEND_COMPREHEND_H
#define GLK_COMPREHEND_COMPREHEND_H
#include "common/scummsys.h"
#include "glk/comprehend/game.h"
#include "glk/glk_api.h"
#include "glk/window_graphics.h"
#include "glk/window_text_buffer.h"
#include "glk/window_text_grid.h"
namespace Glk {
namespace Comprehend {
class DrawSurface;
class Pics;
#define EXTRA_STRING_TABLE(x) (0x8200 | (x))
struct GameStrings {
uint16 game_restart;
};
/**
* Comprehend engine
*/
class Comprehend : public GlkAPI {
private:
int _saveSlot; ///< Save slot when loading savegame from launcher
bool _graphicsEnabled;
bool _disableSaves;
public:
GraphicsWindow *_topWindow;
TextGridWindow *_roomDescWindow;
TextBufferWindow *_bottomWindow;
DrawSurface *_drawSurface;
ComprehendGame *_game;
Pics *_pics;
uint _drawFlags;
private:
/**
* Initialization code
*/
void initialize();
/**
* Deinitialization
*/
void deinitialize();
/**
* Create the debugger
*/
void createDebugger() override;
/**
* Creates the appropriate game class
*/
void createGame();
protected:
/**
* Loads the configuration
*/
void createConfiguration() override;
public:
/**
* Constructor
*/
Comprehend(OSystem *syst, const GlkGameDescription &gameDesc);
~Comprehend() override;
/**
* Returns the running interpreter type
*/
InterpreterType getInterpreterType() const override {
return INTERPRETER_SCOTT;
}
/**
* Initialize the graphics
*/
void initGraphicsMode() override;
/**
* Execute the game
*/
void runGame() override;
/**
* Handles loading a savegame selected from the launcher
*/
bool loadLauncherSavegameIfNeeded();
/**
* 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;
/**
* Print string to the buffer window
*/
void print(const char *fmt, ...);
/**
* Print unicode-string to the buffer window
*/
template<class... TParam>
void print(const Common::U32String &fmt, TParam... param);
/**
* Prints the room description in the room description window
*/
void printRoomDesc(const Common::String &desc);
/**
* Read an input line
*/
void readLine(char *buffer, size_t maxLen);
/**
* Read in a character
*/
int readChar();
/**
* Draw a picture
*/
void drawPicture(uint pictureNum);
/**
* Draw a location image
*/
void drawLocationPicture(int pictureNum, bool clearBg = true);
/**
* Draw an item image
*/
void drawItemPicture(int pictureNum);
/**
* Clear the picture area
*/
void clearScreen(bool isBright);
/**
* Toggles whether the picture window is visible
*/
bool toggleGraphics();
/**
* Ensures the picture window is visible
*/
void showGraphics();
/**
* Returns true if the graphics area is visible
*/
bool isGraphicsEnabled() const {
return _graphicsEnabled;
}
ComprehendGame *getGame() const {
return _game;
}
/**
* Returns true if a savegame can be loaded
*/
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
return !_disableSaves && GlkAPI::canLoadGameStateCurrently();
}
/**
* Returns true if the game can be saved
*/
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
return !_disableSaves && GlkAPI::canSaveGameStateCurrently();
}
/**
* Set whether saving and loading is currently available
*/
void setDisableSaves(bool flag) {
_disableSaves = flag;
}
/**
* Returns true if an input line is currently active
*/
bool isInputLineActive() const;
private:
void print_u32_internal(const Common::U32String *fmt, ...);
};
template<class... TParam>
void Comprehend::print(const Common::U32String &fmt, TParam... param) {
print_u32_internal(&fmt, Common::forward<TParam>(param)...);
}
extern Comprehend *g_comprehend;
} // End of namespace Comprehend
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,166 @@
/* 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/comprehend/debugger.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/pics.h"
namespace Glk {
namespace Comprehend {
Debugger *g_debugger;
Debugger::Debugger() : Glk::Debugger(), _invLimit(true) {
g_debugger = this;
registerCmd("dump", WRAP_METHOD(Debugger, cmdDump));
registerCmd("floodfills", WRAP_METHOD(Debugger, cmdFloodfills));
registerCmd("room", WRAP_METHOD(Debugger, cmdRoom));
registerCmd("itemroom", WRAP_METHOD(Debugger, cmdItemRoom));
registerCmd("findstring", WRAP_METHOD(Debugger, cmdFindString));
registerCmd("draw", WRAP_METHOD(Debugger, cmdDraw));
registerCmd("invlimit", WRAP_METHOD(Debugger, cmdInventoryLimit));
}
Debugger::~Debugger() {
g_debugger = nullptr;
}
void Debugger::print(const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
Common::String msg = Common::String::vformat(fmt, argp);
va_end(argp);
debugPrintf("%s", msg.c_str());
debugN("%s", msg.c_str());
}
bool Debugger::cmdDump(int argc, const char **argv) {
Common::String type = (argc >= 2) ? argv[1] : "";
uint param = (argc == 3) ? strToInt(argv[2]) : 0;
ComprehendGame *game = g_comprehend->_game;
if (!dumpGameData(game, type, param))
debugPrintf("Unknown dump option\n");
return true;
}
bool Debugger::cmdFloodfills(int argc, const char **argv) {
if (argc == 2 && !strcmp(argv[1], "off")) {
g_comprehend->_drawFlags |= IMAGEF_NO_PAINTING;
debugPrintf("Floodfills are off\n");
} else {
g_comprehend->_drawFlags &= ~IMAGEF_NO_PAINTING;
debugPrintf("Floodfills are on\n");
}
return true;
}
bool Debugger::cmdRoom(int argc, const char **argv) {
ComprehendGame *game = g_comprehend->getGame();
if (argc == 1) {
debugPrintf("Current room = %d\n", game->_currentRoom);
return true;
} else {
game->move_to(strToInt(argv[1]));
game->update_graphics();
return false;
}
}
bool Debugger::cmdItemRoom(int argc, const char **argv) {
ComprehendGame *game = g_comprehend->getGame();
if (argc == 1) {
debugPrintf("itemroom <item> [<room>]\n");
} else {
Item *item = game->get_item(strToInt(argv[1]));
if (argc == 2) {
debugPrintf("Item room = %d\n", item->_room);
} else {
int room = strToInt(argv[2]);
if (room == 0)
room = game->_currentRoom;
bool visibleChange = item->_room == game->_currentRoom ||
room == game->_currentRoom;
item->_room = room;
if (visibleChange) {
game->_updateFlags |= UPDATE_GRAPHICS_ITEMS;
game->update_graphics();
}
return false;
}
}
return true;
}
bool Debugger::cmdFindString(int argc, const char **argv) {
ComprehendGame *game = g_comprehend->getGame();
if (argc == 1) {
debugPrintf("findstring <string>\n");
} else {
for (int arrNum = 0; arrNum < 2; ++arrNum) {
const StringTable &table = (arrNum == 0) ? game->_strings : game->_strings2;
const char *name = (arrNum == 0) ? "_strings" : "_strings2";
for (uint idx = 0; idx < table.size(); ++idx) {
if (table[idx].contains(argv[1]))
debugPrintf("%s[%u] = %s\n", name, idx, table[idx].c_str());
}
}
}
return true;
}
bool Debugger::cmdDraw(int argc, const char **argv) {
if (argc == 1) {
debugPrintf("draw <number>\n");
return true;
} else {
g_comprehend->drawLocationPicture(strToInt(argv[1]), true);
return false;
}
}
bool Debugger::cmdInventoryLimit(int argc, const char **argv) {
if (argc == 1) {
debugPrintf("invlimit on|off\n");
} else {
_invLimit = !strcmp(argv[1], "on") || !strcmp(argv[1], "true");
debugPrintf("inventory limit is now %s\n", _invLimit ? "on" : "off");
}
return true;
}
} // namespace Comprehend
} // namespace Glk

View 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/>.
*
*/
#ifndef GLK_COMPREHEND_DEBUGGER_H
#define GLK_COMPREHEND_DEBUGGER_H
#include "glk/debugger.h"
#include "glk/comprehend/debugger_dumper.h"
namespace Glk {
namespace Comprehend {
class Debugger : public Glk::Debugger, public DebuggerDumper {
private:
/**
* Dump data
*/
bool cmdDump(int argc, const char **argv);
/**
* Sets whether floodfills are done when rendering images
*/
bool cmdFloodfills(int argc, const char **argv);
/**
* Sets or lists the current room
*/
bool cmdRoom(int argc, const char **argv);
/**
* Sets or lists the room for an item
*/
bool cmdItemRoom(int argc, const char **argv);
/**
* Find a string given a partial specified
*/
bool cmdFindString(int argc, const char **argv);
/**
* Draw an image to the screen
*/
bool cmdDraw(int argc, const char **argv);
/**
* Flags whether to turn the inventory limit on or off
*/
bool cmdInventoryLimit(int argc, const char **argv);
protected:
void print(const char *fmt, ...) override;
public:
bool _invLimit;
public:
Debugger();
virtual ~Debugger();
};
extern Debugger *g_debugger;
} // End of namespace Comprehend
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,444 @@
/* 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/comprehend/debugger_dumper.h"
#include "glk/comprehend/dictionary.h"
#include "glk/comprehend/game.h"
namespace Glk {
namespace Comprehend {
DebuggerDumper::DebuggerDumper() : _game(nullptr) {
_opcodes[OPCODE_HAVE_OBJECT] = "have_object";
_opcodes[OPCODE_NOT_HAVE_OBJECT] = "not_have_object";
_opcodes[OPCODE_HAVE_CURRENT_OBJECT] = "have_current_object";
_opcodes[OPCODE_NOT_HAVE_CURRENT_OBJECT] = "not_have_current_object";
_opcodes[OPCODE_OBJECT_IS_NOT_NOWHERE] = "object_is_not_nowhere";
_opcodes[OPCODE_CURRENT_IS_OBJECT] = "current_is_object";
_opcodes[OPCODE_CURRENT_OBJECT_NOT_TAKEABLE] = "current_object_not_takeable";
_opcodes[OPCODE_CURRENT_OBJECT_NOT_IN_ROOM] = "current_object_not_in_room";
_opcodes[OPCODE_CURRENT_OBJECT_IS_NOWHERE] = "current_object_is_nowhere";
_opcodes[OPCODE_CURRENT_OBJECT_NOT_PRESENT] = "current_object_not_present";
_opcodes[OPCODE_TAKE_OBJECT] = "take_object";
_opcodes[OPCODE_TAKE_CURRENT_OBJECT] = "take_current_object";
_opcodes[OPCODE_DROP_OBJECT] = "drop_object";
_opcodes[OPCODE_DROP_CURRENT_OBJECT] = "drop_current_object";
_opcodes[OPCODE_OR] = "or";
_opcodes[OPCODE_IN_ROOM] = "in_room";
_opcodes[OPCODE_VAR_EQ1] = "var_eq1";
_opcodes[OPCODE_VAR_EQ2] = "var_eq2";
_opcodes[OPCODE_VAR_GT1] = "var_gt1";
_opcodes[OPCODE_VAR_GT2] = "var_gt2";
_opcodes[OPCODE_VAR_GTE1] = "var_gte1";
_opcodes[OPCODE_VAR_GTE2] = "var_gte2";
_opcodes[OPCODE_CURRENT_OBJECT_NOT_VALID] = "current_object_not_valid";
_opcodes[OPCODE_INVENTORY_FULL] = "inventory_full";
_opcodes[OPCODE_INVENTORY_FULL_X] = "inventory_full_x";
_opcodes[OPCODE_OBJECT_PRESENT] = "object_present";
_opcodes[OPCODE_ELSE] = "else";
_opcodes[OPCODE_OBJECT_IN_ROOM] = "object_in_room";
_opcodes[OPCODE_TEST_FLAG] = "test_flag";
_opcodes[OPCODE_CURRENT_OBJECT_IN_ROOM] = "current_object_in_room";
_opcodes[OPCODE_CURRENT_OBJECT_PRESENT] = "current_object_present";
_opcodes[OPCODE_TEST_ROOM_FLAG] = "test_room_flag";
_opcodes[OPCODE_NOT_IN_ROOM] = "not_in_room";
_opcodes[OPCODE_OBJECT_NOT_PRESENT] = "object_not_present";
_opcodes[OPCODE_OBJECT_NOT_IN_ROOM] = "object_not_in_room";
_opcodes[OPCODE_TEST_NOT_FLAG] = "test_not_flag";
_opcodes[OPCODE_OBJECT_IS_NOWHERE] = "object_is_nowhere";
_opcodes[OPCODE_TEST_NOT_ROOM_FLAG] = "test_not_room_flag";
_opcodes[OPCODE_INVENTORY] = "inventory";
_opcodes[OPCODE_MOVE_OBJECT_TO_ROOM] = "move_object_to_room";
_opcodes[OPCODE_SAVE_ACTION] = "save_action";
_opcodes[OPCODE_CLEAR_LINE] = "clear_line";
_opcodes[OPCODE_MOVE_TO_ROOM] = "move_to_room";
_opcodes[OPCODE_VAR_ADD] = "var_add";
_opcodes[OPCODE_SET_ROOM_DESCRIPTION] = "set_room_description";
_opcodes[OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM] = "move_object_to_current_room";
_opcodes[OPCODE_VAR_SUB] = "var_sub";
_opcodes[OPCODE_SET_OBJECT_DESCRIPTION] = "set_object_description";
_opcodes[OPCODE_SET_OBJECT_LONG_DESCRIPTION] = "set_object_long_description";
_opcodes[OPCODE_MOVE_DEFAULT] = "move_default";
_opcodes[OPCODE_PRINT] = "print";
_opcodes[OPCODE_REMOVE_OBJECT] = "remove_object";
_opcodes[OPCODE_SET_FLAG] = "set_flag";
_opcodes[OPCODE_CALL_FUNC] = "call_func";
_opcodes[OPCODE_CALL_FUNC2] = "call_func2";
_opcodes[OPCODE_TURN_TICK] = "turn_tick";
_opcodes[OPCODE_CLEAR_FLAG] = "clear_flag";
_opcodes[OPCODE_INVENTORY_ROOM] = "inventory_room";
_opcodes[OPCODE_SPECIAL] = "special";
_opcodes[OPCODE_SET_ROOM_GRAPHIC] = "set_room_graphic";
_opcodes[OPCODE_SET_OBJECT_GRAPHIC] = "set_object_graphic";
_opcodes[OPCODE_REMOVE_CURRENT_OBJECT] = "remove_current_object";
_opcodes[OPCODE_MOVE_DIR] = "move_dir";
_opcodes[OPCODE_VAR_INC] = "var_inc";
_opcodes[OPCODE_VAR_DEC] = "var_dec";
_opcodes[OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM] = "move_current_object_to_room";
_opcodes[OPCODE_DESCRIBE_CURRENT_OBJECT] = "describe_current_object";
_opcodes[OPCODE_SET_STRING_REPLACEMENT1] = "set_string_replacement1";
_opcodes[OPCODE_SET_STRING_REPLACEMENT2] = "set_string_replacement2";
_opcodes[OPCODE_SET_STRING_REPLACEMENT3] = "set_string_replacement3";
_opcodes[OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT] = "set_current_noun_string_replacement";
_opcodes[OPCODE_DRAW_ROOM] = "draw_room";
_opcodes[OPCODE_DRAW_OBJECT] = "draw_object";
_opcodes[OPCODE_WAIT_KEY] = "wait_key";
_opcodes[OPCODE_TEST_FALSE] = "test_false";
_opcodes[OPCODE_OBJECT_CAN_TAKE] = "object_can_take";
_opcodes[OPCODE_OBJECT_TAKEABLE] = "object_takeable";
_opcodes[OPCODE_CLEAR_INVISIBLE] = "clear_invisible";
_opcodes[OPCODE_SET_INVISIBLE] = "set_invisible";
_opcodes[OPCODE_CLEAR_CAN_TAKE] = "clear_can_take";
_opcodes[OPCODE_SET_CAN_TAKE] = "set_can_take";
_opcodes[OPCODE_SET_FLAG40] = "set_flag40";
_opcodes[OPCODE_CLEAR_FLAG40] = "clear_flag40";
_opcodes[OPCODE_RANDOM_MSG] = "random_msg";
_opcodes[OPCODE_SET_WORD] = "set_word";
_opcodes[OPCODE_CLEAR_WORD] = "clear_word";
}
Common::String DebuggerDumper::dumpInstruction(ComprehendGame *game,
const FunctionState *func_state, const Instruction *instr) {
uint i;
int str_index, str_table;
ScriptOpcode opcode = _game->getScriptOpcode(instr);
Common::String line;
if (func_state)
line = Common::String::format("[or=%d,and=%d,test=%d,else=%d]",
func_state->_orCount, func_state->_and,
func_state->_testResult, func_state->_elseResult);
line += Common::String::format(" [%.2x] ", instr->_opcode);
if (_opcodes.contains(opcode)) {
if (_game->_comprehendVersion == 2 && !instr->_isCommand && (instr->_opcode & 0x40) != 0)
line += "!";
line += _opcodes[opcode];
} else {
line += "unknown";
}
if (instr->_nr_operands) {
line += "(";
for (i = 0; i < instr->_nr_operands; i++)
line += Common::String::format("%.2x%s",
instr->_operand[i],
i == (instr->_nr_operands - 1) ? ")" : ", ");
}
switch (opcode) {
case OPCODE_PRINT:
case OPCODE_SET_ROOM_DESCRIPTION:
case OPCODE_SET_OBJECT_DESCRIPTION:
case OPCODE_SET_OBJECT_LONG_DESCRIPTION:
if (opcode == OPCODE_PRINT) {
str_index = instr->_operand[0];
str_table = instr->_operand[1];
} else {
str_index = instr->_operand[1];
str_table = instr->_operand[2];
}
line += Common::String::format(" %s", game->instrStringLookup(str_index, str_table).c_str());
break;
case OPCODE_SET_STRING_REPLACEMENT1:
case OPCODE_SET_STRING_REPLACEMENT2:
case OPCODE_SET_STRING_REPLACEMENT3:
str_index = instr->_operand[0] - 1;
if (str_index < 0 || str_index >= (int)game->_replaceWords.size())
warning("invalid string replacement index - %d", str_index);
else
line += Common::String::format(" %s", game->_replaceWords[str_index].c_str());
break;
default:
break;
}
return line;
}
void DebuggerDumper::dumpFunctions() {
uint i;
print("Functions (%u entries)\n", _game->_functions.size());
for (i = 0; i < _game->_functions.size(); i++)
dumpFunction(i);
}
void DebuggerDumper::dumpFunction(uint functionNum) {
const Function &func = _game->_functions[functionNum];
print("[%.4x] (%u instructions)\n", functionNum, func.size());
for (uint i = 0; i < func.size(); i++) {
Common::String line = dumpInstruction(_game, nullptr, &func[i]);
print("%s\n", line.c_str());
}
print("\n");
}
void DebuggerDumper::dumpActionTable() {
Action *action;
uint i, j;
print("Action tables: %u tables\n", _game->_actions.size());
for (uint tableNum = 0; tableNum < _game->_actions.size(); ++tableNum) {
ActionTable &table = _game->_actions[tableNum];
print("Action table #u (%u entries)\n", tableNum, table.size());
for (i = 0; i < table.size(); i++) {
action = &table[i];
print(" [%.4x] ", i);
for (j = 0; j < action->_nr_words; j++)
print("%.2x ", action->_words[j]);
print("-> %.4x\n", action->_function);
}
}
}
int DebuggerDumper::wordIndexCompare(const Word &a, const Word &b) {
if (a._index > b._index)
return 1;
if (a._index < b._index)
return -1;
return 0;
}
void DebuggerDumper::dumpDictionary() {
Common::Array<Word> dictionary;
/* Sort the dictionary by index */
dictionary = _game->_words;
Common::sort(dictionary.begin(), dictionary.end(), wordIndexCompare);
print("Dictionary (%u words)\n", dictionary.size());
for (uint i = 0; i < dictionary.size(); i++) {
const Word &word = dictionary[i];
print(" [%.2x] %.2x %s\n", word._index, word._type, word._word);
}
}
void DebuggerDumper::dumpWordMap() {
Word *word[3];
char str[3][7];
WordMap *map;
uint i, j;
print("Word pairs (%u entries)\n", _game->_wordMaps.size());
for (i = 0; i < _game->_wordMaps.size(); i++) {
map = &_game->_wordMaps[i];
for (j = 0; j < 3; j++) {
word[j] = dict_find_word_by_index_type(
_game, map->_word[j]._index, map->_word[j]._type);
if (word[j])
snprintf(str[j], sizeof(str[j]),
"%s", word[j]->_word);
else
snprintf(str[j], sizeof(str[j]), "%.2x:%.2x ",
map->_word[j]._index, map->_word[j]._type);
}
print(" [%.2x] %-6s %-6s -> %-6s\n",
i, str[0], str[1], str[2]);
}
}
void DebuggerDumper::dumpRooms() {
Room *room;
uint i;
// Room zero acts as the players inventory
print("Rooms (%u entries)\n", (uint)_game->_rooms.size() - 1);
for (i = 1; i < _game->_rooms.size(); i++) {
room = &_game->_rooms[i];
print(" [%.2x] flags=%.2x, graphic=%.2x\n",
i, room->_flags, room->_graphic);
print(" %s\n", _game->stringLookup(room->_stringDesc).c_str());
print(" n: %.2x s: %.2x e: %.2x w: %.2x\n",
room->_direction[DIRECTION_NORTH],
room->_direction[DIRECTION_SOUTH],
room->_direction[DIRECTION_EAST],
room->_direction[DIRECTION_WEST]);
print(" u: %.2x d: %.2x i: %.2x o: %.2x\n",
room->_direction[DIRECTION_UP],
room->_direction[DIRECTION_DOWN],
room->_direction[DIRECTION_IN],
room->_direction[DIRECTION_OUT]);
print("\n");
}
}
void DebuggerDumper::dumpItems() {
Item *item;
uint i, j;
print("Items (%u entries)\n", _game->_items.size());
for (i = 0; i < _game->_items.size(); i++) {
item = &_game->_items[i];
print(" [%.2x] %s\n", i + 1,
item->_stringDesc ? _game->stringLookup(item->_stringDesc).c_str() : "");
if (_game->_comprehendVersion == 2)
print(" long desc: %s\n",
_game->stringLookup(item->_longString).c_str());
print(" words: ");
for (j = 0; j < _game->_words.size(); j++)
if (_game->_words[j]._index == item->_word &&
(_game->_words[j]._type & WORD_TYPE_NOUN_MASK))
print("%s ", _game->_words[j]._word);
print("\n");
print(" flags=%.2x (takeable=%d, weight=%d)\n",
item->_flags, !!(item->_flags & ITEMF_CAN_TAKE),
(item->_flags & ITEMF_WEIGHT_MASK));
print(" room=%.2x, graphic=%.2x\n",
item->_room, item->_graphic);
print("\n");
}
}
void DebuggerDumper::dumpStringTable(Common::StringArray &table) {
uint i;
for (i = 0; i < table.size(); i++)
print("[%.4x] %s\n", i, table[i].c_str());
}
void DebuggerDumper::dumpGameDataStrings() {
print("Main string table (%u entries)\n",
_game->_strings.size());
dumpStringTable(_game->_strings);
}
void DebuggerDumper::dumpExtraStrings() {
print("Extra strings (%u entries)\n",
_game->_strings2.size());
dumpStringTable(_game->_strings2);
}
void DebuggerDumper::dumpReplaceWords() {
uint i;
print("Replacement words (%u entries)\n",
_game->_replaceWords.size());
for (i = 0; i < _game->_replaceWords.size(); i++)
print(" [%.2x] %s\n", i + 1, _game->_replaceWords[i].c_str());
}
void DebuggerDumper::dumpHeader() {
GameHeader *header = &_game->_header;
uint16 *dir_table = header->room_direction_table;
print("Game header:\n");
print(" magic: %.4x\n", header->magic);
print(" functions: %.4x\n", header->addr_vm);
print(" dictionary: %.4x\n", header->addr_dictionary);
print(" word map pairs: %.4x\n", header->addr_word_map);
print(" room desc strings: %.4x\n", header->room_desc_table);
print(" room north: %.4x\n", dir_table[DIRECTION_NORTH]);
print(" room south: %.4x\n", dir_table[DIRECTION_SOUTH]);
print(" room east: %.4x\n", dir_table[DIRECTION_EAST]);
print(" room west: %.4x\n", dir_table[DIRECTION_WEST]);
print(" room up: %.4x\n", dir_table[DIRECTION_UP]);
print(" room down: %.4x\n", dir_table[DIRECTION_DOWN]);
print(" room in: %.4x\n", dir_table[DIRECTION_IN]);
print(" room out: %.4x\n", dir_table[DIRECTION_OUT]);
print(" room flags: %.4x\n", header->room_flags_table);
print(" room images: %.4x\n", header->room_graphics_table);
print(" item locations: %.4x\n", header->addr_item_locations);
print(" item flags: %.4x\n", header->addr_item_flags);
print(" item words: %.4x\n", header->addr_item_word);
print(" item desc strings: %.4x\n", header->addr_item_strings);
print(" item images: %.4x\n", header->addr_item_graphics);
print(" string table: %.4x\n", header->addr_strings);
print(" string table end: %.4x\n", header->addr_strings_end);
}
void DebuggerDumper::dumpState() {
print("Current room: %.2x\n", _game->_currentRoom);
print("Carry weight %d/%d\n\n",
_game->_variables[VAR_INVENTORY_WEIGHT],
_game->_variables[VAR_INVENTORY_LIMIT]);
print("Flags:\n");
for (uint i = 0; i < ARRAY_SIZE(_game->_flags); i++)
print(" [%.2x]: %d\n", i, _game->_flags[i]);
print("\n");
print("Variables:\n");
for (uint i = 0; i < ARRAY_SIZE(_game->_variables); i++)
print(" [%.2x]: %5d (0x%.4x)\n",
i, _game->_variables[i],
_game->_variables[i]);
print("\n");
}
bool DebuggerDumper::dumpGameData(ComprehendGame *game, const Common::String &type, int param) {
_game = game;
if (type == "header")
dumpHeader();
else if (type == "strings")
dumpGameDataStrings();
else if (type == "extra_strings")
dumpExtraStrings();
else if (type == "rooms")
dumpRooms();
else if (type == "items")
dumpItems();
else if (type == "dictionary")
dumpDictionary();
else if (type == "word_map")
dumpWordMap();
else if (type == "actions")
dumpActionTable();
else if (type == "functions")
dumpFunctions();
else if (type == "function")
dumpFunction(param);
else if (type == "replace_words")
dumpReplaceWords();
else if (type == "state")
dumpState();
else
return false;
return true;
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,77 @@
/* 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_COMPREHEND_DEBUGGER_DUMPER_H
#define GLK_COMPREHEND_DEBUGGER_DUMPER_H
#include "common/hashmap.h"
#include "common/str-array.h"
namespace Glk {
namespace Comprehend {
class ComprehendGame;
struct FunctionState;
struct Instruction;
struct Word;
class DebuggerDumper {
private:
Common::HashMap<byte, Common::String> _opcodes;
ComprehendGame *_game;
private:
void dumpFunctions();
void dumpFunction(uint functionNum);
void dumpActionTable();
static int wordIndexCompare(const Word &a, const Word &b);
void dumpDictionary();
void dumpWordMap();
void dumpRooms();
void dumpItems();
void dumpStringTable(Common::StringArray &table);
void dumpGameDataStrings();
void dumpExtraStrings();
void dumpReplaceWords();
void dumpHeader();
void dumpState();
protected:
/**
* Prints out dumped text
*/
virtual void print(const char *fmt, ...) = 0;
public:
DebuggerDumper();
virtual ~DebuggerDumper() {}
Common::String dumpInstruction(ComprehendGame *game,
const FunctionState *func_state, const Instruction *instr);
bool dumpGameData(ComprehendGame *game, const Common::String &type,
int param = 0);
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,126 @@
/* 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/comprehend/detection.h"
#include "glk/comprehend/detection_tables.h"
#include "glk/blorb.h"
#include "common/file.h"
#include "common/md5.h"
#include "engines/game.h"
namespace Glk {
namespace Comprehend {
void ComprehendMetaEngine::getSupportedGames(PlainGameList &games) {
for (const PlainGameDescriptor *pd = COMPREHEND_GAME_LIST; pd->gameId; ++pd)
games.push_back(*pd);
}
const GlkDetectionEntry* ComprehendMetaEngine::getDetectionEntries() {
static Common::Array<GlkDetectionEntry> entries;
for (const ComprehendDetectionEntry *entry = COMPREHEND_GAMES; entry->_gameId; ++entry) {
GlkDetectionEntry detection = {
entry->_gameId,
entry->_filename,
entry->_md5,
0,
Common::EN_ANY,
Common::kPlatformUnknown
};
entries.push_back(detection);
}
entries.push_back({nullptr,
nullptr,
nullptr,
0,
Common::UNK_LANG,
Common::kPlatformUnknown});
return entries.data();
}
GameDescriptor ComprehendMetaEngine::findGame(const char *gameId) {
for (const PlainGameDescriptor *pd = COMPREHEND_GAME_LIST; pd->gameId; ++pd) {
if (!strcmp(gameId, pd->gameId)) {
GameDescriptor gd = *pd;
Common::String s(pd->gameId);
gd._supportLevel = (s == "transylvaniav2" || s == "talisman") ?
kUnstableGame : kTestingGame;
return gd;
}
}
return GameDescriptor::empty();
}
bool ComprehendMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
// 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;
// Check if file occurs in the list
Common::String filename = file->getName();
bool isPossible = false;
const ComprehendDetectionEntry *p = COMPREHEND_GAMES;
for (; p->_gameId && !isPossible; ++p)
isPossible = filename.equalsIgnoreCase(p->_filename);
if (!isPossible)
continue;
// Get the file's MD5
Common::File gameFile;
if (!gameFile.open(*file))
continue;
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
gameFile.close();
// Iterate through the known games
p = COMPREHEND_GAMES;
for (; p->_gameId; ++p) {
if (filename.equalsIgnoreCase(p->_filename)) {
// Check for an md5 match
if (p->_md5 == md5) {
// Found a match
PlainGameDescriptor gameDesc = findGame(p->_gameId);
GlkDetectedGame gd(p->_gameId, gameDesc.description, filename);
gameList.push_back(gd);
}
}
}
}
return !gameList.empty();
}
void ComprehendMetaEngine::detectClashes(Common::StringMap &map) {
for (const PlainGameDescriptor *pd = COMPREHEND_GAME_LIST; pd->gameId; ++pd) {
if (map.contains(pd->gameId))
error("Duplicate game Id found - %s", pd->gameId);
map[pd->gameId] = "";
}
}
} // End of namespace Comprehend
} // End of namespace Glk

View 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_COMPREHEND_DETECTION
#define GLK_COMPREHEND_DETECTION
#include "common/fs.h"
#include "common/hash-str.h"
#include "engines/game.h"
#include "glk/detection.h"
namespace Glk {
namespace Comprehend {
class ComprehendMetaEngine {
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 Comprehend
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,57 @@
/* 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 "common/language.h"
#include "engines/game.h"
namespace Glk {
namespace Comprehend {
const PlainGameDescriptor COMPREHEND_GAME_LIST[] = {
{"transylvania", "Transylvania"},
{"crimsoncrown", "Crimson Crown"},
{"ootopos", "OO-Topos"},
{"transylvaniav2", "Transylvania (V2)"},
{"talisman", "Talisman: Challenging the Sands of Time"},
{nullptr, nullptr}
};
struct ComprehendDetectionEntry {
const char *const _gameId;
const char *const _filename;
const char *const _md5;
};
const ComprehendDetectionEntry COMPREHEND_GAMES[] = {
{ "transylvania", "tr.gda", "22e08633eea02ceee49b909dfd982d22" },
{ "crimsoncrown", "cc1.gda", "f2abf019675ac5c9bcfd81032bc7787b" },
{ "ootopos", "g0", "56460c1ee669c253607534155d7e9db4" },
{ "transylvaniav2", "g0", "384cbf0cd50888310fd33574e6baf880" },
{ "talisman", "g0", "35770d4815e610b5252e3fcd9f11def3" },
{nullptr, nullptr, nullptr}
};
} // End of namespace Comprehend
} // End of namespace Glk

View File

@@ -0,0 +1,92 @@
/* 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/comprehend/comprehend.h"
#include "glk/comprehend/game.h"
#include "glk/comprehend/game_data.h"
#include "glk/comprehend/dictionary.h"
namespace Glk {
namespace Comprehend {
static bool word_match(Word *word, const char *string) {
/* Words less than 6 characters must match exactly */
if (strlen(word->_word) < 6 && strlen(string) != strlen(word->_word))
return false;
return strncmp(word->_word, string, strlen(word->_word)) == 0;
}
Word *dict_find_word_by_string(ComprehendGame *game,
const char *string) {
uint i;
if (!string)
return nullptr;
for (i = 0; i < game->_words.size(); i++)
if (word_match(&game->_words[i], string))
return &game->_words[i];
return nullptr;
}
Word *dict_find_word_by_index_type(ComprehendGame *game,
uint8 index, uint8 type) {
uint i;
for (i = 0; i < game->_words.size(); i++) {
if (game->_words[i]._index == index &&
game->_words[i]._type == type)
return &game->_words[i];
}
return nullptr;
}
Word *find_dict_word_by_index(ComprehendGame *game,
uint8 index, uint8 type_mask) {
uint i;
for (i = 0; i < game->_words.size(); i++) {
if (game->_words[i]._index == index &&
(game->_words[i]._type & type_mask) != 0)
return &game->_words[i];
}
return nullptr;
}
bool dict_match_index_type(ComprehendGame *game, const char *word,
uint8 index, uint8 type_mask) {
uint i;
for (i = 0; i < game->_words.size(); i++)
if (game->_words[i]._index == index &&
((game->_words[i]._type & type_mask) != 0) &&
word_match(&game->_words[i], word))
return true;
return false;
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,43 @@
/* 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_COMPREHEND_DICTIONARY_H
#define GLK_COMPREHEND_DICTIONARY_H
namespace Glk {
namespace Comprehend {
class ComprehendGame;
struct Word;
Word *find_dict_word_by_index(ComprehendGame *game,
uint8 index, uint8 type_mask);
Word *dict_find_word_by_index_type(ComprehendGame *game,
uint8 index, uint8 type);
Word *dict_find_word_by_string(ComprehendGame *game,
const char *string);
bool dict_match_index_type(ComprehendGame *game, const char *word,
uint8 index, uint8 type_mask);
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,446 @@
/* 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/comprehend/draw_surface.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/pics.h"
#include "glk/window_graphics.h"
namespace Glk {
namespace Comprehend {
const uint32 Surface::PEN_COLORS[8] = {
G_COLOR_BLACK,
RGB(0x00, 0x66, 0x00),
RGB(0x00, 0xff, 0x00),
G_COLOR_WHITE,
G_COLOR_BLACK,
RGB(0x00, 0xff, 0xff),
RGB(0xff, 0x00, 0xff),
RGB(0xff, 0x00, 0x00),
};
/* Used by Transylvania and Crimson Crown */
const uint32 Surface::DEFAULT_COLOR_TABLE[256] = {
G_COLOR_WHITE, // 00
G_COLOR_DARK_BLUE, // 01
G_COLOR_GRAY1, // 02
G_COLOR_DARK_RED, // 03
G_COLOR_GRAY2, // 04
0, G_COLOR_GRAY3, 0, 0, 0, 0, 0, 0,
G_COLOR_BROWN1, G_COLOR_DARK_PURPLE, 0,
0, 0, G_COLOR_DARK_RED, G_COLOR_BROWN2, 0, 0, 0,
G_COLOR_DARK_BLUE, G_COLOR_BLACK, 0, 0, 0, 0, 0, 0, G_COLOR_DARK_PURPLE,
G_COLOR_DARK_PURPLE, 0, G_COLOR_DARK_RED, 0, 0, 0, 0, 0,
0, 0, 0, G_COLOR_DARK_PURPLE, 0, 0, 0, 0,
0, 0, 0, 0, G_COLOR_WHITE, G_COLOR_GRAY0, RGB(0xb5, 0x6c, 0x47),
0, 0, 0, 0, 0, G_COLOR_CYAN, G_COLOR_DARK_RED,
G_COLOR_DARK_GREEN1, G_COLOR_DARK_GREEN2,
G_COLOR_DARK_PURPLE, 0, G_COLOR_DITHERED_PINK, 0, 0,
G_COLOR_BROWN2, G_COLOR_DARK_RED, G_COLOR_DARK_BLUE,
G_COLOR_DARK_BLUE, G_COLOR_DARK_BLUE, 0, 0, 0,
G_COLOR_WHITE, G_COLOR_BROWN2, G_COLOR_BROWN2,
G_COLOR_BLACK, G_COLOR_DARK_PURPLE, 0, G_COLOR_GRAY2,
G_COLOR_BROWN2, 0, 0, G_COLOR_AQUA, 0, 0, G_COLOR_GREEN,
G_COLOR_DARK_BLUE, G_COLOR_DARK_PURPLE, G_COLOR_BROWN1,
G_COLOR_BROWN2, 0,
G_COLOR_DARK_PURPLE, G_COLOR_LIGHT_ORANGE, 0, 0,
G_COLOR_ORANGE, G_COLOR_RED, G_COLOR_DARK_RED, 0, 0, 0,
G_COLOR_DARK_BLUE, G_COLOR_DARK_PURPLE, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
G_COLOR_BLACK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/* Used by OO-topos */
/* FIXME - incomplete */
const uint32 Surface::COLOR_TABLE_1[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
0,
0,
0,
0,
RGB(0x80, 0x00, 0x00),
0,
RGB(0xe6, 0xe6, 0x00),
0,
0,
0,
0,
RGB(0xc0, 0x00, 0x00),
RGB(0x80, 0x00, 0x00),
G_COLOR_ORANGE,
0,
0,
G_COLOR_BROWN1,
RGB(0x00, 0x00, 0x66),
RGB(0x33, 0x99, 0xff),
0,
RGB(0xe8, 0xe8, 0xe8),
RGB(0x99, 0xcc, 0xff),
0,
RGB(0x99, 0x33, 0x33),
RGB(0xcc, 0x66, 0x00),
0,
0,
0,
0,
0,
0,
G_COLOR_GRAY3,
0,
0,
0,
0,
0,
0,
RGB(0x99, 0x33, 0x00),
G_COLOR_CYAN,
0,
0,
RGB(0x66, 0x00, 0x33),
0,
0,
0,
0,
G_COLOR_AQUA,
G_COLOR_GRAY2,
0,
0,
0,
G_COLOR_DARK_BLUE,
0,
0,
0,
0,
G_COLOR_GRAY1,
0,
0,
0,
0,
0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
const uint32 *Surface::COLOR_TABLES[2] = {
DEFAULT_COLOR_TABLE,
COLOR_TABLE_1,
};
static const byte SHAPE_DATA[32][8] = {
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0x80 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0x80 },
{ 0x80, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 1, 3 },
{ 3, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0x80, 0xC0 },
{ 0xC0, 0x80, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 3, 7, 7 },
{ 7, 7, 3, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0xC0, 0xE0, 0xE0 },
{ 0xE0, 0xE0, 0xC0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 3, 0x0F, 0x0F, 0x1F, 0x1F },
{ 0x1F, 0x1F, 0x0F, 0x0F, 3, 0, 0, 0 },
{ 0, 0, 0, 0xC0, 0xF0, 0xF0, 0xF8, 0xF8 },
{ 0xF8, 0xF8, 0xF0, 0xF0, 0xC0, 0, 0, 0 },
{ 0, 3, 0x1F, 0x3F, 0x3F, 0x3F, 0x7F, 0x7F },
{ 0x7F, 0x7F, 0x3F, 0x3F, 0x3F, 0x1F, 3, 0 },
{ 0, 0xC0, 0xF8, 0xFC, 0xFC, 0xFC, 0xFE, 0xFE },
{ 0xFE, 0xFE, 0xFC, 0xFC, 0xFC, 0xF8, 0xC0, 0 },
{ 0, 0, 0, 0, 1, 8, 2, 0 },
{ 0x0A, 0, 4, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0x20, 0, 0x90 },
{ 0, 0xA0, 0, 0x80, 0, 0, 0, 0 },
{ 0, 2, 8, 0x12, 1, 0x24, 0x0B, 3 },
{ 0x23, 9, 0x22, 0x0A, 4, 1, 0, 0 },
{ 0, 0x20, 0x80, 0x28, 0, 0xD4, 0xC0, 0xE4 },
{ 0xE8, 0x90, 0x44, 0xA8, 0, 0x50, 0, 0 }
};
/*-------------------------------------------------------*/
void Surface::reset() {
create(G_RENDER_WIDTH, G_RENDER_HEIGHT,
Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
}
void Surface::setColorTable(uint index) {
if (index >= ARRAY_SIZE(COLOR_TABLES)) {
warning("Bad color table %d - using default", index);
_colorTable = DEFAULT_COLOR_TABLE;
}
_colorTable = COLOR_TABLES[index];
}
uint Surface::getPenColor(uint8 param) const {
return PEN_COLORS[param];
}
uint32 Surface::getFillColor(uint8 index) {
unsigned color;
color = _colorTable[index];
if (!color) {
/* Unknown color - use ugly purple */
debugC(kDebugGraphics, "Unknown color %.2x", index);
return RGB(0xff, 0x00, 0xff);
}
return color;
}
void Surface::drawLine(int16 x1, int16 y1, int16 x2, int16 y2, uint32 color) {
#if 1
Graphics::ManagedSurface::drawLine(x1, y1, x2, y2, color);
#else
bool swapped = false;
int deltaX = -1, deltaY = -1;
int xDiff = x1 - x2, yDiff = y1 - y2;
// Draw pixel at starting point
drawPixel(x1, y1, color);
// Figure out the deltas movement for creating the line
if (xDiff < 0) {
deltaX = 1;
xDiff = -xDiff;
}
if (yDiff < 0) {
deltaY = 1;
yDiff = -yDiff;
}
if (xDiff < yDiff) {
swapped = true;
SWAP(xDiff, yDiff);
SWAP(deltaX, deltaY);
SWAP(x1, y1);
}
int temp1 = yDiff;
int temp2 = yDiff - xDiff;
int temp3 = temp2;
// Iterate to draw the remaining pixels of the line
for (int ctr = xDiff; ctr > 0; --ctr) {
x1 += deltaX;
if (temp3 >= 0) {
y1 += deltaY;
temp3 += temp2;
} else {
temp3 += temp1;
}
int xp = x1, yp = y1;
if (swapped)
SWAP(xp, yp);
drawPixel(xp, yp, color);
}
#endif
}
void Surface::drawBox(int16 x1, int16 y1, int16 x2, int16 y2, uint32 color) {
if (x1 > x2)
SWAP(x1, x2);
if (y1 > y2)
SWAP(y1, y2);
Common::Rect r(x1, y1, x2 + 1, y2 + 1);
frameRect(r, color);
}
void Surface::drawFilledBox(int16 x1, int16 y1, int16 x2, int16 y2, uint32 color) {
if (x1 > x2)
SWAP(x1, x2);
if (y1 > y2)
SWAP(y1, y2);
Common::Rect r(x1, y1, x2 + 1, y2 + 1);
fillRect(r, color);
}
void Surface::drawShape(int16 x, int16 y, Shape shapeType, uint32 fillColor) {
uint shapeNum = (uint)shapeType * 4;
// Outer loop to draw the shape across a 2x2 grid of 8x8 sub-shapes
for (int shapeX = 0; shapeX <= 8; shapeX += 8) {
for (int shapeY = 0; shapeY <= 8; shapeY += 8, ++shapeNum) {
// Inner loop for character
for (int charY = 0; charY < 8; ++charY) {
int yp = y + shapeY + charY;
if (yp < 0 || yp >= this->h)
continue;
int xp = x + shapeX;
uint32 *lineP = (uint32 *)getBasePtr(xp, yp);
byte bits = SHAPE_DATA[shapeNum][charY];
for (int charX = 0; charX < 8; ++lineP, ++charX, ++xp, bits <<= 1) {
if (xp >= 0 && xp < this->w && (bits & 0x80) != 0)
*lineP = fillColor;
}
}
}
}
}
void Surface::drawPixel(int16 x, int16 y, uint32 color) {
if (x >= 0 && y >= 0 && x < this->w && y < this->h) {
uint32 *ptr = (uint32 *)getBasePtr(x, y);
*ptr = color;
}
}
uint32 Surface::getPixelColor(int16 x, int16 y) const {
assert(x >= 0 && y >= 0 && x < this->w && y < this->h);
const uint32 *ptr = (const uint32 *)getBasePtr(x, y);
return *ptr;
}
void Surface::clearScreen(uint32 color) {
fillRect(Common::Rect(0, 0, this->w, this->h), color);
}
void Surface::drawCircle(int16 x, int16 y, int16 diameter, uint32 color) {
int invert = -diameter;
int delta = 0;
do {
drawPixel(x - delta, y - diameter, color);
drawPixel(x + delta, y - diameter, color);
drawPixel(x + delta, y + diameter, color);
drawPixel(x - delta, y + diameter, color);
drawPixel(x + diameter, y - delta, color);
drawPixel(x - diameter, y - delta, color);
drawPixel(x - diameter, y + delta, color);
drawPixel(x + diameter, y + delta, color);
invert += (delta * 2) + 1;
++delta;
if (!((uint)invert & 0x80)) {
invert += 2;
diameter <<= 1;
invert -= diameter;
diameter >>= 1;
--diameter;
}
} while (diameter >= delta);
}
/*--------------------------------------------------------------------------*/
bool FloodFillSurface::isPixelWhite(int16 x, int16 y) const {
if (x < 0 || y < 0 || x >= this->w || y >= this->h) {
return false;
} else {
byte r, g, b;
format.colorToRGB(getPixelColor(x, y), r, g, b);
return r == 255 && g == 255 && b == 255;
}
}
void FloodFillSurface::dumpToScreen() {
Graphics::ManagedSurface s(w * 2, h * 2, g_system->getScreenFormat());
s.transBlitFrom(*this, Common::Rect(0, 0, w, h), Common::Rect(0, 0, w * 2, h * 2), 0x888888);
g_system->copyRectToScreen(s.getPixels(), s.pitch, 0, 0, w * 2, h * 2);
g_system->updateScreen();
}
void FloodFillSurface::floodFill(int16 x, int16 y, uint32 fillColor) {
if (y == this->h)
y = this->h - 1;
else if (y > this->h)
return;
if (!isPixelWhite(x, y))
return;
floodFillRow(x, y, fillColor);
}
void FloodFillSurface::floodFillRow(int16 x, int16 y, uint32 fillColor) {
int x1, x2, i;
// Left end of scanline
for (x1 = x; x1 > 0; x1--)
if (!isPixelWhite(x1 - 1, y))
break;
// Right end of scanline
for (x2 = x; x2 < this->w; x2++)
if (!isPixelWhite(x2 + 1, y))
break;
drawLine(x1, y, x2, y, fillColor);
//dumpToScreen();
// Scanline above
if (y > 0) {
for (i = x1; i <= x2; i++)
if (isPixelWhite(i, y - 1))
floodFillRow(i, y - 1, fillColor);
}
// Scanline below
if (y < (this->h - 1)) {
for (i = x1; i <= x2; i++)
if (isPixelWhite(i, y + 1))
floodFillRow(i, y + 1, fillColor);
}
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,128 @@
/* 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_COMPREHEND_GRAPHICS_H
#define GLK_COMPREHEND_GRAPHICS_H
#include "common/scummsys.h"
#include "graphics/managed_surface.h"
namespace Glk {
namespace Comprehend {
#define G_RENDER_WIDTH 280
#define G_RENDER_HEIGHT 160
#define RGB(r, g, b) (uint32)(((r) << 24) | ((g) << 16) | ((b) << 8) | 0xff)
#define G_COLOR_BLACK 0x000000ff
#define G_COLOR_WHITE 0xffffffff
#define G_COLOR_CYAN 0x3366ffff
#define G_COLOR_YELLOW 0xffff00ff
#define G_COLOR_RED 0xff0000ff
#define G_COLOR_GRAY0 0x202020ff
#define G_COLOR_GRAY1 0x404040ff
#define G_COLOR_GRAY2 0x808080ff
#define G_COLOR_GRAY3 0xc0c0c0ff
#define G_COLOR_LIGHT_ORANGE 0xff9966ff
#define G_COLOR_ORANGE 0xff9900ff
#define G_COLOR_DARK_PURPLE 0x666699ff
#define G_COLOR_DARK_BLUE 0x000099ff
#define G_COLOR_DARK_RED 0xcc0033ff
#define G_COLOR_DITHERED_PINK 0xff6699ff
#define G_COLOR_DARK_GREEN1 0x009966ff
#define G_COLOR_DARK_GREEN2 0x003300ff
#define G_COLOR_AQUA 0x33ccccff
#define G_COLOR_GREEN 0x33cc00ff
#define G_COLOR_BROWN1 0x7a5200ff
#define G_COLOR_BROWN2 0x663300ff
enum Shape {
SHAPE_PIXEL = 0,
SHAPE_BOX = 1,
SHAPE_CIRCLE_TINY = 2,
SHAPE_CIRCLE_SMALL = 3,
SHAPE_CIRCLE_MED = 4,
SHAPE_CIRCLE_LARGE = 5,
SHAPE_A = 6,
SHAPE_SPRAY = 7
};
class Surface : public Graphics::ManagedSurface {
private:
static const uint32 PEN_COLORS[8];
static const uint32 DEFAULT_COLOR_TABLE[256];
static const uint32 COLOR_TABLE_1[256];
static const uint32 *COLOR_TABLES[2];
public:
const uint32 *_colorTable;
public:
Surface() : _colorTable(DEFAULT_COLOR_TABLE) {
reset();
}
/**
* Sets up the surface to the correct size and pixel format
*/
void reset();
void setColorTable(uint index);
uint getPenColor(uint8 param) const;
uint32 getFillColor(uint8 index);
void drawLine(int16 x1, int16 y1, int16 x2, int16 y2, uint32 color);
void drawBox(int16 x1, int16 y1, int16 x2, int16 y2, uint32 color);
void drawFilledBox(int16 x1, int16 y1, int16 x2, int16 y2, uint32 color);
void drawShape(int16 x, int16 y, Shape shapeType, uint32 fill_color);
void drawPixel(int16 x, int16 y, uint32 color);
uint32 getPixelColor(int16 x, int16 y) const;
void clearScreen(uint32 color);
void drawCircle(int16 x, int16 y, int16 diameter, uint32 color);
void drawCirclePoint(int16 x, int16 y);
};
class FloodFillSurface : public Surface {
private:
bool isPixelWhite(int16 x, int16 y) const;
void floodFillRow(int16 x, int16 y, uint32 fillColor);
public:
void floodFill(int16 x, int16 y, uint32 fillColor);
void dumpToScreen();
};
class DrawSurface : public FloodFillSurface {
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,119 @@
/* 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/comprehend/file_buf.h"
#include "common/algorithm.h"
#include "common/file.h"
namespace Glk {
namespace Comprehend {
FileBuffer::FileBuffer(const Common::String &filename) : _pos(0) {
// Open the file
Common::File f;
if (!f.open(Common::Path(filename)))
error("Could not open - %s", filename.c_str());
_data.resize(f.size());
_readBytes.resize(f.size());
f.read(&_data[0], f.size());
}
FileBuffer::FileBuffer(Common::ReadStream *stream, size_t size) : _pos(0) {
_data.resize(size);
_readBytes.resize(size);
stream->read(&_data[0], size);
}
bool FileBuffer::exists(const Common::String &filename) {
return Common::File::exists(Common::Path(filename));
}
void FileBuffer::close() {
_data.clear();
_readBytes.clear();
_pos = 0;
}
bool FileBuffer::seek(int64 offset, int whence) {
switch (whence) {
case SEEK_SET:
_pos = offset;
break;
case SEEK_CUR:
_pos += offset;
break;
case SEEK_END:
_pos = (int)_data.size() + offset;
break;
default:
break;
}
return true;
}
uint32 FileBuffer::read(void *dataPtr, uint32 dataSize) {
int32 bytesRead = CLIP((int32)dataSize, (int32)0, (int32)_data.size() - _pos);
if (bytesRead) {
Common::fill(&_readBytes[_pos], &_readBytes[_pos] + bytesRead, true);
Common::copy(&_data[_pos], &_data[_pos] + bytesRead, (byte *)dataPtr);
_pos += bytesRead;
}
return bytesRead;
}
size_t FileBuffer::strlen(bool *eof) {
uint8 *end;
if (eof)
*eof = false;
end = (uint8 *)memchr(&_data[_pos], '\0', size() - _pos);
if (!end) {
// No null terminator - string is remaining length
if (eof)
*eof = true;
return size() - _pos;
}
return end - &_data[_pos];
}
void FileBuffer::showUnmarked() {
int i, start = -1;
for (i = 0; i < (int)_data.size(); i++) {
if (!_readBytes[i] && start == -1)
start = i;
if ((_readBytes[i] || i == (int)_data.size() - 1) && start != -1) {
warning("%.4x - %.4x unmarked (%d bytes)\n",
start, i - 1, i - start);
start = -1;
}
}
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,86 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_COMPREHEND_FILE_BUF_H
#define GLK_COMPREHEND_FILE_BUF_H
#include "common/array.h"
#include "common/memstream.h"
#include "common/stream.h"
namespace Glk {
namespace Comprehend {
struct FileBuffer : public Common::SeekableReadStream {
private:
Common::Array<byte> _data;
Common::Array<bool> _readBytes;
int32 _pos;
public:
FileBuffer() : _pos(0) {}
FileBuffer(const Common::String &filename);
FileBuffer(Common::ReadStream *stream, size_t size);
static bool exists(const Common::String &filename);
void close();
int64 pos() const override {
return _pos;
}
int64 size() const override {
return _data.size();
}
bool seek(int64 offset, int whence = SEEK_SET) override;
bool eos() const override {
return _pos >= (int)_data.size();
}
uint32 read(void *dataPtr, uint32 dataSize) override;
const byte *dataPtr() const {
return &_data[_pos];
}
size_t strlen(bool *eof = nullptr);
/*
* Debugging function to show regions of a file that have not been read.
*/
void showUnmarked();
};
#define file_buf_get_array(fb, type, base, array, member, size) \
do { \
uint __i; \
for (__i = (base); __i < (base) + (size); __i++) \
(array)[__i].member = fb->read##type(); \
} while (0)
#define file_buf_get_array_u8(fb, base, array, member, size) \
file_buf_get_array(fb, Byte, base, array, member, size)
#define file_buf_get_array_le16(fb, base, array, member, size) \
file_buf_get_array(fb, Uint16LE, base, array, member, size)
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,938 @@
/* 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/comprehend/game.h"
#include "common/debug-channels.h"
#include "common/translation.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/debugger.h"
#include "glk/comprehend/dictionary.h"
#include "glk/comprehend/draw_surface.h"
#include "glk/comprehend/game_data.h"
namespace Glk {
namespace Comprehend {
void Sentence::clear() {
for (uint idx = 0; idx < 4; ++idx)
_words[idx].clear();
for (uint idx = 0; idx < 6; ++idx)
_formattedWords[idx] = 0;
_nr_words = 0;
_specialOpcodeVal2 = 0;
}
void Sentence::copyFrom(const Sentence &src, bool copyNoun) {
for (uint idx = (copyNoun ? 0 : 1); idx < 6; ++idx)
_formattedWords[idx] = src._formattedWords[idx];
}
void Sentence::format() {
for (uint idx = 0; idx < 6; ++idx)
_formattedWords[idx] = 0;
byte wordTypes[5] = { 0, 0, 0, 0, 0 };
for (uint idx = 0; idx < _nr_words; ++idx) {
const Word &w = _words[idx];
if (w._type & 8) {
if (w._type < 24) {
int index, type;
if (w._type & 0xf0 & wordTypes[2]) {
index = _formattedWords[2];
type = wordTypes[2];
} else if (w._type & 0xf0 & wordTypes[3]) {
index = _formattedWords[3];
type = wordTypes[3];
} else {
continue;
}
if (!_formattedWords[2]) {
_formattedWords[2] = index;
wordTypes[2] = type;
} else if (!_formattedWords[3]) {
_formattedWords[3] = index;
wordTypes[3] = type;
}
} else {
if (w._type == 8)
_specialOpcodeVal2 = 1;
else if (w._type == 9)
_specialOpcodeVal2 = 2;
}
} else {
int val = w._type & 0xf0;
if (val) {
if ((w._type & 1) && !_formattedWords[0]) {
_formattedWords[0] = w._index;
} else if (!_formattedWords[2]) {
_formattedWords[2] = w._index;
wordTypes[2] = val;
} else if (!_formattedWords[3]) {
_formattedWords[3] = w._index;
wordTypes[3] = val;
}
} else if (w._type & 1) {
if (!_formattedWords[0]) {
_formattedWords[0] = w._index;
} else if (!_formattedWords[1]) {
_formattedWords[1] = w._index;
}
} else if (w._type == 2) {
if (!_formattedWords[4])
_formattedWords[4] = w._index;
} else if (w._type == 4) {
if (!_formattedWords[5])
_formattedWords[5] = w._index;
}
}
}
}
/*-------------------------------------------------------*/
ComprehendGame::ComprehendGame() : _gameStrings(nullptr), _ended(false),
_functionNum(0), _specialOpcode(0), _nounState(NOUNSTATE_INITIAL),
_inputLineIndex(0), _currentRoomCopy(-1), _redoLine(REDO_NONE) {
Common::fill(&_inputLine[0], &_inputLine[INPUT_LINE_SIZE], 0);
}
ComprehendGame::~ComprehendGame() {
}
void ComprehendGame::synchronizeSave(Common::Serializer &s) {
uint dir, i;
size_t nr_rooms, nr_items;
s.syncAsUint16LE(_currentRoom);
// Variables
for (i = 0; i < ARRAY_SIZE(_variables); i++)
s.syncAsUint16LE(_variables[i]);
// Flags
for (i = 0; i < ARRAY_SIZE(_flags); i++)
s.syncAsByte(_flags[i]);
// Rooms. Note that index 0 is the player's inventory
nr_rooms = _rooms.size();
s.syncAsByte(nr_rooms);
assert(nr_rooms == _rooms.size());
for (i = 1; i < _rooms.size(); ++i) {
s.syncAsUint16LE(_rooms[i]._stringDesc);
for (dir = 0; dir < NR_DIRECTIONS; dir++)
s.syncAsByte(_rooms[i]._direction[dir]);
s.syncAsByte(_rooms[i]._flags);
s.syncAsByte(_rooms[i]._graphic);
}
// Objects
nr_items = _items.size();
s.syncAsByte(nr_items);
assert(nr_items == _items.size());
for (i = 0; i < _items.size(); ++i)
_items[i].synchronize(s);
_redoLine = REDO_NONE;
}
Common::String ComprehendGame::stringLookup(uint16 index) {
uint16 string;
uint8 table;
/*
* There are two tables of strings. The first is stored in the main
* game data file, and the second is stored in multiple string files.
*
* In instructions string indexes are split into a table and index
* value. In other places such as the save files strings from the
* main table are occasionally just a straight 16-bit index. We
* convert all string indexes to the former case so that we can handle
* them the same everywhere.
*/
table = (index >> 8) & 0xff;
string = index & 0xff;
switch (table) {
case 0x81:
case 0x01:
string += 0x100;
/* Fall-through */
case 0x00:
case 0x80:
if (string < _strings.size())
return _strings[string];
break;
case 0x83:
string += 0x100;
/* Fall-through */
case 0x02:
case 0x82:
if (string < _strings2.size())
return _strings2[string];
break;
}
return Common::String::format("BAD_STRING(%.4x)", index);
}
Common::String ComprehendGame::instrStringLookup(uint8 index, uint8 table) {
return stringLookup(table << 8 | index);
}
int ComprehendGame::console_get_key() {
return g_comprehend->readChar();
}
void ComprehendGame::console_println(const char *text) {
const char *replace, *word = nullptr, *p = text;
char bad_word[64];
int word_len = 0;
if (!text) {
g_comprehend->print("\n");
return;
}
while (*p) {
switch (*p) {
case '\n':
word = nullptr;
word_len = 0;
g_comprehend->print("\n");
p++;
break;
case '@':
/* Replace word */
if (_currentReplaceWord >= _replaceWords.size()) {
snprintf(bad_word, sizeof(bad_word),
"[BAD_REPLACE_WORD(%.2x)]",
_currentReplaceWord);
word = bad_word;
} else {
word = _replaceWords[_currentReplaceWord].c_str();
}
word_len = strlen(word);
p++;
break;
default:
/* Find next space */
word_len = strcspn(p, " \n");
if (word_len == 0)
break;
/*
* If this word contains a replacement symbol, then
* print everything before the symbol.
*/
replace = strchr(p, '@');
if (replace)
word_len = replace - p;
word = p;
p += word_len;
break;
}
if (!word || !word_len)
continue;
Common::String wordStr(word, word_len);
g_comprehend->print("%s", wordStr.c_str());
if (*p == ' ') {
g_comprehend->print(" ");
p++;
/* Skip any double spaces */
while (*p == ' ')
p++;
}
}
g_comprehend->print("\n");
}
Room *ComprehendGame::get_room(uint16 index) {
/* Room zero is reserved for the players inventory */
if (index == 0)
error("Room index 0 (player inventory) is invalid");
if (index >= (int)_rooms.size())
error("Room index %d is invalid", index);
return &_rooms[index];
}
Item *ComprehendGame::get_item(uint16 index) {
if (index >= _items.size())
error("Bad item %d\n", index);
return &_items[index];
}
void ComprehendGame::game_save() {
int c;
console_println(_strings[STRING_SAVE_GAME].c_str());
c = console_get_key();
if (g_comprehend->shouldQuit())
return;
if (c < '1' || c > '3') {
/*
* The original Comprehend games just silently ignore any
* invalid selection.
*/
console_println("Invalid save game number");
return;
}
g_comprehend->saveGameState(c - '0', _("Savegame"));
}
void ComprehendGame::game_restore() {
int c;
console_println(_strings[STRING_RESTORE_GAME].c_str());
c = console_get_key();
if (g_comprehend->shouldQuit())
return;
if (c < '1' || c > '3') {
/*
* The original Comprehend games just silently ignore any
* invalid selection.
*/
console_println("Invalid save game number");
return;
}
(void)g_comprehend->loadGameState(c - '0');
}
bool ComprehendGame::handle_restart() {
console_println(stringLookup(_gameStrings->game_restart).c_str());
_ended = false;
if (tolower(console_get_key()) == 'r') {
loadGame();
_updateFlags = UPDATE_ALL;
return true;
} else {
g_comprehend->quitGame();
return false;
}
}
Item *ComprehendGame::get_item_by_noun(byte noun) {
uint i;
if (!noun)
return nullptr;
/*
* FIXME - in oo-topos the word 'box' matches more than one object
* (the box and the snarl-in-a-box). The player is unable
* to drop the latter because this will match the former.
*/
for (i = 0; i < _items.size(); i++)
if (_items[i]._word == noun)
return &_items[i];
return nullptr;
}
int ComprehendGame::get_item_id(byte noun) {
for (int i = 0; i < (int)_items.size(); i++)
if (_items[i]._word == noun)
return i;
return -1;
}
void ComprehendGame::update_graphics() {
Item *item;
Room *room;
int type;
uint i;
if (!g_comprehend->isGraphicsEnabled())
return;
type = roomIsSpecial(_currentRoomCopy, nullptr);
switch (type) {
case ROOM_IS_DARK:
if (_updateFlags & UPDATE_GRAPHICS)
g_comprehend->clearScreen(false);
break;
case ROOM_IS_TOO_BRIGHT:
if (_updateFlags & UPDATE_GRAPHICS)
g_comprehend->clearScreen(true);
break;
default:
if (_updateFlags & UPDATE_GRAPHICS) {
room = get_room(_currentRoom);
g_comprehend->drawLocationPicture(room->_graphic - 1);
}
if ((_updateFlags & UPDATE_GRAPHICS) ||
(_updateFlags & UPDATE_GRAPHICS_ITEMS)) {
for (i = 0; i < _items.size(); i++) {
item = &_items[i];
if (item->_room == _currentRoom &&
item->_graphic != 0)
g_comprehend->drawItemPicture(item->_graphic - 1);
}
}
break;
}
}
void ComprehendGame::describe_objects_in_current_room() {
Item *item;
size_t count = 0;
uint i;
for (i = 0; i < _items.size(); i++) {
item = &_items[i];
if (item->_room == _currentRoom && item->_stringDesc != 0
&& !(item->_flags & ITEMF_INVISIBLE))
count++;
}
if (count > 0) {
console_println(stringLookup(STRING_YOU_SEE).c_str());
for (i = 0; i < _items.size(); i++) {
item = &_items[i];
if (item->_room == _currentRoom && item->_stringDesc != 0
&& !(item->_flags & ITEMF_INVISIBLE))
console_println(stringLookup(item->_stringDesc).c_str());
}
}
}
void ComprehendGame::updateRoomDesc() {
Room *room = get_room(_currentRoom);
uint room_desc_string = room->_stringDesc;
roomIsSpecial(_currentRoom, &room_desc_string);
Common::String desc = stringLookup(room_desc_string);
g_comprehend->printRoomDesc(desc);
}
void ComprehendGame::update() {
Room *room = get_room(_currentRoom);
uint room_type, room_desc_string;
update_graphics();
/* Check if the room is special (dark, too bright, etc) */
room_desc_string = room->_stringDesc;
room_type = roomIsSpecial(_currentRoom, &room_desc_string);
if (_updateFlags & UPDATE_ROOM_DESC) {
Common::String desc = stringLookup(room_desc_string);
console_println(desc.c_str());
g_comprehend->printRoomDesc(desc.c_str());
}
if ((_updateFlags & UPDATE_ITEM_LIST) && room_type == ROOM_IS_NORMAL)
describe_objects_in_current_room();
_updateFlags = 0;
}
void ComprehendGame::move_to(uint8 room) {
if (room >= (int)_rooms.size())
error("Attempted to move to invalid room %.2x\n", room);
_currentRoom = _currentRoomCopy = room;
_updateFlags = (UPDATE_GRAPHICS | UPDATE_ROOM_DESC |
UPDATE_ITEM_LIST);
}
size_t ComprehendGame::num_objects_in_room(int room) {
size_t count = 0, i;
for (i = 0; i < _items.size(); i++)
if (_items[i]._room == room)
count++;
return count;
}
void ComprehendGame::move_object(Item *item, int new_room) {
unsigned obj_weight = item->_flags & ITEMF_WEIGHT_MASK;
if (item->_room == new_room)
return;
if (item->_room == ROOM_INVENTORY) {
/* Removed from player's inventory */
_variables[VAR_INVENTORY_WEIGHT] -= obj_weight;
}
if (new_room == ROOM_INVENTORY) {
/* Moving to the player's inventory */
_variables[VAR_INVENTORY_WEIGHT] += obj_weight;
}
if (item->_room == _currentRoom) {
/* Item moved away from the current room */
_updateFlags |= UPDATE_GRAPHICS;
} else if (new_room == _currentRoom) {
/*
* Item moved into the current room. Only the item needs a
* redraw, not the whole room.
*/
_updateFlags |= (UPDATE_GRAPHICS_ITEMS |
UPDATE_ITEM_LIST);
}
item->_room = new_room;
}
void ComprehendGame::eval_instruction(FunctionState *func_state,
const Function &func, uint functionOffset, const Sentence *sentence) {
const Instruction *instr = &func[functionOffset];
if (DebugMan.isDebugChannelEnabled(kDebugScripts)) {
Common::String line;
if (!instr->_isCommand) {
line += "? ";
} else {
if (func_state->_testResult)
line += "+ ";
else
line += "- ";
}
line += Common::String::format("%.2x ", functionOffset);
line += g_debugger->dumpInstruction(this, func_state, instr);
debugC(kDebugScripts, "%s", line.c_str());
}
if (func_state->_orCount)
func_state->_orCount--;
if (instr->_isCommand) {
bool do_command;
func_state->_inCommand = true;
do_command = func_state->_testResult;
if (func_state->_orCount != 0)
g_comprehend->print("Warning: or_count == %d\n",
func_state->_orCount);
func_state->_orCount = 0;
if (!do_command)
return;
func_state->_elseResult = false;
func_state->_executed = true;
} else {
if (func_state->_inCommand) {
/* Finished command sequence - clear test result */
func_state->_inCommand = false;
func_state->_testResult = false;
func_state->_and = false;
}
}
execute_opcode(instr, sentence, func_state);
}
void ComprehendGame::eval_function(uint functionNum, const Sentence *sentence) {
FunctionState func_state;
uint i;
const Function &func = _functions[functionNum];
func_state._elseResult = true;
func_state._executed = false;
debugC(kDebugScripts, "Start of function %.4x", functionNum);
for (i = 0; i < func.size(); i++) {
if (func_state._executed && !func[i]._isCommand) {
/*
* At least one command has been executed and the
* current instruction is a test. Exit the function.
*/
break;
}
eval_instruction(&func_state, func, i, sentence);
}
debugC(kDebugScripts, "End of function %.4x\n", functionNum);
}
void ComprehendGame::skip_whitespace(const char **p) {
while (**p && Common::isSpace(**p))
(*p)++;
}
void ComprehendGame::skip_non_whitespace(const char **p) {
while (**p && !Common::isSpace(**p) && **p != ',' && **p != '\n')
(*p)++;
}
bool ComprehendGame::handle_sentence(Sentence *sentence) {
if (sentence->_nr_words == 1 && !strcmp(sentence->_words[0]._word, "quit")) {
g_comprehend->quitGame();
return true;
}
// Set up default sentence
Common::Array<byte> words;
const byte *src = &sentence->_formattedWords[0];
if (src[1] && src[3]) {
words.clear();
for (int idx = 0; idx < 4; ++idx)
words.push_back(src[idx]);
if (handle_sentence(0, sentence, words))
return true;
}
if (src[1]) {
words.clear();
for (int idx = 0; idx < 3; ++idx)
words.push_back(src[idx]);
if (handle_sentence(1, sentence, words))
return true;
}
if (src[3] && src[4]) {
words.clear();
words.push_back(src[4]);
words.push_back(src[0]);
words.push_back(src[2]);
words.push_back(src[3]);
if (handle_sentence(2, sentence, words))
return true;
}
if (src[4]) {
words.clear();
words.push_back(src[4]);
words.push_back(src[0]);
words.push_back(src[2]);
if (handle_sentence(3, sentence, words))
return true;
}
if (src[3]) {
words.clear();
words.push_back(src[0]);
words.push_back(src[2]);
words.push_back(src[3]);
if (handle_sentence(4, sentence, words))
return true;
}
if (src[2]) {
words.clear();
words.push_back(src[0]);
words.push_back(src[2]);
if (handle_sentence(5, sentence, words))
return true;
}
if (src[0]) {
words.clear();
words.push_back(src[0]);
if (handle_sentence(6, sentence, words))
return true;
}
return false;
}
bool ComprehendGame::handle_sentence(uint tableNum, Sentence *sentence, Common::Array<byte> &words) {
const ActionTable &table = _actions[tableNum];
for (uint i = 0; i < table.size(); i++) {
const Action &action = table[i];
// Check for a match on the words of the action
bool isMatch = true;
for (uint idx = 0; idx < action._nr_words && isMatch; ++idx)
isMatch = action._words[idx] == words[idx];
if (isMatch) {
// Match
_functionNum = action._function;
return true;
}
}
// No matching action
return false;
}
void ComprehendGame::handleAction(Sentence *sentence) {
_specialOpcode = 0;
if (_functionNum == 0) {
console_println(stringLookup(STRING_DONT_UNDERSTAND).c_str());
} else {
eval_function(_functionNum, sentence);
_functionNum = 0;
eval_function(0, nullptr);
}
handleSpecialOpcode();
}
void ComprehendGame::read_sentence(Sentence *sentence) {
bool sentence_end = false;
const char *word_string, *p = &_inputLine[_inputLineIndex];
Word *word;
sentence->clear();
for (;;) {
// Get the next word
skip_whitespace(&p);
word_string = p;
skip_non_whitespace(&p);
Common::String wordStr(word_string, p);
// Check for end of sentence
// FIXME: The below is a hacked simplified version of how the
// original handles cases like "get item1, item2"
if (*p == ',' || *p == '\n' || wordStr.equalsIgnoreCase("and")) {
// Sentence separator
++p;
sentence_end = true;
} else if (*p == '\0') {
sentence_end = true;
}
/* Find the dictionary word for this */
word = dict_find_word_by_string(this, wordStr.c_str());
if (!word)
sentence->_words[sentence->_nr_words].clear();
else
sentence->_words[sentence->_nr_words] = *word;
sentence->_nr_words++;
if (sentence->_nr_words >= ARRAY_SIZE(sentence->_words) ||
sentence_end)
break;
}
parse_sentence_word_pairs(sentence);
sentence->format();
_inputLineIndex = p - _inputLine;
}
void ComprehendGame::parse_sentence_word_pairs(Sentence *sentence) {
if (sentence->_nr_words < 2)
return;
// Iterate through the pairs
for (uint idx = 0; idx < _wordMaps.size(); ++idx) {
for (int firstWord = 0; firstWord < (int)sentence->_nr_words - 1; ++firstWord) {
for (int secondWord = firstWord + 1; secondWord < (int)sentence->_nr_words; ) {
if (sentence->_words[firstWord] == _wordMaps[idx]._word[0] &&
sentence->_words[secondWord] == _wordMaps[idx]._word[1]) {
// Found a word pair match
// Delete the second word
for (; secondWord < (int)sentence->_nr_words - 1; ++secondWord)
sentence->_words[secondWord] = sentence->_words[secondWord + 1];
sentence->_words[sentence->_nr_words - 1].clear();
sentence->_nr_words--;
// Replace the first word with the target
sentence->_words[firstWord] = _wordMaps[idx]._word[2];
} else {
// Move to next word
++secondWord;
}
}
}
}
}
void ComprehendGame::doBeforeTurn() {
// Make a copy of the current room
_currentRoomCopy = _currentRoom;
beforeTurn();
if (!_ended)
update();
}
void ComprehendGame::beforeTurn() {
// Run the each turn functions
eval_function(0, nullptr);
}
void ComprehendGame::read_input() {
Sentence tempSentence;
bool handled;
turn:
doBeforeTurn();
if (_ended)
return;
// If we're in full screen text, we can afford a blank row between
// any game response and the next line of text
if (!g_comprehend->isGraphicsEnabled())
g_comprehend->print("\n");
beforePrompt();
for (;;) {
_redoLine = REDO_NONE;
g_comprehend->print("> ");
g_comprehend->readLine(_inputLine, INPUT_LINE_SIZE);
if (g_comprehend->shouldQuit())
return;
_inputLineIndex = 0;
if (strlen(_inputLine) == 0) {
// Empty line, so toggle picture window visibility
if (!g_comprehend->toggleGraphics())
updateRoomDesc();
g_comprehend->print(_("Picture window toggled\n"));
_updateFlags |= UPDATE_GRAPHICS;
update_graphics();
continue;
}
afterPrompt();
if (_redoLine == REDO_NONE)
break;
else if (_redoLine == REDO_TURN)
goto turn;
}
for (;;) {
NounState prevNounState = _nounState;
_nounState = NOUNSTATE_STANDARD;
read_sentence(&tempSentence);
_sentence.copyFrom(tempSentence, tempSentence._formattedWords[0] || prevNounState != NOUNSTATE_STANDARD);
handled = handle_sentence(&_sentence);
handleAction(&_sentence);
if (!handled)
return;
/* FIXME - handle the 'before you can continue' case */
if (_inputLine[_inputLineIndex] == '\0')
break;
}
afterTurn();
}
void ComprehendGame::playGame() {
if (!g_comprehend->loadLauncherSavegameIfNeeded())
beforeGame();
_updateFlags = (uint)UPDATE_ALL;
while (!g_comprehend->shouldQuit()) {
read_input();
if (_ended && !handle_restart())
break;
}
}
uint ComprehendGame::getRandomNumber(uint max) const {
return g_comprehend->getRandomNumber(max);
}
void ComprehendGame::doMovementVerb(uint verbNum) {
assert(verbNum >= 1 && verbNum <= NR_DIRECTIONS);
Room *room = get_room(_currentRoom);
byte newRoom = room->_direction[verbNum - 1];
if (newRoom)
move_to(newRoom);
else
console_println(_strings[0].c_str());
}
void ComprehendGame::weighInventory() {
_totalInventoryWeight = 0;
if (!g_debugger->_invLimit)
// Allow for an unlimited number of items in inventory
return;
for (int idx = _itemCount - 1; idx > 0; --idx) {
Item *item = get_item(idx);
if (item->_room == ROOM_INVENTORY)
_totalInventoryWeight += item->_flags & ITEMF_WEIGHT_MASK;
}
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,204 @@
/* 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_COMPREHEND_GAME_H
#define GLK_COMPREHEND_GAME_H
#include "glk/comprehend/game_data.h"
#include "common/array.h"
#include "common/serializer.h"
namespace Glk {
namespace Comprehend {
#define ROOM_IS_NORMAL 0
#define ROOM_IS_DARK 1
#define ROOM_IS_TOO_BRIGHT 2
#define INPUT_LINE_SIZE 1024
enum NounState { NOUNSTATE_STANDARD = 0, NOUNSTATE_QUERY = 1, NOUNSTATE_INITIAL = 2 };
enum RedoLine { REDO_NONE, REDO_PROMPT, REDO_TURN };
struct GameStrings;
struct Sentence;
struct Sentence {
Word _words[20];
size_t _nr_words;
byte _formattedWords[6];
byte _specialOpcodeVal2;
Sentence() {
clear();
}
bool empty() const {
return !_formattedWords[0];
}
/**
* Clears the sentence
*/
void clear();
/**
* Copies from another sentence to this one
*/
void copyFrom(const Sentence &src, bool copyNoun = true);
/**
* Splits up the array of _words into a _formattedWords
* array, placing the words in appropriate noun, verb, etc.
* positions appropriately
*/
void format();
};
class ComprehendGame : public GameData {
protected:
bool _ended;
NounState _nounState;
Sentence _sentence;
char _inputLine[INPUT_LINE_SIZE];
int _inputLineIndex;
int _currentRoomCopy;
int _functionNum;
int _specialOpcode;
RedoLine _redoLine;
public:
const GameStrings *_gameStrings;
private:
void describe_objects_in_current_room();
void eval_instruction(FunctionState *func_state,
const Function &func, uint functionOffset,
const Sentence *sentence);
void skip_whitespace(const char **p);
void skip_non_whitespace(const char **p);
bool handle_sentence(Sentence *sentence);
bool handle_sentence(uint tableNum, Sentence *sentence, Common::Array<byte> &words);
void read_sentence(Sentence *sentence);
void parse_sentence_word_pairs(Sentence *sentence);
void read_input();
void doBeforeTurn();
protected:
void game_save();
void game_restore();
void game_restart() {
_ended = true;
}
virtual bool handle_restart();
virtual void execute_opcode(const Instruction *instr, const Sentence *sentence,
FunctionState *func_state) = 0;
int console_get_key();
void console_println(const char *text);
void move_object(Item *item, int new_room);
/*
* Comprehend functions consist of test and command instructions (if the MSB
* of the opcode is set then it is a command). Functions are parsed by
* evaluating each test until a command instruction is encountered. If the
* overall result of the tests was true then the command instructions are
* executed until either a test instruction is found or the end of the function
* is reached. Otherwise the commands instructions are skipped over and the
* next test sequence (if there is one) is tried.
*/
void eval_function(uint functionNum, const Sentence *sentence);
void parse_header(FileBuffer *fb) override {
GameData::parse_header(fb);
}
Item *get_item_by_noun(byte noun);
int get_item_id(byte noun);
void weighInventory();
size_t num_objects_in_room(int room);
void doMovementVerb(uint verbNum);
public:
ComprehendGame();
virtual ~ComprehendGame();
/**
* Called before the game starts
*/
virtual void beforeGame() {}
/**
* Called just before the prompt for user input
*/
virtual void beforePrompt() {}
/**
* Called after input has been entered.
*/
virtual void afterPrompt() {}
/**
* Called before the start of a game turn
*/
virtual void beforeTurn();
/**
* Called at the end of a game turn
*/
virtual void afterTurn() {}
/**
* Called when an action function has been selected
*/
virtual void handleAction(Sentence *sentence);
virtual int roomIsSpecial(uint room_index, uint *room_desc_string) {
return ROOM_IS_NORMAL;
}
virtual void handleSpecialOpcode() {}
virtual void synchronizeSave(Common::Serializer &s);
virtual ScriptOpcode getScriptOpcode(const Instruction *instr) = 0;
Common::String stringLookup(uint16 index);
Common::String instrStringLookup(uint8 index, uint8 table);
virtual void playGame();
void move_to(uint8 room);
Room *get_room(uint16 index);
Item *get_item(uint16 index);
void updateRoomDesc();
void update();
void update_graphics();
/**
* Gets a random number
*/
uint getRandomNumber(uint max) const;
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,181 @@
/* 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/comprehend/game_cc.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/pics.h"
namespace Glk {
namespace Comprehend {
static const GameStrings CC1_STRINGS = {0x9};
CrimsonCrownGame::CrimsonCrownGame() : ComprehendGameV1(),
_diskNum(1), _newDiskNum(1) {
setupDisk(1);
}
void CrimsonCrownGame::setupDisk(uint diskNum) {
assert(diskNum == 1 || diskNum == 2);
_gameDataFile = Common::String::format("cc%u.gda", diskNum);
_stringFiles.clear();
_stringFiles.push_back(Common::String::format("ma.ms%u", diskNum).c_str());
_locationGraphicFiles.clear();
_locationGraphicFiles.push_back(Common::String::format("ra.ms%u", diskNum));
_locationGraphicFiles.push_back(Common::String::format("rb.ms%u", diskNum));
if (diskNum == 1)
_locationGraphicFiles.push_back("RC.ms1");
_itemGraphicFiles.clear();
_itemGraphicFiles.push_back(Common::String::format("oa.ms%u", diskNum));
_itemGraphicFiles.push_back(Common::String::format("ob.ms%u", diskNum));
if (diskNum == 1)
_gameStrings = &CC1_STRINGS;
else
_gameStrings = nullptr;
_titleGraphicFile = "cctitle.ms1";
_diskNum = diskNum;
}
void CrimsonCrownGame::beforeGame() {
// Draw the title
g_comprehend->drawPicture(TITLE_IMAGE);
g_comprehend->readChar();
}
void CrimsonCrownGame::synchronizeSave(Common::Serializer &s) {
if (s.isSaving()) {
s.syncAsByte(_diskNum);
} else {
// Get the disk the save is for. The beforeTurn call allows
// for the currently loaded disk to be switched if necessary
s.syncAsByte(_newDiskNum);
beforeTurn();
}
ComprehendGame::synchronizeSave(s);
}
void CrimsonCrownGame::handleSpecialOpcode() {
switch (_specialOpcode) {
case 1:
// Crystyal ball cutscene
if (_diskNum == 1) {
crystalBallCutscene();
} else {
throneCutscene();
}
break;
case 3:
// Game over - failure
game_restart();
break;
case 5:
if (_diskNum == 1) {
// Finished disk 1
g_comprehend->readChar();
g_comprehend->drawLocationPicture(41);
console_println(_strings2[26].c_str());
g_comprehend->readChar();
_newDiskNum = 2;
move_to(21);
console_println(_strings[407].c_str());
} else {
// Won the game
g_comprehend->drawLocationPicture(29, false);
g_comprehend->drawItemPicture(20);
console_println(stringLookup(0x21c).c_str());
console_println(stringLookup(0x21d).c_str());
g_comprehend->readChar();
g_comprehend->quitGame();
}
break;
case 6:
game_save();
break;
case 7:
game_restore();
break;
default:
break;
}
}
void CrimsonCrownGame::crystalBallCutscene() {
g_comprehend->showGraphics();
for (int screenNum = 38; screenNum <= 40; ++screenNum) {
g_comprehend->drawLocationPicture(screenNum);
g_comprehend->readChar();
if (g_comprehend->shouldQuit())
return;
}
}
void CrimsonCrownGame::throneCutscene() {
// Show the screen
update();
console_println(stringLookup(0x20A).c_str());
// Handle what happens in climatic showdown
eval_function(14, nullptr);
}
void CrimsonCrownGame::beforePrompt() {
// Clear the Sabrina/Erik action flags
_flags[0xa] = 0;
_flags[0xb] = 0;
}
void CrimsonCrownGame::beforeTurn() {
if (_newDiskNum != _diskNum) {
setupDisk(_newDiskNum);
loadGame();
move_to(_currentRoom);
}
ComprehendGameV1::beforeTurn();
}
bool CrimsonCrownGame::handle_restart() {
if (_diskNum != 1) {
setupDisk(1);
loadGame();
}
return ComprehendGame::handle_restart();
}
} // namespace Comprehend
} // namespace Glk

View 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_COMPREHEND_GAME_CC_H
#define GLK_COMPREHEND_GAME_CC_H
#include "glk/comprehend/game_opcodes.h"
namespace Glk {
namespace Comprehend {
class CrimsonCrownGame : public ComprehendGameV1 {
private:
uint _diskNum;
uint _newDiskNum;
private:
/**
* Cutscene triggered when looking at crystal ball
*/
void crystalBallCutscene();
/**
* Start of throneroom cutscene
*/
void throneCutscene();
protected:
bool handle_restart() override;
public:
CrimsonCrownGame();
~CrimsonCrownGame() override {}
void beforeGame() override;
void beforePrompt() override;
void beforeTurn() override;
void handleSpecialOpcode() override;
void synchronizeSave(Common::Serializer &s) override;
void setupDisk(uint diskNum);
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,726 @@
/* 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/comprehend/game_data.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/dictionary.h"
#include "glk/comprehend/draw_surface.h"
#include "glk/comprehend/file_buf.h"
#include "glk/comprehend/game.h"
#include "glk/comprehend/pics.h"
namespace Glk {
namespace Comprehend {
static const char CHARSET[] = "..abcdefghijklmnopqrstuvwxyz .";
static const char SPECIAL_CHARSET[] = "[]\n!\"#$%&'(),-/0123456789:;?<>";
#define STRING_FILE_COUNT 64
void FunctionState::clear() {
_testResult = true;
_elseResult = false;
_orCount = 0;
_and = false;
_inCommand = false;
_executed = false;
_notComparison = false;
}
/*-------------------------------------------------------*/
void Room::clear() {
_flags = 0;
_graphic = 0;
_stringDesc = 0;
Common::fill(&_direction[0], &_direction[NR_DIRECTIONS], 0);
}
/*-------------------------------------------------------*/
void Item::clear() {
_stringDesc = 0;
_longString = 0;
_room = 0;
_flags = 0;
_word = 0;
_graphic = 0;
}
void Item::synchronize(Common::Serializer &s) {
s.syncAsUint16LE(_stringDesc);
s.syncAsUint16LE(_longString);
s.syncAsByte(_room);
s.syncAsByte(_flags);
s.syncAsByte(_word);
s.syncAsByte(_graphic);
}
/*-------------------------------------------------------*/
void Word::clear() {
WordIndex::clear();
Common::fill(&_word[0], &_word[7], '\0');
}
Word &Word::operator=(const WordIndex &src) {
_index = src._index;
_type = src._type;
Common::fill(&_word[0], &_word[7], '\0');
return *this;
}
void Word::load(FileBuffer *fb) {
fb->read(_word, 6);
// Decode
for (int j = 0; j < 6; j++)
_word[j] = tolower((char)(_word[j] ^ 0xaa));
// Strip off trailing spaces
_word[6] = '\0';
for (int j = 5; j > 0 && _word[j] == ' '; --j)
_word[j] = '\0';
_index = fb->readByte();
_type = fb->readByte();
}
/*-------------------------------------------------------*/
void WordMap::clear() {
_flags = 0;
for (int idx = 0; idx < 3; ++idx)
_word[idx].clear();
}
/*-------------------------------------------------------*/
void Action::clear() {
_nr_words = 0;
_function = 0;
Common::fill(&_words[0], &_words[4], 0);
}
/*-------------------------------------------------------*/
Instruction::Instruction(byte opcode, byte op1, byte op2, byte op3) : _opcode(opcode) {
_operand[0] = op1;
_operand[1] = op2;
_operand[2] = op3;
}
void Instruction::clear() {
_opcode = 0;
_nr_operands = 0;
_isCommand = false;
Common::fill(&_operand[0], &_operand[3], 0);
}
/*-------------------------------------------------------*/
void GameHeader::clear() {
magic = 0;
room_desc_table = 0;
room_flags_table = 0;
room_graphics_table = 0;
nr_items = 0;
addr_item_locations = 0;
addr_item_flags = 0;
addr_item_word = 0;
addr_item_strings = 0;
addr_item_graphics = 0;
addr_dictionary = 0;
addr_word_map = 0;
addr_strings = 0;
addr_strings_end = 0;
Common::fill(&addr_actions[0], &addr_actions[7], 0);
Common::fill(&room_direction_table[0], &room_direction_table[NR_DIRECTIONS], 0);
}
/*-------------------------------------------------------*/
void GameData::clearGame() {
_header.clear();
_magicWord = 0;
_comprehendVersion = 0;
_startRoom = 0;
_currentRoom = 0;
_currentReplaceWord = 0;
_wordFlags = 0;
_updateFlags = 0;
_colorTable = 0;
_itemCount = 0;
_totalInventoryWeight = 0;
_strings.clear();
_strings2.clear();
_rooms.clear();
_items.clear();
_wordMaps.clear();
_actions.clear();
_functions.clear();
_replaceWords.clear();
Common::fill(&_flags[0], &_flags[MAX_FLAGS], false);
Common::fill(&_variables[0], &_variables[MAX_VARIABLES], 0);
}
void GameData::parse_header_le16(FileBuffer *fb, uint16 *val) {
*val = fb->readUint16LE();
*val += _magicWord;
}
uint8 GameData::parse_vm_instruction(FileBuffer *fb,
Instruction *instr) {
uint i;
/* Get the opcode */
instr->_opcode = fb->readByte();
instr->_nr_operands = opcode_nr_operands(instr->_opcode);
/* Get the operands */
for (i = 0; i < instr->_nr_operands; i++)
instr->_operand[i] = fb->readByte();
instr->_isCommand = opcode_is_command(instr->_opcode);
return instr->_opcode;
}
#define MAX_FUNCTION_SIZE 0x100
void GameData::parse_function(FileBuffer *fb, Function *func) {
const uint8 *p;
uint8 opcode;
p = (const uint8 *)memchr(fb->dataPtr(), 0x00, fb->size() - fb->pos());
if (!p)
error("bad function @ %.4x", (int)fb->pos());
for (;;) {
Instruction instruction;
opcode = parse_vm_instruction(fb, &instruction);
if (opcode == 0)
break;
func->push_back(instruction);
assert(func->size() <= MAX_FUNCTION_SIZE);
}
assert(fb->dataPtr() == (p + 1));
}
void GameData::parse_vm(FileBuffer *fb) {
fb->seek(_header.addr_vm);
while (1) {
Function func;
parse_function(fb, &func);
if (func.empty())
break;
_functions.push_back(func);
// WORKAROUND: Parsing functions for Talisman
if (_functions.size() == 0x1d8 && g_vm->getGameID() == "talisman")
break;
}
}
void GameData::parse_action_tables(FileBuffer *fb) {
uint8 verb, count;
uint i, j;
_actions.clear();
_actions.resize(7);
const byte NUM_WORDS[7] = { 3, 2, 3, 2, 2, 1, 0 };
for (int tableNum = 0; tableNum < 7; ++tableNum) {
ActionTable &table = _actions[tableNum];
fb->seek(_header.addr_actions[tableNum]);
while (1) {
verb = fb->readByte();
if (verb == 0)
break;
count = fb->readByte();
for (i = 0; i < count; i++) {
Action action;
action._nr_words = NUM_WORDS[tableNum] + 1;
action._words[0] = verb;
for (j = 1; j < action._nr_words; j++)
action._words[j] = fb->readByte();
action._function = fb->readUint16LE();
table.push_back(action);
}
}
}
}
void GameData::parse_dictionary(FileBuffer *fb) {
fb->seek(_header.addr_dictionary);
for (uint i = 0; i < _words.size(); i++)
_words[i].load(fb);
}
void GameData::parse_word_map(FileBuffer *fb) {
uint8 index, type;
uint i;
_wordMaps.clear();
fb->seek(_header.addr_word_map);
/*
* Parse the word pair table. Each entry has a pair of dictionary
* index/type values for a first and second word.
*/
while (1) {
WordMap map;
index = fb->readByte();
type = fb->readByte();
if (type == 0 && index == 0) {
/* End of pairs */
break;
}
map._word[0]._index = index;
map._word[0]._type = type;
map._flags = fb->readByte();
map._word[1]._index = fb->readByte();
map._word[1]._type = fb->readByte();
_wordMaps.push_back(map);
}
/*
* Parse the target word table. Each entry has a dictionary
* index/type. The first and second words from above map to the
* target word here. E.g. 'go north' -> 'north'.
*/
fb->seek(_header.addr_word_map_target);
for (i = 0; i < _wordMaps.size(); i++) {
WordMap &map = _wordMaps[i];
map._word[2]._index = fb->readByte();
map._word[2]._type = fb->readByte();
}
}
void GameData::parse_items(FileBuffer *fb) {
size_t nr_items = _header.nr_items;
_items.resize(nr_items);
/* Item descriptions */
fb->seek(_header.addr_item_strings);
file_buf_get_array_le16(fb, 0, _items, _stringDesc, nr_items);
if (_comprehendVersion == 2) {
/* Comprehend version 2 adds long string descriptions */
fb->seek(_header.addr_item_strings +
(_items.size() * sizeof(uint16)));
file_buf_get_array_le16(fb, 0, _items, _longString, nr_items);
}
/* Item flags */
fb->seek(_header.addr_item_flags);
file_buf_get_array_u8(fb, 0, _items, _flags, nr_items);
/* Item word */
fb->seek(_header.addr_item_word);
file_buf_get_array_u8(fb, 0, _items, _word, nr_items);
/* Item locations */
fb->seek(_header.addr_item_locations);
file_buf_get_array_u8(fb, 0, _items, _room, nr_items);
/* Item graphic */
fb->seek(_header.addr_item_graphics);
file_buf_get_array_u8(fb, 0, _items, _graphic, nr_items);
}
void GameData::parse_rooms(FileBuffer *fb) {
size_t nr_rooms = _rooms.size() - 1;
int i;
/* Room exit directions */
for (i = 0; i < NR_DIRECTIONS; i++) {
fb->seek(_header.room_direction_table[i]);
file_buf_get_array_u8(fb, 1, _rooms, _direction[i], nr_rooms);
}
/* Room string descriptions */
fb->seek(_header.room_desc_table);
file_buf_get_array_le16(fb, 1, _rooms, _stringDesc, nr_rooms);
/* Room flags */
fb->seek(_header.room_flags_table);
file_buf_get_array_u8(fb, 1, _rooms, _flags, nr_rooms);
/* Room graphic */
fb->seek(_header.room_graphics_table);
file_buf_get_array_u8(fb, 1, _rooms, _graphic, nr_rooms);
}
uint64 GameData::string_get_chunk(uint8 *string) {
uint64 c, val = 0;
int i;
for (i = 0; i < 5; i++) {
c = string[i] & 0xff;
val |= (c << ((4 - i) * 8));
}
return val;
}
char GameData::decode_string_elem(uint8 c, bool capital, bool special) {
if (special) {
if (c < sizeof(SPECIAL_CHARSET) - 1)
return SPECIAL_CHARSET[c];
} else {
if (c < sizeof(CHARSET) - 1) {
c = CHARSET[c];
if (capital) {
/*
* A capital space means that the character
* is dynamically replaced by at runtime.
* We use the character '@' since it cannot
* otherwise appear in strings.
*/
if (c == ' ')
return '@';
return c - 0x20;
} else {
return c;
}
}
}
// Unknown character
g_comprehend->print("Unknown char %d, caps=%d, special=%d\n", c, capital, special);
return '*';
}
Common::String GameData::parseString(FileBuffer *fb) {
bool capital_next = false, special_next = false;
unsigned i, j;
uint64 chunk;
uint8 elem, *encoded;
char c;
size_t encoded_len;
Common::String string;
encoded_len = fb->strlen();
/* Get the encoded string */
encoded = (uint8 *)malloc(encoded_len + 5);
Common::fill(encoded, encoded + encoded_len + 5, 0);
fb->read(encoded, encoded_len);
/* Skip over the zero byte */
if (fb->pos() < fb->size())
fb->skip(1);
for (i = 0; i < encoded_len; i += 5) {
chunk = string_get_chunk(&encoded[i]);
for (j = 0; j < 8; j++) {
elem = (chunk >> (35 - (5 * j))) & 0x1f;
if (elem == 0)
goto done;
if (elem == 0x1e) {
capital_next = true;
} else if (elem == 0x1f) {
special_next = true;
} else {
c = decode_string_elem(elem, capital_next,
special_next);
special_next = false;
capital_next = false;
string += c;
}
}
}
done:
free(encoded);
return string;
}
void GameData::parse_string_table(FileBuffer *fb, uint start_addr,
uint32 end_addr, StringTable *table) {
if (start_addr < end_addr) {
fb->seek(start_addr);
while (1) {
table->push_back(parseString(fb));
if (fb->pos() >= (int32)end_addr)
break;
}
}
}
void GameData::parse_variables(FileBuffer *fb) {
uint i;
for (i = 0; i < ARRAY_SIZE(_variables); i++)
_variables[i] = fb->readUint16LE();
}
void GameData::parse_flags(FileBuffer *fb) {
uint i, flag_index = 0;
int bit;
uint8 bitmask;
for (i = 0; i < ARRAY_SIZE(_flags) / 8; i++) {
bitmask = fb->readByte();
for (bit = 7; bit >= 0; bit--) {
_flags[flag_index] = !!(bitmask & (1 << bit));
flag_index++;
}
}
}
void GameData::parse_replace_words(FileBuffer *fb) {
size_t len;
bool eof;
/* FIXME - Rename addr_strings_end */
fb->seek(_header.addr_strings_end);
/* FIXME - what is this for */
fb->skip(2);
for (;;) {
len = fb->strlen(&eof);
if (len == 0)
break;
_replaceWords.push_back(Common::String((const char *)fb->dataPtr(), len));
fb->skip(len + (eof ? 0 : 1));
if (eof)
break;
}
}
void GameData::parse_header(FileBuffer *fb) {
GameHeader *header = &_header;
uint16 dummy, addr_dictionary_end;
fb->seek(0);
header->magic = fb->readUint16LE();
fb->skip(2); // Unknown in earlier versions
switch (header->magic) {
case 0x2000: /* Transylvania, Crimson Crown disk one */
case 0x4800: /* Crimson Crown disk two */
_comprehendVersion = 1;
_magicWord = (uint16)(-0x5a00 + 0x4);
break;
case 0x8bc3: /* Transylvania v2 */
case 0x93f0: /* OO-Topos */
case 0xa429: /* Talisman */
_comprehendVersion = 2;
_magicWord = (uint16)-0x5a00;
// Actions table starts right at the start of the file
fb->seek(0);
break;
default:
error("Unknown game_data magic %.4x\n", header->magic);
break;
}
/* Basic data */
for (int idx = 0; idx < 7; ++idx)
parse_header_le16(fb, &header->addr_actions[idx]);
parse_header_le16(fb, &header->addr_vm);
parse_header_le16(fb, &header->addr_dictionary);
parse_header_le16(fb, &header->addr_word_map);
parse_header_le16(fb, &header->addr_word_map_target);
addr_dictionary_end = header->addr_word_map;
/* Rooms */
parse_header_le16(fb, &header->room_desc_table);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_NORTH]);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_SOUTH]);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_EAST]);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_WEST]);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_UP]);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_DOWN]);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_IN]);
parse_header_le16(fb, &header->room_direction_table[DIRECTION_OUT]);
parse_header_le16(fb, &header->room_flags_table);
parse_header_le16(fb, &header->room_graphics_table);
/*
* Objects.
*
* Layout is dependent on comprehend version.
*/
if (_comprehendVersion == 1) {
parse_header_le16(fb, &header->addr_item_locations);
parse_header_le16(fb, &header->addr_item_flags);
parse_header_le16(fb, &header->addr_item_word);
parse_header_le16(fb, &header->addr_item_strings);
parse_header_le16(fb, &header->addr_item_graphics);
header->nr_items = (header->addr_item_word -
header->addr_item_flags);
} else {
parse_header_le16(fb, &header->addr_item_strings);
parse_header_le16(fb, &header->addr_item_word);
parse_header_le16(fb, &header->addr_item_locations);
parse_header_le16(fb, &header->addr_item_flags);
parse_header_le16(fb, &header->addr_item_graphics);
header->nr_items = (header->addr_item_flags -
header->addr_item_locations);
}
parse_header_le16(fb, &header->addr_strings);
parse_header_le16(fb, &dummy);
parse_header_le16(fb, &header->addr_strings_end);
fb->skip(1);
_startRoom = fb->readByte();
fb->skip(1);
parse_variables(fb);
parse_flags(fb);
fb->skip(9);
_itemCount = fb->readByte();
_rooms.resize(header->room_direction_table[DIRECTION_SOUTH] -
header->room_direction_table[DIRECTION_NORTH] + 1);
_words.resize((addr_dictionary_end - header->addr_dictionary) / 8);
}
void GameData::load_extra_string_file(const StringFile &stringFile) {
FileBuffer fb(stringFile._filename);
if (stringFile._baseOffset > 0) {
// Explicit offset specified, so read the strings in sequentially
uint endOffset = stringFile._endOffset;
if (!endOffset)
endOffset = fb.size();
parse_string_table(&fb, stringFile._baseOffset, endOffset, &_strings2);
} else {
// Standard strings file. Has a 4-byte header we can ignore,
// followed by 64 2-byte string offsets
fb.seek(4);
uint fileSize = fb.size();
// Read in the index
uint16 index[STRING_FILE_COUNT];
Common::fill(&index[0], &index[STRING_FILE_COUNT], 0);
for (int i = 0; i < STRING_FILE_COUNT; ++i) {
uint v = fb.readUint16LE();
if (v > fileSize)
break;
index[i] = v;
}
// Iterate through parsing the strings
for (int i = 0; i < STRING_FILE_COUNT; ++i) {
if (index[i]) {
fb.seek(index[i] + 4);
_strings2.push_back(parseString(&fb));
} else {
_strings2.push_back("");
}
}
}
}
void GameData::load_extra_string_files() {
_strings2.clear();
_strings2.reserve(STRING_FILE_COUNT * _stringFiles.size() + 1);
for (uint i = 0; i < _stringFiles.size(); i++) {
// TODO: Is this needed for other than OO-Topos?
if (_comprehendVersion == 2 && (i == 0 || i == 4))
_strings2.push_back("");
load_extra_string_file(_stringFiles[i]);
}
}
void GameData::loadGameData() {
FileBuffer fb(_gameDataFile);
clearGame();
parse_header(&fb);
parse_rooms(&fb);
parse_items(&fb);
parse_dictionary(&fb);
parse_word_map(&fb);
if (g_comprehend->getGameID() != "talisman")
parse_string_table(&fb, _header.addr_strings, _header.addr_strings_end, &_strings);
load_extra_string_files();
parse_vm(&fb);
parse_action_tables(&fb);
parse_replace_words(&fb);
}
void GameData::loadGame() {
/* Load the main game data file */
loadGameData();
if (g_comprehend->isGraphicsEnabled()) {
// Set up the picture archive
g_comprehend->_pics->load(_locationGraphicFiles,
_itemGraphicFiles, _titleGraphicFile);
if (_colorTable)
g_comprehend->_drawSurface->setColorTable(_colorTable);
}
// FIXME: This can be merged, don't need to keep start room around
_currentRoom = _startRoom;
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,475 @@
/* 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_COMPREHEND_GAME_DATA_H
#define GLK_COMPREHEND_GAME_DATA_H
#include "glk/comprehend/file_buf.h"
#include "common/serializer.h"
#include "common/str-array.h"
namespace Glk {
namespace Comprehend {
#define MAX_FLAGS 256
#define MAX_VARIABLES 128
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
class ComprehendGame;
enum {
DIRECTION_NORTH,
DIRECTION_SOUTH,
DIRECTION_EAST,
DIRECTION_WEST,
DIRECTION_UP,
DIRECTION_DOWN,
DIRECTION_IN,
DIRECTION_OUT,
NR_DIRECTIONS
};
enum ScriptOpcode {
OPCODE_UNKNOWN,
OPCODE_HAVE_OBJECT,
OPCODE_OR,
OPCODE_IN_ROOM,
OPCODE_VAR_EQ1,
OPCODE_VAR_EQ2,
OPCODE_VAR_GT1,
OPCODE_VAR_GT2,
OPCODE_VAR_GTE1,
OPCODE_VAR_GTE2,
OPCODE_CURRENT_IS_OBJECT,
OPCODE_OBJECT_PRESENT,
OPCODE_ELSE,
OPCODE_OBJECT_IN_ROOM,
OPCODE_CURRENT_OBJECT_NOT_VALID,
OPCODE_INVENTORY_FULL,
OPCODE_INVENTORY_FULL_X,
OPCODE_TEST_FLAG,
OPCODE_CURRENT_OBJECT_IN_ROOM,
OPCODE_HAVE_CURRENT_OBJECT,
OPCODE_OBJECT_IS_NOT_NOWHERE,
OPCODE_CURRENT_OBJECT_PRESENT,
OPCODE_TEST_ROOM_FLAG,
OPCODE_NOT_HAVE_OBJECT,
OPCODE_NOT_IN_ROOM,
OPCODE_CURRENT_OBJECT_NOT_IN_ROOM,
OPCODE_OBJECT_NOT_IN_ROOM,
OPCODE_TEST_NOT_FLAG,
OPCODE_NOT_HAVE_CURRENT_OBJECT,
OPCODE_OBJECT_IS_NOWHERE,
OPCODE_OBJECT_NOT_PRESENT,
OPCODE_CURRENT_OBJECT_IS_NOWHERE,
OPCODE_CURRENT_OBJECT_NOT_PRESENT,
OPCODE_CURRENT_OBJECT_NOT_TAKEABLE,
OPCODE_TEST_NOT_ROOM_FLAG,
OPCODE_INVENTORY,
OPCODE_TAKE_OBJECT,
OPCODE_MOVE_OBJECT_TO_ROOM,
OPCODE_SAVE_ACTION,
OPCODE_CLEAR_LINE,
OPCODE_MOVE_TO_ROOM,
OPCODE_VAR_ADD,
OPCODE_SET_ROOM_DESCRIPTION,
OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM,
OPCODE_VAR_SUB,
OPCODE_SET_OBJECT_DESCRIPTION,
OPCODE_SET_OBJECT_LONG_DESCRIPTION,
OPCODE_MOVE_DEFAULT,
OPCODE_PRINT,
OPCODE_REMOVE_OBJECT,
OPCODE_SET_FLAG,
OPCODE_CALL_FUNC,
OPCODE_CALL_FUNC2,
OPCODE_TURN_TICK,
OPCODE_CLEAR_FLAG,
OPCODE_INVENTORY_ROOM,
OPCODE_TAKE_CURRENT_OBJECT,
OPCODE_SPECIAL,
OPCODE_DROP_OBJECT,
OPCODE_DROP_CURRENT_OBJECT,
OPCODE_SET_ROOM_GRAPHIC,
OPCODE_SET_OBJECT_GRAPHIC,
OPCODE_REMOVE_CURRENT_OBJECT,
OPCODE_MOVE_DIR,
OPCODE_VAR_INC,
OPCODE_VAR_DEC,
OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM,
OPCODE_DESCRIBE_CURRENT_OBJECT,
OPCODE_SET_STRING_REPLACEMENT1,
OPCODE_SET_STRING_REPLACEMENT2,
OPCODE_SET_STRING_REPLACEMENT3,
OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT,
OPCODE_DRAW_ROOM,
OPCODE_DRAW_OBJECT,
OPCODE_WAIT_KEY,
OPCODE_TEST_FALSE,
OPCODE_CAN_TAKE,
OPCODE_TOO_HEAVY,
OPCODE_OBJECT_TAKEABLE,
OPCODE_OBJECT_CAN_TAKE,
OPCODE_CLEAR_INVISIBLE,
OPCODE_SET_INVISIBLE,
OPCODE_CLEAR_CAN_TAKE,
OPCODE_SET_CAN_TAKE,
OPCODE_CLEAR_FLAG40,
OPCODE_SET_FLAG40,
OPCODE_RANDOM_MSG,
OPCODE_SET_WORD,
OPCODE_CLEAR_WORD
};
/* Game state update flags */
#define UPDATE_GRAPHICS (1 << 0) /* Implies UPDATE_GRAPHICS_ITEMS */
#define UPDATE_GRAPHICS_ITEMS (1 << 1)
#define UPDATE_ROOM_DESC (1 << 2)
#define UPDATE_ITEM_LIST (1 << 3)
#define UPDATE_ALL (~0U)
/* Action types */
enum {
ACTION_VERB_VERB_NOUN_NOUN,
ACTION_VERB_NOUN_JOIN_NOUN,
ACTION_VERB_JOIN_NOUN,
ACTION_VERB_DIR_NOUN,
ACTION_VERB_NOUN_NOUN,
ACTION_VERB_NOUN,
ACTION_VERB_OPT_NOUN
};
/* Standard strings (main string table) */
#define STRING_CANT_GO 0
#define STRING_DONT_UNDERSTAND 1
#define STRING_YOU_SEE 2
#define STRING_INVENTORY 3
#define STRING_INVENTORY_EMPTY 4
#define STRING_BEFORE_CONTINUE 5
#define STRING_SAVE_GAME 6
#define STRING_RESTORE_GAME 7
/* Special variables */
#define VAR_INVENTORY_WEIGHT 0
#define VAR_INVENTORY_LIMIT 1
#define VAR_TURN_COUNT 2
/* Special rooms */
#define ROOM_INVENTORY 0x00
#define ROOM_CONTAINER 0xfe
#define ROOM_NOWHERE 0xff
/* Item flags */
enum ItemFlag {
ITEMF_WEIGHT_MASK = 0x7,
ITEMF_CAN_TAKE = 1 << 3,
ITEMF_UNKNOWN = 1 << 6,
ITEMF_INVISIBLE = 1 << 7
};
/* Word types */
#define WORD_TYPE_VERB 0x01
#define WORD_TYPE_JOIN 0x02
#define WORD_TYPE_FEMALE 0x10
#define WORD_TYPE_MALE 0x20
#define WORD_TYPE_NOUN 0x40
#define WORD_TYPE_NOUN_PLURAL 0x80
#define WORD_TYPE_NOUN_MASK (WORD_TYPE_FEMALE | WORD_TYPE_MALE | \
WORD_TYPE_NOUN | WORD_TYPE_NOUN_PLURAL)
struct FunctionState {
bool _testResult;
bool _elseResult;
uint _orCount;
bool _and;
bool _inCommand;
bool _executed;
bool _notComparison;
FunctionState() {
clear();
}
void clear();
};
struct Room {
uint8 _direction[NR_DIRECTIONS];
uint8 _flags;
uint8 _graphic;
uint16 _stringDesc;
Room() {
clear();
}
void clear();
};
struct Item {
uint16 _stringDesc;
uint16 _longString; /* Only used by version 2 */
uint8 _room;
uint8 _flags;
uint8 _word;
uint8 _graphic;
Item() {
clear();
}
void clear();
void synchronize(Common::Serializer &s);
};
struct WordIndex {
uint8 _index;
uint8 _type;
WordIndex() {
clear();
}
void clear() {
_index = _type = 0;
}
bool operator==(WordIndex &src) {
return _index == src._index && _type == src._type;
}
bool operator()() const {
return _index != 0;
}
};
struct Word : public WordIndex {
char _word[7];
Word() : WordIndex() {
Word::clear();
}
void clear();
void load(FileBuffer *fb);
Word &operator=(const WordIndex &src);
};
struct WordMap {
/* <word[0]>, <word[1]> == <word[2]> */
WordIndex _word[3];
uint8 _flags;
WordMap() {
clear();
}
void clear();
};
struct Action {
size_t _nr_words;
uint8 _words[4];
uint16 _function;
Action() {
clear();
}
void clear();
};
struct Instruction {
uint8 _opcode;
size_t _nr_operands;
uint8 _operand[3];
bool _isCommand;
Instruction() {
clear();
}
Instruction(byte opcode, byte op1 = 0, byte op2 = 0, byte op3 = 0);
void clear();
};
typedef Common::Array<Instruction> Function;
typedef Common::StringArray StringTable;
struct StringFile {
Common::String _filename;
uint32 _baseOffset;
uint32 _endOffset;
StringFile() : _baseOffset(0), _endOffset(0) {
}
StringFile(const char *fname, uint32 baseOfs = 0, uint32 endO = 0) :
_filename(fname), _baseOffset(baseOfs), _endOffset(endO) {
}
};
struct GameHeader {
uint16 magic;
uint16 room_desc_table;
uint16 room_direction_table[NR_DIRECTIONS];
uint16 room_flags_table;
uint16 room_graphics_table;
size_t nr_items;
uint16 addr_item_locations;
uint16 addr_item_flags;
uint16 addr_item_word;
uint16 addr_item_strings;
uint16 addr_item_graphics;
uint16 addr_dictionary;
uint16 addr_word_map;
uint16 addr_word_map_target;
uint16 addr_strings;
uint16 addr_strings_end;
uint16 addr_actions[7];
uint16 addr_vm; // FIXME - functions
GameHeader() {
clear();
}
void clear();
};
typedef Common::Array<Action> ActionTable;
class GameData {
private:
uint16 _magicWord;
protected:
Common::String _gameDataFile;
Common::Array<StringFile> _stringFiles;
Common::StringArray _locationGraphicFiles;
Common::StringArray _itemGraphicFiles;
Common::String _titleGraphicFile;
uint _colorTable;
public:
GameHeader _header;
uint _comprehendVersion;
Common::Array<Room> _rooms;
uint8 _currentRoom;
uint8 _startRoom;
uint8 _itemCount;
uint8 _totalInventoryWeight;
Common::Array<Item> _items;
Common::Array<Word> _words;
StringTable _strings;
StringTable _strings2;
bool _flags[MAX_FLAGS];
uint16 _variables[MAX_VARIABLES];
uint8 _currentReplaceWord;
uint8 _wordFlags;
uint _updateFlags;
Common::Array<WordMap> _wordMaps;
Common::Array<ActionTable> _actions;
Common::Array<Function> _functions;
Common::StringArray _replaceWords;
private:
size_t opcode_nr_operands(uint8 opcode) const {
// Number of operands is encoded in the low 2 bits
return opcode & 0x3;
}
bool opcode_is_command(uint8 opcode) const {
/* If the MSB is set the instruction is a command */
return opcode & 0x80;
}
void load_extra_string_files();
void load_extra_string_file(const StringFile &stringFile);
void parse_header_le16(FileBuffer *fb, uint16 *val);
uint8 parse_vm_instruction(FileBuffer *fb, Instruction *instr);
void parse_function(FileBuffer *fb, Function *func);
void parse_vm(FileBuffer *fb);
void parse_action_tables(FileBuffer *fb);
void parse_dictionary(FileBuffer *fb);
void parse_word_map(FileBuffer *fb);
void parse_items(FileBuffer *fb);
void parse_rooms(FileBuffer *fb);
uint64 string_get_chunk(uint8 *string);
char decode_string_elem(uint8 c, bool capital, bool special);
void parse_string_table(FileBuffer *fb, uint start_addr,
uint32 end_addr, StringTable *table);
void parse_variables(FileBuffer *fb);
void parse_flags(FileBuffer *fb);
void parse_replace_words(FileBuffer *fb);
void loadGameData();
protected:
/**
* Game strings are stored using 5-bit characters. By default a character
* value maps to the lower-case letter table. If a character has the value 0x1e
* then the next character is upper-case. An upper-case space is used to
* specify that the character should be replaced at runtime (like a '%s'
* specifier). If a character has the value 0x1f then the next character is
* taken from the symbols table.
*/
Common::String parseString(FileBuffer *fb);
/**
* The main game data file header has the offsets for where each bit of
* game data is. The offsets have a magic constant value added to them.
*/
virtual void parse_header(FileBuffer *fb);
public:
GameData() {
clearGame();
}
virtual ~GameData() {
clearGame();
}
void clearGame();
void loadGame();
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,412 @@
/* 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/comprehend/game_oo.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/draw_surface.h"
#include "glk/comprehend/pics.h"
#include "common/md5.h"
namespace Glk {
namespace Comprehend {
enum OOToposRoomFlag {
OO_ROOM_IN_SHIP = 1,
OO_ROOM_FLAG_DARK = 2
};
enum OOToposFlag {
OO_FLAG_9 = 9,
OO_FLAG_13 = 13,
OO_FLAG_22 = 22,
OO_BRIGHT_ROOM = 25,
OO_FLAG_WEARING_GOGGLES = 27,
OO_FLAG_FLASHLIGHT_ON = 39,
OO_FLAG_43 = 43,
OO_FLAG_44 = 44,
OO_FLAG_SUFFICIENT_FUEL = 51,
OO_FLAG_REVERSE_VIDEO = 53, // Effect of wearing goggles
OO_FLAG_TOO_DARK = 55,
OO_FLAG_TOO_BRIGHT = 56,
OO_FLAG_58 = 58,
OO_FLAG_59 = 59,
OO_FLAG_READY_TO_DEPART = 60,
OO_TRACTOR_BEAM = 71
};
enum OOToposItem {
ITEM_SERUM_VIAL = 39
};
static const GameStrings OO_STRINGS = {
EXTRA_STRING_TABLE(154)
};
OOToposGame::OOToposGame() : ComprehendGameV2(), _restartMode(RESTART_IMMEDIATE),
_noFloodfill(UNSET), _stringVal1(0), _stringVal2(0),
_printComputerMsg(true), _shipNotWorking(false) {
_gameDataFile = "g0";
// Extra strings are (annoyingly) stored in the game binary
Common::File f;
if (!f.open("novel.exe"))
error("novel.exe is a required file");
Common::String md5 = Common::computeStreamMD5AsString(f, 1024);
f.close();
if (md5 == "3fc2072f6996b17d2f21f0a92e53cdcc") {
// DOS version from if-archive
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x16564, 0x17640));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x17702, 0x18600));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x186b2, 0x19b80));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x19c62, 0x1a590));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x1a634, 0x1b080));
} else if (md5 == "e26858f2aaa9dcc28f468b07902813c5") {
// DOS version from graphicsmagician.com
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x164c4, 0x175a0));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x17662, 0x18560));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x18612, 0x19ae0));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x19bc2, 0x1a4f0));
_stringFiles.push_back(StringFile("NOVEL.EXE", 0x1a594, 0x1afe0));
} else {
error("Unrecognised novel.exe encountered");
}
_locationGraphicFiles.push_back("RA");
_locationGraphicFiles.push_back("RB");
_locationGraphicFiles.push_back("RC");
_locationGraphicFiles.push_back("RD");
_locationGraphicFiles.push_back("RE");
_itemGraphicFiles.push_back("OA");
_itemGraphicFiles.push_back("OB");
_itemGraphicFiles.push_back("OC");
_itemGraphicFiles.push_back("OD");
_colorTable = 1;
_gameStrings = &OO_STRINGS;
_titleGraphicFile = "t0";
}
void OOToposGame::beforeGame() {
// Draw the title
g_comprehend->drawPicture(TITLE_IMAGE);
// Print game information
console_println("Story by Michael and Muffy Berlyn, graphics by Raim und Redlich and Brian Poff");
console_println("IBM version by Jeffrey A. Jay. Copyright 1987 POLARWARE, Inc.");
g_comprehend->readChar();
g_comprehend->glk_window_clear(g_comprehend->_bottomWindow);
}
int OOToposGame::roomIsSpecial(uint room_index, uint *roomDescString) {
Room *room = &_rooms[room_index];
// Is the room dark
if ((room->_flags & OO_ROOM_FLAG_DARK) &&
!(_flags[OO_FLAG_FLASHLIGHT_ON])) {
if (roomDescString)
*roomDescString = 0xb3;
return ROOM_IS_DARK;
}
// Is the room too bright
if (room_index == OO_BRIGHT_ROOM &&
!_flags[OO_FLAG_WEARING_GOGGLES]) {
if (roomDescString)
*roomDescString = 0x1c;
return ROOM_IS_TOO_BRIGHT;
}
return ROOM_IS_NORMAL;
}
void OOToposGame::beforeTurn() {
ComprehendGameV2::beforeTurn();
if (_flags[OO_FLAG_TOO_DARK]) {
// Show placeholder room if room is too dark
_currentRoom = 55;
_updateFlags |= UPDATE_GRAPHICS;
} else if (_flags[OO_FLAG_TOO_BRIGHT]) {
// Show placeholder room if room is too bright
_currentRoom = 54;
_updateFlags |= UPDATE_GRAPHICS;
} else {
YesNo nff = _flags[OO_FLAG_REVERSE_VIDEO] ? YES : NO;
if (_noFloodfill != nff) {
_noFloodfill = nff;
_updateFlags |= UPDATE_GRAPHICS | UPDATE_ROOM_DESC;
if (_noFloodfill == YES)
g_comprehend->_drawFlags |= IMAGEF_REVERSE;
else
g_comprehend->_drawFlags &= ~IMAGEF_REVERSE;
}
}
}
void OOToposGame::beforePrompt() {
// Handle the computer console if in front of it
computerConsole();
}
void OOToposGame::afterPrompt() {
ComprehendGameV2::afterPrompt();
// WORKAROUND: Allow for the Apple 2 password in the DOS version
if (!scumm_stricmp(_inputLine, "vug957a"))
Common::strcpy_s(_inputLine, "tse957x");
if (_currentRoom != _currentRoomCopy)
_updateFlags |= UPDATE_GRAPHICS;
_currentRoom = _currentRoomCopy;
}
void OOToposGame::handleSpecialOpcode() {
switch (_specialOpcode) {
case 1:
// Update guard location
randomizeGuardLocation();
break;
case 2:
_restartMode = RESTART_IMMEDIATE;
game_restart();
break;
case 3:
_restartMode = RESTART_WITH_MSG;
game_restart();
break;
case 4:
_restartMode = RESTART_WITHOUT_MSG;
game_restart();
break;
case 5:
// Won the game
g_comprehend->quitGame();
break;
case 6:
// Save game
game_save();
break;
case 7:
// Restore game
game_restore();
break;
case 8:
// Computer response
computerResponse();
randomizeGuardLocation();
break;
case 9:
// Checks the ship fuel
checkShipFuel();
randomizeGuardLocation();
break;
case 10:
// Checks whether the ship is working
checkShipWorking();
break;
default:
break;
}
}
bool OOToposGame::handle_restart() {
_ended = false;
if (_restartMode != RESTART_IMMEDIATE) {
if (_restartMode == RESTART_WITH_MSG)
console_println(stringLookup(_gameStrings->game_restart).c_str());
if (tolower(console_get_key()) != 'r') {
g_comprehend->quitGame();
return false;
}
}
loadGame();
_updateFlags = UPDATE_ALL;
return true;
}
void OOToposGame::synchronizeSave(Common::Serializer &s) {
if (s.isSaving())
_currentRoom = _currentRoomCopy;
ComprehendGameV2::synchronizeSave(s);
if (s.isLoading()) {
_noFloodfill = UNSET;
_currentRoomCopy = _currentRoom;
beforeTurn();
}
}
void OOToposGame::randomizeGuardLocation() {
Item *item = get_item(22);
if (_flags[OO_FLAG_13] && item->_room != _currentRoom) {
if (getRandomNumber(255) > 128 && (_currentRoom == 3 || _currentRoom == 6))
item->_room = _currentRoom;
}
}
void OOToposGame::computerConsole() {
if (_currentRoom == 57) {
if (!_flags[OO_FLAG_9]) {
// Mission Code:
console_println(_strings2[129].c_str());
} else if (!_flags[OO_FLAG_58]) {
// Welcome back! I was wondering if you would be returning
console_println(_strings2[131].c_str());
_flags[OO_FLAG_58] = true;
_printComputerMsg = true;
checkShipWorking();
} else if (_flags[OO_FLAG_59]) {
checkShipDepart();
} else if (_flags[OO_FLAG_43]) {
// We can reach Mealy Sukas with the fuel we have left
console_println(_strings2[142].c_str());
_flags[OO_FLAG_59] = true;
if (_flags[OO_FLAG_44])
// The currency on Mealy Sukas is the 'frod'
console_println(_strings2[144].c_str());
else
// Without evaluation data as to the current fuel prices
console_println(_strings2[143].c_str());
}
}
}
void OOToposGame::computerResponse() {
console_println(_strings2[145].c_str());
if (_flags[OO_FLAG_43])
console_println(_strings2[144].c_str());
else
console_println(_strings2[152].c_str());
}
void OOToposGame::checkShipWorking() {
_stringVal1 = 164;
_stringVal2 = 0;
// Iterate through the ship's flags
for (int idx = 42; idx < 51; ++idx, ++_stringVal1) {
if (!_flags[idx]) {
if (!_stringVal2) {
// The following components are not installed
printComputerMsg(_strings2[132].c_str());
_stringVal2 = 1;
}
// Power Cylinder
printComputerMsg(_strings[_stringVal1].c_str());
}
}
_shipNotWorking = _stringVal2 != 0;
if (!_shipNotWorking)
// The ship is in working order
printComputerMsg(_strings2[153].c_str());
}
void OOToposGame::checkShipFuel() {
const byte ITEMS[7] = { 24, 27, 28, 29, 30, 31, 32 };
_variables[0x4b] = 0;
_stringVal1 = 68;
_stringVal2 = 0;
for (int idx = 168; idx < 175; ++idx, ++_stringVal1, ++_stringVal2) {
if (_flags[idx]) {
Item *item = get_item(ITEMS[_stringVal2] - 1);
if (item->_room == ROOM_INVENTORY || (get_room(item->_room)->_flags & OO_ROOM_IN_SHIP) != 0) {
Instruction varAdd(0x86, 0x4B, _stringVal1);
execute_opcode(&varAdd, nullptr, nullptr);
}
}
}
// Computer: "Our current evaluation...
Instruction strReplace(0xC9, 0x4B);
execute_opcode(&strReplace, nullptr, nullptr);
printComputerMsg(_strings2[146].c_str());
FunctionState funcState;
Instruction test(2, 75, 76);
execute_opcode(&test, nullptr, nullptr);
if (funcState._testResult) {
// Computer: "We should now have enough
_flags[OO_FLAG_SUFFICIENT_FUEL] = true;
printComputerMsg(_strings2[151].c_str());
} else {
_flags[OO_FLAG_SUFFICIENT_FUEL] = false;
}
}
void OOToposGame::checkShipDepart() {
_printComputerMsg = false;
checkShipWorking();
checkShipFuel();
_printComputerMsg = true;
if (!_shipNotWorking && _flags[OO_FLAG_SUFFICIENT_FUEL]) {
Item *item = get_item(ITEM_SERUM_VIAL - 1);
if (item->_room == ROOM_INVENTORY || (get_room(item->_room)->_flags & OO_ROOM_IN_SHIP) != 0) {
if (!_flags[OO_TRACTOR_BEAM]) {
// I detect a tractor beam
console_println(_strings2[77].c_str());
} else if (!_flags[OO_FLAG_READY_TO_DEPART]) {
// All systems check. Ready to depart
_flags[OO_FLAG_22] = true;
console_println(_strings2[79].c_str());
} else {
// Please close the airlock
console_println(_strings2[76].c_str());
}
} else {
// The serum vial is not aboard the ship
console_println(_strings2[78].c_str());
}
}
}
void OOToposGame::printComputerMsg(const char *str) {
if (_printComputerMsg)
console_println(str);
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,93 @@
/* 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_COMPREHEND_GAME_OO_H
#define GLK_COMPREHEND_GAME_OO_H
#include "glk/comprehend/game_opcodes.h"
namespace Glk {
namespace Comprehend {
enum RestartMode { RESTART_IMMEDIATE, RESTART_WITH_MSG, RESTART_WITHOUT_MSG };
enum YesNo { NO, YES, UNSET };
class OOToposGame : public ComprehendGameV2 {
private:
RestartMode _restartMode;
YesNo _noFloodfill;
int _stringVal1, _stringVal2;
bool _printComputerMsg, _shipNotWorking;
/**
* Randomizes a guard to different locations
*/
void randomizeGuardLocation();
/**
* Handles the computer console
*/
void computerConsole();
/**
* Handles displaying a computer response
*/
void computerResponse();
/**
* Checks whether the ship is in working order
*/
void checkShipWorking();
/**
* Tests if the player has enough to purchase needed ship fuel
*/
void checkShipFuel();
/**
* Checks whether the ship can depart, printing out the computer's response
*/
void checkShipDepart();
/**
* A wrapped version of console_println that only prints the passed string
* if the _addStringFlag is set
*/
void printComputerMsg(const char *str);
public:
OOToposGame();
~OOToposGame() override {}
void beforeGame() override;
void beforeTurn() override;
void beforePrompt() override;
void afterPrompt() override;
int roomIsSpecial(uint room_index, uint *room_desc_string) override;
void handleSpecialOpcode() override;
bool handle_restart() override;
void synchronizeSave(Common::Serializer &s) override;
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,871 @@
/* 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
* 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; write to the Free Software
* Foundation; Inc.; 51 Franklin Street; Fifth Floor; Boston; MA 02110-1301; USA.
*
*/
#include "glk/comprehend/game_opcodes.h"
#include "glk/comprehend/game_data.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/debugger.h"
#include "common/algorithm.h"
#include "common/textconsole.h"
namespace Glk {
namespace Comprehend {
ComprehendGameOpcodes::ComprehendGameOpcodes() {
Common::fill(&_opcodeMap[0], &_opcodeMap[0x100], OPCODE_UNKNOWN);
}
void ComprehendGameOpcodes::execute_opcode(const Instruction *instr, const Sentence *sentence,
FunctionState *func_state) {
byte verb = sentence ? sentence->_formattedWords[0] : 0;
byte noun = sentence ? sentence->_formattedWords[2] : 0;
Room *room = get_room(_currentRoom);
Item *item;
uint index;
byte opcode = getOpcode(instr);
switch (_opcodeMap[opcode]) {
case OPCODE_CALL_FUNC:
case OPCODE_CALL_FUNC2:
// Note: CALL_FUNC2 in the original did some extra backing of data which is
// redundant in the ScummVM version, so it can be handled the same as CALL_FUNC.
index = instr->_operand[0];
if (instr->_operand[1] == 0x81)
index += 256;
if (index >= _functions.size())
error("Bad function %.4x >= %.4x\n", index, _functions.size());
eval_function(index, sentence);
break;
case OPCODE_CLEAR_CAN_TAKE:
item = get_item_by_noun(noun);
item->_flags &= ~ITEMF_CAN_TAKE;
break;
case OPCODE_CLEAR_FLAG:
_flags[instr->_operand[0]] = false;
break;
case OPCODE_CLEAR_FLAG40:
item = getItem(instr);
item->_flags &= ~ITEMF_UNKNOWN;
break;
case OPCODE_CLEAR_INVISIBLE:
item = get_item_by_noun(noun);
item->_flags &= ~ITEMF_INVISIBLE;
break;
case OPCODE_CLEAR_WORD:
item = getItem(instr);
item->_word = 0;
break;
case OPCODE_CURRENT_OBJECT_NOT_VALID:
func_set_test_result(func_state, !noun);
break;
case OPCODE_CURRENT_IS_OBJECT:
item = get_item_by_noun(noun);
func_set_test_result(func_state, item != nullptr);
break;
case OPCODE_ELSE:
func_state->_testResult = func_state->_elseResult;
break;
case OPCODE_HAVE_CURRENT_OBJECT:
item = get_item_by_noun(noun);
func_set_test_result(func_state, item->_room == ROOM_INVENTORY);
break;
case OPCODE_HAVE_OBJECT:
item = getItem(instr);
func_set_test_result(func_state, item->_room == ROOM_INVENTORY);
break;
case OPCODE_IN_ROOM:
func_set_test_result(func_state, _currentRoom == instr->_operand[0]);
break;
case OPCODE_INVENTORY: {
uint count = num_objects_in_room(ROOM_INVENTORY);
if (count == 0) {
console_println(stringLookup(STRING_INVENTORY_EMPTY).c_str());
break;
}
console_println(stringLookup(STRING_INVENTORY).c_str());
for (uint i = 0; i < _items.size(); i++) {
item = &_items[i];
if (item->_room == ROOM_INVENTORY)
g_comprehend->print("%s\n",
stringLookup(item->_stringDesc).c_str());
}
break;
}
case OPCODE_MOVE_DEFAULT:
// Move in the direction dictated by the current verb
if (verb - 1 >= NR_DIRECTIONS)
error("Bad verb %d in move", verb);
if (room->_direction[verb - 1])
move_to(room->_direction[verb - 1]);
else
console_println(stringLookup(STRING_CANT_GO).c_str());
break;
case OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM:
item = getItem(instr);
move_object(item, _currentRoom);
break;
case OPCODE_MOVE_OBJECT_TO_ROOM:
item = getItem(instr);
move_object(item, instr->_operand[1]);
break;
case OPCODE_MOVE_TO_ROOM:
if (instr->_operand[0] != 0xff)
move_to(instr->_operand[0]);
break;
case OPCODE_OBJECT_IN_ROOM:
item = getItem(instr);
func_set_test_result(func_state, item->_room == instr->_operand[1]);
break;
case OPCODE_OBJECT_IS_NOWHERE:
item = getItem(instr);
func_set_test_result(func_state, item->_room == ROOM_NOWHERE);
break;
case OPCODE_OBJECT_PRESENT:
item = getItem(instr);
func_set_test_result(func_state, item->_room == _currentRoom);
break;
case OPCODE_OR:
if (func_state->_orCount) {
func_state->_orCount += 2;
} else {
func_state->_testResult = false;
func_state->_orCount += 3;
}
break;
case OPCODE_PRINT:
console_println(instrStringLookup(instr->_operand[0], instr->_operand[1]).c_str());
break;
case OPCODE_RANDOM_MSG: {
int msgId = (instr->_operand[2] << 8 | instr->_operand[1]) +
getRandomNumber(instr->_operand[0] - 1);
console_println(stringLookup(msgId).c_str());
break;
}
case OPCODE_REMOVE_OBJECT:
item = getItem(instr);
move_object(item, ROOM_NOWHERE);
break;
case OPCODE_SAVE_ACTION:
// Causes the next sentence inputed to re-use the first word of the current one.
// As far as I'm aware, this is only used for handling responses to questions
_nounState = NOUNSTATE_QUERY;
// fall-through
case OPCODE_CLEAR_LINE:
// Resets the input line, removing any pending further actions that were specified
Common::fill(&_inputLine[0], &_inputLine[INPUT_LINE_SIZE], 0);
_inputLineIndex = 0;
break;
case OPCODE_SET_CAN_TAKE:
item = get_item_by_noun(noun);
item->_flags |= ITEMF_CAN_TAKE;
break;
case OPCODE_SET_FLAG:
_flags[instr->_operand[0]] = true;
break;
case OPCODE_SET_FLAG40:
item = getItem(instr);
item->_flags |= ITEMF_UNKNOWN;
break;
case OPCODE_SET_INVISIBLE:
item = get_item_by_noun(noun);
item->_flags |= ITEMF_INVISIBLE;
break;
case OPCODE_SET_OBJECT_DESCRIPTION:
item = getItem(instr);
item->_stringDesc = (instr->_operand[2] << 8) | instr->_operand[1];
break;
case OPCODE_SET_ROOM_DESCRIPTION:
room = get_room(instr->_operand[0]);
switch (instr->_operand[2]) {
case 0x80:
room->_stringDesc = instr->_operand[1];
break;
case 0x81:
room->_stringDesc = instr->_operand[1] + 0x100;
break;
case 0x82:
room->_stringDesc = instr->_operand[1] + 0x200;
break;
default:
error("Bad string desc %.2x:%.2x\n", instr->_operand[1], instr->_operand[2]);
break;
}
break;
case OPCODE_SET_ROOM_GRAPHIC:
room = get_room(instr->_operand[0]);
room->_graphic = instr->_operand[1];
if (instr->_operand[0] == _currentRoom)
_updateFlags |= UPDATE_GRAPHICS;
break;
case OPCODE_SET_STRING_REPLACEMENT1:
_currentReplaceWord = (instr->_operand[0] & 0x80) - 1;
break;
case OPCODE_SET_STRING_REPLACEMENT2:
_currentReplaceWord = instr->_operand[0] - 1;
break;
case OPCODE_SET_WORD:
item = getItem(instr);
item->_word = instr->_operand[1];
break;
case OPCODE_SPECIAL:
// Game specific opcode
_specialOpcode = instr->_operand[0];
break;
case OPCODE_TAKE_CURRENT_OBJECT:
item = get_item_by_noun(noun);
if (!item)
error("Attempt to take object failed\n");
move_object(item, ROOM_INVENTORY);
break;
case OPCODE_TAKE_OBJECT:
item = getItem(instr);
move_object(item, ROOM_INVENTORY);
break;
case OPCODE_TEST_FLAG:
func_set_test_result(func_state, _flags[instr->_operand[0]]);
break;
case OPCODE_TEST_ROOM_FLAG:
func_set_test_result(func_state, room->_flags & instr->_operand[0]);
break;
case OPCODE_TURN_TICK:
_variables[VAR_TURN_COUNT]++;
break;
case OPCODE_VAR_ADD:
_variables[instr->_operand[0]] += _variables[instr->_operand[1]];
break;
case OPCODE_VAR_DEC:
_variables[instr->_operand[0]]--;
break;
case OPCODE_VAR_EQ2:
func_set_test_result(func_state,
_variables[instr->_operand[0]] == _variables[instr->_operand[1]]);
break;
case OPCODE_VAR_GT1:
func_set_test_result(func_state,
_variables[0] >
_variables[instr->_operand[0]]);
break;
case OPCODE_VAR_GT2:
func_set_test_result(func_state, _variables[instr->_operand[0]] >
_variables[instr->_operand[1]]);
break;
case OPCODE_VAR_GTE1:
func_set_test_result(func_state,
_variables[0] >=
_variables[instr->_operand[0]]);
break;
case OPCODE_VAR_GTE2:
func_set_test_result(func_state,
_variables[instr->_operand[0]] >=
_variables[instr->_operand[1]]);
break;
case OPCODE_VAR_EQ1:
func_set_test_result(func_state,
_variables[0] ==
_variables[instr->_operand[0]]);
break;
case OPCODE_VAR_INC:
_variables[instr->_operand[0]]++;
break;
case OPCODE_VAR_SUB:
_variables[instr->_operand[0]] -= _variables[instr->_operand[1]];
break;
default:
if (instr->_opcode & 0x80) {
warning("Unhandled command opcode %.2x", opcode);
} else {
warning("Unhandled test opcode %.2x - returning false", opcode);
func_set_test_result(func_state, false);
}
break;
}
}
void ComprehendGameOpcodes::func_set_test_result(FunctionState *func_state, bool value) {
if (func_state->_orCount == 0) {
/* And */
if (func_state->_and) {
if (!value)
func_state->_testResult = false;
} else {
func_state->_testResult = value;
func_state->_and = true;
}
} else {
/* Or */
if (value)
func_state->_testResult = value;
}
}
bool ComprehendGameOpcodes::isItemPresent(Item *item) const {
return item && (
item->_room == _currentRoom || item->_room == ROOM_INVENTORY
|| item->_room == ROOM_CONTAINER
);
}
Item *ComprehendGameOpcodes::getItem(const Instruction *instr) {
return get_item(instr->_operand[0] - 1);
}
/*-------------------------------------------------------*/
ComprehendGameV1::ComprehendGameV1() {
_opcodeMap[0x01] = OPCODE_HAVE_OBJECT;
_opcodeMap[0x02] = OPCODE_VAR_GT2;
_opcodeMap[0x04] = OPCODE_OR;
_opcodeMap[0x05] = OPCODE_IN_ROOM;
_opcodeMap[0x06] = OPCODE_VAR_EQ2;
_opcodeMap[0x08] = OPCODE_CURRENT_IS_OBJECT;
_opcodeMap[0x09] = OPCODE_OBJECT_PRESENT;
_opcodeMap[0x0a] = OPCODE_VAR_GTE2;
_opcodeMap[0x0c] = OPCODE_ELSE;
_opcodeMap[0x0e] = OPCODE_OBJECT_IN_ROOM;
_opcodeMap[0x14] = OPCODE_CURRENT_OBJECT_NOT_VALID;
_opcodeMap[0x18] = OPCODE_INVENTORY_FULL;
_opcodeMap[0x19] = OPCODE_TEST_FLAG;
_opcodeMap[0x1d] = OPCODE_CURRENT_OBJECT_IN_ROOM;
_opcodeMap[0x20] = OPCODE_HAVE_CURRENT_OBJECT;
_opcodeMap[0x21] = OPCODE_OBJECT_IS_NOT_NOWHERE;
_opcodeMap[0x24] = OPCODE_CURRENT_OBJECT_PRESENT;
_opcodeMap[0x25] = OPCODE_VAR_GT1;
_opcodeMap[0x29] = OPCODE_VAR_EQ1;
_opcodeMap[0x2d] = OPCODE_VAR_GTE1;
_opcodeMap[0x31] = OPCODE_TEST_ROOM_FLAG;
_opcodeMap[0x41] = OPCODE_NOT_HAVE_OBJECT;
_opcodeMap[0x45] = OPCODE_NOT_IN_ROOM;
_opcodeMap[0x48] = OPCODE_CURRENT_OBJECT_NOT_PRESENT;
_opcodeMap[0x49] = OPCODE_OBJECT_NOT_IN_ROOM;
_opcodeMap[0x4E] = OPCODE_TEST_FALSE;
_opcodeMap[0x50] = OPCODE_CURRENT_OBJECT_IS_NOWHERE;
_opcodeMap[0x59] = OPCODE_TEST_NOT_FLAG;
_opcodeMap[0x5D] = OPCODE_TEST_FALSE;
_opcodeMap[0x60] = OPCODE_NOT_HAVE_CURRENT_OBJECT;
_opcodeMap[0x61] = OPCODE_OBJECT_IS_NOWHERE;
_opcodeMap[0x64] = OPCODE_CURRENT_OBJECT_NOT_IN_ROOM;
_opcodeMap[0x68] = OPCODE_CURRENT_OBJECT_NOT_TAKEABLE;
_opcodeMap[0x71] = OPCODE_TEST_NOT_ROOM_FLAG;
_opcodeMap[0x80] = OPCODE_INVENTORY;
_opcodeMap[0x81] = OPCODE_TAKE_OBJECT;
_opcodeMap[0x82] = OPCODE_MOVE_OBJECT_TO_ROOM;
_opcodeMap[0x83] = OPCODE_RANDOM_MSG;
_opcodeMap[0x84] = OPCODE_SAVE_ACTION;
_opcodeMap[0x85] = OPCODE_MOVE_TO_ROOM;
_opcodeMap[0x86] = OPCODE_VAR_ADD;
_opcodeMap[0x87] = OPCODE_SET_ROOM_DESCRIPTION;
_opcodeMap[0x88] = OPCODE_CLEAR_LINE;
_opcodeMap[0x89] = OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM;
_opcodeMap[0x8a] = OPCODE_VAR_SUB;
_opcodeMap[0x8b] = OPCODE_SET_OBJECT_DESCRIPTION;
_opcodeMap[0x8c] = OPCODE_MOVE_DEFAULT;
_opcodeMap[0x8d] = OPCODE_SET_CAN_TAKE;
_opcodeMap[0x8e] = OPCODE_PRINT;
_opcodeMap[0x91] = OPCODE_CLEAR_CAN_TAKE;
_opcodeMap[0x95] = OPCODE_REMOVE_OBJECT;
_opcodeMap[0x99] = OPCODE_SET_FLAG;
_opcodeMap[0x92] = OPCODE_CALL_FUNC;
_opcodeMap[0x98] = OPCODE_TURN_TICK;
_opcodeMap[0x9a] = OPCODE_SET_WORD;
_opcodeMap[0x9d] = OPCODE_CLEAR_FLAG;
_opcodeMap[0x9e] = OPCODE_INVENTORY_ROOM;
_opcodeMap[0xa0] = OPCODE_TAKE_CURRENT_OBJECT;
_opcodeMap[0xa1] = OPCODE_SPECIAL;
_opcodeMap[0xa4] = OPCODE_DROP_CURRENT_OBJECT;
_opcodeMap[0xa2] = OPCODE_SET_ROOM_GRAPHIC;
_opcodeMap[0xad] = OPCODE_CLEAR_WORD;
_opcodeMap[0xb0] = OPCODE_REMOVE_CURRENT_OBJECT;
_opcodeMap[0xb1] = OPCODE_MOVE_DIR;
_opcodeMap[0xb5] = OPCODE_SET_STRING_REPLACEMENT1;
_opcodeMap[0xb9] = OPCODE_SET_STRING_REPLACEMENT2;
_opcodeMap[0xbd] = OPCODE_VAR_INC;
_opcodeMap[0xc1] = OPCODE_VAR_DEC;
_opcodeMap[0xc5] = OPCODE_SET_STRING_REPLACEMENT3;
_opcodeMap[0xc9] = OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM;
_opcodeMap[0xcd] = OPCODE_CLEAR_INVISIBLE;
_opcodeMap[0xd1] = OPCODE_SET_INVISIBLE;
_opcodeMap[0xd5] = OPCODE_CLEAR_FLAG40;
_opcodeMap[0xd9] = OPCODE_SET_FLAG40;
}
void ComprehendGameV1::execute_opcode(const Instruction *instr, const Sentence *sentence,
FunctionState *func_state) {
byte noun = sentence ? sentence->_formattedWords[2] : 0;
Room *room = get_room(_currentRoom);
Item *item;
uint count;
switch (_opcodeMap[getOpcode(instr)]) {
case OPCODE_INVENTORY_FULL:
item = get_item_by_noun(noun);
if (g_debugger->_invLimit)
func_set_test_result(func_state, _variables[VAR_INVENTORY_WEIGHT] +
(item->_flags & ITEMF_WEIGHT_MASK) > _variables[VAR_INVENTORY_LIMIT]);
else
// Allow for an unlimited number of items in inventory
func_set_test_result(func_state, false);
break;
case OPCODE_OBJECT_NOT_PRESENT:
item = getItem(instr);
func_set_test_result(func_state, !isItemPresent(item));
break;
case OPCODE_SET_STRING_REPLACEMENT3:
_currentReplaceWord = instr->_operand[0] - 1;
break;
/*--------------------------------------*/
case OPCODE_TEST_NOT_ROOM_FLAG:
func_set_test_result(func_state,
!(room->_flags & instr->_operand[0]));
break;
case OPCODE_NOT_IN_ROOM:
func_set_test_result(func_state,
_currentRoom != instr->_operand[0]);
break;
case OPCODE_OBJECT_NOT_IN_ROOM:
item = getItem(instr);
func_set_test_result(func_state, !item || item->_room != _currentRoom);
break;
case OPCODE_CURRENT_OBJECT_NOT_IN_ROOM:
item = get_item_by_noun(noun);
func_set_test_result(func_state, !item || item->_room != _currentRoom);
break;
case OPCODE_DESCRIBE_CURRENT_OBJECT:
/*
* This opcode is only used in version 2
* FIXME - unsure what the single operand is for.
*/
item = get_item_by_noun(noun);
g_comprehend->print("%s\n", stringLookup(item->_longString).c_str());
break;
case OPCODE_CURRENT_OBJECT_IN_ROOM: {
/* FIXME - use common code for these two ops */
bool test = false;
if (noun) {
for (uint i = 0; i < _items.size(); i++) {
Item *itemP = &_items[i];
if (itemP->_word == noun && itemP->_room == instr->_operand[0]) {
test = true;
break;
}
}
}
func_set_test_result(func_state, test);
break;
}
case OPCODE_CURRENT_OBJECT_PRESENT:
item = get_item_by_noun(noun);
if (item)
func_set_test_result(func_state,
item->_room == _currentRoom);
else
func_set_test_result(func_state, false);
break;
case OPCODE_NOT_HAVE_CURRENT_OBJECT:
item = get_item_by_noun(noun);
func_set_test_result(func_state,
!item || item->_room != ROOM_INVENTORY);
break;
case OPCODE_NOT_HAVE_OBJECT:
item = getItem(instr);
func_set_test_result(func_state,
item->_room != ROOM_INVENTORY);
break;
case OPCODE_CURRENT_OBJECT_NOT_TAKEABLE:
item = get_item_by_noun(noun);
if (!item)
func_set_test_result(func_state, true);
else
func_set_test_result(func_state,
!(item->_flags & ITEMF_CAN_TAKE));
break;
case OPCODE_CURRENT_OBJECT_IS_NOWHERE:
item = get_item_by_noun(noun);
func_set_test_result(func_state, item && item->_room == ROOM_NOWHERE);
break;
case OPCODE_OBJECT_IS_NOT_NOWHERE:
item = getItem(instr);
func_set_test_result(func_state, item->_room != ROOM_NOWHERE);
break;
case OPCODE_CURRENT_OBJECT_NOT_PRESENT:
item = get_item_by_noun(noun);
func_set_test_result(func_state, !isItemPresent(item));
break;
case OPCODE_REMOVE_CURRENT_OBJECT:
item = get_item_by_noun(noun);
move_object(item, ROOM_NOWHERE);
break;
case OPCODE_INVENTORY_ROOM:
count = num_objects_in_room(instr->_operand[0]);
if (count == 0) {
console_println(stringLookup(instr->_operand[1] + 1).c_str());
break;
}
console_println(stringLookup(instr->_operand[1]).c_str());
for (uint i = 0; i < _items.size(); i++) {
item = &_items[i];
if (item->_room == instr->_operand[0])
g_comprehend->print("%s\n",
stringLookup(item->_stringDesc).c_str());
}
break;
case OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM:
item = get_item_by_noun(noun);
if (!item)
error("Bad current object\n");
move_object(item, instr->_operand[0]);
break;
case OPCODE_DROP_OBJECT:
item = getItem(instr);
move_object(item, _currentRoom);
break;
case OPCODE_DROP_CURRENT_OBJECT:
item = get_item_by_noun(noun);
if (!item)
error("Attempt to take object failed\n");
move_object(item, _currentRoom);
break;
case OPCODE_TEST_NOT_FLAG:
func_set_test_result(func_state,
!_flags[instr->_operand[0]]);
break;
case OPCODE_TEST_FALSE:
// The original had two opcodes mapped to the same code that does
// a test, but ignores the result, and is always false
func_set_test_result(func_state, false);
break;
case OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT:
#if 1
error("TODO: OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT");
#else
/*
* FIXME - Not sure what the operand is for,
* maybe capitalisation?
*/
if (noun && (noun->_type & WORD_TYPE_NOUN_PLURAL))
_currentReplaceWord = 3;
else if (noun && (noun->_type & WORD_TYPE_FEMALE))
_currentReplaceWord = 0;
else if (noun && (noun->_type & WORD_TYPE_MALE))
_currentReplaceWord = 1;
else
_currentReplaceWord = 2;
#endif
break;
case OPCODE_MOVE_DIR:
doMovementVerb(instr->_operand[0]);
break;
default:
ComprehendGameOpcodes::execute_opcode(instr, sentence, func_state);
break;
}
}
/*-------------------------------------------------------*/
ComprehendGameV2::ComprehendGameV2() {
_opcodeMap[0x01] = OPCODE_HAVE_OBJECT;
_opcodeMap[0x02] = OPCODE_VAR_GT2;
_opcodeMap[0x04] = OPCODE_OR;
_opcodeMap[0x05] = OPCODE_IN_ROOM;
_opcodeMap[0x06] = OPCODE_VAR_EQ2;
_opcodeMap[0x08] = OPCODE_CURRENT_IS_OBJECT;
_opcodeMap[0x09] = OPCODE_VAR_GT1;
_opcodeMap[0x0a] = OPCODE_VAR_GTE2;
_opcodeMap[0x0c] = OPCODE_ELSE;
_opcodeMap[0x0d] = OPCODE_VAR_EQ1;
_opcodeMap[0x11] = OPCODE_OBJECT_IS_NOWHERE;
_opcodeMap[0x14] = OPCODE_CURRENT_OBJECT_NOT_VALID;
_opcodeMap[0x15] = OPCODE_INVENTORY_FULL_X;
_opcodeMap[0x19] = OPCODE_TEST_FLAG;
_opcodeMap[0x1d] = OPCODE_TEST_ROOM_FLAG;
_opcodeMap[0x20] = OPCODE_HAVE_CURRENT_OBJECT;
_opcodeMap[0x21] = OPCODE_OBJECT_PRESENT;
_opcodeMap[0x22] = OPCODE_OBJECT_IN_ROOM;
_opcodeMap[0x25] = OPCODE_OBJECT_TAKEABLE;
_opcodeMap[0x29] = OPCODE_INVENTORY_FULL;
_opcodeMap[0x2d] = OPCODE_OBJECT_CAN_TAKE;
_opcodeMap[0x80] = OPCODE_INVENTORY;
_opcodeMap[0x81] = OPCODE_TAKE_OBJECT;
_opcodeMap[0x83] = OPCODE_RANDOM_MSG;
_opcodeMap[0x84] = OPCODE_SAVE_ACTION;
_opcodeMap[0x85] = OPCODE_MOVE_TO_ROOM;
_opcodeMap[0x86] = OPCODE_VAR_ADD;
_opcodeMap[0x87] = OPCODE_SET_ROOM_DESCRIPTION;
_opcodeMap[0x88] = OPCODE_CLEAR_LINE;
_opcodeMap[0x89] = OPCODE_SPECIAL;
_opcodeMap[0x8a] = OPCODE_VAR_SUB;
_opcodeMap[0x8b] = OPCODE_SET_OBJECT_DESCRIPTION;
_opcodeMap[0x8c] = OPCODE_MOVE_DEFAULT;
_opcodeMap[0x8e] = OPCODE_PRINT;
_opcodeMap[0x8f] = OPCODE_SET_OBJECT_LONG_DESCRIPTION;
_opcodeMap[0x90] = OPCODE_WAIT_KEY;
_opcodeMap[0x92] = OPCODE_CALL_FUNC;
_opcodeMap[0x95] = OPCODE_CLEAR_WORD;
_opcodeMap[0x96] = OPCODE_CALL_FUNC2;
_opcodeMap[0x98] = OPCODE_TURN_TICK;
_opcodeMap[0x99] = OPCODE_SET_FLAG;
_opcodeMap[0x9a] = OPCODE_SET_WORD;
_opcodeMap[0x9d] = OPCODE_CLEAR_FLAG;
_opcodeMap[0xa0] = OPCODE_TAKE_CURRENT_OBJECT;
_opcodeMap[0xa1] = OPCODE_CLEAR_FLAG40;
_opcodeMap[0xa2] = OPCODE_MOVE_OBJECT_TO_ROOM;
_opcodeMap[0xa5] = OPCODE_SET_FLAG40;
_opcodeMap[0xa9] = OPCODE_CLEAR_INVISIBLE;
_opcodeMap[0xad] = OPCODE_SET_INVISIBLE;
_opcodeMap[0xc1] = OPCODE_VAR_DEC;
_opcodeMap[0xc2] = OPCODE_SET_ROOM_GRAPHIC;
_opcodeMap[0xc5] = OPCODE_SET_STRING_REPLACEMENT3;
_opcodeMap[0xc9] = OPCODE_SET_STRING_REPLACEMENT1;
_opcodeMap[0xcd] = OPCODE_SET_STRING_REPLACEMENT2;
_opcodeMap[0xd1] = OPCODE_MOVE_DIR;
_opcodeMap[0xd5] = OPCODE_DRAW_ROOM;
_opcodeMap[0xd9] = OPCODE_DRAW_OBJECT;
_opcodeMap[0xdd] = OPCODE_VAR_INC;
_opcodeMap[0xe1] = OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM;
_opcodeMap[0xe5] = OPCODE_SET_CAN_TAKE;
_opcodeMap[0xe9] = OPCODE_CLEAR_CAN_TAKE;
_opcodeMap[0xed] = OPCODE_REMOVE_OBJECT;
#if 0
_opcodeMap[0x9e] = OPCODE_INVENTORY_ROOM;
_opcodeMap[0xc6] = OPCODE_SET_OBJECT_GRAPHIC;
_opcodeMap[0xf0] = OPCODE_DROP_CURRENT_OBJECT;
_opcodeMap[0xfc] = OPCODE_REMOVE_CURRENT_OBJECT;
#endif
}
void ComprehendGameV2::execute_opcode(const Instruction *instr, const Sentence *sentence,
FunctionState *func_state) {
Instruction instrCopy;
byte noun = sentence ? sentence->_formattedWords[2] : 0;
Room *room = get_room(_currentRoom);
Item *item;
// In case a single opcode is being executed outside of a function, use a dummy function state
FunctionState dummyState;
if (!func_state)
func_state = &dummyState;
if ((instr->_opcode & 0x30) == 0x30) {
// First operand comes from entered sentence noun, shifting out existing operands
instrCopy = *instr;
instrCopy._operand[2] = instrCopy._operand[1];
instrCopy._operand[1] = instrCopy._operand[0];
instrCopy._operand[0] = get_item_id(noun) + 1;
instr = &instrCopy;
}
func_state->_notComparison = (instr->_opcode & 0x40) != 0;
switch (_opcodeMap[getOpcode(instr)]) {
case OPCODE_CLEAR_INVISIBLE:
item = get_item_by_noun(noun);
item->_flags &= ~ITEMF_INVISIBLE;
break;
case OPCODE_DRAW_OBJECT:
g_comprehend->drawItemPicture(instr->_operand[0] - 1);
break;
case OPCODE_DRAW_ROOM:
g_comprehend->drawLocationPicture(instr->_operand[0] - 1);
g_comprehend->readChar();
break;
case OPCODE_INVENTORY_FULL:
item = get_item_by_noun(noun);
weighInventory();
func_set_test_result(func_state, _totalInventoryWeight + (item->_flags & ITEMF_WEIGHT_MASK) >
_variables[VAR_INVENTORY_LIMIT]);
break;
case OPCODE_INVENTORY_FULL_X:
item = get_item_by_noun(noun);
weighInventory();
func_set_test_result(func_state, _totalInventoryWeight + (item->_flags & ITEMF_WEIGHT_MASK) >
_variables[instr->_operand[1]]);
break;
case OPCODE_MOVE_DIR:
if (room->_direction[instr->_operand[0] - 1])
move_to(room->_direction[instr->_operand[0] - 1]);
else
console_println(stringLookup(STRING_CANT_GO).c_str());
break;
case OPCODE_OBJECT_TAKEABLE:
// WORKAROUND: Trying to get non-items in OO-Topos
func_set_test_result(func_state, instr->_operand[0]
&& (getItem(instr)->_flags & ITEMF_WEIGHT_MASK) != ITEMF_WEIGHT_MASK);
break;
case OPCODE_OBJECT_CAN_TAKE:
item = getItem(instr);
func_set_test_result(func_state, item->_flags & ITEMF_CAN_TAKE);
break;
case OPCODE_SET_OBJECT_GRAPHIC:
item = getItem(instr);
item->_graphic = instr->_operand[1];
if (item->_room == _currentRoom)
_updateFlags |= UPDATE_GRAPHICS;
break;
case OPCODE_SET_OBJECT_LONG_DESCRIPTION:
item = getItem(instr);
item->_longString = (instr->_operand[2] << 8) | instr->_operand[1];
break;
case OPCODE_SET_STRING_REPLACEMENT3: {
int articleNum, bits = _wordFlags;
for (articleNum = 3; articleNum >= 0; --articleNum, bits <<= 1) {
if (bits >= 0x100)
break;
}
if (articleNum == -1)
articleNum = 2;
_currentReplaceWord = instr->_operand[0] + articleNum - 1;
break;
}
case OPCODE_WAIT_KEY:
console_get_key();
break;
default:
ComprehendGameOpcodes::execute_opcode(instr, sentence, func_state);
break;
}
}
byte ComprehendGameV2::getOpcode(const Instruction *instr) {
// Special pre-processing for opcodes
byte opcode = instr->_opcode;
if (!(opcode & 0x80))
opcode &= 0x3f;
if ((opcode & 0x30) == 0x30) {
opcode = (opcode & ~0x10) + 1;
}
return opcode;
}
void ComprehendGameV2::func_set_test_result(FunctionState *func_state, bool value) {
ComprehendGameOpcodes::func_set_test_result(func_state, value ^ func_state->_notComparison);
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,82 @@
/* 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_COMPREHEND_GAME_OPCODES_H
#define GLK_COMPREHEND_GAME_OPCODES_H
#include "glk/comprehend/game.h"
namespace Glk {
namespace Comprehend {
/**
* Intermediate derived game class that handles script opcodes common to both
* version 1 and version 2 of the engine
*/
class ComprehendGameOpcodes : public ComprehendGame {
protected:
ScriptOpcode _opcodeMap[0x100];
void execute_opcode(const Instruction *instr, const Sentence *sentence, FunctionState *func_state) override;
Item *getItem(const Instruction *instr);
virtual void func_set_test_result(FunctionState *func_state, bool value);
bool isItemPresent(Item *item) const;
public:
ComprehendGameOpcodes();
virtual byte getOpcode(const Instruction *instr) {
return instr->_opcode;
}
ScriptOpcode getScriptOpcode(const Instruction *instr) override {
return _opcodeMap[getOpcode(instr)];
}
};
/**
* Version 1 Comprehend game
*/
class ComprehendGameV1 : public ComprehendGameOpcodes {
protected:
void execute_opcode(const Instruction *instr, const Sentence *sentence, FunctionState *func_state) override;
public:
ComprehendGameV1();
};
/**
* Version 2 Comprehend game
*/
class ComprehendGameV2 : public ComprehendGameOpcodes {
protected:
void execute_opcode(const Instruction *instr, const Sentence *sentence, FunctionState *func_state) override;
public:
ComprehendGameV2();
byte getOpcode(const Instruction *instr) override;
void func_set_test_result(FunctionState *func_state, bool value) override;
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,184 @@
/* 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/comprehend/comprehend.h"
#include "glk/comprehend/game_tm.h"
#include "glk/comprehend/pics.h"
#include "common/md5.h"
namespace Glk {
namespace Comprehend {
TalismanGame::TalismanGame() : ComprehendGameV2() {
_gameDataFile = "G0";
_locationGraphicFiles.push_back("RA");
_locationGraphicFiles.push_back("RB");
_locationGraphicFiles.push_back("RC");
_locationGraphicFiles.push_back("RD");
_locationGraphicFiles.push_back("RE");
_locationGraphicFiles.push_back("RF");
_locationGraphicFiles.push_back("RG");
_itemGraphicFiles.push_back("OA");
_itemGraphicFiles.push_back("OB");
_itemGraphicFiles.push_back("OE");
_itemGraphicFiles.push_back("OF");
_titleGraphicFile = "t0";
}
#define STRINGS_SEGMENT1 0x16490
#define STRINGS_SEGMENT2 0x22fa0
#define BANKS_COUNT 15
#define STRINGS_PER_BANK 64
void TalismanGame::loadStrings() {
int bankOffsets[BANKS_COUNT];
int stringOffsets[STRINGS_PER_BANK + 1];
_strings.clear();
_strings2.clear();
Common::File f;
if (!f.open("novel.exe"))
error("novel.exe is a required file");
Common::String md5 = Common::computeStreamMD5AsString(f, 1024);
if (md5 != "0e7f002971acdb055f439020363512ce" && md5 != "2e18c88ce352ebea3e14177703a0485f")
error("Unrecognised novel.exe encountered");
const int STRING_SEGMENTS[2] = { STRINGS_SEGMENT1, STRINGS_SEGMENT2 };
// TODO: Figure out use of string segment 2
for (int strings = 0; strings < 1; ++strings) {
f.seek(STRING_SEGMENTS[strings]);
for (int bank = 0; bank < BANKS_COUNT; ++bank)
bankOffsets[bank] = f.readUint16LE();
// Iterate through the banks loading the strings
for (int bank = 0; bank < BANKS_COUNT; ++bank) {
if (!bankOffsets[bank])
continue;
f.seek(STRING_SEGMENTS[strings] + bankOffsets[bank]);
for (int strNum = 0; strNum <= STRINGS_PER_BANK; ++strNum)
stringOffsets[strNum] = f.readUint16LE();
for (int strNum = 0; strNum < STRINGS_PER_BANK; ++strNum) {
int size = stringOffsets[strNum + 1] - stringOffsets[strNum];
if (size < 0)
size = 0xfff;
f.seek(STRING_SEGMENTS[strings] + bankOffsets[bank] + stringOffsets[strNum]);
FileBuffer fb(&f, size);
Common::String str = parseString(&fb);
if (bank < 8)
_strings.push_back(str);
else
_strings2.push_back(str);
}
}
}
}
void TalismanGame::playGame() {
loadStrings();
ComprehendGameV2::playGame();
}
void TalismanGame::beforeGame() {
// Draw the title
g_comprehend->drawPicture(TITLE_IMAGE);
// Print game information
console_println("Story by Bruce X.Hoffman. Graphics by Ray Redlich and Brian Poff");
console_println("Project managed and IBM version by Jeffrey A. Jay. "
"Copyright 1987 POLARWARE Inc.");
g_comprehend->readChar();
g_comprehend->glk_window_clear(g_comprehend->_bottomWindow);
}
void TalismanGame::beforeTurn() {
_variables[0x62] = g_vm->getRandomNumber(255);
_functionNum = 17;
handleAction(nullptr);
}
void TalismanGame::beforePrompt() {
_functionNum = 14;
handleAction(nullptr);
}
void TalismanGame::afterPrompt() {
if (_savedAction.empty()) {
_functionNum = 19;
handleAction(nullptr);
if (_redoLine == REDO_NONE && _flags[3])
_redoLine = REDO_PROMPT;
} else {
Common::strcpy_s(_inputLine, _savedAction.c_str());
_savedAction.clear();
}
}
void TalismanGame::handleAction(Sentence *sentence) {
if (_flags[62] && _functionNum != _variables[125]) {
_variables[124] = _functionNum;
_functionNum = _variables[126];
}
ComprehendGameV2::handleAction(sentence);
}
void TalismanGame::handleSpecialOpcode() {
switch (_specialOpcode) {
case 15:
// Switch to text screen mode
if (g_comprehend->isGraphicsEnabled()) {
g_comprehend->toggleGraphics();
updateRoomDesc();
}
_functionNum = 19;
handleAction(nullptr);
_redoLine = REDO_TURN;
break;
case 17:
// Switch to graphics mode
if (!g_comprehend->isGraphicsEnabled())
g_comprehend->toggleGraphics();
_updateFlags |= UPDATE_ALL;
update();
_redoLine = REDO_TURN;
break;
default:
break;
}
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,54 @@
/* 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_COMPREHEND_GAME_TM_H
#define GLK_COMPREHEND_GAME_TM_H
#include "glk/comprehend/game_opcodes.h"
namespace Glk {
namespace Comprehend {
class TalismanGame : public ComprehendGameV2 {
private:
Common::String _savedAction;
private:
/**
* Load strings from the executable
*/
void loadStrings();
public:
TalismanGame();
~TalismanGame() override {}
void playGame() override;
void beforeGame() override;
void beforeTurn() override;
void beforePrompt() override;
void afterPrompt() override;
void handleAction(Sentence *sentence) override;
void handleSpecialOpcode() override;
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,287 @@
/* 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/comprehend/comprehend.h"
#include "glk/comprehend/game_data.h"
#include "glk/comprehend/game_tr1.h"
#include "glk/comprehend/pics.h"
namespace Glk {
namespace Comprehend {
enum RoomId {
ROOM_CLAY_HUT = 7,
ROOM_FIELD = 26
};
enum RoomFlag {
ROOMFLAG_FOREST = 1 << 0,
ROOMFLAG_WEREWOLF = 1 << 6,
ROOMFLAG_VAMPIRE = 1 << 7
};
enum ItemId {
ITEM_GOBLIN = 9,
ITEM_SILVER_BULLET = 21,
ITEM_BLACK_CAT = 23,
ITEM_WEREWOLF = 33,
ITEM_VAMPIRE = 38
};
struct TransylvaniaMonster {
uint8 _object;
uint8 _deadFlag;
uint _roomAllowFlag;
uint _minTurnsBefore;
uint _randomness;
};
const TransylvaniaMonster TransylvaniaGame1::WEREWOLF = {
ITEM_WEREWOLF, 7, ROOMFLAG_WEREWOLF, 10, 190
};
const TransylvaniaMonster TransylvaniaGame1::VAMPIRE = {
ITEM_VAMPIRE, 5, ROOMFLAG_VAMPIRE, 0, 200
};
static const GameStrings TR_STRINGS = {
EXTRA_STRING_TABLE(0x8a)
};
TransylvaniaGame1::TransylvaniaGame1() : ComprehendGameV1(),
_miceReleased(false) {
_gameDataFile = "tr.gda";
_stringFiles.push_back("MA.MS1");
_stringFiles.push_back("MB.MS1");
_stringFiles.push_back("MC.MS1");
_stringFiles.push_back("MD.MS1");
_stringFiles.push_back("ME.MS1");
_locationGraphicFiles.push_back("RA.MS1");
_locationGraphicFiles.push_back("RB.MS1");
_locationGraphicFiles.push_back("RC.MS1");
_itemGraphicFiles.push_back("OA.MS1");
_itemGraphicFiles.push_back("OB.MS1");
_itemGraphicFiles.push_back("OC.MS1");
_titleGraphicFile = "trtitle.ms1";
_gameStrings = &TR_STRINGS;
}
bool TransylvaniaGame1::updateMonster(const TransylvaniaMonster *monsterInfo) {
Item *monster;
Room *room;
uint16 turn_count;
room = &_rooms[_currentRoom];
if (!(room->_flags & monsterInfo->_roomAllowFlag))
return false;
turn_count = _variables[VAR_TURN_COUNT];
monster = get_item(monsterInfo->_object);
if (monster->_room == _currentRoom) {
// The monster is in the current room - leave it there
return true;
}
if (!_flags[monsterInfo->_deadFlag] &&
turn_count > monsterInfo->_minTurnsBefore) {
/*
* The monster is alive and allowed to move to the current
* room. Randomly decide whether on not to. If not, move
* it back to limbo.
*/
if (getRandomNumber(255) > monsterInfo->_randomness) {
move_object(monster, _currentRoom);
_variables[15] = turn_count + 1;
} else {
move_object(monster, ROOM_NOWHERE);
}
}
return true;
}
bool TransylvaniaGame1::isMonsterInRoom(const TransylvaniaMonster *monsterInfo) {
Item *monster = get_item(monsterInfo->_object);
return monster->_room == _currentRoom;
}
int TransylvaniaGame1::roomIsSpecial(uint room_index, uint *roomDescString) {
Room *room = &_rooms[room_index];
if (room_index == 0x28) {
if (roomDescString)
*roomDescString = room->_stringDesc;
return ROOM_IS_DARK;
}
return ROOM_IS_NORMAL;
}
void TransylvaniaGame1::beforeTurn() {
Room *room;
if (!isMonsterInRoom(&WEREWOLF) && !isMonsterInRoom(&VAMPIRE)) {
if (_currentRoom == ROOM_CLAY_HUT) {
Item *blackCat = get_item(ITEM_BLACK_CAT);
if (blackCat->_room == _currentRoom && getRandomNumber(255) >= 128)
console_println(_strings[109].c_str());
goto done;
} else if (_currentRoom == ROOM_FIELD) {
Item *goblin = get_item(ITEM_GOBLIN);
if (goblin->_room == _currentRoom)
console_println(_strings[94 + getRandomNumber(3)].c_str());
goto done;
}
}
if (updateMonster(&WEREWOLF) || updateMonster(&VAMPIRE))
goto done;
room = &_rooms[_currentRoom];
if ((room->_flags & ROOMFLAG_FOREST) && (_variables[VAR_TURN_COUNT] % 255) >= 4
&& getRandomNumber(255) < 40) {
int stringNum = _miceReleased ? 108 : 107;
console_println(_strings[stringNum].c_str());
// Until the mice are released, an eagle moves player to a random room
if (!_miceReleased) {
// Get new room to get moved to
int roomNum = getRandomNumber(3) + 1;
if (roomNum == _currentRoom)
roomNum += 15;
move_to(roomNum);
// Make sure Werwolf and Vampire aren't present
get_item(ITEM_WEREWOLF)->_room = 0xff;
get_item(ITEM_VAMPIRE)->_room = 0xff;
}
}
done:
ComprehendGameV1::beforeTurn();
}
void TransylvaniaGame1::synchronizeSave(Common::Serializer &s) {
ComprehendGame::synchronizeSave(s);
s.syncAsByte(_miceReleased);
// As a post-step, ensure the vampire and werewolf aren't present
get_item(ITEM_WEREWOLF)->_room = 0xff;
get_item(ITEM_VAMPIRE)->_room = 0xff;
}
void TransylvaniaGame1::handleSpecialOpcode() {
switch (_specialOpcode) {
case 1:
// Mice have been released
_miceReleased = true;
break;
case 2:
// Gun is fired. Drop the bullet in a random room
get_item(ITEM_SILVER_BULLET)->_room = getRandomNumber(7) + 1;
_updateFlags |= UPDATE_GRAPHICS;
break;
case 3:
case 4:
// Game over - failure
console_println(_strings2[138].c_str());
game_restart();
break;
case 5:
// Won the game
g_comprehend->showGraphics();
g_comprehend->drawLocationPicture(40);
game_restart();
break;
case 6:
game_save();
break;
case 7:
game_restore();
break;
case 8:
// Restart game
game_restart();
break;
case 9:
// Show the Zin screen in response to doing
// 'sing some enchanted evening' in his cabin.
g_comprehend->showGraphics();
g_comprehend->drawLocationPicture(41);
console_get_key();
_updateFlags |= UPDATE_GRAPHICS;
break;
default:
break;
}
}
#define READ_LINE do { \
g_comprehend->readLine(buffer, sizeof(buffer)); \
if (g_comprehend->shouldQuit()) return; \
} while (strlen(buffer) == 0)
void TransylvaniaGame1::beforeGame() {
char buffer[128];
g_comprehend->setDisableSaves(true);
// Draw the title
g_comprehend->drawPicture(TITLE_IMAGE);
// Print game information
console_println("Story and graphics by Antonio Antiochia.");
console_println("IBM version by Jeffrey A. Jay. Copyright 1987 POLARWARE, Inc.");
g_comprehend->readChar();
// Welcome to Transylvania - sign your name
console_println(_strings[0x20].c_str());
READ_LINE;
// The player's name is stored in word 0
_replaceWords[0] = Common::String(buffer);
// And your next of kin - This isn't stored by the game
console_println(_strings[0x21].c_str());
READ_LINE;
g_comprehend->setDisableSaves(false);
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,54 @@
/* 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_COMPREHEND_GAME_TR1_H
#define GLK_COMPREHEND_GAME_TR1_H
#include "glk/comprehend/game_opcodes.h"
namespace Glk {
namespace Comprehend {
struct TransylvaniaMonster;
class TransylvaniaGame1 : public ComprehendGameV1 {
private:
static const TransylvaniaMonster WEREWOLF;
static const TransylvaniaMonster VAMPIRE;
bool _miceReleased;
bool updateMonster(const TransylvaniaMonster *monsterInfo);
bool isMonsterInRoom(const TransylvaniaMonster *monsterInfo);
public:
TransylvaniaGame1();
~TransylvaniaGame1() override {}
void beforeGame() override;
void beforeTurn() override;
void synchronizeSave(Common::Serializer &s) override;
int roomIsSpecial(uint room_index, uint *roomDescString) override;
void handleSpecialOpcode() override;
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,280 @@
/* 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/comprehend/comprehend.h"
#include "glk/comprehend/game_data.h"
#include "glk/comprehend/game_tr2.h"
#include "glk/comprehend/pics.h"
namespace Glk {
namespace Comprehend {
enum RoomId {
ROOM_CLAY_HUT = 7,
ROOM_FIELD = 26
};
enum RoomFlag {
ROOMFLAG_FOREST = 1 << 0,
ROOMFLAG_WEREWOLF = 1 << 6,
ROOMFLAG_VAMPIRE = 1 << 7
};
enum ItemId {
ITEM_GOBLIN = 9,
ITEM_SILVER_BULLET = 21,
ITEM_BLACK_CAT = 23,
ITEM_WEREWOLF = 33,
ITEM_VAMPIRE = 38
};
struct TransylvaniaMonster {
uint8 _object;
uint8 _deadFlag;
uint _roomAllowFlag;
uint _minTurnsBefore;
uint _randomness;
};
const TransylvaniaMonster TransylvaniaGame2::WEREWOLF = {
ITEM_WEREWOLF, 7, ROOMFLAG_WEREWOLF, 10, 190
};
const TransylvaniaMonster TransylvaniaGame2::VAMPIRE = {
ITEM_VAMPIRE, 5, ROOMFLAG_VAMPIRE, 0, 200
};
static const GameStrings TR_STRINGS = {
EXTRA_STRING_TABLE(0x8a)
};
TransylvaniaGame2::TransylvaniaGame2() : ComprehendGameV2(),
_miceReleased(false) {
_gameDataFile = "g0";
_locationGraphicFiles.push_back("RA");
_locationGraphicFiles.push_back("RB");
_locationGraphicFiles.push_back("RC");
_itemGraphicFiles.push_back("OA");
_itemGraphicFiles.push_back("OB");
_itemGraphicFiles.push_back("OC");
_titleGraphicFile = "t0";
_gameStrings = &TR_STRINGS;
}
bool TransylvaniaGame2::updateMonster(const TransylvaniaMonster *monsterInfo) {
Item *monster;
Room *room;
uint16 turn_count;
room = &_rooms[_currentRoom];
if (!(room->_flags & monsterInfo->_roomAllowFlag))
return false;
turn_count = _variables[VAR_TURN_COUNT];
monster = get_item(monsterInfo->_object);
if (monster->_room == _currentRoom) {
// The monster is in the current room - leave it there
return true;
}
if (!_flags[monsterInfo->_deadFlag] &&
turn_count > monsterInfo->_minTurnsBefore) {
/*
* The monster is alive and allowed to move to the current
* room. Randomly decide whether on not to. If not, move
* it back to limbo.
*/
if (getRandomNumber(255) > monsterInfo->_randomness) {
move_object(monster, _currentRoom);
_variables[15] = turn_count + 1;
} else {
move_object(monster, ROOM_NOWHERE);
}
}
return true;
}
bool TransylvaniaGame2::isMonsterInRoom(const TransylvaniaMonster *monsterInfo) {
Item *monster = get_item(monsterInfo->_object);
return monster->_room == _currentRoom;
}
int TransylvaniaGame2::roomIsSpecial(uint room_index, uint *roomDescString) {
Room *room = &_rooms[room_index];
if (room_index == 0x28) {
if (roomDescString)
*roomDescString = room->_stringDesc;
return ROOM_IS_DARK;
}
return ROOM_IS_NORMAL;
}
void TransylvaniaGame2::beforeTurn() {
Room *room;
if (!isMonsterInRoom(&WEREWOLF) && !isMonsterInRoom(&VAMPIRE)) {
if (_currentRoom == ROOM_CLAY_HUT) {
Item *blackCat = get_item(ITEM_BLACK_CAT);
if (blackCat->_room == _currentRoom && getRandomNumber(255) >= 128)
console_println(_strings[109].c_str());
goto done;
} else if (_currentRoom == ROOM_FIELD) {
Item *goblin = get_item(ITEM_GOBLIN);
if (goblin->_room == _currentRoom)
console_println(_strings[94 + getRandomNumber(3)].c_str());
goto done;
}
}
if (updateMonster(&WEREWOLF) || updateMonster(&VAMPIRE))
goto done;
room = &_rooms[_currentRoom];
if ((room->_flags & ROOMFLAG_FOREST) && (_variables[VAR_TURN_COUNT] % 255) >= 4
&& getRandomNumber(255) < 40) {
int stringNum = _miceReleased ? 108 : 107;
console_println(_strings[stringNum].c_str());
// Until the mice are released, an eagle moves player to a random room
if (!_miceReleased) {
// Get new room to get moved to
int roomNum = getRandomNumber(3) + 1;
if (roomNum == _currentRoom)
roomNum += 15;
move_to(roomNum);
// Make sure Werwolf and Vampire aren't present
get_item(ITEM_WEREWOLF)->_room = 0xff;
get_item(ITEM_VAMPIRE)->_room = 0xff;
}
}
done:
ComprehendGameV2::beforeTurn();
}
void TransylvaniaGame2::synchronizeSave(Common::Serializer &s) {
ComprehendGame::synchronizeSave(s);
s.syncAsByte(_miceReleased);
// As a post-step, ensure the vampire and werewolf aren't present
get_item(ITEM_WEREWOLF)->_room = 0xff;
get_item(ITEM_VAMPIRE)->_room = 0xff;
}
void TransylvaniaGame2::handleSpecialOpcode() {
switch (_specialOpcode) {
case 1:
// Mice have been released
_miceReleased = true;
break;
case 2:
// Gun is fired. Drop the bullet in a random room
get_item(ITEM_SILVER_BULLET)->_room = getRandomNumber(7) + 1;
_updateFlags |= UPDATE_GRAPHICS;
break;
case 3:
case 4:
// Game over - failure
console_println(_strings2[138].c_str());
game_restart();
break;
case 5:
// Won the game
g_comprehend->showGraphics();
g_comprehend->drawLocationPicture(40);
game_restart();
break;
case 6:
game_save();
break;
case 7:
game_restore();
break;
case 8:
// Restart game
game_restart();
break;
case 9:
// Show the Zin screen in response to doing
// 'sing some enchanted evening' in his cabin.
g_comprehend->showGraphics();
g_comprehend->drawLocationPicture(41);
console_get_key();
_updateFlags |= UPDATE_GRAPHICS;
break;
default:
break;
}
}
#define READ_LINE do { \
g_comprehend->readLine(buffer, sizeof(buffer)); \
if (g_comprehend->shouldQuit()) return; \
} while (strlen(buffer) == 0)
void TransylvaniaGame2::beforeGame() {
char buffer[128];
g_comprehend->setDisableSaves(true);
// Draw the title
g_comprehend->drawPicture(TITLE_IMAGE);
// Print game information
console_println("Story and graphics by Antonio Antiochia.");
console_println("IBM version by Jeffrey A. Jay. Copyright 1987 POLARWARE, Inc.");
g_comprehend->readChar();
// Welcome to Transylvania - sign your name
console_println(_strings[0x20].c_str());
READ_LINE;
// The player's name is stored in word 0
_replaceWords[0] = Common::String(buffer);
// And your next of kin - This isn't stored by the game
console_println(_strings[0x21].c_str());
READ_LINE;
g_comprehend->setDisableSaves(false);
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,54 @@
/* 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_COMPREHEND_GAME_TR2_H
#define GLK_COMPREHEND_GAME_TR2_H
#include "glk/comprehend/game_opcodes.h"
namespace Glk {
namespace Comprehend {
struct TransylvaniaMonster;
class TransylvaniaGame2 : public ComprehendGameV2 {
private:
static const TransylvaniaMonster WEREWOLF;
static const TransylvaniaMonster VAMPIRE;
bool _miceReleased;
bool updateMonster(const TransylvaniaMonster *monsterInfo);
bool isMonsterInRoom(const TransylvaniaMonster *monsterInfo);
public:
TransylvaniaGame2();
~TransylvaniaGame2() override {}
void beforeGame() override;
void beforeTurn() override;
void synchronizeSave(Common::Serializer &s) override;
int roomIsSpecial(uint room_index, uint *roomDescString) override;
void handleSpecialOpcode() override;
};
} // namespace Comprehend
} // namespace Glk
#endif

View File

@@ -0,0 +1,447 @@
/* 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/comprehend/pics.h"
#include "common/memstream.h"
#include "glk/comprehend/charset.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/draw_surface.h"
#include "glk/comprehend/file_buf.h"
#include "glk/comprehend/game.h"
#include "glk/comprehend/game_data.h"
namespace Glk {
namespace Comprehend {
#define IMAGES_PER_FILE 16
enum Opcode {
OPCODE_END = 0,
OPCODE_SET_TEXT_POS = 1,
OPCODE_SET_PEN_COLOR = 2,
OPCODE_TEXT_CHAR = 3,
OPCODE_SET_SHAPE = 4,
OPCODE_TEXT_OUTLINE = 5,
OPCODE_SET_FILL_COLOR = 6,
OPCODE_END2 = 7,
OPCODE_MOVE_TO = 8,
OPCODE_DRAW_BOX = 9,
OPCODE_DRAW_LINE = 10,
OPCODE_DRAW_CIRCLE = 11,
OPCODE_DRAW_SHAPE = 12,
OPCODE_DELAY = 13,
OPCODE_PAINT = 14,
OPCODE_RESET = 15
};
enum SpecialOpcode {
RESETOP_0 = 0,
RESETOP_RESET = 1,
RESETOP_OO_TOPOS_UNKNOWN = 3
};
/*-------------------------------------------------------*/
uint32 Pics::ImageContext::getFillColor() const {
uint color = _fillColor;
// FIXME: Properly display text color in Crimson Crown
if (g_vm->getGameID() == "crimsoncrown" && color == 0x000000ff)
color = G_COLOR_WHITE;
return color;
}
void Pics::ImageContext::lineFixes() {
// WORKAROUND: Fix lines on title screens so floodfill works correctly
if (g_vm->getGameID() == "transylvania" && _picIndex == 9999) {
_drawSurface->drawLine(191, 31, 192, 31, G_COLOR_BLACK); // v
_drawSurface->drawLine(196, 50, 197, 50, G_COLOR_BLACK); // a
_drawSurface->drawLine(203, 49, 204, 49, G_COLOR_BLACK);
_drawSurface->drawLine(197, 53, 202, 53, G_COLOR_BLACK);
_drawSurface->drawLine(215, 51, 220, 51, G_COLOR_BLACK); // n
_drawSurface->drawLine(221, 51, 222, 51, G_COLOR_BLACK);
_drawSurface->drawLine(228, 50, 229, 50, G_COLOR_BLACK);
_drawSurface->drawLine(217, 59, 220, 59, G_COLOR_BLACK);
_drawSurface->drawLine(212, 49, 212, 50, G_COLOR_BLACK);
_drawSurface->drawLine(213, 49, 213, 52, G_COLOR_WHITE);
_drawSurface->drawLine(235, 52, 236, 61, G_COLOR_BLACK); // i
_drawSurface->drawLine(237, 61, 238, 61, G_COLOR_BLACK);
}
if (g_vm->getGameID() == "crimsoncrown" && _picIndex == 9999 && _x == 67 && _y == 55) {
_drawSurface->drawLine(78, 28, 77, 29, G_COLOR_WHITE);
_drawSurface->drawLine(71, 43, 69, 47, G_COLOR_WHITE);
_drawSurface->drawLine(67, 57, 68, 56, G_COLOR_WHITE);
_drawSurface->drawLine(79, 101, 80, 101, G_COLOR_WHITE);
_drawSurface->drawLine(183, 101, 184, 100, G_COLOR_WHITE);
_drawSurface->drawLine(193, 47, 193, 48, G_COLOR_WHITE);
_drawSurface->drawLine(68, 48, 71, 48, G_COLOR_BLACK);
}
}
/*-------------------------------------------------------*/
Pics::ImageFile::ImageFile(const Common::String &filename, bool isSingleImage) : _filename(filename) {
Common::File f;
uint16 version;
int i;
if (!f.open(_filename))
error("Could not open file - %s", filename.c_str());
if (isSingleImage) {
// It's a title image file, which has only a single image with no
// table of image offsets
_imageOffsets.resize(1);
_imageOffsets[0] = 4;
return;
}
version = f.readUint16LE();
if (version == 0x1000)
f.seek(4);
else
f.seek(0);
// Get the image offsets in the file
_imageOffsets.resize(IMAGES_PER_FILE);
for (i = 0; i < IMAGES_PER_FILE; i++) {
_imageOffsets[i] = f.readUint16LE();
if (version == 0x1000)
_imageOffsets[i] += 4;
}
}
void Pics::ImageFile::draw(uint index, ImageContext *ctx) const {
if (!ctx->_file.open(_filename))
error("Opening image file");
ctx->_file.seek(_imageOffsets[index]);
for (bool done = false; !done;) {
done = doImageOp(ctx);
}
}
bool Pics::ImageFile::doImageOp(Pics::ImageContext *ctx) const {
uint8 opcode;
uint16 a, b;
opcode = ctx->_file.readByte();
debugCN(kDebugGraphics, " %.4x [%.2x]: ", (int)ctx->_file.pos() - 1, opcode);
byte param = opcode & 0xf;
opcode >>= 4;
switch (opcode) {
case OPCODE_END:
case OPCODE_END2:
// End of the rendering
debugC(kDebugGraphics, "End of image");
return true;
case OPCODE_SET_TEXT_POS:
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
b = imageGetOperand(ctx);
debugC(kDebugGraphics, "set_text_pos(%d, %d)", a, b);
ctx->_textX = a;
ctx->_textY = b;
break;
case OPCODE_SET_PEN_COLOR:
debugC(kDebugGraphics, "set_pen_color(%.2x)", opcode);
if (!(ctx->_drawFlags & IMAGEF_NO_FILL))
ctx->_penColor = ctx->_drawSurface->getPenColor(param);
break;
case OPCODE_TEXT_CHAR:
case OPCODE_TEXT_OUTLINE:
// Text outline mode draws a bunch of pixels that sort of looks like the char
// TODO: See if the outline mode is ever used
if (opcode == OPCODE_TEXT_OUTLINE)
warning("TODO: Implement drawing text outlines");
a = imageGetOperand(ctx);
if (a < 0x20 || a >= 0x7f) {
warning("Invalid character - %c", a);
a = '?';
}
debugC(kDebugGraphics, "draw_char(%c)", a);
ctx->_font->drawChar(ctx->_drawSurface, a, ctx->_textX, ctx->_textY, ctx->getFillColor());
ctx->_textX += ctx->_font->getCharWidth(a);
break;
case OPCODE_SET_SHAPE:
debugC(kDebugGraphics, "set_shape_type(%.2x)", param);
if (param == 8) {
// FIXME: This appears to be a _shape type. Only used by OO-Topos
warning("TODO: Shape type 8");
ctx->_shape = SHAPE_PIXEL;
} else {
ctx->_shape = (Shape)param;
}
break;
case OPCODE_SET_FILL_COLOR:
a = imageGetOperand(ctx);
debugC(kDebugGraphics, "set_fill_color(%.2x)", a);
ctx->_fillColor = ctx->_drawSurface->getFillColor(a);
break;
case OPCODE_MOVE_TO:
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
b = imageGetOperand(ctx);
debugC(kDebugGraphics, "move_to(%d, %d)", a, b);
ctx->_x = a;
ctx->_y = b;
break;
case OPCODE_DRAW_BOX:
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
b = imageGetOperand(ctx);
debugC(kDebugGraphics, "draw_box (%d, %d) - (%d, %d)",
ctx->_x, ctx->_y, a, b);
ctx->_drawSurface->drawBox(ctx->_x, ctx->_y, a, b, ctx->_penColor);
break;
case OPCODE_DRAW_LINE:
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
b = imageGetOperand(ctx);
debugC(kDebugGraphics, "draw_line (%d, %d) - (%d, %d)",
ctx->_x, ctx->_y, a, b);
ctx->_drawSurface->drawLine(ctx->_x, ctx->_y, a, b, ctx->_penColor);
ctx->_x = a;
ctx->_y = b;
break;
case OPCODE_DRAW_CIRCLE:
a = imageGetOperand(ctx);
debugC(kDebugGraphics, "draw_circle (%d, %d) diameter=%d",
ctx->_x, ctx->_y, a);
ctx->_drawSurface->drawCircle(ctx->_x, ctx->_y, a, ctx->_penColor);
break;
case OPCODE_DRAW_SHAPE:
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
b = imageGetOperand(ctx);
debugC(kDebugGraphics, "draw_shape(%d, %d), style=%.2x, fill=%.2x",
a, b, ctx->_shape, ctx->_fillColor);
if (!(ctx->_drawFlags & IMAGEF_NO_FILL))
ctx->_drawSurface->drawShape(a, b, ctx->_shape, ctx->_fillColor);
break;
case OPCODE_DELAY:
// The original allowed for rendering to be paused briefly. We don't do
// that in ScummVM, and just show the finished rendered image
(void)imageGetOperand(ctx);
break;
case OPCODE_PAINT:
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
b = imageGetOperand(ctx);
if (opcode & 0x1)
a += 255;
debugC(kDebugGraphics, "paint(%d, %d)", a, b);
ctx->lineFixes();
if (!(ctx->_drawFlags & IMAGEF_NO_FILL))
ctx->_drawSurface->floodFill(a, b, ctx->_fillColor);
break;
#if 0
// FIXME: The reset case was causing room outside cell to be drawn all white
case OPCODE_RESET:
a = imageGetOperand(ctx);
doResetOp(ctx, a);
break;
#endif
}
//ctx->_drawSurface->dumpToScreen();
return false;
}
void Pics::ImageFile::doResetOp(ImageContext *ctx, byte param) const {
switch (param) {
case RESETOP_0:
// In Transylvania this sub-opcode is a do nothing
break;
case RESETOP_RESET:
// TODO: Calls same reset that first gets called when rendering starts.
// Figure out what the implication of resetting the variables does
break;
case RESETOP_OO_TOPOS_UNKNOWN:
// TODO: This is called for some scenes in OO-Topis. Figure out what it does
break;
default:
break;
}
}
uint16 Pics::ImageFile::imageGetOperand(ImageContext *ctx) const {
return ctx->_file.readByte();
}
/*-------------------------------------------------------*/
Pics::Pics() : _font(nullptr) {
if (Common::File::exists("charset.gda"))
_font = new CharSet();
else if (g_comprehend->getGameID() == "talisman")
_font = new TalismanFont();
}
Pics::~Pics() {
delete _font;
}
void Pics::clear() {
_rooms.clear();
_items.clear();
}
void Pics::load(const Common::StringArray &roomFiles,
const Common::StringArray &itemFiles,
const Common::String &titleFile) {
clear();
for (uint idx = 0; idx < roomFiles.size(); ++idx)
_rooms.push_back(ImageFile(roomFiles[idx]));
for (uint idx = 0; idx < itemFiles.size(); ++idx)
_items.push_back(ImageFile(itemFiles[idx]));
if (!titleFile.empty())
_title = ImageFile(titleFile, true);
}
int Pics::getPictureNumber(const Common::String &filename) const {
// Ensure prefix and suffix
if (!filename.hasPrefixIgnoreCase("pic") ||
!filename.hasSuffixIgnoreCase(".raw"))
return -1;
// Get the number part
Common::String num(filename.c_str() + 3, filename.size() - 7);
if (num.empty() || !Common::isDigit(num[0]))
return -1;
return atoi(num.c_str());
}
bool Pics::hasFile(const Common::Path &path) const {
Common::String name = path.baseName();
int num = getPictureNumber(name);
if (num == -1)
return false;
if (num == DARK_ROOM || num == BRIGHT_ROOM || num == TITLE_IMAGE)
return true;
if (num >= ITEMS_OFFSET && num < (int)(ITEMS_OFFSET + _items.size() * IMAGES_PER_FILE))
return true;
if (num < ITEMS_OFFSET && (num % 100) < (int)(_rooms.size() * IMAGES_PER_FILE))
return true;
return false;
}
int Pics::listMembers(Common::ArchiveMemberList &list) const {
return list.size();
}
const Common::ArchiveMemberPtr Pics::getMember(const Common::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *Pics::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.baseName();
// Get the picture number
int num = getPictureNumber(name);
if (num == -1 || !hasFile(path))
return nullptr;
// Draw the image
drawPicture(num);
// Create a stream with the data for the surface
Common::MemoryReadWriteStream *stream =
new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
const DrawSurface &ds = *g_comprehend->_drawSurface;
stream->writeUint16LE(ds.w);
stream->writeUint16LE(ds.h);
stream->writeUint16LE(0); // Palette size
stream->write(ds.getPixels(), ds.w * ds.h * 4);
return stream;
}
void Pics::drawPicture(int pictureNum) const {
ImageContext ctx(g_comprehend->_drawSurface, _font, g_comprehend->_drawFlags, pictureNum);
if (pictureNum == DARK_ROOM) {
ctx._drawSurface->clearScreen(G_COLOR_BLACK);
} else if (pictureNum == BRIGHT_ROOM) {
ctx._drawSurface->clearScreen(G_COLOR_WHITE);
} else if (pictureNum == TITLE_IMAGE) {
ctx._drawSurface->clearScreen(G_COLOR_WHITE);
_title.draw(0, &ctx);
} else if (pictureNum >= ITEMS_OFFSET) {
pictureNum -= ITEMS_OFFSET;
ctx._drawSurface->clear(0);
_items[pictureNum / IMAGES_PER_FILE].draw(
pictureNum % IMAGES_PER_FILE, &ctx);
} else {
if (pictureNum < LOCATIONS_NO_BG_OFFSET) {
ctx._drawSurface->clearScreen((ctx._drawFlags & IMAGEF_REVERSE) ? G_COLOR_BLACK : G_COLOR_WHITE);
if (ctx._drawFlags & IMAGEF_REVERSE)
ctx._penColor = RGB(255, 255, 255);
} else {
ctx._drawSurface->clear(0);
}
pictureNum %= 100;
_rooms[pictureNum / IMAGES_PER_FILE].draw(
pictureNum % IMAGES_PER_FILE, &ctx);
}
}
} // namespace Comprehend
} // namespace Glk

View File

@@ -0,0 +1,150 @@
/* 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_COMPREHEND_PICS_H
#define GLK_COMPREHEND_PICS_H
#include "glk/comprehend/draw_surface.h"
#include "common/archive.h"
#include "common/file.h"
#include "common/str-array.h"
#include "graphics/font.h"
namespace Glk {
namespace Comprehend {
enum ImageFlag {
IMAGEF_REVERSE = 1 << 0,
IMAGEF_NO_PAINTING = 1 << 1,
IMAGEF_NO_FILL = IMAGEF_REVERSE | IMAGEF_NO_PAINTING
};
enum {
LOCATIONS_OFFSET = 0,
LOCATIONS_NO_BG_OFFSET = 100,
ITEMS_OFFSET = 200,
DARK_ROOM = 1000,
BRIGHT_ROOM = 1001,
TITLE_IMAGE = 9999
};
class Pics : public Common::Archive {
struct ImageContext {
Common::File _file;
uint _picIndex;
DrawSurface *_drawSurface;
Graphics::Font *_font;
uint _drawFlags;
uint16 _x;
uint16 _y;
uint32 _penColor;
uint32 _fillColor;
Shape _shape;
uint16 _textX;
uint16 _textY;
ImageContext(DrawSurface *drawSurface, Graphics::Font *font, uint flags, uint picIndex) :
_drawSurface(drawSurface), _font(font), _drawFlags(flags), _picIndex(picIndex),
_x(0), _y(0), _penColor(G_COLOR_BLACK), _fillColor(G_COLOR_BLACK),
_shape(SHAPE_CIRCLE_LARGE), _textX(0), _textY(0) {
}
uint32 getFillColor() const;
void lineFixes();
};
struct ImageFile {
private:
Common::Array<uint16> _imageOffsets;
Common::Path _filename;
private:
bool doImageOp(ImageContext *ctx) const;
uint16 imageGetOperand(ImageContext *ctx) const;
void doResetOp(ImageContext *ctx, byte param) const;
public:
ImageFile() {}
ImageFile(const Common::String &filename, bool isSingleImage = false);
void draw(uint index, ImageContext *ctx) const;
};
private:
Common::Array<ImageFile> _rooms;
Common::Array<ImageFile> _items;
ImageFile _title;
Graphics::Font *_font;
private:
/**
* Returns the image number if the passed filename is a picture
*/
int getPictureNumber(const Common::String &filename) const;
/**
* Draw the specified picture
*/
void drawPicture(int pictureNum) const;
public:
Pics();
~Pics();
void clear();
void load(const Common::StringArray &roomFiles,
const Common::StringArray &itemFiles,
const Common::String &titleFile);
/**
* Check if a member with the given name is present in the Archive.
* Patterns are not allowed, as this is meant to be a quick File::exists()
* replacement.
*/
bool hasFile(const Common::Path &path) const override;
/**
* Add all members of the Archive to list.
* Must only append to list, and not remove elements from it.
*
* @return the number of names added to list
*/
int listMembers(Common::ArchiveMemberList &list) const override;
/**
* Returns a ArchiveMember representation of the given file.
*/
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
/**
* Create a stream bound to a member with the specified name in the
* archive. If no member with this name exists, 0 is returned.
* @return the newly created input stream
*/
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
};
} // namespace Comprehend
} // namespace Glk
#endif