diff options
author | Walter van Niftrik | 2016-03-07 20:43:37 +0100 |
---|---|---|
committer | Walter van Niftrik | 2016-03-09 10:03:13 +0100 |
commit | 63adab81edc8f44d4b4387352e0869e3042c2a13 (patch) | |
tree | 7fbae76a457730dc61d6d95132c8adb1e0afb1c5 | |
parent | 86d58534e7138c7b58995e1f730c8531ca2d4273 (diff) | |
download | scummvm-rg350-63adab81edc8f44d4b4387352e0869e3042c2a13.tar.gz scummvm-rg350-63adab81edc8f44d4b4387352e0869e3042c2a13.tar.bz2 scummvm-rg350-63adab81edc8f44d4b4387352e0869e3042c2a13.zip |
ADL: Clean up HiRes1Engine class
-rw-r--r-- | engines/adl/adl.cpp | 1266 | ||||
-rw-r--r-- | engines/adl/adl.h | 14 | ||||
-rw-r--r-- | engines/adl/hires1.cpp | 214 | ||||
-rw-r--r-- | engines/adl/hires1.h | 2 |
4 files changed, 746 insertions, 750 deletions
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp index 5b43a6cd31..dd4f4068d1 100644 --- a/engines/adl/adl.cpp +++ b/engines/adl/adl.cpp @@ -41,10 +41,14 @@ namespace Adl { +AdlEngine::~AdlEngine() { + delete _display; +} + AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) : Engine(syst), - _gameDescription(gd), _display(nullptr), + _gameDescription(gd), _isRestarting(false), _isRestoring(false), _saveVerb(0), @@ -55,89 +59,6 @@ AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) : _canRestoreNow(false) { } -AdlEngine::~AdlEngine() { - delete _display; -} - -bool AdlEngine::hasFeature(EngineFeature f) const { - switch (f) { - case kSupportsLoadingDuringRuntime: - case kSupportsSavingDuringRuntime: - case kSupportsRTL: - return true; - default: - return false; - } -} - -Common::Error AdlEngine::run() { - _display = new Display(); - - loadData(); - - int saveSlot = ConfMan.getInt("save_slot"); - if (saveSlot >= 0) { - if (loadGameState(saveSlot).getCode() != Common::kNoError) - error("Failed to load save game from slot %i", saveSlot); - _display->moveCursorTo(Common::Point(0, 23)); - _isRestoring = true; - } else { - runIntro(); - initState(); - } - - _display->setMode(DISPLAY_MODE_MIXED); - _display->printAsciiString("\r\r\r\r\r"); - - while (1) { - uint verb = 0, noun = 0; - - // When restoring from the launcher, we don't read - // input on the first iteration. This is needed to - // ensure that restoring from the launcher and - // restoring in-game brings us to the same game state. - // (Also see comment below.) - if (!_isRestoring) { - clearScreen(); - showRoom(); - - _canSaveNow = _canRestoreNow = true; - getInput(verb, noun); - _canSaveNow = _canRestoreNow = false; - - if (shouldQuit()) - break; - - if (!doOneCommand(_roomCommands, verb, noun)) - printMessage(_messageIds.dontUnderstand); - } - - if (_isRestoring) { - // We restored from the GMM or launcher. As restoring - // with "RESTORE GAME" does not end command processing, - // we don't break it off here either. This essentially - // means that restoring a game will always run through - // the global commands and increase the move counter - // before the first user input. - _display->printAsciiString("\r"); - _isRestoring = false; - verb = _restoreVerb; - noun = _restoreNoun; - } - - // Restarting does end command processing - if (_isRestarting) { - _isRestarting = false; - continue; - } - - doAllCommands(_globalCommands, verb, noun); - _state.moves++; - } - - return Common::kNoError; -} - Common::String AdlEngine::readString(Common::ReadStream &stream, byte until) const { Common::String str; @@ -161,21 +82,6 @@ Common::String AdlEngine::readStringAt(Common::SeekableReadStream &stream, uint return readString(stream, until); } -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; - } -} - void AdlEngine::printMessage(uint idx, bool wait) const { Common::String msg = _messages[idx - 1]; wordWrap(msg); @@ -185,456 +91,254 @@ void AdlEngine::printMessage(uint idx, bool wait) const { delay(14 * 166018 / 1000); } -void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) { - while (1) { - Command command; - command.room = stream.readByte(); +void AdlEngine::delay(uint32 ms) const { + Common::EventManager *ev = g_system->getEventManager(); - if (command.room == 0xff) - return; + uint32 start = g_system->getMillis(); - command.verb = stream.readByte(); - command.noun = stream.readByte(); + 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; + } + } + } + g_system->delayMillis(16); + } +} - byte scriptSize = stream.readByte() - 6; +Common::String AdlEngine::inputString(byte prompt) const { + Common::String s; - command.numCond = stream.readByte(); - command.numAct = stream.readByte(); + if (prompt > 0) + _display->printString(Common::String(prompt)); - for (uint i = 0; i < scriptSize; ++i) - command.script.push_back(stream.readByte()); + while (1) { + byte b = inputKey(); - if (stream.eos() || stream.err()) - error("Failed to read commands"); + if (g_engine->shouldQuit() || _isRestoring) + return 0; - if (command.numCond == 0 && command.script[0] == IDO_ACT_SAVE) { - _saveVerb = command.verb; - _saveNoun = command.noun; - } + if (b == 0) + continue; - if (command.numCond == 0 && command.script[0] == IDO_ACT_LOAD) { - _restoreVerb = command.verb; - _restoreNoun = command.noun; + if (b == ('\r' | 0x80)) { + s += b; + _display->printString(Common::String(b)); + return s; } - commands.push_back(command); + if (b < 0xa0) { + switch (b) { + case Common::KEYCODE_BACKSPACE | 0x80: + if (!s.empty()) { + _display->moveCursorBackward(); + _display->setCharAtCursor(APPLECHAR(' ')); + s.deleteLastChar(); + } + break; + }; + } else { + s += b; + _display->printString(Common::String(b)); + } } } -void AdlEngine::takeItem(byte noun) { - Common::Array<Item>::iterator item; +byte AdlEngine::inputKey() const { + Common::EventManager *ev = g_system->getEventManager(); - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != _state.room) - continue; + byte key = 0; - if (item->state == IDI_ITEM_DOESNT_MOVE) { - printMessage(_messageIds.itemDoesntMove); - return; - } + _display->showCursor(true); - if (item->state == IDI_ITEM_MOVED) { - item->room = IDI_NONE; - return; - } + while (!g_engine->shouldQuit() && !_isRestoring && key == 0) { + Common::Event event; + if (ev->pollEvent(event)) { + if (event.type != Common::EVENT_KEYDOWN) + continue; - Common::Array<byte>::const_iterator pic; - for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { - if (*pic == curRoom().curPicture) { - item->room = IDI_NONE; - item->state = IDI_ITEM_MOVED; - return; + if (event.kbd.flags & Common::KBD_CTRL) { + if (event.kbd.keycode == Common::KEYCODE_q) + g_engine->quitGame(); + continue; } - } - } - - printMessage(_messageIds.itemNotHere); -} - -void AdlEngine::dropItem(byte noun) { - Common::Array<Item>::iterator item; - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != IDI_NONE) - 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); + }; + } - item->room = _state.room; - item->state = IDI_ITEM_MOVED; - return; + _display->updateTextScreen(); + g_system->delayMillis(16); } - printMessage(_messageIds.dontUnderstand); + _display->showCursor(false); + + return key; } -#define ARG(N) (command.script[offset + (N)]) +void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { + uint index = 0; -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: - var(ARG(2)) += ARG(1); - offset += 3; - break; - case IDO_ACT_VAR_SUB: - var(ARG(2)) -= ARG(1); - offset += 3; - break; - case IDO_ACT_VAR_SET: - var(ARG(1)) = ARG(2); - offset += 3; - break; - case IDO_ACT_LIST_ITEMS: { - Common::Array<Item>::const_iterator item; + while (1) { + ++index; - for (item = _state.items.begin(); item != _state.items.end(); ++item) - if (item->room == IDI_NONE) - printMessage(item->description); + byte buf[IDI_WORD_SIZE]; - ++offset; - break; - } - case IDO_ACT_MOVE_ITEM: - item(ARG(1)).room = ARG(2); - offset += 3; - break; - case IDO_ACT_SET_ROOM: - curRoom().curPicture = curRoom().picture; - _state.room = ARG(1); - offset += 2; - break; - case IDO_ACT_SET_CUR_PIC: - curRoom().curPicture = ARG(1); - offset += 2; - break; - case IDO_ACT_SET_PIC: - curRoom().picture = curRoom().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); + if (stream.read(buf, IDI_WORD_SIZE) < IDI_WORD_SIZE) + error("Error reading word list"); - // We allow restoring via GMM here - _canRestoreNow = true; - Common::String input = inputString(); - _canRestoreNow = false; + Common::String word((char *)buf, IDI_WORD_SIZE); - // If the user restored with the GMM, we break off the restart - if (_isRestoring) - return; + if (!map.contains(word)) + map[word] = index; - 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: - item(ARG(1)).room = ARG(2); - item(ARG(1)).position.x = ARG(3); - item(ARG(1)).position.y = ARG(4); - offset += 5; - break; - case IDO_ACT_SET_ITEM_PIC: - item(ARG(2)).picture = ARG(1); - offset += 3; - break; - case IDO_ACT_RESET_PIC: - curRoom().curPicture = curRoom().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 = curRoom().connections[ARG(0) - IDO_ACT_GO_NORTH]; + byte synonyms = stream.readByte(); - if (room == 0) { - printMessage(_messageIds.cantGoThere); - return; - } + if (stream.err() || stream.eos()) + error("Error reading word list"); - curRoom().curPicture = curRoom().picture; - _state.room = room; - 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: - room(ARG(1)).picture = room(ARG(1)).curPicture = ARG(2); - offset += 3; + if (synonyms == 0xff) break; - default: - error("Invalid action opcode %02x", ARG(0)); - } - } -} - -bool AdlEngine::matchCommand(const Command &command, byte verb, byte noun, uint *actions) const { - if (command.room != IDI_NONE && command.room != _state.room) - return false; - if (command.verb != IDI_NONE && command.verb != verb) - return false; + for (uint i = 0; i < synonyms; ++i) { + if (stream.read((char *)buf, IDI_WORD_SIZE) < IDI_WORD_SIZE) + error("Error reading word list"); - if (command.noun != IDI_NONE && command.noun != noun) - return false; + word = Common::String((char *)buf, IDI_WORD_SIZE); - uint offset = 0; - for (uint i = 0; i < command.numCond; ++i) { - switch (ARG(0)) { - case IDO_CND_ITEM_IN_ROOM: - if (item(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 (var(ARG(1)) != ARG(2)) - return false; - offset += 3; - break; - case IDO_CND_CUR_PIC_EQ: - if (curRoom().curPicture != ARG(1)) - return false; - offset += 2; - break; - case IDO_CND_ITEM_PIC_EQ: - if (item(ARG(1)).picture != ARG(2)) - return false; - offset += 3; - break; - default: - error("Invalid condition opcode %02x", command.script[offset]); + if (!map.contains(word)) + map[word] = index; } } - - *actions = offset; - - return true; } -#undef ARG - -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); - return true; - } - } +void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) { + while (1) { + Command command; + command.room = stream.readByte(); - return false; -} + if (command.room == 0xff) + return; -void AdlEngine::doAllCommands(const Commands &commands, byte verb, byte noun) { - Commands::const_iterator cmd; - bool oldIsRestoring = _isRestoring; + command.verb = stream.readByte(); + command.noun = stream.readByte(); - for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { - uint offset = 0; - if (matchCommand(*cmd, verb, noun, &offset)) - doActions(*cmd, noun, offset); + byte scriptSize = stream.readByte() - 6; - // We assume no restarts happen in this command group. This - // simplifies enabling GMM savegame loading on the restart - // prompt. - if (_isRestarting || _isRestoring != oldIsRestoring) - error("Unexpected restart action encountered"); - } -} + command.numCond = stream.readByte(); + command.numAct = stream.readByte(); -bool AdlEngine::canSaveGameStateCurrently() const { - if (!_canSaveNow) - return false; + for (uint i = 0; i < scriptSize; ++i) + command.script.push_back(stream.readByte()); - Commands::const_iterator cmd; + if (stream.eos() || stream.err()) + error("Failed to read commands"); - // Here we check whether or not the game currently accepts the command - // "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) { - if (matchCommand(*cmd, _saveVerb, _saveNoun)) { - if (cmd->verb != _saveVerb || cmd->noun != _saveNoun) - return false; - return cmd->numCond == 0 && cmd->script[0] == IDO_ACT_SAVE; + if (command.numCond == 0 && command.script[0] == IDO_ACT_SAVE) { + _saveVerb = command.verb; + _saveNoun = command.noun; } - } - - return false; -} - -bool AdlEngine::canLoadGameStateCurrently() const { - return _canRestoreNow; -} - -void AdlEngine::clearScreen() const { - _display->setMode(DISPLAY_MODE_MIXED); - _display->clear(0x00); -} - -void AdlEngine::drawItems() const { - Common::Array<Item>::const_iterator item; - uint dropped = 0; - - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->room != _state.room) - continue; - - if (item->state == IDI_ITEM_MOVED) { - if (curRoom().picture == curRoom().curPicture) { - const Common::Point &p = _itemOffsets[dropped]; - if (item->isLineArt) - drawLineArt(_lineArt[item->picture - 1], p); - else - drawPic(item->picture, p); - ++dropped; - } - continue; + if (command.numCond == 0 && command.script[0] == IDO_ACT_LOAD) { + _restoreVerb = command.verb; + _restoreNoun = command.noun; } - Common::Array<byte>::const_iterator pic; - - for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { - if (*pic == curRoom().curPicture) { - if (item->isLineArt) - drawLineArt(_lineArt[item->picture - 1], item->position); - else - drawPic(item->picture, item->position); - continue; - } - } + commands.push_back(command); } } -void AdlEngine::showRoom() const { - if (!_state.isDark) { - drawPic(curRoom().curPicture); - drawItems(); - } - - _display->updateHiResScreen(); - printMessage(curRoom().description, false); -} +Common::Error AdlEngine::run() { + _display = new Display(); -Common::Error AdlEngine::saveGameState(int slot, const Common::String &desc) { - Common::String fileName = Common::String::format("%s.s%02d", _targetName.c_str(), slot); - Common::OutSaveFile *outFile = getSaveFileManager()->openForSaving(fileName); + loadData(); - if (!outFile) { - warning("Failed to open file '%s'", fileName.c_str()); - return Common::kUnknownError; + int saveSlot = ConfMan.getInt("save_slot"); + if (saveSlot >= 0) { + if (loadGameState(saveSlot).getCode() != Common::kNoError) + error("Failed to load save game from slot %i", saveSlot); + _display->moveCursorTo(Common::Point(0, 23)); + _isRestoring = true; + } else { + runIntro(); + initState(); } - outFile->writeUint32BE(MKTAG('A', 'D', 'L', ':')); - outFile->writeByte(SAVEGAME_VERSION); - - char name[SAVEGAME_NAME_LEN] = { }; - - if (!desc.empty()) - strncpy(name, desc.c_str(), sizeof(name) - 1); - else { - Common::String defaultName("Save "); - defaultName += 'A' + slot; - strncpy(name, defaultName.c_str(), sizeof(name) - 1); - } + _display->setMode(DISPLAY_MODE_MIXED); + _display->printAsciiString("\r\r\r\r\r"); - outFile->write(name, sizeof(name)); + while (1) { + uint verb = 0, noun = 0; - TimeDate t; - g_system->getTimeAndDate(t); + // When restoring from the launcher, we don't read + // input on the first iteration. This is needed to + // ensure that restoring from the launcher and + // restoring in-game brings us to the same game state. + // (Also see comment below.) + if (!_isRestoring) { + clearScreen(); + showRoom(); - outFile->writeUint16BE(t.tm_year); - outFile->writeByte(t.tm_mon); - outFile->writeByte(t.tm_mday); - outFile->writeByte(t.tm_hour); - outFile->writeByte(t.tm_min); + _canSaveNow = _canRestoreNow = true; + getInput(verb, noun); + _canSaveNow = _canRestoreNow = false; - uint32 playTime = getTotalPlayTime(); - outFile->writeUint32BE(playTime); + if (shouldQuit()) + break; - _display->saveThumbnail(*outFile); + if (!doOneCommand(_roomCommands, verb, noun)) + printMessage(_messageIds.dontUnderstand); + } - outFile->writeByte(_state.room); - outFile->writeByte(_state.moves); - outFile->writeByte(_state.isDark); + if (_isRestoring) { + // We restored from the GMM or launcher. As restoring + // with "RESTORE GAME" does not end command processing, + // we don't break it off here either. This essentially + // means that restoring a game will always run through + // the global commands and increase the move counter + // before the first user input. + _display->printAsciiString("\r"); + _isRestoring = false; + verb = _restoreVerb; + noun = _restoreNoun; + } - 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); - } + // Restarting does end command processing + if (_isRestarting) { + _isRestarting = false; + continue; + } - 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); + doAllCommands(_globalCommands, verb, noun); + _state.moves++; } - outFile->writeUint32BE(_state.vars.size()); - for (uint i = 0; i < _state.vars.size(); ++i) - outFile->writeByte(_state.vars[i]); - - outFile->finalize(); + return Common::kNoError; +} - if (outFile->err()) { - delete outFile; - warning("Failed to save game '%s'", fileName.c_str()); - return Common::kUnknownError; +bool AdlEngine::hasFeature(EngineFeature f) const { + switch (f) { + case kSupportsLoadingDuringRuntime: + case kSupportsSavingDuringRuntime: + case kSupportsRTL: + return true; + default: + return false; } - - delete outFile; - return Common::kNoError; } Common::Error AdlEngine::loadGameState(int slot) { @@ -713,92 +417,132 @@ Common::Error AdlEngine::loadGameState(int slot) { return Common::kNoError; } -const Room &AdlEngine::room(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]; +bool AdlEngine::canLoadGameStateCurrently() const { + return _canRestoreNow; } -Room &AdlEngine::room(uint i) { - if (i < 1 || i > _state.rooms.size()) - error("Room %i out of range [1, %i]", i, _state.rooms.size()); +Common::Error AdlEngine::saveGameState(int slot, const Common::String &desc) { + Common::String fileName = Common::String::format("%s.s%02d", _targetName.c_str(), slot); + Common::OutSaveFile *outFile = getSaveFileManager()->openForSaving(fileName); - return _state.rooms[i - 1]; -} + if (!outFile) { + warning("Failed to open file '%s'", fileName.c_str()); + return Common::kUnknownError; + } -const Room &AdlEngine::curRoom() const { - return room(_state.room); -} + outFile->writeUint32BE(MKTAG('A', 'D', 'L', ':')); + outFile->writeByte(SAVEGAME_VERSION); -Room &AdlEngine::curRoom() { - return room(_state.room); -} + char name[SAVEGAME_NAME_LEN] = { }; -const Item &AdlEngine::item(uint i) const { - if (i < 1 || i > _state.items.size()) - error("Item %i out of range [1, %i]", i, _state.items.size()); + if (!desc.empty()) + strncpy(name, desc.c_str(), sizeof(name) - 1); + else { + Common::String defaultName("Save "); + defaultName += 'A' + slot; + strncpy(name, defaultName.c_str(), sizeof(name) - 1); + } - return _state.items[i - 1]; -} + outFile->write(name, sizeof(name)); -Item &AdlEngine::item(uint i) { - if (i < 1 || i > _state.items.size()) - error("Item %i out of range [1, %i]", i, _state.items.size()); + TimeDate t; + g_system->getTimeAndDate(t); - return _state.items[i - 1]; -} + outFile->writeUint16BE(t.tm_year); + outFile->writeByte(t.tm_mon); + outFile->writeByte(t.tm_mday); + outFile->writeByte(t.tm_hour); + outFile->writeByte(t.tm_min); -const byte &AdlEngine::var(uint i) const { - if (i >= _state.vars.size()) - error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); + uint32 playTime = getTotalPlayTime(); + outFile->writeUint32BE(playTime); - return _state.vars[i]; -} + _display->saveThumbnail(*outFile); -byte &AdlEngine::var(uint i) { - if (i >= _state.vars.size()) - error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); + outFile->writeByte(_state.room); + outFile->writeByte(_state.moves); + outFile->writeByte(_state.isDark); - return _state.vars[i]; -} + 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); + } -void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { - uint index = 0; + 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); + } - while (1) { - ++index; + outFile->writeUint32BE(_state.vars.size()); + for (uint i = 0; i < _state.vars.size(); ++i) + outFile->writeByte(_state.vars[i]); - byte buf[IDI_WORD_SIZE]; + outFile->finalize(); - if (stream.read(buf, IDI_WORD_SIZE) < IDI_WORD_SIZE) - error("Error reading word list"); + if (outFile->err()) { + delete outFile; + warning("Failed to save game '%s'", fileName.c_str()); + return Common::kUnknownError; + } - Common::String word((char *)buf, IDI_WORD_SIZE); + delete outFile; + return Common::kNoError; +} - if (!map.contains(word)) - map[word] = index; +bool AdlEngine::canSaveGameStateCurrently() const { + if (!_canSaveNow) + return false; - byte synonyms = stream.readByte(); + Commands::const_iterator cmd; - if (stream.err() || stream.eos()) - error("Error reading word list"); + // Here we check whether or not the game currently accepts the command + // "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) { + if (matchCommand(*cmd, _saveVerb, _saveNoun)) { + if (cmd->verb != _saveVerb || cmd->noun != _saveNoun) + return false; + return cmd->numCond == 0 && cmd->script[0] == IDO_ACT_SAVE; + } + } - if (synonyms == 0xff) - break; + return false; +} - for (uint i = 0; i < synonyms; ++i) { - if (stream.read((char *)buf, IDI_WORD_SIZE) < IDI_WORD_SIZE) - error("Error reading word list"); +void AdlEngine::wordWrap(Common::String &str) const { + uint end = 39; - word = Common::String((char *)buf, IDI_WORD_SIZE); + while (1) { + if (str.size() <= end) + return; - if (!map.contains(word)) - map[word] = index; - } + while (str[end] != APPLECHAR(' ')) + --end; + + str.setChar(APPLECHAR('\r'), end); + end += 40; } } +byte AdlEngine::convertKey(uint16 ascii) const { + ascii = toupper(ascii); + + if (ascii >= 0x80) + return 0; + + ascii |= 0x80; + + if (ascii >= 0x80 && ascii <= 0xe0) + return ascii; + + return 0; +} + Common::String AdlEngine::getLine() const { // Original engine uses a global here, which isn't reset between // calls and may not match actual mode @@ -889,116 +633,53 @@ void AdlEngine::getInput(uint &verb, uint &noun) { } } -Common::String AdlEngine::inputString(byte prompt) const { - Common::String s; - - if (prompt > 0) - _display->printString(Common::String(prompt)); - - while (1) { - byte b = inputKey(); - - if (g_engine->shouldQuit() || _isRestoring) - return 0; - - if (b == 0) - continue; - - if (b == ('\r' | 0x80)) { - s += b; - _display->printString(Common::String(b)); - return s; - } - - if (b < 0xa0) { - switch (b) { - case Common::KEYCODE_BACKSPACE | 0x80: - if (!s.empty()) { - _display->moveCursorBackward(); - _display->setCharAtCursor(APPLECHAR(' ')); - s.deleteLastChar(); - } - break; - }; - } else { - s += b; - _display->printString(Common::String(b)); - } +void AdlEngine::showRoom() const { + if (!_state.isDark) { + drawPic(curRoom().curPicture); + drawItems(); } -} -byte AdlEngine::convertKey(uint16 ascii) const { - ascii = toupper(ascii); - - if (ascii >= 0x80) - return 0; - - ascii |= 0x80; - - if (ascii >= 0x80 && ascii <= 0xe0) - return ascii; - - return 0; + _display->updateHiResScreen(); + printMessage(curRoom().description, false); } -byte AdlEngine::inputKey() const { - Common::EventManager *ev = g_system->getEventManager(); +void AdlEngine::clearScreen() const { + _display->setMode(DISPLAY_MODE_MIXED); + _display->clear(0x00); +} - byte key = 0; +void AdlEngine::drawItems() const { + Common::Array<Item>::const_iterator item; - _display->showCursor(true); + uint dropped = 0; - while (!g_engine->shouldQuit() && !_isRestoring && key == 0) { - Common::Event event; - if (ev->pollEvent(event)) { - if (event.type != Common::EVENT_KEYDOWN) - continue; + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->room != _state.room) + continue; - if (event.kbd.flags & Common::KBD_CTRL) { - if (event.kbd.keycode == Common::KEYCODE_q) - g_engine->quitGame(); - continue; + if (item->state == IDI_ITEM_MOVED) { + if (curRoom().picture == curRoom().curPicture) { + const Common::Point &p = _itemOffsets[dropped]; + if (item->isLineArt) + drawLineArt(_lineArt[item->picture - 1], p); + else + drawPic(item->picture, p); + ++dropped; } - - 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); - }; + continue; } - _display->updateTextScreen(); - g_system->delayMillis(16); - } - - _display->showCursor(false); - - return key; -} - -void AdlEngine::delay(uint32 ms) const { - Common::EventManager *ev = g_system->getEventManager(); - - uint32 start = g_system->getMillis(); + Common::Array<byte>::const_iterator pic; - 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; - } + for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { + if (*pic == curRoom().curPicture) { + if (item->isLineArt) + drawLineArt(_lineArt[item->picture - 1], item->position); + else + drawPic(item->picture, item->position); + continue; } } - g_system->delayMillis(16); } } @@ -1047,4 +728,323 @@ void AdlEngine::drawLineArt(const Common::Array<byte> &lineArt, const Common::Po } } +const Room &AdlEngine::room(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::room(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::curRoom() const { + return room(_state.room); +} + +Room &AdlEngine::curRoom() { + return room(_state.room); +} + +const Item &AdlEngine::item(uint i) const { + if (i < 1 || i > _state.items.size()) + error("Item %i out of range [1, %i]", i, _state.items.size()); + + return _state.items[i - 1]; +} + +Item &AdlEngine::item(uint i) { + if (i < 1 || i > _state.items.size()) + error("Item %i out of range [1, %i]", i, _state.items.size()); + + return _state.items[i - 1]; +} + +const byte &AdlEngine::var(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]; +} + +byte &AdlEngine::var(uint i) { + if (i >= _state.vars.size()) + error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); + + return _state.vars[i]; +} + +void AdlEngine::takeItem(byte noun) { + Common::Array<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_MOVED) { + item->room = IDI_NONE; + return; + } + + Common::Array<byte>::const_iterator pic; + for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { + if (*pic == curRoom().curPicture) { + item->room = IDI_NONE; + item->state = IDI_ITEM_MOVED; + return; + } + } + } + + printMessage(_messageIds.itemNotHere); +} + +void AdlEngine::dropItem(byte noun) { + Common::Array<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != IDI_NONE) + continue; + + item->room = _state.room; + item->state = IDI_ITEM_MOVED; + return; + } + + printMessage(_messageIds.dontUnderstand); +} + +#define ARG(N) (command.script[offset + (N)]) + +bool AdlEngine::matchCommand(const Command &command, byte verb, byte noun, uint *actions) const { + if (command.room != IDI_NONE && command.room != _state.room) + return false; + + if (command.verb != IDI_NONE && command.verb != verb) + return false; + + if (command.noun != IDI_NONE && command.noun != noun) + return false; + + uint offset = 0; + for (uint i = 0; i < command.numCond; ++i) { + switch (ARG(0)) { + case IDO_CND_ITEM_IN_ROOM: + if (item(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 (var(ARG(1)) != ARG(2)) + return false; + offset += 3; + break; + case IDO_CND_CUR_PIC_EQ: + if (curRoom().curPicture != ARG(1)) + return false; + offset += 2; + break; + case IDO_CND_ITEM_PIC_EQ: + if (item(ARG(1)).picture != ARG(2)) + return false; + offset += 3; + break; + default: + error("Invalid condition opcode %02x", command.script[offset]); + } + } + + *actions = offset; + + 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: + var(ARG(2)) += ARG(1); + offset += 3; + break; + case IDO_ACT_VAR_SUB: + var(ARG(2)) -= ARG(1); + offset += 3; + break; + case IDO_ACT_VAR_SET: + var(ARG(1)) = ARG(2); + offset += 3; + break; + case IDO_ACT_LIST_ITEMS: { + Common::Array<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == IDI_NONE) + printMessage(item->description); + + ++offset; + break; + } + case IDO_ACT_MOVE_ITEM: + item(ARG(1)).room = ARG(2); + offset += 3; + break; + case IDO_ACT_SET_ROOM: + curRoom().curPicture = curRoom().picture; + _state.room = ARG(1); + offset += 2; + break; + case IDO_ACT_SET_CUR_PIC: + curRoom().curPicture = ARG(1); + offset += 2; + break; + case IDO_ACT_SET_PIC: + curRoom().picture = curRoom().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); + + // We allow restoring via GMM here + _canRestoreNow = true; + Common::String input = inputString(); + _canRestoreNow = false; + + // If the user restored with the GMM, we break off the restart + if (_isRestoring) + return; + + 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: + item(ARG(1)).room = ARG(2); + item(ARG(1)).position.x = ARG(3); + item(ARG(1)).position.y = ARG(4); + offset += 5; + break; + case IDO_ACT_SET_ITEM_PIC: + item(ARG(2)).picture = ARG(1); + offset += 3; + break; + case IDO_ACT_RESET_PIC: + curRoom().curPicture = curRoom().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 = curRoom().connections[ARG(0) - IDO_ACT_GO_NORTH]; + + if (room == 0) { + printMessage(_messageIds.cantGoThere); + return; + } + + curRoom().curPicture = curRoom().picture; + _state.room = room; + 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: + room(ARG(1)).picture = room(ARG(1)).curPicture = ARG(2); + offset += 3; + break; + default: + error("Invalid action opcode %02x", ARG(0)); + } + } +} + +#undef ARG + +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); + return true; + } + } + + return false; +} + +void AdlEngine::doAllCommands(const Commands &commands, byte verb, byte noun) { + Commands::const_iterator cmd; + bool oldIsRestoring = _isRestoring; + + for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { + uint offset = 0; + if (matchCommand(*cmd, verb, noun, &offset)) + doActions(*cmd, noun, offset); + + // We assume no restarts happen in this command group. This + // simplifies enabling GMM savegame loading on the restart + // prompt. + if (_isRestarting || _isRestoring != oldIsRestoring) + error("Unexpected restart action encountered"); + } +} + } // End of namespace Adl diff --git a/engines/adl/adl.h b/engines/adl/adl.h index b1d5b7c0b3..a230f0f03b 100644 --- a/engines/adl/adl.h +++ b/engines/adl/adl.h @@ -23,8 +23,9 @@ #ifndef ADL_ADL_H #define ADL_ADL_H -#include "common/random.h" +#include "common/array.h" #include "common/rect.h" +#include "common/str.h" #include "engines/engine.h" @@ -34,16 +35,10 @@ class SeekableReadStream; } namespace Adl { + class Display; -class Parser; -class Console; struct AdlGameDescription; -struct StringOffset { - int stringIdx; - uint offset; -}; - // Conditional opcodes #define IDO_CND_ITEM_IN_ROOM 0x03 #define IDO_CND_MOVES_GE 0x05 @@ -155,7 +150,6 @@ protected: void readCommands(Common::ReadStream &stream, Commands &commands); Display *_display; - Parser *_parser; // Message strings in data file Common::Array<Common::String> _messages; @@ -236,9 +230,9 @@ private: void takeItem(byte noun); void dropItem(byte noun); bool matchCommand(const Command &command, byte verb, byte noun, uint *actions = nullptr) const; + void doActions(const Command &command, byte noun, byte offset); 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); const AdlGameDescription *_gameDescription; bool _isRestarting, _isRestoring; diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp index 95f02899a2..6e1e31df9f 100644 --- a/engines/adl/hires1.cpp +++ b/engines/adl/hires1.cpp @@ -136,113 +136,6 @@ void HiRes1Engine::runIntro() const { delay(2000); } -void HiRes1Engine::drawPic(Common::ReadStream &stream, const Common::Point &pos) const { - 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 += pos.x; - y += pos.y; - - if (y > 160) - y = 160; - - if (bNewLine) { - _display->putPixel(Common::Point(x, y), 0x7f); - bNewLine = false; - } else { - drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f); - } - - oldX = x; - oldY = y; - } -} - -void HiRes1Engine::drawPic(byte pic, Common::Point pos) const { - Common::File f; - Common::String name = Common::String::format("BLOCK%i", _pictures[pic].block); - - if (!f.open(name)) - error("Failed to open file '%s'", name.c_str()); - - f.seek(_pictures[pic].offset); - drawPic(f, pos); -} - -void HiRes1Engine::initState() { - Common::File f; - - _state.room = 1; - _state.moves = 0; - _state.isDark = false; - - _state.vars.clear(); - _state.vars.resize(IDI_HR1_NUM_VARS); - - if (!f.open(IDS_HR1_EXE_1)) - error("Failed to open file '" IDS_HR1_EXE_1 "'"); - - // Load room data from executable - _state.rooms.clear(); - f.seek(IDI_HR1_OFS_ROOMS); - for (uint i = 0; i < IDI_HR1_NUM_ROOMS; ++i) { - Room room; - f.readByte(); - room.description = f.readByte(); - for (uint j = 0; j < 6; ++j) - room.connections[j] = f.readByte(); - room.picture = f.readByte(); - room.curPicture = f.readByte(); - _state.rooms.push_back(room); - } - - // Load item data from executable - _state.items.clear(); - f.seek(IDI_HR1_OFS_ITEMS); - while (f.readByte() != 0xff) { - Item item; - item.noun = f.readByte(); - item.room = f.readByte(); - item.picture = f.readByte(); - item.isLineArt = f.readByte(); - item.position.x = f.readByte(); - item.position.y = f.readByte(); - item.state = f.readByte(); - item.description = f.readByte(); - - f.readByte(); - - byte size = f.readByte(); - - for (uint i = 0; i < size; ++i) - item.roomPictures.push_back(f.readByte()); - - _state.items.push_back(item); - } -} - -void HiRes1Engine::restartGame() { - initState(); - _display->printString(_gameStrings.pressReturn); - inputString(); // Missing in the original - _display->printAsciiString("\r\r\r\r\r"); -} - void HiRes1Engine::loadData() { Common::File f; @@ -331,6 +224,76 @@ void HiRes1Engine::loadData() { loadWords(f, _nouns); } +void HiRes1Engine::initState() { + Common::File f; + + _state.room = 1; + _state.moves = 0; + _state.isDark = false; + + _state.vars.clear(); + _state.vars.resize(IDI_HR1_NUM_VARS); + + if (!f.open(IDS_HR1_EXE_1)) + error("Failed to open file '" IDS_HR1_EXE_1 "'"); + + // Load room data from executable + _state.rooms.clear(); + f.seek(IDI_HR1_OFS_ROOMS); + for (uint i = 0; i < IDI_HR1_NUM_ROOMS; ++i) { + Room room; + f.readByte(); + room.description = f.readByte(); + for (uint j = 0; j < 6; ++j) + room.connections[j] = f.readByte(); + room.picture = f.readByte(); + room.curPicture = f.readByte(); + _state.rooms.push_back(room); + } + + // Load item data from executable + _state.items.clear(); + f.seek(IDI_HR1_OFS_ITEMS); + while (f.readByte() != 0xff) { + Item item; + item.noun = f.readByte(); + item.room = f.readByte(); + item.picture = f.readByte(); + item.isLineArt = f.readByte(); + item.position.x = f.readByte(); + item.position.y = f.readByte(); + item.state = f.readByte(); + item.description = f.readByte(); + + f.readByte(); + + byte size = f.readByte(); + + for (uint i = 0; i < size; ++i) + item.roomPictures.push_back(f.readByte()); + + _state.items.push_back(item); + } +} + +void HiRes1Engine::restartGame() { + initState(); + _display->printString(_gameStrings.pressReturn); + inputString(); // Missing in the original + _display->printAsciiString("\r\r\r\r\r"); +} + +void HiRes1Engine::drawPic(byte pic, Common::Point pos) const { + Common::File f; + Common::String name = Common::String::format("BLOCK%i", _pictures[pic].block); + + if (!f.open(name)) + error("Failed to open file '%s'", name.c_str()); + + f.seek(_pictures[pic].offset); + drawPic(f, pos); +} + void HiRes1Engine::printMessage(uint idx, bool wait) const { // Messages with hardcoded overrides don't delay after printing. // It's unclear if this is a bug or not. In some cases the result @@ -389,6 +352,43 @@ void HiRes1Engine::drawLine(const Common::Point &p1, const Common::Point &p2, by } } +void HiRes1Engine::drawPic(Common::ReadStream &stream, const Common::Point &pos) const { + 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 += pos.x; + y += pos.y; + + if (y > 160) + y = 160; + + if (bNewLine) { + _display->putPixel(Common::Point(x, y), 0x7f); + bNewLine = false; + } else { + drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f); + } + + oldX = x; + oldY = y; + } +} + Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd) { return new HiRes1Engine(syst, gd); } diff --git a/engines/adl/hires1.h b/engines/adl/hires1.h index b7c7f41e12..d9d67c46e4 100644 --- a/engines/adl/hires1.h +++ b/engines/adl/hires1.h @@ -23,6 +23,8 @@ #ifndef ADL_HIRES1_H #define ADL_HIRES1_H +#include "common/str.h" + #include "adl/adl.h" namespace Common { |