diff options
Diffstat (limited to 'engines')
67 files changed, 3709 insertions, 550 deletions
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp new file mode 100644 index 0000000000..1ab74c3cf6 --- /dev/null +++ b/engines/adl/adl.cpp @@ -0,0 +1,1038 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/error.h" +#include "common/file.h" +#include "common/system.h" +#include "common/events.h" +#include "common/stream.h" +#include "common/savefile.h" + +#include "engines/util.h" + +#include "graphics/palette.h" +#include "graphics/thumbnail.h" + +#include "adl/adl.h" +#include "adl/display.h" +#include "adl/detection.h" + +namespace Adl { + +AdlEngine::~AdlEngine() { + delete _display; +} + +AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) : + Engine(syst), + _display(nullptr), + _gameDescription(gd), + _isRestarting(false), + _isRestoring(false), + _saveVerb(0), + _saveNoun(0), + _restoreVerb(0), + _restoreNoun(0), + _canSaveNow(false), + _canRestoreNow(false) { +} + +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; +} + +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 { + if (s.size() < 255) { + 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); + } +} + +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 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); + } + + 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; +} + +bool AdlEngine::hasFeature(EngineFeature f) const { + switch (f) { + case kSupportsLoadingDuringRuntime: + case kSupportsSavingDuringRuntime: + case kSupportsRTL: + return true; + default: + return false; + } +} + +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(); + + 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(); + } + + 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(); + } + + 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; +} + +bool AdlEngine::canLoadGameStateCurrently() { + return _canRestoreNow; +} + +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(); + + if (outFile->err()) { + delete outFile; + warning("Failed to save game '%s'", fileName.c_str()); + return Common::kUnknownError; + } + + delete outFile; + return Common::kNoError; +} + +bool AdlEngine::canSaveGameStateCurrently() { + 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) { + uint offset; + if (matchCommand(*cmd, _saveVerb, _saveNoun, &offset)) + return cmd->script[offset] == 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); + + 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 + bool textMode = false; + + while (1) { + Common::String line = inputString(APPLECHAR('?')); + + if (shouldQuit() || _isRestoring) + return ""; + + if ((byte)line[0] == ('\r' | 0x80)) { + textMode = !textMode; + _display->setMode(textMode ? DISPLAY_MODE_TEXT : DISPLAY_MODE_MIXED); + continue; + } + + // Remove the return + line.deleteLastChar(); + return line; + } +} + +Common::String AdlEngine::getWord(const Common::String &line, uint &index) const { + Common::String str; + + for (uint i = 0; i < 8; ++i) + str += APPLECHAR(' '); + + int copied = 0; + + // Skip initial whitespace + while (1) { + if (index == line.size()) + return str; + if (line[index] != APPLECHAR(' ')) + break; + ++index; + } + + // Copy up to 8 characters + while (1) { + if (copied < 8) + str.setChar(line[index], copied++); + + index++; + + if (index == line.size() || line[index] == APPLECHAR(' ')) + return str; + } +} + +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]; + + Common::String nounStr = 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); + continue; + } + + noun = _nouns[nounStr]; + return; + } +} + +void AdlEngine::showRoom() const { + if (!_state.isDark) { + drawPic(getCurRoom().curPicture); + drawItems(); + } + + _display->updateHiResScreen(); + printMessage(getCurRoom().description, false); +} + +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 (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; + } + + Common::Array<byte>::const_iterator pic; + + 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; + } + } + } +} + +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); +} + +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 + }; + + 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); + } +} + +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 { + 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::getItem(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]; +} + +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::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 == getCurRoom().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 (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 (actions) + *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: + 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; + + 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: + 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; + } + + getCurRoom().curPicture = getCurRoom().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: + getRoom(ARG(1)).picture = getRoom(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; + + for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { + uint offset = 0; + if (matchCommand(*cmd, verb, noun, &offset)) + doActions(*cmd, noun, offset); + } +} + +} // End of namespace Adl diff --git a/engines/adl/adl.h b/engines/adl/adl.h new file mode 100644 index 0000000000..4ea7566669 --- /dev/null +++ b/engines/adl/adl.h @@ -0,0 +1,247 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ADL_ADL_H +#define ADL_ADL_H + +#include "common/array.h" +#include "common/rect.h" +#include "common/str.h" + +#include "engines/engine.h" + +namespace Common { +class ReadStream; +class SeekableReadStream; +} + +namespace Adl { + +class Display; +struct AdlGameDescription; + +// Conditional opcodes +#define IDO_CND_ITEM_IN_ROOM 0x03 +#define IDO_CND_MOVES_GE 0x05 +#define IDO_CND_VAR_EQ 0x06 +#define IDO_CND_CUR_PIC_EQ 0x09 +#define IDO_CND_ITEM_PIC_EQ 0x0a + +// Action opcodes +#define IDO_ACT_VAR_ADD 0x01 +#define IDO_ACT_VAR_SUB 0x02 +#define IDO_ACT_VAR_SET 0x03 +#define IDO_ACT_LIST_ITEMS 0x04 +#define IDO_ACT_MOVE_ITEM 0x05 +#define IDO_ACT_SET_ROOM 0x06 +#define IDO_ACT_SET_CUR_PIC 0x07 +#define IDO_ACT_SET_PIC 0x08 +#define IDO_ACT_PRINT_MSG 0x09 +#define IDO_ACT_SET_LIGHT 0x0a +#define IDO_ACT_SET_DARK 0x0b +#define IDO_ACT_QUIT 0x0d +#define IDO_ACT_SAVE 0x0f +#define IDO_ACT_LOAD 0x10 +#define IDO_ACT_RESTART 0x11 +#define IDO_ACT_PLACE_ITEM 0x12 +#define IDO_ACT_SET_ITEM_PIC 0x13 +#define IDO_ACT_RESET_PIC 0x14 +#define IDO_ACT_GO_NORTH 0x15 +#define IDO_ACT_GO_SOUTH 0x16 +#define IDO_ACT_GO_EAST 0x17 +#define IDO_ACT_GO_WEST 0x18 +#define IDO_ACT_GO_UP 0x19 +#define IDO_ACT_GO_DOWN 0x1a +#define IDO_ACT_TAKE_ITEM 0x1b +#define IDO_ACT_DROP_ITEM 0x1c +#define IDO_ACT_SET_ROOM_PIC 0x1d + +#define IDI_WORD_SIZE 8 + +struct Room { + byte description; + byte connections[6]; + byte picture; + byte curPicture; +}; + +struct Picture { + byte block; + uint16 offset; +}; + +struct Command { + byte room; + byte verb, noun; + byte numCond, numAct; + Common::Array<byte> script; +}; + +enum { + IDI_ITEM_NOT_MOVED, + IDI_ITEM_MOVED, + IDI_ITEM_DOESNT_MOVE +}; + +#define IDI_NONE 0xfe + +struct Item { + byte noun; + byte room; + byte picture; + bool isLineArt; + Common::Point position; + int state; + byte description; + Common::Array<byte> roomPictures; +}; + +struct State { + Common::Array<Room> rooms; + Common::Array<Item> items; + Common::Array<byte> vars; + + byte room; + uint16 moves; + bool isDark; + + State() : room(1), moves(0), isDark(false) { } +}; + +typedef Common::List<Command> Commands; +typedef Common::HashMap<Common::String, uint> WordMap; + +class AdlEngine : public Engine { +public: + virtual ~AdlEngine(); + +protected: + AdlEngine(OSystem *syst, const AdlGameDescription *gd); + + Common::String readString(Common::ReadStream &stream, byte until = 0) const; + Common::String readStringAt(Common::SeekableReadStream &stream, uint offset, byte until = 0) const; + + virtual void printMessage(uint idx, bool wait = true) const; + void delay(uint32 ms) const; + + Common::String inputString(byte prompt = 0) const; + byte inputKey() const; + + void loadWords(Common::ReadStream &stream, WordMap &map) const; + void readCommands(Common::ReadStream &stream, Commands &commands); + + Display *_display; + + // Message strings in data file + Common::Array<Common::String> _messages; + // Picture data + Common::Array<Picture> _pictures; + // Dropped item screen offsets + Common::Array<Common::Point> _itemOffsets; + // Drawings consisting of horizontal and vertical lines only, but + // supporting scaling and rotation + Common::Array<Common::Array<byte> > _lineArt; + // <room, verb, noun, script> lists + Commands _roomCommands; + Commands _globalCommands; + + WordMap _verbs; + WordMap _nouns; + + struct { + Common::String enterCommand; + Common::String dontHaveIt; + Common::String gettingDark; + Common::String verbError; + Common::String nounError; + Common::String playAgain; + } _strings; + + struct { + uint cantGoThere; + uint dontUnderstand; + uint itemDoesntMove; + uint itemNotHere; + uint thanksForPlaying; + } _messageIds; + + // Game state + State _state; + +private: + virtual void runIntro() const { } + virtual void loadData() = 0; + virtual void initState() = 0; + virtual void restartGame() = 0; + virtual void drawPic(byte pic, Common::Point pos = Common::Point()) const = 0; + + // Engine + Common::Error run(); + bool hasFeature(EngineFeature f) const; + Common::Error loadGameState(int slot); + bool canLoadGameStateCurrently(); + Common::Error saveGameState(int slot, const Common::String &desc); + bool canSaveGameStateCurrently(); + + // Text output + void wordWrap(Common::String &str) const; + + // Text input + byte convertKey(uint16 ascii) const; + Common::String getLine() const; + Common::String getWord(const Common::String &line, uint &index) const; + void getInput(uint &verb, uint &noun); + + // Graphics + void showRoom() const; + void clearScreen() const; + void drawItems() const; + void drawNextPixel(Common::Point &p, byte color, byte bits, byte quadrant) const; + void drawLineArt(const Common::Array<byte> &lineArt, const Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const; + + // Game state functions + const Room &getRoom(uint i) const; + Room &getRoom(uint i); + const Room &getCurRoom() const; + Room &getCurRoom(); + const Item &getItem(uint i) const; + Item &getItem(uint i); + byte getVar(uint i) const; + void setVar(uint i, byte value); + 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); + + const AdlGameDescription *_gameDescription; + bool _isRestarting, _isRestoring; + byte _saveVerb, _saveNoun, _restoreVerb, _restoreNoun; + bool _canSaveNow, _canRestoreNow; +}; + +AdlEngine *HiRes1Engine__create(OSystem *syst, const AdlGameDescription *gd); + +} // End of namespace Adl + +#endif diff --git a/engines/adl/configure.engine b/engines/adl/configure.engine new file mode 100644 index 0000000000..844e2b8e6a --- /dev/null +++ b/engines/adl/configure.engine @@ -0,0 +1,3 @@ +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine adl "ADL" no diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp new file mode 100644 index 0000000000..1a8c5025e8 --- /dev/null +++ b/engines/adl/detection.cpp @@ -0,0 +1,247 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/system.h" +#include "common/savefile.h" +#include "common/translation.h" + +#include "graphics/thumbnail.h" + +#include "engines/advancedDetector.h" + +#include "adl/detection.h" + +namespace Adl { + +#define GAMEOPTION_COLOR GUIO_GAMEOPTIONS1 +#define GAMEOPTION_SCANLINES GUIO_GAMEOPTIONS2 + +static const ADExtraGuiOptionsMap optionsList[] = { + { + GAMEOPTION_COLOR, + { + _s("Color mode"), + _s("Use color graphics"), + "color", + false + } + }, + + { + GAMEOPTION_SCANLINES, + { + _s("Scanlines"), + _s("Show scanlines"), + "scanlines", + false + } + }, + + AD_EXTRA_GUI_OPTIONS_TERMINATOR +}; + +static const PlainGameDescriptor adlGames[] = { + {"hires1", "Hi-Res Adventure #1: Mystery House"}, + {0, 0} +}; + +static const AdlGameDescription gameDescriptions[] = { + { // Hi-Res Adventure #1: Mystery House - Apple II - 1987 PD release + { + "hires1", 0, + { + {"ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952}, + {"AUTO LOAD OBJ", 0, "23bfccfe9fcff9b22cf6c41bde9078ac", 12291}, + {"MYSTERY.HELLO", 0, "2289b7fea300b506e902a4c597968369", 836}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformApple2GS, // FIXME + ADGF_NO_FLAGS, + GUIO2(GAMEOPTION_COLOR, GAMEOPTION_SCANLINES) + }, + GAME_TYPE_HIRES1 + }, + { AD_TABLE_END_MARKER, GAME_TYPE_NONE } +}; + +class AdlMetaEngine : public AdvancedMetaEngine { +public: + AdlMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(AdlGameDescription), adlGames, optionsList) { } + + const char *getName() const { + return "ADL"; + } + + const char *getOriginalCopyright() const { + return "Copyright (C) Sierra On-Line"; + } + + bool hasFeature(MetaEngineFeature f) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; + int getMaximumSaveSlot() const { return 'O' - 'A'; } + SaveStateList listSaves(const char *target) const; + void removeSaveState(const char *target, int slot) const; + + bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; +}; + +bool AdlMetaEngine::hasFeature(MetaEngineFeature f) const { + switch(f) { + case kSupportsListSaves: + case kSupportsLoadingDuringStartup: + case kSupportsDeleteSave: + case kSavesSupportMetaInfo: + case kSavesSupportThumbnail: + case kSavesSupportCreationDate: + case kSavesSupportPlayTime: + return true; + default: + return false; + } +} + +SaveStateDescriptor AdlMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String fileName = Common::String::format("%s.s%02d", target, slot); + Common::InSaveFile *inFile = g_system->getSavefileManager()->openForLoading(fileName); + + if (!inFile) + return SaveStateDescriptor(); + + if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) { + delete inFile; + return SaveStateDescriptor(); + } + + byte saveVersion = inFile->readByte(); + if (saveVersion != SAVEGAME_VERSION) { + delete inFile; + return SaveStateDescriptor(); + } + + char name[SAVEGAME_NAME_LEN] = { }; + inFile->read(name, sizeof(name) - 1); + inFile->readByte(); + + if (inFile->eos() || inFile->err()) { + delete inFile; + return SaveStateDescriptor(); + } + + SaveStateDescriptor sd(slot, name); + + int year = inFile->readUint16BE(); + int month = inFile->readByte(); + int day = inFile->readByte(); + sd.setSaveDate(year + 1900, month + 1, day); + + int hour = inFile->readByte(); + int minutes = inFile->readByte(); + sd.setSaveTime(hour, minutes); + + uint32 playTime = inFile->readUint32BE(); + sd.setPlayTime(playTime); + + if (inFile->eos() || inFile->err()) { + delete inFile; + return SaveStateDescriptor(); + } + + Graphics::Surface *const thumbnail = Graphics::loadThumbnail(*inFile); + sd.setThumbnail(thumbnail); + + delete inFile; + return sd; +} + +SaveStateList AdlMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray files = saveFileMan->listSavefiles(Common::String(target) + ".s##"); + + SaveStateList saveList; + + for (uint i = 0; i < files.size(); ++i) { + const Common::String &fileName = files[i]; + Common::InSaveFile *inFile = saveFileMan->openForLoading(fileName); + if (!inFile) { + warning("Cannot open save file '%s'", fileName.c_str()); + continue; + } + + if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) { + warning("No header found in '%s'", fileName.c_str()); + delete inFile; + continue; + } + + byte saveVersion = inFile->readByte(); + if (saveVersion != SAVEGAME_VERSION) { + warning("Unsupported save game version %i found in '%s'", saveVersion, fileName.c_str()); + delete inFile; + continue; + } + + char name[SAVEGAME_NAME_LEN] = { }; + inFile->read(name, sizeof(name) - 1); + delete inFile; + + int slotNum = atoi(fileName.c_str() + fileName.size() - 2); + SaveStateDescriptor sd(slotNum, name); + saveList.push_back(sd); + } + + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); + return saveList; +} + +void AdlMetaEngine::removeSaveState(const char *target, int slot) const { + Common::String fileName = Common::String::format("%s.s%02d", target, slot); + g_system->getSavefileManager()->removeSavefile(fileName); +} + +Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd); + +bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { + if (!gd) + return false; + + const AdlGameDescription *adlGd = (const AdlGameDescription *)gd; + + switch (adlGd->gameType) { + case GAME_TYPE_HIRES1: + *engine = HiRes1Engine_create(syst, adlGd); + break; + default: + error("Unknown GameType"); + } + + return true; +} + +} // End of namespace Adl + +#if PLUGIN_ENABLED_DYNAMIC(ADL) + REGISTER_PLUGIN_DYNAMIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine); +#else + REGISTER_PLUGIN_STATIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine); +#endif diff --git a/engines/adl/detection.h b/engines/adl/detection.h new file mode 100644 index 0000000000..c646aeb5b9 --- /dev/null +++ b/engines/adl/detection.h @@ -0,0 +1,45 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ADL_DETECTION_H +#define ADL_DETECTION_H + +#include "engines/advancedDetector.h" + +namespace Adl { + +#define SAVEGAME_VERSION 0 +#define SAVEGAME_NAME_LEN 32 + +enum GameType { + GAME_TYPE_NONE, + GAME_TYPE_HIRES1 +}; + +struct AdlGameDescription { + ADGameDescription desc; + GameType gameType; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp new file mode 100644 index 0000000000..6342504bc3 --- /dev/null +++ b/engines/adl/display.cpp @@ -0,0 +1,520 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/stream.h" +#include "common/rect.h" +#include "common/system.h" +#include "common/str.h" +#include "common/config-manager.h" + +#include "graphics/surface.h" +#include "graphics/palette.h" +#include "graphics/thumbnail.h" + +#include "engines/util.h" + +#include "adl/display.h" + +namespace Adl { + +// This implements the Apple II "Hi-Res" display mode + +#define DISPLAY_PITCH (DISPLAY_WIDTH / 7) +#define DISPLAY_SIZE (DISPLAY_PITCH * DISPLAY_HEIGHT) + +#define TEXT_WIDTH 40 +#define TEXT_HEIGHT 24 +#define TEXT_BUF_SIZE (TEXT_WIDTH * TEXT_HEIGHT) + +#define COLOR_PALETTE_ENTRIES 8 +static const byte colorPalette[COLOR_PALETTE_ENTRIES * 3] = { + 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, + 0xc7, 0x34, 0xff, + 0x38, 0xcb, 0x00, + 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, + 0x0d, 0xa1, 0xff, + 0xf2, 0x5e, 0x00 +}; + +// Corresponding color in second palette +#define PAL2(X) ((X) | 0x04) + +// Alternate color for odd pixel rows (for scanlines) +#define ALTCOL(X) ((X) | 0x08) + +// Green monochrome palette +#define MONO_PALETTE_ENTRIES 2 +static const byte monoPalette[MONO_PALETTE_ENTRIES * 3] = { + 0x00, 0x00, 0x00, + 0x00, 0xc0, 0x01 +}; + +// Uppercase-only Apple II font (manually created). +static const byte font[64][5] = { + { 0x7c, 0x82, 0xba, 0xb2, 0x9c }, { 0xf8, 0x24, 0x22, 0x24, 0xf8 }, // @A + { 0xfe, 0x92, 0x92, 0x92, 0x6c }, { 0x7c, 0x82, 0x82, 0x82, 0x44 }, // BC + { 0xfe, 0x82, 0x82, 0x82, 0x7c }, { 0xfe, 0x92, 0x92, 0x92, 0x82 }, // DE + { 0xfe, 0x12, 0x12, 0x12, 0x02 }, { 0x7c, 0x82, 0x82, 0xa2, 0xe2 }, // FG + { 0xfe, 0x10, 0x10, 0x10, 0xfe }, { 0x00, 0x82, 0xfe, 0x82, 0x00 }, // HI + { 0x40, 0x80, 0x80, 0x80, 0x7e }, { 0xfe, 0x10, 0x28, 0x44, 0x82 }, // JK + { 0xfe, 0x80, 0x80, 0x80, 0x80 }, { 0xfe, 0x04, 0x18, 0x04, 0xfe }, // LM + { 0xfe, 0x08, 0x10, 0x20, 0xfe }, { 0x7c, 0x82, 0x82, 0x82, 0x7c }, // NO + { 0xfe, 0x12, 0x12, 0x12, 0x0c }, { 0x7c, 0x82, 0xa2, 0x42, 0xbc }, // PQ + { 0xfe, 0x12, 0x32, 0x52, 0x8c }, { 0x4c, 0x92, 0x92, 0x92, 0x64 }, // RS + { 0x02, 0x02, 0xfe, 0x02, 0x02 }, { 0x7e, 0x80, 0x80, 0x80, 0x7e }, // TU + { 0x3e, 0x40, 0x80, 0x40, 0x3e }, { 0xfe, 0x40, 0x30, 0x40, 0xfe }, // VW + { 0xc6, 0x28, 0x10, 0x28, 0xc6 }, { 0x06, 0x08, 0xf0, 0x08, 0x06 }, // XY + { 0xc2, 0xa2, 0x92, 0x8a, 0x86 }, { 0xfe, 0xfe, 0x82, 0x82, 0x82 }, // Z[ + { 0x04, 0x08, 0x10, 0x20, 0x40 }, { 0x82, 0x82, 0x82, 0xfe, 0xfe }, // \] + { 0x20, 0x10, 0x08, 0x10, 0x20 }, { 0x80, 0x80, 0x80, 0x80, 0x80 }, // ^_ + { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0xbe, 0x00, 0x00 }, // ! + { 0x00, 0x0e, 0x00, 0x0e, 0x00 }, { 0x28, 0xfe, 0x28, 0xfe, 0x28 }, // "# + { 0x48, 0x54, 0xfe, 0x54, 0x24 }, { 0x46, 0x26, 0x10, 0xc8, 0xc4 }, // $% + { 0x6c, 0x92, 0xac, 0x40, 0xa0 }, { 0x00, 0x00, 0x0e, 0x00, 0x00 }, // &' + { 0x38, 0x44, 0x82, 0x00, 0x00 }, { 0x00, 0x00, 0x82, 0x44, 0x38 }, // () + { 0x44, 0x28, 0xfe, 0x28, 0x44 }, { 0x10, 0x10, 0x7c, 0x10, 0x10 }, // *+ + { 0x00, 0x80, 0x60, 0x00, 0x00 }, { 0x10, 0x10, 0x10, 0x10, 0x10 }, // ,- + { 0x00, 0x00, 0x80, 0x00, 0x00 }, { 0x40, 0x20, 0x10, 0x08, 0x04 }, // ./ + { 0x7c, 0xa2, 0x92, 0x8a, 0x7c }, { 0x00, 0x84, 0xfe, 0x80, 0x00 }, // 01 + { 0xc4, 0xa2, 0x92, 0x92, 0x8c }, { 0x42, 0x82, 0x92, 0x9a, 0x66 }, // 23 + { 0x30, 0x28, 0x24, 0xfe, 0x20 }, { 0x4e, 0x8a, 0x8a, 0x8a, 0x72 }, // 45 + { 0x78, 0x94, 0x92, 0x92, 0x62 }, { 0x02, 0xe2, 0x12, 0x0a, 0x06 }, // 67 + { 0x6c, 0x92, 0x92, 0x92, 0x6c }, { 0x8c, 0x92, 0x92, 0x52, 0x3c }, // 89 + { 0x00, 0x00, 0x28, 0x00, 0x00 }, { 0x00, 0x80, 0x68, 0x00, 0x00 }, // :; + { 0x10, 0x28, 0x44, 0x82, 0x00 }, { 0x28, 0x28, 0x28, 0x28, 0x28 }, // <= + { 0x00, 0x82, 0x44, 0x28, 0x10 }, { 0x04, 0x02, 0xb2, 0x0a, 0x04 } // >? +}; + +Display::Display() : + _mode(DISPLAY_MODE_TEXT), + _cursorPos(0), + _showCursor(false) { + + initGraphics(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, true); + + _monochrome = !ConfMan.getBool("color"); + _scanlines = ConfMan.getBool("scanlines"); + + if (_monochrome) + g_system->getPaletteManager()->setPalette(monoPalette, 0, MONO_PALETTE_ENTRIES); + else + g_system->getPaletteManager()->setPalette(colorPalette, 0, COLOR_PALETTE_ENTRIES); + + showScanlines(_scanlines); + + _frameBuf = new byte[DISPLAY_SIZE]; + memset(_frameBuf, 0, DISPLAY_SIZE); + _frameBufSurface = new Graphics::Surface; + // We need 2x scaling to properly render the half-pixel shift + // of the second palette + _frameBufSurface->create(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, Graphics::PixelFormat::createFormatCLUT8()); + + _textBuf = new byte[TEXT_BUF_SIZE]; + memset(_textBuf, APPLECHAR(' '), TEXT_BUF_SIZE); + _textBufSurface = new Graphics::Surface; + // For ease of copying, also use 2x scaling here + _textBufSurface->create(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, Graphics::PixelFormat::createFormatCLUT8()); + + createFont(); +} + +Display::~Display() { + delete[] _frameBuf; + _frameBufSurface->free(); + delete _frameBufSurface; + + delete[] _textBuf; + _textBufSurface->free(); + delete _textBufSurface; + + _font->free(); + delete _font; +} + +void Display::setMode(DisplayMode mode) { + _mode = mode; + + if (_mode == DISPLAY_MODE_TEXT || _mode == DISPLAY_MODE_MIXED) + updateTextScreen(); + if (_mode == DISPLAY_MODE_HIRES || _mode == DISPLAY_MODE_MIXED) + updateHiResScreen(); +} + +void Display::updateTextScreen() { + updateTextSurface(); + + if (_mode == DISPLAY_MODE_TEXT) + g_system->copyRectToScreen(_textBufSurface->getPixels(), _textBufSurface->pitch, 0, 0, _textBufSurface->w, _textBufSurface->h); + else if (_mode == DISPLAY_MODE_MIXED) + g_system->copyRectToScreen(_textBufSurface->getBasePtr(0, _textBufSurface->h - 4 * 8 * 2), _textBufSurface->pitch, 0, _textBufSurface->h - 4 * 8 * 2, _textBufSurface->w, 4 * 8 * 2); + + g_system->updateScreen(); +} + +void Display::updateHiResScreen() { + updateHiResSurface(); + + if (_mode == DISPLAY_MODE_HIRES) + g_system->copyRectToScreen(_frameBufSurface->getPixels(), _frameBufSurface->pitch, 0, 0, _frameBufSurface->w, _frameBufSurface->h); + else if (_mode == DISPLAY_MODE_MIXED) + g_system->copyRectToScreen(_frameBufSurface->getPixels(), _frameBufSurface->pitch, 0, 0, _frameBufSurface->w, _frameBufSurface->h - 4 * 8 * 2); + + g_system->updateScreen(); +} + +bool Display::saveThumbnail(Common::WriteStream &out) { + if (_scanlines) { + showScanlines(false); + g_system->updateScreen(); + } + + bool retval = Graphics::saveThumbnail(out); + + if (_scanlines) { + showScanlines(true); + g_system->updateScreen(); + } + + return retval; +} + +void Display::loadFrameBuffer(Common::ReadStream &stream) { + byte *dst = _frameBuf; + for (uint j = 0; j < 8; ++j) { + for (uint i = 0; i < 8; ++i) { + stream.read(dst, DISPLAY_PITCH); + dst += DISPLAY_PITCH * 64; + stream.read(dst, DISPLAY_PITCH); + dst += DISPLAY_PITCH * 64; + stream.read(dst, DISPLAY_PITCH); + stream.readUint32LE(); + stream.readUint32LE(); + dst -= DISPLAY_PITCH * 120; + } + dst -= DISPLAY_PITCH * 63; + } + + if (stream.eos() || stream.err()) + error("Failed to read frame buffer"); +} + +void Display::putPixel(const Common::Point &p, byte color) { + byte offset = p.x / 7; + + if (offset & 1) { + byte c = color << 1; + if (c >= 0x40 && c < 0xc0) + color ^= 0x7f; + } + + byte *b = _frameBuf + p.y * DISPLAY_PITCH + offset; + color ^= *b; + color &= 1 << (p.x % 7); + *b ^= color; +} + +void Display::clear(byte color) { + byte val = 0; + + byte c = color << 1; + if (c >= 0x40 && c < 0xc0) + val = 0x7f; + + for (uint i = 0; i < DISPLAY_SIZE; ++i) { + _frameBuf[i] = color; + color ^= val; + } +} + +void Display::home() { + memset(_textBuf, APPLECHAR(' '), TEXT_BUF_SIZE); + _cursorPos = 0; +} + +void Display::moveCursorForward() { + ++_cursorPos; + + if (_cursorPos >= TEXT_BUF_SIZE) + scrollUp(); +} + +void Display::moveCursorBackward() { + if (_cursorPos > 0) + --_cursorPos; +} + +void Display::moveCursorTo(const Common::Point &pos) { + _cursorPos = pos.y * TEXT_WIDTH + pos.x; + + if (_cursorPos >= TEXT_BUF_SIZE) + error("Cursor position (%i, %i) out of bounds", pos.x, pos.y); +} + +void Display::printString(const Common::String &str) { + Common::String::const_iterator c; + for (c = str.begin(); c != str.end(); ++c) { + byte b = *c; + + if (*c == APPLECHAR('\r')) + _cursorPos = (_cursorPos / TEXT_WIDTH + 1) * TEXT_WIDTH; + else if (b < 0x80 || b >= 0xa0) { + setCharAtCursor(b); + ++_cursorPos; + } + + if (_cursorPos == TEXT_BUF_SIZE) + scrollUp(); + } + + updateTextScreen(); +} + +void Display::printAsciiString(const Common::String &str) { + Common::String aStr; + + Common::String::const_iterator it; + for (it = str.begin(); it != str.end(); ++it) + aStr += APPLECHAR(*it); + + printString(aStr); +} + +void Display::setCharAtCursor(byte c) { + _textBuf[_cursorPos] = c; +} + +void Display::showCursor(bool enable) { + _showCursor = enable; +} + +void Display::showScanlines(bool enable) { + byte pal[COLOR_PALETTE_ENTRIES * 3] = { }; + + if (enable) + g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES); + else { + g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES); + g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES); + } +} + +static byte processColorBits(uint16 &bits, bool &odd, bool secondPal) { + byte color = 0; + + switch (bits & 0x7) { + case 0x3: // 011 (white) + case 0x6: // 110 + case 0x7: // 111 + color = 1; + break; + case 0x2: // 010 (color) + color = 2 + odd; + break; + case 0x5: // 101 (color) + color = 2 + !odd; + } + + if (secondPal) + color = PAL2(color); + + odd = !odd; + bits >>= 1; + + return color; +} + +static void renderPixelRowColor(byte *dst, byte *src) { + uint16 bits = (src[0] & 0x7f) << 1; + byte pal = src[0] >> 7; + + if (pal != 0) + *dst++ = 0; + + bool odd = false; + + for (uint i = 0; i < DISPLAY_PITCH; ++i) { + if (i != DISPLAY_PITCH - 1) { + bits |= (src[i + 1] & 0x7f) << 8; + pal |= (src[i + 1] >> 7) << 1; + } + + // For the first 6 bits in the block we draw two pixels + for (uint j = 0; j < 6; ++j) { + byte color = processColorBits(bits, odd, pal & 1); + *dst++ = color; + *dst++ = color; + } + + // Last bit of the block, draw one, two or three pixels + byte color = processColorBits(bits, odd, pal & 1); + + // Draw the first pixel + *dst++ = color; + + switch (pal) { + case 0x0: + case 0x3: + // If palette stays the same, draw a second pixel + *dst++ = color; + break; + case 0x2: + // If we're moving from first to second palette, + // draw a second pixel, and a third in the second + // palette. + *dst++ = color; + *dst++ = PAL2(color); + } + + pal >>= 1; + } +} + +static void renderPixelRowMono(byte *dst, byte *src) { + byte pal = src[0] >> 7; + + if (pal != 0) + *dst++ = 0; + + for (uint i = 0; i < DISPLAY_PITCH; ++i) { + if (i != DISPLAY_PITCH - 1) + pal |= (src[i + 1] >> 7) << 1; + + for (uint j = 0; j < 6; ++j) { + bool color = src[i] & (1 << j); + *dst++ = color; + *dst++ = color; + } + + bool color = src[i] & (1 << 6); + + *dst++ = color; + + switch (pal) { + case 0x0: + case 0x3: + *dst++ = color; + break; + case 0x2: + *dst++ = color; + *dst++ = color; + } + + pal >>= 1; + } +} + +static void copyEvenSurfaceRows(Graphics::Surface &surf) { + byte *src = (byte *)surf.getPixels(); + + for (uint y = 0; y < surf.h / 2; ++y) { + byte *dst = src + surf.pitch; + for (uint x = 0; x < surf.w; ++x) + dst[x] = ALTCOL(src[x]); + src += surf.pitch * 2; + } +} + +void Display::updateHiResSurface() { + byte *src = _frameBuf; + byte *dst = (byte *)_frameBufSurface->getPixels(); + + for (uint i = 0; i < DISPLAY_HEIGHT; ++i) { + if (_monochrome) + renderPixelRowMono(dst, src); + else + renderPixelRowColor(dst, src); + src += DISPLAY_PITCH; + dst += _frameBufSurface->pitch * 2; + } + + copyEvenSurfaceRows(*_frameBufSurface); +} + +void Display::updateTextSurface() { + for (uint row = 0; row < 24; ++row) + for (uint col = 0; col < TEXT_WIDTH; ++col) { + uint charPos = row * TEXT_WIDTH + col; + char c = _textBuf[row * TEXT_WIDTH + col]; + + if (charPos == _cursorPos && _showCursor) + c = (c & 0x3f) | 0x40; + + Common::Rect r(7 * 2, 8 * 2); + r.translate(((c & 0x3f) % 16) * 7 * 2, (c & 0x3f) / 16 * 8 * 2); + + if (!(c & 0x80)) { + if (!(c & 0x40) || ((g_system->getMillis() / 270) & 1)) + r.translate(0, 4 * 8 * 2); + } + + _textBufSurface->copyRectToSurface(*_font, col * 7 * 2, row * 8 * 2, r); + } +} + +void Display::drawChar(byte c, int x, int y) { + byte *buf = (byte *)_font->getPixels() + y * _font->pitch + x; + + for (uint row = 0; row < 8; ++row) { + for (uint col = 1; col < 6; ++col) { + if (font[c][col - 1] & (1 << row)) { + buf[col * 2] = 1; + buf[col * 2 + 1] = 1; + } + } + + buf += 2 * _font->pitch; + } +} + +void Display::createFont() { + _font = new Graphics::Surface; + _font->create(16 * 7 * 2, 4 * 8 * 2 * 2, Graphics::PixelFormat::createFormatCLUT8()); + + for (uint i = 0; i < 4; ++i) + for (uint j = 0; j < 16; ++j) + drawChar(i * 16 + j, j * 7 * 2, i * 8 * 2); + + // Create inverted font + byte *buf = (byte *)_font->getPixels(); + byte *bufInv = buf + (_font->h / 2) * _font->pitch; + + for (uint row = 0; row < _font->h / 2; row += 2) { + for (uint col = 0; col < _font->w; ++col) + bufInv[col] = (buf[col] ? 0 : 1); + + buf += _font->pitch * 2; + bufInv += _font->pitch * 2; + } + + copyEvenSurfaceRows(*_font); +} + +void Display::scrollUp() { + memmove(_textBuf, _textBuf + TEXT_WIDTH, TEXT_BUF_SIZE - TEXT_WIDTH); + memset(_textBuf + TEXT_BUF_SIZE - TEXT_WIDTH, APPLECHAR(' '), TEXT_WIDTH); + if (_cursorPos >= TEXT_WIDTH) + _cursorPos -= TEXT_WIDTH; +} + +} // End of namespace Adl diff --git a/engines/adl/display.h b/engines/adl/display.h new file mode 100644 index 0000000000..ff01e3faf2 --- /dev/null +++ b/engines/adl/display.h @@ -0,0 +1,102 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ADL_DISPLAY_H +#define ADL_DISPLAY_H + +#include <common/types.h> + +namespace Common { +class ReadStream; +class WriteStream; +class String; +struct Point; +} + +namespace Graphics { +struct Surface; +} + +namespace Adl { + +#define DISPLAY_WIDTH 280 +#define DISPLAY_HEIGHT 192 + +enum DisplayMode { + DISPLAY_MODE_HIRES, + DISPLAY_MODE_TEXT, + DISPLAY_MODE_MIXED +}; + +#define APPLECHAR(C) ((char)((C) | 0x80)) + +class Display { +public: + Display(); + ~Display(); + + void setMode(DisplayMode mode); + void updateTextScreen(); + void updateHiResScreen(); + bool saveThumbnail(Common::WriteStream &out); + + // Graphics + void loadFrameBuffer(Common::ReadStream &stream); + void putPixel(const Common::Point &p, byte color); + void clear(byte color); + + // Text + void home(); + void moveCursorTo(const Common::Point &pos); + void moveCursorForward(); + void moveCursorBackward(); + void printString(const Common::String &str); + void printAsciiString(const Common::String &str); + void setCharAtCursor(byte c); + void showCursor(bool enable); + +private: + void updateHiResSurface(); + void showScanlines(bool enable); + + void updateTextSurface(); + void drawChar(byte c, int x, int y); + void createFont(); + void scrollUp(); + + DisplayMode _mode; + + byte *_frameBuf; + Graphics::Surface *_frameBufSurface; + bool _scanlines; + bool _monochrome; + + byte *_textBuf; + Graphics::Surface *_textBufSurface; + Graphics::Surface *_font; + uint _cursorPos; + bool _showCursor; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp new file mode 100644 index 0000000000..6e1e31df9f --- /dev/null +++ b/engines/adl/hires1.cpp @@ -0,0 +1,396 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/system.h" +#include "common/debug.h" +#include "common/error.h" +#include "common/file.h" +#include "common/stream.h" + +#include "adl/hires1.h" +#include "adl/display.h" + +namespace Adl { + +void HiRes1Engine::runIntro() const { + Common::File file; + + if (!file.open(IDS_HR1_EXE_0)) + error("Failed to open file '" IDS_HR1_EXE_0 "'"); + + file.seek(IDI_HR1_OFS_LOGO_0); + _display->setMode(DISPLAY_MODE_HIRES); + _display->loadFrameBuffer(file); + _display->updateHiResScreen(); + delay(4000); + + if (shouldQuit()) + return; + + _display->setMode(DISPLAY_MODE_TEXT); + + Common::File basic; + if (!basic.open(IDS_HR1_LOADER)) + error("Failed to open file '" IDS_HR1_LOADER "'"); + + Common::String str; + + str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_0, '"'); + _display->printAsciiString(str + '\r'); + + str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_1, '"'); + _display->printAsciiString(str + "\r\r"); + + str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_2, '"'); + _display->printAsciiString(str + "\r\r"); + + str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_3, '"'); + _display->printAsciiString(str + '\r'); + + inputKey(); + if (g_engine->shouldQuit()) + return; + + _display->setMode(DISPLAY_MODE_MIXED); + + str = readStringAt(file, IDI_HR1_OFS_GAME_OR_HELP); + + bool instructions = false; + + while (1) { + _display->printString(str); + Common::String s = inputString(); + + if (g_engine->shouldQuit()) + break; + + if (s.empty()) + continue; + + if (s[0] == APPLECHAR('I')) { + instructions = true; + break; + } else if (s[0] == APPLECHAR('G')) { + break; + } + }; + + if (instructions) { + _display->setMode(DISPLAY_MODE_TEXT); + file.seek(IDI_HR1_OFS_INTRO_TEXT); + + const uint pages[] = { 6, 6, 4, 5, 8, 7, 0 }; + + uint page = 0; + while (pages[page] != 0) { + _display->home(); + + uint count = pages[page++]; + for (uint i = 0; i < count; ++i) { + str = readString(file); + _display->printString(str); + file.seek(3, SEEK_CUR); + } + + inputString(); + + if (g_engine->shouldQuit()) + return; + + file.seek(6, SEEK_CUR); + } + } + + _display->printAsciiString("\r"); + + file.close(); + + _display->setMode(DISPLAY_MODE_MIXED); + + if (!file.open(IDS_HR1_EXE_1)) + error("Failed to open file '" IDS_HR1_EXE_1 "'"); + + // Title screen shown during loading + file.seek(IDI_HR1_OFS_LOGO_1); + _display->loadFrameBuffer(file); + _display->updateHiResScreen(); + delay(2000); +} + +void HiRes1Engine::loadData() { + Common::File f; + + if (!f.open(IDS_HR1_MESSAGES)) + error("Failed to open file '" IDS_HR1_MESSAGES "'"); + + for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i) + _messages.push_back(readString(f, APPLECHAR('\r')) + APPLECHAR('\r')); + + f.close(); + + if (!f.open(IDS_HR1_EXE_1)) + error("Failed to open file '" IDS_HR1_EXE_1 "'"); + + // Some messages have overrides inside the executable + _messages[IDI_HR1_MSG_CANT_GO_THERE - 1] = readStringAt(f, IDI_HR1_OFS_STR_CANT_GO_THERE); + _messages[IDI_HR1_MSG_DONT_HAVE_IT - 1] = readStringAt(f, IDI_HR1_OFS_STR_DONT_HAVE_IT); + _messages[IDI_HR1_MSG_DONT_UNDERSTAND - 1] = readStringAt(f, IDI_HR1_OFS_STR_DONT_UNDERSTAND); + _messages[IDI_HR1_MSG_GETTING_DARK - 1] = readStringAt(f, IDI_HR1_OFS_STR_GETTING_DARK); + + // Load other strings from executable + _strings.enterCommand = readStringAt(f, IDI_HR1_OFS_STR_ENTER_COMMAND); + _strings.dontHaveIt = readStringAt(f, IDI_HR1_OFS_STR_DONT_HAVE_IT); + _strings.gettingDark = readStringAt(f, IDI_HR1_OFS_STR_GETTING_DARK); + _strings.verbError = readStringAt(f, IDI_HR1_OFS_STR_VERB_ERROR); + _strings.nounError = readStringAt(f, IDI_HR1_OFS_STR_NOUN_ERROR); + _strings.playAgain = readStringAt(f, IDI_HR1_OFS_STR_PLAY_AGAIN); + _gameStrings.pressReturn = readStringAt(f, IDI_HR1_OFS_STR_PRESS_RETURN); + + // Set message IDs + _messageIds.cantGoThere = IDI_HR1_MSG_CANT_GO_THERE; + _messageIds.dontUnderstand = IDI_HR1_MSG_DONT_UNDERSTAND; + _messageIds.itemDoesntMove = IDI_HR1_MSG_ITEM_DOESNT_MOVE; + _messageIds.itemNotHere = IDI_HR1_MSG_ITEM_NOT_HERE; + _messageIds.thanksForPlaying = IDI_HR1_MSG_THANKS_FOR_PLAYING; + + // Load picture data from executable + f.seek(IDI_HR1_OFS_PICS); + for (uint i = 0; i < IDI_HR1_NUM_PICS; ++i) { + struct Picture pic; + pic.block = f.readByte(); + pic.offset = f.readUint16LE(); + _pictures.push_back(pic); + } + + // Load commands from executable + f.seek(IDI_HR1_OFS_CMDS_1); + readCommands(f, _roomCommands); + + f.seek(IDI_HR1_OFS_CMDS_0); + readCommands(f, _globalCommands); + + // Load dropped item offsets + f.seek(IDI_HR1_OFS_ITEM_OFFSETS); + for (uint i = 0; i < IDI_HR1_NUM_ITEM_OFFSETS; ++i) { + Common::Point p; + p.x = f.readByte(); + p.y = f.readByte(); + _itemOffsets.push_back(p); + } + + // Load right-angle line art + f.seek(IDI_HR1_OFS_LINE_ART); + uint16 lineArtTotal = f.readUint16LE(); + for (uint i = 0; i < lineArtTotal; ++i) { + f.seek(IDI_HR1_OFS_LINE_ART + 2 + i * 2); + uint16 offset = f.readUint16LE(); + f.seek(IDI_HR1_OFS_LINE_ART + offset); + + Common::Array<byte> lineArt; + byte b = f.readByte(); + while (b != 0) { + lineArt.push_back(b); + b = f.readByte(); + } + _lineArt.push_back(lineArt); + } + + if (f.eos() || f.err()) + error("Failed to read game data from '" IDS_HR1_EXE_1 "'"); + + f.seek(IDI_HR1_OFS_VERBS); + loadWords(f, _verbs); + + f.seek(IDI_HR1_OFS_NOUNS); + 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 + // is that these strings will scroll past the four-line text window + // before the user gets a chance to read them. + // NOTE: later games seem to wait for a key when the text window + // overflows and don't use delays. It might be better to use + // that system for this game as well. + switch (idx) { + case IDI_HR1_MSG_CANT_GO_THERE: + case IDI_HR1_MSG_DONT_HAVE_IT: + case IDI_HR1_MSG_DONT_UNDERSTAND: + case IDI_HR1_MSG_GETTING_DARK: + wait = false; + } + + AdlEngine::printMessage(idx, wait); +} + +void HiRes1Engine::drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const { + // This draws a four-connected line + + int16 deltaX = p2.x - p1.x; + int8 xStep = 1; + + if (deltaX < 0) { + deltaX = -deltaX; + xStep = -1; + } + + int16 deltaY = p2.y - p1.y; + int8 yStep = -1; + + if (deltaY > 0) { + deltaY = -deltaY; + yStep = 1; + } + + Common::Point p(p1); + int16 steps = deltaX - deltaY + 1; + int16 err = deltaX + deltaY; + + while (1) { + _display->putPixel(p, color); + + if (--steps == 0) + return; + + if (err < 0) { + p.y += yStep; + err += deltaX; + } else { + p.x += xStep; + err += deltaY; + } + } +} + +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); +} + +} // End of namespace Adl diff --git a/engines/adl/hires1.h b/engines/adl/hires1.h new file mode 100644 index 0000000000..25f4744d26 --- /dev/null +++ b/engines/adl/hires1.h @@ -0,0 +1,113 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ADL_HIRES1_H +#define ADL_HIRES1_H + +#include "common/str.h" + +#include "adl/adl.h" + +namespace Common { +class ReadStream; +struct Point; +} + +namespace Adl { + +#define IDS_HR1_EXE_0 "AUTO LOAD OBJ" +#define IDS_HR1_EXE_1 "ADVENTURE" +#define IDS_HR1_LOADER "MYSTERY.HELLO" +#define IDS_HR1_MESSAGES "MESSAGES" + +#define IDI_HR1_NUM_ROOMS 41 +#define IDI_HR1_NUM_PICS 98 +#define IDI_HR1_NUM_VARS 20 +#define IDI_HR1_NUM_ITEM_OFFSETS 21 +#define IDI_HR1_NUM_MESSAGES 167 + +// Messages used outside of scripts +#define IDI_HR1_MSG_CANT_GO_THERE 137 +#define IDI_HR1_MSG_DONT_UNDERSTAND 37 +#define IDI_HR1_MSG_ITEM_DOESNT_MOVE 151 +#define IDI_HR1_MSG_ITEM_NOT_HERE 152 +#define IDI_HR1_MSG_THANKS_FOR_PLAYING 140 +#define IDI_HR1_MSG_DONT_HAVE_IT 127 +#define IDI_HR1_MSG_GETTING_DARK 7 + +#define IDI_HR1_OFS_STR_ENTER_COMMAND 0x5bbc +#define IDI_HR1_OFS_STR_VERB_ERROR 0x5b4f +#define IDI_HR1_OFS_STR_NOUN_ERROR 0x5b8e +#define IDI_HR1_OFS_STR_PLAY_AGAIN 0x5f1e +#define IDI_HR1_OFS_STR_CANT_GO_THERE 0x6c0a +#define IDI_HR1_OFS_STR_DONT_HAVE_IT 0x6c31 +#define IDI_HR1_OFS_STR_DONT_UNDERSTAND 0x6c51 +#define IDI_HR1_OFS_STR_GETTING_DARK 0x6c7c +#define IDI_HR1_OFS_STR_PRESS_RETURN 0x5f68 + +#define IDI_HR1_OFS_PD_TEXT_0 0x005d +#define IDI_HR1_OFS_PD_TEXT_1 0x012b +#define IDI_HR1_OFS_PD_TEXT_2 0x016d +#define IDI_HR1_OFS_PD_TEXT_3 0x0259 + +#define IDI_HR1_OFS_INTRO_TEXT 0x0066 +#define IDI_HR1_OFS_GAME_OR_HELP 0x000f + +#define IDI_HR1_OFS_LOGO_0 0x1003 +#define IDI_HR1_OFS_LOGO_1 0x1800 + +#define IDI_HR1_OFS_ITEMS 0x0100 +#define IDI_HR1_OFS_ROOMS 0x050a +#define IDI_HR1_OFS_PICS 0x4b00 +#define IDI_HR1_OFS_CMDS_0 0x3c00 +#define IDI_HR1_OFS_CMDS_1 0x3d00 + +#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff +#define IDI_HR1_OFS_LINE_ART 0x4f00 + +#define IDI_HR1_OFS_VERBS 0x3800 +#define IDI_HR1_OFS_NOUNS 0x0f00 + +class HiRes1Engine : public AdlEngine { +public: + HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine(syst, gd) { } + +private: + // AdlEngine + void runIntro() const; + void loadData(); + void initState(); + void restartGame(); + void drawPic(byte pic, Common::Point pos) const; + void printMessage(uint idx, bool wait = true) const; + + void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const; + void drawPic(Common::ReadStream &stream, const Common::Point &pos) const; + + struct { + Common::String pressReturn; + } _gameStrings; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/module.mk b/engines/adl/module.mk new file mode 100644 index 0000000000..6acd06f6de --- /dev/null +++ b/engines/adl/module.mk @@ -0,0 +1,18 @@ +MODULE := engines/adl + +MODULE_OBJS := \ + adl.o \ + detection.o \ + display.o \ + hires1.o + +MODULE_DIRS += \ + engines/adl + +# This module can be built as a plugin +ifeq ($(ENABLE_ADL), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp index 0cacce2421..110ba10632 100644 --- a/engines/agi/text.cpp +++ b/engines/agi/text.cpp @@ -885,6 +885,12 @@ void TextMgr::stringEdit(int16 stringMaxLen) { _inputStringRow = _textPos.row; _inputStringColumn = _textPos.column; + if (_inputCursorChar) { + // Cursor character is shown, which means we are one beyond the start of the input + // Adjust the column for predictive input dialog + _inputStringColumn--; + } + // Caller can set the input string _inputStringCursorPos = 0; while (_inputStringCursorPos < inputStringLen) { diff --git a/engines/composer/detection.cpp b/engines/composer/detection.cpp index a3ab18ae54..689a72a743 100644 --- a/engines/composer/detection.cpp +++ b/engines/composer/detection.cpp @@ -253,6 +253,36 @@ static const ComposerGameDescription gameDescriptions[] = { GType_ComposerV2 }, + { // Provided by WindlePoons, "100% Kids Darby & Gregor" Pack. Bugreport #6825 + { + "darby", + 0, + { + {"book.ini", 0, "285308372f7dddff2ca5a25c9192cf5c", 2545}, + {"page99.rsc", 0, "40b4879e9ba6a34d6aa2a9d2e30c5ef7", 1286480}, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_ComposerV2 + }, + + { // Provided by Niv Baehr, Bugreport #6878 + { + "darby", + 0, + AD_ENTRY1("page99.rsc", "183463d18c050563dcdec2d9f9670515"), + Common::HE_ISR, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_ComposerV2 + }, + { { "gregory", @@ -296,6 +326,23 @@ static const ComposerGameDescription gameDescriptions[] = { GType_ComposerV2 }, + { // Provided by WindlePoons, "100% Kids Darby & Gregor" Pack. Bugreport #6825 + { + "gregory", + 0, + { + {"book.ini", 0, "e54fc5c00de5f94e908a969e445af5d0", 2234}, + {"page99.rsc", 0, "1ae6610de621a9901bf87b874fbf331f", 388644}, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_ComposerV2 + }, + { // Provided by sev { "princess", diff --git a/engines/dialogs.cpp b/engines/dialogs.cpp index 8498e50b8d..a21a4a8126 100644 --- a/engines/dialogs.cpp +++ b/engines/dialogs.cpp @@ -43,13 +43,13 @@ #include "engines/engine.h" #include "engines/metaengine.h" -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG #include "gui/KeysDialog.h" #endif class ConfigDialog : public GUI::OptionsDialog { protected: -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG GUI::Dialog *_keysDialog; #endif @@ -307,14 +307,14 @@ ConfigDialog::ConfigDialog(bool subtitleControls) new GUI::ButtonWidget(this, "GlobalConfig.Ok", _("~O~K"), 0, GUI::kOKCmd); new GUI::ButtonWidget(this, "GlobalConfig.Cancel", _("~C~ancel"), 0, GUI::kCloseCmd); -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG new GUI::ButtonWidget(this, "GlobalConfig.Keys", _("~K~eys"), 0, kKeysCmd); _keysDialog = NULL; #endif } ConfigDialog::~ConfigDialog() { -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG delete _keysDialog; #endif } @@ -323,7 +323,7 @@ void ConfigDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 switch (cmd) { case kKeysCmd: -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG // // Create the sub dialog(s) // diff --git a/engines/drascula/actors.cpp b/engines/drascula/actors.cpp index 849e2ccd24..b459c4539b 100644 --- a/engines/drascula/actors.cpp +++ b/engines/drascula/actors.cpp @@ -123,6 +123,8 @@ void DrasculaEngine::startWalking() { walkUp(); else if (roomY > curY + curHeight) walkDown(); + else + characterMoved = 0; } else { if ((roomX < curX + curWidth / 2 ) && (roomY <= (curY + curHeight))) quadrant_1(); @@ -189,7 +191,7 @@ void DrasculaEngine::moveCharacters() { } if (currentChapter != 2 && currentChapter != 3) { - if (hare_se_ve == 0) { + if (characterVisible == 0) { increaseFrameNum(); return; } diff --git a/engines/drascula/animation.cpp b/engines/drascula/animation.cpp index 5009a62e84..439253e5d6 100644 --- a/engines/drascula/animation.cpp +++ b/engines/drascula/animation.cpp @@ -360,7 +360,7 @@ void DrasculaEngine::animation_2_1() { int l; gotoObject(231, 91); - hare_se_ve = 0; + characterVisible = 0; term_int = 0; @@ -433,7 +433,7 @@ void DrasculaEngine::animation_2_1() { curX = 91; curY = 95; trackProtagonist = 1; - hare_se_ve = 1; + characterVisible = 1; loadPic("97g.alg", extraSurface); if (animate("lev.bin", 15)) @@ -1434,7 +1434,7 @@ void DrasculaEngine::animation_12_5() { doBreak = 1; previousMusic = roomMusic; - hare_se_ve = 1; + characterVisible = 1; clearRoom(); trackProtagonist = 1; characterMoved = 0; @@ -1543,7 +1543,7 @@ void DrasculaEngine::animation_1_6() { updateEvents(); clearRoom(); black(); - hare_se_ve = 0; + characterVisible = 0; flags[0] = 0; updateRoom(); updateScreen(); @@ -1618,7 +1618,7 @@ void DrasculaEngine::animation_6_6() { curX = -1; selectVerb(kVerbNone); enterRoom(58); - hare_se_ve = 1; + characterVisible = 1; trackProtagonist = 1; animate("hbp.bin", 14); @@ -2138,7 +2138,7 @@ void DrasculaEngine::animation_5_4(){ loadPic("anh_dr.alg", backSurface); gotoObject(99, 160); gotoObject(38, 177); - hare_se_ve = 0; + characterVisible = 0; updateRoom(); updateScreen(); delay(800); @@ -2156,7 +2156,7 @@ void DrasculaEngine::animation_5_4(){ talk_igor(30, kIgorFront); loadPic(96, frontSurface); loadPic(99, backSurface); - hare_se_ve = 1; + characterVisible = 1; fadeToBlack(0); exitRoom(0); } @@ -2211,7 +2211,7 @@ void DrasculaEngine::activatePendulum() { debug(4, "activatePendulum()"); flags[1] = 2; - hare_se_ve = 0; + characterVisible = 0; _roomNumber = 102; loadPic(102, bgSurface, HALF_PAL); loadPic("an_p1.alg", drawSurface3); diff --git a/engines/drascula/drascula.cpp b/engines/drascula/drascula.cpp index 9ac9031fb7..b821e7dbbe 100644 --- a/engines/drascula/drascula.cpp +++ b/engines/drascula/drascula.cpp @@ -144,7 +144,7 @@ DrasculaEngine::DrasculaEngine(OSystem *syst, const DrasculaGameDescription *gam curDirection = 0; trackProtagonist = 0; _characterFrame = 0; - hare_se_ve = 0; + characterVisible = 0; roomX = 0; roomY = 0; checkFlags = 0; @@ -299,7 +299,7 @@ Common::Error DrasculaEngine::run() { characterMoved = 0; trackProtagonist = 3; _characterFrame = 0; - hare_se_ve = 1; + characterVisible = 1; checkFlags = 1; doBreak = 0; walkToObject = 0; diff --git a/engines/drascula/drascula.h b/engines/drascula/drascula.h index 762add50a5..acca2e5915 100644 --- a/engines/drascula/drascula.h +++ b/engines/drascula/drascula.h @@ -429,7 +429,7 @@ public: int frame_y; int curX, curY, characterMoved, curDirection, trackProtagonist, _characterFrame; - int hare_se_ve; // TODO: what is this for? + int characterVisible; int roomX, roomY, checkFlags; int doBreak; int stepX, stepY; diff --git a/engines/drascula/graphics.cpp b/engines/drascula/graphics.cpp index 077047a6eb..4ed949cc99 100644 --- a/engines/drascula/graphics.cpp +++ b/engines/drascula/graphics.cpp @@ -319,28 +319,42 @@ int DrasculaEngine::print_abc_opc(const char *said, int screenY, int game) { } bool DrasculaEngine::textFitsCentered(char *text, int x) { - int len = strlen(text); - int tmp = CLIP<int>(x - len * CHAR_WIDTH / 2, 60, 255); - return (tmp + len * CHAR_WIDTH) <= 320; + int halfLen = (strlen(text) / 2) * CHAR_WIDTH; + + // See comment in centerText() + if (x > 160) + x = 315 - x; + return (halfLen <= x); } void DrasculaEngine::centerText(const char *message, int textX, int textY) { char msg[200]; - char messageLine[200]; - char tmpMessageLine[200]; - *messageLine = 0; - *tmpMessageLine = 0; - char *curWord; - int curLine = 0; - int x = 0; - // original starts printing 4 lines above textY - int y = CLIP<int>(textY - (4 * CHAR_HEIGHT), 0, 320); - Common::strlcpy(msg, message, 200); + + // We make sure to have a width of at least 120 pixels by clipping the center. + // In theory since the screen width is 320 I would expect something like this: + // x = CLIP<int>(x, 60, 260); + // return (x - halfLen >= 0 && x + halfLen <= 319); + + // The engines does things differently though. It tries to clips text at 315 instead of 319. + // And instead of testing the upper bound if x is greater than 160 it takes the complement to 315 + // and test only the lower bounds. However since 160 is not the middle of 315, we end up having + // text that can go beyond 315 (up to 320) if x is in [159, 160]. + // Also note that if the numbers of characters is odd, there is one more character to the right + // than to the left as it computes the half length with an integer division by two BEFORE multiplying + // by CHAR_WIDTH. Thus in theory we may end up with one character out of the screen! + // Be faithfull to the original and do the same though. + + textX = CLIP<int>(textX, 60, 255); // If the message fits on screen as-is, just print it here if (textFitsCentered(msg, textX)) { - x = CLIP<int>(textX - strlen(msg) * CHAR_WIDTH / 2, 60, 255); + int x = textX - (strlen(msg) / 2) * CHAR_WIDTH - 1; + // The original starts to draw (nbLines + 2) lines above textY, except if there is a single line + // in which case it starts drawing at (nbLines + 3) above textY. + // Also clip to the screen height although the original does not do it. + int y = textY - 4 * CHAR_HEIGHT; + y = CLIP<int>(y, 0, 200 - CHAR_HEIGHT); print_abc(msg, x, y); return; } @@ -351,42 +365,61 @@ void DrasculaEngine::centerText(const char *message, int textX, int textY) { // with the German translation. if (!strchr(msg, ' ')) { int len = strlen(msg); - x = CLIP<int>(textX - len * CHAR_WIDTH / 2, 0, 319 - len * CHAR_WIDTH); + int x = CLIP<int>(textX - (len / 2) * CHAR_WIDTH - 1, 0, 319 - len * CHAR_WIDTH); + int y = textY - 4 * CHAR_HEIGHT; + y = CLIP<int>(y, 0, 200 - CHAR_HEIGHT); print_abc(msg, x, y); return; } // Message doesn't fit on screen, split it - + char messageLines[15][41]; // screenWidth/charWidth = 320/8 = 40. Thus lines can have up to 41 characters with the null terminator (despite the original allocating only 40 characters here). + int curLine = 0; + char messageCurLine[50]; + char tmpMessageCurLine[50]; + *messageCurLine = 0; + *tmpMessageCurLine = 0; // Get a word from the message - curWord = strtok(msg, " "); + char* curWord = strtok(msg, " "); while (curWord != NULL) { // Check if the word and the current line fit on screen - if (tmpMessageLine[0] != '\0') - Common::strlcat(tmpMessageLine, " ", 200); - Common::strlcat(tmpMessageLine, curWord, 200); - if (textFitsCentered(tmpMessageLine, textX)) { + if (tmpMessageCurLine[0] != '\0') + Common::strlcat(tmpMessageCurLine, " ", 50); + Common::strlcat(tmpMessageCurLine, curWord, 50); + if (textFitsCentered(tmpMessageCurLine, textX)) { // Line fits, so add the word to the current message line - strcpy(messageLine, tmpMessageLine); + strcpy(messageCurLine, tmpMessageCurLine); } else { - // Line doesn't fit, so show the current line on screen and - // create a new one - // If it goes off screen, print_abc will adjust it - x = CLIP<int>(textX - strlen(messageLine) * CHAR_WIDTH / 2, 60, 255); - print_abc(messageLine, x, y + curLine * CHAR_HEIGHT); - Common::strlcpy(messageLine, curWord, 200); - Common::strlcpy(tmpMessageLine, curWord, 200); - curLine++; + // Line does't fit. Store the current line and start a new line. + Common::strlcpy(messageLines[curLine++], messageCurLine, 41); + Common::strlcpy(messageCurLine, curWord, 50); + Common::strlcpy(tmpMessageCurLine, curWord, 50); } // Get next word curWord = strtok(NULL, " "); - if (curWord == NULL) { - x = CLIP<int>(textX - strlen(messageLine) * CHAR_WIDTH / 2, 60, 255); - print_abc(messageLine, x, y + curLine * CHAR_HEIGHT); + // The original has an interesting bug that if we split the text on several lines + // a space is added at the end (which impacts the alignment, and may even cause the line + // to become too long). + Common::strlcat(messageCurLine, " ", 50); + if (!textFitsCentered(messageCurLine, textX)) { + messageCurLine[strlen(messageCurLine) - 1] = '\0'; + Common::strlcpy(messageLines[curLine++], messageCurLine, 41); + strcpy(messageLines[curLine++], " "); + } else + Common::strlcpy(messageLines[curLine++], messageCurLine, 41); } } + + // The original starts to draw (nbLines + 2) lines above textY. + // Also clip to the screen height although the original does not do it. + int y = textY - (curLine + 2) * CHAR_HEIGHT; + y = CLIP<int>(y, 0, 200 - curLine * (CHAR_HEIGHT + 2) + 2); + for (int line = 0 ; line < curLine ; ++line, y += CHAR_HEIGHT + 2) { + int textHalfLen = (strlen(messageLines[line]) / 2) * CHAR_WIDTH; + print_abc(messageLines[line], textX - textHalfLen - 1, y); + } } void DrasculaEngine::screenSaver() { diff --git a/engines/drascula/objects.cpp b/engines/drascula/objects.cpp index cd7d502194..823c073d43 100644 --- a/engines/drascula/objects.cpp +++ b/engines/drascula/objects.cpp @@ -62,7 +62,7 @@ void DrasculaEngine::gotoObject(int pointX, int pointY) { hideCursor(); if (currentChapter == 5 || currentChapter == 6) { - if (hare_se_ve == 0) { + if (characterVisible == 0) { curX = roomX; curY = roomY; updateRoom(); diff --git a/engines/drascula/rooms.cpp b/engines/drascula/rooms.cpp index 8691bd2cb4..57d4517295 100644 --- a/engines/drascula/rooms.cpp +++ b/engines/drascula/rooms.cpp @@ -980,12 +980,12 @@ bool DrasculaEngine::room_59(int fl) { playSound(12); pause(19); stopSound(); - hare_se_ve = 0; + characterVisible = 0; updateRoom(); copyRect(101, 34, curX - 4, curY - 1, 37, 70, drawSurface3, screenSurface); copyBackground(0, 0, 0, 0, 320, 200, screenSurface, bgSurface); updateScreen(); - hare_se_ve = 1; + characterVisible = 1; clearRoom(); loadPic("tlef0.alg", bgSurface, COMPLETE_PAL); loadPic("tlef1.alg", drawSurface3); @@ -1399,7 +1399,7 @@ void DrasculaEngine::update_58_pre() { } void DrasculaEngine::update_58() { - if (hare_se_ve == 1) + if (characterVisible == 1) copyRect(67, 139, 140, 147, 12, 16, drawSurface3, screenSurface); } @@ -1845,7 +1845,7 @@ void DrasculaEngine::enterRoom(int roomIndex) { } if (currentChapter == 5) - hare_se_ve = 1; + characterVisible = 1; updateVisible(); @@ -1885,7 +1885,7 @@ void DrasculaEngine::enterRoom(int roomIndex) { if (currentChapter == 5) { if (_roomNumber == 45) - hare_se_ve = 0; + characterVisible = 0; if (_roomNumber == 49 && flags[7] == 0) { playTalkSequence(4); // sequence 4, chapter 5 } @@ -1961,7 +1961,7 @@ bool DrasculaEngine::exitRoom(int doorNumber) { } if (currentChapter == 5) - hare_se_ve = 1; + characterVisible = 1; clearRoom(); if (!sscanf(_targetSurface[doorNumber], "%d", &roomNum)) { diff --git a/engines/drascula/saveload.cpp b/engines/drascula/saveload.cpp index d0f16aa941..eb72a999d4 100644 --- a/engines/drascula/saveload.cpp +++ b/engines/drascula/saveload.cpp @@ -255,6 +255,19 @@ bool DrasculaEngine::loadGame(int slot) { if (!(in = _saveFileMan->openForLoading(saveFileName))) { error("missing savegame file %s", saveFileName.c_str()); } + + // If we currently are in room 102 while being attached below the pendulum + // the character is invisible and some surface are temporarily used for other + // things. Reset those before loading the savegame otherwise we may have some + // issues such as the protagonist being invisible after reloading a savegame. + if (_roomNumber == 102 && flags[1] == 2) { + characterVisible = 1; + loadPic(96, frontSurface); + loadPic(97, frontSurface); + loadPic(97, extraSurface); + loadPic(99, backSurface); + } + loadMetaData(in, slot, true); Graphics::skipThumbnail(*in); @@ -287,8 +300,23 @@ bool DrasculaEngine::loadGame(int slot) { if (!sscanf(currentData, "%d.ald", &roomNum)) { error("Bad save format"); } + + // When loading room 102 while being attached below the pendulum Some variables + // are not correctly set and can cause random crashes when calling enterRoom below. + // The crash occurs in moveCharacters() when accessing factor_red[curY + curHeight]. + if (roomNum == 102 && flags[1] == 2) { + curX = 103; + curY = 108; + curWidth = curHeight = 0; + } + enterRoom(roomNum); selectVerb(kVerbNone); + + // When loading room 102 while being attached below the pendulum we + // need to call activatePendulum() to properly initialized the scene. + if (_roomNumber == 102 && flags[1] == 2) + activatePendulum(); return true; } diff --git a/engines/drascula/talk.cpp b/engines/drascula/talk.cpp index e9fec868f8..cc329b206b 100644 --- a/engines/drascula/talk.cpp +++ b/engines/drascula/talk.cpp @@ -232,7 +232,7 @@ void DrasculaEngine::talk_solo(const char *said, const char *filename) { if (currentChapter == 1) color_abc(color_solo); - else if (currentChapter == 4) + else if (currentChapter == 5) color_abc(kColorRed); talkInit(filename); diff --git a/engines/dreamweb/detection_tables.h b/engines/dreamweb/detection_tables.h index cb9bebb304..0a59543c51 100644 --- a/engines/dreamweb/detection_tables.h +++ b/engines/dreamweb/detection_tables.h @@ -244,6 +244,26 @@ static const DreamWebGameDescription gameDescriptions[] = { }, }, + // Czech fan-made translation + // From bug #7078 + { + { + "dreamweb", + "CD", + { + {"dreamweb.r00", 0, "3b5c87717fc40cc5a5ae19c155662ee3", 152918}, + {"dreamweb.r02", 0, "28458718167a040d7e988cf7d2298eae", 210466}, + {"dreamweb.exe", 0, "40cc15bdc8fa3a785b5fd1ecd6194119", 65440}, + AD_LISTEND + }, + Common::CZ_CZE, + Common::kPlatformDOS, + ADGF_CD, + GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_BRIGHTPALETTE) + }, + }, + + { AD_TABLE_END_MARKER } }; diff --git a/engines/gob/detection/tables_fascin.h b/engines/gob/detection/tables_fascin.h index 7c7c9a7a2f..92272e9852 100644 --- a/engines/gob/detection/tables_fascin.h +++ b/engines/gob/detection/tables_fascin.h @@ -187,6 +187,20 @@ kFeaturesCD, "intro.stk", 0, 0 }, +{ // From bug #7069 + { + "fascination", + "", + AD_ENTRY1s("disk0.stk", "fbf73d7919e1a6752d924eccc14838d7", 190498), + ES_ESP, + kPlatformDOS, + ADGF_NO_FLAGS, + GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) + }, + kGameTypeFascination, + kFeaturesNone, + "disk0.stk", 0, 0 +}, // -- Amiga -- diff --git a/engines/gob/detection/tables_playtoons.h b/engines/gob/detection/tables_playtoons.h index f249e3ffa6..e495db9e25 100644 --- a/engines/gob/detection/tables_playtoons.h +++ b/engines/gob/detection/tables_playtoons.h @@ -222,6 +222,24 @@ kFeatures640x480, "intro2.stk", 0, 0 }, +{ // Version 1.002. Bug #7052 + { + "playtoons2", + "", + { + {"playtoon.stk", 0, "8c98e9a11be9bb203a55e8c6e68e519b", 25574338}, + {"spirou.stk", 0, "91080dc148de1bbd6a97321c1a1facf3", 9817086}, + {0, 0, 0, 0} + }, + FR_FRA, + kPlatformDOS, + ADGF_NO_FLAGS, + GUIO3(GUIO_NOSUBTITLES, GUIO_NOSPEECH, GUIO_NOASPECT) + }, + kGameTypePlaytoons, + kFeatures640x480, + "intro2.stk", 0, 0 +}, { { "playtoons2", diff --git a/engines/kyra/lol.h b/engines/kyra/lol.h index e060b307af..af58397200 100644 --- a/engines/kyra/lol.h +++ b/engines/kyra/lol.h @@ -463,6 +463,7 @@ private: const uint8 *_musicTrackMap; const uint16 *_ingameSoundIndex; + int _ingameSoundIndexSize; const uint8 *_ingameGMSoundIndex; int _ingameGMSoundIndexSize; const uint8 *_ingameMT32SoundIndex; diff --git a/engines/kyra/sound_lol.cpp b/engines/kyra/sound_lol.cpp index 8be0cb6ab9..6e7551ed0e 100644 --- a/engines/kyra/sound_lol.cpp +++ b/engines/kyra/sound_lol.cpp @@ -161,7 +161,7 @@ void LoLEngine::snd_playSoundEffect(int track, int volume) { return; _lastSfxTrack = track; - if (track == -1 || track >= _ingameSoundListSize) + if (track == -1 || track >= _ingameSoundIndexSize) return; volume &= 0xFF; @@ -216,10 +216,10 @@ bool LoLEngine::snd_processEnvironmentalSoundEffect(int soundId, int block) { for (int i = 3; i > 0; i--) { int dir = calcMonsterDirection(cbl & 0x1F, cbl >> 5, block & 0x1F, block >> 5); cbl = (cbl + blockShiftTable[dir]) & 0x3FF; - if (cbl != block) { - if (testWallFlag(cbl, 0, 1)) - _environmentSfxVol >>= 1; - } + if (cbl == block) + break; + if (testWallFlag(cbl, 0, 1)) + _environmentSfxVol >>= 1; } } diff --git a/engines/kyra/staticres_lol.cpp b/engines/kyra/staticres_lol.cpp index 9a4fc281d5..c40b4a0c7d 100644 --- a/engines/kyra/staticres_lol.cpp +++ b/engines/kyra/staticres_lol.cpp @@ -255,7 +255,7 @@ void LoLEngine::initStaticResource() { int tempSize; _pakFileList = _staticres->loadStrings(kLoLIngamePakFiles, _pakFileListSize); _charDefaults = _staticres->loadCharData(kLoLCharacterDefs, _charDefaultsSize); - _ingameSoundIndex = (const uint16 *)_staticres->loadRawData(kLoLIngameSfxIndex, tempSize); + _ingameSoundIndex = (const uint16 *)_staticres->loadRawData(kLoLIngameSfxIndex, _ingameSoundIndexSize); _musicTrackMap = _staticres->loadRawData(kLoLMusicTrackMap, tempSize); _ingameGMSoundIndex = _staticres->loadRawData(kLoLIngameGMSfxIndex, _ingameGMSoundIndexSize); _ingameMT32SoundIndex = _staticres->loadRawData(kLoLIngameMT32SfxIndex, _ingameMT32SoundIndexSize); diff --git a/engines/mohawk/POTFILES b/engines/mohawk/POTFILES index 54d9dcaa3a..036059da6a 100644 --- a/engines/mohawk/POTFILES +++ b/engines/mohawk/POTFILES @@ -1,3 +1,4 @@ +engines/mohawk/detection.cpp engines/mohawk/dialogs.cpp engines/mohawk/myst.cpp engines/mohawk/riven.cpp diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp index a64d7ff7df..7c202998eb 100644 --- a/engines/mohawk/detection.cpp +++ b/engines/mohawk/detection.cpp @@ -221,10 +221,25 @@ SaveStateList MohawkMetaEngine::listSaves(const char *target) const { // Loading games is only supported in Myst/Riven currently. #ifdef ENABLE_MYST if (strstr(target, "myst")) { - filenames = Mohawk::MystGameState::generateSaveGameList(); + filenames = g_system->getSavefileManager()->listSavefiles("myst-###.mys"); + size_t prefixLen = sizeof("myst") - 1; + + for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) { + // Extract the slot number from the filename + char slot[4]; + slot[0] = (*filename)[prefixLen + 1]; + slot[1] = (*filename)[prefixLen + 2]; + slot[2] = (*filename)[prefixLen + 3]; + slot[3] = '\0'; + + int slotNum = atoi(slot); + + // Read the description from the save + Common::String description = Mohawk::MystGameState::querySaveDescription(slotNum); + saveList.push_back(SaveStateDescriptor(slotNum, description)); + } - for (uint32 i = 0; i < filenames.size(); i++) - saveList.push_back(SaveStateDescriptor(i, filenames[i])); + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); } else #endif if (strstr(target, "riven")) { @@ -238,11 +253,11 @@ SaveStateList MohawkMetaEngine::listSaves(const char *target) const { } void MohawkMetaEngine::removeSaveState(const char *target, int slot) const { + // Removing saved games is only supported in Myst/Riven currently. #ifdef ENABLE_MYST if (strstr(target, "myst")) { - Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList(); - Mohawk::MystGameState::deleteSave(filenames[slot]); + Mohawk::MystGameState::deleteSave(slot); } else #endif if (strstr(target, "riven")) { @@ -254,13 +269,7 @@ void MohawkMetaEngine::removeSaveState(const char *target, int slot) const { SaveStateDescriptor MohawkMetaEngine::querySaveMetaInfos(const char *target, int slot) const { #ifdef ENABLE_MYST if (strstr(target, "myst")) { - Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList(); - - if (slot >= (int) filenames.size()) { - return SaveStateDescriptor(); - } - - return Mohawk::MystGameState::querySaveMetaInfos(filenames[slot]); + return Mohawk::MystGameState::querySaveMetaInfos(slot); } else #endif { diff --git a/engines/mohawk/detection_tables.h b/engines/mohawk/detection_tables.h index 7941a0d51a..e3eab89a34 100644 --- a/engines/mohawk/detection_tables.h +++ b/engines/mohawk/detection_tables.h @@ -40,7 +40,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "ae3258c9c90128d274aa6a790b3ad181"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -58,7 +58,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("DEMO.DAT", "c39303dd53fb5c4e7f3c23231c606cd0"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_DEMO | ADGF_UNSTABLE, + ADGF_DEMO | ADGF_TESTING, GUI_OPTIONS_MYST_DEMO }, GType_MYST, @@ -76,7 +76,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "4beb3366ed3f3b9bfb6e81a14a43bdcc"), Common::DE_DEU, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -94,7 +94,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "e0937cca1ab125e48e30dc3cd5046ddf"), Common::DE_DEU, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -112,7 +112,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "f7e7d7ca69934f1351b5acd4fe4d44c2"), Common::ES_ESP, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -130,7 +130,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "a5795ce1751fc42525e4f9a1859181d5"), Common::IT_ITA, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -148,7 +148,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "032c88e3b7e8db4ca475e7b7db9a66bb"), Common::JA_JPN, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -166,7 +166,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "d631d42567a941c67c78f2e491f4ea58"), Common::FR_FRA, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -184,7 +184,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MAKING.DAT", "f6387e8f0f7b8a3e42c95294315d6a0e"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, @@ -202,7 +202,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MAKING.DAT", "03ff62607e64419ab2b6ebf7b7bcdf63"), Common::JA_JPN, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, @@ -220,7 +220,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "c4cae9f143b5947262e6cb2397e1617e"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_ME }, GType_MYST, @@ -238,7 +238,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "f88e0ace66dbca78eebdaaa1d3314ceb"), Common::DE_DEU, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_ME }, GType_MYST, @@ -256,7 +256,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "aea81633b2d2ae498f09072fb87263b6"), Common::FR_FRA, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_ME }, GType_MYST, @@ -274,7 +274,7 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "4a05771b60f4a69869838d01e85c9e80"), Common::PL_POL, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_ME }, GType_MYST, @@ -355,6 +355,23 @@ static const MohawkGameDescription gameDescriptions[] = { }, // Riven: The Sequel to Myst + // Version 1.0.0 (5CD) - Russian, Fargus + { + { + "riven", + "", + AD_ENTRY1s("a_Data.MHK", "2a840ed74fe5dc3a388bced674d379d5", 12024358), + Common::RU_RUS, + Common::kPlatformWindows, + ADGF_UNSTABLE, + GUIO1(GUIO_NOASPECT) + }, + GType_RIVEN, + 0, + 0, + }, + + // Riven: The Sequel to Myst // Version 1.1 (5CD) - Russian, Fargus { { @@ -1878,6 +1895,23 @@ static const MohawkGameDescription gameDescriptions[] = { "Living Books Player" }, + // From Matthew Winder in bug#6557 + // v1.0E, English, Windows + { + { + "arthurbday", + "", + AD_ENTRY1s("AB16B.LB", "c169be346de7b0bbfcd18761fc0a3e49", 3093), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_LIVINGBOOKSV2, + 0, + 0, + }, + // From Torsten in bug#3422652 { { @@ -2100,6 +2134,22 @@ static const MohawkGameDescription gameDescriptions[] = { 0 }, + // From Matthew Winder in bug#6557 + { + { + "lilmonster", + "", + AD_ENTRY1s("lmasf.lb", "fcb665df1713d0411a41515efb20bebc", 4136), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_LIVINGBOOKSV2, + 0, + 0 + }, + // From afholman in bug#3309308 { { @@ -2704,7 +2754,7 @@ static const MohawkGameDescription fallbackDescs[] = { AD_ENTRY1(0, 0), Common::UNK_LANG, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST }, GType_MYST, @@ -2719,7 +2769,7 @@ static const MohawkGameDescription fallbackDescs[] = { AD_ENTRY1(0, 0), Common::UNK_LANG, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, @@ -2734,7 +2784,7 @@ static const MohawkGameDescription fallbackDescs[] = { AD_ENTRY1(0, 0), Common::UNK_LANG, Common::kPlatformWindows, - ADGF_UNSTABLE, + ADGF_TESTING, GUI_OPTIONS_MYST_ME }, GType_MYST, diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp index c16fab9131..e2bc88ebf6 100644 --- a/engines/mohawk/myst.cpp +++ b/engines/mohawk/myst.cpp @@ -231,11 +231,9 @@ Common::Error MohawkEngine_Myst::run() { // Load game from launcher/command line if requested if (ConfMan.hasKey("save_slot") && hasGameSaveSupport()) { - uint32 gameToLoad = ConfMan.getInt("save_slot"); - Common::StringArray savedGamesList = MystGameState::generateSaveGameList(); - if (gameToLoad > savedGamesList.size()) - error ("Could not find saved game"); - _gameState->load(savedGamesList[gameToLoad]); + int saveSlot = ConfMan.getInt("save_slot"); + if (!_gameState->load(saveSlot)) + error("Failed to load save game from slot %i", saveSlot); } else { // Start us on the first stack. if (getGameType() == GType_MAKINGOF) @@ -1083,19 +1081,14 @@ void MohawkEngine_Myst::loadResources() { } Common::Error MohawkEngine_Myst::loadGameState(int slot) { - if (_gameState->load(MystGameState::generateSaveGameList()[slot])) + if (_gameState->load(slot)) return Common::kNoError; return Common::kUnknownError; } Common::Error MohawkEngine_Myst::saveGameState(int slot, const Common::String &desc) { - Common::StringArray saveList = MystGameState::generateSaveGameList(); - - if ((uint)slot < saveList.size()) - MystGameState::deleteSave(saveList[slot]); - - return _gameState->save(desc) ? Common::kNoError : Common::kUnknownError; + return _gameState->save(slot, desc) ? Common::kNoError : Common::kUnknownError; } bool MohawkEngine_Myst::hasGameSaveSupport() const { diff --git a/engines/mohawk/myst_scripts.cpp b/engines/mohawk/myst_scripts.cpp index 04e7c5a9b7..6ad7dd088b 100644 --- a/engines/mohawk/myst_scripts.cpp +++ b/engines/mohawk/myst_scripts.cpp @@ -685,9 +685,14 @@ void MystScriptParser::o_changeBackgroundSound(uint16 op, uint16 var, uint16 arg // Used on Channelwood Card 3225 with argc = 8 i.e. Conditional Sound List debugC(kDebugScript, "Opcode %d: Process Sound Block", op); - Common::MemoryReadStream stream = Common::MemoryReadStream((const byte *) argv, argc * sizeof(uint16)); + Common::MemoryWriteStreamDynamic writeStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES); + for (uint i = 0; i < argc; i++) { + writeStream.writeUint16LE(argv[i]); + } + + Common::MemoryReadStream readStream = Common::MemoryReadStream(writeStream.getData(), writeStream.size()); - MystSoundBlock soundBlock = _vm->readSoundBlock(&stream); + MystSoundBlock soundBlock = _vm->readSoundBlock(&readStream); _vm->applySoundBlock(soundBlock); } diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp index 9d23d2fb10..4dc392a7e9 100644 --- a/engines/mohawk/myst_stacks/myst.cpp +++ b/engines/mohawk/myst_stacks/myst.cpp @@ -3089,6 +3089,8 @@ void Myst::clockReset() { } void Myst::clockResetWeight() { + _vm->_sound->replaceSoundMyst(9113); + _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack)); if (!_clockWeightVideo) error("Failed to open cl1wlfch movie"); diff --git a/engines/mohawk/myst_state.cpp b/engines/mohawk/myst_state.cpp index 06cd69b23c..4324d6bde5 100644 --- a/engines/mohawk/myst_state.cpp +++ b/engines/mohawk/myst_state.cpp @@ -106,16 +106,12 @@ MystGameState::MystGameState(MohawkEngine_Myst *vm, Common::SaveFileManager *sav MystGameState::~MystGameState() { } -Common::StringArray MystGameState::generateSaveGameList() { - return g_system->getSavefileManager()->listSavefiles("*.mys"); -} - -bool MystGameState::load(const Common::String &filename) { - if (!loadState(filename)) { +bool MystGameState::load(int slot) { + if (!loadState(slot)) { return false; } - loadMetadata(filename); + loadMetadata(slot); // Set Channelwood elevator state to down, because we start on the lower level _channelwood.elevatorState = 0; @@ -136,7 +132,8 @@ bool MystGameState::load(const Common::String &filename) { return true; } -bool MystGameState::loadState(const Common::String &filename) { +bool MystGameState::loadState(int slot) { + Common::String filename = buildSaveFilename(slot); Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); if (!loadFile) { return false; @@ -160,9 +157,10 @@ bool MystGameState::loadState(const Common::String &filename) { return true; } -void MystGameState::loadMetadata(const Common::String &filename) { +void MystGameState::loadMetadata(int slot) { // Open the metadata file - Common::InSaveFile *metadataFile = openMetadataFile(filename); + Common::String filename = buildMetadataFilename(slot); + Common::InSaveFile *metadataFile = _vm->getSaveFileManager()->openForLoading(filename); if (!metadataFile) { return; } @@ -179,25 +177,19 @@ void MystGameState::loadMetadata(const Common::String &filename) { delete metadataFile; } -bool MystGameState::save(const Common::String &filename) { - // Make sure the description does not have an extension - Common::String desc = filename; - if (filename.hasSuffix(".mys") || filename.hasSuffix(".MYS")) { - desc = removeExtension(filename); - } - - if (!saveState(desc)) { +bool MystGameState::save(int slot, const Common::String &desc) { + if (!saveState(slot)) { return false; } updateMetadateForSaving(desc); - return saveMetadata(desc); + return saveMetadata(slot); } -bool MystGameState::saveState(const Common::String &desc) { +bool MystGameState::saveState(int slot) { // Make sure we have the right extension - Common::String filename = desc + ".mys"; + Common::String filename = buildSaveFilename(slot); Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(filename); if (!saveFile) { return false; @@ -213,6 +205,14 @@ bool MystGameState::saveState(const Common::String &desc) { return true; } +Common::String MystGameState::buildSaveFilename(int slot) { + return Common::String::format("myst-%03d.mys", slot); +} + +Common::String MystGameState::buildMetadataFilename(int slot) { + return Common::String::format("myst-%03d.mym", slot); +} + void MystGameState::updateMetadateForSaving(const Common::String &desc) { // Update save creation info TimeDate t; @@ -226,10 +226,10 @@ void MystGameState::updateMetadateForSaving(const Common::String &desc) { _metadata.totalPlayTime = _vm->getTotalPlayTime(); } -bool MystGameState::saveMetadata(const Common::String &desc) { +bool MystGameState::saveMetadata(int slot) { // Write the metadata to a separate file so that the save files // are still compatible with the original engine - Common::String metadataFilename = desc + ".mym"; + Common::String metadataFilename = buildMetadataFilename(slot); Common::OutSaveFile *metadataFile = _saveFileMan->openForSaving(metadataFilename); if (!metadataFile) { return false; @@ -248,14 +248,12 @@ bool MystGameState::saveMetadata(const Common::String &desc) { return true; } -SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filename) { - SaveStateDescriptor desc; - desc.setDescription(filename); - +SaveStateDescriptor MystGameState::querySaveMetaInfos(int slot) { // Open the metadata file - Common::InSaveFile *metadataFile = openMetadataFile(filename); + Common::String filename = buildMetadataFilename(slot); + Common::InSaveFile *metadataFile = g_system->getSavefileManager()->openForLoading(filename); if (!metadataFile) { - return desc; + return SaveStateDescriptor(); } Common::Serializer m(metadataFile, nullptr); @@ -264,10 +262,11 @@ SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filen Mohawk::MystSaveMetadata metadata; if (!metadata.sync(m)) { delete metadataFile; - return desc; + return SaveStateDescriptor(); } // Set the save description + SaveStateDescriptor desc; desc.setDescription(metadata.saveDescription); desc.setSaveDate(metadata.saveYear, metadata.saveMonth, metadata.saveDay); desc.setSaveTime(metadata.saveHour, metadata.saveMinute); @@ -279,20 +278,26 @@ SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filen return desc; } -Common::InSaveFile *MystGameState::openMetadataFile(const Common::String &filename) { - // Remove the extension - Common::String baseName = removeExtension(filename); - +Common::String MystGameState::querySaveDescription(int slot) { // Open the metadata file - return g_system->getSavefileManager()->openForLoading(baseName + ".mym"); -} + Common::String filename = buildMetadataFilename(slot); + Common::InSaveFile *metadataFile = g_system->getSavefileManager()->openForLoading(filename); + if (!metadataFile) { + return ""; + } + + Common::Serializer m(metadataFile, nullptr); -Common::String MystGameState::removeExtension(const Common::String &filename) { - Common::String baseName = filename; - for (uint i = 0; i < 4; i++) { - baseName.deleteLastChar(); + // Read the metadata file + Mohawk::MystSaveMetadata metadata; + if (!metadata.sync(m)) { + delete metadataFile; + return ""; } - return baseName; + + delete metadataFile; + + return metadata.saveDescription; } void MystGameState::syncGameState(Common::Serializer &s, bool isME) { @@ -471,12 +476,14 @@ void MystGameState::syncGameState(Common::Serializer &s, bool isME) { warning("Unexpected File Position 0x%03X At End of Save/Load", s.bytesSynced()); } -void MystGameState::deleteSave(const Common::String &saveName) { - debugC(kDebugSaveLoad, "Deleting save file \'%s\'", saveName.c_str()); - Common::String basename = removeExtension(saveName); +void MystGameState::deleteSave(int slot) { + Common::String filename = buildSaveFilename(slot); + Common::String metadataFilename = buildMetadataFilename(slot); + + debugC(kDebugSaveLoad, "Deleting save file \'%s\'", filename.c_str()); - g_system->getSavefileManager()->removeSavefile(saveName); - g_system->getSavefileManager()->removeSavefile(basename + ".mym"); + g_system->getSavefileManager()->removeSavefile(filename); + g_system->getSavefileManager()->removeSavefile(metadataFilename); } void MystGameState::addZipDest(uint16 stack, uint16 view) { diff --git a/engines/mohawk/myst_state.h b/engines/mohawk/myst_state.h index 50359a5b52..7d5f3f7102 100644 --- a/engines/mohawk/myst_state.h +++ b/engines/mohawk/myst_state.h @@ -58,12 +58,12 @@ public: MystGameState(MohawkEngine_Myst*, Common::SaveFileManager*); ~MystGameState(); - static Common::StringArray generateSaveGameList(); - static SaveStateDescriptor querySaveMetaInfos(const Common::String filename); + static SaveStateDescriptor querySaveMetaInfos(int slot); + static Common::String querySaveDescription(int slot); - bool load(const Common::String &filename); - bool save(const Common::String &filename); - static void deleteSave(const Common::String &saveName); + bool load(int slot); + bool save(int slot, const Common::String &desc); + static void deleteSave(int slot); void addZipDest(uint16 stack, uint16 view); bool isReachableZipDest(uint16 stack, uint16 view); @@ -292,13 +292,13 @@ public: private: void syncGameState(Common::Serializer &s, bool isME); - static Common::InSaveFile *openMetadataFile(const Common::String &filename); - bool loadState(const Common::String &filename); - void loadMetadata(const Common::String &filename); - bool saveState(const Common::String &desc); + static Common::String buildSaveFilename(int slot); + static Common::String buildMetadataFilename(int slot); + bool loadState(int slot); + void loadMetadata(int slot); + bool saveState(int slot); void updateMetadateForSaving(const Common::String &desc); - bool saveMetadata(const Common::String &desc); - static Common::String removeExtension(const Common::String &filename); + bool saveMetadata(int slot); // The values in these regions are lists of VIEW resources // which correspond to visited zip destinations diff --git a/engines/queen/detection.cpp b/engines/queen/detection.cpp index 81e0767836..aed8b7dcb1 100644 --- a/engines/queen/detection.cpp +++ b/engines/queen/detection.cpp @@ -105,6 +105,19 @@ static const QueenGameDescription gameDescriptions[] = { }, }, + // DOS Demo - English (from Bugreport #6946) + { + { + "queen", + "Demo Alt", + AD_ENTRY1s("queen.1", "2871fc6f8090f37fa1a0c556a1c97460", 3735447), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_DEMO, + GUIO1(GUIO_NOSPEECH) + }, + }, + // DOS Interview Demo - English { { @@ -131,20 +144,18 @@ static const QueenGameDescription gameDescriptions[] = { }, }, -#if 0 // Amiga Floppy - English { { "queen", "Floppy", - AD_ENTRY1s("queen.1", NULL, 351775), // TODO: Fill in correct MD5 + AD_ENTRY1s("queen.1", "9c209c2cbc1730e3138663c4fd29c2e8", 351775), Common::EN_ANY, Common::kPlatformAmiga, ADGF_NO_FLAGS, GUIO1(GUIO_NOSPEECH) }, }, -#endif // DOS Floppy - English { @@ -198,6 +209,19 @@ static const QueenGameDescription gameDescriptions[] = { }, }, + // DOS Floppy - Russian (From Bugreport #6946) + { + { + "queen", + "Floppy", + AD_ENTRY1s("queen.1", "f5e827645d3c887be3bdf4729d847756", 22677657), + Common::RU_RUS, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOSPEECH) + }, + }, + // DOS CD - French { { @@ -211,35 +235,31 @@ static const QueenGameDescription gameDescriptions[] = { }, }, -#if 0 // DOS Floppy - German { { "queen", "Floppy", - AD_ENTRY1s("queen.1", NULL, 22240013), // TODO: Fill in correct MD5 + AD_ENTRY1s("queen.1", "f5e827645d3c887be3bdf4729d847756", 22240013), Common::DE_DEU, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GUIO_NOSPEECH) }, }, -#endif -#if 0 // DOS CD - German { { "queen", "Talkie", - AD_ENTRY1s("queen.1", NULL, 217648975), // TODO: Fill in correct MD5 + AD_ENTRY1s("queen.1", "551d595be8af890fc4cb8533c9c5f5f1", 217648975), Common::DE_DEU, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_ALT_INTRO) }, }, -#endif #if 0 // DOS CD - Hebrew @@ -256,20 +276,18 @@ static const QueenGameDescription gameDescriptions[] = { }, #endif -#if 0 // DOS Floppy - Italian { { "queen", "Floppy", - AD_ENTRY1s("queen.1", NULL, 22461366), // TODO: Fill in correct MD5 + AD_ENTRY1s("queen.1", "f5e827645d3c887be3bdf4729d847756", 22461366), Common::IT_ITA, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GUIO_NOSPEECH) }, }, -#endif // DOS CD - Italian { @@ -284,20 +302,18 @@ static const QueenGameDescription gameDescriptions[] = { }, }, -#if 0 // DOS CD - Spanish { { "queen", "Talkie", - AD_ENTRY1s("queen.1", NULL, 190730602), // TODO: Fill in correct MD5 + AD_ENTRY1s("queen.1", "b6302bccf70463de3d5faf0f0628f742", 190730602), Common::ES_ESP, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_ALT_INTRO) }, }, -#endif // DOS CD - English (Compressed Freeware Release v1.0) { @@ -377,6 +393,19 @@ static const QueenGameDescription gameDescriptions[] = { }, }, + // DOS CD - Hungarian (Compressed Freeware Release v1.02) + { + { + "queen", + "Talkie", + AD_ENTRY1s("queen.1c", "21fd690b372f8a6289f6f33bc986276c", 51329031), + Common::HU_HUN, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GAMEOPTION_ALT_INTRO) + }, + }, + // TODO: Freeware Release for Spanish DOS CD is missing. #if 0 // DOS CD - Spanish (Compressed Freeware Release v1.0) diff --git a/engines/saga/detection_tables.h b/engines/saga/detection_tables.h index 8b3a0e5207..71225ceb2f 100644 --- a/engines/saga/detection_tables.h +++ b/engines/saga/detection_tables.h @@ -733,6 +733,36 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO1(GUIO_NOASPECT) }, GID_IHNM, + GF_IHNM_COLOR_FIX, + IHNM_DEFAULT_SCENE, + &IHNM_Resources, + ARRAYSIZE(IHNMCD_GameFonts), + IHNMCD_GameFonts, + NULL, + }, + + // I Have No Mouth And I Must Scream - German fan CD translation + // English CD version with German text patch (with Nimdok) + // (English speech - German text) + { + { + "ihnm", + "fan-made", + { + {"musicfm.res", GAME_MUSICFILE_FM, "0439083e3dfdc51b486071d45872ae52", 302676}, + {"musicgm.res", GAME_MUSICFILE_GM, "80f875a1fb384160d1f4b27166eef583", 314020}, + {"scream.res", GAME_RESOURCEFILE, "46bbdc65d164ba7e89836a0935eec8e6", 79219797}, + {"scripts.res", GAME_SCRIPTFILE, "be38bbc5a26be809dbf39f13befebd01", 523800}, + {"patch.re_", GAME_PATCHFILE | GAME_RESOURCEFILE, "58b79e61594779513c7f2d35509fa89e", 5038599}, + {"sfx.res", GAME_SOUNDFILE, "1c610d543f32ec8b525e3f652536f269", 22561056}, + { NULL, 0, NULL, 0} + }, + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GID_IHNM, 0, IHNM_DEFAULT_SCENE, &IHNM_Resources, @@ -761,7 +791,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO1(GUIO_NOASPECT) }, GID_IHNM, - 0, + GF_IHNM_COLOR_FIX, IHNM_DEFAULT_SCENE, &IHNM_Resources, ARRAYSIZE(IHNMCD_GameFonts), @@ -790,7 +820,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO1(GUIO_NOASPECT) }, GID_IHNM, - 0, + GF_IHNM_COLOR_FIX, IHNM_DEFAULT_SCENE, &IHNM_Resources, ARRAYSIZE(IHNMCD_GameFonts), diff --git a/engines/saga/displayinfo.h b/engines/saga/displayinfo.h index a0cdf5733b..67448936ce 100644 --- a/engines/saga/displayinfo.h +++ b/engines/saga/displayinfo.h @@ -177,11 +177,11 @@ static PanelButton ITE_OptionPanelButtons[] = { {kPanelButtonOption, 241,98, 57,17, kTextSave,'s',0, 0,0,0}, //save {kPanelButtonOptionSaveFiles, 166,20, 112,74, 0,'-',0, 0,0,0}, //savefiles - {kPanelButtonOptionText,106,4, 0,0, kTextGameOptions,'-',0, 0,0,0}, // text: game options - {kPanelButtonOptionText,11,22, 0,0, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed - {kPanelButtonOptionText,28,22, 0,0, kTextShowDialog,'-',0, 0,0,0}, // text: read speed - {kPanelButtonOptionText,73,41, 0,0, kTextMusic,'-',0, 0,0,0}, // text: music - {kPanelButtonOptionText,69,60, 0,0, kTextSound,'-',0, 0,0,0}, // text: noise + {kPanelButtonOptionText,-1,4, 0,0, kTextGameOptions,'-',0, 0,0,0}, // text: game options + {kPanelButtonOptionText,5,18, 109,17, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed + {kPanelButtonOptionText,5,18, 109,17, kTextShowDialog,'-',0, 0,0,0}, // text: read speed + {kPanelButtonOptionText,5,37, 109,17, kTextMusic,'-',0, 0,0,0}, // text: music + {kPanelButtonOptionText,5,56, 109,17, kTextSound,'-',0, 0,0,0}, // text: noise }; static PanelButton ITE_QuitPanelButtons[] = { @@ -326,10 +326,10 @@ static PanelButton IHNM_ConversePanelButtons[] = { static PanelButton IHNM_OptionPanelButtons[] = { {kPanelButtonOptionSlider, 421,16, 16,138, 0,'-',0, 0,0,0}, //slider-scroller - {kPanelButtonOptionText,28,36, 0,0, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed - {kPanelButtonOptionText,60,61, 0,0, kTextMusic,'-',0, 0,0,0}, // text: music - {kPanelButtonOptionText,60,86, 0,0, kTextSound,'-',0, 0,0,0}, // text: noise - {kPanelButtonOptionText,56,111, 0,0, kTextVoices,'-',0, 0,0,0}, // text: voices + {kPanelButtonOptionText,11,30, 139,21, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed + {kPanelButtonOptionText,11,55, 139,21, kTextMusic,'-',0, 0,0,0}, // text: music + {kPanelButtonOptionText,11,80, 139,21, kTextSound,'-',0, 0,0,0}, // text: noise + {kPanelButtonOptionText,11,105, 139,21, kTextVoices,'-',0, 0,0,0}, // text: voices {kPanelButtonOption, 154,30, 79,23, kTextReadingSpeed,'r',0, 0,0,0}, //read speed {kPanelButtonOption, 154,55, 79,23, kTextMusic,'m',0, 0,0,0}, //music {kPanelButtonOption, 154,80, 79,23, kTextSound,'n',0, 0,0,0}, //sound-noise diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp index ad940aaf8b..cb09d53762 100644 --- a/engines/saga/interface.cpp +++ b/engines/saga/interface.cpp @@ -867,7 +867,7 @@ void Interface::calcOptionSaveSlider() { void Interface::drawPanelText(InterfacePanel *panel, PanelButton *panelButton) { const char *text; - int textWidth; + int textWidth, textHeight; Rect rect; Point textPoint; KnownColor textShadowKnownColor = kKnownColorVerbTextShadow; @@ -900,12 +900,26 @@ void Interface::drawPanelText(InterfacePanel *panel, PanelButton *panelButton) { } panel->calcPanelButtonRect(panelButton, rect); + if (_vm->getGameId() == GID_ITE) { + textWidth = _vm->_font->getStringWidth(kKnownFontMedium, text, 0, kFontNormal); + textHeight = _vm->_font->getHeight(kKnownFontMedium); + } else { + textWidth = _vm->_font->getStringWidth(kKnownFontVerb, text, 0, kFontNormal); + textHeight = _vm->_font->getHeight(kKnownFontVerb); + } if (panelButton->xOffset < 0) { - if (_vm->getGameId() == GID_ITE) - textWidth = _vm->_font->getStringWidth(kKnownFontMedium, text, 0, kFontNormal); - else - textWidth = _vm->_font->getStringWidth(kKnownFontVerb, text, 0, kFontNormal); + // Special case: Centered to dialog. This is used for things like the + // title of a dialog. rect.left += 2 + (panel->imageWidth - 1 - textWidth) / 2; + } else { + // The standard case is used for the things that look a bit like buttons + // but are not clickable, e.g. texts like "Music", "Sound", etc. + if (_vm->getGameId() == GID_ITE) { + rect.left = rect.right - textWidth - 3; + } else { + rect.left = (rect.right + rect.left - textWidth) / 2; + } + rect.top = (rect.top + rect.bottom - textHeight) / 2; } textPoint.x = rect.left; @@ -1865,7 +1879,7 @@ void Interface::drawStatusBar() { // Fixes bug #1848016 - "IHNM: Wrong Subtitles Color (Spanish)". This // also applies to the German and French versions (bug #7064 - "IHNM: // text mistake in german version"). - int offset = (_vm->getLanguage() == Common::ES_ESP || _vm->getLanguage() == Common::DE_DEU || _vm->getLanguage() == Common::FR_FRA) ? 1 : 0; + int offset = (_vm->getFeatures() & GF_IHNM_COLOR_FIX) ? 1 : 0; // Disable the status text in IHNM when the chapter is 8 if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 8) diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index 77a21e7f93..649888e7ea 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -582,7 +582,7 @@ ColorId SagaEngine::KnownColor2ColorId(KnownColor knownColor) { // Fixes bug #1848016 - "IHNM: Wrong Subtitles Color (Spanish)". This // also applies to the German and French versions (bug #7064 - "IHNM: // text mistake in german version"). - int offset = (getLanguage() == Common::ES_ESP || getLanguage() == Common::DE_DEU || getLanguage() == Common::FR_FRA) ? 1 : 0; + int offset = (getFeatures() & GF_IHNM_COLOR_FIX) ? 1 : 0; switch (knownColor) { case(kKnownColorTransparent): diff --git a/engines/saga/saga.h b/engines/saga/saga.h index 9c7b2f5295..06cb411e5a 100644 --- a/engines/saga/saga.h +++ b/engines/saga/saga.h @@ -139,7 +139,8 @@ enum GameFeatures { GF_ITE_FLOPPY = 1 << 0, GF_ITE_DOS_DEMO = 1 << 1, GF_EXTRA_ITE_CREDITS = 1 << 2, - GF_8BIT_UNSIGNED_PCM = 1 << 3 + GF_8BIT_UNSIGNED_PCM = 1 << 3, + GF_IHNM_COLOR_FIX = 1 << 4 }; enum VerbTypeIds { diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index a092e0676d..1661f92cfe 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -1861,7 +1861,7 @@ bool Console::cmdSavedBits(int argc, const char **argv) { for (uint i = 0; i < entries.size(); ++i) { uint16 offset = entries[i].getOffset(); - const Hunk& h = hunks->_table[offset]; + const Hunk& h = hunks->at(offset); if (strcmp(h.type, "SaveBits()") == 0) { byte* memoryPtr = (byte *)h.mem; @@ -1928,7 +1928,7 @@ bool Console::cmdShowSavedBits(int argc, const char **argv) { return true; } - const Hunk& h = hunks->_table[memoryHandle.getOffset()]; + const Hunk& h = hunks->at(memoryHandle.getOffset()); if (strcmp(h.type, "SaveBits()") != 0) { debugPrintf("Invalid address.\n"); @@ -2144,32 +2144,32 @@ bool Console::segmentInfo(int nr) { break; case SEG_TYPE_CLONES: { - CloneTable *ct = (CloneTable *)mobj; + CloneTable &ct = *(CloneTable *)mobj; debugPrintf("clones\n"); - for (uint i = 0; i < ct->_table.size(); i++) - if (ct->isValidEntry(i)) { + for (uint i = 0; i < ct.size(); i++) + if (ct.isValidEntry(i)) { reg_t objpos = make_reg(nr, i); debugPrintf(" [%04x] %s; copy of ", i, _engine->_gamestate->_segMan->getObjectName(objpos)); // Object header - const Object *obj = _engine->_gamestate->_segMan->getObject(ct->_table[i].getPos()); + const Object *obj = _engine->_gamestate->_segMan->getObject(ct[i].getPos()); if (obj) - debugPrintf("[%04x:%04x] %s : %3d vars, %3d methods\n", PRINT_REG(ct->_table[i].getPos()), - _engine->_gamestate->_segMan->getObjectName(ct->_table[i].getPos()), + debugPrintf("[%04x:%04x] %s : %3d vars, %3d methods\n", PRINT_REG(ct[i].getPos()), + _engine->_gamestate->_segMan->getObjectName(ct[i].getPos()), obj->getVarCount(), obj->getMethodCount()); } } break; case SEG_TYPE_LISTS: { - ListTable *lt = (ListTable *)mobj; + ListTable < = *(ListTable *)mobj; debugPrintf("lists\n"); - for (uint i = 0; i < lt->_table.size(); i++) - if (lt->isValidEntry(i)) { + for (uint i = 0; i < lt.size(); i++) + if (lt.isValidEntry(i)) { debugPrintf(" [%04x]: ", i); - printList(&(lt->_table[i])); + printList(<[i]); } } break; @@ -2180,13 +2180,13 @@ bool Console::segmentInfo(int nr) { } case SEG_TYPE_HUNK: { - HunkTable *ht = (HunkTable *)mobj; + HunkTable &ht = *(HunkTable *)mobj; - debugPrintf("hunk (total %d)\n", ht->entries_used); - for (uint i = 0; i < ht->_table.size(); i++) - if (ht->isValidEntry(i)) { + debugPrintf("hunk (total %d)\n", ht.entries_used); + for (uint i = 0; i < ht.size(); i++) + if (ht.isValidEntry(i)) { debugPrintf(" [%04x] %d bytes at %p, type=%s\n", - i, ht->_table[i].size, ht->_table[i].mem, ht->_table[i].type); + i, ht[i].size, ht[i].mem, ht[i].type); } } break; @@ -4362,7 +4362,7 @@ void Console::printList(List *list) { return; } - node = &(nt->_table[pos.getOffset()]); + node = &nt->at(pos.getOffset()); debugPrintf("\t%04x:%04x : %04x:%04x -> %04x:%04x\n", PRINT_REG(pos), PRINT_REG(node->key), PRINT_REG(node->value)); @@ -4392,7 +4392,7 @@ int Console::printNode(reg_t addr) { return 1; } - list = &(lt->_table[addr.getOffset()]); + list = <->at(addr.getOffset()); debugPrintf("%04x:%04x : first x last = (%04x:%04x, %04x:%04x)\n", PRINT_REG(addr), PRINT_REG(list->first), PRINT_REG(list->last)); } else { @@ -4411,7 +4411,7 @@ int Console::printNode(reg_t addr) { debugPrintf("Address does not contain a node\n"); return 1; } - node = &(nt->_table[addr.getOffset()]); + node = &nt->at(addr.getOffset()); debugPrintf("%04x:%04x : prev x next = (%04x:%04x, %04x:%04x); maps %04x:%04x -> %04x:%04x\n", PRINT_REG(addr), PRINT_REG(node->pred), PRINT_REG(node->succ), PRINT_REG(node->key), PRINT_REG(node->value)); diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index fcb65157d8..0cc1e752e1 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -60,24 +60,125 @@ namespace Sci { #pragma mark - -// Experimental hack: Use syncWithSerializer to sync. By default, this assume -// the object to be synced is a subclass of Serializable and thus tries to invoke -// the saveLoadWithSerializer() method. But it is possible to specialize this -// template function to handle stuff that is not implementing that interface. -template<typename T> -void syncWithSerializer(Common::Serializer &s, T &obj) { +// These are serialization functions for various objects. + +void syncWithSerializer(Common::Serializer &s, Common::Serializable &obj) { + obj.saveLoadWithSerializer(s); +} + +// FIXME: Object could implement Serializable to make use of the function +// above. +void syncWithSerializer(Common::Serializer &s, Object &obj) { obj.saveLoadWithSerializer(s); } +void syncWithSerializer(Common::Serializer &s, reg_t &obj) { + // Segment and offset are accessed directly here + s.syncAsUint16LE(obj._segment); + s.syncAsUint16LE(obj._offset); +} + +void syncWithSerializer(Common::Serializer &s, synonym_t &obj) { + s.syncAsUint16LE(obj.replaceant); + s.syncAsUint16LE(obj.replacement); +} + +void syncWithSerializer(Common::Serializer &s, Class &obj) { + s.syncAsSint32LE(obj.script); + syncWithSerializer(s, obj.reg); +} + +void syncWithSerializer(Common::Serializer &s, List &obj) { + syncWithSerializer(s, obj.first); + syncWithSerializer(s, obj.last); +} + +void syncWithSerializer(Common::Serializer &s, Node &obj) { + syncWithSerializer(s, obj.pred); + syncWithSerializer(s, obj.succ); + syncWithSerializer(s, obj.key); + syncWithSerializer(s, obj.value); +} + +#ifdef ENABLE_SCI32 +void syncWithSerializer(Common::Serializer &s, SciArray<reg_t> &obj) { + byte type = 0; + uint32 size = 0; + + if (s.isSaving()) { + type = (byte)obj.getType(); + size = obj.getSize(); + } + s.syncAsByte(type); + s.syncAsUint32LE(size); + if (s.isLoading()) { + obj.setType((int8)type); + + // HACK: Skip arrays that have a negative type + if ((int8)type < 0) + return; + + obj.setSize(size); + } + + for (uint32 i = 0; i < size; i++) { + reg_t value; + + if (s.isSaving()) + value = obj.getValue(i); + + syncWithSerializer(s, value); + + if (s.isLoading()) + obj.setValue(i, value); + } +} + +void syncWithSerializer(Common::Serializer &s, SciString &obj) { + uint32 size = 0; + + if (s.isSaving()) { + size = obj.getSize(); + s.syncAsUint32LE(size); + } else { + s.syncAsUint32LE(size); + obj.setSize(size); + } + + for (uint32 i = 0; i < size; i++) { + char value = 0; + + if (s.isSaving()) + value = obj.getValue(i); + + s.syncAsByte(value); + + if (s.isLoading()) + obj.setValue(i, value); + } +} +#endif + +#pragma mark - + // By default, sync using syncWithSerializer, which in turn can easily be overloaded. template<typename T> struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> { void operator()(Common::Serializer &s, T &obj) const { - //obj.saveLoadWithSerializer(s); syncWithSerializer(s, obj); } }; +// Syncer for entries in a segment obj table +template<typename T> +struct SegmentObjTableEntrySyncer : Common::BinaryFunction<Common::Serializer, typename T::Entry &, void> { + void operator()(Common::Serializer &s, typename T::Entry &entry) const { + s.syncAsSint32LE(entry.next_free); + + syncWithSerializer(s, entry.data); + } +}; + /** * Sync a Common::Array using a Common::Serializer. * When saving, this writes the length of the array, then syncs (writes) all entries. @@ -116,18 +217,10 @@ void syncArray(Common::Serializer &s, Common::Array<T> &arr) { sync(s, arr); } - -template<> -void syncWithSerializer(Common::Serializer &s, reg_t &obj) { - // Segment and offset are accessed directly here - s.syncAsUint16LE(obj._segment); - s.syncAsUint16LE(obj._offset); -} - -template<> -void syncWithSerializer(Common::Serializer &s, synonym_t &obj) { - s.syncAsUint16LE(obj.replaceant); - s.syncAsUint16LE(obj.replacement); +template<typename T, class Syncer> +void syncArray(Common::Serializer &s, Common::Array<T> &arr) { + ArraySyncer<T, Syncer> sync; + sync(s, arr); } void SegManager::saveLoadWithSerializer(Common::Serializer &s) { @@ -247,12 +340,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { } -template<> -void syncWithSerializer(Common::Serializer &s, Class &obj) { - s.syncAsSint32LE(obj.script); - syncWithSerializer(s, obj.reg); -} - static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) { s.syncString(obj.name); s.syncVersion(CURRENT_SAVEGAME_VERSION); @@ -331,102 +418,13 @@ void Object::saveLoadWithSerializer(Common::Serializer &s) { syncArray<reg_t>(s, _variables); } -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Clone>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer<Object>(s, obj); -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<List>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer(s, obj.first); - syncWithSerializer(s, obj.last); -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Node>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer(s, obj.pred); - syncWithSerializer(s, obj.succ); - syncWithSerializer(s, obj.key); - syncWithSerializer(s, obj.value); -} - -#ifdef ENABLE_SCI32 -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciArray<reg_t> >::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - byte type = 0; - uint32 size = 0; - - if (s.isSaving()) { - type = (byte)obj.getType(); - size = obj.getSize(); - } - s.syncAsByte(type); - s.syncAsUint32LE(size); - if (s.isLoading()) { - obj.setType((int8)type); - - // HACK: Skip arrays that have a negative type - if ((int8)type < 0) - return; - - obj.setSize(size); - } - - for (uint32 i = 0; i < size; i++) { - reg_t value; - - if (s.isSaving()) - value = obj.getValue(i); - - syncWithSerializer(s, value); - - if (s.isLoading()) - obj.setValue(i, value); - } -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciString>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - uint32 size = 0; - - if (s.isSaving()) { - size = obj.getSize(); - s.syncAsUint32LE(size); - } else { - s.syncAsUint32LE(size); - obj.setSize(size); - } - - for (uint32 i = 0; i < size; i++) { - char value = 0; - - if (s.isSaving()) - value = obj.getValue(i); - - s.syncAsByte(value); - - if (s.isLoading()) - obj.setValue(i, value); - } -} -#endif template<typename T> void sync_Table(Common::Serializer &s, T &obj) { s.syncAsSint32LE(obj.first_free); s.syncAsSint32LE(obj.entries_used); - syncArray<typename T::Entry>(s, obj._table); + syncArray<typename T::Entry, SegmentObjTableEntrySyncer<T> >(s, obj._table); } void CloneTable::saveLoadWithSerializer(Common::Serializer &s) { @@ -903,7 +901,7 @@ void SegManager::reconstructClones() { if (!isUsed) continue; - CloneTable::Entry &seeker = ct->_table[j]; + CloneTable::value_type &seeker = ct->at(j); const Object *baseObj = getObject(seeker.getSpeciesSelector()); seeker.cloneFromObject(baseObj); if (!baseObj) { diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index 7d70f30d55..b017e62df7 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -741,13 +741,13 @@ void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *ke switch (mobj->getType()) { case SEG_TYPE_HUNK: { - HunkTable *ht = (HunkTable *)mobj; + HunkTable &ht = *(HunkTable *)mobj; int index = argv[parmNr].getOffset(); - if (ht->isValidEntry(index)) { + if (ht.isValidEntry(index)) { // NOTE: This ", deleted" isn't as useful as it could // be because it prints the status _after_ the kernel // call. - debugN(" ('%s' hunk%s)", ht->_table[index].type, ht->_table[index].mem ? "" : ", deleted"); + debugN(" ('%s' hunk%s)", ht[index].type, ht[index].mem ? "" : ", deleted"); } else debugN(" (INVALID hunk ref)"); break; diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index 8090b1861d..95e3cd15f9 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -247,9 +247,9 @@ Object *SegManager::getObject(reg_t pos) const { if (mobj != NULL) { if (mobj->getType() == SEG_TYPE_CLONES) { - CloneTable *ct = (CloneTable *)mobj; - if (ct->isValidEntry(pos.getOffset())) - obj = &(ct->_table[pos.getOffset()]); + CloneTable &ct = *(CloneTable *)mobj; + if (ct.isValidEntry(pos.getOffset())) + obj = &(ct[pos.getOffset()]); else warning("getObject(): Trying to get an invalid object"); } else if (mobj->getType() == SEG_TYPE_SCRIPT) { @@ -313,7 +313,7 @@ reg_t SegManager::findObjectByName(const Common::String &name, int index) { } else if (mobj->getType() == SEG_TYPE_CLONES) { // It's clone table, scan all objects in it const CloneTable *ct = (const CloneTable *)mobj; - for (uint idx = 0; idx < ct->_table.size(); ++idx) { + for (uint idx = 0; idx < ct->size(); ++idx) { if (!ct->isValidEntry(idx)) continue; @@ -404,7 +404,7 @@ reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) { offset = table->allocEntry(); reg_t addr = make_reg(_hunksSegId, offset); - Hunk *h = &(table->_table[offset]); + Hunk *h = &table->at(offset); if (!h) return NULL_REG; @@ -424,7 +424,7 @@ byte *SegManager::getHunkPointer(reg_t addr) { return NULL; } - return (byte *)ht->_table[addr.getOffset()].mem; + return (byte *)ht->at(addr.getOffset()).mem; } Clone *SegManager::allocateClone(reg_t *addr) { @@ -439,7 +439,7 @@ Clone *SegManager::allocateClone(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_clonesSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } List *SegManager::allocateList(reg_t *addr) { @@ -453,7 +453,7 @@ List *SegManager::allocateList(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_listsSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } Node *SegManager::allocateNode(reg_t *addr) { @@ -467,7 +467,7 @@ Node *SegManager::allocateNode(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_nodesSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } reg_t SegManager::newNode(reg_t value, reg_t key) { @@ -486,14 +486,14 @@ List *SegManager::lookupList(reg_t addr) { return NULL; } - ListTable *lt = (ListTable *)_heap[addr.getSegment()]; + ListTable < = *(ListTable *)_heap[addr.getSegment()]; - if (!lt->isValidEntry(addr.getOffset())) { + if (!lt.isValidEntry(addr.getOffset())) { error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } - return &(lt->_table[addr.getOffset()]); + return &(lt[addr.getOffset()]); } Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { @@ -507,9 +507,9 @@ Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { return NULL; } - NodeTable *nt = (NodeTable *)_heap[addr.getSegment()]; + NodeTable &nt = *(NodeTable *)_heap[addr.getSegment()]; - if (!nt->isValidEntry(addr.getOffset())) { + if (!nt.isValidEntry(addr.getOffset())) { if (!stopOnDiscarded) return NULL; @@ -517,7 +517,7 @@ Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { return NULL; } - return &(nt->_table[addr.getOffset()]); + return &(nt[addr.getOffset()]); } SegmentRef SegManager::dereference(reg_t pointer) { @@ -873,32 +873,32 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_arraysSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } SciArray<reg_t> *SegManager::lookupArray(reg_t addr) { if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()]; + ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()]; - if (!arrayTable->isValidEntry(addr.getOffset())) + if (!arrayTable.isValidEntry(addr.getOffset())) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - return &(arrayTable->_table[addr.getOffset()]); + return &(arrayTable[addr.getOffset()]); } void SegManager::freeArray(reg_t addr) { if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()]; + ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()]; - if (!arrayTable->isValidEntry(addr.getOffset())) + if (!arrayTable.isValidEntry(addr.getOffset())) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - arrayTable->_table[addr.getOffset()].destroy(); - arrayTable->freeEntry(addr.getOffset()); + arrayTable[addr.getOffset()].destroy(); + arrayTable.freeEntry(addr.getOffset()); } SciString *SegManager::allocateString(reg_t *addr) { @@ -913,32 +913,32 @@ SciString *SegManager::allocateString(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_stringSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } SciString *SegManager::lookupString(reg_t addr) { if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING) error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; + StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.getOffset())) + if (!stringTable.isValidEntry(addr.getOffset())) error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - return &(stringTable->_table[addr.getOffset()]); + return &(stringTable[addr.getOffset()]); } void SegManager::freeString(reg_t addr) { if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING) error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; + StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.getOffset())) + if (!stringTable.isValidEntry(addr.getOffset())) error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - stringTable->_table[addr.getOffset()].destroy(); - stringTable->freeEntry(addr.getOffset()); + stringTable[addr.getOffset()].destroy(); + stringTable.freeEntry(addr.getOffset()); } #endif diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp index bb90698e6a..2cff799f4b 100644 --- a/engines/sci/engine/segment.cpp +++ b/engines/sci/engine/segment.cpp @@ -97,7 +97,7 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const { error("Unexpected request for outgoing references from clone at %04x:%04x", PRINT_REG(addr)); } - const Clone *clone = &(_table[addr.getOffset()]); + const Clone *clone = &at(addr.getOffset()); // Emit all member variables (including references to the 'super' delegate) for (uint i = 0; i < clone->getVarCount(); i++) @@ -112,7 +112,7 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const { void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) { #ifdef GC_DEBUG - Object *victim_obj = &(_table[addr.getOffset()]); + Object *victim_obj = &at(addr.getOffset()); if (!(victim_obj->_flags & OBJECT_FLAG_FREED)) warning("[GC] Clone %04x:%04x not reachable and not freed (freeing now)", PRINT_REG(addr)); @@ -208,7 +208,7 @@ Common::Array<reg_t> ListTable::listAllOutgoingReferences(reg_t addr) const { error("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const List *list = &(_table[addr.getOffset()]); + const List *list = &at(addr.getOffset()); tmp.push_back(list->first); tmp.push_back(list->last); @@ -225,7 +225,7 @@ Common::Array<reg_t> NodeTable::listAllOutgoingReferences(reg_t addr) const { if (!isValidEntry(addr.getOffset())) { error("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const Node *node = &(_table[addr.getOffset()]); + const Node *node = &at(addr.getOffset()); // We need all four here. Can't just stick with 'pred' OR 'succ' because node operations allow us // to walk around from any given node @@ -252,13 +252,13 @@ SegmentRef DynMem::dereference(reg_t pointer) { SegmentRef ArrayTable::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = false; - ret.maxSize = _table[pointer.getOffset()].getSize() * 2; - ret.reg = _table[pointer.getOffset()].getRawData(); + ret.maxSize = at(pointer.getOffset()).getSize() * 2; + ret.reg = at(pointer.getOffset()).getRawData(); return ret; } void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.getOffset()].destroy(); + at(sub_addr.getOffset()).destroy(); freeEntry(sub_addr.getOffset()); } @@ -268,7 +268,7 @@ Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const { error("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const SciArray<reg_t> *array = &(_table[addr.getOffset()]); + const SciArray<reg_t> *array = &at(addr.getOffset()); for (uint32 i = 0; i < array->getSize(); i++) { reg_t value = array->getValue(i); @@ -305,8 +305,8 @@ void SciString::fromString(const Common::String &string) { SegmentRef StringTable::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = true; - ret.maxSize = _table[pointer.getOffset()].getSize(); - ret.raw = (byte *)_table[pointer.getOffset()].getRawData(); + ret.maxSize = at(pointer.getOffset()).getSize(); + ret.raw = (byte *)at(pointer.getOffset()).getRawData(); return ret; } diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index 2699bc2e5b..50c77d0538 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -210,16 +210,17 @@ struct Hunk { template<typename T> struct SegmentObjTable : public SegmentObj { typedef T value_type; - struct Entry : public T { + struct Entry { + T data; int next_free; /* Only used for free entries */ }; enum { HEAPENTRY_INVALID = -1 }; - int first_free; /**< Beginning of a singly linked list for entries */ int entries_used; /**< Statistical information */ - Common::Array<Entry> _table; + typedef Common::Array<Entry> ArrayType; + ArrayType _table; public: SegmentObjTable(SegmentType type) : SegmentObj(type) { @@ -272,6 +273,14 @@ public: tmp.push_back(make_reg(segId, i)); return tmp; } + + uint size() const { return _table.size(); } + + T &at(uint index) { return _table[index].data; } + const T &at(uint index) const { return _table[index].data; } + + T &operator[](uint index) { return at(index); } + const T &operator[](uint index) const { return at(index); } }; @@ -323,8 +332,8 @@ struct HunkTable : public SegmentObjTable<Hunk> { } void freeEntryContents(int idx) { - free(_table[idx].mem); - _table[idx].mem = 0; + free(at(idx).mem); + at(idx).mem = 0; } virtual void freeEntry(int idx) { @@ -502,7 +511,7 @@ struct StringTable : public SegmentObjTable<SciString> { StringTable() : SegmentObjTable<SciString>(SEG_TYPE_STRING) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.getOffset()].destroy(); + at(sub_addr.getOffset()).destroy(); freeEntry(sub_addr.getOffset()); } diff --git a/engines/scumm/POTFILES b/engines/scumm/POTFILES index 246f14d3f0..039aa16755 100644 --- a/engines/scumm/POTFILES +++ b/engines/scumm/POTFILES @@ -1,3 +1,4 @@ +engines/scumm/detection.cpp engines/scumm/dialogs.cpp engines/scumm/help.cpp engines/scumm/input.cpp diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index 9264a6443b..0867b20fc3 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -29,6 +29,7 @@ #include "common/md5.h" #include "common/savefile.h" #include "common/system.h" +#include "common/translation.h" #include "audio/mididrv.h" @@ -957,6 +958,7 @@ public: virtual int getMaximumSaveSlot() const; virtual void removeSaveState(const char *target, int slot) const; virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; + virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; }; bool ScummMetaEngine::hasFeature(MetaEngineFeature f) const { @@ -1329,6 +1331,21 @@ SaveStateDescriptor ScummMetaEngine::querySaveMetaInfos(const char *target, int return desc; } +static const ExtraGuiOption comiObjectLabelsOption = { + _s("Show Object Line"), + _s("Show the names of objects at the bottom of the screen"), + "object_labels", + true +}; + +const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &target) const { + ExtraGuiOptions options; + if (target.empty() || ConfMan.get("gameid", target) == "comi") { + options.push_back(comiObjectLabelsOption); + } + return options; +} + #if PLUGIN_ENABLED_DYNAMIC(SCUMM) REGISTER_PLUGIN_DYNAMIC(SCUMM, PLUGIN_TYPE_ENGINE, ScummMetaEngine); #else diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp index 21c7428621..ba9cb2a277 100644 --- a/engines/scumm/dialogs.cpp +++ b/engines/scumm/dialogs.cpp @@ -52,7 +52,7 @@ #include "scumm/help.h" #endif -#ifdef SMALL_SCREEN_DEVICE +#ifdef GUI_ENABLE_KEYSDIALOG #include "gui/KeysDialog.h" #endif diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h index 41c59cb521..e986ae4b47 100644 --- a/engines/scumm/scumm-md5.h +++ b/engines/scumm/scumm-md5.h @@ -1,5 +1,5 @@ /* - This file was generated by the md5table tool on Tue Jan 12 23:47:54 2016 + This file was generated by the md5table tool on Mon Mar 28 09:52:54 2016 DO NOT EDIT MANUALLY! */ @@ -189,6 +189,7 @@ static const MD5Table md5table[] = { { "3b832f4a90740bf22e9b8ed42ca0128c", "freddi4", "HE 99", "", -1, Common::EN_GRB, Common::kPlatformUnknown }, { "3c4c471342bd95505a42334367d8f127", "puttmoon", "HE 70", "", 12161, Common::RU_RUS, Common::kPlatformWindows }, { "3cce1913a3bc586b51a75c3892ff18dd", "indy3", "VGA", "VGA", -1, Common::RU_RUS, Common::kPlatformDOS }, + { "3cf4b6ff78f735b671d8ccc2bc110b15", "maniac", "V2", "V2", -1, Common::ES_ESP, Common::kPlatformAmiga }, { "3d219e7546039543307b55a91282bf18", "funpack", "", "", -1, Common::EN_ANY, Common::kPlatformDOS }, { "3de99ef0523f8ca7958faa3afccd035a", "spyfox", "HE 100", "Updated", -1, Common::EN_USA, Common::kPlatformUnknown }, { "3df6ead57930488bc61e6e41901d0e97", "fbear", "HE 62", "", -1, Common::EN_ANY, Common::kPlatformMacintosh }, diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp index d6f8021e5b..151d986d81 100644 --- a/engines/sherlock/scalpel/scalpel_journal.cpp +++ b/engines/sherlock/scalpel/scalpel_journal.cpp @@ -485,11 +485,6 @@ int ScalpelJournal::getSearchString(bool printError) { screen.makeField(Common::Rect(12, 185, 307, 196)); - screen.fillRect(Common::Rect(12, 185, 307, 186), BUTTON_BOTTOM); - screen.vLine(12, 185, 195, BUTTON_BOTTOM); - screen.hLine(13, 195, 306, BUTTON_TOP); - screen.hLine(306, 186, 195, BUTTON_TOP); - if (printError) { screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth(_fixedTextSearchNotFound)) / 2, 185), INV_FOREGROUND, "%s", _fixedTextSearchNotFound.c_str()); diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp index b543472513..3c6bf44837 100644 --- a/engines/sherlock/talk.cpp +++ b/engines/sherlock/talk.cpp @@ -28,9 +28,11 @@ #include "sherlock/scalpel/scalpel_talk.h" #include "sherlock/scalpel/scalpel_user_interface.h" #include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" #include "sherlock/tattoo/tattoo_people.h" #include "sherlock/tattoo/tattoo_scene.h" #include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_user_interface.h" namespace Sherlock { @@ -306,8 +308,14 @@ void Talk::talkTo(const Common::String filename) { if (_scriptMoreFlag && _scriptSelect != 100) select = _scriptSelect; - if (select == -1) + if (select == -1) { + if (IS_ROSE_TATTOO) { + static_cast<Tattoo::TattooUserInterface *>(&ui)->putMessage( + "%s", _vm->_fixedText->getText(Tattoo::kFixedText_NoEffect)); + return; + } error("Couldn't find statement to display"); + } // Add the statement into the journal and talk history if (_talkTo != -1 && !_talkHistory[_converseNum][select]) diff --git a/engines/sherlock/tattoo/tattoo_journal.cpp b/engines/sherlock/tattoo/tattoo_journal.cpp index 918887f320..8e1a61d36e 100644 --- a/engines/sherlock/tattoo/tattoo_journal.cpp +++ b/engines/sherlock/tattoo/tattoo_journal.cpp @@ -523,15 +523,15 @@ void TattooJournal::drawControls(int mode) { if (mode != 2) { // Draw the Bars separating the Journal Commands - int xp = r.right / 3; + int xp = r.left + r.width() / 3; for (int idx = 0; idx < 2; ++idx) { screen._backBuffer1.SHtransBlitFrom(images[6], Common::Point(xp - 2, r.top + 1)); screen._backBuffer1.SHtransBlitFrom(images[7], Common::Point(xp - 2, yp - 1)); - screen._backBuffer1.hLine(xp - 1, r.top + 4, yp - 2, INFO_TOP); - screen._backBuffer1.hLine(xp, r.top + 4, yp - 2, INFO_MIDDLE); - screen._backBuffer1.hLine(xp + 1, r.top + 4, yp - 2, INFO_BOTTOM); - xp = r.right / 3 * 2; + screen._backBuffer1.vLine(xp - 1, r.top + 4, yp - 2, INFO_TOP); + screen._backBuffer1.vLine(xp, r.top + 4, yp - 2, INFO_MIDDLE); + screen._backBuffer1.vLine(xp + 1, r.top + 4, yp - 2, INFO_BOTTOM); + xp += r.width() / 3; } } diff --git a/engines/sherlock/tattoo/widget_inventory.cpp b/engines/sherlock/tattoo/widget_inventory.cpp index 34331f0eae..9f126cf7a7 100644 --- a/engines/sherlock/tattoo/widget_inventory.cpp +++ b/engines/sherlock/tattoo/widget_inventory.cpp @@ -546,7 +546,7 @@ void WidgetInventory::drawBars() { _surface.SHtransBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2)); } - _surface.hLine(x + 2, INVENTORY_YSIZE + 2, INVENTORY_YSIZE + 8, INFO_BOTTOM); + _surface.vLine(x + 2, INVENTORY_YSIZE + 2, INVENTORY_YSIZE + 8, INFO_BOTTOM); } void WidgetInventory::drawInventory() { diff --git a/engines/wage/detection.cpp b/engines/wage/detection.cpp index 512d432e54..1b418b5aa8 100644 --- a/engines/wage/detection.cpp +++ b/engines/wage/detection.cpp @@ -54,6 +54,7 @@ static const PlainGameDescriptor wageGames[] = { class WageMetaEngine : public AdvancedMetaEngine { public: WageMetaEngine() : AdvancedMetaEngine(Wage::gameDescriptions, sizeof(ADGameDescription), wageGames) { + _md5Bytes = 50000; _singleId = "wage"; _guiOptions = GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI); } diff --git a/engines/wage/detection_tables.h b/engines/wage/detection_tables.h index 1a177c2bb0..096338659b 100644 --- a/engines/wage/detection_tables.h +++ b/engines/wage/detection_tables.h @@ -32,108 +32,148 @@ namespace Wage { #define BIGGAME(t,v,f,m,s) { t,v,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_DEFAULT,GUIO0()} static const ADGameDescription gameDescriptions[] = { - FANGAME("3rd Floor", "913812a1ac7a6b0e48dadd1afa1c7763", 281409), - BIGGAME("afm", "v1.8", "Another Fine Mess 1.8", "94a9c4f8b3dabd1846d76215a49bd221", 1420723), - BIGGAME("amot", "v1.8", "A Mess O' Trouble 1.8", "26207bdf0bb539464f136f0669af885f", 1843104), + FANGAME("3rd Floor", "3ed49d2163e46d2c9b33fd80927d9e22", 281409), + FANGAME("3rd Floor", "3ed49d2163e46d2c9b33fd80927d9e22", 281423), // alt version + BIGGAME("afm", "v1.8", "Another Fine Mess 1.8", "abc7188469a9a7083fd4caec55a4f76e", 1420723), + BIGGAME("amot", "v1.8", "A Mess O' Trouble 1.8", "6b59e5bb9a4b74ecdd9f66d4e36a59cf", 1843104), // No Next on the first screen? - FANGAME("Brownie's Dream", "94a9c4f8b3dabd1846d76215a49bd221", 440704), - FANGAMEN("Brownie's Time Travels", "Brownie's Time Travels v1.2", "94a9c4f8b3dabd1846d76215a49bd221", 471589), - FANGAME("Bug Hunt", "595117cbed33e8de1ab3714b33880205", 195699), - BIGGAME("cantitoe", "", "Camp Cantitoe", "913812a1ac7a6b0e48dadd1afa1c7763", 616985), + FANGAME("Brownie's Dream", "6fdcce532bcd50b7e4f3f6bab50a0ee6", 440704), + FANGAMEN("Brownie's Time Travels", "Brownie's Time Travels v1.2", "55842a100b56e236c5ad69563e01fc24", 471589), + FANGAME("Bug Hunt", "738e2e8a1020be48c5ef42da571674ae", 195699), + FANGAME("Bug Hunt", "118a41121143488719d28daa9af8cd39", 195779), // alt version + BIGGAME("cantitoe", "", "Camp Cantitoe", "1780c41d14b876461a19dbeceebf2a37", 616985), // Problems with letter rendering - FANGAME("Canal District", "a56aa3cd4a6e070e15ce1d5815c7be0a", 641470), - FANGAME("Carbon Copy", "913812a1ac7a6b0e48dadd1afa1c7763", 519445), + FANGAME("Canal District", "34e7a8e84b33ba8ea38b4ffd76ef074f", 641470), + FANGAME("Carbon Copy", "9e781acd63290ae390d515cffc742011", 519445), // Invalid rect in scene "FINALE" - FANGAME("Castle of Ert", "327610eb2298a9427a566288312df040", 198955), - FANGAME("Deep Angst", "b130b3c811cd89024dd5fdd2b71f70b8", 329550), - FANGAME("Deep Ennui", "913812a1ac7a6b0e48dadd1afa1c7763", 86075), + FANGAME("Castle of Ert", "a45b439bb3a9c8a4a14b996024222068", 198955), + FANGAMEN("Castle of Ert", "Castle of Ert.1", "a45b439bb3a9c8a4a14b996024222068", 198983), // alt version + FANGAMEND("Death Mall", "Death Mall Demo", "1c78fc15fb037b242a0bc6bac7d4d889", 254874), + FANGAME("Deep Angst", "7f8821f7b279269a91f9aadfed98eec0", 329550), // Original gile name "Deep Angst™" + FANGAME("Deep Ennui", "7fa4368834a22a9d4b7246a6297b455f", 86075), // Polygons with ignored byte 1 - FANGAME("Double Trouble", "1652e36857a04c01dc560234c4818619", 542371), - BIGGAME("drakmythcastle", "disk I", "Drakmyth Castle disk I of II", "94a9c4f8b3dabd1846d76215a49bd221", 793784), - BIGGAME("drakmythcastle", "disk II", "Drakmyth Castle II", "cc978cc9a5256724702463cb5aaaffa0", 1685659), + FANGAME("Double Trouble", "3f0c032377d87704267283380800633a", 542371), + BIGGAME("drakmythcastle", "disk I", "Drakmyth Castle disk I of II", "5b1fd760fbc081c608acebfe1d07a58a", 793784), + BIGGAME("drakmythcastle", "disk II", "Drakmyth Castle II", "1116f9c2c781f79e1f9c868b51ae7fa5", 1685659), // Crash at start in GUI rendering - FANGAME("Dune Eternity", "94a9c4f8b3dabd1846d76215a49bd221", 290201), // Original file name is "***DUNE ETERNITY*** " - FANGAMEN("Dungeon World II", "DungeonWorld2", "0154ea11d3cbb536c13b4ae9e6902d48", 230199), - FANGAME("Edg's World", "913812a1ac7a6b0e48dadd1afa1c7763", 106769), - FANGAME("Eidisi I", "595117cbed33e8de1ab3714b33880205", 172552), + FANGAME("Dune Eternity", "6b29f82e235815ffc4c9f30dc09968dd", 290201), // Original file name is "***DUNE ETERNITY*** " + FANGAMEN("Dungeon World II", "DungeonWorld2", "753df07166ca48e303d782cc72dd4053", 230199), + // Made for bigger resolution + FANGAME("Dynasty of Dar", "b2e9a5cca28acb85617b1477a5fca3e2", 275693), + FANGAME("Edg's World", "0a3a3aaa36088c514b668f1f62120006", 106769), + FANGAME("Eidisi I", "3d778c0fe7addf5f29e7593ba0fd3953", 172552), + FANGAME("Eidisi I", "8c2fb325a49344568c5536bba36a2556", 172566), // alt version // Problems(?) with text on the first screen - FANGAMEN("Enchanted Pencils", "Enchanted Pencils 0.99 (PG)", "595117cbed33e8de1ab3714b33880205", 408913), - FANGAME("Escape from School!", "913812a1ac7a6b0e48dadd1afa1c7763", 50105), - FANGAME("Everyman 1", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 335705), - FANGAME("Exploration Zeta!", "c477921aeee6ed0f8997ba44447eb2d0", 366599), + FANGAMEN("Enchanted Pencils", "Enchanted Pencils 0.99 (PG)", "9a9777a83e58bebfa6f1662d5e236384", 408913), + FANGAME("Escape!", "3ada261c2d1d9ce6b9da068237472689", 65075), // Original file name "Escape!†" + FANGAME("Escape from School!", "2055747bb874052333190eb993246a7f", 50105), + FANGAME("Escape from School!", "fcc581e52d1fc8ea4603d7c953fa935a", 50119), // Original file name "Escape from School!†" + FANGAME("Everyman 1", "e20cebf0091a1b1bf023aac6f28c9011", 335705), + FANGAME("Exploration Zeta!", "6127d9c04ad68f0cbb5f6aa1d95b48a2", 366599), + // Cannot proceed past the first scene + FANGAMEND("Explorer", "Explorer DEMO", "a9ebdecf6c8de95a03e593d877dacc13", 461228), // Crash in console rendering on the first scene - FANGAME("Fantasy Quest", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 762754), - FANGAME("Find the Heart", "595117cbed33e8de1ab3714b33880205", 106235), // From Joshua's Worlds 1.0 - // Problems with window overlay - FANGAMEN("Jumble", "LSJUMBLE", "e12ec4d76d48bdc86567c5e63750547e", 647339), // Original file name is "LSJUMBLE† " - FANGAME("Karth of the Jungle", "595117cbed33e8de1ab3714b33880205", 96711), - FANGAME("Karth of the Jungle", "595117cbed33e8de1ab3714b33880205", 96960), // Alternative version - FANGAME("Karth of the Jungle II", "c106835ab4436de054e03aec3ce904ce", 201053), - FANGAMEN("Little Pythagoras", "Little Pythagoras 1.1.1", "94a9c4f8b3dabd1846d76215a49bd221", 628821), - FANGAME("Lost Crystal", "8174c81ea1858d0079ae040dae2cefd3", 771072), + FANGAME("Fantasy Quest", "b42b0e86e2c84464283640c74b25e015", 762754), + FANGAME("Find the Heart", "aa244c15f2ba8cef468714be34223acd", 106235), // From Joshua's Worlds 1.0 + FANGAME("Find the Heart", "a6834cb230cea1953f5bf1f8f7aacabd", 105885), // From Joshua's Worlds. Alt version + FANGAME("Find the Heart", "a6834cb230cea1953f5bf1f8f7aacabd", 105871), // Standalone + FANGAMEN("Fortune Teller", "Fortune Teller 1.1", "7d2628eeea67b33379e01c0aef8dd196", 73931), + FANGAMEN("Haunted House", "Haunted House 1.5", "5db2f95c7abaa9d060b94271a5bc57f8", 177500), + // Cropped graphics on first scene + FANGAME("Intro to Gothic", "6f732eaad6e3b85795f8ee6c6a40d837", 208067), + // No Next button in intro + FANGAME("Jamie the Demon Slayer", "fa0ca9618c18425b6d9bf913f762d91b", 232789), + FANGAMEN("Journey", "The Journey 1.6.2 US", "e66f37472e1414a088eb5d5acc4df794", 820572), + FANGAMEN("Jumble", "LSJUMBLE", "7c46851d2f90c7da9efe40b1688869c2", 647339), // Original file name is "LSJUMBLE† " + FANGAME("Karth of the Jungle", "5f2346834821dc3c4008e139cd37b3cb", 96711), + FANGAME("Karth of the Jungle", "444f9426f342135fbcc32180e5ba5b1c", 96960), // Alternative version + FANGAME("Karth of the Jungle II", "32161b27de894fd9e3f054afc4013f34", 201053), + FANGAMEN("Little Pythagoras", "Little Pythagoras 1.1.1", "75906fa955de695ac3e8164e7d88ac7b", 628821), + FANGAME("Lost Crystal", "d5e27a83f2884a24c6ec26c6cb776fe9", 771072), // Crash in design drawing on startup - FANGAMEN("Lost In Kookyville", "Lost In Kookyville 1.2.4", "e6cea2234cee9d0dba7be10bc1ad6055", 721569), - FANGAME("Magic Rings", "913812a1ac7a6b0e48dadd1afa1c7763", 109044), + FANGAMEN("Lost In Kookyville", "Lost In Kookyville 1.2.4", "5ab6259706b33230dbfba05618c2c5c9", 721569), + FANGAME("Magic Rings", "450e986694b96f3b9e6cc64e57b753dc", 109044), // No way to click on the house - FANGAME("Messy House", "913812a1ac7a6b0e48dadd1afa1c7763", 177120), - FANGAME("Midnight Snack", "913812a1ac7a6b0e48dadd1afa1c7763", 67952), - FANGAME("Midnight Snack", "913812a1ac7a6b0e48dadd1afa1c7763", 67966), // Alt version - FANGAME("Minitorian", "913812a1ac7a6b0e48dadd1afa1c7763", 586464), - FANGAME("M'Lord's Warrior", "7d30b6e68ecf197b2d15492630bdeb89", 465639), // Original file name is "M'Lord's Warrior †" + FANGAME("Messy House", "705df61da9e7d742b7ad678e59eb7bfb", 177120), + FANGAME("Midnight Snack", "76986389f9a08dd95450c8b9cf408653", 67952), + FANGAME("Midnight Snack", "76986389f9a08dd95450c8b9cf408653", 67966), // Alt version + FANGAME("Mike's House", "3d23c2b88cefd958bcbc4d4c711003d8", 87357), + FANGAME("Minitorian", "15fbb2bd75d83155ed21edbc5dc9558f", 586464), + FANGAME("M'Lord's Warrior", "0bebb2c62529c89590f6c5be6e1e9838", 465639), // Original file name is "M'Lord's Warrior †" // Unhandled comparison case - FANGAME("Mountain of Mayhem", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 750003), // Original file name "Mountain of Mayhem †" + FANGAME("Mountain of Mayhem", "4088fc534042081b7ab7b49675ab4a6e", 750003), // Original file name "Mountain of Mayhem †" // No way to pass through the first screen - FANGAME("Nightcrawler Ned", "94a9c4f8b3dabd1846d76215a49bd221", 366542), + FANGAME("Nightcrawler Ned", "0cf27bf82de299c69405f7910146bf00", 366542), // Crash on startup - FANGAMEN("Parrot Talk", "PARROT TALK V1", "d81f2d03a1e863f04fb1e3a5495b720e", 118936), + FANGAMEN("Parrot Talk", "PARROT TALK V1", "b1570b0779891d5d50a3cf0146d28202", 118936), // Crash on startup - FANGAMEN("Parrot Talk", "PARROT TALKV2", "d81f2d03a1e863f04fb1e3a5495b720e", 118884), - FANGAME("Pavilion", "4d991d7d1534d48d90598d86ea6d5d97", 231687), - FANGAMEN("Pencils", "Pencils.99", "913812a1ac7a6b0e48dadd1afa1c7763", 408551), + FANGAMEN("Parrot Talk", "PARROT TALKV2", "0c1e920ed3ff74b8f22eaaf0d3496d5a", 118884), + FANGAME("Pavilion", "3a33149569325a44d98544452323c819", 231687), + FANGAMEN("Pencils", "Pencils.99", "9c200938488565080e12989e784586e2", 408551), // Polygons with byte 1 - FANGAME("Periapt", "913812a1ac7a6b0e48dadd1afa1c7763", 406006), - FANGAME("Puzzle Piece Search", "595117cbed33e8de1ab3714b33880205", 247693), // From Joshua's Worlds 1.0 + FANGAME("Periapt", "fb4052819126b88d7e03ebc00c669a9d", 406006), + FANGAME("Psychotic!", "6b4ae6261b405e2feac58c5a2ddb67c5", 247693), + FANGAME("Puzzle Piece Search", "6b4ae6261b405e2feac58c5a2ddb67c5", 247693), // From Joshua's Worlds 1.0 + FANGAME("The Puzzle Piece Search", "fb99797c429c18ec68418fdd12af17a1", 247338), // From Joshua's Worlds + FANGAME("The Puzzle Piece Search", "fb99797c429c18ec68418fdd12af17a1", 247324), // Stnadalone // Empty(?) first scene - FANGAME("Pyramid of No Return", "77a55a45f794b4d4a56703d3acce871e", 385145), - FANGAME("Queen Quest", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 57026), - FANGAME("Quest for T-Rex", "913812a1ac7a6b0e48dadd1afa1c7763", 592584), + FANGAME("Pyramid of No Return", "38383ac85cc16703f13f8d82f1398123", 385145), + // Cropped graphics at the first scene + FANGAME("Psychotic!", "29b30e6aae9cc6db5eccb09f695ff25e", 367309), + FANGAME("P-W Adventure", "9bf86fb946683500d23887ef185726ab", 219216), + FANGAMEN("Pyramid of Ert", "Pyramid of Ert V1.2", "fb931cd35440a66864a434c773b496da", 315783), + FANGAME("Queen Quest", "8273e29afe64a984eb0ce7b43fdf3a59", 57039), // alt version + FANGAME("Quest for T-Rex", "f16f2cd525c9aeb4733295d8d842b902", 592584), // Crash in console rendering on the initial scene - FANGAME("Quest for the Dark Sword", "b35dd0c078da9f35fc25a455f56bb129", 572576), - FANGAME("Radical Castle", "677bfee4afeca2f7152eb8b76c85ca8d", 355601), - FANGAME("Radical Castle 1.0", "677bfee4afeca2f7152eb8b76c85ca8d", 347278), - BIGGAME("raysmaze", "v1.5", "Ray's Maze1.5", "064b16d8c20724f8debbbdc3aafde538", 1408516), - BIGGAME("raysmaze", "v1.5/alt", "Ray's Maze1.5", "92cca777800c3d31a77b5ed7f6ee49ad", 1408516), + FANGAME("Quest for the Dark Sword", "4815d9a770904b26c463b7e4fcd121c7", 572576), + FANGAME("Radical Castle", "09b70763c7a48a76240bd0e42737caaa", 355601), + FANGAME("Radical Castle 1.0", "8ae2e29ffeca52a5c7fae66dec4764a3", 347278), + BIGGAME("raysmaze", "v1.5", "Ray's Maze1.5", "521583e59bdc1d611f963cef1dc25869", 1408516), + BIGGAME("raysmaze", "v1.5/alt", "Ray's Maze1.5", "120e65bec953b981b2e0aed45ad45d70", 1408516), + // Next button is not visible + FANGAME("Ray's World Builder Demo", "d252ee8e38c9abc50455d071a367d031", 116056), // Unhandled comparison case - FANGAME("Sands of Time", "913812a1ac7a6b0e48dadd1afa1c7763", 122672), // Original file name "Sands of Time†" - BIGGAME("scepters", "", "Scepters", "3311deef8bf82f0b4b1cfa15a3b3289d", 346595), + FANGAME("Sands of Time", "b00ea866cb04cd87124e5720bc2c84c7", 122672), // Original file name "Sands of Time†" + BIGGAME("scepters", "", "Scepters", "f8db17cd96be056cf8a8bb9cfe46cf3a", 346595), + BIGGAME("scepters", "", "Scepters", "1fd7ca93ef16f4752fb46ee9cfa0949a", 347540), // alt version + FANGAME("Schmoozer", "e0f416bae626e2c638055b7f495d8c78", 221500), // ??? problems with dog bitmap? - FANGAMEN("Space Adventure", "SpaceAdventure", "f9f3f1c419f56955f7966355b34ea5c8", 155356), - FANGAMEN("Spear of Destiny", "SpearOfDestiny", "913812a1ac7a6b0e48dadd1afa1c7763", 333665), // Original file name "SpearOfDestiny†" - FANGAME("Star Trek", "44aaef4806578700429de5aaf95c266e", 53320), - FANGAME("Strange Disappearance", "d81f2d03a1e863f04fb1e3a5495b720e", 772282), + FANGAMEN("Space Adventure", "SpaceAdventure", "7b6c883b3510e21cfabf4c8caaeb1f16", 155356), + FANGAMEN("Space Adventure", "SpaceAdventure", "3bd6fc9327f35db5390a9bf86afcd872", 155356), // alt version + FANGAMEN("Spear of Destiny", "SpearOfDestiny", "f1252ff34dd279f4ec1844bb403a578c", 333665), // Original file name "SpearOfDestiny†" + FANGAME("Star Trek", "fe20d06bc50c7fcebda0db533e141d4a", 53320), + FANGAME("Strange Disappearance", "782fae517f7374cd7f43f428331ce445", 772282), // Code 0x03 in text - FANGAME("Swamp Witch", "913812a1ac7a6b0e48dadd1afa1c7763", 739781), // Original file name "Swamp Witch†" - FANGAME("Sweetspace Now!", "e12ec4d76d48bdc86567c5e63750547e", 123813), // Comes with Jumble + FANGAME("Swamp Witch", "4f146c0a5c59e7d4717a0423271fa89d", 739781), // Original file name "Swamp Witch†" + FANGAME("Sweetspace Now!", "1d419bc0b04c51468ddc40a90125bf00", 123813), // Comes with Jumble // Wrong scrolling in the first console text - FANGAMEN("Sword of Siegfried", "Sword of Siegfried 1.0", "913812a1ac7a6b0e48dadd1afa1c7763", 234763), - FANGAME("Time Bomb", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 64564), - FANGAME("Time Bomb", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 64578), // Alt version - FANGAMEND("The Ashland Revolution", "The Ashland Revolution Demo", "913812a1ac7a6b0e48dadd1afa1c7763", 145023), // Original file name "The Ashland Revolution Demo†" - FANGAME("The Axe-orcist", "94a9c4f8b3dabd1846d76215a49bd221", 308764), - FANGAMEN("The Hotel Caper", "The Hotel Caper V1.0", "595117cbed33e8de1ab3714b33880205", 231969), + FANGAMEN("Sword of Siegfried", "Sword of Siegfried 1.0", "1ee92830690f89ea142ac0847176a0c3", 234763), + FANGAME("Terrorist", "68208fa5e426312fb12402894add5e4a", 524469), // Original file name "Terrorist†" + FANGAME("Time Bomb", "b7a369d57d43ec8d9fd53832fd38d7db", 64564), + FANGAME("Time Bomb", "b7a369d57d43ec8d9fd53832fd38d7db", 64578), // Alt version + FANGAMEND("The Ashland Revolution", "The Ashland Revolution Demo", "3c7a1bdeab48a077a4f54fe69da61a9f", 145023), // Original file name "The Ashland Revolution Demo†" + FANGAME("The Axe-orcist", "bfdf6a4ce87e6b368977af3b683466db", 308764), + FANGAMEN("The Hotel Caper", "The Hotel Caper V1.0", "0d11a6ca1357e27ffff5231fe89cc429", 231969), + FANGAMEN("The Hotel Caper", "The Hotel Caper V1.0", "6c80fa6a36d16aa0edef86d8800c90db", 231969), // alt version // Invalid rect in scene "Access Tube 1" - FANGAMEN("The Phoenix v1.2", "The Phoenix", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 431640), - FANGAME("The Sultan's Palace", "358799d446ee4fc12f793febd6c94b95", 456855), + FANGAMEN("The Phoenix v1.2", "The Phoenix", "0a4a01b83c993408ae824cc4f63957ea", 431640), + FANGAME("The Phoenix", "0a4a01b83c993408ae824cc4f63957ea", 431643), + FANGAME("The Sultan's Palace", "98c845323489344d7e2c9d1c3e53d1fc", 456855), // Admission for on 3rd screen is messed up - FANGAME("The Tower", "435f420b9dff895ae1ddf1338040c51d", 556539), + FANGAME("The Tower", "135fe861928d15b5acd8b355460c54bf", 556539), // Polygons with ignored byte 1 and 2 on second scene - FANGAME("The Village", "913812a1ac7a6b0e48dadd1afa1c7763", 314828), + FANGAME("The Village", "b9b5cfbfc7f482eae7587b55edc135ed", 314828), + FANGAME("The Wizard's Apprentice", "7eff3cb7d1a3f59639c62cf196039745", 782824), + // Messed up first scene + FANGAMEND("Tombworld", "Demo TombWorld", "f7c86166e29fb8b57f7a1400d4963a4e", 664252), // Original file name "Demo TombWorld©" // Doesn't go past first scene - BIGGAME("twisted", "", "Twisted! 1.6", "26207bdf0bb539464f136f0669af885f", 960954), - FANGAME("Wishing Well", "913812a1ac7a6b0e48dadd1afa1c7763", 103688), - FANGAME("Wizard's Warehouse", "913812a1ac7a6b0e48dadd1afa1c7763", 159748), - FANGAME("ZikTuria", "418e74ca71029a1e9db80d0eb30c0843", 52972), - FANGAME("Zoony", "539a64151426edc92da5eedadf39f23c", 154990), // original filename "Zoony™" + BIGGAME("twisted", "", "Twisted! 1.6", "97ab265eddf0cfed6d43d062c853cbc0", 960954), + FANGAME("Volcano II", "4dbb7ec6111c0f872da8ed8ba14763c9", 82991), // Original file name "Volcano II†" + FANGAME("Wishing Well", "ece06c419cbb2d32941e6b5c7d9d7c1a", 103688), + FANGAME("Wizard's Warehouse", "ee1b86841583e2b58ac39bf97017dc7b", 159748), + FANGAMEN("Wizard's Warehouse 2", "WizWarehouse 2.0", "6502bd974fe149fe76d6d5ae9d1e6878", 230870), + FANGAME("ZikTuria", "1b934fca68d633d231dccd2047d2d274", 52972), + FANGAME("Zoony", "7bb293b81117cbd974ce54fafa06f258", 154990), // original filename "Zoony™" AD_TABLE_END_MARKER }; diff --git a/engines/wage/gui.cpp b/engines/wage/gui.cpp index 9dd1a24b3c..93c799e73a 100644 --- a/engines/wage/gui.cpp +++ b/engines/wage/gui.cpp @@ -50,6 +50,7 @@ #include "graphics/cursorman.h" #include "graphics/fonts/bdf.h" #include "graphics/palette.h" +#include "graphics/primitives.h" #include "wage/wage.h" #include "wage/design.h" @@ -237,39 +238,20 @@ void Gui::draw() { return; } - if (_scene != _engine->_world->_player->_currentScene || _sceneDirty) { - _scene = _engine->_world->_player->_currentScene; - - drawDesktop(); - + if (_scene != _engine->_world->_player->_currentScene) _sceneDirty = true; - _consoleDirty = true; - _menuDirty = true; - _consoleFullRedraw = true; - - _scene->paint(&_screen, _scene->_designBounds->left, _scene->_designBounds->top); - _sceneArea.left = _scene->_designBounds->left + kBorderWidth - 2; - _sceneArea.top = _scene->_designBounds->top + kBorderWidth - 2; - _sceneArea.setWidth(_scene->_designBounds->width() - 2 * kBorderWidth); - _sceneArea.setHeight(_scene->_designBounds->height() - 2 * kBorderWidth); + if (_sceneDirty || _bordersDirty) + drawDesktop(); - _consoleTextArea.left = _scene->_textBounds->left + kBorderWidth - 2; - _consoleTextArea.top = _scene->_textBounds->top + kBorderWidth - 2; - _consoleTextArea.setWidth(_scene->_textBounds->width() - 2 * kBorderWidth); - _consoleTextArea.setHeight(_scene->_textBounds->height() - 2 * kBorderWidth); + if (_sceneIsActive) { + drawConsole(); + drawScene(); + } else { + drawScene(); + drawConsole(); } - if (_scene && (_bordersDirty || _sceneDirty)) - paintBorder(&_screen, _sceneArea, kWindowScene); - - // Render console - if (_consoleDirty || _consoleFullRedraw) - renderConsole(&_screen, _consoleTextArea); - - if (_bordersDirty || _consoleDirty || _consoleFullRedraw) - paintBorder(&_screen, _consoleTextArea, kWindowConsole); - if (_menuDirty) _menu->render(); @@ -287,6 +269,41 @@ void Gui::draw() { _consoleFullRedraw = false; } +void Gui::drawScene() { + if (!_sceneDirty && !_bordersDirty) + return; + + _scene = _engine->_world->_player->_currentScene; + + _sceneDirty = true; + _consoleDirty = true; + _menuDirty = true; + _consoleFullRedraw = true; + + _scene->paint(&_screen, _scene->_designBounds->left, _scene->_designBounds->top); + + _sceneArea.left = _scene->_designBounds->left + kBorderWidth - 2; + _sceneArea.top = _scene->_designBounds->top + kBorderWidth - 2; + _sceneArea.setWidth(_scene->_designBounds->width() - 2 * kBorderWidth); + _sceneArea.setHeight(_scene->_designBounds->height() - 2 * kBorderWidth); + + _consoleTextArea.left = _scene->_textBounds->left + kBorderWidth - 2; + _consoleTextArea.top = _scene->_textBounds->top + kBorderWidth - 2; + _consoleTextArea.setWidth(_scene->_textBounds->width() - 2 * kBorderWidth); + _consoleTextArea.setHeight(_scene->_textBounds->height() - 2 * kBorderWidth); + + paintBorder(&_screen, _sceneArea, kWindowScene); +} + +// Render console +void Gui::drawConsole() { + if (!_consoleDirty && !_consoleFullRedraw && !_bordersDirty) + return; + + renderConsole(&_screen, _consoleTextArea); + paintBorder(&_screen, _consoleTextArea, kWindowConsole); +} + void Gui::drawBox(Graphics::Surface *g, int x, int y, int w, int h) { Common::Rect r(x, y, x + w + 1, y + h + 1); @@ -310,7 +327,17 @@ const int arrowPixels[ARROW_H][ARROW_W] = { {0,1,1,1,1,1,1,1,1,1,1,0}, {1,1,1,1,1,1,1,1,1,1,1,1}}; -void Gui::paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType, int highlightedPart) { +static void drawPixelInverted(int x, int y, int color, void *data) { + Graphics::Surface *surface = (Graphics::Surface *)data; + + if (x >= 0 && x < surface->w && y >= 0 && y < surface->h) { + byte *p = (byte *)surface->getBasePtr(x, y); + + *p = *p == kColorWhite ? kColorBlack : kColorWhite; + } +} + +void Gui::paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType, int highlightedPart, float scrollPos, float scrollSize) { bool active = false, scrollable = false, closeable = false, drawTitle = false; const int size = kBorderWidth; int x = r.left - size; @@ -351,39 +378,28 @@ void Gui::paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowTy } else { int x1 = x + width - 15; int y1 = y + size + 1; - int color1 = kColorBlack; - int color2 = kColorWhite; - if (highlightedPart == kBorderScrollUp) { - SWAP(color1, color2); - fillRect(g, x + width - kBorderWidth + 2, y + size, size - 4, r.height() / 2); - } + for (int yy = 0; yy < ARROW_H; yy++) { - for (int xx = 0; xx < ARROW_W; xx++) { - if (arrowPixels[yy][xx] != 0) { - g->hLine(x1 + xx, y1 + yy, x1 + xx, color1); - } else { - g->hLine(x1 + xx, y1 + yy, x1 + xx, color2); - } - } + for (int xx = 0; xx < ARROW_W; xx++) + g->hLine(x1 + xx, y1 + yy, x1 + xx, (arrowPixels[yy][xx] != 0 ? kColorBlack : kColorWhite)); } - fillRect(g, x + width - 13, y + size + ARROW_H, 8, r.height() / 2 - ARROW_H, color1); - color1 = kColorBlack; - color2 = kColorWhite; - if (highlightedPart == kBorderScrollDown) { - SWAP(color1, color2); - fillRect(g, x + width - kBorderWidth + 2, y + size + r.height() / 2, size - 4, r.height() / 2); - } - fillRect(g, x + width - 13, y + size + r.height() / 2, 8, r.height() / 2 - ARROW_H, color1); + fillRect(g, x + width - 13, y + size + ARROW_H, 8, height - 2 * size - 1 - ARROW_H * 2); + y1 += height - 2 * size - ARROW_H - 2; for (int yy = 0; yy < ARROW_H; yy++) { - for (int xx = 0; xx < ARROW_W; xx++) { - if (arrowPixels[ARROW_H - yy - 1][xx] != 0) { - g->hLine(x1 + xx, y1 + yy, x1 + xx, color1); - } else { - g->hLine(x1 + xx, y1 + yy, x1 + xx, color2); - } - } + for (int xx = 0; xx < ARROW_W; xx++) + g->hLine(x1 + xx, y1 + yy, x1 + xx, (arrowPixels[ARROW_H - yy - 1][xx] != 0 ? kColorBlack : kColorWhite)); + } + + if (highlightedPart == kBorderScrollUp || highlightedPart == kBorderScrollDown) { + int rx1 = x + width - kBorderWidth + 2; + int ry1 = y + size + r.height() * scrollPos; + int rx2 = rx1 + size - 4; + int ry2 = ry1 + r.height() * scrollSize; + Common::Rect rr(rx1, ry1, rx2, ry2); + + Graphics::drawFilledRect(rr, kColorBlack, drawPixelInverted, g); } } if (closeable) { @@ -619,7 +635,11 @@ void Gui::mouseDown(int x, int y) { } else if (_consoleTextArea.contains(x, y)) { startMarking(x, y); } else if ((borderClick = isInBorder(_consoleTextArea, x, y)) != kBorderNone) { - paintBorder(&_screen, _consoleTextArea, kWindowConsole, borderClick); + int textFullSize = _lines.size() * _consoleLineHeight + _consoleTextArea.height(); + float scrollPos = (float)_scrollPos / textFullSize; + float scrollSize = (float)_consoleTextArea.height() / textFullSize; + + paintBorder(&_screen, _consoleTextArea, kWindowConsole, borderClick, scrollPos, scrollSize); } } diff --git a/engines/wage/gui.h b/engines/wage/gui.h index 73814d39b4..48ec41c30a 100644 --- a/engines/wage/gui.h +++ b/engines/wage/gui.h @@ -121,9 +121,12 @@ public: void enableNewGameMenus(); private: + void drawScene(); + void drawConsole(); void undrawCursor(); void drawDesktop(); - void paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType, int highlightedPart = kBorderNone); + void paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType, int highlightedPart = kBorderNone, + float scrollPos = 0.0, float scrollSize = 0.0); void renderConsole(Graphics::Surface *g, Common::Rect &r); void drawBox(Graphics::Surface *g, int x, int y, int w, int h); void fillRect(Graphics::Surface *g, int x, int y, int w, int h, int color = kColorBlack); diff --git a/engines/wage/script.cpp b/engines/wage/script.cpp index 294c08ed82..61336dce88 100644 --- a/engines/wage/script.cpp +++ b/engines/wage/script.cpp @@ -1099,7 +1099,7 @@ struct Mapping { { "\?\?\?(0xf5)", OPCODE }, { "\?\?\?(0xf6)", OPCODE }, { "\?\?\?(0xf7)", OPCODE }, - { "\?\?\?(0xf8)", OPCODE }, // 0xa8 + { "\?\?\?(0xf8)", OPCODE }, // 0xf8 { "\?\?\?(0xf9)", OPCODE }, { "\?\?\?(0xfa)", OPCODE }, { "\?\?\?(0xfb)", OPCODE }, diff --git a/engines/wage/util.cpp b/engines/wage/util.cpp index f31a83ca04..8c8af6652e 100644 --- a/engines/wage/util.cpp +++ b/engines/wage/util.cpp @@ -122,4 +122,17 @@ const char *getGenderSpecificPronoun(int gender, bool capitalize) { return capitalize ? "It" : "it"; } +bool isStorageScene(const Common::String &name) { + if (name.equalsIgnoreCase(STORAGESCENE)) + return true; + + if (name.equalsIgnoreCase("STROAGE@")) // Jumble + return true; + + if (name.equalsIgnoreCase("STORAGE@@")) // Jumble + return true; + + return false; +} + } // End of namespace Wage diff --git a/engines/wage/wage.cpp b/engines/wage/wage.cpp index e0299c8da2..aa480b63fe 100644 --- a/engines/wage/wage.cpp +++ b/engines/wage/wage.cpp @@ -281,15 +281,16 @@ void WageEngine::performInitialSetup() { debug(5, "Resetting Owners: %d", _world->_orderedObjs.size()); for (uint i = 0; i < _world->_orderedObjs.size(); i++) { Obj *obj = _world->_orderedObjs[i]; - if (!obj->_sceneOrOwner.equalsIgnoreCase(STORAGESCENE)) { + if (!isStorageScene(obj->_sceneOrOwner)) { Common::String location = obj->_sceneOrOwner; location.toLowercase(); - if (_world->_scenes.contains(location)) { - _world->move(obj, _world->_scenes[location]); + Scene *scene = getSceneByName(location); + if (scene != NULL) { + _world->move(obj, scene); } else { if (!_world->_chrs.contains(location)) { // Note: PLAYER@ is not a valid target here. - warning("Couldn't move %s to %s", obj->_name.c_str(), obj->_sceneOrOwner.c_str()); + warning("Couldn't move %s to \"%s\"", obj->_name.c_str(), obj->_sceneOrOwner.c_str()); } else { // TODO: Add check for max items. _world->move(obj, _world->_chrs[location]); @@ -301,7 +302,7 @@ void WageEngine::performInitialSetup() { bool playerPlaced = false; for (uint i = 0; i < _world->_orderedChrs.size(); i++) { Chr *chr = _world->_orderedChrs[i]; - if (!chr->_initialScene.equalsIgnoreCase(STORAGESCENE)) { + if (!isStorageScene(chr->_initialScene)) { Common::String key = chr->_initialScene; key.toLowercase(); if (_world->_scenes.contains(key) && _world->_scenes[key] != NULL) { @@ -328,13 +329,14 @@ void WageEngine::doClose() { } Scene *WageEngine::getSceneByName(Common::String &location) { - Scene *scene; if (location.equals("random@")) { - scene = _world->getRandomScene(); + return _world->getRandomScene(); } else { - scene = _world->_scenes[location]; + if (_world->_scenes.contains(location)) + return _world->_scenes[location]; + else + return NULL; } - return scene; } void WageEngine::onMove(Designed *what, Designed *from, Designed *to) { diff --git a/engines/wage/wage.h b/engines/wage/wage.h index 8ca306aea3..a0be7a70a9 100644 --- a/engines/wage/wage.h +++ b/engines/wage/wage.h @@ -75,6 +75,8 @@ typedef Common::Array<Chr *> ChrArray; typedef Common::List<Obj *> ObjList; typedef Common::List<Chr *> ChrList; +#define STORAGESCENE "STORAGE@" + enum OperandType { OBJ = 0, CHR = 1, @@ -113,7 +115,7 @@ Common::Rect *readRect(Common::SeekableReadStream *in); const char *getIndefiniteArticle(const Common::String &word); const char *prependGenderSpecificPronoun(int gender); const char *getGenderSpecificPronoun(int gender, bool capitalize); - +bool isStorageScene(const Common::String &name); typedef Common::Array<byte *> Patterns; diff --git a/engines/wage/world.cpp b/engines/wage/world.cpp index 40b1555e35..7e7bc33712 100644 --- a/engines/wage/world.cpp +++ b/engines/wage/world.cpp @@ -105,6 +105,18 @@ bool World::loadWorld(Common::MacResManager *resMan) { Common::SeekableReadStream *res; Common::MacResIDArray::const_iterator iter; + // Dumping interpreter code +#if 1 + res = resMan->getResource(MKTAG('C','O','D','E'), 1); + warning("code size: %d", res->size()); + byte *buf = (byte *)malloc(res->size()); + res->read(buf, res->size()); + Common::DumpFile out; + out.open("code.bin"); + out.write(buf, res->size()); + out.close(); +#endif + if ((resArray = resMan->getResIDArray(MKTAG('G','C','O','D'))).size() == 0) return false; @@ -421,7 +433,7 @@ static bool objComparator(const Obj *o1, const Obj *o2) { if (o1Immobile == o2Immobile) { return o1->_index - o2->_index; } - return o1Immobile; + return o1Immobile ? -1 : 1; } void World::move(Obj *obj, Scene *scene, bool skipSort) { diff --git a/engines/wage/world.h b/engines/wage/world.h index e9041139df..355d660c8d 100644 --- a/engines/wage/world.h +++ b/engines/wage/world.h @@ -50,8 +50,6 @@ namespace Wage { -#define STORAGESCENE "STORAGE@" - class Sound; class World { diff --git a/engines/wintermute/base/base_persistence_manager.cpp b/engines/wintermute/base/base_persistence_manager.cpp index 39462f7a15..5a694e7ce2 100644 --- a/engines/wintermute/base/base_persistence_manager.cpp +++ b/engines/wintermute/base/base_persistence_manager.cpp @@ -183,7 +183,7 @@ void BasePersistenceManager::getSaveStateDesc(int slot, SaveStateDescriptor &des } } - desc.setSaveDate(_savedTimestamp.tm_year, _savedTimestamp.tm_mon, _savedTimestamp.tm_mday); + desc.setSaveDate(_savedTimestamp.tm_year + 1900, _savedTimestamp.tm_mon + 1, _savedTimestamp.tm_mday); desc.setSaveTime(_savedTimestamp.tm_hour, _savedTimestamp.tm_min); desc.setPlayTime(0); } |