diff options
Diffstat (limited to 'engines/adl/adl.cpp')
-rw-r--r-- | engines/adl/adl.cpp | 1068 |
1 files changed, 673 insertions, 395 deletions
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp index 1ab74c3cf6..b6af54962e 100644 --- a/engines/adl/adl.cpp +++ b/engines/adl/adl.cpp @@ -38,25 +38,61 @@ #include "adl/adl.h" #include "adl/display.h" #include "adl/detection.h" +#include "adl/graphics.h" +#include "adl/speaker.h" namespace Adl { AdlEngine::~AdlEngine() { delete _display; + delete _graphics; + delete _speaker; + delete _console; + delete _dumpFile; } AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) : Engine(syst), + _dumpFile(nullptr), _display(nullptr), - _gameDescription(gd), + _graphics(nullptr), _isRestarting(false), _isRestoring(false), + _skipOneCommand(false), + _gameDescription(gd), _saveVerb(0), _saveNoun(0), _restoreVerb(0), _restoreNoun(0), _canSaveNow(false), _canRestoreNow(false) { + + DebugMan.addDebugChannel(kDebugChannelScript, "Script", "Trace script execution"); +} + +bool AdlEngine::pollEvent(Common::Event &event) const { + _console->onFrame(); + + if (g_system->getEventManager()->pollEvent(event)) { + if (event.type != Common::EVENT_KEYDOWN) + return false; + + if (event.kbd.flags & Common::KBD_CTRL) { + if (event.kbd.keycode == Common::KEYCODE_q) { + quitGame(); + return false; + } + + if (event.kbd.keycode == Common::KEYCODE_d) { + _console->attach(); + return false; + } + } + + return true; + } + + return false; } Common::String AdlEngine::readString(Common::ReadStream &stream, byte until) const { @@ -82,33 +118,28 @@ Common::String AdlEngine::readStringAt(Common::SeekableReadStream &stream, uint return readString(stream, until); } -void AdlEngine::printMessage(uint idx, bool wait) const { - Common::String msg = _messages[idx - 1]; - wordWrap(msg); - _display->printString(msg); +void AdlEngine::openFile(Common::File &file, const Common::String &name) const { + if (!file.open(name)) + error("Error opening '%s'", name.c_str()); +} - if (wait) - delay(14 * 166018 / 1000); +void AdlEngine::printMessage(uint idx) { + printString(loadMessage(idx)); } -void AdlEngine::delay(uint32 ms) const { - Common::EventManager *ev = g_system->getEventManager(); +Common::String AdlEngine::getItemDescription(const Item &item) const { + if (item.description > 0) + return loadMessage(item.description); + else + return Common::String(); +} +void AdlEngine::delay(uint32 ms) const { uint32 start = g_system->getMillis(); - while (!g_engine->shouldQuit() && g_system->getMillis() - start < ms) { + while (!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; - } - } - } + pollEvent(event); g_system->delayMillis(16); } } @@ -122,7 +153,7 @@ Common::String AdlEngine::inputString(byte prompt) const { while (1) { byte b = inputKey(); - if (g_engine->shouldQuit() || _isRestoring) + if (shouldQuit() || _isRestoring) return 0; if (b == 0) @@ -153,25 +184,18 @@ Common::String AdlEngine::inputString(byte prompt) const { } } -byte AdlEngine::inputKey() const { - Common::EventManager *ev = g_system->getEventManager(); - +byte AdlEngine::inputKey(bool showCursor) const { byte key = 0; - _display->showCursor(true); + if (showCursor) + _display->showCursor(true); - while (!g_engine->shouldQuit() && !_isRestoring && key == 0) { + while (!shouldQuit() && !_isRestoring && key == 0) { Common::Event event; - if (ev->pollEvent(event)) { + if (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: @@ -192,9 +216,12 @@ byte AdlEngine::inputKey() const { return key; } -void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { +void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri) const { uint index = 0; + map.clear(); + pri.clear(); + while (1) { ++index; @@ -208,6 +235,8 @@ void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { if (!map.contains(word)) map[word] = index; + pri.push_back(Console::toAscii(word)); + byte synonyms = stream.readByte(); if (stream.err() || stream.eos()) @@ -229,6 +258,8 @@ void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { } void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) { + commands.clear(); + while (1) { Command command; command.room = stream.readByte(); @@ -264,10 +295,238 @@ void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) { } } +void AdlEngine::checkInput(byte verb, byte noun) { + // Try room-local command list first + if (doOneCommand(_roomData.commands, verb, noun)) + return; + + // If no match was found, try the global list + if (!doOneCommand(_roomCommands, verb, noun)) + printMessage(_messageIds.dontUnderstand); +} + +bool AdlEngine::isInputValid(byte verb, byte noun, bool &is_any) { + if (isInputValid(_roomData.commands, verb, noun, is_any)) + return true; + return isInputValid(_roomCommands, verb, noun, is_any); +} + +bool AdlEngine::isInputValid(const Commands &commands, byte verb, byte noun, bool &is_any) { + Commands::const_iterator cmd; + + is_any = false; + for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { + ScriptEnv env(*cmd, _state.room, verb, noun); + if (matchCommand(env)) { + if (cmd->verb == IDI_ANY || cmd->noun == IDI_ANY) + is_any = true; + return true; + } + } + + return false; +} + +typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine> OpcodeV1; +#define SetOpcodeTable(x) table = &x; +#define Opcode(x) table->push_back(new OpcodeV1(this, &AdlEngine::x)) +#define OpcodeUnImpl() table->push_back(new OpcodeV1(this, 0)) + +void AdlEngine::setupOpcodeTables() { + Common::Array<const Opcode *> *table = 0; + + SetOpcodeTable(_condOpcodes); + // 0x00 + OpcodeUnImpl(); + OpcodeUnImpl(); + OpcodeUnImpl(); + Opcode(o1_isItemInRoom); + // 0x04 + OpcodeUnImpl(); + Opcode(o1_isMovesGT); + Opcode(o1_isVarEQ); + OpcodeUnImpl(); + // 0x08 + OpcodeUnImpl(); + Opcode(o1_isCurPicEQ); + Opcode(o1_isItemPicEQ); + + SetOpcodeTable(_actOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o1_varAdd); + Opcode(o1_varSub); + Opcode(o1_varSet); + // 0x04 + Opcode(o1_listInv); + Opcode(o1_moveItem); + Opcode(o1_setRoom); + Opcode(o1_setCurPic); + // 0x08 + Opcode(o1_setPic); + Opcode(o1_printMsg); + Opcode(o1_setLight); + Opcode(o1_setDark); + // 0x0c + OpcodeUnImpl(); + Opcode(o1_quit); + OpcodeUnImpl(); + Opcode(o1_save); + // 0x10 + Opcode(o1_restore); + Opcode(o1_restart); + Opcode(o1_placeItem); + Opcode(o1_setItemPic); + // 0x14 + Opcode(o1_resetPic); + Opcode(o1_goDirection<IDI_DIR_NORTH>); + Opcode(o1_goDirection<IDI_DIR_SOUTH>); + Opcode(o1_goDirection<IDI_DIR_EAST>); + // 0x18 + Opcode(o1_goDirection<IDI_DIR_WEST>); + Opcode(o1_goDirection<IDI_DIR_UP>); + Opcode(o1_goDirection<IDI_DIR_DOWN>); + Opcode(o1_takeItem); + // 0x1c + Opcode(o1_dropItem); + Opcode(o1_setRoomPic); +} + +void AdlEngine::initState() { + _state = State(); + + initGameState(); +} + +byte AdlEngine::roomArg(byte room) const { + return room; +} + +void AdlEngine::clearScreen() const { + _display->setMode(DISPLAY_MODE_MIXED); + _display->clear(0x00); +} + +void AdlEngine::drawPic(byte pic, Common::Point pos) const { + if (_roomData.pictures.contains(pic)) + _graphics->drawPic(*_roomData.pictures[pic]->createReadStream(), pos); + else + _graphics->drawPic(*_pictures[pic]->createReadStream(), pos); +} + +void AdlEngine::bell(uint count) const { + _speaker->bell(count); +} + +const Room &AdlEngine::getRoom(uint i) const { + if (i < 1 || i > _state.rooms.size()) + error("Room %i out of range [1, %i]", i, _state.rooms.size()); + + return _state.rooms[i - 1]; +} + +Room &AdlEngine::getRoom(uint i) { + if (i < 1 || i > _state.rooms.size()) + error("Room %i out of range [1, %i]", i, _state.rooms.size()); + + return _state.rooms[i - 1]; +} + +const Room &AdlEngine::getCurRoom() const { + return getRoom(_state.room); +} + +Room &AdlEngine::getCurRoom() { + return getRoom(_state.room); +} + +const Item &AdlEngine::getItem(uint i) const { + Common::List<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->id == i) + return *item; + + error("Item %i not found", i); +} + +Item &AdlEngine::getItem(uint i) { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->id == i) + return *item; + + error("Item %i not found", i); +} + +byte AdlEngine::getVar(uint i) const { + if (i >= _state.vars.size()) + error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); + + return _state.vars[i]; +} + +void AdlEngine::setVar(uint i, byte value) { + if (i >= _state.vars.size()) + error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); + + _state.vars[i] = value; +} + +void AdlEngine::takeItem(byte noun) { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != _state.room) + continue; + + if (item->state == IDI_ITEM_DOESNT_MOVE) { + printMessage(_messageIds.itemDoesntMove); + return; + } + + if (item->state == IDI_ITEM_DROPPED) { + item->room = IDI_ANY; + return; + } + + Common::Array<byte>::const_iterator pic; + for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { + if (*pic == getCurRoom().curPicture) { + item->room = IDI_ANY; + item->state = IDI_ITEM_DROPPED; + return; + } + } + } + + printMessage(_messageIds.itemNotHere); +} + +void AdlEngine::dropItem(byte noun) { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != IDI_ANY) + continue; + + item->room = _state.room; + item->state = IDI_ITEM_DROPPED; + return; + } + + printMessage(_messageIds.dontUnderstand); +} + Common::Error AdlEngine::run() { + _console = new Console(this); + _speaker = new Speaker(); _display = new Display(); - loadData(); + setupOpcodeTables(); + + init(); int saveSlot = ConfMan.getInt("save_slot"); if (saveSlot >= 0) { @@ -278,13 +537,14 @@ Common::Error AdlEngine::run() { } else { runIntro(); initState(); + _display->printAsciiString(_strings.lineFeeds); } _display->setMode(DISPLAY_MODE_MIXED); - _display->printAsciiString("\r\r\r\r\r"); while (1) { uint verb = 0, noun = 0; + _isRestarting = false; // When restoring from the launcher, we don't read // input on the first iteration. This is needed to @@ -292,9 +552,11 @@ Common::Error AdlEngine::run() { // restoring in-game brings us to the same game state. // (Also see comment below.) if (!_isRestoring) { - clearScreen(); showRoom(); + if (_isRestarting) + continue; + _canSaveNow = _canRestoreNow = true; getInput(verb, noun); _canSaveNow = _canRestoreNow = false; @@ -305,8 +567,7 @@ Common::Error AdlEngine::run() { // If we just restored from the GMM, we skip this command // set, as no command has been input by the user if (!_isRestoring) - if (!doOneCommand(_roomCommands, verb, noun)) - printMessage(_messageIds.dontUnderstand); + checkInput(verb, noun); } if (_isRestoring) { @@ -323,12 +584,15 @@ Common::Error AdlEngine::run() { } // Restarting does end command processing - if (_isRestarting) { - _isRestarting = false; + if (_isRestarting) continue; - } doAllCommands(_globalCommands, verb, noun); + + if (_isRestarting) + continue; + + advanceClock(); _state.moves++; } @@ -382,6 +646,8 @@ Common::Error AdlEngine::loadGameState(int slot) { _state.room = inFile->readByte(); _state.moves = inFile->readByte(); _state.isDark = inFile->readByte(); + _state.time.hours = inFile->readByte(); + _state.time.minutes = inFile->readByte(); uint32 size = inFile->readUint32BE(); if (size != _state.rooms.size()) @@ -390,18 +656,20 @@ Common::Error AdlEngine::loadGameState(int slot) { for (uint i = 0; i < size; ++i) { _state.rooms[i].picture = inFile->readByte(); _state.rooms[i].curPicture = inFile->readByte(); + _state.rooms[i].isFirstTime = inFile->readByte(); } size = inFile->readUint32BE(); if (size != _state.items.size()) error("Item count mismatch (expected %i; found %i)", _state.items.size(), size); - for (uint i = 0; i < size; ++i) { - _state.items[i].room = inFile->readByte(); - _state.items[i].picture = inFile->readByte(); - _state.items[i].position.x = inFile->readByte(); - _state.items[i].position.y = inFile->readByte(); - _state.items[i].state = inFile->readByte(); + Common::List<Item>::iterator item; + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + item->room = inFile->readByte(); + item->picture = inFile->readByte(); + item->position.x = inFile->readByte(); + item->position.y = inFile->readByte(); + item->state = inFile->readByte(); } size = inFile->readUint32BE(); @@ -467,20 +735,24 @@ Common::Error AdlEngine::saveGameState(int slot, const Common::String &desc) { outFile->writeByte(_state.room); outFile->writeByte(_state.moves); outFile->writeByte(_state.isDark); + outFile->writeByte(_state.time.hours); + outFile->writeByte(_state.time.minutes); outFile->writeUint32BE(_state.rooms.size()); for (uint i = 0; i < _state.rooms.size(); ++i) { outFile->writeByte(_state.rooms[i].picture); outFile->writeByte(_state.rooms[i].curPicture); + outFile->writeByte(_state.rooms[i].isFirstTime); } outFile->writeUint32BE(_state.items.size()); - for (uint i = 0; i < _state.items.size(); ++i) { - outFile->writeByte(_state.items[i].room); - outFile->writeByte(_state.items[i].picture); - outFile->writeByte(_state.items[i].position.x); - outFile->writeByte(_state.items[i].position.y); - outFile->writeByte(_state.items[i].state); + Common::List<Item>::const_iterator item; + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + outFile->writeByte(item->room); + outFile->writeByte(item->picture); + outFile->writeByte(item->position.x); + outFile->writeByte(item->position.y); + outFile->writeByte(item->state); } outFile->writeUint32BE(_state.vars.size()); @@ -509,29 +781,14 @@ bool AdlEngine::canSaveGameStateCurrently() { // "SAVE GAME". This prevents saving via the GMM in situations where // it wouldn't otherwise be possible to do so. for (cmd = _roomCommands.begin(); cmd != _roomCommands.end(); ++cmd) { - uint offset; - if (matchCommand(*cmd, _saveVerb, _saveNoun, &offset)) - return cmd->script[offset] == IDO_ACT_SAVE; + ScriptEnv env(*cmd, _state.room, _saveVerb, _saveNoun); + if (matchCommand(env)) + return env.op() == IDO_ACT_SAVE; } return false; } -void AdlEngine::wordWrap(Common::String &str) const { - uint end = 39; - - while (1) { - if (str.size() <= end) - return; - - while (str[end] != APPLECHAR(' ')) - --end; - - str.setChar(APPLECHAR('\r'), end); - end += 40; - } -} - byte AdlEngine::convertKey(uint16 ascii) const { ascii = toupper(ascii); @@ -598,6 +855,22 @@ Common::String AdlEngine::getWord(const Common::String &line, uint &index) const } } +Common::String AdlEngine::formatVerbError(const Common::String &verb) const { + Common::String err = _strings.verbError; + for (uint i = 0; i < verb.size(); ++i) + err.setChar(verb[i], i + 19); + return err; +} + +Common::String AdlEngine::formatNounError(const Common::String &verb, const Common::String &noun) const { + Common::String err = _strings.nounError; + for (uint i = 0; i < verb.size(); ++i) + err.setChar(verb[i], i + 19); + for (uint i = 0; i < noun.size(); ++i) + err.setChar(noun[i], i + 30); + return err; +} + void AdlEngine::getInput(uint &verb, uint &noun) { while (1) { _display->printString(_strings.enterCommand); @@ -607,421 +880,349 @@ void AdlEngine::getInput(uint &verb, uint &noun) { return; uint index = 0; - Common::String verbStr = getWord(line, index); + Common::String verbString = getWord(line, index); - if (!_verbs.contains(verbStr)) { - Common::String err = _strings.verbError; - for (uint i = 0; i < verbStr.size(); ++i) - err.setChar(verbStr[i], i + 19); - _display->printString(err); + if (!_verbs.contains(verbString)) { + _display->printString(formatVerbError(verbString)); continue; } - verb = _verbs[verbStr]; + verb = _verbs[verbString]; - Common::String nounStr = getWord(line, index); + Common::String nounString = getWord(line, index); - if (!_nouns.contains(nounStr)) { - Common::String err = _strings.nounError; - 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); + if (!_nouns.contains(nounString)) { + _display->printString(formatNounError(verbString, nounString)); continue; } - noun = _nouns[nounStr]; + noun = _nouns[nounString]; return; } } -void AdlEngine::showRoom() const { - if (!_state.isDark) { - drawPic(getCurRoom().curPicture); - drawItems(); +bool AdlEngine::op_debug(const char *fmt, ...) const { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) { + va_list va; + va_start(va, fmt); + Common::String output = Common::String::vformat(fmt, va); + va_end(va); + + output += '\n'; + if (_dumpFile) { + _dumpFile->write(output.c_str(), output.size()); + return true; + } else + debugN("%s", output.c_str()); } - _display->updateHiResScreen(); - printMessage(getCurRoom().description, false); + return false; } -void AdlEngine::clearScreen() const { - _display->setMode(DISPLAY_MODE_MIXED); - _display->clear(0x00); +int AdlEngine::o1_isItemInRoom(ScriptEnv &e) { + OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + if (getItem(e.arg(1)).room == roomArg(e.arg(2))) + return 2; + + return -1; } -void AdlEngine::drawItems() const { - Common::Array<Item>::const_iterator item; +int AdlEngine::o1_isMovesGT(ScriptEnv &e) { + OP_DEBUG_1("\t&& MOVES > %d", e.arg(1)); - uint dropped = 0; + if (_state.moves > e.arg(1)) + return 1; - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->room != _state.room) - continue; + return -1; +} - if (item->state == IDI_ITEM_MOVED) { - if (getCurRoom().picture == getCurRoom().curPicture) { - const Common::Point &p = _itemOffsets[dropped]; - if (item->isLineArt) - drawLineArt(_lineArt[item->picture - 1], p); - else - drawPic(item->picture, p); - ++dropped; - } - continue; - } +int AdlEngine::o1_isVarEQ(ScriptEnv &e) { + OP_DEBUG_2("\t&& VARS[%d] == %d", e.arg(1), e.arg(2)); - Common::Array<byte>::const_iterator pic; + if (getVar(e.arg(1)) == e.arg(2)) + return 2; - for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { - if (*pic == getCurRoom().curPicture) { - if (item->isLineArt) - drawLineArt(_lineArt[item->picture - 1], item->position); - else - drawPic(item->picture, item->position); - continue; - } - } - } + return -1; } -void AdlEngine::drawNextPixel(Common::Point &p, byte color, byte bits, byte quadrant) const { - if (bits & 4) - _display->putPixel(p, color); +int AdlEngine::o1_isCurPicEQ(ScriptEnv &e) { + OP_DEBUG_1("\t&& GET_CURPIC() == %d", e.arg(1)); - bits += quadrant; + if (getCurRoom().curPicture == e.arg(1)) + return 1; - if (bits & 1) - p.x += (bits & 2 ? -1 : 1); - else - p.y += (bits & 2 ? 1 : -1); + return -1; } -void AdlEngine::drawLineArt(const Common::Array<byte> &lineArt, const Common::Point &pos, byte rotation, byte scaling, byte color) const { - const byte stepping[] = { - 0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5, - 0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18, - 0xff - }; +int AdlEngine::o1_isItemPicEQ(ScriptEnv &e) { + OP_DEBUG_2("\t&& GET_ITEM_PIC(%s) == %d", itemStr(e.arg(1)).c_str(), e.arg(2)); - byte quadrant = rotation >> 4; - rotation &= 0xf; - byte xStep = stepping[rotation]; - byte yStep = stepping[(rotation ^ 0xf) + 1] + 1; - - Common::Point p(pos); - - for (uint i = 0; i < lineArt.size(); ++i) { - byte b = lineArt[i]; - - do { - byte xFrac = 0x80; - byte yFrac = 0x80; - for (uint j = 0; j < scaling; ++j) { - if (xFrac + xStep + 1 > 255) - drawNextPixel(p, color, b, quadrant); - xFrac += xStep + 1; - if (yFrac + yStep > 255) - drawNextPixel(p, color, b, quadrant + 1); - yFrac += yStep; - } - b >>= 3; - } while (b != 0); - } + if (getItem(e.arg(1)).picture == e.arg(2)) + return 2; + + return -1; } -const Room &AdlEngine::getRoom(uint i) const { - if (i < 1 || i > _state.rooms.size()) - error("Room %i out of range [1, %i]", i, _state.rooms.size()); +int AdlEngine::o1_varAdd(ScriptEnv &e) { + OP_DEBUG_2("\tVARS[%d] += %d", e.arg(2), e.arg(1)); - return _state.rooms[i - 1]; + setVar(e.arg(2), getVar(e.arg(2) + e.arg(1))); + return 2; } -Room &AdlEngine::getRoom(uint i) { - if (i < 1 || i > _state.rooms.size()) - error("Room %i out of range [1, %i]", i, _state.rooms.size()); +int AdlEngine::o1_varSub(ScriptEnv &e) { + OP_DEBUG_2("\tVARS[%d] -= %d", e.arg(2), e.arg(1)); - return _state.rooms[i - 1]; + setVar(e.arg(2), getVar(e.arg(2)) - e.arg(1)); + return 2; } -const Room &AdlEngine::getCurRoom() const { - return getRoom(_state.room); +int AdlEngine::o1_varSet(ScriptEnv &e) { + OP_DEBUG_2("\tVARS[%d] = %d", e.arg(1), e.arg(2)); + + setVar(e.arg(1), e.arg(2)); + return 2; } -Room &AdlEngine::getCurRoom() { - return getRoom(_state.room); +int AdlEngine::o1_listInv(ScriptEnv &e) { + OP_DEBUG_0("\tLIST_INVENTORY()"); + + Common::List<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == IDI_ANY) + printMessage(item->description); + + return 0; } -const Item &AdlEngine::getItem(uint i) const { - if (i < 1 || i > _state.items.size()) - error("Item %i out of range [1, %i]", i, _state.items.size()); +int AdlEngine::o1_moveItem(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); - return _state.items[i - 1]; + getItem(e.arg(1)).room = e.arg(2); + return 2; } -Item &AdlEngine::getItem(uint i) { - if (i < 1 || i > _state.items.size()) - error("Item %i out of range [1, %i]", i, _state.items.size()); +int AdlEngine::o1_setRoom(ScriptEnv &e) { + OP_DEBUG_1("\tROOM = %d", e.arg(1)); - return _state.items[i - 1]; + getCurRoom().curPicture = getCurRoom().picture; + _state.room = e.arg(1); + return 1; } -byte AdlEngine::getVar(uint i) const { - if (i >= _state.vars.size()) - error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); +int AdlEngine::o1_setCurPic(ScriptEnv &e) { + OP_DEBUG_1("\tSET_CURPIC(%d)", e.arg(1)); - return _state.vars[i]; + getCurRoom().curPicture = e.arg(1); + return 1; } -void AdlEngine::setVar(uint i, byte value) { - if (i >= _state.vars.size()) - error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); +int AdlEngine::o1_setPic(ScriptEnv &e) { + OP_DEBUG_1("\tSET_PIC(%d)", e.arg(1)); - _state.vars[i] = value; + getCurRoom().picture = getCurRoom().curPicture = e.arg(1); + return 1; } -void AdlEngine::takeItem(byte noun) { - Common::Array<Item>::iterator item; +int AdlEngine::o1_printMsg(ScriptEnv &e) { + OP_DEBUG_1("\tPRINT(%s)", msgStr(e.arg(1)).c_str()); - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != _state.room) - continue; + printMessage(e.arg(1)); + return 1; +} - if (item->state == IDI_ITEM_DOESNT_MOVE) { - printMessage(_messageIds.itemDoesntMove); - return; - } +int AdlEngine::o1_setLight(ScriptEnv &e) { + OP_DEBUG_0("\tLIGHT()"); - if (item->state == IDI_ITEM_MOVED) { - item->room = IDI_NONE; - return; - } + _state.isDark = false; + return 0; +} - Common::Array<byte>::const_iterator pic; - for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { - if (*pic == getCurRoom().curPicture) { - item->room = IDI_NONE; - item->state = IDI_ITEM_MOVED; - return; - } - } +int AdlEngine::o1_setDark(ScriptEnv &e) { + OP_DEBUG_0("\tDARK()"); + + _state.isDark = true; + return 0; +} + +int AdlEngine::o1_save(ScriptEnv &e) { + OP_DEBUG_0("\tSAVE_GAME()"); + + saveGameState(0, ""); + return 0; +} + +int AdlEngine::o1_restore(ScriptEnv &e) { + OP_DEBUG_0("\tRESTORE_GAME()"); + + loadGameState(0); + _isRestoring = false; + return 0; +} + +int AdlEngine::o1_restart(ScriptEnv &e) { + OP_DEBUG_0("\tRESTART_GAME()"); + + _display->printString(_strings.playAgain); + Common::String input = inputString(); + + if (input.size() == 0 || input[0] != APPLECHAR('N')) { + _isRestarting = true; + _display->clear(0x00); + _display->updateHiResScreen(); + _display->printString(_strings.pressReturn); + initState(); + _display->printAsciiString(_strings.lineFeeds); + return -1; } - printMessage(_messageIds.itemNotHere); + return o1_quit(e); } -void AdlEngine::dropItem(byte noun) { - Common::Array<Item>::iterator item; +int AdlEngine::o1_quit(ScriptEnv &e) { + OP_DEBUG_0("\tQUIT_GAME()"); - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != IDI_NONE) - continue; + printMessage(_messageIds.thanksForPlaying); + quitGame(); + return -1; +} - item->room = _state.room; - item->state = IDI_ITEM_MOVED; - return; +int AdlEngine::o1_placeItem(ScriptEnv &e) { + OP_DEBUG_4("\tPLACE_ITEM(%s, %s, (%d, %d))", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str(), e.arg(3), e.arg(4)); + + Item &item = getItem(e.arg(1)); + + item.room = roomArg(e.arg(2)); + item.position.x = e.arg(3); + item.position.y = e.arg(4); + return 4; +} + +int AdlEngine::o1_setItemPic(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ITEM_PIC(%s, %d)", itemStr(e.arg(2)).c_str(), e.arg(1)); + + getItem(e.arg(2)).picture = e.arg(1); + return 2; +} + +int AdlEngine::o1_resetPic(ScriptEnv &e) { + OP_DEBUG_0("\tRESET_PIC()"); + + getCurRoom().curPicture = getCurRoom().picture; + return 0; +} + +template <Direction D> +int AdlEngine::o1_goDirection(ScriptEnv &e) { + OP_DEBUG_0((Common::String("\tGO_") + dirStr(D) + "()").c_str()); + + byte room = getCurRoom().connections[D]; + + if (room == 0) { + printMessage(_messageIds.cantGoThere); + return -1; } - printMessage(_messageIds.dontUnderstand); + getCurRoom().curPicture = getCurRoom().picture; + _state.room = room; + return -1; } -#define ARG(N) (command.script[offset + (N)]) +int AdlEngine::o1_takeItem(ScriptEnv &e) { + OP_DEBUG_0("\tTAKE_ITEM()"); -bool AdlEngine::matchCommand(const Command &command, byte verb, byte noun, uint *actions) const { - if (command.room != IDI_NONE && command.room != _state.room) - return false; + takeItem(e.getNoun()); + return 0; +} - if (command.verb != IDI_NONE && command.verb != verb) - return false; +int AdlEngine::o1_dropItem(ScriptEnv &e) { + OP_DEBUG_0("\tDROP_ITEM()"); + + dropItem(e.getNoun()); + return 0; +} + +int AdlEngine::o1_setRoomPic(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ROOM_PIC(%d, %d)", e.arg(1), e.arg(2)); - if (command.noun != IDI_NONE && command.noun != noun) + getRoom(e.arg(1)).picture = getRoom(e.arg(1)).curPicture = e.arg(2); + return 2; +} + +bool AdlEngine::matchCommand(ScriptEnv &env) const { + if (!env.isMatch() && !_dumpFile) return false; - uint offset = 0; - for (uint i = 0; i < command.numCond; ++i) { - switch (ARG(0)) { - case IDO_CND_ITEM_IN_ROOM: - if (getItem(ARG(1)).room != ARG(2)) - return false; - offset += 3; - break; - case IDO_CND_MOVES_GE: - if (ARG(1) > _state.moves) - return false; - offset += 2; - break; - case IDO_CND_VAR_EQ: - if (getVar(ARG(1)) != ARG(2)) - return false; - offset += 3; - break; - case IDO_CND_CUR_PIC_EQ: - if (getCurRoom().curPicture != ARG(1)) - return false; - offset += 2; - break; - case IDO_CND_ITEM_PIC_EQ: - if (getItem(ARG(1)).picture != ARG(2)) - return false; - offset += 3; - break; - default: - error("Invalid condition opcode %02x", command.script[offset]); - } + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) { + op_debug("IF\n\tROOM == %s", roomStr(env.getCommand().room).c_str()); + op_debug("\t&& SAID(%s, %s)", verbStr(env.getCommand().verb).c_str(), nounStr(env.getCommand().noun).c_str()); } - if (actions) - *actions = offset; + for (uint i = 0; i < env.getCondCount(); ++i) { + byte op = env.op(); + + if (op >= _condOpcodes.size() || !_condOpcodes[op] || !_condOpcodes[op]->isValid()) + error("Unimplemented condition opcode %02x", op); + + int numArgs = (*_condOpcodes[op])(env); + + if (numArgs < 0) { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("FAIL\n"); + return false; + } + + env.skip(numArgs + 1); + } return true; } -void AdlEngine::doActions(const Command &command, byte noun, byte offset) { - for (uint i = 0; i < command.numAct; ++i) { - switch (ARG(0)) { - case IDO_ACT_VAR_ADD: - setVar(ARG(2), getVar(ARG(2) + ARG(1))); - offset += 3; - break; - case IDO_ACT_VAR_SUB: - setVar(ARG(2), getVar(ARG(2)) - ARG(1)); - offset += 3; - break; - case IDO_ACT_VAR_SET: - setVar(ARG(1), ARG(2)); - offset += 3; - break; - case IDO_ACT_LIST_ITEMS: { - Common::Array<Item>::const_iterator item; +void AdlEngine::doActions(ScriptEnv &env) { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("THEN"); - for (item = _state.items.begin(); item != _state.items.end(); ++item) - if (item->room == IDI_NONE) - printMessage(item->description); + for (uint i = 0; i < env.getActCount(); ++i) { + byte op = env.op(); - ++offset; - break; - } - case IDO_ACT_MOVE_ITEM: - getItem(ARG(1)).room = ARG(2); - offset += 3; - break; - case IDO_ACT_SET_ROOM: - getCurRoom().curPicture = getCurRoom().picture; - _state.room = ARG(1); - offset += 2; - break; - case IDO_ACT_SET_CUR_PIC: - getCurRoom().curPicture = ARG(1); - offset += 2; - break; - case IDO_ACT_SET_PIC: - getCurRoom().picture = getCurRoom().curPicture = ARG(1); - offset += 2; - break; - case IDO_ACT_PRINT_MSG: - printMessage(ARG(1)); - offset += 2; - break; - case IDO_ACT_SET_LIGHT: - _state.isDark = false; - ++offset; - break; - case IDO_ACT_SET_DARK: - _state.isDark = true; - ++offset; - break; - case IDO_ACT_SAVE: - saveGameState(0, ""); - ++offset; - break; - case IDO_ACT_LOAD: - loadGameState(0); - ++offset; - // Original engine does not jump out of the loop, - // so we don't either. - // We reset the restore flag, as the restore game - // process is complete - _isRestoring = false; - break; - case IDO_ACT_RESTART: { - _display->printString(_strings.playAgain); - Common::String input = inputString(); - if (input.size() == 0 || input[0] != APPLECHAR('N')) { - _isRestarting = true; - _display->clear(0x00); - _display->updateHiResScreen(); - restartGame(); - return; - } - // Fall-through - } - case IDO_ACT_QUIT: - printMessage(_messageIds.thanksForPlaying); - quitGame(); - return; - case IDO_ACT_PLACE_ITEM: - getItem(ARG(1)).room = ARG(2); - getItem(ARG(1)).position.x = ARG(3); - getItem(ARG(1)).position.y = ARG(4); - offset += 5; - break; - case IDO_ACT_SET_ITEM_PIC: - getItem(ARG(2)).picture = ARG(1); - offset += 3; - break; - case IDO_ACT_RESET_PIC: - getCurRoom().curPicture = getCurRoom().picture; - ++offset; - break; - case IDO_ACT_GO_NORTH: - case IDO_ACT_GO_SOUTH: - case IDO_ACT_GO_EAST: - case IDO_ACT_GO_WEST: - case IDO_ACT_GO_UP: - case IDO_ACT_GO_DOWN: { - byte room = getCurRoom().connections[ARG(0) - IDO_ACT_GO_NORTH]; - - if (room == 0) { - printMessage(_messageIds.cantGoThere); - return; - } + if (op >= _actOpcodes.size() || !_actOpcodes[op] || !_actOpcodes[op]->isValid()) + error("Unimplemented action opcode %02x", op); + + int numArgs = (*_actOpcodes[op])(env); - getCurRoom().curPicture = getCurRoom().picture; - _state.room = room; + if (numArgs < 0) { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("ABORT\n"); return; } - case IDO_ACT_TAKE_ITEM: - takeItem(noun); - ++offset; - break; - case IDO_ACT_DROP_ITEM: - dropItem(noun); - ++offset; - break; - case IDO_ACT_SET_ROOM_PIC: - getRoom(ARG(1)).picture = getRoom(ARG(1)).curPicture = ARG(2); - offset += 3; - break; - default: - error("Invalid action opcode %02x", ARG(0)); - } + + env.skip(numArgs + 1); } -} -#undef ARG + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("END\n"); +} bool AdlEngine::doOneCommand(const Commands &commands, byte verb, byte noun) { Commands::const_iterator cmd; for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { - uint offset = 0; - if (matchCommand(*cmd, verb, noun, &offset)) { - doActions(*cmd, noun, offset); + + if (_skipOneCommand) { + _skipOneCommand = false; + continue; + } + + ScriptEnv env(*cmd, _state.room, verb, noun); + if (matchCommand(env)) { + doActions(env); return true; } } + _skipOneCommand = false; return false; } @@ -1029,10 +1230,87 @@ void AdlEngine::doAllCommands(const Commands &commands, byte verb, byte noun) { Commands::const_iterator cmd; for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { - uint offset = 0; - if (matchCommand(*cmd, verb, noun, &offset)) - doActions(*cmd, noun, offset); + if (_skipOneCommand) { + _skipOneCommand = false; + continue; + } + + ScriptEnv env(*cmd, _state.room, verb, noun); + if (matchCommand(env)) { + doActions(env); + // The original long jumps on restart, so we need to abort here + if (_isRestarting) + return; + } + } + + _skipOneCommand = false; +} + +Common::String AdlEngine::toAscii(const Common::String &str) { + Common::String ascii = Console::toAscii(str); + if (ascii.lastChar() == '\r') + ascii.deleteLastChar(); + // FIXME: remove '\r's inside string? + return ascii; +} + +Common::String AdlEngine::itemStr(uint i) const { + byte desc = getItem(i).description; + byte noun = getItem(i).noun; + Common::String name = Common::String::format("%d", i); + if (noun > 0) { + name += "/"; + name += _priNouns[noun - 1]; + } + if (desc > 0) { + name += "/"; + name += toAscii(loadMessage(desc)); } + return name; +} + +Common::String AdlEngine::itemRoomStr(uint i) const { + switch (i) { + case IDI_ANY: + return "CARRYING"; + case IDI_VOID_ROOM: + return "GONE"; + case IDI_CUR_ROOM: + return "HERE"; + default: + return Common::String::format("%d", i); + } +} + +Common::String AdlEngine::roomStr(uint i) const { + if (i == IDI_ANY) + return "*"; + else + return Common::String::format("%d", i); +} + +Common::String AdlEngine::verbStr(uint i) const { + if (i == IDI_ANY) + return "*"; + else + return Common::String::format("%d/%s", i, _priVerbs[i - 1].c_str()); +} + +Common::String AdlEngine::nounStr(uint i) const { + if (i == IDI_ANY) + return "*"; + else + return Common::String::format("%d/%s", i, _priNouns[i - 1].c_str()); +} + +Common::String AdlEngine::msgStr(uint i) const { + return Common::String::format("%d/%s", i, toAscii(loadMessage(i)).c_str()); +} + +Common::String AdlEngine::dirStr(Direction dir) const { + static const char *dirs[] = { "NORTH", "SOUTH", "EAST", "WEST", "UP", "DOWN" }; + return dirs[dir]; } } // End of namespace Adl |