aboutsummaryrefslogtreecommitdiff
path: root/engines/adl/adl_v4.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/adl/adl_v4.cpp')
-rw-r--r--engines/adl/adl_v4.cpp609
1 files changed, 465 insertions, 144 deletions
diff --git a/engines/adl/adl_v4.cpp b/engines/adl/adl_v4.cpp
index ed20c82513..dcf0f997c9 100644
--- a/engines/adl/adl_v4.cpp
+++ b/engines/adl/adl_v4.cpp
@@ -20,22 +20,162 @@
*
*/
-#include "common/random.h"
-#include "common/error.h"
-
#include "adl/adl_v4.h"
#include "adl/display.h"
-#include "adl/graphics.h"
+#include "adl/detection.h"
namespace Adl {
AdlEngine_v4::AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v3(syst, gd),
- _curDisk(0) {
+ _currentVolume(0),
+ _itemPicIndex(nullptr) {
+
+}
+
+AdlEngine_v4::~AdlEngine_v4() {
+ delete _itemPicIndex;
+}
+
+void AdlEngine_v4::gameLoop() {
+ uint verb = 0, noun = 0;
+ _isRestarting = false;
+
+ if (_isRestoring) {
+ // Game restored from launcher. As this version of ADL long jumps to
+ // the game loop after restoring, no special action is required.
+ _isRestoring = false;
+ }
+
+ showRoom();
+
+ if (_isRestarting || shouldQuit())
+ return;
+
+ _canSaveNow = _canRestoreNow = true;
+ getInput(verb, noun);
+ _canSaveNow = _canRestoreNow = false;
+
+ if (_isRestoring) {
+ // Game restored from GMM. Move cursor to next line and jump to
+ // start of game loop.
+ _display->printAsciiString("\r");
+ _isRestoring = false;
+ return;
+ }
+
+ if (_isRestarting || shouldQuit())
+ return;
+
+ _linesPrinted = 0;
+
+ checkInput(verb, noun);
+
+ if (_isRestarting || shouldQuit())
+ return;
+
+ doAllCommands(_globalCommands, verb, noun);
+
+ if (_isRestarting || shouldQuit())
+ return;
+
+ _state.moves++;
+}
+
+void AdlEngine_v4::loadState(Common::ReadStream &stream) {
+ _state.room = stream.readByte();
+ _state.region = stream.readByte();
+ _state.prevRegion = stream.readByte();
+
+ uint32 size = stream.readUint32BE();
+ if (size != _state.regions.size())
+ error("Region count mismatch (expected %i; found %i)", _state.regions.size(), size);
+
+ Common::Array<Region>::iterator region;
+ for (region = _state.regions.begin(); region != _state.regions.end(); ++region) {
+ size = stream.readUint32BE();
+ if (size != region->rooms.size())
+ error("Room count mismatch (expected %i; found %i)", region->rooms.size(), size);
+
+ Common::Array<RoomState>::iterator room;
+ for (room = region->rooms.begin(); room != region->rooms.end(); ++room) {
+ room->picture = stream.readByte();
+ room->isFirstTime = stream.readByte();
+ }
+
+ size = stream.readUint32BE();
+ if (size != region->vars.size())
+ error("Variable count mismatch (expected %i; found %i)", region->vars.size(), size);
+
+ for (uint i = 0; i < region->vars.size(); ++i)
+ region->vars[i] = stream.readByte();
+ }
+
+ size = stream.readUint32BE();
+ if (size != _state.items.size())
+ error("Item count mismatch (expected %i; found %i)", _state.items.size(), size);
+
+ Common::List<Item>::iterator item;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ item->room = stream.readByte();
+ item->region = stream.readByte();
+ item->state = stream.readByte();
+ }
+
+ size = stream.readUint32BE();
+ const uint expectedSize = _state.vars.size() - getRegion(1).vars.size();
+ if (size != expectedSize)
+ error("Variable count mismatch (expected %i; found %i)", expectedSize, size);
+
+ for (uint i = getRegion(1).vars.size(); i < size; ++i)
+ _state.vars[i] = stream.readByte();
+
+ if (stream.err() || stream.eos())
+ return;
+
+ loadRegion(_state.region);
+ restoreRoomState(_state.room);
+ _roomOnScreen = _picOnScreen = 0;
+}
+
+void AdlEngine_v4::saveState(Common::WriteStream &stream) {
+ backupVars();
+ backupRoomState(_state.room);
+
+ stream.writeByte(_state.room);
+ stream.writeByte(_state.region);
+ stream.writeByte(_state.prevRegion);
+
+ stream.writeUint32BE(_state.regions.size());
+ Common::Array<Region>::const_iterator region;
+ for (region = _state.regions.begin(); region != _state.regions.end(); ++region) {
+ stream.writeUint32BE(region->rooms.size());
+ Common::Array<RoomState>::const_iterator room;
+ for (room = region->rooms.begin(); room != region->rooms.end(); ++room) {
+ stream.writeByte(room->picture);
+ stream.writeByte(room->isFirstTime);
+ }
+
+ stream.writeUint32BE(region->vars.size());
+ for (uint i = 0; i < region->vars.size(); ++i)
+ stream.writeByte(region->vars[i]);
+ }
+
+ stream.writeUint32BE(_state.items.size());
+ Common::List<Item>::const_iterator item;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ stream.writeByte(item->room);
+ stream.writeByte(item->region);
+ stream.writeByte(item->state);
+ }
+
+ stream.writeUint32BE(_state.vars.size() - getRegion(1).vars.size());
+ for (uint i = getRegion(1).vars.size(); i < _state.vars.size(); ++i)
+ stream.writeByte(_state.vars[i]);
}
Common::String AdlEngine_v4::loadMessage(uint idx) const {
- Common::String str = AdlEngine_v2::loadMessage(idx);
+ Common::String str = AdlEngine_v3::loadMessage(idx);
for (uint i = 0; i < str.size(); ++i) {
const char *xorStr = "AVISDURGAN";
@@ -49,114 +189,237 @@ Common::String AdlEngine_v4::getItemDescription(const Item &item) const {
return _itemDesc[item.id - 1];
}
-void AdlEngine_v4::applyDiskOffset(byte &track, byte &sector) const {
- sector += _diskOffsets[_curDisk].sector;
+DiskImage *AdlEngine_v4::loadDisk(byte volume) const {
+ const ADGameFileDescription *ag;
+
+ for (ag = _gameDescription->desc.filesDescriptions; ag->fileName; ag++) {
+ if (ag->fileType == volume) {
+ DiskImage *disk = new DiskImage();
+ if (!disk->open(ag->fileName))
+ error("Failed to open %s", ag->fileName);
+ return disk;
+ }
+ }
+
+ error("Disk volume %d not found", volume);
+}
+
+void AdlEngine_v4::insertDisk(byte volume) {
+ delete _disk;
+ _disk = loadDisk(volume);
+ _currentVolume = volume;
+}
+
+void AdlEngine_v4::loadRegionLocations(Common::ReadStream &stream, uint regions) {
+ for (uint r = 0; r < regions; ++r) {
+ RegionLocation loc;
+ loc.track = stream.readByte();
+ loc.sector = stream.readByte();
+
+ if (stream.eos() || stream.err())
+ error("Failed to read region locations");
+
+ _regionLocations.push_back(loc);
+ }
+}
+
+void AdlEngine_v4::loadRegionInitDataOffsets(Common::ReadStream &stream, uint regions) {
+ for (uint r = 0; r < regions; ++r) {
+ RegionInitDataOffset initOfs;
+ initOfs.track = stream.readByte();
+ initOfs.sector = stream.readByte();
+ initOfs.offset = stream.readByte();
+ initOfs.volume = stream.readByte();
+
+ if (stream.eos() || stream.err())
+ error("Failed to read region init data offsets");
+
+ _regionInitDataOffsets.push_back(initOfs);
+ }
+}
+
+void AdlEngine_v4::initRegions(const byte *roomsPerRegion, uint regions) {
+ _state.regions.resize(regions);
+
+ for (uint r = 0; r < regions; ++r) {
+ Region &regn = _state.regions[r];
+ // Each region has 24 variables
+ regn.vars.resize(24);
+
+ regn.rooms.resize(roomsPerRegion[r]);
+ for (uint rm = 0; rm < roomsPerRegion[r]; ++rm) {
+ // TODO: hires6 uses 0xff and has slightly different
+ // code working on these values
+ regn.rooms[rm].picture = 1;
+ regn.rooms[rm].isFirstTime = 1;
+ }
+ }
+}
+
+void AdlEngine_v4::fixupDiskOffset(byte &track, byte &sector) const {
+ if (_state.region == 0)
+ return;
+
+ sector += _regionLocations[_state.region - 1].sector;
if (sector >= 16) {
sector -= 16;
++track;
}
- track += _diskOffsets[_curDisk].track;
+ track += _regionLocations[_state.region - 1].track;
}
void AdlEngine_v4::adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const {
- applyDiskOffset(track, sector);
-}
-
-typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v4> OpcodeV4;
-#define SetOpcodeTable(x) table = &x;
-#define Opcode(x) table->push_back(new OpcodeV4(this, &AdlEngine_v4::x))
-#define OpcodeUnImpl() table->push_back(new OpcodeV4(this, 0))
-
-void AdlEngine_v4::setupOpcodeTables() {
- Common::Array<const Opcode *> *table = 0;
-
- SetOpcodeTable(_condOpcodes);
- // 0x00
- OpcodeUnImpl();
- Opcode(o2_isFirstTime);
- Opcode(o2_isRandomGT);
- Opcode(o4_isItemInRoom);
- // 0x04
- Opcode(o4_isNounNotInRoom);
- Opcode(o1_isMovesGT);
- Opcode(o1_isVarEQ);
- Opcode(o2_isCarryingSomething);
- // 0x08
- Opcode(o4_isVarGT);
- Opcode(o1_isCurPicEQ);
- Opcode(o4_skipOneCommand);
-
- SetOpcodeTable(_actOpcodes);
- // 0x00
- OpcodeUnImpl();
- Opcode(o1_varAdd);
- Opcode(o1_varSub);
- Opcode(o1_varSet);
- // 0x04
- Opcode(o4_listInv);
- Opcode(o4_moveItem);
- Opcode(o1_setRoom);
- Opcode(o2_setCurPic);
- // 0x08
- Opcode(o2_setPic);
- Opcode(o1_printMsg);
- Opcode(o4_dummy);
- Opcode(o4_setTextMode);
- // 0x0c
- Opcode(o2_moveAllItems);
- Opcode(o1_quit);
- Opcode(o4_dummy);
- Opcode(o2_save);
- // 0x10
- Opcode(o2_restore);
- Opcode(o1_restart);
- Opcode(o4_setDisk);
- Opcode(o4_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(o4_sound);
- OpcodeUnImpl();
- // 0x20
- Opcode(o2_initDisk);
+ fixupDiskOffset(track, sector);
}
-int AdlEngine_v4::o4_isVarGT(ScriptEnv &e) {
- OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2));
+void AdlEngine_v4::loadRegion(byte region) {
+ if (_currentVolume != _regionInitDataOffsets[region - 1].volume) {
+ insertDisk(_regionInitDataOffsets[region - 1].volume);
- if (getVar(e.arg(1)) > e.arg(2))
- return 2;
+ // FIXME: This shouldn't be needed, but currently is, due to
+ // implementation choices made earlier on for DataBlockPtr and DiskImage.
+ _state.region = 0; // To avoid region offset being applied
+ _itemPics.clear();
+ _itemPicIndex->seek(0);
+ loadItemPictures(*_itemPicIndex, _itemPicIndex->size() / 5);
+ }
- return -1;
+ _state.region = region;
+
+ byte track = _regionInitDataOffsets[region - 1].track;
+ byte sector = _regionInitDataOffsets[region - 1].sector;
+ uint offset = _regionInitDataOffsets[region - 1].offset;
+
+ fixupDiskOffset(track, sector);
+
+ for (uint block = 0; block < 7; ++block) {
+ StreamPtr stream(_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;
+ loadMessages(*stream, count);
+ break;
+ }
+ case 0x4a80: {
+ // Global pics
+ _pictures.clear();
+ loadPictures(*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();
+ loadRooms(*stream, count);
+ break;
+ }
+ case 0x7b00:
+ // TODO: hires6 has global and room lists swapped
+ // Room commands
+ readCommands(*stream, _roomCommands);
+ break;
+ case 0x9500:
+ // Global commands
+ readCommands(*stream, _globalCommands);
+ 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;
+ }
+ }
+ }
+
+ applyRegionWorkarounds();
+ restoreVars();
}
-int AdlEngine_v4::o4_skipOneCommand(ScriptEnv &e) {
- OP_DEBUG_0("\t&& SKIP_ONE_COMMAND()");
+void AdlEngine_v4::loadItemPicIndex(Common::ReadStream &stream, uint items) {
+ _itemPicIndex = stream.readStream(items * 5);
- _skipOneCommand = true;
- setVar(2, 0);
+ if (stream.eos() || stream.err())
+ error("Error reading item index");
+}
- return -1;
+void AdlEngine_v4::backupRoomState(byte room) {
+ RoomState &backup = getCurRegion().rooms[room - 1];
+
+ backup.isFirstTime = getRoom(room).isFirstTime;
+ backup.picture = getRoom(room).picture;
+}
+
+void AdlEngine_v4::restoreRoomState(byte room) {
+ const RoomState &backup = getCurRegion().rooms[room - 1];
+
+ getRoom(room).isFirstTime = backup.isFirstTime;
+ getRoom(room).picture = backup.picture;
+}
+
+void AdlEngine_v4::backupVars() {
+ Region &region = getCurRegion();
+
+ for (uint i = 0; i < region.vars.size(); ++i)
+ region.vars[i] = getVar(i);
+}
+
+void AdlEngine_v4::restoreVars() {
+ const Region &region = getCurRegion();
+
+ for (uint i = 0; i < region.vars.size(); ++i)
+ setVar(i, region.vars[i]);
+}
+
+void AdlEngine_v4::switchRegion(byte region) {
+ backupVars();
+ backupRoomState(_state.room);
+ _state.prevRegion = _state.region;
+ _state.region = region;
+ loadRegion(region);
+ _state.room = 1;
+ _picOnScreen = _roomOnScreen = 0;
+}
+
+void AdlEngine_v4::switchRoom(byte roomNr) {
+ getCurRoom().curPicture = getCurRoom().picture;
+ getCurRoom().isFirstTime = false;
+ backupRoomState(_state.room);
+ _state.room = roomNr;
+ restoreRoomState(_state.room);
}
-// FIXME: Rename "isLineArt" and look at code duplication
int AdlEngine_v4::o4_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)
+ if (e.arg(2) != IDI_ANY && item.region != _state.region)
return -1;
if (item.room == roomArg(e.arg(2)))
@@ -165,94 +428,152 @@ int AdlEngine_v4::o4_isItemInRoom(ScriptEnv &e) {
return -1;
}
-int AdlEngine_v4::o4_isNounNotInRoom(ScriptEnv &e) {
- OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
+int AdlEngine_v4::o4_isVarGT(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2));
- Common::List<Item>::const_iterator item;
+ if (getVar(e.arg(1)) > e.arg(2))
+ return 2;
- setVar(24, 0);
+ return -1;
+}
- for (item = _state.items.begin(); item != _state.items.end(); ++item)
- if (item->noun == e.getNoun()) {
- setVar(24, 1);
+int AdlEngine_v4::o4_moveItem(ScriptEnv &e) {
+ o2_moveItem(e);
+ getItem(e.arg(1)).region = _state.region;
+ return 2;
+}
- if (item->room == roomArg(e.arg(1)))
- return -1;
- }
+int AdlEngine_v4::o4_setRegionToPrev(ScriptEnv &e) {
+ OP_DEBUG_0("\tREGION = PREV_REGION");
- return 1;
+ switchRegion(_state.prevRegion);
+ // Long jump
+ _isRestarting = true;
+ return -1;
}
-int AdlEngine_v4::o4_listInv(ScriptEnv &e) {
- OP_DEBUG_0("\tLIST_INVENTORY()");
+int AdlEngine_v4::o4_moveAllItems(ScriptEnv &e) {
+ OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
- Common::List<Item>::const_iterator item;
+ byte room1 = roomArg(e.arg(1));
- for (item = _state.items.begin(); item != _state.items.end(); ++item)
- if (item->room == IDI_ANY)
- printString(_itemDesc[item->id - 1]);
+ if (room1 == _state.room)
+ _picOnScreen = 0;
- return 0;
-}
+ byte room2 = roomArg(e.arg(2));
-int AdlEngine_v4::o4_moveItem(ScriptEnv &e) {
- OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+ Common::List<Item>::iterator item;
- byte room = roomArg(e.arg(2));
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ if (room1 != item->room)
+ continue;
- Item &item = getItem(e.arg(1));
+ if (room1 != IDI_ANY) {
+ if (_state.region != item->region)
+ continue;
+ if (room2 == IDI_ANY) {
+ if (isInventoryFull())
+ break;
+ if (item->state == IDI_ITEM_DOESNT_MOVE)
+ continue;
+ }
+ }
- if (item.room == _roomOnScreen)
- _picOnScreen = 0;
+ item->room = room2;
+ item->region = _state.region;
- // 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;
+ if (room1 == IDI_ANY)
+ item->state = IDI_ITEM_DROPPED;
+ }
- item.room = room;
- item.isLineArt = _curDisk;
return 2;
}
-int AdlEngine_v4::o4_dummy(ScriptEnv &e) {
- OP_DEBUG_0("\tDUMMY()");
+int AdlEngine_v4::o4_setRegion(ScriptEnv &e) {
+ OP_DEBUG_1("\tREGION = %d", e.arg(1));
+
+ switchRegion(e.arg(1));
+ // Long jump
+ _isRestarting = true;
+ return -1;
+}
+
+int AdlEngine_v4::o4_save(ScriptEnv &e) {
+ OP_DEBUG_0("\tSAVE_GAME()");
+
+ _display->printString(_strings_v2.saveReplace);
+ const char key = inputKey();
+ if (shouldQuit())
+ return -1;
+
+ if (key != APPLECHAR('Y'))
+ return 0;
+
+ const int slot = askForSlot(_strings_v2.saveInsert);
+
+ if (slot < 0)
+ return -1;
+
+ saveGameState(slot, "");
return 0;
}
-int AdlEngine_v4::o4_setTextMode(ScriptEnv &e) {
- OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1));
+int AdlEngine_v4::o4_restore(ScriptEnv &e) {
+ OP_DEBUG_0("\tRESTORE_GAME()");
- // TODO
- // 1: 4-line mode
- // 2: 24-line mode
+ const int slot = askForSlot(_strings_v2.restoreInsert);
- switch (e.arg(1)) {
- case 3:
- // We re-use the restarting flag here, to simulate a long jump
- _isRestarting = true;
+ if (slot < 0)
return -1;
- }
- return 1;
+ loadGameState(slot);
+ _isRestoring = false;
+
+ _picOnScreen = 0;
+ _roomOnScreen = 0;
+
+ // Long jump
+ _isRestarting = true;
+ return -1;
}
-int AdlEngine_v4::o4_setDisk(ScriptEnv &e) {
- OP_DEBUG_2("\tSET_DISK(%d, %d)", e.arg(1), e.arg(2));
+int AdlEngine_v4::o4_restart(ScriptEnv &e) {
+ OP_DEBUG_0("\tRESTART_GAME()");
- // TODO
- // Arg 1: disk
- // Arg 2: room
+ while (true) {
+ _display->printString(_strings.playAgain);
+ const Common::String input(inputString());
- return 2;
+ if (shouldQuit())
+ return -1;
+
+ if (input.firstChar() == APPLECHAR('N')) {
+ return o1_quit(e);
+ } else if (input.firstChar() == APPLECHAR('Y')) {
+ // The original game loads a special save game from volume 3
+ initState();
+ // Long jump
+ _isRestarting = true;
+ return -1;
+ }
+ }
}
-int AdlEngine_v4::o4_sound(ScriptEnv &e) {
- OP_DEBUG_0("\tSOUND()");
+int AdlEngine_v4::o4_setRegionRoom(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_REGION_ROOM(%d, %d)", e.arg(1), e.arg(2));
- // TODO
+ switchRegion(e.arg(1));
+ _state.room = e.arg(2);
+ // Long jump
+ _isRestarting = true;
+ return -1;
+}
- return 0;
+int AdlEngine_v4::o4_setRoomPic(ScriptEnv &e) {
+ o1_setRoomPic(e);
+ backupRoomState(e.arg(1));
+ return 2;
}
} // End of namespace Adl