aboutsummaryrefslogtreecommitdiff
path: root/engines/adl
diff options
context:
space:
mode:
Diffstat (limited to 'engines/adl')
-rw-r--r--engines/adl/POTFILES1
-rw-r--r--engines/adl/adl.cpp1068
-rw-r--r--engines/adl/adl.h324
-rw-r--r--engines/adl/adl_v2.cpp539
-rw-r--r--engines/adl/adl_v2.h95
-rw-r--r--engines/adl/adl_v3.cpp259
-rw-r--r--engines/adl/adl_v3.h76
-rw-r--r--engines/adl/console.cpp333
-rw-r--r--engines/adl/console.h65
-rw-r--r--engines/adl/detection.cpp99
-rw-r--r--engines/adl/detection.h5
-rw-r--r--engines/adl/disk.cpp460
-rw-r--r--engines/adl/disk.h188
-rw-r--r--engines/adl/display.cpp63
-rw-r--r--engines/adl/display.h7
-rw-r--r--engines/adl/graphics.cpp69
-rw-r--r--engines/adl/graphics.h76
-rw-r--r--engines/adl/graphics_v1.cpp120
-rw-r--r--engines/adl/graphics_v2.cpp304
-rw-r--r--engines/adl/hires1.cpp352
-rw-r--r--engines/adl/hires1.h47
-rw-r--r--engines/adl/hires2.cpp183
-rw-r--r--engines/adl/hires2.h66
-rw-r--r--engines/adl/hires6.cpp444
-rw-r--r--engines/adl/hires6.h92
-rw-r--r--engines/adl/module.mk12
-rw-r--r--engines/adl/speaker.cpp94
-rw-r--r--engines/adl/speaker.h49
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 &sector) 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 &sector) 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