diff options
Diffstat (limited to 'engines/adl')
-rw-r--r-- | engines/adl/POTFILES | 1 | ||||
-rw-r--r-- | engines/adl/adl.cpp | 1068 | ||||
-rw-r--r-- | engines/adl/adl.h | 324 | ||||
-rw-r--r-- | engines/adl/adl_v2.cpp | 539 | ||||
-rw-r--r-- | engines/adl/adl_v2.h | 95 | ||||
-rw-r--r-- | engines/adl/adl_v3.cpp | 259 | ||||
-rw-r--r-- | engines/adl/adl_v3.h | 76 | ||||
-rw-r--r-- | engines/adl/console.cpp | 333 | ||||
-rw-r--r-- | engines/adl/console.h | 65 | ||||
-rw-r--r-- | engines/adl/detection.cpp | 99 | ||||
-rw-r--r-- | engines/adl/detection.h | 5 | ||||
-rw-r--r-- | engines/adl/disk.cpp | 460 | ||||
-rw-r--r-- | engines/adl/disk.h | 188 | ||||
-rw-r--r-- | engines/adl/display.cpp | 63 | ||||
-rw-r--r-- | engines/adl/display.h | 7 | ||||
-rw-r--r-- | engines/adl/graphics.cpp | 69 | ||||
-rw-r--r-- | engines/adl/graphics.h | 76 | ||||
-rw-r--r-- | engines/adl/graphics_v1.cpp | 120 | ||||
-rw-r--r-- | engines/adl/graphics_v2.cpp | 304 | ||||
-rw-r--r-- | engines/adl/hires1.cpp | 352 | ||||
-rw-r--r-- | engines/adl/hires1.h | 47 | ||||
-rw-r--r-- | engines/adl/hires2.cpp | 183 | ||||
-rw-r--r-- | engines/adl/hires2.h | 66 | ||||
-rw-r--r-- | engines/adl/hires6.cpp | 444 | ||||
-rw-r--r-- | engines/adl/hires6.h | 92 | ||||
-rw-r--r-- | engines/adl/module.mk | 12 | ||||
-rw-r--r-- | engines/adl/speaker.cpp | 94 | ||||
-rw-r--r-- | engines/adl/speaker.h | 49 |
28 files changed, 4776 insertions, 714 deletions
diff --git a/engines/adl/POTFILES b/engines/adl/POTFILES new file mode 100644 index 0000000000..ca485932f7 --- /dev/null +++ b/engines/adl/POTFILES @@ -0,0 +1 @@ +engines/adl/detection.cpp diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp index 1ab74c3cf6..b6af54962e 100644 --- a/engines/adl/adl.cpp +++ b/engines/adl/adl.cpp @@ -38,25 +38,61 @@ #include "adl/adl.h" #include "adl/display.h" #include "adl/detection.h" +#include "adl/graphics.h" +#include "adl/speaker.h" namespace Adl { AdlEngine::~AdlEngine() { delete _display; + delete _graphics; + delete _speaker; + delete _console; + delete _dumpFile; } AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) : Engine(syst), + _dumpFile(nullptr), _display(nullptr), - _gameDescription(gd), + _graphics(nullptr), _isRestarting(false), _isRestoring(false), + _skipOneCommand(false), + _gameDescription(gd), _saveVerb(0), _saveNoun(0), _restoreVerb(0), _restoreNoun(0), _canSaveNow(false), _canRestoreNow(false) { + + DebugMan.addDebugChannel(kDebugChannelScript, "Script", "Trace script execution"); +} + +bool AdlEngine::pollEvent(Common::Event &event) const { + _console->onFrame(); + + if (g_system->getEventManager()->pollEvent(event)) { + if (event.type != Common::EVENT_KEYDOWN) + return false; + + if (event.kbd.flags & Common::KBD_CTRL) { + if (event.kbd.keycode == Common::KEYCODE_q) { + quitGame(); + return false; + } + + if (event.kbd.keycode == Common::KEYCODE_d) { + _console->attach(); + return false; + } + } + + return true; + } + + return false; } Common::String AdlEngine::readString(Common::ReadStream &stream, byte until) const { @@ -82,33 +118,28 @@ Common::String AdlEngine::readStringAt(Common::SeekableReadStream &stream, uint return readString(stream, until); } -void AdlEngine::printMessage(uint idx, bool wait) const { - Common::String msg = _messages[idx - 1]; - wordWrap(msg); - _display->printString(msg); +void AdlEngine::openFile(Common::File &file, const Common::String &name) const { + if (!file.open(name)) + error("Error opening '%s'", name.c_str()); +} - if (wait) - delay(14 * 166018 / 1000); +void AdlEngine::printMessage(uint idx) { + printString(loadMessage(idx)); } -void AdlEngine::delay(uint32 ms) const { - Common::EventManager *ev = g_system->getEventManager(); +Common::String AdlEngine::getItemDescription(const Item &item) const { + if (item.description > 0) + return loadMessage(item.description); + else + return Common::String(); +} +void AdlEngine::delay(uint32 ms) const { uint32 start = g_system->getMillis(); - while (!g_engine->shouldQuit() && g_system->getMillis() - start < ms) { + while (!shouldQuit() && g_system->getMillis() - start < ms) { Common::Event event; - if (ev->pollEvent(event)) { - if (event.type == Common::EVENT_KEYDOWN && (event.kbd.flags & Common::KBD_CTRL)) { - switch(event.kbd.keycode) { - case Common::KEYCODE_q: - g_engine->quitGame(); - break; - default: - break; - } - } - } + pollEvent(event); g_system->delayMillis(16); } } @@ -122,7 +153,7 @@ Common::String AdlEngine::inputString(byte prompt) const { while (1) { byte b = inputKey(); - if (g_engine->shouldQuit() || _isRestoring) + if (shouldQuit() || _isRestoring) return 0; if (b == 0) @@ -153,25 +184,18 @@ Common::String AdlEngine::inputString(byte prompt) const { } } -byte AdlEngine::inputKey() const { - Common::EventManager *ev = g_system->getEventManager(); - +byte AdlEngine::inputKey(bool showCursor) const { byte key = 0; - _display->showCursor(true); + if (showCursor) + _display->showCursor(true); - while (!g_engine->shouldQuit() && !_isRestoring && key == 0) { + while (!shouldQuit() && !_isRestoring && key == 0) { Common::Event event; - if (ev->pollEvent(event)) { + if (pollEvent(event)) { if (event.type != Common::EVENT_KEYDOWN) continue; - if (event.kbd.flags & Common::KBD_CTRL) { - if (event.kbd.keycode == Common::KEYCODE_q) - g_engine->quitGame(); - continue; - } - switch (event.kbd.keycode) { case Common::KEYCODE_BACKSPACE: case Common::KEYCODE_RETURN: @@ -192,9 +216,12 @@ byte AdlEngine::inputKey() const { return key; } -void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { +void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri) const { uint index = 0; + map.clear(); + pri.clear(); + while (1) { ++index; @@ -208,6 +235,8 @@ void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { if (!map.contains(word)) map[word] = index; + pri.push_back(Console::toAscii(word)); + byte synonyms = stream.readByte(); if (stream.err() || stream.eos()) @@ -229,6 +258,8 @@ void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const { } void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) { + commands.clear(); + while (1) { Command command; command.room = stream.readByte(); @@ -264,10 +295,238 @@ void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) { } } +void AdlEngine::checkInput(byte verb, byte noun) { + // Try room-local command list first + if (doOneCommand(_roomData.commands, verb, noun)) + return; + + // If no match was found, try the global list + if (!doOneCommand(_roomCommands, verb, noun)) + printMessage(_messageIds.dontUnderstand); +} + +bool AdlEngine::isInputValid(byte verb, byte noun, bool &is_any) { + if (isInputValid(_roomData.commands, verb, noun, is_any)) + return true; + return isInputValid(_roomCommands, verb, noun, is_any); +} + +bool AdlEngine::isInputValid(const Commands &commands, byte verb, byte noun, bool &is_any) { + Commands::const_iterator cmd; + + is_any = false; + for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { + ScriptEnv env(*cmd, _state.room, verb, noun); + if (matchCommand(env)) { + if (cmd->verb == IDI_ANY || cmd->noun == IDI_ANY) + is_any = true; + return true; + } + } + + return false; +} + +typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine> OpcodeV1; +#define SetOpcodeTable(x) table = &x; +#define Opcode(x) table->push_back(new OpcodeV1(this, &AdlEngine::x)) +#define OpcodeUnImpl() table->push_back(new OpcodeV1(this, 0)) + +void AdlEngine::setupOpcodeTables() { + Common::Array<const Opcode *> *table = 0; + + SetOpcodeTable(_condOpcodes); + // 0x00 + OpcodeUnImpl(); + OpcodeUnImpl(); + OpcodeUnImpl(); + Opcode(o1_isItemInRoom); + // 0x04 + OpcodeUnImpl(); + Opcode(o1_isMovesGT); + Opcode(o1_isVarEQ); + OpcodeUnImpl(); + // 0x08 + OpcodeUnImpl(); + Opcode(o1_isCurPicEQ); + Opcode(o1_isItemPicEQ); + + SetOpcodeTable(_actOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o1_varAdd); + Opcode(o1_varSub); + Opcode(o1_varSet); + // 0x04 + Opcode(o1_listInv); + Opcode(o1_moveItem); + Opcode(o1_setRoom); + Opcode(o1_setCurPic); + // 0x08 + Opcode(o1_setPic); + Opcode(o1_printMsg); + Opcode(o1_setLight); + Opcode(o1_setDark); + // 0x0c + OpcodeUnImpl(); + Opcode(o1_quit); + OpcodeUnImpl(); + Opcode(o1_save); + // 0x10 + Opcode(o1_restore); + Opcode(o1_restart); + Opcode(o1_placeItem); + Opcode(o1_setItemPic); + // 0x14 + Opcode(o1_resetPic); + Opcode(o1_goDirection<IDI_DIR_NORTH>); + Opcode(o1_goDirection<IDI_DIR_SOUTH>); + Opcode(o1_goDirection<IDI_DIR_EAST>); + // 0x18 + Opcode(o1_goDirection<IDI_DIR_WEST>); + Opcode(o1_goDirection<IDI_DIR_UP>); + Opcode(o1_goDirection<IDI_DIR_DOWN>); + Opcode(o1_takeItem); + // 0x1c + Opcode(o1_dropItem); + Opcode(o1_setRoomPic); +} + +void AdlEngine::initState() { + _state = State(); + + initGameState(); +} + +byte AdlEngine::roomArg(byte room) const { + return room; +} + +void AdlEngine::clearScreen() const { + _display->setMode(DISPLAY_MODE_MIXED); + _display->clear(0x00); +} + +void AdlEngine::drawPic(byte pic, Common::Point pos) const { + if (_roomData.pictures.contains(pic)) + _graphics->drawPic(*_roomData.pictures[pic]->createReadStream(), pos); + else + _graphics->drawPic(*_pictures[pic]->createReadStream(), pos); +} + +void AdlEngine::bell(uint count) const { + _speaker->bell(count); +} + +const Room &AdlEngine::getRoom(uint i) const { + if (i < 1 || i > _state.rooms.size()) + error("Room %i out of range [1, %i]", i, _state.rooms.size()); + + return _state.rooms[i - 1]; +} + +Room &AdlEngine::getRoom(uint i) { + if (i < 1 || i > _state.rooms.size()) + error("Room %i out of range [1, %i]", i, _state.rooms.size()); + + return _state.rooms[i - 1]; +} + +const Room &AdlEngine::getCurRoom() const { + return getRoom(_state.room); +} + +Room &AdlEngine::getCurRoom() { + return getRoom(_state.room); +} + +const Item &AdlEngine::getItem(uint i) const { + Common::List<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->id == i) + return *item; + + error("Item %i not found", i); +} + +Item &AdlEngine::getItem(uint i) { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->id == i) + return *item; + + error("Item %i not found", i); +} + +byte AdlEngine::getVar(uint i) const { + if (i >= _state.vars.size()) + error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); + + return _state.vars[i]; +} + +void AdlEngine::setVar(uint i, byte value) { + if (i >= _state.vars.size()) + error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); + + _state.vars[i] = value; +} + +void AdlEngine::takeItem(byte noun) { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != _state.room) + continue; + + if (item->state == IDI_ITEM_DOESNT_MOVE) { + printMessage(_messageIds.itemDoesntMove); + return; + } + + if (item->state == IDI_ITEM_DROPPED) { + item->room = IDI_ANY; + return; + } + + Common::Array<byte>::const_iterator pic; + for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { + if (*pic == getCurRoom().curPicture) { + item->room = IDI_ANY; + item->state = IDI_ITEM_DROPPED; + return; + } + } + } + + printMessage(_messageIds.itemNotHere); +} + +void AdlEngine::dropItem(byte noun) { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != IDI_ANY) + continue; + + item->room = _state.room; + item->state = IDI_ITEM_DROPPED; + return; + } + + printMessage(_messageIds.dontUnderstand); +} + Common::Error AdlEngine::run() { + _console = new Console(this); + _speaker = new Speaker(); _display = new Display(); - loadData(); + setupOpcodeTables(); + + init(); int saveSlot = ConfMan.getInt("save_slot"); if (saveSlot >= 0) { @@ -278,13 +537,14 @@ Common::Error AdlEngine::run() { } else { runIntro(); initState(); + _display->printAsciiString(_strings.lineFeeds); } _display->setMode(DISPLAY_MODE_MIXED); - _display->printAsciiString("\r\r\r\r\r"); while (1) { uint verb = 0, noun = 0; + _isRestarting = false; // When restoring from the launcher, we don't read // input on the first iteration. This is needed to @@ -292,9 +552,11 @@ Common::Error AdlEngine::run() { // restoring in-game brings us to the same game state. // (Also see comment below.) if (!_isRestoring) { - clearScreen(); showRoom(); + if (_isRestarting) + continue; + _canSaveNow = _canRestoreNow = true; getInput(verb, noun); _canSaveNow = _canRestoreNow = false; @@ -305,8 +567,7 @@ Common::Error AdlEngine::run() { // If we just restored from the GMM, we skip this command // set, as no command has been input by the user if (!_isRestoring) - if (!doOneCommand(_roomCommands, verb, noun)) - printMessage(_messageIds.dontUnderstand); + checkInput(verb, noun); } if (_isRestoring) { @@ -323,12 +584,15 @@ Common::Error AdlEngine::run() { } // Restarting does end command processing - if (_isRestarting) { - _isRestarting = false; + if (_isRestarting) continue; - } doAllCommands(_globalCommands, verb, noun); + + if (_isRestarting) + continue; + + advanceClock(); _state.moves++; } @@ -382,6 +646,8 @@ Common::Error AdlEngine::loadGameState(int slot) { _state.room = inFile->readByte(); _state.moves = inFile->readByte(); _state.isDark = inFile->readByte(); + _state.time.hours = inFile->readByte(); + _state.time.minutes = inFile->readByte(); uint32 size = inFile->readUint32BE(); if (size != _state.rooms.size()) @@ -390,18 +656,20 @@ Common::Error AdlEngine::loadGameState(int slot) { for (uint i = 0; i < size; ++i) { _state.rooms[i].picture = inFile->readByte(); _state.rooms[i].curPicture = inFile->readByte(); + _state.rooms[i].isFirstTime = inFile->readByte(); } size = inFile->readUint32BE(); if (size != _state.items.size()) error("Item count mismatch (expected %i; found %i)", _state.items.size(), size); - for (uint i = 0; i < size; ++i) { - _state.items[i].room = inFile->readByte(); - _state.items[i].picture = inFile->readByte(); - _state.items[i].position.x = inFile->readByte(); - _state.items[i].position.y = inFile->readByte(); - _state.items[i].state = inFile->readByte(); + Common::List<Item>::iterator item; + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + item->room = inFile->readByte(); + item->picture = inFile->readByte(); + item->position.x = inFile->readByte(); + item->position.y = inFile->readByte(); + item->state = inFile->readByte(); } size = inFile->readUint32BE(); @@ -467,20 +735,24 @@ Common::Error AdlEngine::saveGameState(int slot, const Common::String &desc) { outFile->writeByte(_state.room); outFile->writeByte(_state.moves); outFile->writeByte(_state.isDark); + outFile->writeByte(_state.time.hours); + outFile->writeByte(_state.time.minutes); outFile->writeUint32BE(_state.rooms.size()); for (uint i = 0; i < _state.rooms.size(); ++i) { outFile->writeByte(_state.rooms[i].picture); outFile->writeByte(_state.rooms[i].curPicture); + outFile->writeByte(_state.rooms[i].isFirstTime); } outFile->writeUint32BE(_state.items.size()); - for (uint i = 0; i < _state.items.size(); ++i) { - outFile->writeByte(_state.items[i].room); - outFile->writeByte(_state.items[i].picture); - outFile->writeByte(_state.items[i].position.x); - outFile->writeByte(_state.items[i].position.y); - outFile->writeByte(_state.items[i].state); + Common::List<Item>::const_iterator item; + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + outFile->writeByte(item->room); + outFile->writeByte(item->picture); + outFile->writeByte(item->position.x); + outFile->writeByte(item->position.y); + outFile->writeByte(item->state); } outFile->writeUint32BE(_state.vars.size()); @@ -509,29 +781,14 @@ bool AdlEngine::canSaveGameStateCurrently() { // "SAVE GAME". This prevents saving via the GMM in situations where // it wouldn't otherwise be possible to do so. for (cmd = _roomCommands.begin(); cmd != _roomCommands.end(); ++cmd) { - uint offset; - if (matchCommand(*cmd, _saveVerb, _saveNoun, &offset)) - return cmd->script[offset] == IDO_ACT_SAVE; + ScriptEnv env(*cmd, _state.room, _saveVerb, _saveNoun); + if (matchCommand(env)) + return env.op() == IDO_ACT_SAVE; } return false; } -void AdlEngine::wordWrap(Common::String &str) const { - uint end = 39; - - while (1) { - if (str.size() <= end) - return; - - while (str[end] != APPLECHAR(' ')) - --end; - - str.setChar(APPLECHAR('\r'), end); - end += 40; - } -} - byte AdlEngine::convertKey(uint16 ascii) const { ascii = toupper(ascii); @@ -598,6 +855,22 @@ Common::String AdlEngine::getWord(const Common::String &line, uint &index) const } } +Common::String AdlEngine::formatVerbError(const Common::String &verb) const { + Common::String err = _strings.verbError; + for (uint i = 0; i < verb.size(); ++i) + err.setChar(verb[i], i + 19); + return err; +} + +Common::String AdlEngine::formatNounError(const Common::String &verb, const Common::String &noun) const { + Common::String err = _strings.nounError; + for (uint i = 0; i < verb.size(); ++i) + err.setChar(verb[i], i + 19); + for (uint i = 0; i < noun.size(); ++i) + err.setChar(noun[i], i + 30); + return err; +} + void AdlEngine::getInput(uint &verb, uint &noun) { while (1) { _display->printString(_strings.enterCommand); @@ -607,421 +880,349 @@ void AdlEngine::getInput(uint &verb, uint &noun) { return; uint index = 0; - Common::String verbStr = getWord(line, index); + Common::String verbString = getWord(line, index); - if (!_verbs.contains(verbStr)) { - Common::String err = _strings.verbError; - for (uint i = 0; i < verbStr.size(); ++i) - err.setChar(verbStr[i], i + 19); - _display->printString(err); + if (!_verbs.contains(verbString)) { + _display->printString(formatVerbError(verbString)); continue; } - verb = _verbs[verbStr]; + verb = _verbs[verbString]; - Common::String nounStr = getWord(line, index); + Common::String nounString = getWord(line, index); - if (!_nouns.contains(nounStr)) { - Common::String err = _strings.nounError; - for (uint i = 0; i < verbStr.size(); ++i) - err.setChar(verbStr[i], i + 19); - for (uint i = 0; i < nounStr.size(); ++i) - err.setChar(nounStr[i], i + 30); - _display->printString(err); + if (!_nouns.contains(nounString)) { + _display->printString(formatNounError(verbString, nounString)); continue; } - noun = _nouns[nounStr]; + noun = _nouns[nounString]; return; } } -void AdlEngine::showRoom() const { - if (!_state.isDark) { - drawPic(getCurRoom().curPicture); - drawItems(); +bool AdlEngine::op_debug(const char *fmt, ...) const { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) { + va_list va; + va_start(va, fmt); + Common::String output = Common::String::vformat(fmt, va); + va_end(va); + + output += '\n'; + if (_dumpFile) { + _dumpFile->write(output.c_str(), output.size()); + return true; + } else + debugN("%s", output.c_str()); } - _display->updateHiResScreen(); - printMessage(getCurRoom().description, false); + return false; } -void AdlEngine::clearScreen() const { - _display->setMode(DISPLAY_MODE_MIXED); - _display->clear(0x00); +int AdlEngine::o1_isItemInRoom(ScriptEnv &e) { + OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + if (getItem(e.arg(1)).room == roomArg(e.arg(2))) + return 2; + + return -1; } -void AdlEngine::drawItems() const { - Common::Array<Item>::const_iterator item; +int AdlEngine::o1_isMovesGT(ScriptEnv &e) { + OP_DEBUG_1("\t&& MOVES > %d", e.arg(1)); - uint dropped = 0; + if (_state.moves > e.arg(1)) + return 1; - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->room != _state.room) - continue; + return -1; +} - if (item->state == IDI_ITEM_MOVED) { - if (getCurRoom().picture == getCurRoom().curPicture) { - const Common::Point &p = _itemOffsets[dropped]; - if (item->isLineArt) - drawLineArt(_lineArt[item->picture - 1], p); - else - drawPic(item->picture, p); - ++dropped; - } - continue; - } +int AdlEngine::o1_isVarEQ(ScriptEnv &e) { + OP_DEBUG_2("\t&& VARS[%d] == %d", e.arg(1), e.arg(2)); - Common::Array<byte>::const_iterator pic; + if (getVar(e.arg(1)) == e.arg(2)) + return 2; - for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { - if (*pic == getCurRoom().curPicture) { - if (item->isLineArt) - drawLineArt(_lineArt[item->picture - 1], item->position); - else - drawPic(item->picture, item->position); - continue; - } - } - } + return -1; } -void AdlEngine::drawNextPixel(Common::Point &p, byte color, byte bits, byte quadrant) const { - if (bits & 4) - _display->putPixel(p, color); +int AdlEngine::o1_isCurPicEQ(ScriptEnv &e) { + OP_DEBUG_1("\t&& GET_CURPIC() == %d", e.arg(1)); - bits += quadrant; + if (getCurRoom().curPicture == e.arg(1)) + return 1; - if (bits & 1) - p.x += (bits & 2 ? -1 : 1); - else - p.y += (bits & 2 ? 1 : -1); + return -1; } -void AdlEngine::drawLineArt(const Common::Array<byte> &lineArt, const Common::Point &pos, byte rotation, byte scaling, byte color) const { - const byte stepping[] = { - 0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5, - 0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18, - 0xff - }; +int AdlEngine::o1_isItemPicEQ(ScriptEnv &e) { + OP_DEBUG_2("\t&& GET_ITEM_PIC(%s) == %d", itemStr(e.arg(1)).c_str(), e.arg(2)); - byte quadrant = rotation >> 4; - rotation &= 0xf; - byte xStep = stepping[rotation]; - byte yStep = stepping[(rotation ^ 0xf) + 1] + 1; - - Common::Point p(pos); - - for (uint i = 0; i < lineArt.size(); ++i) { - byte b = lineArt[i]; - - do { - byte xFrac = 0x80; - byte yFrac = 0x80; - for (uint j = 0; j < scaling; ++j) { - if (xFrac + xStep + 1 > 255) - drawNextPixel(p, color, b, quadrant); - xFrac += xStep + 1; - if (yFrac + yStep > 255) - drawNextPixel(p, color, b, quadrant + 1); - yFrac += yStep; - } - b >>= 3; - } while (b != 0); - } + if (getItem(e.arg(1)).picture == e.arg(2)) + return 2; + + return -1; } -const Room &AdlEngine::getRoom(uint i) const { - if (i < 1 || i > _state.rooms.size()) - error("Room %i out of range [1, %i]", i, _state.rooms.size()); +int AdlEngine::o1_varAdd(ScriptEnv &e) { + OP_DEBUG_2("\tVARS[%d] += %d", e.arg(2), e.arg(1)); - return _state.rooms[i - 1]; + setVar(e.arg(2), getVar(e.arg(2) + e.arg(1))); + return 2; } -Room &AdlEngine::getRoom(uint i) { - if (i < 1 || i > _state.rooms.size()) - error("Room %i out of range [1, %i]", i, _state.rooms.size()); +int AdlEngine::o1_varSub(ScriptEnv &e) { + OP_DEBUG_2("\tVARS[%d] -= %d", e.arg(2), e.arg(1)); - return _state.rooms[i - 1]; + setVar(e.arg(2), getVar(e.arg(2)) - e.arg(1)); + return 2; } -const Room &AdlEngine::getCurRoom() const { - return getRoom(_state.room); +int AdlEngine::o1_varSet(ScriptEnv &e) { + OP_DEBUG_2("\tVARS[%d] = %d", e.arg(1), e.arg(2)); + + setVar(e.arg(1), e.arg(2)); + return 2; } -Room &AdlEngine::getCurRoom() { - return getRoom(_state.room); +int AdlEngine::o1_listInv(ScriptEnv &e) { + OP_DEBUG_0("\tLIST_INVENTORY()"); + + Common::List<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == IDI_ANY) + printMessage(item->description); + + return 0; } -const Item &AdlEngine::getItem(uint i) const { - if (i < 1 || i > _state.items.size()) - error("Item %i out of range [1, %i]", i, _state.items.size()); +int AdlEngine::o1_moveItem(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); - return _state.items[i - 1]; + getItem(e.arg(1)).room = e.arg(2); + return 2; } -Item &AdlEngine::getItem(uint i) { - if (i < 1 || i > _state.items.size()) - error("Item %i out of range [1, %i]", i, _state.items.size()); +int AdlEngine::o1_setRoom(ScriptEnv &e) { + OP_DEBUG_1("\tROOM = %d", e.arg(1)); - return _state.items[i - 1]; + getCurRoom().curPicture = getCurRoom().picture; + _state.room = e.arg(1); + return 1; } -byte AdlEngine::getVar(uint i) const { - if (i >= _state.vars.size()) - error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); +int AdlEngine::o1_setCurPic(ScriptEnv &e) { + OP_DEBUG_1("\tSET_CURPIC(%d)", e.arg(1)); - return _state.vars[i]; + getCurRoom().curPicture = e.arg(1); + return 1; } -void AdlEngine::setVar(uint i, byte value) { - if (i >= _state.vars.size()) - error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1); +int AdlEngine::o1_setPic(ScriptEnv &e) { + OP_DEBUG_1("\tSET_PIC(%d)", e.arg(1)); - _state.vars[i] = value; + getCurRoom().picture = getCurRoom().curPicture = e.arg(1); + return 1; } -void AdlEngine::takeItem(byte noun) { - Common::Array<Item>::iterator item; +int AdlEngine::o1_printMsg(ScriptEnv &e) { + OP_DEBUG_1("\tPRINT(%s)", msgStr(e.arg(1)).c_str()); - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != _state.room) - continue; + printMessage(e.arg(1)); + return 1; +} - if (item->state == IDI_ITEM_DOESNT_MOVE) { - printMessage(_messageIds.itemDoesntMove); - return; - } +int AdlEngine::o1_setLight(ScriptEnv &e) { + OP_DEBUG_0("\tLIGHT()"); - if (item->state == IDI_ITEM_MOVED) { - item->room = IDI_NONE; - return; - } + _state.isDark = false; + return 0; +} - Common::Array<byte>::const_iterator pic; - for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { - if (*pic == getCurRoom().curPicture) { - item->room = IDI_NONE; - item->state = IDI_ITEM_MOVED; - return; - } - } +int AdlEngine::o1_setDark(ScriptEnv &e) { + OP_DEBUG_0("\tDARK()"); + + _state.isDark = true; + return 0; +} + +int AdlEngine::o1_save(ScriptEnv &e) { + OP_DEBUG_0("\tSAVE_GAME()"); + + saveGameState(0, ""); + return 0; +} + +int AdlEngine::o1_restore(ScriptEnv &e) { + OP_DEBUG_0("\tRESTORE_GAME()"); + + loadGameState(0); + _isRestoring = false; + return 0; +} + +int AdlEngine::o1_restart(ScriptEnv &e) { + OP_DEBUG_0("\tRESTART_GAME()"); + + _display->printString(_strings.playAgain); + Common::String input = inputString(); + + if (input.size() == 0 || input[0] != APPLECHAR('N')) { + _isRestarting = true; + _display->clear(0x00); + _display->updateHiResScreen(); + _display->printString(_strings.pressReturn); + initState(); + _display->printAsciiString(_strings.lineFeeds); + return -1; } - printMessage(_messageIds.itemNotHere); + return o1_quit(e); } -void AdlEngine::dropItem(byte noun) { - Common::Array<Item>::iterator item; +int AdlEngine::o1_quit(ScriptEnv &e) { + OP_DEBUG_0("\tQUIT_GAME()"); - for (item = _state.items.begin(); item != _state.items.end(); ++item) { - if (item->noun != noun || item->room != IDI_NONE) - continue; + printMessage(_messageIds.thanksForPlaying); + quitGame(); + return -1; +} - item->room = _state.room; - item->state = IDI_ITEM_MOVED; - return; +int AdlEngine::o1_placeItem(ScriptEnv &e) { + OP_DEBUG_4("\tPLACE_ITEM(%s, %s, (%d, %d))", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str(), e.arg(3), e.arg(4)); + + Item &item = getItem(e.arg(1)); + + item.room = roomArg(e.arg(2)); + item.position.x = e.arg(3); + item.position.y = e.arg(4); + return 4; +} + +int AdlEngine::o1_setItemPic(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ITEM_PIC(%s, %d)", itemStr(e.arg(2)).c_str(), e.arg(1)); + + getItem(e.arg(2)).picture = e.arg(1); + return 2; +} + +int AdlEngine::o1_resetPic(ScriptEnv &e) { + OP_DEBUG_0("\tRESET_PIC()"); + + getCurRoom().curPicture = getCurRoom().picture; + return 0; +} + +template <Direction D> +int AdlEngine::o1_goDirection(ScriptEnv &e) { + OP_DEBUG_0((Common::String("\tGO_") + dirStr(D) + "()").c_str()); + + byte room = getCurRoom().connections[D]; + + if (room == 0) { + printMessage(_messageIds.cantGoThere); + return -1; } - printMessage(_messageIds.dontUnderstand); + getCurRoom().curPicture = getCurRoom().picture; + _state.room = room; + return -1; } -#define ARG(N) (command.script[offset + (N)]) +int AdlEngine::o1_takeItem(ScriptEnv &e) { + OP_DEBUG_0("\tTAKE_ITEM()"); -bool AdlEngine::matchCommand(const Command &command, byte verb, byte noun, uint *actions) const { - if (command.room != IDI_NONE && command.room != _state.room) - return false; + takeItem(e.getNoun()); + return 0; +} - if (command.verb != IDI_NONE && command.verb != verb) - return false; +int AdlEngine::o1_dropItem(ScriptEnv &e) { + OP_DEBUG_0("\tDROP_ITEM()"); + + dropItem(e.getNoun()); + return 0; +} + +int AdlEngine::o1_setRoomPic(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ROOM_PIC(%d, %d)", e.arg(1), e.arg(2)); - if (command.noun != IDI_NONE && command.noun != noun) + getRoom(e.arg(1)).picture = getRoom(e.arg(1)).curPicture = e.arg(2); + return 2; +} + +bool AdlEngine::matchCommand(ScriptEnv &env) const { + if (!env.isMatch() && !_dumpFile) return false; - uint offset = 0; - for (uint i = 0; i < command.numCond; ++i) { - switch (ARG(0)) { - case IDO_CND_ITEM_IN_ROOM: - if (getItem(ARG(1)).room != ARG(2)) - return false; - offset += 3; - break; - case IDO_CND_MOVES_GE: - if (ARG(1) > _state.moves) - return false; - offset += 2; - break; - case IDO_CND_VAR_EQ: - if (getVar(ARG(1)) != ARG(2)) - return false; - offset += 3; - break; - case IDO_CND_CUR_PIC_EQ: - if (getCurRoom().curPicture != ARG(1)) - return false; - offset += 2; - break; - case IDO_CND_ITEM_PIC_EQ: - if (getItem(ARG(1)).picture != ARG(2)) - return false; - offset += 3; - break; - default: - error("Invalid condition opcode %02x", command.script[offset]); - } + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) { + op_debug("IF\n\tROOM == %s", roomStr(env.getCommand().room).c_str()); + op_debug("\t&& SAID(%s, %s)", verbStr(env.getCommand().verb).c_str(), nounStr(env.getCommand().noun).c_str()); } - if (actions) - *actions = offset; + for (uint i = 0; i < env.getCondCount(); ++i) { + byte op = env.op(); + + if (op >= _condOpcodes.size() || !_condOpcodes[op] || !_condOpcodes[op]->isValid()) + error("Unimplemented condition opcode %02x", op); + + int numArgs = (*_condOpcodes[op])(env); + + if (numArgs < 0) { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("FAIL\n"); + return false; + } + + env.skip(numArgs + 1); + } return true; } -void AdlEngine::doActions(const Command &command, byte noun, byte offset) { - for (uint i = 0; i < command.numAct; ++i) { - switch (ARG(0)) { - case IDO_ACT_VAR_ADD: - setVar(ARG(2), getVar(ARG(2) + ARG(1))); - offset += 3; - break; - case IDO_ACT_VAR_SUB: - setVar(ARG(2), getVar(ARG(2)) - ARG(1)); - offset += 3; - break; - case IDO_ACT_VAR_SET: - setVar(ARG(1), ARG(2)); - offset += 3; - break; - case IDO_ACT_LIST_ITEMS: { - Common::Array<Item>::const_iterator item; +void AdlEngine::doActions(ScriptEnv &env) { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("THEN"); - for (item = _state.items.begin(); item != _state.items.end(); ++item) - if (item->room == IDI_NONE) - printMessage(item->description); + for (uint i = 0; i < env.getActCount(); ++i) { + byte op = env.op(); - ++offset; - break; - } - case IDO_ACT_MOVE_ITEM: - getItem(ARG(1)).room = ARG(2); - offset += 3; - break; - case IDO_ACT_SET_ROOM: - getCurRoom().curPicture = getCurRoom().picture; - _state.room = ARG(1); - offset += 2; - break; - case IDO_ACT_SET_CUR_PIC: - getCurRoom().curPicture = ARG(1); - offset += 2; - break; - case IDO_ACT_SET_PIC: - getCurRoom().picture = getCurRoom().curPicture = ARG(1); - offset += 2; - break; - case IDO_ACT_PRINT_MSG: - printMessage(ARG(1)); - offset += 2; - break; - case IDO_ACT_SET_LIGHT: - _state.isDark = false; - ++offset; - break; - case IDO_ACT_SET_DARK: - _state.isDark = true; - ++offset; - break; - case IDO_ACT_SAVE: - saveGameState(0, ""); - ++offset; - break; - case IDO_ACT_LOAD: - loadGameState(0); - ++offset; - // Original engine does not jump out of the loop, - // so we don't either. - // We reset the restore flag, as the restore game - // process is complete - _isRestoring = false; - break; - case IDO_ACT_RESTART: { - _display->printString(_strings.playAgain); - Common::String input = inputString(); - if (input.size() == 0 || input[0] != APPLECHAR('N')) { - _isRestarting = true; - _display->clear(0x00); - _display->updateHiResScreen(); - restartGame(); - return; - } - // Fall-through - } - case IDO_ACT_QUIT: - printMessage(_messageIds.thanksForPlaying); - quitGame(); - return; - case IDO_ACT_PLACE_ITEM: - getItem(ARG(1)).room = ARG(2); - getItem(ARG(1)).position.x = ARG(3); - getItem(ARG(1)).position.y = ARG(4); - offset += 5; - break; - case IDO_ACT_SET_ITEM_PIC: - getItem(ARG(2)).picture = ARG(1); - offset += 3; - break; - case IDO_ACT_RESET_PIC: - getCurRoom().curPicture = getCurRoom().picture; - ++offset; - break; - case IDO_ACT_GO_NORTH: - case IDO_ACT_GO_SOUTH: - case IDO_ACT_GO_EAST: - case IDO_ACT_GO_WEST: - case IDO_ACT_GO_UP: - case IDO_ACT_GO_DOWN: { - byte room = getCurRoom().connections[ARG(0) - IDO_ACT_GO_NORTH]; - - if (room == 0) { - printMessage(_messageIds.cantGoThere); - return; - } + if (op >= _actOpcodes.size() || !_actOpcodes[op] || !_actOpcodes[op]->isValid()) + error("Unimplemented action opcode %02x", op); + + int numArgs = (*_actOpcodes[op])(env); - getCurRoom().curPicture = getCurRoom().picture; - _state.room = room; + if (numArgs < 0) { + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("ABORT\n"); return; } - case IDO_ACT_TAKE_ITEM: - takeItem(noun); - ++offset; - break; - case IDO_ACT_DROP_ITEM: - dropItem(noun); - ++offset; - break; - case IDO_ACT_SET_ROOM_PIC: - getRoom(ARG(1)).picture = getRoom(ARG(1)).curPicture = ARG(2); - offset += 3; - break; - default: - error("Invalid action opcode %02x", ARG(0)); - } + + env.skip(numArgs + 1); } -} -#undef ARG + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) + op_debug("END\n"); +} bool AdlEngine::doOneCommand(const Commands &commands, byte verb, byte noun) { Commands::const_iterator cmd; for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { - uint offset = 0; - if (matchCommand(*cmd, verb, noun, &offset)) { - doActions(*cmd, noun, offset); + + if (_skipOneCommand) { + _skipOneCommand = false; + continue; + } + + ScriptEnv env(*cmd, _state.room, verb, noun); + if (matchCommand(env)) { + doActions(env); return true; } } + _skipOneCommand = false; return false; } @@ -1029,10 +1230,87 @@ void AdlEngine::doAllCommands(const Commands &commands, byte verb, byte noun) { Commands::const_iterator cmd; for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { - uint offset = 0; - if (matchCommand(*cmd, verb, noun, &offset)) - doActions(*cmd, noun, offset); + if (_skipOneCommand) { + _skipOneCommand = false; + continue; + } + + ScriptEnv env(*cmd, _state.room, verb, noun); + if (matchCommand(env)) { + doActions(env); + // The original long jumps on restart, so we need to abort here + if (_isRestarting) + return; + } + } + + _skipOneCommand = false; +} + +Common::String AdlEngine::toAscii(const Common::String &str) { + Common::String ascii = Console::toAscii(str); + if (ascii.lastChar() == '\r') + ascii.deleteLastChar(); + // FIXME: remove '\r's inside string? + return ascii; +} + +Common::String AdlEngine::itemStr(uint i) const { + byte desc = getItem(i).description; + byte noun = getItem(i).noun; + Common::String name = Common::String::format("%d", i); + if (noun > 0) { + name += "/"; + name += _priNouns[noun - 1]; + } + if (desc > 0) { + name += "/"; + name += toAscii(loadMessage(desc)); } + return name; +} + +Common::String AdlEngine::itemRoomStr(uint i) const { + switch (i) { + case IDI_ANY: + return "CARRYING"; + case IDI_VOID_ROOM: + return "GONE"; + case IDI_CUR_ROOM: + return "HERE"; + default: + return Common::String::format("%d", i); + } +} + +Common::String AdlEngine::roomStr(uint i) const { + if (i == IDI_ANY) + return "*"; + else + return Common::String::format("%d", i); +} + +Common::String AdlEngine::verbStr(uint i) const { + if (i == IDI_ANY) + return "*"; + else + return Common::String::format("%d/%s", i, _priVerbs[i - 1].c_str()); +} + +Common::String AdlEngine::nounStr(uint i) const { + if (i == IDI_ANY) + return "*"; + else + return Common::String::format("%d/%s", i, _priNouns[i - 1].c_str()); +} + +Common::String AdlEngine::msgStr(uint i) const { + return Common::String::format("%d/%s", i, toAscii(loadMessage(i)).c_str()); +} + +Common::String AdlEngine::dirStr(Direction dir) const { + static const char *dirs[] = { "NORTH", "SOUTH", "EAST", "WEST", "UP", "DOWN" }; + return dirs[dir]; } } // End of namespace Adl diff --git a/engines/adl/adl.h b/engines/adl/adl.h index 4ea7566669..c9d77fcc62 100644 --- a/engines/adl/adl.h +++ b/engines/adl/adl.h @@ -23,88 +23,125 @@ #ifndef ADL_ADL_H #define ADL_ADL_H +#include "common/debug-channels.h" #include "common/array.h" #include "common/rect.h" #include "common/str.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/func.h" +#include "common/scummsys.h" #include "engines/engine.h" +#include "audio/mixer.h" +#include "audio/softsynth/pcspk.h" + +#include "adl/console.h" +#include "adl/disk.h" + namespace Common { class ReadStream; class SeekableReadStream; +class File; +struct Event; } namespace Adl { +class Console; class Display; +class GraphicsMan; +class Speaker; struct AdlGameDescription; +class ScriptEnv; -// 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 +enum kDebugChannels { + kDebugChannelScript = 1 << 0 +}; + +// Save and restore opcodes #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_CUR_ROOM 0xfc +#define IDI_VOID_ROOM 0xfd +#define IDI_ANY 0xfe #define IDI_WORD_SIZE 8 +enum Direction { + IDI_DIR_NORTH, + IDI_DIR_SOUTH, + IDI_DIR_EAST, + IDI_DIR_WEST, + IDI_DIR_UP, + IDI_DIR_DOWN, + IDI_DIR_TOTAL +}; + struct Room { + Room() : + description(0), + picture(0), + curPicture(0) { + memset(connections, 0, sizeof(connections)); + } + byte description; - byte connections[6]; + byte connections[IDI_DIR_TOTAL]; + DataBlockPtr data; byte picture; byte curPicture; + bool isFirstTime; }; -struct Picture { - byte block; - uint16 offset; -}; +typedef Common::HashMap<byte, DataBlockPtr> PictureMap; + +typedef Common::Array<byte> Script; struct Command { byte room; byte verb, noun; byte numCond, numAct; - Common::Array<byte> script; + Script script; +}; + +class ScriptEnv { +public: + ScriptEnv(const Command &cmd, byte room, byte verb, byte noun) : + _cmd(cmd), _room(room), _verb(verb), _noun(noun), _ip(0) { } + + byte op() const { return _cmd.script[_ip]; } + // We keep this 1-based for easier comparison with the original engine + byte arg(uint i) const { return _cmd.script[_ip + i]; } + void skip(uint i) { _ip += i; } + + bool isMatch() const { + return (_cmd.room == IDI_ANY || _cmd.room == _room) && + (_cmd.verb == IDI_ANY || _cmd.verb == _verb) && + (_cmd.noun == IDI_ANY || _cmd.noun == _noun); + } + + byte getCondCount() const { return _cmd.numCond; } + byte getActCount() const { return _cmd.numAct; } + byte getNoun() const { return _noun; } + const Command &getCommand() const { return _cmd; } + +private: + const Command &_cmd; + const byte _room, _verb, _noun; + byte _ip; }; enum { IDI_ITEM_NOT_MOVED, - IDI_ITEM_MOVED, + IDI_ITEM_DROPPED, IDI_ITEM_DOESNT_MOVE }; -#define IDI_NONE 0xfe - struct Item { + byte id; byte noun; byte room; byte picture; @@ -113,67 +150,200 @@ struct Item { int state; byte description; Common::Array<byte> roomPictures; + bool isOnScreen; +}; + +struct Time { + byte hours, minutes; + + Time() : hours(12), minutes(0) { } }; struct State { Common::Array<Room> rooms; - Common::Array<Item> items; + Common::List<Item> items; Common::Array<byte> vars; byte room; uint16 moves; bool isDark; + Time time; - State() : room(1), moves(0), isDark(false) { } + State() : room(1), moves(1), isDark(false) { } }; typedef Common::List<Command> Commands; typedef Common::HashMap<Common::String, uint> WordMap; +struct RoomData { + Common::String description; + PictureMap pictures; + Commands commands; +}; + +// Opcode debugging macros +#define OP_DEBUG_0(F) do { \ + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F)) \ + return 0; \ +} while (0) + +#define OP_DEBUG_1(F, P1) do { \ + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1)) \ + return 1; \ +} while (0) + +#define OP_DEBUG_2(F, P1, P2) do { \ + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2)) \ + return 2; \ +} while (0) + +#define OP_DEBUG_3(F, P1, P2, P3) do { \ + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3)) \ + return 3; \ +} while (0) + +#define OP_DEBUG_4(F, P1, P2, P3, P4) do { \ + if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3, P4)) \ + return 4; \ +} while (0) + class AdlEngine : public Engine { +friend class Console; public: virtual ~AdlEngine(); + bool pollEvent(Common::Event &event) const; + protected: AdlEngine(OSystem *syst, const AdlGameDescription *gd); + // Engine + Common::Error loadGameState(int slot); + Common::Error saveGameState(int slot, const Common::String &desc); + Common::String readString(Common::ReadStream &stream, byte until = 0) const; Common::String readStringAt(Common::SeekableReadStream &stream, uint offset, byte until = 0) const; + void openFile(Common::File &file, const Common::String &name) const; - virtual void printMessage(uint idx, bool wait = true) const; + virtual void printString(const Common::String &str) = 0; + virtual Common::String loadMessage(uint idx) const = 0; + virtual void printMessage(uint idx); + virtual Common::String getItemDescription(const Item &item) const; void delay(uint32 ms) const; Common::String inputString(byte prompt = 0) const; - byte inputKey() const; + byte inputKey(bool showCursor = true) const; - void loadWords(Common::ReadStream &stream, WordMap &map) const; + virtual Common::String formatVerbError(const Common::String &verb) const; + virtual Common::String formatNounError(const Common::String &verb, const Common::String &noun) const; + void loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri) const; void readCommands(Common::ReadStream &stream, Commands &commands); + void checkInput(byte verb, byte noun); + virtual bool isInputValid(byte verb, byte noun, bool &is_any); + virtual bool isInputValid(const Commands &commands, byte verb, byte noun, bool &is_any); + + virtual void setupOpcodeTables(); + virtual void initState(); + virtual byte roomArg(byte room) const; + virtual void advanceClock() { } + + // Opcodes + int o1_isItemInRoom(ScriptEnv &e); + int o1_isMovesGT(ScriptEnv &e); + int o1_isVarEQ(ScriptEnv &e); + int o1_isCurPicEQ(ScriptEnv &e); + int o1_isItemPicEQ(ScriptEnv &e); + + int o1_varAdd(ScriptEnv &e); + int o1_varSub(ScriptEnv &e); + int o1_varSet(ScriptEnv &e); + int o1_listInv(ScriptEnv &e); + int o1_moveItem(ScriptEnv &e); + int o1_setRoom(ScriptEnv &e); + int o1_setCurPic(ScriptEnv &e); + int o1_setPic(ScriptEnv &e); + int o1_printMsg(ScriptEnv &e); + int o1_setLight(ScriptEnv &e); + int o1_setDark(ScriptEnv &e); + int o1_save(ScriptEnv &e); + int o1_restore(ScriptEnv &e); + int o1_restart(ScriptEnv &e); + int o1_quit(ScriptEnv &e); + int o1_placeItem(ScriptEnv &e); + int o1_setItemPic(ScriptEnv &e); + int o1_resetPic(ScriptEnv &e); + template <Direction D> + int o1_goDirection(ScriptEnv &e); + int o1_takeItem(ScriptEnv &e); + int o1_dropItem(ScriptEnv &e); + int o1_setRoomPic(ScriptEnv &e); + + // Graphics + void clearScreen() const; + void drawPic(byte pic, Common::Point pos = Common::Point()) const; + + // Sound + void bell(uint count = 1) 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); + virtual void takeItem(byte noun); + void dropItem(byte noun); + bool matchCommand(ScriptEnv &env) const; + void doActions(ScriptEnv &env); + bool doOneCommand(const Commands &commands, byte verb, byte noun); + void doAllCommands(const Commands &commands, byte verb, byte noun); + + // Debug functions + static Common::String toAscii(const Common::String &str); + Common::String itemStr(uint i) const; + Common::String roomStr(uint i) const; + Common::String itemRoomStr(uint i) const; + Common::String verbStr(uint i) const; + Common::String nounStr(uint i) const; + Common::String msgStr(uint i) const; + Common::String dirStr(Direction dir) const; + bool op_debug(const char *fmt, ...) const; + Common::DumpFile *_dumpFile; Display *_display; + GraphicsMan *_graphics; + Speaker *_speaker; + // Opcodes + typedef Common::Functor1<ScriptEnv &, int> Opcode; + Common::Array<const Opcode *> _condOpcodes, _actOpcodes; // Message strings in data file - Common::Array<Common::String> _messages; + Common::Array<DataBlockPtr> _messages; // Picture data - Common::Array<Picture> _pictures; + PictureMap _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; + // Data related to the current room + RoomData _roomData; WordMap _verbs; WordMap _nouns; + Common::StringArray _priVerbs; + Common::StringArray _priNouns; struct { Common::String enterCommand; - Common::String dontHaveIt; - Common::String gettingDark; Common::String verbError; Common::String nounError; Common::String playAgain; + Common::String pressReturn; + Common::String lineFeeds; } _strings; struct { @@ -187,61 +357,37 @@ protected: // Game state State _state; + bool _isRestarting, _isRestoring; + bool _skipOneCommand; + 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; + virtual void init() = 0; + virtual void initGameState() = 0; + virtual void drawItems() = 0; + virtual void drawItem(Item &item, const Common::Point &pos) = 0; + virtual void loadRoom(byte roomNr) = 0; + virtual void showRoom() = 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); - + Console *_console; + GUI::Debugger *getDebugger() { return _console; } 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/adl_v2.cpp b/engines/adl/adl_v2.cpp new file mode 100644 index 0000000000..4fdf796701 --- /dev/null +++ b/engines/adl/adl_v2.cpp @@ -0,0 +1,539 @@ +/* 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/random.h" +#include "common/error.h" + +#include "adl/adl_v2.h" +#include "adl/display.h" +#include "adl/graphics.h" + +namespace Adl { + +AdlEngine_v2::~AdlEngine_v2() { + delete _random; + delete _disk; +} + +AdlEngine_v2::AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine(syst, gd), + _linesPrinted(0), + _disk(nullptr), + _itemRemoved(false), + _roomOnScreen(0), + _picOnScreen(0), + _itemsOnScreen(0) { + _random = new Common::RandomSource("adl"); +} + +typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v2> OpcodeV2; +#define SetOpcodeTable(x) table = &x; +#define Opcode(x) table->push_back(new OpcodeV2(this, &AdlEngine_v2::x)) +#define OpcodeUnImpl() table->push_back(new OpcodeV2(this, 0)) + +void AdlEngine_v2::setupOpcodeTables() { + Common::Array<const Opcode *> *table = 0; + + SetOpcodeTable(_condOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o2_isFirstTime); + Opcode(o2_isRandomGT); + Opcode(o1_isItemInRoom); + // 0x04 + Opcode(o2_isNounNotInRoom); + Opcode(o1_isMovesGT); + Opcode(o1_isVarEQ); + Opcode(o2_isCarryingSomething); + // 0x08 + OpcodeUnImpl(); + Opcode(o1_isCurPicEQ); + Opcode(o1_isItemPicEQ); + + SetOpcodeTable(_actOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o1_varAdd); + Opcode(o1_varSub); + Opcode(o1_varSet); + // 0x04 + Opcode(o1_listInv); + Opcode(o2_moveItem); + Opcode(o1_setRoom); + Opcode(o1_setCurPic); + // 0x08 + Opcode(o1_setPic); + Opcode(o1_printMsg); + Opcode(o1_setLight); + Opcode(o1_setDark); + // 0x0c + Opcode(o2_moveAllItems); + Opcode(o1_quit); + OpcodeUnImpl(); + Opcode(o2_save); + // 0x10 + Opcode(o2_restore); + Opcode(o1_restart); + Opcode(o2_placeItem); + Opcode(o1_setItemPic); + // 0x14 + Opcode(o1_resetPic); + Opcode(o1_goDirection<IDI_DIR_NORTH>); + Opcode(o1_goDirection<IDI_DIR_SOUTH>); + Opcode(o1_goDirection<IDI_DIR_EAST>); + // 0x18 + Opcode(o1_goDirection<IDI_DIR_WEST>); + Opcode(o1_goDirection<IDI_DIR_UP>); + Opcode(o1_goDirection<IDI_DIR_DOWN>); + Opcode(o1_takeItem); + // 0x1c + Opcode(o1_dropItem); + Opcode(o1_setRoomPic); + Opcode(o2_tellTime); + Opcode(o2_setRoomFromVar); + // 0x20 + Opcode(o2_initDisk); +} + +void AdlEngine_v2::initState() { + AdlEngine::initState(); + + _linesPrinted = 0; + _picOnScreen = 0; + _roomOnScreen = 0; + _itemRemoved = false; + _itemsOnScreen = 0; +} + +byte AdlEngine_v2::roomArg(byte room) const { + if (room == IDI_CUR_ROOM) + return _state.room; + return room; +} + +void AdlEngine_v2::advanceClock() { + Time &time = _state.time; + + time.minutes += 5; + + if (time.minutes == 60) { + time.minutes = 0; + + ++time.hours; + + if (time.hours == 13) + time.hours = 1; + } +} + +void AdlEngine_v2::checkTextOverflow(char c) { + if (c != APPLECHAR('\r')) + return; + + ++_linesPrinted; + + if (_linesPrinted < 4) + return; + + _linesPrinted = 0; + _display->updateTextScreen(); + bell(); + + while (true) { + char key = inputKey(false); + + if (shouldQuit()) + return; + + if (key == APPLECHAR('\r')) + break; + + bell(3); + } +} + +Common::String AdlEngine_v2::loadMessage(uint idx) const { + if (_messages[idx]) { + StreamPtr strStream(_messages[idx]->createReadStream()); + return readString(*strStream, 0xff); + } + + return Common::String(); +} + +void AdlEngine_v2::printString(const Common::String &str) { + Common::String s(str); + byte endPos = TEXT_WIDTH - 1; + byte pos = 0; + + while (true) { + while (pos <= endPos && pos != s.size()) { + s.setChar(APPLECHAR(s[pos]), pos); + ++pos; + } + + if (pos == s.size()) + break; + + while (s[pos] != APPLECHAR(' ') && s[pos] != APPLECHAR('\r')) + --pos; + + s.setChar(APPLECHAR('\r'), pos); + endPos = pos + TEXT_WIDTH; + ++pos; + } + + pos = 0; + while (pos != s.size()) { + checkTextOverflow(s[pos]); + _display->printChar(s[pos]); + ++pos; + } + + checkTextOverflow(APPLECHAR('\r')); + _display->printChar(APPLECHAR('\r')); + _display->updateTextScreen(); +} + +void AdlEngine_v2::drawItem(Item &item, const Common::Point &pos) { + item.isOnScreen = true; + StreamPtr stream(_itemPics[item.picture - 1]->createReadStream()); + stream->readByte(); // Skip clear opcode + _graphics->drawPic(*stream, pos); +} + +void AdlEngine_v2::loadRoom(byte roomNr) { + Room &room = getRoom(roomNr); + StreamPtr stream(room.data->createReadStream()); + + uint16 descOffset = stream->readUint16LE(); + uint16 commandOffset = stream->readUint16LE(); + + _roomData.pictures.clear(); + // There's no picture count. The original engine always checks at most + // five pictures. We use the description offset to bound our search. + uint16 picCount = (descOffset - 4) / 5; + + for (uint i = 0; i < picCount; ++i) { + byte nr = stream->readByte(); + _roomData.pictures[nr] = readDataBlockPtr(*stream); + } + + _roomData.description = readStringAt(*stream, descOffset, 0xff); + + _roomData.commands.clear(); + if (commandOffset != 0) { + stream->seek(commandOffset); + readCommands(*stream, _roomData.commands); + } +} + +void AdlEngine_v2::showRoom() { + bool redrawPic = false; + + if (_state.room != _roomOnScreen) { + loadRoom(_state.room); + clearScreen(); + + if (!_state.isDark) + redrawPic = true; + } else { + if (getCurRoom().curPicture != _picOnScreen || _itemRemoved) + redrawPic = true; + } + + if (redrawPic) { + _roomOnScreen = _state.room; + _picOnScreen = getCurRoom().curPicture; + + drawPic(getCurRoom().curPicture); + _itemRemoved = false; + _itemsOnScreen = 0; + + Common::List<Item>::iterator item; + for (item = _state.items.begin(); item != _state.items.end(); ++item) + item->isOnScreen = false; + } + + if (!_state.isDark) + drawItems(); + + _display->updateHiResScreen(); + printString(_roomData.description); + + // FIXME: move to main loop? + _linesPrinted = 0; +} + +void AdlEngine_v2::takeItem(byte noun) { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + if (item->noun != noun || item->room != _state.room) + continue; + + if (item->state == IDI_ITEM_DOESNT_MOVE) { + printMessage(_messageIds.itemDoesntMove); + return; + } + + if (item->state == IDI_ITEM_DROPPED) { + item->room = IDI_ANY; + _itemRemoved = true; + return; + } + + Common::Array<byte>::const_iterator pic; + for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { + if (*pic == getCurRoom().curPicture || *pic == IDI_ANY) { + item->room = IDI_ANY; + _itemRemoved = true; + item->state = IDI_ITEM_DROPPED; + return; + } + } + } + + printMessage(_messageIds.itemNotHere); +} + +void AdlEngine_v2::drawItems() { + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + // Skip items not in this room + if (item->room != _state.room) + continue; + + if (item->isOnScreen) + continue; + + if (item->state == IDI_ITEM_DROPPED) { + // Draw dropped item if in normal view + if (getCurRoom().picture == getCurRoom().curPicture) + drawItem(*item, _itemOffsets[_itemsOnScreen++]); + } else { + // Draw fixed item if current view is in the pic list + Common::Array<byte>::const_iterator pic; + + for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { + if (*pic == getCurRoom().curPicture || *pic == IDI_ANY) { + drawItem(*item, item->position); + break; + } + } + } + } +} + +DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const { + byte track = f.readByte(); + byte sector = f.readByte(); + byte offset = f.readByte(); + byte size = f.readByte(); + + if (f.eos() || f.err()) + error("Error reading DataBlockPtr"); + + if (track == 0 && sector == 0 && offset == 0 && size == 0) + return DataBlockPtr(); + + return _disk->getDataBlock(track, sector, offset, size); +} + +int AdlEngine_v2::o2_isFirstTime(ScriptEnv &e) { + OP_DEBUG_0("\t&& IS_FIRST_TIME()"); + + bool oldFlag = getCurRoom().isFirstTime; + + getCurRoom().isFirstTime = false; + + if (!oldFlag) + return -1; + + return 0; +} + +int AdlEngine_v2::o2_isRandomGT(ScriptEnv &e) { + OP_DEBUG_1("\t&& RAND() > %d", e.arg(1)); + + byte rnd = _random->getRandomNumber(255); + + if (rnd > e.arg(1)) + return 1; + + return -1; +} + +int AdlEngine_v2::o2_isNounNotInRoom(ScriptEnv &e) { + OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str()); + + Common::List<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->noun == e.getNoun() && (item->room == roomArg(e.arg(1)))) + return -1; + + return 1; +} + +int AdlEngine_v2::o2_isCarryingSomething(ScriptEnv &e) { + OP_DEBUG_0("\t&& IS_CARRYING_SOMETHING()"); + + Common::List<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == IDI_ANY) + return 0; + return -1; +} + +int AdlEngine_v2::o2_moveItem(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + byte room = roomArg(e.arg(2)); + + Item &item = getItem(e.arg(1)); + + if (item.room == _roomOnScreen) + _picOnScreen = 0; + + // Set items that move from inventory to a room to state "dropped" + if (item.room == IDI_ANY && room != IDI_VOID_ROOM) + item.state = IDI_ITEM_DROPPED; + + item.room = room; + return 2; +} + +int AdlEngine_v2::o2_moveAllItems(ScriptEnv &e) { + OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + byte room1 = roomArg(e.arg(1)); + + if (room1 == _state.room) + _picOnScreen = 0; + + byte room2 = roomArg(e.arg(2)); + + Common::List<Item>::iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == room1) { + item->room = room2; + if (room1 == IDI_ANY) + item->state = IDI_ITEM_DROPPED; + } + + return 2; +} + +int AdlEngine_v2::o2_save(ScriptEnv &e) { + OP_DEBUG_0("\tSAVE_GAME()"); + + int slot = askForSlot(_strings_v2.saveInsert); + + if (slot < 0) + return -1; + + saveGameState(slot, ""); + + _display->printString(_strings_v2.saveReplace); + inputString(); + return 0; +} + +int AdlEngine_v2::o2_restore(ScriptEnv &e) { + OP_DEBUG_0("\tRESTORE_GAME()"); + + int slot = askForSlot(_strings_v2.restoreInsert); + + if (slot < 0) + return -1; + + loadGameState(slot); + _isRestoring = false; + + _display->printString(_strings_v2.restoreReplace); + inputString(); + _picOnScreen = 0; + _roomOnScreen = 0; + return 0; +} + +int AdlEngine_v2::o2_placeItem(ScriptEnv &e) { + OP_DEBUG_4("\tPLACE_ITEM(%s, %s, (%d, %d))", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str(), e.arg(3), e.arg(4)); + + Item &item = getItem(e.arg(1)); + + item.room = roomArg(e.arg(2)); + item.position.x = e.arg(3); + item.position.y = e.arg(4); + item.state = IDI_ITEM_NOT_MOVED; + + return 4; +} + +int AdlEngine_v2::o2_tellTime(ScriptEnv &e) { + OP_DEBUG_0("\tTELL_TIME()"); + + Common::String time = _strings_v2.time; + + time.setChar(APPLECHAR('0') + _state.time.hours / 10, 12); + time.setChar(APPLECHAR('0') + _state.time.hours % 10, 13); + time.setChar(APPLECHAR('0') + _state.time.minutes / 10, 15); + time.setChar(APPLECHAR('0') + _state.time.minutes % 10, 16); + + printString(time); + + return 0; +} + +int AdlEngine_v2::o2_setRoomFromVar(ScriptEnv &e) { + OP_DEBUG_1("\tROOM = VAR[%d]", e.arg(1)); + getCurRoom().curPicture = getCurRoom().picture; + _state.room = getVar(e.arg(1)); + return 1; +} + +int AdlEngine_v2::o2_initDisk(ScriptEnv &e) { + OP_DEBUG_0("\tINIT_DISK()"); + + _display->printAsciiString("NOT REQUIRED\r"); + return 0; +} + +int AdlEngine_v2::askForSlot(const Common::String &question) { + while (1) { + _display->printString(question); + + Common::String input = inputString(); + + if (shouldQuit()) + return -1; + + if (input.size() > 0 && input[0] >= APPLECHAR('A') && input[0] <= APPLECHAR('O')) + return input[0] - APPLECHAR('A'); + } +} + +} // End of namespace Adl diff --git a/engines/adl/adl_v2.h b/engines/adl/adl_v2.h new file mode 100644 index 0000000000..f18972b74b --- /dev/null +++ b/engines/adl/adl_v2.h @@ -0,0 +1,95 @@ +/* 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_V2_H +#define ADL_ADL_V2_H + +#include "adl/adl.h" + +// Note: this version of ADL redraws only when necessary, but +// this is not currently implemented. + +namespace Common { +class RandomSource; +} + +namespace Adl { + +class AdlEngine_v2 : public AdlEngine { +public: + virtual ~AdlEngine_v2(); + +protected: + AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd); + + // AdlEngine + virtual void setupOpcodeTables(); + virtual void initState(); + byte roomArg(byte room) const; + void advanceClock(); + virtual void printString(const Common::String &str); + virtual Common::String loadMessage(uint idx) const; + void drawItems(); + void drawItem(Item &item, const Common::Point &pos); + void loadRoom(byte roomNr); + virtual void showRoom(); + void takeItem(byte noun); + + virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const; + + void checkTextOverflow(char c); + + int o2_isFirstTime(ScriptEnv &e); + int o2_isRandomGT(ScriptEnv &e); + int o2_isNounNotInRoom(ScriptEnv &e); + int o2_isCarryingSomething(ScriptEnv &e); + + int o2_moveItem(ScriptEnv &e); + int o2_moveAllItems(ScriptEnv &e); + int o2_save(ScriptEnv &e); + int o2_restore(ScriptEnv &e); + int o2_placeItem(ScriptEnv &e); + int o2_tellTime(ScriptEnv &e); + int o2_setRoomFromVar(ScriptEnv &e); + int o2_initDisk(ScriptEnv &e); + + struct { + Common::String time; + Common::String saveInsert, saveReplace; + Common::String restoreInsert, restoreReplace; + } _strings_v2; + + uint _linesPrinted; + DiskImage *_disk; + Common::Array<DataBlockPtr> _itemPics; + bool _itemRemoved; + byte _roomOnScreen, _picOnScreen, _itemsOnScreen; + +private: + int askForSlot(const Common::String &question); + + Common::RandomSource *_random; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/adl_v3.cpp b/engines/adl/adl_v3.cpp new file mode 100644 index 0000000000..623db661bc --- /dev/null +++ b/engines/adl/adl_v3.cpp @@ -0,0 +1,259 @@ +/* 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/random.h" +#include "common/error.h" + +#include "adl/adl_v3.h" +#include "adl/display.h" +#include "adl/graphics.h" + +namespace Adl { + +AdlEngine_v3::AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine_v2(syst, gd), + _curDisk(0) { +} + +Common::String AdlEngine_v3::loadMessage(uint idx) const { + Common::String str = AdlEngine_v2::loadMessage(idx); + + for (uint i = 0; i < str.size(); ++i) { + const char *xorStr = "AVISDURGAN"; + str.setChar(str[i] ^ xorStr[i % strlen(xorStr)], i); + } + + return str; +} + +Common::String AdlEngine_v3::getItemDescription(const Item &item) const { + return _itemDesc[item.id - 1]; +} + +void AdlEngine_v3::applyDiskOffset(byte &track, byte §or) const { + sector += _diskOffsets[_curDisk].sector; + if (sector >= 16) { + sector -= 16; + ++track; + } + + track += _diskOffsets[_curDisk].track; +} + +DataBlockPtr AdlEngine_v3::readDataBlockPtr(Common::ReadStream &f) const { + byte track = f.readByte(); + byte sector = f.readByte(); + byte offset = f.readByte(); + byte size = f.readByte(); + + if (f.eos() || f.err()) + error("Error reading DataBlockPtr"); + + if (track == 0 && sector == 0 && offset == 0 && size == 0) + return DataBlockPtr(); + + applyDiskOffset(track, sector); + + return _disk->getDataBlock(track, sector, offset, size); +} + +typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v3> OpcodeV3; +#define SetOpcodeTable(x) table = &x; +#define Opcode(x) table->push_back(new OpcodeV3(this, &AdlEngine_v3::x)) +#define OpcodeUnImpl() table->push_back(new OpcodeV3(this, 0)) + +void AdlEngine_v3::setupOpcodeTables() { + Common::Array<const Opcode *> *table = 0; + + SetOpcodeTable(_condOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o2_isFirstTime); + Opcode(o2_isRandomGT); + Opcode(o3_isItemInRoom); + // 0x04 + Opcode(o3_isNounNotInRoom); + Opcode(o1_isMovesGT); + Opcode(o1_isVarEQ); + Opcode(o2_isCarryingSomething); + // 0x08 + Opcode(o3_isVarGT); + Opcode(o1_isCurPicEQ); + Opcode(o3_skipOneCommand); + + SetOpcodeTable(_actOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o1_varAdd); + Opcode(o1_varSub); + Opcode(o1_varSet); + // 0x04 + Opcode(o1_listInv); + Opcode(o3_moveItem); + Opcode(o1_setRoom); + Opcode(o1_setCurPic); + // 0x08 + Opcode(o1_setPic); + Opcode(o1_printMsg); + Opcode(o3_dummy); + Opcode(o3_setTextMode); + // 0x0c + Opcode(o2_moveAllItems); + Opcode(o1_quit); + Opcode(o3_dummy); + Opcode(o2_save); + // 0x10 + Opcode(o2_restore); + Opcode(o1_restart); + Opcode(o3_setDisk); + Opcode(o3_dummy); + // 0x14 + Opcode(o1_resetPic); + Opcode(o1_goDirection<IDI_DIR_NORTH>); + Opcode(o1_goDirection<IDI_DIR_SOUTH>); + Opcode(o1_goDirection<IDI_DIR_EAST>); + // 0x18 + Opcode(o1_goDirection<IDI_DIR_WEST>); + Opcode(o1_goDirection<IDI_DIR_UP>); + Opcode(o1_goDirection<IDI_DIR_DOWN>); + Opcode(o1_takeItem); + // 0x1c + Opcode(o1_dropItem); + Opcode(o1_setRoomPic); + Opcode(o3_sound); + OpcodeUnImpl(); + // 0x20 + Opcode(o2_initDisk); +} + +int AdlEngine_v3::o3_isVarGT(ScriptEnv &e) { + OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2)); + + if (getVar(e.arg(1)) > e.arg(2)) + return 2; + + return -1; +} + +int AdlEngine_v3::o3_skipOneCommand(ScriptEnv &e) { + OP_DEBUG_0("\t&& SKIP_ONE_COMMAND()"); + + _skipOneCommand = true; + setVar(2, 0); + + return -1; +} + +// FIXME: Rename "isLineArt" and look at code duplication +int AdlEngine_v3::o3_isItemInRoom(ScriptEnv &e) { + OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + const Item &item = getItem(e.arg(1)); + + if (e.arg(2) != IDI_ANY && item.isLineArt != _curDisk) + return -1; + + if (item.room == roomArg(e.arg(2))) + return 2; + + return -1; +} + +int AdlEngine_v3::o3_isNounNotInRoom(ScriptEnv &e) { + OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str()); + + Common::List<Item>::const_iterator item; + + setVar(24, 0); + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->noun == e.getNoun()) { + setVar(24, 1); + + if (item->room == roomArg(e.arg(1))) + return -1; + } + + return 1; +} + +int AdlEngine_v3::o3_moveItem(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + byte room = roomArg(e.arg(2)); + + Item &item = getItem(e.arg(1)); + + if (item.room == _roomOnScreen) + _picOnScreen = 0; + + // Set items that move from inventory to a room to state "dropped" + if (item.room == IDI_ANY && room != IDI_VOID_ROOM) + item.state = IDI_ITEM_DROPPED; + + item.room = room; + item.isLineArt = _curDisk; + return 2; +} + +int AdlEngine_v3::o3_dummy(ScriptEnv &e) { + OP_DEBUG_0("\tDUMMY()"); + + return 0; +} + +int AdlEngine_v3::o3_setTextMode(ScriptEnv &e) { + OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1)); + + // TODO + // 1: 4-line mode + // 2: 24-line mode + + switch (e.arg(1)) { + case 3: + // We re-use the restarting flag here, to simulate a long jump + _isRestarting = true; + return -1; + } + + return 1; +} + +int AdlEngine_v3::o3_setDisk(ScriptEnv &e) { + OP_DEBUG_2("\tSET_DISK(%d, %d)", e.arg(1), e.arg(2)); + + // TODO + // Arg 1: disk + // Arg 2: room + + return 2; +} + +int AdlEngine_v3::o3_sound(ScriptEnv &e) { + OP_DEBUG_0("\tSOUND()"); + + // TODO + + return 0; +} + +} // End of namespace Adl diff --git a/engines/adl/adl_v3.h b/engines/adl/adl_v3.h new file mode 100644 index 0000000000..61dd5852e7 --- /dev/null +++ b/engines/adl/adl_v3.h @@ -0,0 +1,76 @@ +/* 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_V3_H +#define ADL_ADL_V3_H + +#include "adl/adl_v2.h" + +// Note: this version of ADL redraws only when necessary, but +// this is not currently implemented. + +namespace Common { +class RandomSource; +} + +struct DiskOffset { + byte track; + byte sector; +}; + +namespace Adl { + +class AdlEngine_v3 : public AdlEngine_v2 { +public: + virtual ~AdlEngine_v3() { } + +protected: + AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd); + + // AdlEngine + virtual void setupOpcodeTables(); + virtual Common::String loadMessage(uint idx) const; + Common::String getItemDescription(const Item &item) const; + + // AdlEngine_v2 + virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const; + + void applyDiskOffset(byte &track, byte §or) const; + + int o3_isVarGT(ScriptEnv &e); + int o3_isItemInRoom(ScriptEnv &e); + int o3_isNounNotInRoom(ScriptEnv &e); + int o3_skipOneCommand(ScriptEnv &e); + int o3_moveItem(ScriptEnv &e); + int o3_dummy(ScriptEnv &e); + int o3_setTextMode(ScriptEnv &e); + int o3_setDisk(ScriptEnv &e); + int o3_sound(ScriptEnv &e); + + Common::Array<Common::String> _itemDesc; + byte _curDisk; + Common::Array<DiskOffset> _diskOffsets; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/console.cpp b/engines/adl/console.cpp new file mode 100644 index 0000000000..c35e8b02aa --- /dev/null +++ b/engines/adl/console.cpp @@ -0,0 +1,333 @@ +/* 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/debug-channels.h" + +#include "adl/console.h" +#include "adl/display.h" +#include "adl/adl.h" + +namespace Adl { + +Console::Console(AdlEngine *engine) : GUI::Debugger() { + _engine = engine; + + registerCmd("nouns", WRAP_METHOD(Console, Cmd_Nouns)); + registerCmd("verbs", WRAP_METHOD(Console, Cmd_Verbs)); + registerCmd("dump_scripts", WRAP_METHOD(Console, Cmd_DumpScripts)); + registerCmd("valid_cmds", WRAP_METHOD(Console, Cmd_ValidCommands)); + registerCmd("room", WRAP_METHOD(Console, Cmd_Room)); + registerCmd("items", WRAP_METHOD(Console, Cmd_Items)); + registerCmd("give_item", WRAP_METHOD(Console, Cmd_GiveItem)); + registerCmd("vars", WRAP_METHOD(Console, Cmd_Vars)); + registerCmd("var", WRAP_METHOD(Console, Cmd_Var)); +} + +Common::String Console::toAscii(const Common::String &str) { + Common::String ascii(str); + + for (uint i = 0; i < ascii.size(); ++i) + ascii.setChar(ascii[i] & 0x7f, i); + + return ascii; +} + +Common::String Console::toAppleWord(const Common::String &str) { + Common::String apple(str); + + if (apple.size() > IDI_WORD_SIZE) + apple.erase(IDI_WORD_SIZE); + apple.toUppercase(); + + for (uint i = 0; i < apple.size(); ++i) + apple.setChar(APPLECHAR(apple[i]), i); + + while (apple.size() < IDI_WORD_SIZE) + apple += APPLECHAR(' '); + + return apple; +} + +bool Console::Cmd_Verbs(int argc, const char **argv) { + if (argc != 1) { + debugPrintf("Usage: %s\n", argv[0]); + return true; + } + + debugPrintf("Verbs in alphabetical order:\n"); + printWordMap(_engine->_verbs); + return true; +} + +bool Console::Cmd_Nouns(int argc, const char **argv) { + if (argc != 1) { + debugPrintf("Usage: %s\n", argv[0]); + return true; + } + + debugPrintf("Nouns in alphabetical order:\n"); + printWordMap(_engine->_nouns); + return true; +} + +bool Console::Cmd_ValidCommands(int argc, const char **argv) { + if (argc != 1) { + debugPrintf("Usage: %s\n", argv[0]); + return true; + } + + WordMap::const_iterator verb, noun; + bool is_any; + + for (verb = _engine->_verbs.begin(); verb != _engine->_verbs.end(); ++verb) { + for (noun = _engine->_nouns.begin(); noun != _engine->_nouns.end(); ++noun) { + if (_engine->isInputValid(verb->_value, noun->_value, is_any) && !is_any) + debugPrintf("%s %s\n", toAscii(verb->_key).c_str(), toAscii(noun->_key).c_str()); + } + if (_engine->isInputValid(verb->_value, IDI_ANY, is_any)) + debugPrintf("%s *\n", toAscii(verb->_key).c_str()); + } + if (_engine->isInputValid(IDI_ANY, IDI_ANY, is_any)) + debugPrintf("* *\n"); + + return true; +} + +bool Console::Cmd_DumpScripts(int argc, const char **argv) { + if (argc != 1) { + debugPrintf("Usage: %s\n", argv[0]); + return true; + } + + bool oldFlag = DebugMan.isDebugChannelEnabled(kDebugChannelScript); + + DebugMan.enableDebugChannel("Script"); + + _engine->_dumpFile = new Common::DumpFile(); + + for (byte roomNr = 1; roomNr <= _engine->_state.rooms.size(); ++roomNr) { + _engine->loadRoom(roomNr); + if (_engine->_roomData.commands.size() != 0) { + _engine->_dumpFile->open(Common::String::format("%03d.ADL", roomNr).c_str()); + _engine->doAllCommands(_engine->_roomData.commands, IDI_ANY, IDI_ANY); + _engine->_dumpFile->close(); + } + } + _engine->loadRoom(_engine->_state.room); + + _engine->_dumpFile->open("GLOBAL.ADL"); + _engine->doAllCommands(_engine->_globalCommands, IDI_ANY, IDI_ANY); + _engine->_dumpFile->close(); + + _engine->_dumpFile->open("RESPONSE.ADL"); + _engine->doAllCommands(_engine->_roomCommands, IDI_ANY, IDI_ANY); + _engine->_dumpFile->close(); + + delete _engine->_dumpFile; + _engine->_dumpFile = nullptr; + + if (!oldFlag) + DebugMan.disableDebugChannel("Script"); + + return true; +} + +bool Console::Cmd_Room(int argc, const char **argv) { + if (argc > 2) { + debugPrintf("Usage: %s [<new_room>]\n", argv[0]); + return true; + } + + if (argc == 2) { + if (!_engine->_canRestoreNow) { + debugPrintf("Cannot change rooms right now\n"); + return true; + } + + uint roomCount = _engine->_state.rooms.size(); + uint room = strtoul(argv[1], NULL, 0); + if (room < 1 || room > roomCount) { + debugPrintf("Room %u out of valid range [1, %u]\n", room, roomCount); + return true; + } + + _engine->_state.room = room; + _engine->clearScreen(); + _engine->loadRoom(_engine->_state.room); + _engine->showRoom(); + _engine->_display->updateTextScreen(); + _engine->_display->updateHiResScreen(); + } + + debugPrintf("Current room: %u\n", _engine->_state.room); + + return true; +} + +bool Console::Cmd_Items(int argc, const char **argv) { + if (argc != 1) { + debugPrintf("Usage: %s\n", argv[0]); + return true; + } + + Common::List<Item>::const_iterator item; + + for (item = _engine->_state.items.begin(); item != _engine->_state.items.end(); ++item) + printItem(*item); + + return true; +} + +bool Console::Cmd_GiveItem(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Usage: %s <ID | name>\n", argv[0]); + return true; + } + + Common::List<Item>::iterator item; + + char *end; + uint id = strtoul(argv[1], &end, 0); + + if (*end != 0) { + Common::Array<Item *> matches; + + Common::String name = toAppleWord(argv[1]); + + if (!_engine->_nouns.contains(name)) { + debugPrintf("Item '%s' not found\n", argv[1]); + return true; + } + + byte noun = _engine->_nouns[name]; + + for (item = _engine->_state.items.begin(); item != _engine->_state.items.end(); ++item) { + if (item->noun == noun) + matches.push_back(&*item); + } + + if (matches.size() == 0) { + debugPrintf("Item '%s' not found\n", argv[1]); + return true; + } + + if (matches.size() > 1) { + debugPrintf("Multiple matches found, please use item ID:\n"); + for (uint i = 0; i < matches.size(); ++i) + printItem(*matches[i]); + return true; + } + + matches[0]->room = IDI_ANY; + debugPrintf("OK\n"); + return true; + } + + for (item = _engine->_state.items.begin(); item != _engine->_state.items.end(); ++item) + if (item->id == id) { + item->room = IDI_ANY; + debugPrintf("OK\n"); + return true; + } + + debugPrintf("Item %i not found\n", id); + return true; +} + +bool Console::Cmd_Vars(int argc, const char **argv) { + if (argc != 1) { + debugPrintf("Usage: %s\n", argv[0]); + return true; + } + + Common::StringArray vars; + for (uint i = 0; i < _engine->_state.vars.size(); ++i) + vars.push_back(Common::String::format("%3d: %3d", i, _engine->_state.vars[i])); + + debugPrintf("Variables:\n"); + debugPrintColumns(vars); + + return true; +} + +bool Console::Cmd_Var(int argc, const char **argv) { + if (argc < 2 || argc > 3) { + debugPrintf("Usage: %s <index> [<value>]\n", argv[0]); + return true; + } + + uint varCount = _engine->_state.vars.size(); + uint var = strtoul(argv[1], NULL, 0); + + if (var >= varCount) { + debugPrintf("Variable %u out of valid range [0, %u]\n", var, varCount - 1); + return true; + } + + if (argc == 3) { + uint value = strtoul(argv[2], NULL, 0); + _engine->_state.vars[var] = value; + } + + debugPrintf("%3d: %3d\n", var, _engine->_state.vars[var]); + + return true; +} + +void Console::printItem(const Item &item) { + Common::String name, desc, state; + + if (item.noun > 0) + name = _engine->_priNouns[item.noun - 1]; + + desc = toAscii(_engine->getItemDescription(item)); + if (desc.lastChar() == '\r') + desc.deleteLastChar(); + + switch (item.state) { + case IDI_ITEM_NOT_MOVED: + state = "PLACED"; + break; + case IDI_ITEM_DROPPED: + state = "DROPPED"; + break; + case IDI_ITEM_DOESNT_MOVE: + state = "FIXED"; + break; + } + + debugPrintf("%3d %s %-30s %-10s %-8s (%3d, %3d)\n", item.id, name.c_str(), desc.c_str(), _engine->itemRoomStr(item.room).c_str(), state.c_str(), item.position.x, item.position.y); +} + +void Console::printWordMap(const WordMap &wordMap) { + Common::StringArray words; + WordMap::const_iterator verb; + + for (verb = wordMap.begin(); verb != wordMap.end(); ++verb) + words.push_back(Common::String::format("%s: %3d", toAscii(verb->_key).c_str(), wordMap[verb->_key])); + + Common::sort(words.begin(), words.end()); + + debugPrintColumns(words); +} + +} // End of namespace Adl diff --git a/engines/adl/console.h b/engines/adl/console.h new file mode 100644 index 0000000000..a8c6adc1cc --- /dev/null +++ b/engines/adl/console.h @@ -0,0 +1,65 @@ +/* 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_CONSOLE_H +#define ADL_CONSOLE_H + +#include "gui/debugger.h" + +#include "common/hashmap.h" + +namespace Common { +class String; +} + +namespace Adl { + +class AdlEngine; +struct Item; + +class Console : public GUI::Debugger { +public: + Console(AdlEngine *engine); + + static Common::String toAscii(const Common::String &str); + static Common::String toAppleWord(const Common::String &str); + +private: + bool Cmd_Nouns(int argc, const char **argv); + bool Cmd_Verbs(int argc, const char **argv); + bool Cmd_DumpScripts(int argc, const char **argv); + bool Cmd_ValidCommands(int argc, const char **argv); + bool Cmd_Room(int argc, const char **argv); + bool Cmd_Items(int argc, const char **argv); + bool Cmd_GiveItem(int argc, const char **argv); + bool Cmd_Vars(int argc, const char **argv); + bool Cmd_Var(int argc, const char **argv); + + void printItem(const Item &item); + void printWordMap(const Common::HashMap<Common::String, uint> &wordMap); + + AdlEngine *_engine; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp index 1a8c5025e8..12405e7c5e 100644 --- a/engines/adl/detection.cpp +++ b/engines/adl/detection.cpp @@ -32,8 +32,9 @@ namespace Adl { -#define GAMEOPTION_COLOR GUIO_GAMEOPTIONS1 -#define GAMEOPTION_SCANLINES GUIO_GAMEOPTIONS2 +#define GAMEOPTION_COLOR GUIO_GAMEOPTIONS1 +#define GAMEOPTION_SCANLINES GUIO_GAMEOPTIONS2 +#define GAMEOPTION_MONO GUIO_GAMEOPTIONS3 static const ADExtraGuiOptionsMap optionsList[] = { { @@ -47,6 +48,16 @@ static const ADExtraGuiOptionsMap optionsList[] = { }, { + GAMEOPTION_MONO, + { + _s("Color mode"), + _s("Use color graphics"), + "color", + true + } + }, + + { GAMEOPTION_SCANLINES, { _s("Scanlines"), @@ -60,27 +71,89 @@ static const ADExtraGuiOptionsMap optionsList[] = { }; static const PlainGameDescriptor adlGames[] = { - {"hires1", "Hi-Res Adventure #1: Mystery House"}, - {0, 0} + { "hires0", "Hi-Res Adventure #0: Mission Asteroid" }, + { "hires1", "Hi-Res Adventure #1: Mystery House" }, + { "hires2", "Hi-Res Adventure #2: Wizard and the Princess" }, + { "hires6", "Hi-Res Adventure #6: The Dark Crystal" }, + { 0, 0 } }; static const AdlGameDescription gameDescriptions[] = { - { // Hi-Res Adventure #1: Mystery House - Apple II - 1987 PD release + { // Hi-Res Adventure #1: Mystery House - Apple II - 1987 PD release - Plain files { "hires1", 0, { - {"ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952}, - {"AUTO LOAD OBJ", 0, "23bfccfe9fcff9b22cf6c41bde9078ac", 12291}, - {"MYSTERY.HELLO", 0, "2289b7fea300b506e902a4c597968369", 836}, + { "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, + ADGF_UNSTABLE, GUIO2(GAMEOPTION_COLOR, GAMEOPTION_SCANLINES) }, GAME_TYPE_HIRES1 }, + { // Hi-Res Adventure #1: Mystery House - Apple II - 1987 PD release - .DSK format + { + "hires1", 0, + { + { "MYSTHOUS.DSK", 0, "34ba05e62bf51404c4475c349ca48921", 143360 }, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformApple2GS, // FIXME + ADGF_UNSTABLE, + GUIO2(GAMEOPTION_COLOR, GAMEOPTION_SCANLINES) + }, + GAME_TYPE_HIRES1 + }, + { // Hi-Res Adventure #2: Wizard and the Princess - Apple II - Roberta Williams Anthology + { + "hires2", 0, + { + { "WIZARD.DSK", 0, "816fdfc35e25496213c8db40ecf26569", 143360 }, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformApple2GS, // FIXME + ADGF_UNSTABLE, + GUIO2(GAMEOPTION_MONO, GAMEOPTION_SCANLINES) + }, + GAME_TYPE_HIRES2 + }, + { // Hi-Res Adventure #0: Mission Asteroid - Apple II - Roberta Williams Anthology + { + "hires0", 0, + { + { "MISSION.NIB", 0, "b158f6f79681d4edd651e1932f9e01d7", 232960 }, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformApple2GS, // FIXME + ADGF_UNSTABLE, + GUIO2(GAMEOPTION_MONO, GAMEOPTION_SCANLINES) + }, + GAME_TYPE_HIRES0 + }, + { // Hi-Res Adventure #6: The Dark Crystal - Apple II - Roberta Williams Anthology + { + "hires6", 0, + { + { "DARK1A.DSK", 0, "00c2646d6943d1405717332a6f42d493", 143360 }, + { "DARK2A.NIB", 0, "271eb92db107e8d5829437f8ba77991e", 232960 }, + { "DARK1B.NIB", 0, "dbedd736617343ade0e6bead8bf2b10c", 232960 }, + { "DARK2B.NIB", 0, "cb72044a9b391c4285f4752f746bea2e", 232960 }, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformApple2GS, // FIXME + ADGF_UNSTABLE, + GUIO2(GAMEOPTION_MONO, GAMEOPTION_SCANLINES) + }, + GAME_TYPE_HIRES6 + }, { AD_TABLE_END_MARKER, GAME_TYPE_NONE } }; @@ -220,6 +293,8 @@ void AdlMetaEngine::removeSaveState(const char *target, int slot) const { } Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd); +Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd); +Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd); bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { if (!gd) @@ -231,6 +306,12 @@ bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameD case GAME_TYPE_HIRES1: *engine = HiRes1Engine_create(syst, adlGd); break; + case GAME_TYPE_HIRES2: + *engine = HiRes2Engine_create(syst, adlGd); + break; + case GAME_TYPE_HIRES6: + *engine = HiRes6Engine_create(syst, adlGd); + break; default: error("Unknown GameType"); } diff --git a/engines/adl/detection.h b/engines/adl/detection.h index c646aeb5b9..533466c094 100644 --- a/engines/adl/detection.h +++ b/engines/adl/detection.h @@ -32,7 +32,10 @@ namespace Adl { enum GameType { GAME_TYPE_NONE, - GAME_TYPE_HIRES1 + GAME_TYPE_HIRES0, + GAME_TYPE_HIRES1, + GAME_TYPE_HIRES2, + GAME_TYPE_HIRES6 }; struct AdlGameDescription { diff --git a/engines/adl/disk.cpp b/engines/adl/disk.cpp new file mode 100644 index 0000000000..214f76aeae --- /dev/null +++ b/engines/adl/disk.cpp @@ -0,0 +1,460 @@ +/* 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/substream.h" +#include "common/memstream.h" + +#include "adl/disk.h" + +namespace Adl { + +const DataBlockPtr DiskImage_DSK::getDataBlock(uint track, uint sector, uint offset, uint size) const { + return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size)); +} + +Common::SeekableReadStream *DiskImage_DSK::createReadStream(uint track, uint sector, uint offset, uint size) const { + _f->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset); + Common::SeekableReadStream *stream = _f->readStream(size * _bytesPerSector + _bytesPerSector - offset); + + if (_f->eos() || _f->err()) + error("Error reading disk image"); + + return stream; +} + +bool DiskImage_DSK::open(const Common::String &filename) { + assert(!_f->isOpen()); + + if (!_f->open(filename)) + return false; + + uint filesize = _f->size(); + switch (filesize) { + case 143360: + _tracks = 35; + _sectorsPerTrack = 16; + _bytesPerSector = 256; + break; + default: + warning("Unrecognized disk image '%s' of size %d bytes", filename.c_str(), filesize); + return false; + } + + return true; +} + +const DataBlockPtr DiskImage_NIB::getDataBlock(uint track, uint sector, uint offset, uint size) const { + return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size)); +} + +Common::SeekableReadStream *DiskImage_NIB::createReadStream(uint track, uint sector, uint offset, uint size) const { + _memStream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset); + Common::SeekableReadStream *stream = _memStream->readStream(size * _bytesPerSector + _bytesPerSector - offset); + + if (_memStream->eos() || _memStream->err()) + error("Error reading NIB image"); + + return stream; +} + +// 4-and-4 encoding (odd-even) +static uint8 read44(Common::SeekableReadStream *f) { + // 1s in the other fields, so we can just AND + uint8 ret = f->readByte(); + return ((ret << 1) | 1) & f->readByte(); +} + +bool DiskImage_NIB::open(const Common::String &filename) { + assert(!_f->isOpen()); + + if (!_f->open(filename)) + return false; + + uint filesize = _f->size(); + switch (filesize) { + case 232960: + _tracks = 35; + _sectorsPerTrack = 16; // we always pad it out + _bytesPerSector = 256; + break; + default: + error("Unrecognized NIB image '%s' of size %d bytes", filename.c_str(), filesize); + } + + // starting at 0xaa, 32 is invalid (see below) + const byte c_5and3_lookup[] = { 32, 0, 32, 1, 2, 3, 32, 32, 32, 32, 32, 4, 5, 6, 32, 32, 7, 8, 32, 9, 10, 11, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 12, 13, 32, 32, 14, 15, 32, 16, 17, 18, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 20, 32, 21, 22, 23, 32, 32, 32, 32, 32, 24, 25, 26, 32, 32, 27, 28, 32, 29, 30, 31 }; + // starting at 0x96, 64 is invalid (see below) + const byte c_6and2_lookup[] = { 0, 1, 64, 64, 2, 3, 64, 4, 5, 6, 64, 64, 64, 64, 64, 64, 7, 8, 64, 64, 64, 9, 10, 11, 12, 13, 64, 64, 14, 15, 16, 17, 18, 19, 64, 20, 21, 22, 23, 24, 25, 26, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 27, 64, 28, 29, 30, 64, 64, 64, 31, 64, 64, 32, 33, 64, 34, 35, 36, 37, 38, 39, 40, 64, 64, 64, 64, 64, 41, 42, 43, 64, 44, 45, 46, 47, 48, 49, 50, 64, 64, 51, 52, 53, 54, 55, 56, 64, 57, 58, 59, 60, 61, 62, 63 }; + + uint32 diskSize = _tracks * _sectorsPerTrack * _bytesPerSector; + byte *diskImage = (byte *)calloc(diskSize, 1); + _memStream = new Common::MemoryReadStream(diskImage, diskSize, DisposeAfterUse::YES); + + bool sawAddress = false; + uint8 volNo, track, sector; + bool newStyle; + + while (_f->pos() < _f->size()) { + // Read until we find two sync bytes. + if (_f->readByte() != 0xd5 || _f->readByte() != 0xaa) + continue; + + byte prologue = _f->readByte(); + + if (sawAddress && (prologue == 0xb5 || prologue == 0x96)) { + warning("NIB: data for %02x/%02x/%02x missing", volNo, track, sector); + sawAddress = false; + } + + if (!sawAddress) { + sawAddress = true; + newStyle = false; + + // We should always find the address field first. + if (prologue != 0xb5) { + // Accept a DOS 3.3(?) header at the start. + if (prologue == 0x96) { + newStyle = true; + } else { + error("unknown NIB field prologue %02x", prologue); + } + } + + volNo = read44(_f); + track = read44(_f); + sector = read44(_f); + uint8 checksum = read44(_f); + if ((volNo ^ track ^ sector) != checksum) + error("invalid NIB checksum"); + + // FIXME: This is a hires0/hires2-specific hack. + if (volNo == 0xfe) { + if (track == 1) + track = 2; + else if (track == 2) + track = 1; + } + + // Epilogue is de/aa plus a gap, but we don't care. + continue; + } + + sawAddress = false; + + // We should always find the data field after an address field. + // TODO: we ignore volNo? + byte *output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector; + + if (newStyle) { + // We hardcode the DOS 3.3 mapping here. TODO: Do we also need raw/prodos? + int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 }; + sector = raw2dos[sector]; + output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector; + + // 6-and-2 uses 342 on-disk bytes + byte inbuffer[342]; + _f->read(inbuffer, 342); + + byte oldVal = 0; + for (uint n = 0; n < 342; ++n) { + // expand + assert(inbuffer[n] >= 0x96); // corrupt file (TODO: assert?) + byte val = c_6and2_lookup[inbuffer[n] - 0x96]; + if (val == 0x40) { + error("NIB: invalid nibble value %02x", inbuffer[n]); + } + // undo checksum + oldVal = val ^ oldVal; + inbuffer[n] = oldVal; + } + + byte checksum = _f->readByte(); + if (checksum < 0x96 || oldVal != c_6and2_lookup[checksum - 0x96]) + warning("NIB: checksum mismatch @ (%x, %x)", track, sector); + + for (uint n = 0; n < 256; ++n) { + output[n] = inbuffer[86 + n] << 2; + if (n < 86) { // use first pair of bits + output[n] |= ((inbuffer[n] & 1) << 1); + output[n] |= ((inbuffer[n] & 2) >> 1); + } else if (n < 86*2) { // second pair + output[n] |= ((inbuffer[n-86] & 4) >> 1); + output[n] |= ((inbuffer[n-86] & 8) >> 3); + } else { // third pair + output[n] |= ((inbuffer[n-86*2] & 0x10) >> 3); + output[n] |= ((inbuffer[n-86*2] & 0x20) >> 5); + } + } + } else { + // 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes + byte inbuffer[410]; + _f->read(inbuffer, 410); + + bool truncated = false; + byte oldVal = 0; + for (uint n = 0; n < 410; ++n) { + // expand + assert(inbuffer[n] >= 0xaa); // corrupt file (TODO: assert?) + if (inbuffer[n] == 0xd5) { + // Early end of block. + truncated = true; + _f->seek(-(410 - n), SEEK_CUR); + warning("NIB: early end of block @ 0x%x (%x, %x)", _f->pos(), track, sector); + break; + } + byte val = c_5and3_lookup[inbuffer[n] - 0xaa]; + if (val == 0x20) { + // Badly-encoded nibbles, stop trying to decode here. + truncated = true; + warning("NIB: bad nibble %02x @ 0x%x (%x, %x)", inbuffer[n], _f->pos(), track, sector); + _f->seek(-(410 - n), SEEK_CUR); + break; + } + // undo checksum + oldVal = val ^ oldVal; + inbuffer[n] = oldVal; + } + if (!truncated) { + byte checksum = _f->readByte(); + if (checksum < 0xaa || oldVal != c_5and3_lookup[checksum - 0xaa]) + warning("NIB: checksum mismatch @ (%x, %x)", track, sector); + } + + // 8 bytes of nibbles expand to 5 bytes + // so we have 51 of these batches (255 bytes), plus 2 bytes of 'leftover' nibbles for byte 256 + for (uint n = 0; n < 51; ++n) { + // e.g. figure 3.18 of Beneath Apple DOS + byte lowbits1 = inbuffer[51*3 - n]; + byte lowbits2 = inbuffer[51*2 - n]; + byte lowbits3 = inbuffer[51*1 - n]; + byte lowbits4 = (lowbits1 & 2) << 1 | (lowbits2 & 2) | (lowbits3 & 2) >> 1; + byte lowbits5 = (lowbits1 & 1) << 2 | (lowbits2 & 1) << 1 | (lowbits3 & 1); + output[250 - 5*n] = (inbuffer[n + 51*3 + 1] << 3) | ((lowbits1 >> 2) & 0x7); + output[251 - 5*n] = (inbuffer[n + 51*4 + 1] << 3) | ((lowbits2 >> 2) & 0x7); + output[252 - 5*n] = (inbuffer[n + 51*5 + 1] << 3) | ((lowbits3 >> 2) & 0x7); + output[253 - 5*n] = (inbuffer[n + 51*6 + 1] << 3) | lowbits4; + output[254 - 5*n] = (inbuffer[n + 51*7 + 1] << 3) | lowbits5; + } + output[255] = (inbuffer[409] << 3) | (inbuffer[0] & 0x7); + } + } + + return true; +} + +const DataBlockPtr Files_Plain::getDataBlock(const Common::String &filename, uint offset) const { + return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset)); +} + +Common::SeekableReadStream *Files_Plain::createReadStream(const Common::String &filename, uint offset) const { + Common::File *f(new Common::File()); + + if (!f->open(filename)) + error("Failed to open '%s'", filename.c_str()); + + if (offset == 0) + return f; + else + return new Common::SeekableSubReadStream(f, offset, f->size(), DisposeAfterUse::YES); +} + +Files_DOS33::~Files_DOS33() { + delete _disk; +} + +Files_DOS33::Files_DOS33() : + _disk(nullptr) { +} + +void Files_DOS33::readSectorList(TrackSector start, Common::Array<TrackSector> &list) { + TrackSector index = start; + + while (index.track != 0) { + Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(index.track, index.sector)); + + stream->readByte(); + index.track = stream->readByte(); + index.sector = stream->readByte(); + + stream->seek(9, SEEK_CUR); + + // This only handles sequential files + TrackSector ts; + ts.track = stream->readByte(); + ts.sector = stream->readByte(); + + while (ts.track != 0) { + list.push_back(ts); + + ts.track = stream->readByte(); + ts.sector = stream->readByte(); + + if (stream->err()) + error("Error reading sector list"); + + if (stream->eos()) + break; + } + } +} + +void Files_DOS33::readVTOC() { + Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(0x11, 0x00)); + stream->readByte(); + byte track = stream->readByte(); + byte sector = stream->readByte(); + + while (track != 0) { + char name[kFilenameLen + 1] = { }; + + stream.reset(_disk->createReadStream(track, sector)); + stream->readByte(); + track = stream->readByte(); + sector = stream->readByte(); + stream->seek(8, SEEK_CUR); + + for (uint i = 0; i < 7; ++i) { + TOCEntry entry; + TrackSector sectorList; + sectorList.track = stream->readByte(); + sectorList.sector = stream->readByte(); + entry.type = stream->readByte(); + stream->read(name, kFilenameLen); + + // Convert to ASCII + for (uint j = 0; j < kFilenameLen; j++) + name[j] &= 0x7f; + + // Strip trailing spaces + for (int j = kFilenameLen - 1; j >= 0; --j) { + if (name[j] == ' ') + name[j] = 0; + else + break; + } + + entry.totalSectors = stream->readUint16BE(); + + if (sectorList.track != 0) { + readSectorList(sectorList, entry.sectors); + _toc[name] = entry; + } + } + } +} + +const DataBlockPtr Files_DOS33::getDataBlock(const Common::String &filename, uint offset) const { + return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset)); +} + +Common::SeekableReadStream *Files_DOS33::createReadStreamText(const TOCEntry &entry) const { + byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize); + byte *p = buf; + + for (uint i = 0; i < entry.sectors.size(); ++i) { + Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[i].track, entry.sectors[i].sector)); + + assert(stream->size() == kSectorSize); + + while (true) { + byte textChar = stream->readByte(); + + if (stream->eos() || textChar == 0) + break; + + if (stream->err()) + error("Error reading text file"); + + *p++ = textChar; + } + } + + return new Common::MemoryReadStream(buf, p - buf, DisposeAfterUse::YES); +} + +Common::SeekableReadStream *Files_DOS33::createReadStreamBinary(const TOCEntry &entry) const { + byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize); + + Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[0].track, entry.sectors[0].sector)); + + if (entry.type == kFileTypeBinary) + stream->readUint16LE(); // Skip start address + + uint16 size = stream->readUint16LE(); + uint16 offset = 0; + uint16 sectorIdx = 1; + + while (true) { + offset += stream->read(buf + offset, size - offset); + + if (offset == size) + break; + + if (stream->err()) + error("Error reading binary file"); + + assert(stream->eos()); + + if (sectorIdx == entry.sectors.size()) + error("Not enough sectors for binary file size"); + + stream.reset(_disk->createReadStream(entry.sectors[sectorIdx].track, entry.sectors[sectorIdx].sector)); + ++sectorIdx; + } + + return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES); +} + +Common::SeekableReadStream *Files_DOS33::createReadStream(const Common::String &filename, uint offset) const { + if (!_toc.contains(filename)) + error("Failed to locate '%s'", filename.c_str()); + + const TOCEntry &entry = _toc[filename]; + + Common::SeekableReadStream *stream; + + switch(entry.type) { + case kFileTypeText: + stream = createReadStreamText(entry); + break; + case kFileTypeAppleSoft: + case kFileTypeBinary: + stream = createReadStreamBinary(entry); + break; + default: + error("Unsupported file type %i", entry.type); + } + + return new Common::SeekableSubReadStream(stream, offset, stream->size(), DisposeAfterUse::YES); +} + +bool Files_DOS33::open(const Common::String &filename) { + _disk = new DiskImage_DSK(); + if (!_disk->open(filename)) + return false; + + readVTOC(); + return true; +} + +} // End of namespace Adl diff --git a/engines/adl/disk.h b/engines/adl/disk.h new file mode 100644 index 0000000000..43b9e387ba --- /dev/null +++ b/engines/adl/disk.h @@ -0,0 +1,188 @@ +/* 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/ptr.h" +#include "common/file.h" +#include "common/debug.h" + +#ifndef ADL_DISK_H +#define ADL_DISK_H + +namespace Common { +class SeekableReadStream; +class String; +} + +namespace Adl { + +class DataBlock { +public: + virtual ~DataBlock() { } + + virtual Common::SeekableReadStream *createReadStream() const = 0; +}; + +typedef Common::SharedPtr<DataBlock> DataBlockPtr; +typedef Common::ScopedPtr<Common::SeekableReadStream> StreamPtr; + +class Files { +public: + virtual ~Files() { } + + virtual const DataBlockPtr getDataBlock(const Common::String &filename, uint offset = 0) const = 0; + virtual Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const = 0; + +protected: + class DataBlock : public Adl::DataBlock { + public: + DataBlock(const Files *files, const Common::String &filename, uint offset) : + _files(files), + _filename(filename), + _offset(offset) { } + + Common::SeekableReadStream *createReadStream() const { + return _files->createReadStream(_filename, _offset); + } + + private: + const Common::String _filename; + uint _offset; + const Files *_files; + }; +}; + +class DiskImage { +public: + DiskImage() : + _tracks(0), + _sectorsPerTrack(0), + _bytesPerSector(0) { + _f = new Common::File(); + } + + virtual ~DiskImage() { + delete _f; + } + + virtual bool open(const Common::String &filename) = 0; + virtual const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const = 0; + virtual Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const = 0; + +protected: + class DataBlock : public Adl::DataBlock { + public: + DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size) : + _track(track), + _sector(sector), + _offset(offset), + _size(size), + _disk(disk) { } + + Common::SeekableReadStream *createReadStream() const { + return _disk->createReadStream(_track, _sector, _offset, _size); + } + + private: + uint _track, _sector, _offset, _size; + const DiskImage *_disk; + }; + + Common::File *_f; + uint _tracks, _sectorsPerTrack, _bytesPerSector; +}; + +// Data in plain files +class Files_Plain : public Files { +public: + const DataBlockPtr getDataBlock(const Common::String &filename, uint offset = 0) const; + Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const; +}; + +// .DSK disk image - 35 tracks, 16 sectors per track, 256 bytes per sector +class DiskImage_DSK : public DiskImage { +public: + bool open(const Common::String &filename); + const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const; + Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const; +}; + +// .NIB disk image +class DiskImage_NIB : public DiskImage { +public: + DiskImage_NIB() : _memStream(nullptr) { } + virtual ~DiskImage_NIB() { + delete _memStream; + } + + bool open(const Common::String &filename); + const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const; + Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const; + +private: + Common::SeekableReadStream *_memStream; +}; + +// Data in files contained in Apple DOS 3.3 disk image +class Files_DOS33 : public Files { +public: + Files_DOS33(); + ~Files_DOS33(); + + bool open(const Common::String &filename); + const DataBlockPtr getDataBlock(const Common::String &filename, uint offset = 0) const; + Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const; + +private: + enum FileType { + kFileTypeText = 0, + kFileTypeAppleSoft = 2, + kFileTypeBinary = 4 + }; + + enum { + kSectorSize = 256, + kFilenameLen = 30 + }; + + struct TrackSector { + byte track; + byte sector; + }; + + struct TOCEntry { + byte type; + uint16 totalSectors; + Common::Array<TrackSector> sectors; + }; + + void readVTOC(); + void readSectorList(TrackSector start, Common::Array<TrackSector> &list); + Common::SeekableReadStream *createReadStreamText(const TOCEntry &entry) const; + Common::SeekableReadStream *createReadStreamBinary(const TOCEntry &entry) const; + + DiskImage *_disk; + Common::HashMap<Common::String, TOCEntry> _toc; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp index 6342504bc3..b7f6eb9923 100644 --- a/engines/adl/display.cpp +++ b/engines/adl/display.cpp @@ -41,8 +41,6 @@ namespace Adl { #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 @@ -221,17 +219,35 @@ void Display::loadFrameBuffer(Common::ReadStream &stream) { void Display::putPixel(const Common::Point &p, byte color) { byte offset = p.x / 7; + byte mask = 0x80 | (1 << (p.x % 7)); + // Since white and black are in both palettes, we leave + // the palette bit alone + if ((color & 0x7f) == 0x7f || (color & 0x7f) == 0) + mask &= 0x7f; + + // Adjust colors starting with bits '01' or '10' for + // odd offsets 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; + writeFrameBuffer(p, color, mask); +} + +void Display::setPixelBit(const Common::Point &p, byte color) { + writeFrameBuffer(p, color, 1 << (p.x % 7)); +} + +void Display::setPixelPalette(const Common::Point &p, byte color) { + writeFrameBuffer(p, color, 0x80); +} + +bool Display::getPixelBit(const Common::Point &p) const { + byte *b = _frameBuf + p.y * DISPLAY_PITCH + p.x / 7; + return *b & (1 << (p.x % 7)); } void Display::clear(byte color) { @@ -271,21 +287,23 @@ void Display::moveCursorTo(const Common::Point &pos) { error("Cursor position (%i, %i) out of bounds", pos.x, pos.y); } +// FIXME: This does not currently update the surfaces +void Display::printChar(char c) { + if (c == APPLECHAR('\r')) + _cursorPos = (_cursorPos / TEXT_WIDTH + 1) * TEXT_WIDTH; + else if ((byte)c < 0x80 || (byte)c >= 0xa0) { + setCharAtCursor(c); + ++_cursorPos; + } + + if (_cursorPos == TEXT_BUF_SIZE) + scrollUp(); +} + 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(); - } + for (c = str.begin(); c != str.end(); ++c) + printChar(*c); updateTextScreen(); } @@ -308,6 +326,13 @@ void Display::showCursor(bool enable) { _showCursor = enable; } +void Display::writeFrameBuffer(const Common::Point &p, byte color, byte mask) { + byte *b = _frameBuf + p.y * DISPLAY_PITCH + p.x / 7; + color ^= *b; + color &= mask; + *b ^= color; +} + void Display::showScanlines(bool enable) { byte pal[COLOR_PALETTE_ENTRIES * 3] = { }; diff --git a/engines/adl/display.h b/engines/adl/display.h index ff01e3faf2..bc27b7cb6b 100644 --- a/engines/adl/display.h +++ b/engines/adl/display.h @@ -40,6 +40,8 @@ namespace Adl { #define DISPLAY_WIDTH 280 #define DISPLAY_HEIGHT 192 +#define TEXT_WIDTH 40 +#define TEXT_HEIGHT 24 enum DisplayMode { DISPLAY_MODE_HIRES, @@ -62,6 +64,9 @@ public: // Graphics void loadFrameBuffer(Common::ReadStream &stream); void putPixel(const Common::Point &p, byte color); + void setPixelBit(const Common::Point &p, byte color); + void setPixelPalette(const Common::Point &p, byte color); + bool getPixelBit(const Common::Point &p) const; void clear(byte color); // Text @@ -69,12 +74,14 @@ public: void moveCursorTo(const Common::Point &pos); void moveCursorForward(); void moveCursorBackward(); + void printChar(char c); void printString(const Common::String &str); void printAsciiString(const Common::String &str); void setCharAtCursor(byte c); void showCursor(bool enable); private: + void writeFrameBuffer(const Common::Point &p, byte color, byte mask); void updateHiResSurface(); void showScanlines(bool enable); diff --git a/engines/adl/graphics.cpp b/engines/adl/graphics.cpp new file mode 100644 index 0000000000..f9af442a9f --- /dev/null +++ b/engines/adl/graphics.cpp @@ -0,0 +1,69 @@ +/* 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 "adl/display.h" +#include "adl/graphics.h" + +namespace Adl { + +// Draws a four-connected line +void GraphicsMan::drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const { + 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 (true) { + _display.putPixel(p, color); + + if (--steps == 0) + return; + + if (err < 0) { + p.y += yStep; + err += deltaX; + } else { + p.x += xStep; + err += deltaY; + } + } +} + +} // End of namespace Adl diff --git a/engines/adl/graphics.h b/engines/adl/graphics.h new file mode 100644 index 0000000000..aab807696c --- /dev/null +++ b/engines/adl/graphics.h @@ -0,0 +1,76 @@ +/* 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_PICTURE_H +#define ADL_PICTURE_H + +namespace Common { +class SeekableReadStream; +struct Point; +} + +namespace Adl { + +class Display; + +class GraphicsMan { +public: + virtual ~GraphicsMan() { } + virtual void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) = 0; + +protected: + GraphicsMan(Display &display) : _display(display) { } + void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const; + + Display &_display; +}; + +class Graphics_v1 : public GraphicsMan { +public: + Graphics_v1(Display &display) : GraphicsMan(display) { } + void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos); + void drawCorners(Common::ReadStream &corners, const Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const; + +private: + void drawCornerPixel(Common::Point &p, byte color, byte bits, byte quadrant) const; +}; + +class Graphics_v2 : public GraphicsMan { +public: + Graphics_v2(Display &display) : GraphicsMan(display), _color(0) { } + void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos); + +private: + void clear(); + void drawCorners(Common::SeekableReadStream &pic, bool yFirst); + void drawRelativeLines(Common::SeekableReadStream &pic); + void drawAbsoluteLines(Common::SeekableReadStream &pic); + void fillRow(const Common::Point &p, bool fillBit, byte pattern); + void fill(Common::SeekableReadStream &pic); + + byte _color; + Common::Point _offset; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/graphics_v1.cpp b/engines/adl/graphics_v1.cpp new file mode 100644 index 0000000000..29c5ef47af --- /dev/null +++ b/engines/adl/graphics_v1.cpp @@ -0,0 +1,120 @@ +/* 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/textconsole.h" + +#include "adl/display.h" +#include "adl/graphics.h" + +namespace Adl { + +void Graphics_v1::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) { + byte x, y; + bool bNewLine = false; + byte oldX = 0, oldY = 0; + while (1) { + x = pic.readByte(); + y = pic.readByte(); + + if (pic.err() || pic.eos()) + error("Error reading picture"); + + if (x == 0xff && y == 0xff) + return; + + if (x == 0 && y == 0) { + bNewLine = true; + continue; + } + + x += pos.x; + y += pos.y; + + if (y > 160) + y = 160; + + if (bNewLine) { + _display.putPixel(Common::Point(x, y), 0x7f); + bNewLine = false; + } else { + drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f); + } + + oldX = x; + oldY = y; + } +} + +void Graphics_v1::drawCornerPixel(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 Graphics_v1::drawCorners(Common::ReadStream &corners, 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); + + while (true) { + byte b = corners.readByte(); + + if (corners.eos() || corners.err()) + error("Error reading corners"); + + if (b == 0) + return; + + do { + byte xFrac = 0x80; + byte yFrac = 0x80; + for (uint j = 0; j < scaling; ++j) { + if (xFrac + xStep + 1 > 255) + drawCornerPixel(p, color, b, quadrant); + xFrac += xStep + 1; + if (yFrac + yStep > 255) + drawCornerPixel(p, color, b, quadrant + 1); + yFrac += yStep; + } + b >>= 3; + } while (b != 0); + } +} + +} // End of namespace Adl diff --git a/engines/adl/graphics_v2.cpp b/engines/adl/graphics_v2.cpp new file mode 100644 index 0000000000..40936f5b7d --- /dev/null +++ b/engines/adl/graphics_v2.cpp @@ -0,0 +1,304 @@ +/* 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/textconsole.h" + +#include "adl/display.h" +#include "adl/graphics.h" +#include "adl/adl.h" + +namespace Adl { + +// FIXME: Add clipping + +#define NUM_PATTERNS 22 +#define PATTERN_LEN 4 +static const byte fillPatterns[NUM_PATTERNS][PATTERN_LEN] = { + { 0x00, 0x00, 0x00, 0x00 }, + { 0x80, 0x80, 0x80, 0x80 }, + { 0xff, 0xff, 0xff, 0xff }, + { 0x7f, 0x7f, 0x7f, 0x7f }, + { 0x2a, 0x55, 0x2a, 0x55 }, + { 0xaa, 0xd5, 0xaa, 0xd5 }, + { 0x55, 0x2a, 0x55, 0x2a }, + { 0xd5, 0xaa, 0xd5, 0xaa }, + { 0x33, 0x66, 0x4c, 0x19 }, + { 0xb3, 0xe6, 0xcc, 0x99 }, + { 0x22, 0x44, 0x08, 0x11 }, + { 0xa2, 0xc4, 0x88, 0x91 }, + { 0x11, 0x22, 0x44, 0x08 }, + { 0x91, 0xa2, 0xc4, 0x88 }, + { 0x6e, 0x5d, 0x3b, 0x77 }, + { 0xee, 0xdd, 0xbb, 0xf7 }, + { 0x5d, 0x3b, 0x77, 0x6e }, + { 0xdd, 0xbb, 0xf7, 0xee }, + { 0x66, 0x4c, 0x19, 0x33 }, + { 0xe6, 0xcc, 0x99, 0xb3 }, + { 0x33, 0x66, 0x4c, 0x19 }, + { 0xb3, 0xe6, 0xcc, 0x99 } +}; + +#define MIN_COMMAND 0xe0 + +#define CHECK_COMMAND(X) \ + do { \ + if ((X) >= MIN_COMMAND) { \ + pic.seek(-1, SEEK_CUR); \ + return; \ + } \ + } while (0) + +#define READ_BYTE(b) \ + do { \ + b = pic.readByte(); \ + if (pic.eos() || pic.err()) \ + error("Error reading picture"); \ + CHECK_COMMAND(b); \ + } while (0) + +#define READ_POINT(p) \ + do { \ + READ_BYTE(p.x); \ + p.x += _offset.x; \ + p.x <<= 1; \ + READ_BYTE(p.y); \ + p.y += _offset.y; \ + } while (0) + +void Graphics_v2::clear() { + _display.clear(0xff); + _color = 0; +} + +void Graphics_v2::drawCorners(Common::SeekableReadStream &pic, bool yFirst) { + Common::Point p; + + READ_POINT(p); + + if (yFirst) + goto doYStep; + + while (true) { + int16 n; + + READ_BYTE(n); + n += _offset.x; + + _display.putPixel(p, _color); + + n <<= 1; + drawLine(p, Common::Point(n, p.y), _color); + p.x = n; + +doYStep: + READ_BYTE(n); + n += _offset.y; + + _display.putPixel(p, _color); + drawLine(p, Common::Point(p.x, n), _color); + + _display.putPixel(Common::Point(p.x + 1, p.y), _color); + drawLine(Common::Point(p.x + 1, p.y), Common::Point(p.x + 1, n), _color); + + p.y = n; + } +} + +void Graphics_v2::drawRelativeLines(Common::SeekableReadStream &pic) { + Common::Point p1; + + READ_POINT(p1); + _display.putPixel(p1, _color); + + while (true) { + Common::Point p2(p1); + + byte n; + READ_BYTE(n); + + byte h = (n & 0x70) >> 4; + byte l = n & 7; + + if (n & 0x80) + p2.x -= (h << 1); + else + p2.x += (h << 1); + + if (n & 8) + p2.y -= l; + else + p2.y += l; + + drawLine(p1, p2, _color); + p1 = p2; + } +} + +void Graphics_v2::drawAbsoluteLines(Common::SeekableReadStream &pic) { + Common::Point p1; + + READ_POINT(p1); + _display.putPixel(p1, _color); + + while (true) { + Common::Point p2; + + READ_POINT(p2); + drawLine(p1, p2, _color); + p1 = p2; + } +} + +static byte getPatternColor(const Common::Point &p, byte pattern) { + if (pattern >= NUM_PATTERNS) + error("Invalid fill pattern %i encountered in picture", pattern); + + byte offset = (p.y & 1) << 1; + offset += (p.x / 7) & 3; + + return fillPatterns[pattern][offset % PATTERN_LEN]; +} + +void Graphics_v2::fillRow(const Common::Point &p, bool stopBit, byte pattern) { + const byte color = getPatternColor(p, pattern); + _display.setPixelPalette(p, color); + _display.setPixelBit(p, color); + + Common::Point q(p); + byte c = color; + + while (++q.x < DISPLAY_WIDTH) { + if ((q.x % 7) == 0) { + c = getPatternColor(q, pattern); + // Palette is set before the first bit is tested + _display.setPixelPalette(q, c); + } + if (_display.getPixelBit(q) == stopBit) + break; + _display.setPixelBit(q, c); + } + + q = p; + c = color; + while (--q.x >= 0) { + if ((q.x % 7) == 6) { + c = getPatternColor(q, pattern); + _display.setPixelPalette(q, c); + } + if (_display.getPixelBit(q) == stopBit) + break; + _display.setPixelBit(q, c); + } +} + +// Basic flood fill +void Graphics_v2::fill(Common::SeekableReadStream &pic) { + byte pattern; + READ_BYTE(pattern); + + while (true) { + Common::Point p; + READ_POINT(p); + + bool stopBit = !_display.getPixelBit(p); + + while (--p.y >= 0) { + if (_display.getPixelBit(p) == stopBit) + break; + if (_display.getPixelBit(Common::Point(p.x + 1, p.y)) == stopBit) + break; + } + + while (++p.y < DISPLAY_HEIGHT) { + if (_display.getPixelBit(p) == stopBit) + break; + if (_display.getPixelBit(Common::Point(p.x + 1, p.y)) == stopBit) + break; + fillRow(p, stopBit, pattern); + } + } +} + +void Graphics_v2::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) { + _color = 0; + _offset = pos; + + while (true) { + byte opcode = pic.readByte(); + + if (pic.eos() || pic.err()) + error("Error reading picture"); + + switch (opcode) { + case 0xe0: + drawCorners(pic, false); + break; + case 0xe1: + drawCorners(pic, true); + break; + case 0xe2: + drawRelativeLines(pic); + break; + case 0xe3: + drawAbsoluteLines(pic); + break; + case 0xe4: + fill(pic); + break; + case 0xe5: + clear(); + break; + case 0xf0: + _color = 0x00; + break; + case 0xf1: + _color = 0x2a; + break; + case 0xf2: + _color = 0x55; + break; + case 0xf3: + _color = 0x7f; + break; + case 0xf4: + _color = 0x80; + break; + case 0xf5: + _color = 0xaa; + break; + case 0xf6: + _color = 0xd5; + break; + case 0xf7: + _color = 0xff; + break; + case 0xff: + return; + default: + error("Invalid pic opcode %02x", opcode); + } + } +} + +} // End of namespace Adl diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp index 6e1e31df9f..096d8ef496 100644 --- a/engines/adl/hires1.cpp +++ b/engines/adl/hires1.cpp @@ -25,6 +25,7 @@ #include "common/error.h" #include "common/file.h" #include "common/stream.h" +#include "common/ptr.h" #include "adl/hires1.h" #include "adl/display.h" @@ -32,14 +33,11 @@ namespace Adl { void HiRes1Engine::runIntro() const { - Common::File file; + StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0)); - if (!file.open(IDS_HR1_EXE_0)) - error("Failed to open file '" IDS_HR1_EXE_0 "'"); - - file.seek(IDI_HR1_OFS_LOGO_0); + stream->seek(IDI_HR1_OFS_LOGO_0); _display->setMode(DISPLAY_MODE_HIRES); - _display->loadFrameBuffer(file); + _display->loadFrameBuffer(*stream); _display->updateHiResScreen(); delay(4000); @@ -48,22 +46,19 @@ void HiRes1Engine::runIntro() const { _display->setMode(DISPLAY_MODE_TEXT); - Common::File basic; - if (!basic.open(IDS_HR1_LOADER)) - error("Failed to open file '" IDS_HR1_LOADER "'"); - + StreamPtr basic(_files->createReadStream(IDS_HR1_LOADER)); Common::String str; - str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_0, '"'); + str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_0, '"'); _display->printAsciiString(str + '\r'); - str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_1, '"'); + str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_1, '"'); _display->printAsciiString(str + "\r\r"); - str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_2, '"'); + str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_2, '"'); _display->printAsciiString(str + "\r\r"); - str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_3, '"'); + str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_3, '"'); _display->printAsciiString(str + '\r'); inputKey(); @@ -72,7 +67,7 @@ void HiRes1Engine::runIntro() const { _display->setMode(DISPLAY_MODE_MIXED); - str = readStringAt(file, IDI_HR1_OFS_GAME_OR_HELP); + str = readStringAt(*stream, IDI_HR1_OFS_GAME_OR_HELP); bool instructions = false; @@ -96,7 +91,7 @@ void HiRes1Engine::runIntro() const { if (instructions) { _display->setMode(DISPLAY_MODE_TEXT); - file.seek(IDI_HR1_OFS_INTRO_TEXT); + stream->seek(IDI_HR1_OFS_INTRO_TEXT); const uint pages[] = { 6, 6, 4, 5, 8, 7, 0 }; @@ -106,9 +101,9 @@ void HiRes1Engine::runIntro() const { uint count = pages[page++]; for (uint i = 0; i < count; ++i) { - str = readString(file); + str = readString(*stream); _display->printString(str); - file.seek(3, SEEK_CUR); + stream->seek(3, SEEK_CUR); } inputString(); @@ -116,54 +111,47 @@ void HiRes1Engine::runIntro() const { if (g_engine->shouldQuit()) return; - file.seek(6, SEEK_CUR); + stream->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); + stream.reset(_files->createReadStream(IDS_HR1_EXE_1)); + stream->seek(IDI_HR1_OFS_LOGO_1); + _display->loadFrameBuffer(*stream); _display->updateHiResScreen(); delay(2000); } -void HiRes1Engine::loadData() { - Common::File f; - - if (!f.open(IDS_HR1_MESSAGES)) - error("Failed to open file '" IDS_HR1_MESSAGES "'"); +void HiRes1Engine::init() { + if (Common::File::exists("MYSTHOUS.DSK")) { + _files = new Files_DOS33(); + if (!static_cast<Files_DOS33 *>(_files)->open("MYSTHOUS.DSK")) + error("Failed to open MYSTHOUS.DSK"); + } else + _files = new Files_Plain(); - for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i) - _messages.push_back(readString(f, APPLECHAR('\r')) + APPLECHAR('\r')); + _graphics = new Graphics_v1(*_display); - f.close(); - - if (!f.open(IDS_HR1_EXE_1)) - error("Failed to open file '" IDS_HR1_EXE_1 "'"); + StreamPtr stream(_files->createReadStream(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); + _gameStrings.cantGoThere = readStringAt(*stream, IDI_HR1_OFS_STR_CANT_GO_THERE); + _gameStrings.dontHaveIt = readStringAt(*stream, IDI_HR1_OFS_STR_DONT_HAVE_IT); + _gameStrings.dontUnderstand = readStringAt(*stream, IDI_HR1_OFS_STR_DONT_UNDERSTAND); + _gameStrings.gettingDark = readStringAt(*stream, 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); + _strings.enterCommand = readStringAt(*stream, IDI_HR1_OFS_STR_ENTER_COMMAND); + _strings.verbError = readStringAt(*stream, IDI_HR1_OFS_STR_VERB_ERROR); + _strings.nounError = readStringAt(*stream, IDI_HR1_OFS_STR_NOUN_ERROR); + _strings.playAgain = readStringAt(*stream, IDI_HR1_OFS_STR_PLAY_AGAIN); + _strings.pressReturn = readStringAt(*stream, IDI_HR1_OFS_STR_PRESS_RETURN); + _strings.lineFeeds = readStringAt(*stream, IDI_HR1_OFS_STR_LINE_FEEDS); // Set message IDs _messageIds.cantGoThere = IDI_HR1_MSG_CANT_GO_THERE; @@ -172,129 +160,118 @@ void HiRes1Engine::loadData() { _messageIds.itemNotHere = IDI_HR1_MSG_ITEM_NOT_HERE; _messageIds.thanksForPlaying = IDI_HR1_MSG_THANKS_FOR_PLAYING; + // Load message offsets + stream->seek(IDI_HR1_OFS_MSGS); + for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i) + _messages.push_back(_files->getDataBlock(IDS_HR1_MESSAGES, stream->readUint16LE())); + // 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); + stream->seek(IDI_HR1_OFS_PICS); + for (uint i = 1; i <= IDI_HR1_NUM_PICS; ++i) { + byte block = stream->readByte(); + Common::String name = Common::String::format("BLOCK%i", block); + uint16 offset = stream->readUint16LE(); + _pictures[i] = _files->getDataBlock(name, offset); } // Load commands from executable - f.seek(IDI_HR1_OFS_CMDS_1); - readCommands(f, _roomCommands); + stream->seek(IDI_HR1_OFS_CMDS_1); + readCommands(*stream, _roomCommands); - f.seek(IDI_HR1_OFS_CMDS_0); - readCommands(f, _globalCommands); + stream->seek(IDI_HR1_OFS_CMDS_0); + readCommands(*stream, _globalCommands); // Load dropped item offsets - f.seek(IDI_HR1_OFS_ITEM_OFFSETS); + stream->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(); + p.x = stream->readByte(); + p.y = stream->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); - } + stream->seek(IDI_HR1_OFS_CORNERS); + uint16 cornersCount = stream->readUint16LE(); + for (uint i = 0; i < cornersCount; ++i) + _corners.push_back(_files->getDataBlock(IDS_HR1_EXE_1, IDI_HR1_OFS_CORNERS + stream->readUint16LE())); - if (f.eos() || f.err()) + if (stream->eos() || stream->err()) error("Failed to read game data from '" IDS_HR1_EXE_1 "'"); - f.seek(IDI_HR1_OFS_VERBS); - loadWords(f, _verbs); + stream->seek(IDI_HR1_OFS_VERBS); + loadWords(*stream, _verbs, _priVerbs); - f.seek(IDI_HR1_OFS_NOUNS); - loadWords(f, _nouns); + stream->seek(IDI_HR1_OFS_NOUNS); + loadWords(*stream, _nouns, _priNouns); } -void HiRes1Engine::initState() { - Common::File f; - - _state.room = 1; - _state.moves = 0; - _state.isDark = false; - - _state.vars.clear(); +void HiRes1Engine::initGameState() { _state.vars.resize(IDI_HR1_NUM_VARS); - if (!f.open(IDS_HR1_EXE_1)) - error("Failed to open file '" IDS_HR1_EXE_1 "'"); + StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1)); // Load room data from executable - _state.rooms.clear(); - f.seek(IDI_HR1_OFS_ROOMS); + _roomDesc.clear(); + stream->seek(IDI_HR1_OFS_ROOMS); for (uint i = 0; i < IDI_HR1_NUM_ROOMS; ++i) { Room room; - f.readByte(); - room.description = f.readByte(); + stream->readByte(); + _roomDesc.push_back(stream->readByte()); for (uint j = 0; j < 6; ++j) - room.connections[j] = f.readByte(); - room.picture = f.readByte(); - room.curPicture = f.readByte(); + room.connections[j] = stream->readByte(); + room.picture = stream->readByte(); + room.curPicture = stream->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(); + stream->seek(IDI_HR1_OFS_ITEMS); + byte id; + while ((id = stream->readByte()) != 0xff) { + Item item = Item(); + item.id = id; + item.noun = stream->readByte(); + item.room = stream->readByte(); + item.picture = stream->readByte(); + item.isLineArt = stream->readByte(); + item.position.x = stream->readByte(); + item.position.y = stream->readByte(); + item.state = stream->readByte(); + item.description = stream->readByte(); + + stream->readByte(); + + byte size = stream->readByte(); for (uint i = 0; i < size; ++i) - item.roomPictures.push_back(f.readByte()); + item.roomPictures.push_back(stream->readByte()); _state.items.push_back(item); } } void HiRes1Engine::restartGame() { + _display->printString(_strings.pressReturn); initState(); - _display->printString(_gameStrings.pressReturn); - inputString(); // Missing in the original - _display->printAsciiString("\r\r\r\r\r"); + _display->printAsciiString(_strings.lineFeeds); } -void HiRes1Engine::drawPic(byte pic, Common::Point pos) const { - Common::File f; - Common::String name = Common::String::format("BLOCK%i", _pictures[pic].block); +void HiRes1Engine::printString(const Common::String &str) { + Common::String wrap = str; + wordWrap(wrap); + _display->printString(wrap); - if (!f.open(name)) - error("Failed to open file '%s'", name.c_str()); + if (_messageDelay) + delay(14 * 166018 / 1000); +} - f.seek(_pictures[pic].offset); - drawPic(f, pos); +Common::String HiRes1Engine::loadMessage(uint idx) const { + StreamPtr stream(_messages[idx]->createReadStream()); + return readString(*stream, APPLECHAR('\r')) + APPLECHAR('\r'); } -void HiRes1Engine::printMessage(uint idx, bool wait) const { +void HiRes1Engine::printMessage(uint idx) { // 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 @@ -304,88 +281,89 @@ void HiRes1Engine::printMessage(uint idx, bool wait) const { // that system for this game as well. switch (idx) { case IDI_HR1_MSG_CANT_GO_THERE: + _display->printString(_gameStrings.cantGoThere); + return; case IDI_HR1_MSG_DONT_HAVE_IT: + _display->printString(_gameStrings.dontHaveIt); + return; case IDI_HR1_MSG_DONT_UNDERSTAND: + _display->printString(_gameStrings.dontUnderstand); + return; case IDI_HR1_MSG_GETTING_DARK: - wait = false; + _display->printString(_gameStrings.gettingDark); + return; + default: + printString(loadMessage(idx)); } - - 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; +void HiRes1Engine::drawItems() { + Common::List<Item>::iterator item; - while (1) { - _display->putPixel(p, color); + uint dropped = 0; - if (--steps == 0) - return; + for (item = _state.items.begin(); item != _state.items.end(); ++item) { + // Skip items not in this room + if (item->room != _state.room) + continue; - if (err < 0) { - p.y += yStep; - err += deltaX; + if (item->state == IDI_ITEM_DROPPED) { + // Draw dropped item if in normal view + if (getCurRoom().picture == getCurRoom().curPicture) + drawItem(*item, _itemOffsets[dropped++]); } else { - p.x += xStep; - err += deltaY; + // Draw fixed item if current view is in the pic list + Common::Array<byte>::const_iterator pic; + + for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { + if (*pic == getCurRoom().curPicture) { + drawItem(*item, item->position); + break; + } + } } } } -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(); +void HiRes1Engine::drawItem(Item &item, const Common::Point &pos) { + if (item.isLineArt) { + StreamPtr stream(_corners[item.picture - 1]->createReadStream()); + static_cast<Graphics_v1 *>(_graphics)->drawCorners(*stream, pos); + } else + drawPic(item.picture, pos); +} + +void HiRes1Engine::loadRoom(byte roomNr) { + _roomData.description = loadMessage(_roomDesc[_state.room - 1]); +} - if (stream.err() || stream.eos()) - error("Failed to read picture"); +void HiRes1Engine::showRoom() { + clearScreen(); + loadRoom(_state.room); - if (x == 0xff && y == 0xff) - return; + if (!_state.isDark) { + drawPic(getCurRoom().curPicture); + drawItems(); + } - if (x == 0 && y == 0) { - bNewLine = true; - continue; - } + _display->updateHiResScreen(); + _messageDelay = false; + printString(_roomData.description); + _messageDelay = true; +} - x += pos.x; - y += pos.y; +void HiRes1Engine::wordWrap(Common::String &str) const { + uint end = 39; - if (y > 160) - y = 160; + while (1) { + if (str.size() <= end) + return; - if (bNewLine) { - _display->putPixel(Common::Point(x, y), 0x7f); - bNewLine = false; - } else { - drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f); - } + while (str[end] != APPLECHAR(' ')) + --end; - oldX = x; - oldY = y; + str.setChar(APPLECHAR('\r'), end); + end += 40; } } diff --git a/engines/adl/hires1.h b/engines/adl/hires1.h index 25f4744d26..c060bc892e 100644 --- a/engines/adl/hires1.h +++ b/engines/adl/hires1.h @@ -26,6 +26,8 @@ #include "common/str.h" #include "adl/adl.h" +#include "adl/graphics.h" +#include "adl/disk.h" namespace Common { class ReadStream; @@ -40,10 +42,10 @@ namespace Adl { #define IDS_HR1_MESSAGES "MESSAGES" #define IDI_HR1_NUM_ROOMS 41 -#define IDI_HR1_NUM_PICS 98 +#define IDI_HR1_NUM_PICS 97 #define IDI_HR1_NUM_VARS 20 #define IDI_HR1_NUM_ITEM_OFFSETS 21 -#define IDI_HR1_NUM_MESSAGES 167 +#define IDI_HR1_NUM_MESSAGES 168 // Messages used outside of scripts #define IDI_HR1_MSG_CANT_GO_THERE 137 @@ -63,6 +65,7 @@ namespace Adl { #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_STR_LINE_FEEDS 0x59d4 #define IDI_HR1_OFS_PD_TEXT_0 0x005d #define IDI_HR1_OFS_PD_TEXT_1 0x012b @@ -77,34 +80,52 @@ namespace Adl { #define IDI_HR1_OFS_ITEMS 0x0100 #define IDI_HR1_OFS_ROOMS 0x050a -#define IDI_HR1_OFS_PICS 0x4b00 +#define IDI_HR1_OFS_PICS 0x4b03 #define IDI_HR1_OFS_CMDS_0 0x3c00 #define IDI_HR1_OFS_CMDS_1 0x3d00 +#define IDI_HR1_OFS_MSGS 0x4d00 #define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff -#define IDI_HR1_OFS_LINE_ART 0x4f00 +#define IDI_HR1_OFS_CORNERS 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) { } + HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine(syst, gd), + _files(nullptr), + _messageDelay(true) { } + ~HiRes1Engine() { delete _files; } private: // AdlEngine void runIntro() const; - void loadData(); - void initState(); + void init(); + void initGameState(); 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; + void printString(const Common::String &str); + Common::String loadMessage(uint idx) const; + void printMessage(uint idx); + void drawItems(); + void drawItem(Item &item, const Common::Point &pos); + void loadRoom(byte roomNr); + void showRoom(); + + void wordWrap(Common::String &str) const; + + Files *_files; + Common::File _exe; + Common::Array<DataBlockPtr> _corners; + Common::Array<byte> _roomDesc; + bool _messageDelay; struct { - Common::String pressReturn; + Common::String cantGoThere; + Common::String dontHaveIt; + Common::String dontUnderstand; + Common::String gettingDark; } _gameStrings; }; diff --git a/engines/adl/hires2.cpp b/engines/adl/hires2.cpp new file mode 100644 index 0000000000..d8e8a65e29 --- /dev/null +++ b/engines/adl/hires2.cpp @@ -0,0 +1,183 @@ +/* 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/hires2.h" +#include "adl/display.h" +#include "adl/graphics.h" +#include "adl/disk.h" + +namespace Adl { + +void HiRes2Engine::runIntro() const { + StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1)); + + _display->setMode(DISPLAY_MODE_TEXT); + + Common::String str = readString(*stream); + + if (stream->eos() || stream->err()) + error("Error reading disk image"); + + _display->printString(str); + delay(2000); +} + +void HiRes2Engine::init() { + _graphics = new Graphics_v2(*_display); + + _disk = new DiskImage_DSK(); + if (!_disk->open(IDS_HR2_DISK_IMAGE)) + error("Failed to open disk image '" IDS_HR2_DISK_IMAGE "'"); + + StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4)); + + for (uint i = 0; i < IDI_HR2_NUM_MESSAGES; ++i) + _messages.push_back(readDataBlockPtr(*stream)); + + // Read parser messages + stream.reset(_disk->createReadStream(0x1a, 0x1)); + _strings.verbError = readStringAt(*stream, 0x4f); + _strings.nounError = readStringAt(*stream, 0x8e); + _strings.enterCommand = readStringAt(*stream, 0xbc); + + // Read time string + stream.reset(_disk->createReadStream(0x19, 0x7, 0xd7)); + _strings_v2.time = readString(*stream, 0xff); + + // Read line feeds + stream.reset(_disk->createReadStream(0x19, 0xb, 0xf8, 1)); + _strings.lineFeeds = readString(*stream); + + // Read opcode strings + stream.reset(_disk->createReadStream(0x1a, 0x6, 0x00, 2)); + _strings_v2.saveInsert = readStringAt(*stream, 0x5f); + _strings_v2.saveReplace = readStringAt(*stream, 0xe5); + _strings_v2.restoreInsert = readStringAt(*stream, 0x132); + _strings_v2.restoreReplace = readStringAt(*stream, 0x1c2); + _strings.playAgain = readStringAt(*stream, 0x225); + _strings.pressReturn = readStringAt(*stream, 0x25f); + + _messageIds.cantGoThere = IDI_HR2_MSG_CANT_GO_THERE; + _messageIds.dontUnderstand = IDI_HR2_MSG_DONT_UNDERSTAND; + _messageIds.itemDoesntMove = IDI_HR2_MSG_ITEM_DOESNT_MOVE; + _messageIds.itemNotHere = IDI_HR2_MSG_ITEM_NOT_HERE; + _messageIds.thanksForPlaying = IDI_HR2_MSG_THANKS_FOR_PLAYING; + + // Load global picture data + stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0)); + byte picNr; + while ((picNr = stream->readByte()) != 0xff) { + if (stream->eos() || stream->err()) + error("Error reading global pic list"); + + _pictures[picNr] = readDataBlockPtr(*stream); + } + + // Load item picture data + stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05)); + for (uint i = 0; i < IDI_HR2_NUM_ITEM_PICS; ++i) { + stream->readByte(); // number + _itemPics.push_back(readDataBlockPtr(*stream)); + } + + // Load commands from executable + stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 4)); + readCommands(*stream, _roomCommands); + + stream.reset(_disk->createReadStream(0x1f, 0x7, 0x00, 2)); + readCommands(*stream, _globalCommands); + + // Load dropped item offsets + stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15)); + for (uint i = 0; i < IDI_HR2_NUM_ITEM_OFFSETS; ++i) { + Common::Point p; + p.x = stream->readByte(); + p.y = stream->readByte(); + _itemOffsets.push_back(p); + } + + // Load verbs + stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3)); + loadWords(*stream, _verbs, _priVerbs); + + // Load nouns + stream.reset(_disk->createReadStream(0x22, 0x2, 0x00, 7)); + loadWords(*stream, _nouns, _priNouns); +} + +void HiRes2Engine::initGameState() { + _state.vars.resize(IDI_HR2_NUM_VARS); + + StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7)); + + for (uint i = 0; i < IDI_HR2_NUM_ROOMS; ++i) { + Room room; + stream->readByte(); // number + for (uint j = 0; j < 6; ++j) + room.connections[j] = stream->readByte(); + room.data = readDataBlockPtr(*stream); + room.picture = stream->readByte(); + room.curPicture = stream->readByte(); + room.isFirstTime = stream->readByte(); + _state.rooms.push_back(room); + } + + stream.reset(_disk->createReadStream(0x21, 0x0, 0x00, 2)); + + byte id; + while ((id = stream->readByte()) != 0xff) { + Item item = Item(); + item.id = id; + item.noun = stream->readByte(); + item.room = stream->readByte(); + item.picture = stream->readByte(); + item.isLineArt = stream->readByte(); // Is this still used in this way? + item.position.x = stream->readByte(); + item.position.y = stream->readByte(); + item.state = stream->readByte(); + item.description = stream->readByte(); + + stream->readByte(); // Struct size + + byte picListSize = stream->readByte(); + + // Flag to keep track of what has been drawn on the screen + stream->readByte(); + + for (uint i = 0; i < picListSize; ++i) + item.roomPictures.push_back(stream->readByte()); + + _state.items.push_back(item); + } +} + +Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd) { + return new HiRes2Engine(syst, gd); +} + +} // End of namespace Adl diff --git a/engines/adl/hires2.h b/engines/adl/hires2.h new file mode 100644 index 0000000000..50016725d6 --- /dev/null +++ b/engines/adl/hires2.h @@ -0,0 +1,66 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ADL_HIRES2_H +#define ADL_HIRES2_H + +#include "common/str.h" + +#include "adl/adl_v2.h" +#include "adl/disk.h" + +namespace Common { +class ReadStream; +struct Point; +} + +namespace Adl { + +#define IDS_HR2_DISK_IMAGE "WIZARD.DSK" + +#define IDI_HR2_NUM_ROOMS 135 +#define IDI_HR2_NUM_MESSAGES 255 +#define IDI_HR2_NUM_VARS 40 +#define IDI_HR2_NUM_ITEM_PICS 38 +#define IDI_HR2_NUM_ITEM_OFFSETS 16 + +// Messages used outside of scripts +#define IDI_HR2_MSG_CANT_GO_THERE 123 +#define IDI_HR2_MSG_DONT_UNDERSTAND 19 +#define IDI_HR2_MSG_ITEM_DOESNT_MOVE 242 +#define IDI_HR2_MSG_ITEM_NOT_HERE 4 +#define IDI_HR2_MSG_THANKS_FOR_PLAYING 239 + +class HiRes2Engine : public AdlEngine_v2 { +public: + HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine_v2(syst, gd) { } + +private: + // AdlEngine + void runIntro() const; + void init(); + void initGameState(); +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/hires6.cpp b/engines/adl/hires6.cpp new file mode 100644 index 0000000000..c42b4165a6 --- /dev/null +++ b/engines/adl/hires6.cpp @@ -0,0 +1,444 @@ +/* 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 "common/memstream.h" + +#include "adl/hires6.h" +#include "adl/display.h" +#include "adl/graphics.h" +#include "adl/disk.h" + +namespace Adl { + +static const char *disks[] = { "DARK1A.DSK", "DARK1B.NIB", "DARK2A.NIB", "DARK2B.NIB" }; + +#define SECTORS_PER_TRACK 16 +#define BYTES_PER_SECTOR 256 + +static Common::MemoryReadStream *loadSectors(DiskImage *disk, byte track, byte sector = SECTORS_PER_TRACK - 1, byte count = SECTORS_PER_TRACK) { + const int bufSize = count * BYTES_PER_SECTOR; + byte *const buf = (byte *)malloc(bufSize); + byte *p = buf; + + while (count-- > 0) { + StreamPtr stream(disk->createReadStream(track, sector, 0, 0)); + stream->read(p, BYTES_PER_SECTOR); + + if (stream->err() || stream->eos()) + error("Error loading from disk image"); + + p += BYTES_PER_SECTOR; + if (sector > 0) + --sector; + else { + ++track; + + // Skip VTOC track + if (track == 17) + ++track; + + sector = SECTORS_PER_TRACK - 1; + } + } + + return new Common::MemoryReadStream(buf, bufSize, DisposeAfterUse::YES); +} + +void HiRes6Engine::runIntro() const { + DiskImage_DSK *boot(new DiskImage_DSK()); + + if (!boot->open(disks[0])) + error("Failed to open disk image '%s'", disks[0]); + + StreamPtr stream(loadSectors(boot, 11, 1, 96)); + + _display->setMode(DISPLAY_MODE_HIRES); + _display->loadFrameBuffer(*stream); + _display->updateHiResScreen(); + delay(256 * 8609 / 1000); + + _display->loadFrameBuffer(*stream); + _display->updateHiResScreen(); + delay(256 * 8609 / 1000); + + _display->loadFrameBuffer(*stream); + + delete boot; + + // Load copyright string from boot file + Files_DOS33 *files(new Files_DOS33()); + + if (!files->open(disks[0])) + error("Failed to open disk image '%s'", disks[0]); + + stream.reset(files->createReadStream("\010\010\010\010\010\010")); + Common::String copyright(readStringAt(*stream, 0x103, APPLECHAR('\r'))); + + delete files; + + _display->updateHiResScreen(); + _display->home(); + _display->setMode(DISPLAY_MODE_MIXED); + _display->moveCursorTo(Common::Point(0, 21)); + _display->printString(copyright); + delay(256 * 8609 / 1000); +} + +void HiRes6Engine::init() { + _boot = new DiskImage_DSK(); + _graphics = new Graphics_v2(*_display); + + if (!_boot->open(disks[0])) + error("Failed to open disk image '%s'", disks[0]); + + StreamPtr stream(loadSectors(_boot, 0x7)); + + // Read parser messages + _strings.verbError = readStringAt(*stream, 0x666); + _strings.nounError = readStringAt(*stream, 0x6bd); + _strings.enterCommand = readStringAt(*stream, 0x6e9); + + // Read line feeds + _strings.lineFeeds = readStringAt(*stream, 0x408); + + // Read opcode strings (TODO) + _strings_v2.saveInsert = readStringAt(*stream, 0xad8); + readStringAt(*stream, 0xb95); // Confirm save + // _strings_v2.saveReplace + _strings_v2.restoreInsert = readStringAt(*stream, 0xc07); + // _strings_v2.restoreReplace + _strings.playAgain = readStringAt(*stream, 0xcdf, 0xff); + + _messageIds.cantGoThere = IDI_HR6_MSG_CANT_GO_THERE; + _messageIds.dontUnderstand = IDI_HR6_MSG_DONT_UNDERSTAND; + _messageIds.itemDoesntMove = IDI_HR6_MSG_ITEM_DOESNT_MOVE; + _messageIds.itemNotHere = IDI_HR6_MSG_ITEM_NOT_HERE; + _messageIds.thanksForPlaying = IDI_HR6_MSG_THANKS_FOR_PLAYING; + + // Item descriptions + stream.reset(loadSectors(_boot, 0x6, 0xb, 2)); + stream->seek(0x34); + for (uint i = 0; i < IDI_HR6_NUM_ITEM_DESCS; ++i) + _itemDesc.push_back(readString(*stream, 0xff)); + + // Load dropped item offsets + stream.reset(_boot->createReadStream(0x8, 0x9, 0x16)); + for (uint i = 0; i < IDI_HR6_NUM_ITEM_OFFSETS; ++i) { + Common::Point p; + p.x = stream->readByte(); + p.y = stream->readByte(); + _itemOffsets.push_back(p); + } + + // Location of game data for each disc + stream.reset(_boot->createReadStream(0x5, 0xa, 0x03)); + for (uint i = 0; i < sizeof(disks); ++i) { + DiskDataDesc desc; + desc.track = stream->readByte(); + desc.sector = stream->readByte(); + desc.offset = stream->readByte(); + desc.volume = stream->readByte(); + _diskDataDesc.push_back(desc); + } + + // DataBlockPtr offsets for each disk + stream.reset(_boot->createReadStream(0x3, 0xf, 0x03)); + for (uint i = 0; i < sizeof(disks); ++i) { + DiskOffset offset; + offset.track = stream->readByte(); + offset.sector = stream->readByte(); + _diskOffsets.push_back(offset); + } +} + +void HiRes6Engine::loadDisk(byte disk) { + delete _disk; + _disk = new DiskImage_NIB(); + + if (!_disk->open(disks[disk])) + error("Failed to open disk image '%s'", disks[disk]); + + _curDisk = 0; + + // Load item picture data (indexed on boot disk) + StreamPtr stream(_boot->createReadStream(0xb, 0xd, 0x08)); + _itemPics.clear(); + for (uint i = 0; i < IDI_HR6_NUM_ITEM_PICS; ++i) { + stream->readByte(); + _itemPics.push_back(readDataBlockPtr(*stream)); + } + + _curDisk = disk; + + byte track = _diskDataDesc[disk].track; + byte sector = _diskDataDesc[disk].sector; + uint offset = _diskDataDesc[disk].offset; + + applyDiskOffset(track, sector); + + for (uint block = 0; block < 7; ++block) { + stream.reset(_disk->createReadStream(track, sector, offset, 1)); + + uint16 addr = stream->readUint16LE(); + uint16 size = stream->readUint16LE(); + + stream.reset(_disk->createReadStream(track, sector, offset, size / 256 + 1)); + stream->skip(4); + + switch (addr) { + case 0x9000: { + // Messages + _messages.clear(); + uint count = size / 4; + for (uint i = 0; i < count; ++i) + _messages.push_back(readDataBlockPtr(*stream)); + break; + } + case 0x4a80: { + // Global pics + _pictures.clear(); + byte picNr; + while ((picNr = stream->readByte()) != 0xff) { + if (stream->eos() || stream->err()) + error("Error reading global pic list"); + _pictures[picNr] = readDataBlockPtr(*stream); + } + break; + } + case 0x4000: + // Verbs + loadWords(*stream, _verbs, _priVerbs); + break; + case 0x1800: + // Nouns + loadWords(*stream, _nouns, _priNouns); + break; + case 0x0e00: { + // Rooms + uint count = size / 14 - 1; + stream->skip(14); // Skip invalid room 0 + + _state.rooms.clear(); + for (uint i = 0; i < count; ++i) { + Room room; + stream->readByte(); // number + for (uint j = 0; j < 6; ++j) + room.connections[j] = stream->readByte(); + room.data = readDataBlockPtr(*stream); + room.picture = stream->readByte(); + room.curPicture = stream->readByte(); + room.isFirstTime = stream->readByte(); + _state.rooms.push_back(room); + } + break; + } + case 0x7b00: + // Global commands + readCommands(*stream, _globalCommands); + break; + case 0x9500: + // Room commands + readCommands(*stream, _roomCommands); + break; + default: + error("Unknown data block found (addr %04x; size %04x)", addr, size); + } + + offset += 4 + size; + while (offset >= 256) { + offset -= 256; + ++sector; + if (sector >= 16) { + sector = 0; + ++track; + } + } + } +} + +void HiRes6Engine::initGameState() { + _state.vars.resize(IDI_HR6_NUM_VARS); + + loadDisk(1); + + StreamPtr stream(_boot->createReadStream(0x3, 0xe, 0x03)); + + byte id; + while ((id = stream->readByte()) != 0xff) { + Item item = Item(); + item.id = id; + item.noun = stream->readByte(); + item.room = stream->readByte(); + item.picture = stream->readByte(); + item.isLineArt = stream->readByte(); // Now seems to be disk number + item.position.x = stream->readByte(); + item.position.y = stream->readByte(); + item.state = stream->readByte(); + item.description = stream->readByte(); + + stream->readByte(); // Struct size + + byte picListSize = stream->readByte(); + + // Flag to keep track of what has been drawn on the screen + stream->readByte(); + + for (uint i = 0; i < picListSize; ++i) + item.roomPictures.push_back(stream->readByte()); + + _state.items.push_back(item); + } + + _currVerb = _currNoun = 0; +} + +void HiRes6Engine::showRoom() { + bool redrawPic = false; + + if (getVar(26) == 0xfe) + setVar(26, 0); + else if (getVar(26) != 0xff) + setVar(26, _state.room); + + if (_state.room != _roomOnScreen) { + loadRoom(_state.room); + + if (getVar(26) < 0x80 && getCurRoom().isFirstTime) + setVar(26, 0); + + clearScreen(); + + if (!_state.isDark) + redrawPic = true; + } else { + if (getCurRoom().curPicture != _picOnScreen || _itemRemoved) + redrawPic = true; + } + + if (redrawPic) { + _roomOnScreen = _state.room; + _picOnScreen = getCurRoom().curPicture; + + drawPic(getCurRoom().curPicture); + _itemRemoved = false; + _itemsOnScreen = 0; + + Common::List<Item>::iterator item; + for (item = _state.items.begin(); item != _state.items.end(); ++item) + item->isOnScreen = false; + } + + if (!_state.isDark) + drawItems(); + + _display->updateHiResScreen(); + setVar(2, 0xff); + printString(_roomData.description); + + // FIXME: move to main loop? + _linesPrinted = 0; +} + +Common::String HiRes6Engine::formatVerbError(const Common::String &verb) const { + Common::String err = _strings.verbError; + + for (uint i = 0; i < verb.size(); ++i) + err.setChar(verb[i], i + 24); + + err.setChar(APPLECHAR(' '), 32); + + uint i = 24; + while (err[i] != APPLECHAR(' ')) + ++i; + + err.setChar(APPLECHAR('.'), i); + + return err; +} + +Common::String HiRes6Engine::formatNounError(const Common::String &verb, const Common::String &noun) const { + Common::String err = _strings.nounError; + + for (uint i = 0; i < noun.size(); ++i) + err.setChar(noun[i], i + 24); + + for (uint i = 35; i > 31; --i) + err.setChar(APPLECHAR(' '), i); + + uint i = 24; + while (err[i] != APPLECHAR(' ')) + ++i; + + err.setChar(APPLECHAR('I'), i + 1); + err.setChar(APPLECHAR('S'), i + 2); + err.setChar(APPLECHAR('.'), i + 3); + + return err; +} + +void HiRes6Engine::printString(const Common::String &str) { + Common::String s; + uint found = 0; + + // This does not emulate the corner cases of the original, hence this check + if (getVar(27) > 1) + error("Invalid value %i encountered for variable 27", getVar(27)); + + for (uint i = 0; i < str.size(); ++i) { + if (str[i] == '%') { + ++found; + if (found == 3) + found = 0; + } else { + if (found == 0 || found - 1 == getVar(27)) + s += str[i]; + } + } + + if (getVar(2) != 0xff) { + AdlEngine_v2::printString(s); + } else { + if (getVar(26) == 0) { + if (str.size() != 1 || APPLECHAR(str[0]) != APPLECHAR(' ')) + return AdlEngine_v2::printString(s); + setVar(2, APPLECHAR(' ')); + } else if (getVar(26) != 0xff) { + setVar(2, 'P'); + } else { + setVar(26, _state.room); + setVar(2, 1); + } + + doAllCommands(_globalCommands, _currVerb, _currNoun); + } +} + +Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd) { + return new HiRes6Engine(syst, gd); +} + +} // End of namespace Adl diff --git a/engines/adl/hires6.h b/engines/adl/hires6.h new file mode 100644 index 0000000000..4bd2bcc7cc --- /dev/null +++ b/engines/adl/hires6.h @@ -0,0 +1,92 @@ +/* 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_HIRES6_H +#define ADL_HIRES6_H + +#include "common/str.h" + +#include "adl/adl_v3.h" +#include "adl/disk.h" + +namespace Common { +class ReadStream; +struct Point; +} + +namespace Adl { + +#define IDI_HR6_NUM_ROOMS 35 +#define IDI_HR6_NUM_MESSAGES 256 +#define IDI_HR6_NUM_VARS 40 +#define IDI_HR6_NUM_ITEM_DESCS 15 +#define IDI_HR6_NUM_ITEM_PICS 15 +#define IDI_HR6_NUM_ITEM_OFFSETS 16 + +// Messages used outside of scripts +#define IDI_HR6_MSG_CANT_GO_THERE 249 +#define IDI_HR6_MSG_DONT_UNDERSTAND 247 +#define IDI_HR6_MSG_ITEM_DOESNT_MOVE 253 +#define IDI_HR6_MSG_ITEM_NOT_HERE 254 +#define IDI_HR6_MSG_THANKS_FOR_PLAYING 252 + +struct DiskDataDesc { + byte track; + byte sector; + byte offset; + byte volume; +}; + +class HiRes6Engine : public AdlEngine_v3 { +public: + HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine_v3(syst, gd), + _boot(nullptr), + _currVerb(0), + _currNoun(0) { + } + + ~HiRes6Engine() { delete _boot; } + +private: + // AdlEngine + void runIntro() const; + void init(); + void initGameState(); + void printRoomDescription(); + void showRoom(); + Common::String formatVerbError(const Common::String &verb) const; + Common::String formatNounError(const Common::String &verb, const Common::String &noun) const; + + // AdlEngine_v2 + void printString(const Common::String &str); + + void loadDisk(byte disk); + + DiskImage_DSK *_boot; + byte _currVerb, _currNoun; + Common::Array<DiskDataDesc> _diskDataDesc; +}; + +} // End of namespace Adl + +#endif diff --git a/engines/adl/module.mk b/engines/adl/module.mk index 6acd06f6de..7ab37efc67 100644 --- a/engines/adl/module.mk +++ b/engines/adl/module.mk @@ -2,9 +2,19 @@ MODULE := engines/adl MODULE_OBJS := \ adl.o \ + adl_v2.o \ + adl_v3.o \ + console.o \ detection.o \ + disk.o \ display.o \ - hires1.o + graphics.o \ + graphics_v1.o \ + graphics_v2.o \ + hires1.o \ + hires2.o \ + hires6.o \ + speaker.o MODULE_DIRS += \ engines/adl diff --git a/engines/adl/speaker.cpp b/engines/adl/speaker.cpp new file mode 100644 index 0000000000..532d361cd9 --- /dev/null +++ b/engines/adl/speaker.cpp @@ -0,0 +1,94 @@ +/* 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/events.h" + +#include "engines/engine.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +#include "adl/speaker.h" +#include "adl/adl.h" + +namespace Adl { + +// Number of times to duplicate each sample +#define SAMPLE_DUP 4 +// Bell frequency in Hz +#define BELL_FREQ 1000 +// Sample rate +#define SAMPLE_RATE (BELL_FREQ * SAMPLE_DUP * 2) +// Number of waves per 0.1 seconds (bell length) +#define BELL_WAVE_COUNT (SAMPLE_RATE / 10 / SAMPLE_DUP / 2) +// Length of bell in samples +#define BELL_LEN (BELL_WAVE_COUNT * SAMPLE_DUP * 2) +// Length of silence in samples +#define SILENCE_LEN (SAMPLE_RATE / 80) + +Speaker::~Speaker() { + delete[] _bell; + delete[] _silence; +} + +Speaker::Speaker() { + _bell = new byte[BELL_LEN]; + + byte *buf = _bell; + for (uint i = 0; i < BELL_WAVE_COUNT; ++i) { + for (uint j = 0; j < SAMPLE_DUP; ++j) + *buf++ = 0x00; + for (uint j = 0; j < SAMPLE_DUP; ++j) + *buf++ = 0xff; + } + + _silence = new byte[SILENCE_LEN]; + + buf = _silence; + for (uint i = 0; i < SILENCE_LEN; ++i) + *buf++ = 0x80; +} + +void Speaker::bell(uint count) { + Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SAMPLE_RATE, false); + Audio::SoundHandle handle; + + stream->queueBuffer(_bell, BELL_LEN, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED); + + for (uint i = 1; i < count; ++i) { + stream->queueBuffer(_silence, SILENCE_LEN, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED); + stream->queueBuffer(_bell, BELL_LEN, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED); + } + + stream->finish(); + + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &handle, stream); + + while (!g_engine->shouldQuit() && g_system->getMixer()->isSoundHandleActive(handle)) { + Common::Event event; + static_cast<AdlEngine *>(g_engine)->pollEvent(event); + g_system->delayMillis(16); + } +} + +} // End of namespace Adl diff --git a/engines/adl/speaker.h b/engines/adl/speaker.h new file mode 100644 index 0000000000..31aaac32d2 --- /dev/null +++ b/engines/adl/speaker.h @@ -0,0 +1,49 @@ +/* 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_SPEAKER_H +#define ADL_SPEAKER_H + +#include "common/types.h" + +#include "audio/mixer.h" + +namespace Audio { +class AudioStream; +} + +namespace Adl { + +class Speaker { +public: + Speaker(); + ~Speaker(); + + void bell(uint count); + +private: + byte *_bell, *_silence; +}; + +} // End of namespace Adl + +#endif |