From 63adab81edc8f44d4b4387352e0869e3042c2a13 Mon Sep 17 00:00:00 2001 From: Walter van Niftrik Date: Mon, 7 Mar 2016 20:43:37 +0100 Subject: ADL: Clean up HiRes1Engine class --- engines/adl/adl.cpp | 1398 ++++++++++++++++++++++++------------------------ engines/adl/adl.h | 14 +- engines/adl/hires1.cpp | 214 ++++---- engines/adl/hires1.h | 2 + 4 files changed, 812 insertions(+), 816 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,18 +59,206 @@ AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) : _canRestoreNow(false) { } -AdlEngine::~AdlEngine() { - delete _display; +Common::String AdlEngine::readString(Common::ReadStream &stream, byte until) const { + Common::String str; + + while (1) { + byte b = stream.readByte(); + + if (stream.eos() || stream.err()) + error("Error reading string"); + + if (b == until) + break; + + str += b; + }; + + return str; } -bool AdlEngine::hasFeature(EngineFeature f) const { - switch (f) { - case kSupportsLoadingDuringRuntime: - case kSupportsSavingDuringRuntime: - case kSupportsRTL: - return true; - default: - return false; +Common::String AdlEngine::readStringAt(Common::SeekableReadStream &stream, uint offset, byte until) const { + stream.seek(offset); + return readString(stream, until); +} + +void AdlEngine::printMessage(uint idx, bool wait) const { + Common::String msg = _messages[idx - 1]; + wordWrap(msg); + _display->printString(msg); + + if (wait) + delay(14 * 166018 / 1000); +} + +void AdlEngine::delay(uint32 ms) const { + 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; + } + } + } + g_system->delayMillis(16); + } +} + +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)); + } + } +} + +byte AdlEngine::inputKey() const { + Common::EventManager *ev = g_system->getEventManager(); + + byte key = 0; + + _display->showCursor(true); + + while (!g_engine->shouldQuit() && !_isRestoring && 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); + }; + } + + _display->updateTextScreen(); + g_system->delayMillis(16); + } + + _display->showCursor(false); + + return key; +} + +void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { + uint index = 0; + + while (1) { + ++index; + + byte buf[IDI_WORD_SIZE]; + + if (stream.read(buf, IDI_WORD_SIZE) < IDI_WORD_SIZE) + error("Error reading word list"); + + Common::String word((char *)buf, IDI_WORD_SIZE); + + 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, IDI_WORD_SIZE) < IDI_WORD_SIZE) + error("Error reading word list"); + + word = Common::String((char *)buf, IDI_WORD_SIZE); + + if (!map.contains(word)) + map[word] = index; + } + } +} + +void AdlEngine::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"); + + if (command.numCond == 0 && command.script[0] == IDO_ACT_SAVE) { + _saveVerb = command.verb; + _saveNoun = command.noun; + } + + if (command.numCond == 0 && command.script[0] == IDO_ACT_LOAD) { + _restoreVerb = command.verb; + _restoreNoun = command.noun; + } + + commands.push_back(command); } } @@ -138,380 +330,317 @@ Common::Error AdlEngine::run() { return Common::kNoError; } -Common::String AdlEngine::readString(Common::ReadStream &stream, byte until) const { - Common::String str; - - while (1) { - byte b = stream.readByte(); +bool AdlEngine::hasFeature(EngineFeature f) const { + switch (f) { + case kSupportsLoadingDuringRuntime: + case kSupportsSavingDuringRuntime: + case kSupportsRTL: + return true; + default: + return false; + } +} - if (stream.eos() || stream.err()) - error("Error reading string"); +Common::Error AdlEngine::loadGameState(int slot) { + Common::String fileName = Common::String::format("%s.s%02d", _targetName.c_str(), slot); + Common::InSaveFile *inFile = getSaveFileManager()->openForLoading(fileName); - if (b == until) - break; + if (!inFile) { + warning("Failed to open file '%s'", fileName.c_str()); + return Common::kUnknownError; + } - str += b; - }; + if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) { + warning("No header found in '%s'", fileName.c_str()); + delete inFile; + return Common::kUnknownError; + } - return str; -} + byte saveVersion = inFile->readByte(); + if (saveVersion != SAVEGAME_VERSION) { + warning("Save game version %i not supported", saveVersion); + delete inFile; + return Common::kUnknownError; + } -Common::String AdlEngine::readStringAt(Common::SeekableReadStream &stream, uint offset, byte until) const { - stream.seek(offset); - return readString(stream, until); -} + // Skip description + inFile->seek(SAVEGAME_NAME_LEN, SEEK_CUR); + // Skip save time + inFile->seek(6, SEEK_CUR); -void AdlEngine::wordWrap(Common::String &str) const { - uint end = 39; + uint32 playTime = inFile->readUint32BE(); - while (1) { - if (str.size() <= end) - return; + Graphics::skipThumbnail(*inFile); - while (str[end] != APPLECHAR(' ')) - --end; + initState(); - str.setChar(APPLECHAR('\r'), end); - end += 40; + _state.room = inFile->readByte(); + _state.moves = inFile->readByte(); + _state.isDark = inFile->readByte(); + + uint32 size = inFile->readUint32BE(); + if (size != _state.rooms.size()) + error("Room count mismatch (expected %i; found %i)", _state.rooms.size(), size); + + for (uint i = 0; i < size; ++i) { + _state.rooms[i].picture = inFile->readByte(); + _state.rooms[i].curPicture = inFile->readByte(); } -} -void AdlEngine::printMessage(uint idx, bool wait) const { - Common::String msg = _messages[idx - 1]; - wordWrap(msg); - _display->printString(msg); + size = inFile->readUint32BE(); + if (size != _state.items.size()) + error("Item count mismatch (expected %i; found %i)", _state.items.size(), size); - if (wait) - delay(14 * 166018 / 1000); + 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(); + } + + size = inFile->readUint32BE(); + if (size != _state.vars.size()) + error("Variable count mismatch (expected %i; found %i)", _state.vars.size(), size); + + for (uint i = 0; i < size; ++i) + _state.vars[i] = inFile->readByte(); + + if (inFile->err() || inFile->eos()) + error("Failed to load game '%s'", fileName.c_str()); + + delete inFile; + + setTotalPlayTime(playTime); + + _isRestoring = true; + return Common::kNoError; } -void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) { - while (1) { - Command command; - command.room = stream.readByte(); +bool AdlEngine::canLoadGameStateCurrently() const { + return _canRestoreNow; +} - if (command.room == 0xff) - return; +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); - command.verb = stream.readByte(); - command.noun = stream.readByte(); + if (!outFile) { + warning("Failed to open file '%s'", fileName.c_str()); + return Common::kUnknownError; + } - byte scriptSize = stream.readByte() - 6; + outFile->writeUint32BE(MKTAG('A', 'D', 'L', ':')); + outFile->writeByte(SAVEGAME_VERSION); - command.numCond = stream.readByte(); - command.numAct = stream.readByte(); + char name[SAVEGAME_NAME_LEN] = { }; - for (uint i = 0; i < scriptSize; ++i) - command.script.push_back(stream.readByte()); + 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); + } - if (stream.eos() || stream.err()) - error("Failed to read commands"); + outFile->write(name, sizeof(name)); - if (command.numCond == 0 && command.script[0] == IDO_ACT_SAVE) { - _saveVerb = command.verb; - _saveNoun = command.noun; - } + TimeDate t; + g_system->getTimeAndDate(t); - if (command.numCond == 0 && command.script[0] == IDO_ACT_LOAD) { - _restoreVerb = command.verb; - _restoreNoun = command.noun; - } + 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); - commands.push_back(command); + uint32 playTime = getTotalPlayTime(); + outFile->writeUint32BE(playTime); + + _display->saveThumbnail(*outFile); + + outFile->writeByte(_state.room); + outFile->writeByte(_state.moves); + outFile->writeByte(_state.isDark); + + 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::takeItem(byte noun) { - Common::Array::iterator item; + 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); + } - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != _state.room) - continue; + outFile->writeUint32BE(_state.vars.size()); + for (uint i = 0; i < _state.vars.size(); ++i) + outFile->writeByte(_state.vars[i]); - if (item->state == IDI_ITEM_DOESNT_MOVE) { - printMessage(_messageIds.itemDoesntMove); - return; - } + outFile->finalize(); - if (item->state == IDI_ITEM_MOVED) { - item->room = IDI_NONE; - return; - } + if (outFile->err()) { + delete outFile; + warning("Failed to save game '%s'", fileName.c_str()); + return Common::kUnknownError; + } - Common::Array::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; - } + delete outFile; + return Common::kNoError; +} + +bool AdlEngine::canSaveGameStateCurrently() const { + if (!_canSaveNow) + return false; + + Commands::const_iterator cmd; + + // 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; } } - printMessage(_messageIds.itemNotHere); + return false; } -void AdlEngine::dropItem(byte noun) { - Common::Array::iterator item; +void AdlEngine::wordWrap(Common::String &str) const { + uint end = 39; - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != IDI_NONE) - continue; + while (1) { + if (str.size() <= end) + return; - item->room = _state.room; - item->state = IDI_ITEM_MOVED; - return; + while (str[end] != APPLECHAR(' ')) + --end; + + str.setChar(APPLECHAR('\r'), end); + end += 40; } +} - printMessage(_messageIds.dontUnderstand); +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; } -#define ARG(N) (command.script[offset + (N)]) +Common::String AdlEngine::getLine() const { + // Original engine uses a global here, which isn't reset between + // calls and may not match actual mode + bool textMode = false; -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::const_iterator item; + while (1) { + Common::String line = inputString(APPLECHAR('?')); - for (item = _state.items.begin(); item != _state.items.end(); ++item) - if (item->room == IDI_NONE) - printMessage(item->description); + if (shouldQuit() || _isRestoring) + return ""; - ++offset; - break; + if ((byte)line[0] == ('\r' | 0x80)) { + textMode = !textMode; + _display->setMode(textMode ? DISPLAY_MODE_TEXT : DISPLAY_MODE_MIXED); + continue; } - 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)); - } + // Remove the return + line.deleteLastChar(); + return line; } } -bool AdlEngine::matchCommand(const Command &command, byte verb, byte noun, uint *actions) const { - if (command.room != IDI_NONE && command.room != _state.room) - return false; +Common::String AdlEngine::getWord(const Common::String &line, uint &index) const { + Common::String str; - if (command.verb != IDI_NONE && command.verb != verb) - return false; + for (uint i = 0; i < 8; ++i) + str += APPLECHAR(' '); - if (command.noun != IDI_NONE && command.noun != noun) - return false; + int copied = 0; - 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; + // Skip initial whitespace + while (1) { + if (index == line.size()) + return str; + if (line[index] != APPLECHAR(' ')) break; - default: - error("Invalid condition opcode %02x", command.script[offset]); - } + ++index; } - *actions = offset; + // Copy up to 8 characters + while (1) { + if (copied < 8) + str.setChar(line[index], copied++); - return true; + index++; + + if (index == line.size() || line[index] == APPLECHAR(' ')) + return str; + } } -#undef ARG +void AdlEngine::getInput(uint &verb, uint &noun) { + while (1) { + _display->printString(_strings.enterCommand); + Common::String line = getLine(); -bool AdlEngine::doOneCommand(const Commands &commands, byte verb, byte noun) { - Commands::const_iterator cmd; + if (shouldQuit() || _isRestoring) + return; - for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { - uint offset = 0; - if (matchCommand(*cmd, verb, noun, &offset)) { - doActions(*cmd, noun, offset); - return true; + uint index = 0; + Common::String verbStr = 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); + continue; } - } - return false; -} + verb = _verbs[verbStr]; -void AdlEngine::doAllCommands(const Commands &commands, byte verb, byte noun) { - Commands::const_iterator cmd; - bool oldIsRestoring = _isRestoring; + Common::String nounStr = getWord(line, index); - for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { - uint offset = 0; - if (matchCommand(*cmd, verb, noun, &offset)) - doActions(*cmd, noun, offset); + 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); + continue; + } - // 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"); + noun = _nouns[nounStr]; + return; } } -bool AdlEngine::canSaveGameStateCurrently() const { - if (!_canSaveNow) - return false; - - Commands::const_iterator cmd; - - // 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; - } +void AdlEngine::showRoom() const { + if (!_state.isDark) { + drawPic(curRoom().curPicture); + drawItems(); } - return false; -} - -bool AdlEngine::canLoadGameStateCurrently() const { - return _canRestoreNow; + _display->updateHiResScreen(); + printMessage(curRoom().description, false); } void AdlEngine::clearScreen() const { @@ -554,163 +683,49 @@ void AdlEngine::drawItems() const { } } -void AdlEngine::showRoom() const { - if (!_state.isDark) { - drawPic(curRoom().curPicture); - drawItems(); - } - - _display->updateHiResScreen(); - printMessage(curRoom().description, false); -} - -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); - - if (!outFile) { - warning("Failed to open file '%s'", fileName.c_str()); - return Common::kUnknownError; - } - - 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); - } - - outFile->write(name, sizeof(name)); - - TimeDate t; - g_system->getTimeAndDate(t); - - 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); - - uint32 playTime = getTotalPlayTime(); - outFile->writeUint32BE(playTime); - - _display->saveThumbnail(*outFile); - - outFile->writeByte(_state.room); - outFile->writeByte(_state.moves); - outFile->writeByte(_state.isDark); - - 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->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); - } - - outFile->writeUint32BE(_state.vars.size()); - for (uint i = 0; i < _state.vars.size(); ++i) - outFile->writeByte(_state.vars[i]); - - outFile->finalize(); +void AdlEngine::drawNextPixel(Common::Point &p, byte color, byte bits, byte quadrant) const { + if (bits & 4) + _display->putPixel(p, color); - if (outFile->err()) { - delete outFile; - warning("Failed to save game '%s'", fileName.c_str()); - return Common::kUnknownError; - } + bits += quadrant; - delete outFile; - return Common::kNoError; + if (bits & 1) + p.x += (bits & 2 ? -1 : 1); + else + p.y += (bits & 2 ? 1 : -1); } -Common::Error AdlEngine::loadGameState(int slot) { - Common::String fileName = Common::String::format("%s.s%02d", _targetName.c_str(), slot); - Common::InSaveFile *inFile = getSaveFileManager()->openForLoading(fileName); - - if (!inFile) { - warning("Failed to open file '%s'", fileName.c_str()); - return Common::kUnknownError; - } - - if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) { - warning("No header found in '%s'", fileName.c_str()); - delete inFile; - return Common::kUnknownError; - } - - byte saveVersion = inFile->readByte(); - if (saveVersion != SAVEGAME_VERSION) { - warning("Save game version %i not supported", saveVersion); - delete inFile; - return Common::kUnknownError; - } - - // Skip description - inFile->seek(SAVEGAME_NAME_LEN, SEEK_CUR); - // Skip save time - inFile->seek(6, SEEK_CUR); - - uint32 playTime = inFile->readUint32BE(); - - Graphics::skipThumbnail(*inFile); - - initState(); - - _state.room = inFile->readByte(); - _state.moves = inFile->readByte(); - _state.isDark = inFile->readByte(); +void AdlEngine::drawLineArt(const Common::Array &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 + }; - uint32 size = inFile->readUint32BE(); - if (size != _state.rooms.size()) - error("Room count mismatch (expected %i; found %i)", _state.rooms.size(), size); + byte quadrant = rotation >> 4; + rotation &= 0xf; + byte xStep = stepping[rotation]; + byte yStep = stepping[(rotation ^ 0xf) + 1] + 1; - for (uint i = 0; i < size; ++i) { - _state.rooms[i].picture = inFile->readByte(); - _state.rooms[i].curPicture = inFile->readByte(); - } + Common::Point p(pos); - 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 < lineArt.size(); ++i) { + byte b = lineArt[i]; - 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(); + 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); } - - size = inFile->readUint32BE(); - if (size != _state.vars.size()) - error("Variable count mismatch (expected %i; found %i)", _state.vars.size(), size); - - for (uint i = 0; i < size; ++i) - _state.vars[i] = inFile->readByte(); - - if (inFile->err() || inFile->eos()) - error("Failed to load game '%s'", fileName.c_str()); - - delete inFile; - - setTotalPlayTime(playTime); - - _isRestoring = true; - return Common::kNoError; } const Room &AdlEngine::room(uint i) const { @@ -763,287 +778,272 @@ byte &AdlEngine::var(uint i) { return _state.vars[i]; } -void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { - uint index = 0; - - while (1) { - ++index; - - byte buf[IDI_WORD_SIZE]; - - if (stream.read(buf, IDI_WORD_SIZE) < IDI_WORD_SIZE) - error("Error reading word list"); - - Common::String word((char *)buf, IDI_WORD_SIZE); - - if (!map.contains(word)) - map[word] = index; - - byte synonyms = stream.readByte(); - - if (stream.err() || stream.eos()) - error("Error reading word list"); +void AdlEngine::takeItem(byte noun) { + Common::Array::iterator item; - if (synonyms == 0xff) - break; + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != _state.room) + continue; - for (uint i = 0; i < synonyms; ++i) { - if (stream.read((char *)buf, IDI_WORD_SIZE) < IDI_WORD_SIZE) - error("Error reading word list"); + if (item->state == IDI_ITEM_DOESNT_MOVE) { + printMessage(_messageIds.itemDoesntMove); + return; + } - word = Common::String((char *)buf, IDI_WORD_SIZE); + if (item->state == IDI_ITEM_MOVED) { + item->room = IDI_NONE; + return; + } - if (!map.contains(word)) - map[word] = index; + Common::Array::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; + } } } -} -Common::String AdlEngine::getLine() const { - // 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 = inputString(APPLECHAR('?')); + printMessage(_messageIds.itemNotHere); +} - if (shouldQuit() || _isRestoring) - return ""; +void AdlEngine::dropItem(byte noun) { + Common::Array::iterator item; - if ((byte)line[0] == ('\r' | 0x80)) { - textMode = !textMode; - _display->setMode(textMode ? DISPLAY_MODE_TEXT : DISPLAY_MODE_MIXED); + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != IDI_NONE) continue; - } - // Remove the return - line.deleteLastChar(); - return line; + item->room = _state.room; + item->state = IDI_ITEM_MOVED; + return; } + + printMessage(_messageIds.dontUnderstand); } -Common::String AdlEngine::getWord(const Common::String &line, uint &index) const { - Common::String str; +#define ARG(N) (command.script[offset + (N)]) - for (uint i = 0; i < 8; ++i) - str += APPLECHAR(' '); +bool AdlEngine::matchCommand(const Command &command, byte verb, byte noun, uint *actions) const { + if (command.room != IDI_NONE && command.room != _state.room) + return false; - int copied = 0; + if (command.verb != IDI_NONE && command.verb != verb) + return false; - // Skip initial whitespace - while (1) { - if (index == line.size()) - return str; - if (line[index] != APPLECHAR(' ')) + 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; - ++index; + 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]); + } } - // Copy up to 8 characters - while (1) { - if (copied < 8) - str.setChar(line[index], copied++); - - index++; + *actions = offset; - if (index == line.size() || line[index] == APPLECHAR(' ')) - return str; - } + return true; } -void AdlEngine::getInput(uint &verb, uint &noun) { - while (1) { - _display->printString(_strings.enterCommand); - Common::String line = getLine(); - - if (shouldQuit() || _isRestoring) - return; - - uint index = 0; - Common::String verbStr = 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); - continue; - } - - verb = _verbs[verbStr]; +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::const_iterator item; - Common::String nounStr = getWord(line, index); + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == IDI_NONE) + printMessage(item->description); - 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); - continue; + ++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); - noun = _nouns[nounStr]; - return; - } -} - -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; + // We allow restoring via GMM here + _canRestoreNow = true; + Common::String input = inputString(); + _canRestoreNow = false; - if (b == ('\r' | 0x80)) { - s += b; - _display->printString(Common::String(b)); - return s; - } + // If the user restored with the GMM, we break off the restart + if (_isRestoring) + return; - 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)); + if (input.size() == 0 || input[0] != APPLECHAR('N')) { + _isRestarting = true; + _display->clear(0x00); + _display->updateHiResScreen(); + restartGame(); + return; + } + // Fall-through } - } -} - -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; -} - -byte AdlEngine::inputKey() const { - Common::EventManager *ev = g_system->getEventManager(); - - byte key = 0; - - _display->showCursor(true); - - while (!g_engine->shouldQuit() && !_isRestoring && key == 0) { - Common::Event event; - if (ev->pollEvent(event)) { - if (event.type != Common::EVENT_KEYDOWN) - continue; + 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 (event.kbd.flags & Common::KBD_CTRL) { - if (event.kbd.keycode == Common::KEYCODE_q) - g_engine->quitGame(); - continue; + if (room == 0) { + printMessage(_messageIds.cantGoThere); + return; } - 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); - }; + 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)); } - - _display->updateTextScreen(); - g_system->delayMillis(16); } - - _display->showCursor(false); - - return key; } -void AdlEngine::delay(uint32 ms) const { - Common::EventManager *ev = g_system->getEventManager(); +#undef ARG - uint32 start = g_system->getMillis(); +bool AdlEngine::doOneCommand(const Commands &commands, byte verb, byte noun) { + Commands::const_iterator cmd; - 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 (cmd = commands.begin(); cmd != commands.end(); ++cmd) { + uint offset = 0; + if (matchCommand(*cmd, verb, noun, &offset)) { + doActions(*cmd, noun, offset); + return true; } - g_system->delayMillis(16); } -} -void AdlEngine::drawNextPixel(Common::Point &p, byte color, byte bits, byte quadrant) const { - if (bits & 4) - _display->putPixel(p, color); - - bits += quadrant; - - if (bits & 1) - p.x += (bits & 2 ? -1 : 1); - else - p.y += (bits & 2 ? 1 : -1); + return false; } -void AdlEngine::drawLineArt(const Common::Array &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 - }; - - byte quadrant = rotation >> 4; - rotation &= 0xf; - byte xStep = stepping[rotation]; - byte yStep = stepping[(rotation ^ 0xf) + 1] + 1; - - Common::Point p(pos); +void AdlEngine::doAllCommands(const Commands &commands, byte verb, byte noun) { + Commands::const_iterator cmd; + bool oldIsRestoring = _isRestoring; - for (uint i = 0; i < lineArt.size(); ++i) { - byte b = lineArt[i]; + for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { + uint offset = 0; + if (matchCommand(*cmd, verb, noun, &offset)) + doActions(*cmd, noun, offset); - 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); + // 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"); } } 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 _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 { -- cgit v1.2.3