diff options
-rw-r--r-- | engines/adl/adl.cpp | 155 | ||||
-rw-r--r-- | engines/adl/adl.h | 98 | ||||
-rw-r--r-- | engines/adl/adl_v1.cpp | 690 | ||||
-rw-r--r-- | engines/adl/adl_v1.h | 133 | ||||
-rw-r--r-- | engines/adl/configure.engine | 3 | ||||
-rw-r--r-- | engines/adl/detection.cpp | 108 | ||||
-rw-r--r-- | engines/adl/display.cpp | 611 | ||||
-rw-r--r-- | engines/adl/display.h | 103 | ||||
-rw-r--r-- | engines/adl/module.mk | 19 | ||||
-rw-r--r-- | engines/adl/parser.cpp | 172 | ||||
-rw-r--r-- | engines/adl/parser.h | 66 |
11 files changed, 2158 insertions, 0 deletions
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp new file mode 100644 index 0000000000..be3d8d7151 --- /dev/null +++ b/engines/adl/adl.cpp @@ -0,0 +1,155 @@ +/* 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 2 + * 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 "common/scummsys.h" + +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/debug-channels.h" +#include "common/error.h" +#include "common/file.h" +#include "common/fs.h" +#include "common/system.h" +#include "common/events.h" +#include "common/stream.h" +#include "graphics/palette.h" + +#include "engines/util.h" + +#include "adl/adl.h" +#include "adl/display.h" +#include "adl/parser.h" + +namespace Adl { + +Common::String asciiToApple(Common::String str) { + Common::String ret(str); + Common::String::iterator it; + + for (it = ret.begin(); it != ret.end(); ++it) + *it = *it | 0x80; + + return ret; +} + +Common::String appleToAscii(Common::String str) { + Common::String ret(str); + Common::String::iterator it; + + for (it = ret.begin(); it != ret.end(); ++it) + *it = *it & 0x7f; + + return ret; +} + +AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) : + Engine(syst), + _gameDescription(gd), + _console(nullptr), + _display(nullptr) { + // Put your engine in a sane state, but do nothing big yet; + // in particular, do not load data from files; rather, if you + // need to do such things, do them from run(). + + // Do not initialize graphics here + // Do not initialize audio devices here + + // However this is the place to specify all default directories + const Common::FSNode gameDataDir(ConfMan.get("path")); + SearchMan.addSubDirectoryMatching(gameDataDir, "sound"); + + // Don't forget to register your random source + _rnd = new Common::RandomSource("adl"); + + debug("AdlEngine::AdlEngine"); +} + +AdlEngine::~AdlEngine() { + debug("AdlEngine::~AdlEngine"); + + delete _rnd; + delete _console; + delete _display; + + DebugMan.clearAllDebugChannels(); +} + +Common::Error AdlEngine::run() { + initGraphics(560, 384, true); + + byte palette[6 * 3] = { + 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, + 0xc7, 0x34, 0xff, + 0x38, 0xcb, 0x00, + 0x00, 0x00, 0xff, // FIXME + 0xff, 0xa5, 0x00 // FIXME + }; + + g_system->getPaletteManager()->setPalette(palette, 0, 6); + + _console = new Console(this); + _display = new Display(); + _parser = new Parser(*this, *_display); + + runGame(); + + return Common::kNoError; +} + +Common::String AdlEngine::readString(Common::ReadStream &stream, byte until) { + Common::String str; + + while (1) { + byte b = stream.readByte(); + + if (stream.eos() || stream.err() || b == until) + break; + + str += b; + }; + + return str; +} + +void AdlEngine::printStrings(Common::SeekableReadStream &stream, int count) { + while (1) { + Common::String str = readString(stream); + _display->printString(str); + + if (--count == 0) + break; + + stream.seek(3, SEEK_CUR); + }; +} + +AdlEngine *AdlEngine::create(GameType type, OSystem *syst, const AdlGameDescription *gd) { + switch(type) { + case kGameTypeAdl1: + return AdlEngine_v1__create(syst, gd); + default: + error("Unknown GameType"); + } +} + +} // End of namespace Adl diff --git a/engines/adl/adl.h b/engines/adl/adl.h new file mode 100644 index 0000000000..d5a518faa7 --- /dev/null +++ b/engines/adl/adl.h @@ -0,0 +1,98 @@ +/* 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 2 + * 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. + * + */ + +#ifndef ADL_ADL_H +#define ADL_ADL_H + +#include "common/random.h" +#include "engines/engine.h" +#include "gui/debugger.h" + +namespace Common { +class ReadStream; +class SeekableReadStream; +} + +namespace Adl { + +class Display; +class Parser; +class Console; +struct AdlGameDescription; + +enum GameType { + kGameTypeNone = 0, + kGameTypeAdl1 +}; + +Common::String asciiToApple(Common::String str); +Common::String appleToAscii(Common::String str); + +enum { + STR_COMMON_ENTERCMD, + STR_COMMON_VERBERR, + STR_COMMON_NOUNERR, + STR_CUSTOM_START +}; + +#define A2CHAR(C) ((C) | 0x80) + +class AdlEngine : public Engine { +public: + AdlEngine(OSystem *syst, const AdlGameDescription *gd); + virtual ~AdlEngine(); + + const AdlGameDescription *_gameDescription; + uint32 getFeatures() const; + const char *getGameId() const; + + static AdlEngine *create(GameType type, OSystem *syst, const AdlGameDescription *gd); + + Common::Error run(); + virtual Common::String getExeString(uint id) = 0; + +protected: + virtual void runGame() = 0; + Common::String readString(Common::ReadStream &stream, byte until = 0); + void printStrings(Common::SeekableReadStream &stream, int count = 1); + Display *_display; + Parser *_parser; + +private: + Console *_console; + + // We need random numbers + Common::RandomSource *_rnd; +}; + +// Example console class +class Console : public GUI::Debugger { +public: + Console(AdlEngine *vm) {} + virtual ~Console(void) {} +}; + +AdlEngine *AdlEngine_v1__create(OSystem *syst, const AdlGameDescription *gd); + +} // End of namespace Adl + +#endif diff --git a/engines/adl/adl_v1.cpp b/engines/adl/adl_v1.cpp new file mode 100644 index 0000000000..61671e5d7f --- /dev/null +++ b/engines/adl/adl_v1.cpp @@ -0,0 +1,690 @@ +/* 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 2 + * 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 "common/scummsys.h" + +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/debug-channels.h" +#include "common/error.h" +#include "common/file.h" +#include "common/fs.h" +#include "common/system.h" +#include "common/events.h" +#include "common/stream.h" +#include "graphics/palette.h" + +#include "engines/util.h" + +#include "adl/adl_v1.h" +#include "adl/display.h" +#include "adl/parser.h" + +namespace Adl { + +static uint exeStrings[STR_MH_TOTAL] = { + 23484, 23375, 23438, 27658, 0x6c31, 27729, 27772, 0x5f1e +}; + +AdlEngine_v1::AdlEngine_v1(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine(syst, gd), + _state(kIntro), + _room(1), + _steps(1), + _isDark(false) { + _variables.resize(20); +} + +void AdlEngine_v1::runIntro() { + Common::File file; + + if (!file.open("AUTO LOAD OBJ")) + error("Failed to open file"); + + file.seek(0x1003); + _display->setMode(Display::kModeHires); + _display->loadFrameBuffer(file); + _display->decodeFrameBuffer(); + _display->delay(4000); + + if (shouldQuit()) + return; + + _display->setMode(Display::kModeText); + + Common::File basic; + if (!basic.open("MYSTERY.HELLO")) + error("Failed to open file"); + + Common::String str; + + basic.seek(93); + str = readString(basic, '"'); + _display->printASCIIString(str + '\r'); + + basic.seek(299); + str = readString(basic, '"'); + _display->printASCIIString(str + "\r\r"); + + basic.seek(365); + str = readString(basic, '"'); + _display->printASCIIString(str + "\r\r"); + + basic.seek(601); + str = readString(basic, '"'); + _display->printASCIIString(str + '\r'); + + _display->inputKey(); + if (g_engine->shouldQuit()) + return; + + _display->setMode(Display::kModeMixed); + + file.seek(15); + str = readString(file); + + while (1) { + _display->printString(str); + Common::String s = _display->inputString(); + + if (g_engine->shouldQuit()) + break; + + if (s.empty()) + continue; + + if ((byte)s[0] == ('I' | 0x80)) + break; + else if ((byte)s[0] == ('G' | 0x80)) + return; + }; + + _display->setMode(Display::kModeText); + file.seek(102); + + const int pages[] = { 6, 6, 4, 5, 8, 7, 0 }; + + int page = 0; + while (pages[page] != 0) { + _display->home(); + printStrings(file, pages[page++]); + _display->inputString(); + + if (g_engine->shouldQuit()) + return; + + file.seek(9, SEEK_CUR); + } +} + +void AdlEngine_v1::drawPic(Common::ReadStream &stream, byte xOffset, byte yOffset) { + byte x, y; + bool bNewLine = false; + byte oldX = 0, oldY = 0; + while (1) { + x = stream.readByte(); + y = stream.readByte(); + + if (stream.err() || stream.eos()) + error("Failed to read picture"); + + if (x == 0xff && y == 0xff) + return; + + if (x == 0 && y == 0) { + bNewLine = true; + continue; + } + + x += xOffset; + y += yOffset; + + if (y > 160) + y = 160; + + if (bNewLine) { + _display->drawPixel(x, y, 0x7f); + bNewLine = false; + } else { + _display->drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f); + } + + oldX = x; + oldY = y; + } +} + +void AdlEngine_v1::drawPic(byte pic, byte xOffset, byte yOffset) { + Common::File f; + Common::String name = Common::String::format("BLOCK%i", _pictures[pic].block); + + if (!f.open(name)) + error("Failed to open file"); + + f.seek(_pictures[pic].offset); + drawPic(f, xOffset, yOffset); +} + +void AdlEngine_v1::drawItems() { + Common::Array<Item>::const_iterator it; + + uint dropped = 0; + + for (it = _inventory.begin(); it != _inventory.end(); ++it) { + if (it->field2 != _room) + continue; + + if (it->field7 == 1) { + if (_rooms[_room].field8 == _rooms[_room].picture) { + const Common::Point &p = _itemOffsets[dropped]; + if (it->field4) + _display->drawRightAngles(_drawings[it->field3 - 1], Common::Point(p.x, p.y), 0, 1, 0x7f); + else + drawPic(it->field3, p.x, p.y); + ++dropped; + } + continue; + } + + Common::Array<byte>::const_iterator it2; + + for (it2 = it->field10.begin(); it2 != it->field10.end(); ++it2) { + if (*it2 == _rooms[_room].picture) { + if (it->field4) + _display->drawRightAngles(_drawings[it->field3 - 1], Common::Point(it->field5, it->field6), 0, 1, 0x7f); + else + drawPic(it->field3, it->field5, it->field6); + continue; + } + } + } +} + +void AdlEngine_v1::showRoom() { + if (!_isDark) { + drawPic(_rooms[_room].picture, 0, 0); + drawItems(); + } + + _display->decodeFrameBuffer(); + printMessage(_rooms[_room].description, false); +} + +Common::String AdlEngine_v1::getExeString(uint idx) { + return _exeStrings[idx]; +} + +void AdlEngine_v1::wordWrap(Common::String &str) { + uint end = 39; + + while (1) { + if (str.size() <= end) + return; + + while (str[end] != (char)A2CHAR(' ')) + --end; + + str.setChar((char)A2CHAR('\r'), end); + end += 40; + } +} + +void AdlEngine_v1::printMessage(uint idx, bool wait) { + // Hardcoded overrides that don't wait after printing + // Note: strings may differ slightly from the ones in MESSAGES + switch (idx) { + case 137: + _display->printString(_exeStrings[STR_MH_DIRERR]); + return; + case 127: + _display->printString(_exeStrings[STR_MH_DONTHAVEIT]); + return; + case 37: + _display->printString(_exeStrings[STR_MH_DONTUNDERSTAND]); + return; + case 7: + _display->printString(_exeStrings[STR_MH_GETTINGDARK]); + return; + } + + Common::String msg = _msgStrings[idx - 1]; + wordWrap(msg); + _display->printString(msg); + + if (wait) + _display->delay(14 * 166018 / 1000); +} + +void AdlEngine_v1::readCommands(Common::ReadStream &stream, Commands &commands) { + while (1) { + Command command; + command.room = stream.readByte(); + + if (command.room == 0xff) + return; + + command.verb = stream.readByte(); + command.noun = stream.readByte(); + + byte scriptSize = stream.readByte() - 6; + + command.numCond = stream.readByte(); + command.numAct = stream.readByte(); + + for (uint i = 0; i < scriptSize; ++i) + command.script.push_back(stream.readByte()); + + if (stream.eos() || stream.err()) + error("Failed to read commands"); + + commands.push_back(command); + } +} + +void AdlEngine_v1::takeItem(byte noun) { + Common::Array<Item>::iterator it; + + for (it = _inventory.begin(); it != _inventory.end(); ++it) { + if (it->field1 != noun || it->field2 != _room) + continue; + + if (it->field7 == 2) { + // It doesn't move + printMessage(151); + return; + } + + if (it->field7 == 1) { + it->field2 = 0xfe; + it->field7 = 1; + return; + } + + Common::Array<byte>::const_iterator it2; + for (it2 = it->field10.begin(); it->field10.end(); ++it2) { + if (*it2 == _rooms[_room].picture) { + it->field2 = 0xfe; + it->field7 = 1; + return; + } + } + } + + // Item not here + printMessage(152); +} + +void AdlEngine_v1::dropItem(byte noun) { + Common::Array<Item>::iterator it; + + for (it = _inventory.begin(); it != _inventory.end(); ++it) { + if (it->field1 != noun || it->field2 != 0xfe) + continue; + + it->field2 = _room; + it->field7 = 1; + return; + } + + // Don't understand + printMessage(37); +} + +void AdlEngine_v1::doActions(const Command &command, byte noun, byte offset) { + for (uint i = 0; i < command.numAct; ++i) { + switch (command.script[offset]) { + case 1: + _variables[command.script[offset + 2]] += command.script[offset + 1]; + offset += 3; + break; + case 2: + _variables[command.script[offset + 2]] -= command.script[offset + 1]; + offset += 3; + break; + case 3: + _variables[command.script[offset + 1]] = command.script[offset + 2]; + offset += 3; + break; + case 4: { + Common::Array<Item>::const_iterator it; + + for (it = _inventory.begin(); it != _inventory.end(); ++it) + if (it->field2 == 0xfe) + printMessage(it->field8); + + ++offset; + break; + } + case 5: + _inventory[command.script[offset + 1] - 1].field2 = command.script[offset + 2]; + offset += 3; + break; + case 6: + _rooms[_room].picture = _rooms[_room].field8; + _room = command.script[offset + 1]; + offset += 2; + break; + case 7: + _rooms[_room].picture = command.script[offset + 1]; + offset += 2; + break; + case 8: + _rooms[_room].field8 = _rooms[_room].picture = command.script[offset + 1]; + offset += 2; + break; + case 9: + printMessage(command.script[offset + 1]); + offset += 2; + break; + case 0xa: + _isDark = false; + ++offset; + break; + case 0xb: + _isDark = true; + ++offset; + break; + case 0xf: + warning("Save game not implemented"); + ++offset; + break; + case 0x10: + warning("Load game not implemented"); + ++offset; + break; + case 0x11: { + _display->printString(_exeStrings[STR_MH_PLAYAGAIN]); + Common::String input = _display->inputString(); + if (input.size() == 0 || input[0] != (char)A2CHAR('N')) { + warning("Restart game not implemented"); + return; + } + // Fall-through + } + case 0xd: + printMessage(140); + quitGame(); + return; + case 0x12: { + byte item = command.script[offset + 1] - 1; + _inventory[item].field2 = command.script[offset + 2]; + _inventory[item].field5 = command.script[offset + 3]; + _inventory[item].field6 = command.script[offset + 4]; + offset += 5; + break; + } + case 0x13: { + byte item = command.script[offset + 2] - 1; + _inventory[item].field3 = command.script[offset + 1]; + offset += 3; + break; + } + case 0x14: + _rooms[_room].picture = _rooms[_room].field8; + ++offset; + break; + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: { + byte room = _rooms[_room].connections[command.script[offset] - 0x15]; + + if (room == 0) { + printMessage(137); + return; + } + + _rooms[_room].picture = _rooms[_room].field8; + _room = room; + return; + } + case 0x1b: + takeItem(noun); + ++offset; + break; + case 0x1c: + dropItem(noun); + ++offset; + break; + case 0x1d: + _rooms[command.script[offset + 1]].field8 = _rooms[command.script[offset + 1]].picture = command.script[offset + 2]; + offset += 3; + break; + default: + error("Invalid action opcode %02x", command.script[offset]); + } + } +} + +bool AdlEngine_v1::checkCommand(const Command &command, byte verb, byte noun) { + if (command.room != 0xfe && command.room != _room) + return false; + + if (command.verb != 0xfe && command.verb != verb) + return false; + + if (command.noun != 0xfe && command.noun != noun) + return false; + + uint offset = 0; + for (uint i = 0; i < command.numCond; ++i) { + switch (command.script[offset]) { + case 3: + if (_inventory[command.script[offset + 1] - 1].field2 != command.script[offset + 2]) + return false; + offset += 3; + break; + case 5: + if (command.script[offset + 1] > _steps) + return false; + offset += 2; + break; + case 6: + if (_variables[command.script[offset + 1]] != command.script[offset + 2]) + return false; + offset += 3; + break; + case 9: + if (_rooms[_room].picture != command.script[offset + 1]) + return false; + offset += 2; + break; + case 10: + if (_inventory[command.script[offset + 1] - 1].field3 != command.script[offset + 2]) + return false; + offset += 3; + break; + default: + error("Invalid condition opcode %02x", command.script[offset]); + } + } + + doActions(command, noun, offset); + + return true; +} + +bool AdlEngine_v1::doOneCommand(const Commands &commands, byte verb, byte noun) { + Commands::const_iterator it; + + for (it = commands.begin(); it != commands.end(); ++it) { + if (checkCommand(*it, verb, noun)) { + debug("Found match: %i %i %i", it->room, it->verb, it->noun); + return true; + } + } + + return false; +} + +void AdlEngine_v1::doAllCommands(const Commands &commands, byte verb, byte noun) { + Commands::const_iterator it; + + for (it = commands.begin(); it != commands.end(); ++it) { + if (checkCommand(*it, verb, noun)) { + debug("Found match: %i %i %i", it->room, it->verb, it->noun); + } + } +} + +void AdlEngine_v1::clearScreen() { + _display->setMode(Display::kModeMixed); + _display->clear(0x00); +} + +void AdlEngine_v1::runGame() { + runIntro(); + _display->printASCIIString("\r"); + + Common::File f; + + if (!f.open("MESSAGES")) + error("Failed to open file"); + + while (!f.eos() && !f.err()) + _msgStrings.push_back(readString(f, A2CHAR('\r')) + (char)A2CHAR('\r')); + + f.close(); + + if (!f.open("ADVENTURE")) + error("Failed to open file"); + + // Load strings from executable + for (uint idx = 0; idx < STR_MH_TOTAL; ++idx) { + f.seek(exeStrings[idx]); + _exeStrings.push_back(readString(f)); + } + + // Load room data from executable + f.seek(1280); + for (uint i = 0; i < MH_ROOMS; ++i) { + struct Room room; + f.readByte(); + room.description = f.readByte(); + for (uint j = 0; j < 6; ++j) + room.connections[j] = f.readByte(); + room.field8 = f.readByte(); + room.picture = f.readByte(); + _rooms.push_back(room); + } + + // Load inventory data from executable + f.seek(0x100); + while (f.readByte() != 0xff) { + struct Item item; + item.field1 = f.readByte(); + item.field2 = f.readByte(); + item.field3 = f.readByte(); + item.field4 = f.readByte(); + item.field5 = f.readByte(); + item.field6 = f.readByte(); + item.field7 = f.readByte(); + item.field8 = f.readByte(); + + f.readByte(); + + byte size = f.readByte(); + + for (uint i = 0; i < size; ++i) + item.field10.push_back(f.readByte()); + + _inventory.push_back(item); + } + + // Load picture data from executable + f.seek(0x4b00); + for (uint i = 0; i < MH_PICS; ++i) { + struct Picture pic; + pic.block = f.readByte(); + pic.offset = f.readUint16LE(); + _pictures.push_back(pic); + } + + // Load commands from executable + f.seek(0x3D00); + readCommands(f, _roomCommands); + + f.seek(0x3C00); + readCommands(f, _globalCommands); + + // Load dropped item offsets + f.seek(0x68ff); + for (uint i = 0; i < MH_ITEM_OFFSETS; ++i) { + Common::Point p; + p.x = f.readByte(); + p.y = f.readByte(); + _itemOffsets.push_back(p); + } + + // Load right-angle drawings + f.seek(0x4f00); + uint16 drawingsTotal = f.readUint16LE(); + for (uint i = 0; i < drawingsTotal; ++i) { + f.seek(0x4f00 + 2 + i * 2); + uint16 offset = f.readUint16LE(); + f.seek(0x4f00 + offset); + + Common::Array<byte> drawing; + byte b = f.readByte(); + while (b != 0) { + drawing.push_back(b); + b = f.readByte(); + } + _drawings.push_back(drawing); + } + + // Title screen shown during loading + f.seek(0x1800); + _display->loadFrameBuffer(f); + _display->decodeFrameBuffer(); + _display->delay(2000); + + f.seek(0x3800); + _parser->loadVerbs(f); + + f.seek(0xf00); + _parser->loadNouns(f); + + while (1) { + uint verb = 0, noun = 0; + clearScreen(); + showRoom(); + _parser->getInput(verb, noun); + + if (!doOneCommand(_roomCommands, verb, noun)) + printMessage(37); + doAllCommands(_globalCommands, verb, noun); + + _steps++; + + if (shouldQuit()) + return; + } +} + +AdlEngine *AdlEngine_v1__create(OSystem *syst, const AdlGameDescription *gd) { + return new AdlEngine_v1(syst, gd); +} + +} // End of namespace Adl diff --git a/engines/adl/adl_v1.h b/engines/adl/adl_v1.h new file mode 100644 index 0000000000..b8f4c536a5 --- /dev/null +++ b/engines/adl/adl_v1.h @@ -0,0 +1,133 @@ +/* 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 2 + * 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. + * + */ + +#ifndef ADL_ADL_V1_H +#define ADL_ADL_V1_H + +#include "adl/adl.h" + +namespace Common { +class ReadStream; +} + +namespace Adl { + +enum { + // Some of these are probably common + STR_MH_DIRERR = STR_CUSTOM_START, + STR_MH_DONTHAVEIT, + STR_MH_DONTUNDERSTAND, + STR_MH_GETTINGDARK, + STR_MH_PLAYAGAIN, + + STR_MH_TOTAL +}; + +class AdlEngine_v1 : public AdlEngine { +public: + AdlEngine_v1(OSystem *syst, const AdlGameDescription *gd); + Common::String getExeString(uint idx); + +protected: + void runGame(); + +private: + enum { + MH_ROOMS = 42, + MH_PICS = 98, + MH_ITEM_OFFSETS = 21 + }; + + enum State { + kIntro, + kIdle + }; + + struct Room { + byte description; + byte connections[6]; + byte field8; + byte picture; + }; + + struct Picture { + byte block; + uint16 offset; + }; + + struct Command { + byte room; + byte verb, noun; + byte numCond, numAct; + Common::Array<byte> script; + }; + + struct Item { + byte field1; + byte field2; + byte field3; + byte field4; + byte field5; + byte field6; + byte field7; + byte field8; + Common::Array<byte> field10; + }; + + typedef Common::List<Command> Commands; + + int _state; + + void runIntro(); + void drawPic(Common::ReadStream &stream, byte xOffset, byte yOffset); + void showRoom(); + void printMessage(uint idx, bool wait = true); + void wordWrap(Common::String &str); + void readCommands(Common::ReadStream &stream, Commands &commands); + bool checkCommand(const Command &command, byte verb, byte noun); + bool doOneCommand(const Commands &commands, byte verb, byte noun); + void doAllCommands(const Commands &commands, byte verb, byte noun); + void doActions(const Command &command, byte noun, byte offset); + void clearScreen(); + void takeItem(byte noun); + void dropItem(byte noun); + void drawItems(); + void drawPic(byte pic, byte xOffset, byte yOffset); + + Common::Array<Common::String> _exeStrings; + Common::Array<Common::String> _msgStrings; + Common::Array<Room> _rooms; + Common::Array<Picture> _pictures; + Common::Array<Item> _inventory; + Common::Array<Common::Point> _itemOffsets; + Common::Array<Common::Array<byte> > _drawings; + Commands _roomCommands; + Commands _globalCommands; + byte _room; + uint16 _steps; + Common::Array<byte> _variables; + bool _isDark; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/configure.engine b/engines/adl/configure.engine new file mode 100644 index 0000000000..844e2b8e6a --- /dev/null +++ b/engines/adl/configure.engine @@ -0,0 +1,3 @@ +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine adl "ADL" no diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp new file mode 100644 index 0000000000..ba4e0104b9 --- /dev/null +++ b/engines/adl/detection.cpp @@ -0,0 +1,108 @@ +/* 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 2 + * 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 "adl/adl.h" + +#include "common/config-manager.h" +#include "common/error.h" +#include "common/fs.h" + +#include "engines/advancedDetector.h" +#include "engines/metaengine.h" + +namespace Adl { + +struct AdlGameDescription { + ADGameDescription desc; + GameType gameType; +}; + +uint32 AdlEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +const char *AdlEngine::getGameId() const { + return _gameDescription->desc.gameid; +} + +const char *const directoryGlobs[] = { + "game", + "datafiles", + 0 +}; + +static const PlainGameDescriptor adlGames[] = { + // Games + {"hires1", "Hi-Res Adventure #1: Mystery House"}, + {0, 0} +}; + +static const AdlGameDescription gameDescriptions[] = { + + { // MD5 by waltervn + { + "hires1", 0, + { + {"ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952}, + {"AUTO LOAD OBJ", 0, "23bfccfe9fcff9b22cf6c41bde9078ac", 12291}, + {"MYSTERY.HELLO", 0, "2289b7fea300b506e902a4c597968369", 836}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformApple2GS, // FIXME + ADGF_NO_FLAGS, + GUIO0() + }, + kGameTypeAdl1 + }, + {AD_TABLE_END_MARKER, kGameTypeNone} +}; + +class AdlMetaEngine : public AdvancedMetaEngine { +public: + AdlMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(AdlGameDescription), adlGames) { } + + const char *getName() const { + return "Hi-Res Adventure"; + } + + const char *getOriginalCopyright() const { + return "Copyright (C) Sierra On-Line"; + } + + bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; +}; + +bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { + if (gd) { + *engine = AdlEngine::create(((const AdlGameDescription *)gd)->gameType, syst, (const AdlGameDescription *)gd); + } + return gd != 0; +} + +} // End of namespace Adl + +#if PLUGIN_ENABLED_DYNAMIC(ADL) + REGISTER_PLUGIN_DYNAMIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine); +#else + REGISTER_PLUGIN_STATIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine); +#endif diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp new file mode 100644 index 0000000000..02b8d51b7f --- /dev/null +++ b/engines/adl/display.cpp @@ -0,0 +1,611 @@ +/* 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 2 + * 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 "adl/display.h" +#include "common/stream.h" +#include "common/rect.h" +#include "graphics/surface.h" +#include "common/system.h" +#include "common/str.h" +#include "common/events.h" +#include "common/rect.h" +#include "common/array.h" +#include "engines/engine.h" + +namespace Adl { + +static byte font[64][5] = { + { 0x7c, 0x82, 0xba, 0xb2, 0x9c }, { 0xf8, 0x24, 0x22, 0x24, 0xf8 }, // @A + { 0xfe, 0x92, 0x92, 0x92, 0x6c }, { 0x7c, 0x82, 0x82, 0x82, 0x44 }, // BC + { 0xfe, 0x82, 0x82, 0x82, 0x7c }, { 0xfe, 0x92, 0x92, 0x92, 0x82 }, // DE + { 0xfe, 0x12, 0x12, 0x12, 0x02 }, { 0x7c, 0x82, 0x82, 0xa2, 0xe2 }, // FG + { 0xfe, 0x10, 0x10, 0x10, 0xfe }, { 0x00, 0x82, 0xfe, 0x82, 0x00 }, // HI + { 0x40, 0x80, 0x80, 0x80, 0x7e }, { 0xfe, 0x10, 0x28, 0x44, 0x82 }, // JK + { 0xfe, 0x80, 0x80, 0x80, 0x80 }, { 0xfe, 0x04, 0x18, 0x04, 0xfe }, // LM + { 0xfe, 0x08, 0x10, 0x20, 0xfe }, { 0x7c, 0x82, 0x82, 0x82, 0x7c }, // NO + { 0xfe, 0x12, 0x12, 0x12, 0x0c }, { 0x7c, 0x82, 0xa2, 0x42, 0xbc }, // PQ + { 0xfe, 0x12, 0x32, 0x52, 0x8c }, { 0x4c, 0x92, 0x92, 0x92, 0x64 }, // RS + { 0x02, 0x02, 0xfe, 0x02, 0x02 }, { 0x7e, 0x80, 0x80, 0x80, 0x7e }, // TU + { 0x3e, 0x40, 0x80, 0x40, 0x3e }, { 0xfe, 0x40, 0x30, 0x40, 0xfe }, // VW + { 0xc6, 0x28, 0x10, 0x28, 0xc6 }, { 0x06, 0x08, 0xf0, 0x08, 0x06 }, // XY + { 0xc2, 0xa2, 0x92, 0x8a, 0x86 }, { 0xfe, 0xfe, 0x82, 0x82, 0x82 }, // Z[ + { 0x04, 0x08, 0x10, 0x20, 0x40 }, { 0x82, 0x82, 0x82, 0xfe, 0xfe }, // \] + { 0x20, 0x10, 0x08, 0x10, 0x20 }, { 0x80, 0x80, 0x80, 0x80, 0x80 }, // ^_ + { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0xbe, 0x00, 0x00 }, // ! + { 0x00, 0x0e, 0x00, 0x0e, 0x00 }, { 0x28, 0xfe, 0x28, 0xfe, 0x28 }, // "# + { 0x48, 0x54, 0xfe, 0x54, 0x24 }, { 0x46, 0x26, 0x10, 0xc8, 0xc4 }, // $% + { 0x6c, 0x92, 0xac, 0x40, 0xa0 }, { 0x00, 0x00, 0x0e, 0x00, 0x00 }, // &' + { 0x38, 0x44, 0x82, 0x00, 0x00 }, { 0x00, 0x00, 0x82, 0x44, 0x38 }, // () + { 0x44, 0x28, 0xfe, 0x28, 0x44 }, { 0x10, 0x10, 0x7c, 0x10, 0x10 }, // *+ + { 0x00, 0x80, 0x60, 0x00, 0x00 }, { 0x10, 0x10, 0x10, 0x10, 0x10 }, // ,- + { 0x00, 0x00, 0x80, 0x00, 0x00 }, { 0x40, 0x20, 0x10, 0x08, 0x04 }, // ./ + { 0x7c, 0xa2, 0x92, 0x8a, 0x7c }, { 0x00, 0x84, 0xfe, 0x80, 0x00 }, // 01 + { 0xc4, 0xa2, 0x92, 0x92, 0x8c }, { 0x42, 0x82, 0x92, 0x9a, 0x66 }, // 23 + { 0x30, 0x28, 0x24, 0xfe, 0x20 }, { 0x4e, 0x8a, 0x8a, 0x8a, 0x72 }, // 45 + { 0x78, 0x94, 0x92, 0x92, 0x62 }, { 0x02, 0xe2, 0x12, 0x0a, 0x06 }, // 67 + { 0x6c, 0x92, 0x92, 0x92, 0x6c }, { 0x8c, 0x92, 0x92, 0x52, 0x3c }, // 89 + { 0x00, 0x00, 0x28, 0x00, 0x00 }, { 0x00, 0x80, 0x68, 0x00, 0x00 }, // :; + { 0x10, 0x28, 0x44, 0x82, 0x00 }, { 0x28, 0x28, 0x28, 0x28, 0x28 }, // <= + { 0x00, 0x82, 0x44, 0x28, 0x10 }, { 0x04, 0x02, 0xb2, 0x0a, 0x04 } // >? +}; + +Display::Display() : + _scanlines(false), + _cursorPos(0), + _mode(kModeText) { + _frameBuf = new byte[kFrameBufSize]; + _frameBufSurface = new Graphics::Surface; + _frameBufSurface->create(kWidth * 2, kHeight * 2, Graphics::PixelFormat::createFormatCLUT8()); + + _textBuf = new byte[kTextBufSize]; + memset(_textBuf, ' ' | 0x80, kTextBufSize); + _textBufSurface = new Graphics::Surface; + _textBufSurface->create(kWidth * 2, kHeight * 2, Graphics::PixelFormat::createFormatCLUT8()); + + createFont(); + + struct PixelPos rel = getPixelPos(0, 191); + struct PixelPos absy; + for (int i = 191; i >= 0; --i) { + absy = getPixelPos(0, i); + if (absy.rowAddr != rel.rowAddr) + debug("%i: %04x %04x", i, absy.rowAddr, rel.rowAddr); + moveY(rel, false); + } + absy = getPixelPos(0, 191); + if (absy.rowAddr != rel.rowAddr) + debug("%i: %04x %04x", 191, absy.rowAddr, rel.rowAddr); + + rel = getPixelPos(0, 0); + for (int i = 0; i < 192; ++i) { + absy = getPixelPos(0, i); + if (absy.rowAddr != rel.rowAddr) + debug("%i: %04x %04x", i, absy.rowAddr, rel.rowAddr); + moveY(rel, true); + } + absy = getPixelPos(0, 0); + if (absy.rowAddr != rel.rowAddr) + debug("%i: %04x %04x", 191, absy.rowAddr, rel.rowAddr); +} + +Display::~Display() { + delete[] _frameBuf; + _frameBufSurface->free(); + delete _frameBufSurface; + + delete[] _textBuf; + _textBufSurface->free(); + delete _textBufSurface; + + _font->free(); + delete _font; +} + +void Display::loadFrameBuffer(Common::ReadStream &stream) { + stream.read(_frameBuf, kFrameBufSize); +} + +void Display::decodeScanline(byte *dst, int pitch, byte *src) { + // TODO: shift secondPal by half a pixel + + bool prevOn = false; + + for (uint j = 0; j < 39; ++j) { + bool secondPal = src[j] & 0x80; + byte cur = src[j]; + byte next = 0; + if (j != 39) + next = src[j + 1]; + + for (uint k = 0; k < 7; ++k) { + bool curOn = cur & (1 << k); + bool nextOn; + + if (k != 6) + nextOn = cur & (1 << (k + 1)); + else + nextOn = next & 1; + + byte color; + if (curOn == prevOn || curOn == nextOn) + color = curOn ? 1 : 0; + else { + if (secondPal) + color = (curOn == ((j + k) % 2) ? 5 : 4); + else + color = (curOn == ((j + k) % 2) ? 3 : 2); + } + + dst[0] = color; + dst[1] = color; + + if (!_scanlines) { + dst[pitch] = color; + dst[pitch + 1] = color; + } + + dst += 2; + prevOn = curOn; + } + } +} + +Display::PixelPos Display::getPixelPos(byte x, byte y) { + PixelPos pixelPos; + + // FIXME: check X, Y range + + byte offsetL = y & 0xc0; + offsetL |= offsetL >> 2; + byte offsetH = y; + y <<= 2; + offsetH <<= 1; + offsetH |= y >> 7; + y <<= 1; + offsetH <<= 1; + offsetH |= y >> 7; + y <<= 1; + offsetL >>= 1; + offsetL |= y & 0x80; + y <<= 1; + offsetH = offsetH & 0x1f; + pixelPos.rowAddr = (offsetH << 8) | offsetL; + pixelPos.byteOffset = x / 7; + pixelPos.bitMask = 0x80 | (1 << x % 7); + + return pixelPos; +} + +byte Display::getPixelColor(byte offset, byte color) { + if (offset & 1) { + byte c = color << 1; + if (c >= 0x40 && c < 0xc0) + return color ^ 0x7f; + } + + return color; +} + +void Display::decodeFrameBuffer() { + byte *src = _frameBuf; + int pitch = _frameBufSurface->pitch; + for (int j = 0; j < 8; ++j) { + for (int i = 0; i < 8; ++i) { + byte *dst = (byte *)_frameBufSurface->getPixels() + pitch * 2 * (i * 8 + j); + decodeScanline(dst, pitch, src); + src += 40; + dst += pitch * 2 * 64; + decodeScanline(dst, pitch, src); + src += 40; + dst += pitch * 2 * 64; + decodeScanline(dst, pitch, src); + src += 48; + dst += pitch * 2 * 64; + } + } +} + +void Display::drawPixel(byte x, byte y, byte color) { + PixelPos p = getPixelPos(x, y); + byte c = getPixelColor(p.byteOffset, color); + byte *b = _frameBuf + p.rowAddr + p.byteOffset; + c ^= *b; + c &= p.bitMask; + c ^= *b; + *b = c; +} + +void Display::moveX(PixelPos &p, byte &color, bool left) { + if (left) { + byte bit = p.bitMask; + bool b = bit & 1; + bit >>= 1; + if (!b) { + bit ^= 0xc0; + p.bitMask = bit; + return; + } + --p.byteOffset; + if (p.byteOffset & 0x80) + p.byteOffset = 39; + p.bitMask = 0xc0; + } else { + byte bit = p.bitMask; + bit <<= 1; + bit ^= 0x80; + if (bit & 0x80) { + p.bitMask = bit; + return; + } + p.bitMask = 0x81; + ++p.byteOffset; + if (p.byteOffset == 40) + p.byteOffset = 0; + } + + color = getPixelColor(p.byteOffset, color); +} + +void Display::moveY(PixelPos &p, bool down) { + if (!down) { + if (p.rowAddr & 0x1c00) + p.rowAddr -= 0x400; + else if (p.rowAddr & 0x380) + p.rowAddr += 0x1b80; + else { + p.rowAddr += 0x1f58; + if (!(p.rowAddr & 0x80)) + p.rowAddr += 0x78; // Wrap around + } + } else { + p.rowAddr += 0x400; + if (p.rowAddr & 0x1c00) + return; + else if ((p.rowAddr & 0x380) != 0x380) + p.rowAddr -= 0x1f80; + else { + p.rowAddr -= 0x2358; + if ((p.rowAddr & 0x78) == 0x78) + p.rowAddr -= 0x78; // Wrap around + } + } +} + +void Display::drawNextPixel(Display::PixelPos &p, byte &color, byte bits, byte quadrant) { + if (bits & 4) { + byte b = (_frameBuf[p.rowAddr + p.byteOffset] ^ color) & p.bitMask; + _frameBuf[p.rowAddr + p.byteOffset] ^= b; + } + + bits += quadrant; + + if (bits & 1) + moveX(p, color, bits & 2); + else + moveY(p, bits & 2); +} + +void Display::drawRightAngles(Common::Array<byte> &rightAngles, Common::Point p, byte rotation, byte scaling, byte color) { + const byte stepping[] = { + 0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5, + 0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18, + 0xff + }; + + PixelPos pos = getPixelPos(p.x, p.y); + byte c = getPixelColor(pos.byteOffset, color); + + byte quadrant = rotation >> 4; + rotation &= 0xf; + byte xStep = stepping[rotation]; + byte yStep = stepping[(rotation ^ 0xf) + 1] + 1; + + for (uint i = 0; i < rightAngles.size(); ++i) { + byte b = rightAngles[i]; + + do { + byte xFrac = 0x80; + byte yFrac = 0x80; + for (uint j = 0; j < scaling; ++j) { + if (xFrac + xStep + 1 > 255) + drawNextPixel(pos, c, b, quadrant); + xFrac += xStep + 1; + if (yFrac + yStep > 255) + drawNextPixel(pos, c, b, quadrant + 1); + yFrac += yStep; + } + b >>= 3; + } while (b != 0); + } +} + +void Display::drawLine(Common::Point p1, Common::Point p2, byte color) { + PixelPos p = getPixelPos(p1.x, p1.y); + byte c = getPixelColor(p.byteOffset, color); + + int16 deltaX = p2.x - p1.x; + byte dir = deltaX >> 8; + + if (deltaX < 0) + deltaX = -deltaX; + + int16 err = deltaX; + + int16 deltaY = p2.y - p1.y - 1; + dir >>= 1; + if (deltaY >= 0) { + deltaY = -deltaY - 2; + dir |= 0x80; + } + + int16 steps = deltaY - deltaX; + + err += deltaY + 1; + + while (1) { + byte *b = _frameBuf + p.rowAddr + p.byteOffset; + byte d = *b; + d ^= c; + d &= p.bitMask; + d ^= *b; + *b = d; + + if (++steps == 0) + return; + + if (err < 0) { + moveY(p, dir & 0x80); + err += deltaX; + } else { + moveX(p, c, dir & 0x40); + err += deltaY + 1; + } + } +} + +void Display::clear(byte color) { + for (uint i = 0; i < kFrameBufSize; ++i) + _frameBuf[i] = getPixelColor(i & 1, color); +} + +void Display::updateTextSurface() { + for (uint row = 0; row < 24; ++row) + for (uint col = 0; col < 40; ++col) { + char c = _textBuf[row * 40 + col]; + + Common::Rect r(7 * 2, 8 * 2); + r.translate(((c & 0x3f) % 16) * 7 * 2, (c & 0x3f) / 16 * 8 * 2); + + if (!(c & 0x80)) { + if (!(c & 0x40) || ((g_system->getMillis() / 270) & 1)) + r.translate(0, 4 * 8 * 2); + } + + _textBufSurface->copyRectToSurface(*_font, col * 7 * 2, row * 8 * 2, r); + } +} + +void Display::printString(const Common::String &str) { + Common::String::const_iterator it; + for (it = str.begin(); it != str.end(); ++it) { + byte b = *it; + + if (b == ('\r' | 0x80)) + _cursorPos = (_cursorPos / 40 + 1) * 40; + else if (b < 0x80 || b >= 0xa0) + _textBuf[_cursorPos++] = b; + + if (_cursorPos == kTextBufSize) { + memmove(_textBuf, _textBuf + 40, kTextBufSize - 40); + memset(_textBuf + kTextBufSize - 40, ' ' | 0x80, 40); + _cursorPos -= 40; + } + } + + updateTextSurface(); +} + +void Display::printASCIIString(const Common::String &str) { + Common::String aStr; + + Common::String::const_iterator it; + for (it = str.begin(); it != str.end(); ++it) + aStr += *it | 0x80; + + printString(aStr); +} + +void Display::drawChar(byte c, int x, int y) { + byte *buf = (byte *)_font->getPixels() + y * _font->pitch + x; + + for (uint row = 0; row < 8; ++row) { + for (uint col = 1; col < 6; ++col) + if (font[c][col - 1] & (1 << row)) { + buf[col * 2] = 1; + buf[col * 2 + 1] = 1; + + if (!_scanlines) { + buf[_font->pitch + col * 2] = 1; + buf[_font->pitch + col * 2 + 1] = 1; + } + } + + buf += 2 * _font->pitch; + } +} + +void Display::createFont() { + _font = new Graphics::Surface; + _font->create(16 * 7 * 2, 4 * 8 * 2 * 2, Graphics::PixelFormat::createFormatCLUT8()); + + for (uint i = 0; i < 4; ++i) + for (uint j = 0; j < 16; ++j) + drawChar(i * 16 + j, j * 7 * 2, i * 8 * 2); + + // Create inverted font + byte *buf = (byte *)_font->getPixels(); + byte *bufInv = buf + (_font->h / 2) * _font->pitch; + + for (uint row = 0; row < _font->h / 2; ++row) { + if (!_scanlines || !(row & 1)) + for (uint col = 0; col < _font->w; ++col) + bufInv[col] = buf[col] ? 0 : 1; + + buf += _font->pitch; + bufInv += _font->pitch; + } +} + +void Display::updateScreen() { + if (_mode == kModeText) { + g_system->copyRectToScreen(_textBufSurface->getPixels(), _textBufSurface->pitch, 0, 0, _textBufSurface->w, _textBufSurface->h); + } else if (_mode == kModeHires) { + g_system->copyRectToScreen(_frameBufSurface->getPixels(), _frameBufSurface->pitch, 0, 0, _frameBufSurface->w, _frameBufSurface->h); + } else { + g_system->copyRectToScreen(_frameBufSurface->getPixels(), _frameBufSurface->pitch, 0, 0, _frameBufSurface->w, _frameBufSurface->h - 4 * 8 * 2); + g_system->copyRectToScreen(_textBufSurface->getBasePtr(0, _textBufSurface->h - 4 * 8 * 2), _textBufSurface->pitch, 0, _textBufSurface->h - 4 * 8 * 2, _textBufSurface->w, 4 * 8 * 2); + } +} + +Common::String Display::inputString(byte prompt) { + Common::String s; + + if (prompt > 0) + printString(Common::String(prompt)); + + while (1) { + byte b = inputKey(); + + if (g_engine->shouldQuit()) + return 0; + + if (b == 0) + continue; + + if (b == ('\r' | 0x80)) { + s += b; + printString(Common::String(b)); + return s; + } + + if (b < 0xa0) { + switch (b) { + case Common::KEYCODE_BACKSPACE | 0x80: + if (!s.empty()) { + --_cursorPos; + _textBuf[_cursorPos] = ' ' | 0x80; + s.deleteLastChar(); + } + break; + }; + } else { + s += b; + printString(Common::String(b)); + } + } +} + +byte Display::convertKey(uint16 ascii) { + ascii = toupper(ascii); + + if (ascii >= 0x80) + return 0; + + ascii |= 0x80; + + if (ascii >= 0x80 && ascii <= 0xe0) + return ascii; + + return 0; +} + +byte Display::inputKey() { + Common::EventManager *ev = g_system->getEventManager(); + + byte orgChar = _textBuf[_cursorPos]; + _textBuf[_cursorPos] = (orgChar & 0x3f) | 0x40; + + byte key = 0; + + while (!g_engine->shouldQuit() && key == 0) { + Common::Event event; + if (ev->pollEvent(event)) { + if (event.type != Common::EVENT_KEYDOWN) + continue; + + if (event.kbd.flags & Common::KBD_CTRL) { + if (event.kbd.keycode == Common::KEYCODE_q) + g_engine->quitGame(); + continue; + } + + switch (event.kbd.keycode) { + case Common::KEYCODE_BACKSPACE: + case Common::KEYCODE_RETURN: + key = convertKey(event.kbd.keycode); + break; + default: + if (event.kbd.ascii >= 0x20 && event.kbd.ascii < 0x80) + key = convertKey(event.kbd.ascii); + }; + } + + updateTextSurface(); + updateScreen(); + g_system->updateScreen(); + g_system->delayMillis(16); + } + + _textBuf[_cursorPos] = orgChar; + return key; +} + +void Display::delay(uint32 ms) { + Common::EventManager *ev = g_system->getEventManager(); + + uint32 start = g_system->getMillis(); + + while (!g_engine->shouldQuit() && g_system->getMillis() - start < ms) { + Common::Event event; + if (ev->pollEvent(event)) { + if (event.type == Common::EVENT_KEYDOWN && (event.kbd.flags & Common::KBD_CTRL)) { + switch(event.kbd.keycode) { + case Common::KEYCODE_q: + g_engine->quitGame(); + break; + default: + break; + } + } + } + updateScreen(); + g_system->updateScreen(); + g_system->delayMillis(16); + } +} + +void Display::home() { + memset(_textBuf, ' ' | 0x80, kTextBufSize); + _cursorPos = 0; +} + +} // End of namespace Adl diff --git a/engines/adl/display.h b/engines/adl/display.h new file mode 100644 index 0000000000..eabf340573 --- /dev/null +++ b/engines/adl/display.h @@ -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 2 + * 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. + * + */ + +#ifndef ADL_DISPLAY_H +#define ADL_DISPLAY_H + +#include <common/types.h> +#include <common/array.h> + +namespace Common { +class ReadStream; +class String; +class Point; +} + +namespace Graphics { +class Surface; +} + +namespace Adl { + +class Display { +public: + enum Mode { + kModeHires, + kModeText, + kModeMixed + }; + + Display(); + ~Display(); + void loadFrameBuffer(Common::ReadStream &stream); + void decodeFrameBuffer(); + void printString(const Common::String &str); + void printASCIIString(const Common::String &str); + void updateScreen(); + Common::String inputString(byte prompt = 0); + void delay(uint32 ms); + void setMode(Mode mode) { _mode = mode; } + byte inputKey(); + void home(); + void drawPixel(byte x, byte y, byte color); + void drawLine(Common::Point p1, Common::Point p2, byte color); + void clear(byte color); + void drawRightAngles(Common::Array<byte> &rightAngles, Common::Point p, byte rotation, byte scaling, byte color); + +private: + enum { + kWidth = 280, + kHeight = 192, + kFrameBufSize = 0x2000, + kTextBufSize = 40 * 24 + }; + + struct PixelPos { + uint16 rowAddr; + byte byteOffset; + byte bitMask; + }; + + void decodeScanline(byte *dst, int pitch, byte *src); + PixelPos getPixelPos(byte x, byte y); + byte getPixelColor(byte x, byte color); + void drawChar(byte c, int x, int y); + void createFont(); + void updateTextSurface(); + byte convertKey(uint16 ascii); + void moveX(PixelPos &p, byte &color, bool left); + void moveY(PixelPos &p, bool down); + void drawNextPixel(Display::PixelPos &p, byte &color, byte bits, byte quadrant); + + bool _scanlines; + byte *_frameBuf; + byte *_textBuf; + Graphics::Surface *_frameBufSurface; + Graphics::Surface *_textBufSurface; + Graphics::Surface *_font; + int _cursorPos; + Mode _mode; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/module.mk b/engines/adl/module.mk new file mode 100644 index 0000000000..8f86eeec68 --- /dev/null +++ b/engines/adl/module.mk @@ -0,0 +1,19 @@ +MODULE := engines/adl + +MODULE_OBJS := \ + detection.o \ + display.o \ + adl.o \ + adl_v1.o \ + parser.o + +MODULE_DIRS += \ + engines/adl + +# This module can be built as a plugin +ifeq ($(ENABLE_ADL), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/adl/parser.cpp b/engines/adl/parser.cpp new file mode 100644 index 0000000000..cdbaf3ba6c --- /dev/null +++ b/engines/adl/parser.cpp @@ -0,0 +1,172 @@ +/* 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 2 + * 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 "adl/adl.h" +#include "adl/parser.h" +#include "adl/display.h" + +#include "engines/engine.h" + +#include "common/str.h" +#include "common/stream.h" +#include "common/debug.h" +#include "common/textconsole.h" + +namespace Adl { + +Parser::Parser(AdlEngine &engine, Display &display) : + _engine(engine), + _display(display) { + +} + +void Parser::loadWords(Common::ReadStream &stream, WordMap &map) { + uint index = 0; + + while (1) { + ++index; + + byte buf[kWordSize]; + + if (stream.read(buf, kWordSize) < kWordSize) + error("Error reading word list"); + + Common::String word((char *)buf, kWordSize); + + if (!map.contains(word)) + map[word] = index; + + byte synonyms = stream.readByte(); + + if (stream.err() || stream.eos()) + error("Error reading word list"); + + if (synonyms == 0xff) + break; + + for (uint i = 0; i < synonyms; ++i) { + if (stream.read((char *)buf, kWordSize) < kWordSize) + error("Error reading word list"); + + word = Common::String((char *)buf, kWordSize); + + if (!map.contains(word)) + map[word] = index; + } + } +} + +Common::String Parser::getLine() { + // Original engine uses a global here, which isn't reset between + // calls and may not match actual mode + bool textMode = false; + + while (1) { + Common::String line = _display.inputString(A2CHAR('?')); + + if (g_engine->shouldQuit()) + return ""; + + if ((byte)line[0] == ('\r' | 0x80)) { + textMode = !textMode; + _display.setMode(textMode ? Display::kModeText : Display::kModeMixed); + continue; + } + + // Remove the return + line.deleteLastChar(); + return line; + } +} + +Common::String Parser::getWord(const Common::String &line, uint &index) { + Common::String str; + + for (uint i = 0; i < 8; ++i) + str += (char)(A2CHAR(' ')); + + int copied = 0; + + // Skip initial whitespace + while (1) { + if (index == line.size()) + return str; + if (line[index] != (char)(A2CHAR(' '))) + break; + ++index; + } + + // Copy up to 8 characters + while (1) { + if (copied < 8) + str.setChar(line[index], copied++); + + index++; + + if (index == line.size() || line[index] == (char)(A2CHAR(' '))) + return str; + } +} + +void Parser::getInput(uint &verb, uint &noun) { + while (1) { + _display.printString(_engine.getExeString(STR_COMMON_ENTERCMD)); + Common::String line = getLine(); + + if (g_engine->shouldQuit()) + return; + + uint index = 0; + Common::String verbStr = getWord(line, index); + debug("Verb: \"%s\"", appleToAscii(verbStr).c_str()); + + if (!_verbs.contains(verbStr)) { + Common::String err = _engine.getExeString(STR_COMMON_VERBERR); + for (uint i = 0; i < verbStr.size(); ++i) + err.setChar(verbStr[i], i + 19); + _display.printString(err); + continue; + } + + verb = _verbs[verbStr]; + debug("Verb ID: %i", verb); + + Common::String nounStr = getWord(line, index); + debug("Noun: \"%s\"", appleToAscii(nounStr).c_str()); + + if (!_nouns.contains(nounStr)) { + Common::String err = _engine.getExeString(STR_COMMON_NOUNERR); + for (uint i = 0; i < verbStr.size(); ++i) + err.setChar(verbStr[i], i + 19); + for (uint i = 0; i < nounStr.size(); ++i) + err.setChar(nounStr[i], i + 30); + _display.printString(err); + continue; + } + + noun = _nouns[nounStr]; + debug("Noun ID: %i", noun); + return; + } +} + +} // End of namespace Adl diff --git a/engines/adl/parser.h b/engines/adl/parser.h new file mode 100644 index 0000000000..3c191d90f6 --- /dev/null +++ b/engines/adl/parser.h @@ -0,0 +1,66 @@ +/* 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 2 + * 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. + * + */ + +#ifndef ADL_PARSER_H +#define ADL_PARSER_H + +#include "common/types.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +namespace Common { +class ReadStream; +class String; +} + +namespace Adl { + +class Display; + +class Parser { +public: + Parser(AdlEngine &engine, Display &display); + + void loadVerbs(Common::ReadStream &stream) { loadWords(stream, _verbs); } + void loadNouns(Common::ReadStream &stream) { loadWords(stream, _nouns); } + void getInput(uint &verb, uint &noun); + +private: + enum { + kWordSize = 8 + }; + + typedef Common::HashMap<Common::String, uint> WordMap; + + void loadWords(Common::ReadStream &stream, WordMap &map); + Common::String getLine(); + Common::String getWord(const Common::String &line, uint &index); + + AdlEngine &_engine; + Display &_display; + WordMap _verbs; + WordMap _nouns; +}; + +} // End of namespace Adl + +#endif |