aboutsummaryrefslogtreecommitdiff
path: root/engines/lab
diff options
context:
space:
mode:
Diffstat (limited to 'engines/lab')
-rw-r--r--engines/lab/anim.cpp349
-rw-r--r--engines/lab/anim.h105
-rw-r--r--engines/lab/configure.engine3
-rw-r--r--engines/lab/console.cpp136
-rw-r--r--engines/lab/console.h46
-rw-r--r--engines/lab/detection.cpp249
-rw-r--r--engines/lab/dispman.cpp950
-rw-r--r--engines/lab/dispman.h274
-rw-r--r--engines/lab/engine.cpp1196
-rw-r--r--engines/lab/eventman.cpp213
-rw-r--r--engines/lab/eventman.h131
-rw-r--r--engines/lab/image.cpp136
-rw-r--r--engines/lab/image.h83
-rw-r--r--engines/lab/interface.cpp152
-rw-r--r--engines/lab/intro.cpp418
-rw-r--r--engines/lab/intro.h67
-rw-r--r--engines/lab/lab.cpp270
-rw-r--r--engines/lab/lab.h508
-rw-r--r--engines/lab/labsets.cpp77
-rw-r--r--engines/lab/labsets.h61
-rw-r--r--engines/lab/map.cpp553
-rw-r--r--engines/lab/module.mk30
-rw-r--r--engines/lab/music.cpp178
-rw-r--r--engines/lab/music.h94
-rw-r--r--engines/lab/processroom.cpp623
-rw-r--r--engines/lab/processroom.h190
-rw-r--r--engines/lab/resource.cpp309
-rw-r--r--engines/lab/resource.h124
-rw-r--r--engines/lab/savegame.cpp250
-rw-r--r--engines/lab/special.cpp469
-rw-r--r--engines/lab/speciallocks.cpp394
-rw-r--r--engines/lab/speciallocks.h94
-rw-r--r--engines/lab/utils.cpp272
-rw-r--r--engines/lab/utils.h105
34 files changed, 9109 insertions, 0 deletions
diff --git a/engines/lab/anim.cpp b/engines/lab/anim.cpp
new file mode 100644
index 0000000000..01ca905cee
--- /dev/null
+++ b/engines/lab/anim.cpp
@@ -0,0 +1,349 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "lab/lab.h"
+
+#include "lab/anim.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/music.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+Anim::Anim(LabEngine *vm) : _vm(vm) {
+ _lastBlockHeader = 0;
+ _numChunks = 1;
+ _headerdata._width = 0;
+ _headerdata._height = 0;
+ _headerdata._fps = 0;
+ _headerdata._flags = 0;
+ _delayMicros = 0;
+ _continuous = false;
+ _isPlaying = false;
+ _isAnim = false;
+ _isPal = false;
+ _noPalChange = false;
+ _donePal = false;
+ _frameNum = 0;
+ _playOnce = false;
+ _diffFile = nullptr;
+ _diffFileStart = 0;
+ _size = 0;
+ _scrollScreenBuffer = nullptr;
+ _waitForEffect = false;
+ _stopPlayingEnd = false;
+ _sampleSpeed = 0;
+ _doBlack = false;
+ _diffWidth = 0;
+ _diffHeight = 0;
+
+ for (int i = 0; i < 3 * 256; i++)
+ _diffPalette[i] = 0;
+
+ _outputBuffer = nullptr; // output to screen
+}
+
+Anim::~Anim() {
+ delete[] _vm->_anim->_scrollScreenBuffer;
+ _vm->_anim->_scrollScreenBuffer = nullptr;
+}
+
+void Anim::setOutputBuffer(byte *memoryBuffer) {
+ _outputBuffer = memoryBuffer;
+}
+
+uint16 Anim::getDIFFHeight() {
+ return _headerdata._height;
+}
+
+void Anim::diffNextFrame(bool onlyDiffData) {
+ if (_lastBlockHeader == 65535)
+ // Already done.
+ return;
+
+ bool drawOnScreen = false;
+ byte *startOfBuf = _outputBuffer;
+ int bufPitch = _vm->_graphics->_screenWidth;
+
+ if (!startOfBuf) {
+ startOfBuf = _vm->_graphics->getCurrentDrawingBuffer();
+ drawOnScreen = true;
+ }
+ byte *endOfBuf = startOfBuf + (int)_diffWidth * _diffHeight;
+
+ int curBit = 0;
+
+ while (1) {
+ byte *buf = startOfBuf + 0x10000 * curBit;
+
+ if (buf >= endOfBuf) {
+ if (!onlyDiffData) {
+ if (_headerdata._fps) {
+ uint32 targetMillis = _vm->_system->getMillis() + _delayMicros;
+ while (_vm->_system->getMillis() < targetMillis)
+ _vm->_system->delayMillis(10);
+ }
+
+ if (_isPal && !_noPalChange) {
+ _vm->_graphics->setPalette(_diffPalette, 256);
+ _isPal = false;
+ }
+
+ _donePal = true;
+ }
+
+ if (_isPal && !_noPalChange && !onlyDiffData && !_donePal) {
+ _vm->_graphics->setPalette(_diffPalette, 256);
+ _isPal = false;
+ }
+
+ _donePal = false;
+
+ _frameNum++;
+
+ if ((_frameNum == 1) && (_continuous || !_playOnce))
+ _diffFileStart = _diffFile->pos();
+
+ _isAnim = (_frameNum >= 3) && (!_playOnce);
+
+ if (drawOnScreen)
+ _vm->_graphics->screenUpdate();
+
+ // done with the next frame.
+ return;
+ }
+
+ _vm->updateEvents();
+ _lastBlockHeader = _diffFile->readUint32LE();
+ _size = _diffFile->readUint32LE();
+
+ uint32 curPos = 0;
+
+ switch (_lastBlockHeader) {
+ case 8:
+ _diffFile->read(_diffPalette, _size);
+ _isPal = true;
+ break;
+
+ case 10:
+ if (onlyDiffData) {
+ if (curBit > 0)
+ error("diffNextFrame: attempt to read screen to non-zero plane (%d)", curBit);
+ delete[] _scrollScreenBuffer;
+ _scrollScreenBuffer = new byte[_headerdata._width * _headerdata._height];
+ _diffFile->read(_scrollScreenBuffer, _size);
+ } else {
+ _diffFile->read(buf, _size);
+ }
+ curBit++;
+ break;
+
+ case 11:
+ curPos = _diffFile->pos();
+ _diffFile->skip(4);
+ _vm->_utils->runLengthDecode(buf, _diffFile);
+ curBit++;
+ _diffFile->seek(curPos + _size, SEEK_SET);
+ break;
+
+ case 12:
+ curPos = _diffFile->pos();
+ _diffFile->skip(4);
+ _vm->_utils->verticalRunLengthDecode(buf, _diffFile, bufPitch);
+ curBit++;
+ _diffFile->seek(curPos + _size, SEEK_SET);
+ break;
+
+ case 20:
+ curPos = _diffFile->pos();
+ _vm->_utils->unDiff(buf, buf, _diffFile, bufPitch, false);
+ curBit++;
+ _diffFile->seek(curPos + _size, SEEK_SET);
+ break;
+
+ case 21:
+ curPos = _diffFile->pos();
+ _vm->_utils->unDiff(buf, buf, _diffFile, bufPitch, true);
+ curBit++;
+ _diffFile->seek(curPos + _size, SEEK_SET);
+ break;
+
+ case 25:
+ case 26:
+ curBit++;
+ break;
+
+ case 30:
+ case 31:
+ if (_waitForEffect) {
+ while (_vm->_music->isSoundEffectActive()) {
+ _vm->updateEvents();
+ _vm->waitTOF();
+ }
+ }
+
+ _size -= 8;
+
+ _diffFile->skip(4);
+ _sampleSpeed = _diffFile->readUint16LE();
+ _diffFile->skip(2);
+
+ // Sound effects embedded in animations are started here. These are
+ // usually animation-specific, like door opening sounds, and are not looped
+ _vm->_music->playSoundEffect(_sampleSpeed, _size, false, _diffFile);
+ break;
+
+ case 65535:
+ if ((_frameNum == 1) || _playOnce || _stopPlayingEnd) {
+ bool didTOF = false;
+
+ if (_waitForEffect) {
+ while (_vm->_music->isSoundEffectActive()) {
+ _vm->updateEvents();
+ _vm->waitTOF();
+
+ if (drawOnScreen)
+ didTOF = true;
+ }
+ }
+
+ _isPlaying = false;
+
+ if (!didTOF)
+ _vm->_graphics->screenUpdate();
+
+ return;
+ }
+
+ // Random frame number so it never gets back to 2
+ _frameNum = 4;
+ _diffFile->seek(_diffFileStart, SEEK_SET);
+ break;
+
+ default:
+ _diffFile->skip(_size);
+ break;
+ }
+ }
+}
+
+void Anim::stopDiff() {
+ if (_isPlaying && _isAnim)
+ _vm->_graphics->blackScreen();
+}
+
+void Anim::stopDiffEnd() {
+ if (!_isPlaying)
+ return;
+
+ _stopPlayingEnd = true;
+ while (_isPlaying) {
+ _vm->updateEvents();
+ diffNextFrame();
+ }
+}
+
+void Anim::readDiff(Common::File *diffFile, bool playOnce, bool onlyDiffData) {
+ _playOnce = playOnce;
+ _delayMicros = 0;
+ _frameNum = 0;
+ _numChunks = 1;
+ _donePal = false;
+ _stopPlayingEnd = false;
+ _isPlaying = true;
+
+ if (_doBlack) {
+ _doBlack = false;
+ _vm->_graphics->blackScreen();
+ }
+
+ _diffFile = diffFile;
+
+ _continuous = false;
+ uint32 signature1 = _diffFile->readUint32BE();
+ uint32 signature2 = _diffFile->readUint32LE();
+
+ if ((signature1 != MKTAG('D', 'I', 'F', 'F')) || (signature2 != 1219009121)) {
+ _isPlaying = false;
+ return;
+ }
+
+ uint32 signature3 = _diffFile->readUint32LE();
+ _size = _diffFile->readUint32LE();
+
+ if (signature3 != 0)
+ return;
+
+ // sizeof(headerdata) != 18, but the padding might be at the end
+ // 2 bytes, version, unused.
+ _diffFile->skip(2);
+ _headerdata._width = _diffFile->readUint16LE();
+ _headerdata._height = _diffFile->readUint16LE();
+ // 1 byte, depth, unused
+ _diffFile->skip(1);
+ _headerdata._fps = _diffFile->readByte();
+
+ // HACK: The original game defines a 1 second delay when changing screens, which is
+ // very annoying. We first removed the delay, but it looked wrong when changing screens
+ // as it was possible to see that something was displayed, without being able to tell
+ // what it was. A shorter delay (150ms) makes it acceptable during gameplay and
+ // readable. The big question is: do we need that message?
+ _vm->_system->delayMillis(150);
+
+ if (_headerdata._fps == 1)
+ _headerdata._fps = 0;
+
+ // 4 + 2 bytes, buffer size and machine, unused
+ _diffFile->skip(6);
+ _headerdata._flags = _diffFile->readUint32LE();
+
+ _diffFile->skip(_size - 18);
+
+ _continuous = CONTINUOUS & _headerdata._flags;
+ _diffWidth = _headerdata._width;
+ _diffHeight = _headerdata._height;
+ _vm->_utils->setBytesPerRow(_diffWidth);
+
+ delete[] _scrollScreenBuffer;
+ _scrollScreenBuffer = nullptr;
+
+ if (_headerdata._fps)
+ _delayMicros = 1000 / _headerdata._fps;
+
+ _lastBlockHeader = signature3;
+ if (_playOnce) {
+ while (_lastBlockHeader != 65535)
+ diffNextFrame(onlyDiffData);
+ } else
+ diffNextFrame(onlyDiffData);
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/anim.h b/engines/lab/anim.h
new file mode 100644
index 0000000000..bdb02e3f02
--- /dev/null
+++ b/engines/lab/anim.h
@@ -0,0 +1,105 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_ANIM_H
+#define LAB_ANIM_H
+
+namespace Lab {
+
+class LabEngine;
+#define CONTINUOUS 0xFFFF
+
+struct DIFFHeader {
+ uint16 _width;
+ uint16 _height;
+ char _fps;
+ uint32 _flags;
+};
+
+class Anim {
+private:
+ LabEngine *_vm;
+
+ uint32 _lastBlockHeader;
+ uint16 _numChunks;
+ uint32 _delayMicros;
+ bool _continuous;
+ bool _isPlaying;
+ bool _isAnim;
+ bool _isPal;
+ bool _donePal;
+ uint16 _frameNum;
+ bool _playOnce;
+ Common::File *_diffFile;
+ uint32 _diffFileStart;
+ uint32 _size;
+ bool _stopPlayingEnd;
+ uint16 _sampleSpeed;
+ uint32 _diffWidth;
+ uint32 _diffHeight;
+
+ byte *_outputBuffer;
+ DIFFHeader _headerdata;
+
+public:
+ Anim(LabEngine *vm);
+ ~Anim();
+
+ char _diffPalette[256 * 3];
+ bool _waitForEffect; // Wait for each sound effect to finish before continuing.
+ bool _doBlack; // Black the screen before new picture
+ bool _noPalChange; // Don't change the palette.
+ byte *_scrollScreenBuffer;
+
+ /**
+ * Reads in a DIFF file.
+ */
+ void setOutputBuffer(byte *memoryBuffer); // nullptr for output to screen
+ void readDiff(Common::File *diffFile, bool playOnce, bool onlyDiffData);
+ void diffNextFrame(bool onlyDiffData = false);
+
+ /**
+ * Stops an animation from running.
+ */
+ void stopDiff();
+
+ /**
+ * Stops an animation from running.
+ */
+ void stopDiffEnd();
+
+ uint16 getDIFFHeight();
+
+ bool isPlaying() const { return _isPlaying; }
+};
+
+} // End of namespace Lab
+
+#endif // LAB_ANIM_H
diff --git a/engines/lab/configure.engine b/engines/lab/configure.engine
new file mode 100644
index 0000000000..18091b1b84
--- /dev/null
+++ b/engines/lab/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine lab "Labyrinth of Time" no
diff --git a/engines/lab/console.cpp b/engines/lab/console.cpp
new file mode 100644
index 0000000000..217dc28579
--- /dev/null
+++ b/engines/lab/console.cpp
@@ -0,0 +1,136 @@
+/* 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 "gui/debugger.h"
+
+#include "lab/lab.h"
+#include "lab/console.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/processroom.h"
+#include "lab/resource.h"
+
+namespace Lab {
+
+Console::Console(LabEngine *vm) : GUI::Debugger(), _vm(vm) {
+ registerCmd("scene", WRAP_METHOD(Console, Cmd_Scene));
+ registerCmd("scene_resources", WRAP_METHOD(Console, Cmd_DumpSceneResources));
+ registerCmd("find_action", WRAP_METHOD(Console, Cmd_FindAction));
+}
+
+Console::~Console() {
+}
+
+bool Console::Cmd_Scene(int argc, const char **argv) {
+ if (argc != 2) {
+ const char *directions[] = { "North", "South", "East", "West" };
+ debugPrintf("Current scene is %d, direction: %s\n", _vm->_roomNum, directions[_vm->getDirection()]);
+ debugPrintf("Use %s <scene number> to change the current scene\n", argv[0]);
+ return true;
+ }
+
+ _vm->_roomNum = atoi(argv[1]);
+ _vm->_curFileName = " ";
+ _vm->_closeDataPtr = nullptr;
+ _vm->_mainDisplay = true;
+ _vm->_followingCrumbs = false;
+ _vm->_event->simulateEvent();
+ _vm->_graphics->_longWinInFront = false;
+
+ return false;
+}
+
+bool Console::Cmd_DumpSceneResources(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Usage: %s <scene number> to dump the resources for a scene\n", argv[0]);
+ return true;
+ }
+
+ int scene = atoi(argv[1]);
+ _vm->_resource->readViews(scene);
+ RoomData *roomData = &_vm->_rooms[scene];
+ RuleList &rules = roomData->_rules;
+ const char *transitions[] = { "None", "Wipe", "ScrollWipe", "ScrollBlack", "ScrollBounce", "Transporter", "ReadFirstFrame", "ReadNextFrame" };
+ const char *ruleTypes[] = { "None", "Action", "Operate", "Go forward", "Conditions", "Turn", "Go main view", "Turn from to" };
+ const char *directions[] = { "", "North", "South", "East", "West" };
+ const char *actionTypes[] = {
+ "", "PlaySound", "PlaySoundLooping", "ShowDiff", "ShowDiffLooping", "LoadDiff", "LoadBitmap", "ShowBitmap", "Transition", "NoUpdate", "ForceUpdate",
+ "ShowCurPict", "SetElement", "UnsetElement", "ShowMessage", "ShowMessages", "ChangeRoom", "SetCloseup", "MainView", "SubInv", "AddInv", "ShowDir",
+ "WaitSecs", "StopMusic", "StartMusic", "ChangeMusic", "ResetMusic", "FillMusic", "WaitSound", "ClearSound", "WinMusic", "WinGame", "LostGame",
+ "ResetBuffer", "SpecialCmd", "CShowMessage", "PlaySoundNoWait"
+ };
+
+ debugPrintf("Room mesage: %s\n", roomData->_roomMsg.c_str());
+ debugPrintf("Transition: %s (%d)\n", transitions[roomData->_transitionType], roomData->_transitionType);
+
+ debugPrintf("Script:\n");
+
+ for (RuleList::iterator rule = rules.begin(); rule != rules.end(); ++rule) {
+ debugPrintf("Rule type: %s", ruleTypes[rule->_ruleType]);
+ if (rule->_ruleType == kRuleTypeAction || rule->_ruleType == kRuleTypeOperate)
+ debugPrintf(" (item %d, closeup %d)", rule->_param1, rule->_param2);
+ else if (rule->_ruleType == kRuleTypeGoForward)
+ debugPrintf(" (%s)", directions[rule->_param1]);
+ else if (rule->_ruleType == kRuleTypeTurnFromTo)
+ debugPrintf(" (from %s to %s)", directions[rule->_param1], directions[rule->_param2]);
+ debugPrintf("\n");
+
+ ActionList::iterator action;
+ for (action = rule->_actionList.begin(); action != rule->_actionList.end(); ++action) {
+ debugPrintf(" - %s ('%s', %d, %d, %d)\n", actionTypes[action->_actionType], action->_messages[0].c_str(), action->_param1, action->_param2, action->_param3);
+ }
+ }
+
+ return true;
+}
+
+bool Console::Cmd_FindAction(int argc, const char **argv) {
+ if (argc < 2) {
+ debugPrintf("Usage: %s <action id> [param 1] [param 2] [param 3]\n", argv[0]);
+ return true;
+ }
+
+ int actionId = atoi(argv[1]);
+ int param1 = (argc > 2) ? atoi(argv[2]) : -1;
+ int param2 = (argc > 3) ? atoi(argv[3]) : -1;
+ int param3 = (argc > 4) ? atoi(argv[4]) : -1;
+
+ for (int i = 1; i <= _vm->_manyRooms; i++) {
+ _vm->_resource->readViews(i);
+
+ for (RuleList::iterator rule = _vm->_rooms[i]._rules.begin(); rule != _vm->_rooms[i]._rules.end(); ++rule) {
+ ActionList::iterator action;
+ for (action = rule->_actionList.begin(); action != rule->_actionList.end(); ++action) {
+ if (action->_actionType == actionId &&
+ (action->_param1 == param1 || param1 == -1) &&
+ (action->_param2 == param2 || param2 == -1) &&
+ (action->_param3 == param3 || param3 == -1)) {
+ debugPrintf("Found at script %d\n", i);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+} // End of namespace Neverhood
diff --git a/engines/lab/console.h b/engines/lab/console.h
new file mode 100644
index 0000000000..af41b6034b
--- /dev/null
+++ b/engines/lab/console.h
@@ -0,0 +1,46 @@
+/* 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 LAB_CONSOLE_H
+#define LAB_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace Lab {
+
+class LabEngine;
+
+class Console : public GUI::Debugger {
+public:
+ Console(LabEngine *vm);
+ virtual ~Console(void);
+
+private:
+ LabEngine *_vm;
+
+ bool Cmd_Scene(int argc, const char **argv);
+ bool Cmd_DumpSceneResources(int argc, const char **argv);
+ bool Cmd_FindAction(int argc, const char **argv);
+};
+
+} // End of namespace Lab
+#endif
diff --git a/engines/lab/detection.cpp b/engines/lab/detection.cpp
new file mode 100644
index 0000000000..c26231292b
--- /dev/null
+++ b/engines/lab/detection.cpp
@@ -0,0 +1,249 @@
+/* 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.
+ *
+ */
+
+ /*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "engines/advancedDetector.h"
+
+#include "lab/lab.h"
+
+static const PlainGameDescriptor lab_setting[] = {
+ { "lab", "Labyrinth of Time" },
+ { 0, 0 }
+};
+
+static const ADGameDescription labDescriptions[] = {
+ {
+ "lab",
+ "",
+ {
+ { "doors", 0, "d77536010e7e5ae17ee066323ceb9585", 2537 }, // game/doors
+ { "noteold.fon", 0, "6c1d90ad55149556e79d3f7bfddb4bd7", 9252 }, // game/spict/noteold.fon
+ { NULL, 0, NULL, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO0()
+ },
+ {
+ "lab",
+ "Lowres",
+ {
+ { "doors", 0, "d77536010e7e5ae17ee066323ceb9585", 2537 }, // game/doors
+ { "64b", 0, "3a84d41bcc6a782f22e8e954bce09721", 39916 }, // game/pict/h2/64b
+ { NULL, 0, NULL, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ Lab::GF_LOWRES,
+ GUIO0()
+ },
+ {
+ "lab",
+ "Rerelease",
+ {
+ { "doors", 0, "d77536010e7e5ae17ee066323ceb9585", 2537 }, // game/doors
+ { "noteold.fon", 0, "6c1d90ad55149556e79d3f7bfddb4bd7", 9252 }, // game/spict/noteold.fon
+ { "wyrmkeep",0, "97c7064c54c28b952d37c4ebff6efa50", 52286 }, // game/spict/intro
+ { NULL, 0, NULL, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ GUIO0()
+ },
+ {
+ "lab",
+ "",
+ AD_ENTRY1s("doors", "7bf458df6ec30cc8ef4665e4d7c77f59", 2537), // game/doors
+ Common::EN_ANY,
+ Common::kPlatformAmiga,
+ Lab::GF_LOWRES,
+ GUIO0()
+ },
+ AD_TABLE_END_MARKER
+};
+
+static const char *const directoryGlobs[] = {
+ "game",
+ "pict",
+ "spict",
+ "rooms",
+ "h2",
+ "intro",
+ 0
+};
+
+namespace Lab {
+
+Common::Platform LabEngine::getPlatform() const {
+ return _gameDescription->platform;
+}
+
+uint32 LabEngine::getFeatures() const {
+ return _gameDescription->flags | _extraGameFeatures;
+}
+
+} // End of namespace Lab
+
+class LabMetaEngine : public AdvancedMetaEngine {
+public:
+ LabMetaEngine() : AdvancedMetaEngine(labDescriptions, sizeof(ADGameDescription), lab_setting) {
+ _singleid = "lab";
+
+ _maxScanDepth = 4;
+ _directoryGlobs = directoryGlobs;
+ _flags = kADFlagUseExtraAsHint;
+ }
+
+ virtual const char *getName() const {
+ return "Lab";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Labyrinth of Time (c) 2004 The Wyrmkeep Entertainment Co. and Terra Nova Development";
+ }
+
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ // Instantiate Engine even if the game data is not found.
+ *engine = new Lab::LabEngine(syst, desc);
+ return true;
+ }
+
+ virtual bool hasFeature(MetaEngineFeature f) const;
+ SaveStateList listSaves(const char *target) const;
+ virtual int getMaximumSaveSlot() const;
+ void removeSaveState(const char *target, int slot) const;
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
+};
+
+bool LabMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSupportsDeleteSave) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportThumbnail) ||
+ (f == kSavesSupportCreationDate) ||
+ (f == kSavesSupportPlayTime);
+}
+
+bool Lab::LabEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsRTL) ||
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime);
+}
+
+SaveStateList LabMetaEngine::listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Lab::SaveGameHeader header;
+ Common::String pattern = target;
+ pattern += ".???";
+
+ Common::StringArray filenames;
+ filenames = saveFileMan->listSavefiles(pattern.c_str());
+ Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)*/
+
+ SaveStateList saveList;
+
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ // Obtain the last 3 digits of the filename, since they correspond to the save slot
+ int slotNum = atoi(file->c_str() + file->size() - 3);
+
+ if ((slotNum >= 0) && (slotNum <= 999)) {
+ Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str());
+ if (in) {
+ if (Lab::readSaveGameHeader(in, header))
+ saveList.push_back(SaveStateDescriptor(slotNum, header._descr.getDescription()));
+ delete in;
+ }
+ }
+ }
+
+ return saveList;
+}
+
+int LabMetaEngine::getMaximumSaveSlot() const {
+ return 999;
+}
+
+void LabMetaEngine::removeSaveState(const char *target, int slot) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::String filename = Common::String::format("%s.%03u", target, slot);
+
+ saveFileMan->removeSavefile(filename.c_str());
+
+ Common::StringArray filenames;
+ Common::String pattern = target;
+ pattern += ".???";
+ filenames = saveFileMan->listSavefiles(pattern.c_str());
+ Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
+
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ // Obtain the last 3 digits of the filename, since they correspond to the save slot
+ int slotNum = atoi(file->c_str() + file->size() - 3);
+
+ // Rename every slot greater than the deleted slot,
+ if (slotNum > slot) {
+ saveFileMan->renameSavefile(file->c_str(), filename.c_str());
+ filename = Common::String::format("%s.%03u", target, ++slot);
+ }
+ }
+}
+
+SaveStateDescriptor LabMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03u", target, slot);
+ Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str());
+
+ if (in) {
+ Lab::SaveGameHeader header;
+
+ bool successfulRead = Lab::readSaveGameHeader(in, header);
+ delete in;
+
+ if (successfulRead) {
+ SaveStateDescriptor desc(slot, header._descr.getDescription());
+ // Do not allow save slot 0 (used for auto-saving) to be deleted or
+ // overwritten.
+ //desc.setDeletableFlag(slot != 0);
+ //desc.setWriteProtectedFlag(slot == 0);
+
+ return header._descr;
+ }
+ }
+
+ return SaveStateDescriptor();
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(LAB)
+ REGISTER_PLUGIN_DYNAMIC(LAB, PLUGIN_TYPE_ENGINE, LabMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(LAB, PLUGIN_TYPE_ENGINE, LabMetaEngine);
+#endif
diff --git a/engines/lab/dispman.cpp b/engines/lab/dispman.cpp
new file mode 100644
index 0000000000..17623d4f08
--- /dev/null
+++ b/engines/lab/dispman.cpp
@@ -0,0 +1,950 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "graphics/palette.h"
+
+#include "lab/lab.h"
+
+#include "lab/anim.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/music.h"
+#include "lab/image.h"
+#include "lab/resource.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+DisplayMan::DisplayMan(LabEngine *vm) : _vm(vm) {
+ _longWinInFront = false;
+ _lastMessageLong = false;
+ _actionMessageShown = false;
+
+ _screenBytesPerPage = 0;
+ _curBitmap = nullptr;
+ _displayBuffer = nullptr;
+ _currentDisplayBuffer = nullptr;
+ _fadePalette = nullptr;
+
+ _screenWidth = 0;
+ _screenHeight = 0;
+
+ for (int i = 0; i < 256 * 3; i++)
+ _curVgaPal[i] = 0;
+}
+
+DisplayMan::~DisplayMan() {
+ freePict();
+ delete[] _displayBuffer;
+}
+
+void DisplayMan::loadPict(const Common::String filename) {
+ freePict();
+ _curBitmap = _vm->_resource->openDataFile(filename);
+}
+
+void DisplayMan::loadBackPict(const Common::String fileName, uint16 *highPal) {
+ _fadePalette = highPal;
+ _vm->_anim->_noPalChange = true;
+ readPict(fileName);
+
+ for (int i = 0; i < 16; i++) {
+ highPal[i] = ((_vm->_anim->_diffPalette[i * 3] >> 2) << 8) +
+ ((_vm->_anim->_diffPalette[i * 3 + 1] >> 2) << 4) +
+ ((_vm->_anim->_diffPalette[i * 3 + 2] >> 2));
+ }
+
+ _vm->_anim->_noPalChange = false;
+}
+
+void DisplayMan::readPict(const Common::String filename, bool playOnce, bool onlyDiffData, byte *memoryBuffer) {
+ _vm->_anim->stopDiff();
+ loadPict(filename);
+ _vm->updateEvents();
+ _vm->_anim->setOutputBuffer(memoryBuffer);
+ _vm->_anim->readDiff(_curBitmap, playOnce, onlyDiffData);
+}
+
+void DisplayMan::freePict() {
+ delete _curBitmap;
+ _curBitmap = nullptr;
+}
+
+Common::String DisplayMan::getWord(const char *mainBuffer) {
+ Common::String result;
+
+ for (int i = 0; mainBuffer[i] && (mainBuffer[i] != ' ') && (mainBuffer[i] != '\n'); i++)
+ result += mainBuffer[i];
+
+ return result;
+}
+
+Common::String DisplayMan::getLine(TextFont *tf, const char **mainBuffer, uint16 lineWidth) {
+ uint16 curWidth = 0;
+ Common::String result;
+
+ while ((*mainBuffer)[0]) {
+ Common::String wordBuffer = getWord(*mainBuffer);
+
+ if ((curWidth + textLength(tf, wordBuffer)) <= lineWidth) {
+ result += wordBuffer;
+ (*mainBuffer) += wordBuffer.size();
+
+ // end of line
+ if ((*mainBuffer)[0] == '\n') {
+ (*mainBuffer)++;
+ break;
+ }
+
+ // append any space after the word
+ if ((*mainBuffer)[0]) {
+ result += (*mainBuffer)[0];
+ (*mainBuffer)++;
+ }
+
+ curWidth = textLength(tf, result);
+ } else
+ break;
+ }
+
+ return result;
+}
+
+int DisplayMan::flowText(TextFont *font, int16 spacing, byte penColor, byte backPen,
+ bool fillBack, bool centerh, bool centerv, bool output, Common::Rect textRect, const char *str, Image *targetImage) {
+
+ byte *saveDisplayBuffer = _currentDisplayBuffer;
+
+ if (targetImage) {
+ _currentDisplayBuffer = targetImage->_imageData;
+ assert(_screenBytesPerPage == (uint32)(targetImage->_width * targetImage->_height));
+ }
+
+ if (fillBack)
+ rectFill(textRect, backPen);
+
+ if (!str)
+ return 0;
+
+ const char *orig = str;
+
+ TextFont *msgFont = font;
+ uint16 fontHeight = textHeight(msgFont) + spacing;
+ uint16 numLines = (textRect.height() + 1) / fontHeight;
+ uint16 width = textRect.width() + 1;
+ uint16 y = textRect.top;
+
+ if (centerv && output) {
+ const char *temp = str;
+ uint16 actlines = 0;
+
+ while (temp[0]) {
+ getLine(msgFont, &temp, width);
+ actlines++;
+ }
+
+ if (actlines <= numLines)
+ y += ((textRect.height() + 1) - (actlines * fontHeight)) / 2;
+ }
+
+ while (numLines && str[0]) {
+ Common::String lineBuffer;
+ lineBuffer = getLine(msgFont, &str, width);
+
+ uint16 x = textRect.left;
+
+ if (centerh)
+ x += (width - textLength(msgFont, lineBuffer)) / 2;
+
+ if (output)
+ drawText(msgFont, x, y, penColor, lineBuffer);
+
+ numLines--;
+ y += fontHeight;
+ }
+
+ _currentDisplayBuffer = saveDisplayBuffer;
+
+ return (str - orig);
+}
+
+void DisplayMan::createBox(uint16 y2) {
+ // Message box area
+ rectFillScaled(4, 154, 315, y2 - 2, 7);
+
+ // Box around message area
+ drawHLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleX(317), 0);
+ drawVLine(_vm->_utils->vgaScaleX(317), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleY(y2), 0);
+ drawHLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(y2), _vm->_utils->vgaScaleX(317), 0);
+ drawVLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleY(y2), 0);
+}
+
+int DisplayMan::longDrawMessage(Common::String str, bool isActionMessage) {
+ if (isActionMessage) {
+ _actionMessageShown = true;
+ } else if (_actionMessageShown) {
+ _actionMessageShown = false;
+ return 0;
+ }
+
+ if (str.empty())
+ return 0;
+
+ _vm->_event->attachButtonList(nullptr);
+
+ if (!_longWinInFront) {
+ _longWinInFront = true;
+ // Clear Area
+ rectFill(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), _vm->_utils->vgaScaleY(199), 3);
+ }
+
+ createBox(198);
+
+ return flowText(_vm->_msgFont, 0, 1, 7, false, true, true, true, _vm->_utils->vgaRectScale(6, 155, 313, 195), str.c_str());
+}
+
+void DisplayMan::drawMessage(Common::String str, bool isActionMessage) {
+ if (isActionMessage) {
+ _actionMessageShown = true;
+ } else if (_actionMessageShown) {
+ _actionMessageShown = false;
+ return;
+ }
+
+ if (str.empty())
+ return;
+
+ if ((textLength(_vm->_msgFont, str) > _vm->_utils->vgaScaleX(306))) {
+ longDrawMessage(str, isActionMessage);
+ _lastMessageLong = true;
+ } else {
+ if (_longWinInFront) {
+ _longWinInFront = false;
+ drawPanel();
+ }
+
+ createBox(168);
+ drawText(_vm->_msgFont, _vm->_utils->vgaScaleX(7), _vm->_utils->vgaScaleY(155) + _vm->_utils->svgaCord(2), 1, str);
+ _lastMessageLong = false;
+ }
+}
+
+void DisplayMan::drawPanel() {
+ // Clear Area
+ rectFill(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), _vm->_utils->vgaScaleY(199), 3);
+
+ // First Line
+ drawHLine(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), 0);
+ // Second Line
+ drawHLine(0, _vm->_utils->vgaScaleY(149) + 1 + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), 5);
+ // Button Separators
+ drawHLine(0, _vm->_utils->vgaScaleY(170), _vm->_utils->vgaScaleX(319), 0);
+
+ if (!_vm->_alternate) {
+ // The horizontal lines under the black one
+ drawHLine(0, _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(319), 4);
+ _vm->_event->drawButtonList(&_vm->_moveButtonList);
+ } else {
+ if (_vm->getPlatform() != Common::kPlatformWindows) {
+ // Vertical Black lines
+ drawVLine(_vm->_utils->vgaScaleX(124), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
+ drawVLine(_vm->_utils->vgaScaleX(194), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
+ } else {
+ // Vertical Black lines
+ drawVLine(_vm->_utils->vgaScaleX(90), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
+ drawVLine(_vm->_utils->vgaScaleX(160), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
+ drawVLine(_vm->_utils->vgaScaleX(230), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
+ }
+
+ // The horizontal lines under the black one
+ drawHLine(0, _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(122), 4);
+ drawHLine(_vm->_utils->vgaScaleX(126), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(192), 4);
+ drawHLine(_vm->_utils->vgaScaleX(196), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(319), 4);
+ // The vertical high light lines
+ drawVLine(_vm->_utils->vgaScaleX(1), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
+
+ if (_vm->getPlatform() != Common::kPlatformWindows) {
+ drawVLine(_vm->_utils->vgaScaleX(126), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
+ drawVLine(_vm->_utils->vgaScaleX(196), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
+ } else {
+ drawVLine(_vm->_utils->vgaScaleX(92), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
+ drawVLine(_vm->_utils->vgaScaleX(162), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
+ drawVLine(_vm->_utils->vgaScaleX(232), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
+ }
+
+ _vm->_event->drawButtonList(&_vm->_invButtonList);
+ }
+}
+
+void DisplayMan::setUpScreens() {
+ EventManager *e = _vm->_event;
+ ButtonList *moveButtonList = &_vm->_moveButtonList;
+ ButtonList *invButtonList = &_vm->_invButtonList;
+ Image **moveImages = _vm->_moveImages;
+ Image **invImages = _vm->_invImages;
+
+ createScreen(_vm->_isHiRes);
+
+ // TODO: The CONTROL file is not present in the Amiga version
+ Common::File *controlFile = _vm->_resource->openDataFile("P:Control");
+ for (int i = 0; i < 20; i++)
+ _vm->_moveImages[i] = new Image(controlFile, _vm);
+ delete controlFile;
+
+ // Creates the buttons for the movement control panel
+ // The key mapping was only set for the Windows version.
+ // It's very convenient to have those shortcut, so I added them
+ // for all versions. (Strangerke)
+ uint16 y = _vm->_utils->vgaScaleY(173) - _vm->_utils->svgaCord(2);
+ moveButtonList->push_back(e->createButton( 1, y, 0, Common::KEYCODE_t, moveImages[0], moveImages[1]));
+ moveButtonList->push_back(e->createButton( 33, y, 1, Common::KEYCODE_m, moveImages[2], moveImages[3]));
+ moveButtonList->push_back(e->createButton( 65, y, 2, Common::KEYCODE_o, moveImages[4], moveImages[5]));
+ moveButtonList->push_back(e->createButton( 97, y, 3, Common::KEYCODE_c, moveImages[6], moveImages[7]));
+ moveButtonList->push_back(e->createButton(129, y, 4, Common::KEYCODE_l, moveImages[8], moveImages[9]));
+ moveButtonList->push_back(e->createButton(161, y, 5, Common::KEYCODE_i, moveImages[12], moveImages[13]));
+ moveButtonList->push_back(e->createButton(193, y, 6, Common::KEYCODE_LEFT, moveImages[14], moveImages[15]));
+ moveButtonList->push_back(e->createButton(225, y, 7, Common::KEYCODE_UP, moveImages[16], moveImages[17]));
+ moveButtonList->push_back(e->createButton(257, y, 8, Common::KEYCODE_RIGHT, moveImages[18], moveImages[19]));
+ moveButtonList->push_back(e->createButton(289, y, 9, Common::KEYCODE_p, moveImages[10], moveImages[11]));
+
+ // TODO: The INV file is not present in the Amiga version
+ Common::File *invFile = _vm->_resource->openDataFile("P:Inv");
+ if (_vm->getPlatform() == Common::kPlatformWindows) {
+ for (int imgIdx = 0; imgIdx < 10; imgIdx++)
+ _vm->_invImages[imgIdx] = new Image(invFile, _vm);
+ } else {
+ for (int imgIdx = 0; imgIdx < 6; imgIdx++)
+ _vm->_invImages[imgIdx] = new Image(invFile, _vm);
+ }
+ invButtonList->push_back(e->createButton( 24, y, 0, Common::KEYCODE_ESCAPE, invImages[0], invImages[1]));
+ invButtonList->push_back(e->createButton( 56, y, 1, Common::KEYCODE_g, invImages[2], invImages[3]));
+ invButtonList->push_back(e->createButton( 94, y, 2, Common::KEYCODE_u, invImages[4], invImages[5]));
+ invButtonList->push_back(e->createButton(126, y, 3, Common::KEYCODE_l, moveImages[8], moveImages[9]));
+ invButtonList->push_back(e->createButton(164, y, 4, Common::KEYCODE_LEFT, moveImages[14], moveImages[15]));
+ invButtonList->push_back(e->createButton(196, y, 5, Common::KEYCODE_RIGHT, moveImages[18], moveImages[19]));
+
+ // The windows version has 2 extra buttons for breadcrumb trail
+ // CHECKME: the game is really hard to play without those, maybe we could add something to enable that.
+ if (_vm->getPlatform() == Common::kPlatformWindows) {
+ invButtonList->push_back(e->createButton(234, y, 6, Common::KEYCODE_b, invImages[6], invImages[7]));
+ invButtonList->push_back(e->createButton(266, y, 7, Common::KEYCODE_f, invImages[8], invImages[9]));
+ }
+
+ delete invFile;
+}
+
+void DisplayMan::rectFill(Common::Rect fillRect, byte color) {
+ int width = fillRect.width() + 1;
+ int height = fillRect.height() + 1;
+
+ if (fillRect.left + width > _screenWidth)
+ width = _screenWidth - fillRect.left;
+
+ if (fillRect.top + height > _screenHeight)
+ height = _screenHeight - fillRect.top;
+
+ if ((width > 0) && (height > 0)) {
+ byte *d = getCurrentDrawingBuffer() + fillRect.top * _screenWidth + fillRect.left;
+
+ while (height-- > 0) {
+ byte *dd = d;
+ int ww = width;
+
+ while (ww-- > 0) {
+ *dd++ = color;
+ }
+
+ d += _screenWidth;
+ }
+ }
+}
+
+void DisplayMan::rectFill(uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte color) {
+ rectFill(Common::Rect(x1, y1, x2, y2), color);
+}
+
+void DisplayMan::rectFillScaled(uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte color) {
+ rectFill(_vm->_utils->vgaRectScale(x1, y1, x2, y2), color);
+}
+
+void DisplayMan::drawVLine(uint16 x, uint16 y1, uint16 y2, byte color) {
+ rectFill(x, y1, x, y2, color);
+}
+
+void DisplayMan::drawHLine(uint16 x1, uint16 y, uint16 x2, byte color) {
+ rectFill(x1, y, x2, y, color);
+}
+
+void DisplayMan::screenUpdate() {
+ _vm->_system->copyRectToScreen(_displayBuffer, _screenWidth, 0, 0, _screenWidth, _screenHeight);
+ _vm->_system->updateScreen();
+
+ _vm->_event->processInput();
+}
+
+void DisplayMan::createScreen(bool hiRes) {
+ if (hiRes) {
+ _screenWidth = 640;
+ _screenHeight = 480;
+ } else {
+ _screenWidth = 320;
+ _screenHeight = 200;
+ }
+ _screenBytesPerPage = _screenWidth * _screenHeight;
+
+ if (_displayBuffer)
+ delete[] _displayBuffer;
+ _displayBuffer = new byte[_screenBytesPerPage];
+ memset(_displayBuffer, 0, _screenBytesPerPage);
+}
+
+void DisplayMan::setAmigaPal(uint16 *pal) {
+ byte vgaPal[16 * 3];
+ uint16 vgaIdx = 0;
+
+ for (int i = 0; i < 16; i++) {
+ vgaPal[vgaIdx++] = (byte)(((pal[i] & 0xf00) >> 8) << 2);
+ vgaPal[vgaIdx++] = (byte)(((pal[i] & 0x0f0) >> 4) << 2);
+ vgaPal[vgaIdx++] = (byte)(((pal[i] & 0x00f)) << 2);
+ }
+
+ writeColorRegs(vgaPal, 0, 16);
+ _vm->waitTOF();
+}
+
+void DisplayMan::writeColorRegs(byte *buf, uint16 first, uint16 numReg) {
+ assert(first + numReg <= 256);
+ byte tmp[256 * 3];
+
+ for (int i = 0; i < numReg * 3; i++)
+ tmp[i] = (buf[i] << 2) | (buf[i] >> 4); // better results than buf[i] * 4
+
+ _vm->_system->getPaletteManager()->setPalette(tmp, first, numReg);
+ memcpy(&(_curVgaPal[first * 3]), buf, numReg * 3);
+}
+
+void DisplayMan::setPalette(void *newPal, uint16 numColors) {
+ if (memcmp(newPal, _curVgaPal, numColors * 3) != 0)
+ writeColorRegs((byte *)newPal, 0, numColors);
+}
+
+byte *DisplayMan::getCurrentDrawingBuffer() {
+ if (_currentDisplayBuffer)
+ return _currentDisplayBuffer;
+
+ return _displayBuffer;
+}
+
+void DisplayMan::checkerBoardEffect(uint16 penColor, uint16 x1, uint16 y1, uint16 x2, uint16 y2) {
+ int w = x2 - x1 + 1;
+ int h = y2 - y1 + 1;
+
+ if (x1 + w > _screenWidth)
+ w = _screenWidth - x1;
+
+ if (y1 + h > _screenHeight)
+ h = _screenHeight - y1;
+
+ if ((w > 0) && (h > 0)) {
+ byte *d = getCurrentDrawingBuffer() + y1 * _screenWidth + x1;
+
+ while (h-- > 0) {
+ byte *dd = d;
+ int ww = w;
+
+ if (y1 & 1) {
+ dd++;
+ ww--;
+ }
+
+ while (ww > 0) {
+ *dd = penColor;
+ dd += 2;
+ ww -= 2;
+ }
+
+ d += _screenWidth;
+ y1++;
+ }
+ }
+}
+
+void DisplayMan::freeFont(TextFont **font) {
+ if (*font) {
+ if ((*font)->_data)
+ delete[] (*font)->_data;
+
+ delete *font;
+ *font = nullptr;
+ }
+}
+
+uint16 DisplayMan::textLength(TextFont *font, const Common::String text) {
+ uint16 length = 0;
+
+ if (font) {
+ int numChars = text.size();
+ for (int i = 0; i < numChars; i++) {
+ length += font->_widths[(byte)text[i]];
+ }
+ }
+
+ return length;
+}
+
+uint16 DisplayMan::textHeight(TextFont *tf) {
+ return (tf) ? tf->_height : 0;
+}
+
+void DisplayMan::drawText(TextFont *tf, uint16 x, uint16 y, uint16 color, const Common::String text) {
+ byte *vgaTop = getCurrentDrawingBuffer();
+ int numChars = text.size();
+
+ for (int i = 0; i < numChars; i++) {
+ uint32 realOffset = (_screenWidth * y) + x;
+ uint16 curPage = realOffset / _screenBytesPerPage;
+ uint32 segmentOffset = realOffset - (curPage * _screenBytesPerPage);
+ int32 leftInSegment = _screenBytesPerPage - segmentOffset;
+ byte *vgaCur = vgaTop + segmentOffset;
+
+ if (tf->_widths[(byte)text[i]]) {
+ byte *cdata = tf->_data + tf->_offsets[(byte)text[i]];
+ uint16 bwidth = *cdata++;
+ byte *vgaTemp = vgaCur;
+ byte *vgaTempLine = vgaCur;
+
+ for (int rows = 0; rows < tf->_height; rows++) {
+ int32 templeft = leftInSegment;
+
+ vgaTemp = vgaTempLine;
+
+ for (int cols = 0; cols < bwidth; cols++) {
+ uint16 data = *cdata++;
+
+ if (data && (templeft >= 8)) {
+ for (int j = 7; j >= 0; j--) {
+ if ((1 << j) & data)
+ *vgaTemp = color;
+ vgaTemp++;
+ }
+
+ templeft -= 8;
+ } else if (data) {
+ uint16 mask = 0x80;
+ templeft = leftInSegment;
+
+ for (int counterb = 0; counterb < 8; counterb++) {
+ if (templeft <= 0) {
+ curPage++;
+ vgaTemp = vgaTop - templeft;
+ // Set up VGATempLine for next line
+ vgaTempLine -= _screenBytesPerPage;
+ // Set up LeftInSegment for next line
+ leftInSegment += _screenBytesPerPage + templeft;
+ templeft += _screenBytesPerPage;
+ }
+
+ if (mask & data)
+ *vgaTemp = color;
+
+ vgaTemp++;
+
+ mask = mask >> 1;
+ templeft--;
+ }
+ } else {
+ templeft -= 8;
+ vgaTemp += 8;
+ }
+ }
+
+ vgaTempLine += _screenWidth;
+ leftInSegment -= _screenWidth;
+
+ if (leftInSegment <= 0) {
+ curPage++;
+ vgaTempLine -= _screenBytesPerPage;
+ leftInSegment += _screenBytesPerPage;
+ }
+ }
+ }
+
+ x += tf->_widths[(byte)text[i]];
+ }
+}
+
+void DisplayMan::doScrollBlack() {
+ uint16 width = _vm->_utils->vgaScaleX(320);
+ uint16 height = _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2);
+
+ _vm->_event->mouseHide();
+
+ byte *mem = new byte[width * height];
+ int16 by = _vm->_utils->vgaScaleX(4);
+ int16 verticalScroll = height;
+
+ while (verticalScroll > 0) {
+ scrollDisplayY(-by, 0, 0, width - 1, height - 1, mem);
+ verticalScroll -= by;
+
+ _vm->updateEvents();
+ _vm->waitTOF();
+ }
+
+ delete[] mem;
+
+ _vm->_event->mouseShow();
+}
+
+void DisplayMan::copyPage(uint16 width, uint16 height, uint16 nheight, uint16 startLine, byte *mem) {
+ byte *baseAddr = getCurrentDrawingBuffer();
+
+ uint32 size = (int32)(height - nheight) * (int32)width;
+ mem += startLine * width;
+ uint16 curPage = ((int32)nheight * (int32)width) / _screenBytesPerPage;
+ uint32 offSet = ((int32)nheight * (int32)width) - (curPage * _screenBytesPerPage);
+
+ while (size) {
+ uint32 copySize;
+ if (size > (_screenBytesPerPage - offSet))
+ copySize = _screenBytesPerPage - offSet;
+ else
+ copySize = size;
+
+ size -= copySize;
+
+ memcpy(baseAddr + (offSet >> 2), mem, copySize);
+ mem += copySize;
+ curPage++;
+ offSet = 0;
+ }
+}
+
+void DisplayMan::doScrollWipe(const Common::String filename) {
+ _vm->_event->mouseHide();
+ uint16 width = _vm->_utils->vgaScaleX(320);
+ uint16 height = _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2);
+
+ while (_vm->_music->isSoundEffectActive()) {
+ _vm->updateEvents();
+ _vm->waitTOF();
+ }
+
+ readPict(filename, true, true);
+ setPalette(_vm->_anim->_diffPalette, 256);
+ byte *mem = _vm->_anim->_scrollScreenBuffer;
+
+ _vm->updateEvents();
+ uint16 by = _vm->_utils->vgaScaleX(3);
+ uint16 nheight = height;
+ uint16 startLine = 0, onRow = 0;
+
+ while (onRow < _vm->_anim->getDIFFHeight()) {
+ _vm->updateEvents();
+
+ if ((by > nheight) && nheight)
+ by = nheight;
+
+ if ((startLine + by) > (_vm->_anim->getDIFFHeight() - height - 1))
+ break;
+
+ if (nheight)
+ nheight -= by;
+
+ copyPage(width, height, nheight, startLine, mem);
+ screenUpdate();
+
+ if (!nheight)
+ startLine += by;
+
+ onRow += by;
+
+ if (nheight <= (height / 4))
+ by = _vm->_utils->vgaScaleX(5);
+ else if (nheight <= (height / 3))
+ by = _vm->_utils->vgaScaleX(4);
+ else if (nheight <= (height / 2))
+ by = _vm->_utils->vgaScaleX(3);
+ }
+
+ _vm->_event->mouseShow();
+}
+
+void DisplayMan::doScrollBounce() {
+ const uint16 offsets[8] = { 3, 3, 2, 2, 2, 1, 1, 1 };
+ const int multiplier = (_vm->_isHiRes) ? 2 : 1;
+
+ _vm->_event->mouseHide();
+ int width = _vm->_utils->vgaScaleX(320);
+ int height = _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2);
+ byte *mem = _vm->_anim->_scrollScreenBuffer;
+
+ _vm->updateEvents();
+ int startLine = _vm->_anim->getDIFFHeight() - height - 1;
+
+ for (int i = 0; i < 5; i++) {
+ _vm->updateEvents();
+ startLine -= (5 - i) * multiplier;
+ copyPage(width, height, 0, startLine, mem);
+ _vm->waitTOF();
+ }
+
+ for (int i = 8; i > 0; i--) {
+ _vm->updateEvents();
+ startLine += offsets[i - 1] * multiplier;
+ copyPage(width, height, 0, startLine, mem);
+ _vm->waitTOF();
+ }
+
+ _vm->_event->mouseShow();
+}
+
+void DisplayMan::doTransWipe(const Common::String filename) {
+ uint16 lastY, linesLast;
+
+ if (_vm->_isHiRes) {
+ linesLast = 3;
+ lastY = 358;
+ } else {
+ linesLast = 1;
+ lastY = 148;
+ }
+
+ uint16 linesDone = 0;
+
+ for (int j = 0; j < 2; j++) {
+ for (int i = 0; i < 2; i++) {
+ uint16 curY = i * 2;
+
+ while (curY < lastY) {
+ if (linesDone >= linesLast) {
+ _vm->updateEvents();
+ _vm->waitTOF();
+ linesDone = 0;
+ }
+
+ if (j == 0)
+ checkerBoardEffect(0, 0, curY, _screenWidth - 1, curY + 1);
+ else
+ rectFill(0, curY, _screenWidth - 1, curY + 1, 0);
+ curY += 4;
+ linesDone++;
+ } // while
+ } // for i
+ } // for j
+
+ if (filename.empty())
+ _vm->_curFileName = _vm->getPictName(true);
+ else if (filename[0] > ' ')
+ _vm->_curFileName = filename;
+ else
+ _vm->_curFileName = _vm->getPictName(true);
+
+ byte *bitMapBuffer = new byte[_screenWidth * (lastY + 5)];
+ readPict(_vm->_curFileName, true, false, bitMapBuffer);
+
+ setPalette(_vm->_anim->_diffPalette, 256);
+
+ Image imgSource(_vm);
+ imgSource._width = _screenWidth;
+ imgSource._height = lastY;
+ imgSource.setData(bitMapBuffer, true);
+
+ Image imgDest(_vm);
+ imgDest._width = _screenWidth;
+ imgDest._height = _screenHeight;
+ imgDest.setData(getCurrentDrawingBuffer(), false);
+
+ for (int j = 0; j < 2; j++) {
+ for (int i = 0; i < 2; i++) {
+ uint16 curY = i * 2;
+
+ while (curY < lastY) {
+ if (linesDone >= linesLast) {
+ _vm->updateEvents();
+ _vm->waitTOF();
+ linesDone = 0;
+ }
+
+ imgDest.setData(getCurrentDrawingBuffer(), false);
+
+ if (j == 0) {
+ imgSource.blitBitmap(0, curY, &imgDest, 0, curY, _screenWidth, 2, false);
+ checkerBoardEffect(0, 0, curY, _screenWidth - 1, curY + 1);
+ } else {
+ uint16 bitmapHeight = (curY == lastY) ? 1 : 2;
+ imgSource.blitBitmap(0, curY, &imgDest, 0, curY, _screenWidth, bitmapHeight, false);
+ }
+ curY += 4;
+ linesDone++;
+ } // while
+ } // for i
+ } // for j
+
+ // bitMapBuffer will be deleted by the Image destructor
+}
+
+void DisplayMan::doTransition(TransitionType transitionType, const Common::String filename) {
+ switch (transitionType) {
+ case kTransitionWipe:
+ case kTransitionTransporter:
+ doTransWipe(filename);
+ break;
+ case kTransitionScrollWipe: // only used in scene 7 (street, when teleporting to the surreal maze)
+ doScrollWipe(filename);
+ break;
+ case kTransitionScrollBlack: // only used in scene 7 (street, when teleporting to the surreal maze)
+ doScrollBlack();
+ break;
+ case kTransitionScrollBounce: // only used in scene 7 (street, when teleporting to the surreal maze)
+ doScrollBounce();
+ break;
+ case kTransitionReadFirstFrame: // only used in scene 7 (street, when teleporting to the surreal maze)
+ readPict(filename, false);
+ break;
+ case kTransitionReadNextFrame: // only used in scene 7 (street, when teleporting to the surreal maze)
+ _vm->_anim->diffNextFrame();
+ break;
+ case kTransitionNone:
+ default:
+ break;
+ }
+}
+
+void DisplayMan::blackScreen() {
+ byte pal[256 * 3];
+ memset(pal, 0, 248 * 3);
+ writeColorRegs(pal, 8, 248);
+
+ _vm->_system->delayMillis(32);
+}
+
+void DisplayMan::whiteScreen() {
+ byte pal[256 * 3];
+ memset(pal, 255, 248 * 3);
+ writeColorRegs(pal, 8, 248);
+}
+
+void DisplayMan::blackAllScreen() {
+ byte pal[256 * 3];
+ memset(pal, 0, 256 * 3);
+ writeColorRegs(pal, 0, 256);
+
+ _vm->_system->delayMillis(32);
+}
+
+void DisplayMan::scrollDisplayX(int16 dx, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer) {
+ Image img(_vm);
+ img.setData(buffer, false);
+
+ if (x1 > x2)
+ SWAP<uint16>(x1, x2);
+
+ if (y1 > y2)
+ SWAP<uint16>(y1, y2);
+
+ if (dx > 0) {
+ img._width = x2 - x1 + 1 - dx;
+ img._height = y2 - y1 + 1;
+
+ img.readScreenImage(x1, y1);
+ img.drawImage(x1 + dx, y1);
+
+ rectFill(x1, y1, x1 + dx - 1, y2, 0);
+ } else if (dx < 0) {
+ img._width = x2 - x1 + 1 + dx;
+ img._height = y2 - y1 + 1;
+
+ img.readScreenImage(x1 - dx, y1);
+ img.drawImage(x1, y1);
+
+ rectFill(x2 + dx + 1, y1, x2, y2, 0);
+ }
+}
+
+void DisplayMan::scrollDisplayY(int16 dy, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer) {
+ Image img(_vm);
+ img.setData(buffer, false);
+
+ if (x1 > x2)
+ SWAP<uint16>(x1, x2);
+
+ if (y1 > y2)
+ SWAP<uint16>(y1, y2);
+
+ if (dy > 0) {
+ img._width = x2 - x1 + 1;
+ img._height = y2 - y1 + 1 - dy;
+
+ img.readScreenImage(x1, y1);
+ img.drawImage(x1, y1 + dy);
+
+ rectFill(x1, y1, x2, y1 + dy - 1, 0);
+ } else if (dy < 0) {
+ img._width = x2 - x1 + 1;
+ img._height = y2 - y1 + 1 + dy;
+
+ img.readScreenImage(x1, y1 - dy);
+ img.drawImage(x1, y1);
+
+ rectFill(x1, y2 + dy + 1, x2, y2, 0);
+ }
+}
+
+uint16 DisplayMan::fadeNumIn(uint16 num, uint16 res, uint16 counter) {
+ return (num - ((((int32)(15 - counter)) * ((int32)(num - res))) / 15));
+}
+
+uint16 DisplayMan::fadeNumOut(uint16 num, uint16 res, uint16 counter) {
+ return (num - ((((int32) counter) * ((int32)(num - res))) / 15));
+}
+
+void DisplayMan::fade(bool fadeIn) {
+ uint16 newPal[16];
+
+ for (int i = 0; i < 16; i++) {
+ for (int palIdx = 0; palIdx < 16; palIdx++) {
+ if (fadeIn)
+ newPal[palIdx] = (0x00F & fadeNumIn(0x00F & _fadePalette[palIdx], 0, i)) +
+ (0x0F0 & fadeNumIn(0x0F0 & _fadePalette[palIdx], 0, i)) +
+ (0xF00 & fadeNumIn(0xF00 & _fadePalette[palIdx], 0, i));
+ else
+ newPal[palIdx] = (0x00F & fadeNumOut(0x00F & _fadePalette[palIdx], 0, i)) +
+ (0x0F0 & fadeNumOut(0x0F0 & _fadePalette[palIdx], 0, i)) +
+ (0xF00 & fadeNumOut(0xF00 & _fadePalette[palIdx], 0, i));
+ }
+
+ setAmigaPal(newPal);
+ _vm->waitTOF();
+ _vm->updateEvents();
+ }
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/dispman.h b/engines/lab/dispman.h
new file mode 100644
index 0000000000..d83d4fb73e
--- /dev/null
+++ b/engines/lab/dispman.h
@@ -0,0 +1,274 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_DISPMAN_H
+#define LAB_DISPMAN_H
+
+namespace Lab {
+
+class LabEngine;
+class Image;
+
+struct TextFont {
+ uint32 _dataLength;
+ uint16 _height;
+ byte _widths[256];
+ uint16 _offsets[256];
+ byte *_data;
+};
+
+enum TransitionType {
+ kTransitionNone,
+ kTransitionWipe,
+ kTransitionScrollWipe,
+ kTransitionScrollBlack,
+ kTransitionScrollBounce,
+ kTransitionTransporter,
+ kTransitionReadFirstFrame,
+ kTransitionReadNextFrame
+};
+
+class DisplayMan {
+private:
+ LabEngine *_vm;
+
+ /**
+ * Does the fading of the Palette on the screen.
+ */
+ uint16 fadeNumIn(uint16 num, uint16 res, uint16 counter);
+ uint16 fadeNumOut(uint16 num, uint16 res, uint16 counter);
+
+ /**
+ * Extracts the first word from a string.
+ */
+ Common::String getWord(const char *mainBuffer);
+
+ void createBox(uint16 y2);
+
+ /**
+ * Sets up either a low-res or a high-res 256 color screen.
+ */
+ void createScreen(bool hiRes);
+
+ /**
+ * Scrolls the display to black.
+ */
+ void doScrollBlack();
+ void copyPage(uint16 width, uint16 height, uint16 nheight, uint16 startline, byte *mem);
+
+ /**
+ * Scrolls the display to a new picture from a black screen.
+ */
+ void doScrollWipe(const Common::String filename);
+
+ /**
+ * Does the scroll bounce. Assumes bitmap already in memory.
+ */
+ void doScrollBounce();
+
+ /**
+ * Does the transporter wipe.
+ */
+ void doTransWipe(const Common::String filename);
+
+ /**
+ * Draws a vertical line.
+ */
+ void drawHLine(uint16 x, uint16 y1, uint16 y2, byte color);
+
+ /**
+ * Draws a horizontal line.
+ */
+ void drawVLine(uint16 x1, uint16 y, uint16 x2, byte color);
+
+ /**
+ * Draws the text to the screen.
+ */
+ void drawText(TextFont *tf, uint16 x, uint16 y, uint16 color, const Common::String text);
+
+ /**
+ * Gets a line of text for flowText; makes sure that its length is less than
+ * or equal to the maximum width.
+ */
+ Common::String getLine(TextFont *tf, const char **mainBuffer, uint16 lineWidth);
+
+ /**
+ * Returns the length of a text in the specified font.
+ */
+ uint16 textLength(TextFont *font, const Common::String text);
+
+ bool _actionMessageShown;
+ Common::File *_curBitmap;
+ byte _curVgaPal[256 * 3];
+ byte *_currentDisplayBuffer;
+
+public:
+ DisplayMan(LabEngine *lab);
+ virtual ~DisplayMan();
+
+ void loadPict(const Common::String filename);
+ void loadBackPict(const Common::String fileName, uint16 *highPal);
+
+ /**
+ * Reads in a picture into the display bitmap.
+ */
+ void readPict(const Common::String filename, bool playOnce = true, bool onlyDiffData = false, byte *memoryBuffer = nullptr);
+ void freePict();
+
+ /**
+ * Does a certain number of pre-programmed wipes.
+ */
+ void doTransition(TransitionType transitionType, const Common::String filename);
+
+ /**
+ * Changes the front screen to black.
+ */
+ void blackScreen();
+
+ /**
+ * Changes the front screen to white.
+ */
+ void whiteScreen();
+
+ /**
+ * Changes the entire screen to black.
+ */
+ void blackAllScreen();
+
+ /**
+ * Draws the control panel display.
+ */
+ void drawPanel();
+
+ /**
+ * Sets up the Labyrinth screens, and opens up the initial windows.
+ */
+ void setUpScreens();
+
+ int longDrawMessage(Common::String str, bool isActionMessage);
+
+ /**
+ * Draws a message to the message box.
+ */
+ void drawMessage(Common::String str, bool isActionMessage);
+
+ void setActionMessage(bool val) { _actionMessageShown = val; }
+
+ /**
+ * Fills in a rectangle.
+ */
+ void rectFill(uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte color);
+ void rectFill(Common::Rect fillRect, byte color);
+ void rectFillScaled(uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte color);
+ /**
+ * Dumps a chunk of text to an arbitrary box; flows it within that box and
+ * optionally centers it. Returns the number of characters that were processed.
+ * @note Every individual word MUST be int16 enough to fit on a line, and
+ * each line less than 255 characters.
+ * @param font Pointer on the font used
+ * @param spacing How much vertical spacing between the lines
+ * @param penColor Pen number to use for text
+ * @param backPen Background color
+ * @param fillBack Whether to fill the background
+ * @param centerh Whether to center the text horizontally
+ * @param centerv Whether to center the text vertically
+ * @param output Whether to output any text
+ * @param textRect Coords
+ * @param text The text itself
+ */
+ int flowText(TextFont *font, int16 spacing, byte penColor, byte backPen, bool fillBack,
+ bool centerh, bool centerv, bool output, Common::Rect textRect, const char *text, Image *targetImage = nullptr);
+
+ void screenUpdate();
+
+ /**
+ * Converts a 16-color Amiga palette to a VGA palette, then sets
+ * the VGA palette.
+ */
+ void setAmigaPal(uint16 *pal);
+
+ /**
+ * Writes any number of the 256 color registers.
+ * @param buf A char pointer which contains the selected color registers.
+ * Each value representing a color register occupies 3 bytes in the array. The
+ * order is red, green then blue. The first byte in the array is the red component
+ * of the first element selected. The length of the buffer is 3 times the number
+ * of registers selected.
+ * @param first The number of the first color register to write.
+ * @param numReg The number of registers to write.
+ */
+ void writeColorRegs(byte *buf, uint16 first, uint16 numReg);
+ void setPalette(void *newPal, uint16 numColors);
+
+ /**
+ * Overlays a region on the screen using the desired pen color.
+ */
+ void checkerBoardEffect(uint16 penColor, uint16 x1, uint16 y1, uint16 x2, uint16 y2);
+
+ /**
+ * Returns the base address of the current VGA display.
+ */
+ byte *getCurrentDrawingBuffer();
+
+ /**
+ * Scrolls the display in the x direction by blitting.
+ * The _tempScrollData variable must be initialized to some memory, or this
+ * function will fail.
+ */
+ void scrollDisplayX(int16 dx, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer);
+
+ /**
+ * Scrolls the display in the y direction by blitting.
+ */
+ void scrollDisplayY(int16 dy, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer);
+ void fade(bool fadein);
+
+ /**
+ * Closes a font and frees all memory associated with it.
+ */
+ void freeFont(TextFont **font);
+
+ /**
+ * Returns the height of a specified font.
+ */
+ uint16 textHeight(TextFont *tf);
+
+ bool _longWinInFront;
+ bool _lastMessageLong;
+ uint32 _screenBytesPerPage;
+ int _screenWidth;
+ int _screenHeight;
+ byte *_displayBuffer;
+ uint16 *_fadePalette;
+};
+
+} // End of namespace Lab
+
+#endif // LAB_DISPMAN_H
diff --git a/engines/lab/engine.cpp b/engines/lab/engine.cpp
new file mode 100644
index 0000000000..cd47342097
--- /dev/null
+++ b/engines/lab/engine.cpp
@@ -0,0 +1,1196 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/config-manager.h"
+
+#include "lab/lab.h"
+#include "lab/anim.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/image.h"
+#include "lab/intro.h"
+#include "lab/labsets.h"
+#include "lab/music.h"
+#include "lab/processroom.h"
+#include "lab/resource.h"
+#include "lab/speciallocks.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+#define CRUMBSWIDTH 24
+#define CRUMBSHEIGHT 24
+
+enum SpecialLock {
+ kLockCombination = 100,
+ kLockTiles = 101,
+ kLockTileSolution = 102
+};
+
+enum Items {
+ kItemHelmet = 1,
+ kItemBelt = 3,
+ kItemPithHelmet = 7,
+ kItemJournal = 9,
+ kItemNotes = 12,
+ kItemWestPaper = 18,
+ kItemWhiskey = 25,
+ kItemLamp = 27,
+ kItemMap = 28,
+ kItemQuarter = 30
+};
+
+enum Monitors {
+ kMonitorMuseum = 71,
+ kMonitorGramophone = 72,
+ kMonitorUnicycle = 73,
+ kMonitorStatue = 74,
+ kMonitorTalisman = 75,
+ kMonitorLute = 76,
+ kMonitorClock = 77,
+ kMonitorWindow = 78,
+ //kMonitorBelt = 79,
+ kMonitorLibrary = 80,
+ kMonitorTerminal = 81
+ //kMonitorLevers = 82
+};
+
+enum AltButtons {
+ kButtonMainDisplay,
+ kButtonSaveLoad,
+ kButtonUseItem,
+ kButtonLookAtItem,
+ kButtonPrevItem,
+ kButtonNextItem,
+ kButtonBreadCrumbs,
+ kButtonFollowCrumbs
+};
+
+static char initColors[] = { '\x00', '\x00', '\x00', '\x30',
+ '\x30', '\x30', '\x10', '\x10',
+ '\x10', '\x14', '\x14', '\x14',
+ '\x20', '\x20', '\x20', '\x24',
+ '\x24', '\x24', '\x2c', '\x2c',
+ '\x2c', '\x08', '\x08', '\x08' };
+
+uint16 LabEngine::getQuarters() {
+ return _inventory[kItemQuarter]._quantity;
+}
+
+void LabEngine::setQuarters(uint16 quarters) {
+ _inventory[kItemQuarter]._quantity = quarters;
+}
+
+void LabEngine::drawRoomMessage(uint16 curInv, const CloseData *closePtr) {
+ if (_lastTooLong) {
+ _lastTooLong = false;
+ return;
+ }
+
+ if (_alternate) {
+ if ((curInv <= _numInv) && _conditions->in(curInv) && !_inventory[curInv]._bitmapName.empty()) {
+ if ((curInv == kItemLamp) && _conditions->in(kCondLampOn))
+ // LAB: Labyrinth specific
+ drawStaticMessage(kTextkLampOn);
+ else if (_inventory[curInv]._quantity > 1) {
+ Common::String roomMessage = _inventory[curInv]._name + " (" + Common::String::format("%d", _inventory[curInv]._quantity) + ")";
+ _graphics->drawMessage(roomMessage.c_str(), false);
+ } else
+ _graphics->drawMessage(_inventory[curInv]._name.c_str(), false);
+ }
+ } else
+ drawDirection(closePtr);
+
+ _lastTooLong = _graphics->_lastMessageLong;
+}
+
+void LabEngine::freeScreens() {
+ for (int i = 0; i < 20; i++) {
+ delete _moveImages[i];
+ _moveImages[i] = nullptr;
+ }
+
+ for (int imgIdx = 0; imgIdx < 10; imgIdx++) {
+ delete _invImages[imgIdx];
+ _invImages[imgIdx] = nullptr;
+ }
+
+ // We can't use freeButtonList() here, because some buttons are shared
+ // between the two lists.
+ for (ButtonList::iterator buttonIter = _moveButtonList.begin(); buttonIter != _moveButtonList.end(); ++buttonIter) {
+ delete *buttonIter;
+ }
+ _moveButtonList.clear();
+
+ for (ButtonList::iterator buttonIter = _invButtonList.begin(); buttonIter != _invButtonList.end(); ++buttonIter) {
+ delete *buttonIter;
+ }
+ _invButtonList.clear();
+}
+
+void LabEngine::perFlipButton(uint16 buttonId) {
+ for (ButtonList::iterator button = _moveButtonList.begin(); button != _moveButtonList.end(); ++button) {
+ Button *topButton = *button;
+ if (topButton->_buttonId == buttonId) {
+ SWAP<Image *>(topButton->_image, topButton->_altImage);
+
+ if (!_alternate)
+ topButton->_image->drawImage(topButton->_x, topButton->_y);
+
+ break;
+ }
+ }
+}
+
+void LabEngine::eatMessages() {
+ IntuiMessage *msg;
+
+ do {
+ msg = _event->getMsg();
+ } while (msg && !shouldQuit());
+}
+
+bool LabEngine::doCloseUp(const CloseData *closePtr) {
+ if (!closePtr)
+ return false;
+
+ int luteRight;
+ Common::Rect textRect;
+
+ if (getPlatform() != Common::kPlatformWindows) {
+ textRect = Common::Rect(0, 0, 319, 165);
+ luteRight = 124;
+ } else {
+ textRect = Common::Rect(2, 2, 317, 165);
+ luteRight = 128;
+ }
+
+ switch (closePtr->_closeUpType) {
+ case kMonitorMuseum:
+ case kMonitorLibrary:
+ case kMonitorWindow:
+ doMonitor(closePtr->_graphicName, closePtr->_message, false, textRect);
+ break;
+ case kMonitorGramophone:
+ textRect.right = 171;
+ doMonitor(closePtr->_graphicName, closePtr->_message, false, textRect);
+ break;
+ case kMonitorUnicycle:
+ textRect.left = 100;
+ doMonitor(closePtr->_graphicName, closePtr->_message, false, textRect);
+ break;
+ case kMonitorStatue:
+ textRect.left = 117;
+ doMonitor(closePtr->_graphicName, closePtr->_message, false, textRect);
+ break;
+ case kMonitorTalisman:
+ textRect.right = 184;
+ doMonitor(closePtr->_graphicName, closePtr->_message, false, textRect);
+ break;
+ case kMonitorLute:
+ textRect.right = luteRight;
+ doMonitor(closePtr->_graphicName, closePtr->_message, false, textRect);
+ break;
+ case kMonitorClock:
+ textRect.right = 206;
+ doMonitor(closePtr->_graphicName, closePtr->_message, false, textRect);
+ break;
+ case kMonitorTerminal:
+ doMonitor(closePtr->_graphicName, closePtr->_message, true, textRect);
+ break;
+ default:
+ return false;
+ }
+
+ _curFileName = " ";
+ _graphics->drawPanel();
+
+ return true;
+}
+
+Common::String LabEngine::getInvName(uint16 curInv) {
+ if (_mainDisplay)
+ return _inventory[curInv]._bitmapName;
+
+ if ((curInv == kItemLamp) && _conditions->in(kCondLampOn))
+ return "P:Mines/120";
+
+ if ((curInv == kItemBelt) && _conditions->in(kCondBeltGlowing))
+ return "P:Future/kCondBeltGlowing";
+
+ if (curInv == kItemWestPaper) {
+ _curFileName = _inventory[curInv]._bitmapName;
+ _anim->_noPalChange = true;
+ _graphics->readPict(_curFileName, false);
+ _anim->_noPalChange = false;
+ doWestPaper();
+ } else if (curInv == kItemNotes) {
+ _curFileName = _inventory[curInv]._bitmapName;
+ _anim->_noPalChange = true;
+ _graphics->readPict(_curFileName, false);
+ _anim->_noPalChange = false;
+ doNotes();
+ }
+
+ return _inventory[curInv]._bitmapName;
+}
+
+void LabEngine::interfaceOff() {
+ if (!_interfaceOff) {
+ _event->attachButtonList(nullptr);
+ _event->mouseHide();
+ _interfaceOff = true;
+ }
+}
+
+void LabEngine::interfaceOn() {
+ if (_interfaceOff) {
+ _interfaceOff = false;
+ _event->mouseShow();
+ }
+
+ if (_graphics->_longWinInFront)
+ _event->attachButtonList(nullptr);
+ else if (_alternate)
+ _event->attachButtonList(&_invButtonList);
+ else
+ _event->attachButtonList(&_moveButtonList);
+}
+
+bool LabEngine::doUse(uint16 curInv) {
+ switch (curInv) {
+ case kItemMap:
+ drawStaticMessage(kTextUseMap);
+ interfaceOff();
+ _anim->stopDiff();
+ _curFileName = " ";
+ _closeDataPtr = nullptr;
+ doMap(_roomNum);
+ _graphics->setPalette(initColors, 8);
+ _graphics->drawMessage("", false);
+ _graphics->drawPanel();
+ return true;
+ case kItemJournal:
+ drawStaticMessage(kTextUseJournal);
+ interfaceOff();
+ _anim->stopDiff();
+ _curFileName = " ";
+ _closeDataPtr = nullptr;
+ doJournal();
+ _graphics->drawPanel();
+ _graphics->drawMessage("", false);
+ return true;
+ case kItemLamp:
+ interfaceOff();
+
+ if (_conditions->in(kCondLampOn)) {
+ drawStaticMessage(kTextTurnLampOff);
+ _conditions->exclElement(kCondLampOn);
+ } else {
+ drawStaticMessage(kTextTurnkLampOn);
+ _conditions->inclElement(kCondLampOn);
+ }
+
+ _anim->_doBlack = false;
+ _anim->_waitForEffect = true;
+ _graphics->readPict("Music:Click");
+ _anim->_waitForEffect = false;
+
+ _anim->_doBlack = false;
+ _nextFileName = getInvName(curInv);
+ return true;
+ case kItemBelt:
+ if (!_conditions->in(kCondBeltGlowing))
+ _conditions->inclElement(kCondBeltGlowing);
+
+ _anim->_doBlack = false;
+ _nextFileName = getInvName(curInv);
+ return true;
+ case kItemWhiskey:
+ _conditions->inclElement(kCondUsedHelmet);
+ drawStaticMessage(kTextUseWhiskey);
+ return true;
+ case kItemPithHelmet:
+ _conditions->inclElement(kCondUsedHelmet);
+ drawStaticMessage(kTextUsePith);
+ return true;
+ case kItemHelmet:
+ _conditions->inclElement(kCondUsedHelmet);
+ drawStaticMessage(kTextUseHelmet);
+ return true;
+ default:
+ return false;
+ }
+}
+
+void LabEngine::decIncInv(uint16 *curInv, bool decreaseFl) {
+ int8 step = (decreaseFl) ? -1 : 1;
+ uint newInv = *curInv + step;
+
+ // Handle wrapping
+ if (newInv < 1)
+ newInv = _numInv;
+ if (newInv > _numInv)
+ newInv = 1;
+
+ interfaceOff();
+
+ while (newInv && (newInv <= _numInv)) {
+ if (_conditions->in(newInv) && !_inventory[newInv]._bitmapName.empty()) {
+ _nextFileName = getInvName(newInv);
+ *curInv = newInv;
+ break;
+ }
+
+ newInv += step;
+
+ // Handle wrapping
+ if (newInv < 1)
+ newInv = _numInv;
+ if (newInv > _numInv)
+ newInv = 1;
+ }
+}
+
+void LabEngine::mainGameLoop() {
+ _graphics->setPalette(initColors, 8);
+
+ _closeDataPtr = nullptr;
+ _roomNum = 1;
+ _direction = kDirectionNorth;
+
+ _resource->readRoomData("LAB:Doors");
+ if (!(_inventory = _resource->readInventory("LAB:Inventor")))
+ return;
+
+ if (!(_conditions = new LargeSet(_highestCondition + 1, this)))
+ return;
+
+ if (!(_roomsFound = new LargeSet(_manyRooms + 1, this)))
+ return;
+
+ _conditions->readInitialConditions("LAB:Conditio");
+
+ _graphics->_longWinInFront = false;
+ _graphics->drawPanel();
+
+ uint16 actionMode = 4;
+ perFlipButton(actionMode);
+
+ // Load saved slot from the launcher, if requested
+ if (ConfMan.hasKey("save_slot")) {
+ loadGame(ConfMan.getInt("save_slot"));
+
+ // Since the intro hasn't been shown, init the background music here
+ if (getPlatform() != Common::kPlatformAmiga)
+ _music->changeMusic("Music:BackGrou", false, false);
+ else
+ _music->changeMusic("Music:BackGround", false, false);
+ _music->checkRoomMusic();
+ }
+
+ uint16 curInv = kItemMap;
+ bool forceDraw = false;
+ bool gotMessage = true;
+ // Set up initial picture.
+ while (1) {
+ _event->processInput();
+ _system->delayMillis(10);
+
+ if (gotMessage) {
+ if (_quitLab || shouldQuit()) {
+ _anim->stopDiff();
+ break;
+ }
+
+ // Sees what kind of close up we're in and does the appropriate stuff, if any.
+ if (doCloseUp(_closeDataPtr)) {
+ _closeDataPtr = nullptr;
+ mayShowCrumbIndicator();
+ _graphics->screenUpdate();
+ }
+
+ // Sets the current picture properly on the screen
+ if (_mainDisplay)
+ _nextFileName = getPictName(true);
+
+ if (_noUpdateDiff) {
+ // Potentially entered another room
+ _roomsFound->inclElement(_roomNum);
+ forceDraw |= (_nextFileName != _curFileName);
+
+ _noUpdateDiff = false;
+ _curFileName = _nextFileName;
+ } else if (_nextFileName != _curFileName) {
+ interfaceOff();
+ // Potentially entered another room
+ _roomsFound->inclElement(_roomNum);
+ _curFileName = _nextFileName;
+
+ if (_closeDataPtr && _mainDisplay) {
+ switch (_closeDataPtr->_closeUpType) {
+ case kLockCombination:
+ _specialLocks->showCombinationLock(_curFileName);
+ break;
+ case kLockTiles:
+ case kLockTileSolution:
+ _specialLocks->showTileLock(_curFileName, (_closeDataPtr->_closeUpType == kLockTileSolution));
+ break;
+ default:
+ _graphics->readPict(_curFileName, false);
+ break;
+ }
+ } else
+ _graphics->readPict(_curFileName, false);
+
+ drawRoomMessage(curInv, _closeDataPtr);
+ forceDraw = false;
+
+ mayShowCrumbIndicator();
+ _graphics->screenUpdate();
+
+ if (!_followingCrumbs)
+ eatMessages();
+ }
+
+ if (forceDraw) {
+ drawRoomMessage(curInv, _closeDataPtr);
+ forceDraw = false;
+ _graphics->screenUpdate();
+ }
+ }
+
+ // Make sure we check the music at least after every message
+ updateEvents();
+ interfaceOn();
+ IntuiMessage *curMsg = _event->getMsg();
+ if (shouldQuit()) {
+ _quitLab = true;
+ return;
+ }
+
+ if (!curMsg) {
+ // Does music load and next animation frame when you've run out of messages
+ gotMessage = false;
+ _music->checkRoomMusic();
+ updateEvents();
+ _anim->diffNextFrame();
+
+ if (_followingCrumbs) {
+ MainButton code = followCrumbs();
+
+ if (code == kButtonForward || code == kButtonLeft || code == kButtonRight) {
+ gotMessage = true;
+ mayShowCrumbIndicator();
+ _graphics->screenUpdate();
+ if (!processEvent(kMessageButtonUp, code, 0, _event->updateAndGetMousePos(), curInv, curMsg, forceDraw, code, actionMode))
+ break;
+ }
+ }
+
+ mayShowCrumbIndicator();
+ _graphics->screenUpdate();
+ } else {
+ gotMessage = true;
+ _followingCrumbs = false;
+ if (!processEvent(curMsg->_msgClass, curMsg->_code, curMsg->_qualifier, curMsg->_mouse, curInv, curMsg, forceDraw, curMsg->_code, actionMode))
+ break;
+ }
+ }
+}
+
+void LabEngine::showLab2Teaser() {
+ _graphics->blackAllScreen();
+ _graphics->readPict("P:End/L2In.1");
+
+ for (int i = 0; i < 120; i++) {
+ updateEvents();
+ waitTOF();
+ }
+
+ _graphics->readPict("P:End/L2In.9");
+ _graphics->readPict("P:End/Lost");
+
+ while (!_event->getMsg() && !shouldQuit()) {
+ updateEvents();
+ _anim->diffNextFrame();
+ waitTOF();
+ }
+}
+
+bool LabEngine::processEvent(MessageClass tmpClass, uint16 code, uint16 qualifier, Common::Point tmpPos,
+ uint16 &curInv, IntuiMessage *curMsg, bool &forceDraw, uint16 buttonId, uint16 &actionMode) {
+
+ if (shouldQuit())
+ return false;
+
+ MessageClass msgClass = tmpClass;
+ Common::Point curPos = tmpPos;
+ uint16 oldDirection = 0;
+ uint16 lastInv = kItemMap;
+
+ if (code == Common::KEYCODE_RETURN)
+ msgClass = kMessageLeftClick;
+
+ bool leftButtonClick = (msgClass == kMessageLeftClick);
+ bool rightButtonClick = (msgClass == kMessageRightClick);
+
+ _anim->_doBlack = false;
+
+ if (_graphics->_longWinInFront) {
+ if (msgClass == kMessageRawKey || leftButtonClick || rightButtonClick) {
+ _graphics->_longWinInFront = false;
+ _graphics->drawPanel();
+ drawRoomMessage(curInv, _closeDataPtr);
+ _graphics->screenUpdate();
+ }
+ } else if (msgClass == kMessageRawKey) {
+ return processKey(curMsg, msgClass, qualifier, curPos, curInv, forceDraw, code);
+ } else if (msgClass == kMessageButtonUp) {
+ if (!_alternate)
+ processMainButton(curInv, lastInv, oldDirection, forceDraw, buttonId, actionMode);
+ else
+ processAltButton(curInv, lastInv, buttonId, actionMode);
+ } else if (leftButtonClick && _mainDisplay) {
+ interfaceOff();
+ _mainDisplay = true;
+
+ if (_closeDataPtr && _closeDataPtr->_closeUpType == kLockCombination)
+ _specialLocks->combinationClick(curPos);
+ else if (_closeDataPtr && _closeDataPtr->_closeUpType == kLockTiles)
+ _specialLocks->tileClick(curPos);
+ else
+ performAction(actionMode, curPos, curInv);
+
+ mayShowCrumbIndicator();
+ _graphics->screenUpdate();
+ } else if (rightButtonClick) {
+ eatMessages();
+ _alternate = !_alternate;
+ _anim->_doBlack = true;
+ _mainDisplay = true;
+ // Sets the correct button list
+ interfaceOn();
+
+ if (_alternate) {
+ if (lastInv && _conditions->in(lastInv))
+ curInv = lastInv;
+ else
+ decIncInv(&curInv, false);
+ }
+
+ _graphics->drawPanel();
+ drawRoomMessage(curInv, _closeDataPtr);
+
+ mayShowCrumbIndicator();
+ _graphics->screenUpdate();
+ }
+
+ return true;
+}
+
+bool LabEngine::processKey(IntuiMessage *curMsg, uint32 msgClass, uint16 &qualifier, Common::Point &curPos, uint16 &curInv, bool &forceDraw, uint16 code) {
+ if ((getPlatform() == Common::kPlatformWindows) && (code == Common::KEYCODE_b)) {
+ // Start bread crumbs
+ _breadCrumbs[0]._roomNum = 0;
+ _numCrumbs = 0;
+ _droppingCrumbs = true;
+ mayShowCrumbIndicator();
+ _graphics->screenUpdate();
+ } else if (getPlatform() == Common::kPlatformWindows && (code == Common::KEYCODE_f || code == Common::KEYCODE_r)) {
+ // Follow bread crumbs
+ if (_droppingCrumbs) {
+ if (_numCrumbs > 0) {
+ _followingCrumbs = true;
+ _followCrumbsFast = (code == Common::KEYCODE_r);
+ _isCrumbTurning = false;
+ _isCrumbWaiting = false;
+ _crumbTimestamp = _system->getMillis();
+
+ if (_alternate) {
+ eatMessages();
+ _alternate = false;
+ _anim->_doBlack = true;
+
+ _mainDisplay = true;
+ // Sets the correct button list
+ interfaceOn();
+ _graphics->drawPanel();
+ drawRoomMessage(curInv, _closeDataPtr);
+ _graphics->screenUpdate();
+ }
+ } else {
+ _breadCrumbs[0]._roomNum = 0;
+ _droppingCrumbs = false;
+
+ // Need to hide indicator!!!!
+ mayShowCrumbIndicatorOff();
+ _graphics->screenUpdate();
+ }
+ }
+ } else if ((code == Common::KEYCODE_x) || (code == Common::KEYCODE_q)) {
+ // Quit?
+ _graphics->drawMessage("Do you want to quit? (Y/N)", false);
+ eatMessages();
+ interfaceOff();
+
+ while (1) {
+ // Make sure we check the music at least after every message
+ updateEvents();
+ curMsg = _event->getMsg();
+
+ if (shouldQuit())
+ return false;
+
+ if (!curMsg) {
+ // Does music load and next animation frame when you've run out of messages
+ updateEvents();
+ _anim->diffNextFrame();
+ } else if (curMsg->_msgClass == kMessageRawKey) {
+ if ((curMsg->_code == Common::KEYCODE_y) || (curMsg->_code == Common::KEYCODE_q)) {
+ _anim->stopDiff();
+ return false;
+ } else if (curMsg->_code < 128)
+ break;
+ } else if ((curMsg->_msgClass == kMessageLeftClick) || (curMsg->_msgClass == kMessageRightClick))
+ break;
+ }
+
+ forceDraw = true;
+ interfaceOn();
+ } else if (code == Common::KEYCODE_ESCAPE) {
+ _closeDataPtr = nullptr;
+ } else if (code == Common::KEYCODE_TAB) {
+ const CloseData *tmpClosePtr = _closeDataPtr;
+
+ // get next close-up in list after the one pointed to by curPos
+ setCurrentClose(curPos, &tmpClosePtr, true, true);
+
+ if (tmpClosePtr != _closeDataPtr)
+ _event->setMousePos(Common::Point(_utils->scaleX((tmpClosePtr->_x1 + tmpClosePtr->_x2) / 2), _utils->scaleY((tmpClosePtr->_y1 + tmpClosePtr->_y2) / 2)));
+ }
+
+ eatMessages();
+
+ return true;
+}
+
+void LabEngine::processMainButton(uint16 &curInv, uint16 &lastInv, uint16 &oldDirection, bool &forceDraw, uint16 buttonId, uint16 &actionMode) {
+ switch (buttonId) {
+ case kButtonPickup:
+ case kButtonUse:
+ case kButtonOpen:
+ case kButtonClose:
+ case kButtonLook:
+ if ((actionMode == 4) && (buttonId == kButtonLook) && _closeDataPtr) {
+ doMainView();
+
+ _anim->_doBlack = true;
+ _closeDataPtr = nullptr;
+ mayShowCrumbIndicator();
+ } else {
+ uint16 oldActionMode = actionMode;
+ actionMode = buttonId;
+
+ if (oldActionMode < 5)
+ perFlipButton(oldActionMode);
+
+ perFlipButton(actionMode);
+ drawStaticMessage(kTextTakeWhat + buttonId);
+ }
+ break;
+
+ case kButtonInventory:
+ eatMessages();
+
+ _alternate = true;
+ _anim->_doBlack = true;
+ // Sets the correct button list
+ interfaceOn();
+ _mainDisplay = false;
+
+ if (lastInv && _conditions->in(lastInv)) {
+ curInv = lastInv;
+ _nextFileName = getInvName(curInv);
+ } else
+ decIncInv(&curInv, false);
+
+ _graphics->drawPanel();
+ drawRoomMessage(curInv, _closeDataPtr);
+
+ mayShowCrumbIndicator();
+ break;
+
+ case kButtonLeft:
+ case kButtonRight: {
+ _closeDataPtr = nullptr;
+ if (buttonId == kButtonLeft)
+ drawStaticMessage(kTextTurnLeft);
+ else
+ drawStaticMessage(kTextTurnRight);
+
+ _curFileName = " ";
+ oldDirection = _direction;
+
+ uint16 newDir = processArrow(_direction, buttonId - 6);
+ doTurn(_direction, newDir);
+ _anim->_doBlack = true;
+ _direction = newDir;
+ forceDraw = true;
+ mayShowCrumbIndicator();
+ }
+ break;
+
+ case kButtonForward: {
+ _closeDataPtr = nullptr;
+ int oldRoomNum = _roomNum;
+
+ if (doGoForward()) {
+ if (oldRoomNum == _roomNum)
+ _anim->_doBlack = true;
+ } else {
+ _anim->_doBlack = true;
+ _direction = processArrow(_direction, buttonId - 6);
+
+ if (oldRoomNum != _roomNum) {
+ drawStaticMessage(kTextGoForward);
+ // Potentially entered a new room
+ _roomsFound->inclElement(_roomNum);
+ _curFileName = " ";
+ forceDraw = true;
+ } else {
+ _anim->_doBlack = true;
+ drawStaticMessage(kTextNoPath);
+ }
+ }
+
+ if (_followingCrumbs) {
+ if (_isCrumbTurning) {
+ if (_direction == oldDirection)
+ _followingCrumbs = false;
+ } else if (_roomNum == oldRoomNum) { // didn't get there?
+ _followingCrumbs = false;
+ }
+ } else if (_droppingCrumbs && (oldRoomNum != _roomNum)) {
+ // If in surreal maze, turn off DroppingCrumbs.
+ if ((_roomNum >= 245) && (_roomNum <= 280)) {
+ _followingCrumbs = false;
+ _droppingCrumbs = false;
+ _numCrumbs = 0;
+ _breadCrumbs[0]._roomNum = 0;
+ } else {
+ bool intersect = false;
+ for (int idx = 0; idx < _numCrumbs; idx++) {
+ if (_breadCrumbs[idx]._roomNum == _roomNum) {
+ _numCrumbs = idx + 1;
+ _breadCrumbs[_numCrumbs]._roomNum = 0;
+ intersect = true;
+ }
+ }
+
+ if (!intersect) {
+ if (_numCrumbs == MAX_CRUMBS) {
+ _numCrumbs = MAX_CRUMBS - 1;
+ memcpy(&_breadCrumbs[0], &_breadCrumbs[1], _numCrumbs * sizeof _breadCrumbs[0]);
+ }
+
+ _breadCrumbs[_numCrumbs]._roomNum = _roomNum;
+ _breadCrumbs[_numCrumbs++]._direction = _direction;
+ }
+ }
+ }
+
+ mayShowCrumbIndicator();
+ }
+ break;
+
+ case kButtonMap:
+ doUse(kItemMap);
+
+ mayShowCrumbIndicator();
+ break;
+ }
+
+ _graphics->screenUpdate();
+}
+
+void LabEngine::processAltButton(uint16 &curInv, uint16 &lastInv, uint16 buttonId, uint16 &actionMode) {
+ _anim->_doBlack = true;
+
+ switch (buttonId) {
+ case kButtonMainDisplay:
+ eatMessages();
+ _alternate = false;
+ _anim->_doBlack = true;
+
+ _mainDisplay = true;
+ // Sets the correct button list
+ interfaceOn();
+ _graphics->drawPanel();
+ drawRoomMessage(curInv, _closeDataPtr);
+ break;
+
+ case kButtonSaveLoad: {
+ interfaceOff();
+ _anim->stopDiff();
+ _curFileName = " ";
+
+ bool saveRestoreSuccessful = saveRestoreGame();
+ _closeDataPtr = nullptr;
+ _mainDisplay = true;
+
+ curInv = lastInv = kItemMap;
+ _nextFileName = getInvName(curInv);
+
+ _graphics->drawPanel();
+
+ if (!saveRestoreSuccessful) {
+ _graphics->drawMessage("Save/restore aborted", false);
+ _graphics->setPalette(initColors, 8);
+ _system->delayMillis(1000);
+ }
+ }
+ break;
+
+ case kButtonUseItem:
+ if (!doUse(curInv)) {
+ uint16 oldActionMode = actionMode;
+ // Use button
+ actionMode = 5;
+
+ if (oldActionMode < 5)
+ perFlipButton(oldActionMode);
+
+ drawStaticMessage(kTextUseOnWhat);
+ _mainDisplay = true;
+ }
+ break;
+
+ case kButtonLookAtItem:
+ _mainDisplay = !_mainDisplay;
+
+ if ((curInv == 0) || (curInv > _numInv)) {
+ curInv = 1;
+
+ while ((curInv <= _numInv) && !_conditions->in(curInv))
+ curInv++;
+ }
+
+ if ((curInv <= _numInv) && _conditions->in(curInv) && !_inventory[curInv]._bitmapName.empty())
+ _nextFileName = getInvName(curInv);
+
+ break;
+
+ case kButtonPrevItem:
+ decIncInv(&curInv, true);
+ lastInv = curInv;
+ drawRoomMessage(curInv, _closeDataPtr);
+ break;
+
+ case kButtonNextItem:
+ decIncInv(&curInv, false);
+ lastInv = curInv;
+ drawRoomMessage(curInv, _closeDataPtr);
+ break;
+
+ case kButtonBreadCrumbs:
+ _breadCrumbs[0]._roomNum = 0;
+ _numCrumbs = 0;
+ _droppingCrumbs = true;
+ mayShowCrumbIndicator();
+ break;
+
+ case kButtonFollowCrumbs:
+ if (_droppingCrumbs) {
+ if (_numCrumbs > 0) {
+ _followingCrumbs = true;
+ _followCrumbsFast = false;
+ _isCrumbTurning = false;
+ _isCrumbWaiting = false;
+ _crumbTimestamp = _system->getMillis();
+
+ eatMessages();
+ _alternate = false;
+ _anim->_doBlack = true;
+
+ _mainDisplay = true;
+ // Sets the correct button list
+ interfaceOn();
+ _graphics->drawPanel();
+ drawRoomMessage(curInv, _closeDataPtr);
+ } else {
+ _breadCrumbs[0]._roomNum = 0;
+ _droppingCrumbs = false;
+
+ // Need to hide indicator!!!!
+ mayShowCrumbIndicatorOff();
+ }
+ }
+ break;
+ }
+
+ _graphics->screenUpdate();
+}
+
+void LabEngine::performAction(uint16 actionMode, Common::Point curPos, uint16 &curInv) {
+ eatMessages();
+
+ switch (actionMode) {
+ case 0:
+ // Take something.
+ if (doActionRule(curPos, actionMode, _roomNum))
+ _curFileName = _newFileName;
+ else if (takeItem(curPos))
+ drawStaticMessage(kTextTakeItem);
+ else if (doActionRule(curPos, kRuleActionTakeDef, _roomNum))
+ _curFileName = _newFileName;
+ else if (doActionRule(curPos, kRuleActionTake, 0))
+ _curFileName = _newFileName;
+ else if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
+ drawStaticMessage(kTextNothing);
+
+ break;
+
+ case 1:
+ case 2:
+ case 3:
+ // Manipulate an object, Open up a "door" or Close a "door"
+ if (doActionRule(curPos, actionMode, _roomNum))
+ _curFileName = _newFileName;
+ else if (!doActionRule(curPos, actionMode, 0)) {
+ if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
+ drawStaticMessage(kTextNothing);
+ }
+ break;
+
+ case 4: {
+ // Look at closeups
+ const CloseData *tmpClosePtr = _closeDataPtr;
+ setCurrentClose(curPos, &tmpClosePtr, true);
+
+ if (_closeDataPtr == tmpClosePtr) {
+ if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
+ drawStaticMessage(kTextNothing);
+ } else if (!tmpClosePtr->_graphicName.empty()) {
+ _anim->_doBlack = true;
+ _closeDataPtr = tmpClosePtr;
+ } else if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
+ drawStaticMessage(kTextNothing);
+ }
+ break;
+
+ case 5:
+ if (_conditions->in(curInv)) {
+ // Use an item on something else
+ if (doOperateRule(curPos, curInv)) {
+ _curFileName = _newFileName;
+
+ if (!_conditions->in(curInv))
+ decIncInv(&curInv, false);
+ }
+ else if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2)))
+ drawStaticMessage(kTextNothing);
+ }
+ }
+}
+
+void LabEngine::go() {
+ _isHiRes = ((getFeatures() & GF_LOWRES) == 0);
+ _graphics->setUpScreens();
+
+ _event->initMouse();
+ if (_msgFont)
+ _graphics->freeFont(&_msgFont);
+
+ if (getPlatform() != Common::kPlatformAmiga)
+ _msgFont = _resource->getFont("F:AvanteG.12");
+ else
+ _msgFont = _resource->getFont("F:Map.fon");
+
+ // If the user has requested to load a game from the launcher, skip the intro
+ if (!ConfMan.hasKey("save_slot")) {
+ _event->mouseHide();
+ _introPlaying = true;
+ Intro *intro = new Intro(this);
+ intro->play();
+ delete intro;
+ _introPlaying = false;
+ _event->mouseShow();
+ }
+
+ mainGameLoop();
+
+ _graphics->freeFont(&_msgFont);
+ _graphics->freePict();
+
+ freeScreens();
+
+ _music->freeMusic();
+}
+
+MainButton LabEngine::followCrumbs() {
+ // kDirectionNorth, kDirectionSouth, kDirectionEast, kDirectionWest
+ MainButton movement[4][4] = {
+ { kButtonForward, kButtonRight, kButtonRight, kButtonLeft },
+ { kButtonRight, kButtonForward, kButtonLeft, kButtonRight },
+ { kButtonLeft, kButtonRight, kButtonForward, kButtonRight },
+ { kButtonRight, kButtonLeft, kButtonRight, kButtonForward }
+ };
+
+ if (_isCrumbWaiting) {
+ if (_system->getMillis() <= _crumbTimestamp)
+ return kButtonNone;
+
+ _isCrumbWaiting = false;
+ }
+
+ if (!_isCrumbTurning)
+ _breadCrumbs[_numCrumbs--]._roomNum = 0;
+
+ // Is the current crumb this room? If not, logic error.
+ if (_roomNum != _breadCrumbs[_numCrumbs]._roomNum) {
+ _numCrumbs = 0;
+ _breadCrumbs[0]._roomNum = 0;
+ _droppingCrumbs = false;
+ _followingCrumbs = false;
+ return kButtonNone;
+ }
+
+ Direction exitDir;
+ // which direction is last crumb
+ if (_breadCrumbs[_numCrumbs]._direction == kDirectionEast)
+ exitDir = kDirectionWest;
+ else if (_breadCrumbs[_numCrumbs]._direction == kDirectionWest)
+ exitDir = kDirectionEast;
+ else if (_breadCrumbs[_numCrumbs]._direction == kDirectionNorth)
+ exitDir = kDirectionSouth;
+ else
+ exitDir = kDirectionNorth;
+
+ MainButton moveDir = movement[_direction][exitDir];
+
+ if (_numCrumbs == 0) {
+ _isCrumbTurning = false;
+ _breadCrumbs[0]._roomNum = 0;
+ _droppingCrumbs = false;
+ _followingCrumbs = false;
+ } else {
+ _isCrumbTurning = (moveDir != kButtonForward);
+ _isCrumbWaiting = true;
+
+ int theDelay = (_followCrumbsFast ? 1000 / 4 : 1000);
+ _crumbTimestamp = theDelay + _system->getMillis();
+ }
+
+ return moveDir;
+}
+
+
+void LabEngine::mayShowCrumbIndicator() {
+ static byte dropCrumbsImageData[CRUMBSWIDTH * CRUMBSHEIGHT] = {
+ 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0,
+ 0, 4, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 4, 0,
+ 4, 7, 7, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 7, 7, 4,
+ 4, 7, 4, 4, 0, 0, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 3, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 3, 2, 3, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 3, 3, 3, 4, 4, 4, 4, 4, 4, 0, 0, 3, 2, 3, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 0, 4, 7, 7, 7, 7, 7, 7, 4, 3, 2, 2, 2, 3, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 4, 7, 7, 4, 4, 4, 4, 7, 7, 4, 3, 3, 3, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 4, 7, 4, 4, 0, 0, 4, 4, 7, 4, 0, 0, 0, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 4, 7, 4, 0, 0, 0, 0, 4, 7, 4, 0, 0, 0, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 4, 4, 4, 3, 0, 0, 0, 4, 7, 4, 0, 0, 0, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 0, 4, 3, 2, 3, 0, 0, 4, 7, 4, 0, 0, 0, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 0, 0, 3, 2, 3, 0, 0, 4, 7, 4, 0, 0, 0, 0, 4, 7, 4,
+ 4, 7, 4, 0, 0, 0, 0, 0, 3, 2, 2, 2, 3, 4, 4, 7, 4, 0, 0, 0, 0, 4, 7, 4,
+ 4, 7, 7, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, 7, 4, 0, 0, 0, 0, 4, 7, 4,
+ 0, 4, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 4, 0, 0, 0, 0, 0, 4, 7, 4,
+ 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 0, 0, 0, 0, 0, 4, 7, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 3, 0, 0, 0, 0, 4, 7, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 3, 0, 0, 0, 0, 4, 7, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 3, 0, 0, 4, 4, 7, 4,
+ 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, 7, 4,
+ 0, 0, 4, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 4, 0,
+ 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0
+ };
+
+ if (getPlatform() != Common::kPlatformWindows)
+ return;
+
+ if (_droppingCrumbs && _mainDisplay) {
+ static byte *imgData = new byte[CRUMBSWIDTH * CRUMBSHEIGHT];
+ memcpy(imgData, dropCrumbsImageData, CRUMBSWIDTH * CRUMBSHEIGHT);
+ static Image dropCrumbsImage(CRUMBSWIDTH, CRUMBSHEIGHT, imgData, this);
+
+ dropCrumbsImage.drawMaskImage(612, 4);
+ }
+}
+
+void LabEngine::mayShowCrumbIndicatorOff() {
+ static byte dropCrumbsOffImageData[CRUMBSWIDTH * CRUMBSHEIGHT] = {
+ 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0,
+ 0, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 0,
+ 4, 8, 8, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 8, 8, 4,
+ 4, 8, 4, 4, 0, 0, 3, 8, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 3, 8, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 3, 8, 8, 8, 3, 0, 0, 0, 0, 0, 0, 0, 3, 8, 3, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 3, 3, 3, 4, 4, 4, 4, 4, 4, 0, 0, 3, 8, 3, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 0, 4, 8, 8, 8, 8, 8, 8, 4, 3, 8, 8, 8, 3, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 4, 8, 8, 4, 4, 4, 4, 8, 8, 4, 3, 3, 3, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 4, 8, 4, 4, 0, 0, 4, 4, 8, 4, 0, 0, 0, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 4, 8, 4, 0, 0, 0, 0, 4, 8, 4, 0, 0, 0, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 4, 4, 4, 3, 0, 0, 0, 4, 8, 4, 0, 0, 0, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 0, 4, 3, 8, 3, 0, 0, 4, 8, 4, 0, 0, 0, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 0, 0, 3, 8, 3, 0, 0, 4, 8, 4, 0, 0, 0, 0, 4, 8, 4,
+ 4, 8, 4, 0, 0, 0, 0, 0, 3, 8, 8, 8, 3, 4, 4, 8, 4, 0, 0, 0, 0, 4, 8, 4,
+ 4, 8, 8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 4, 0, 0, 0, 0, 4, 8, 4,
+ 0, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 0, 0, 0, 0, 0, 4, 8, 4,
+ 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 0, 0, 0, 0, 0, 4, 8, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 8, 3, 0, 0, 0, 0, 4, 8, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 8, 3, 0, 0, 0, 0, 4, 8, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 8, 8, 8, 3, 0, 0, 4, 4, 8, 4,
+ 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 4,
+ 0, 0, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 0,
+ 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0
+ };
+
+ if (getPlatform() != Common::kPlatformWindows)
+ return;
+
+ if (_mainDisplay) {
+ static byte *imgData = new byte[CRUMBSWIDTH * CRUMBSHEIGHT];
+ memcpy(imgData, dropCrumbsOffImageData, CRUMBSWIDTH * CRUMBSHEIGHT);
+ static Image dropCrumbsOffImage(CRUMBSWIDTH, CRUMBSHEIGHT, imgData, this);
+
+ dropCrumbsOffImage.drawMaskImage(612, 4);
+ }
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/eventman.cpp b/engines/lab/eventman.cpp
new file mode 100644
index 0000000000..a94ddbf16b
--- /dev/null
+++ b/engines/lab/eventman.cpp
@@ -0,0 +1,213 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/events.h"
+
+#include "lab/lab.h"
+
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/image.h"
+
+namespace Lab {
+
+static const byte mouseData[] = {
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 7, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 7, 7, 1, 0, 0, 0, 0, 0, 0,
+ 1, 7, 7, 7, 1, 0, 0, 0, 0, 0,
+ 1, 7, 7, 7, 7, 1, 0, 0, 0, 0,
+ 1, 7, 7, 7, 7, 7, 1, 0, 0, 0,
+ 1, 7, 7, 7, 7, 7, 7, 1, 0, 0,
+ 1, 7, 7, 7, 7, 7, 7, 7, 1, 0,
+ 1, 7, 7, 7, 7, 7, 1, 1, 1, 1,
+ 1, 7, 7, 1, 7, 7, 1, 0, 0, 0,
+ 1, 7, 1, 0, 1, 7, 7, 1, 0, 0,
+ 1, 1, 0, 0, 1, 7, 7, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 7, 7, 1, 0,
+ 0, 0, 0, 0, 0, 1, 7, 7, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0
+};
+
+#define MOUSE_WIDTH 10
+#define MOUSE_HEIGHT 15
+
+EventManager::EventManager(LabEngine *vm) : _vm(vm) {
+ _leftClick = false;
+ _rightClick = false;
+
+ _lastButtonHit = nullptr;
+ _screenButtonList = nullptr;
+ _hitButton = nullptr;
+ _mousePos = Common::Point(0, 0);
+ _keyPressed = Common::KEYCODE_INVALID;
+}
+
+Button *EventManager::checkButtonHit(ButtonList *buttonList, Common::Point pos) {
+ for (ButtonList::iterator buttonItr = buttonList->begin(); buttonItr != buttonList->end(); ++buttonItr) {
+ Button *button = *buttonItr;
+ Common::Rect buttonRect(button->_x, button->_y, button->_x + button->_image->_width - 1, button->_y + button->_image->_height - 1);
+
+ if (buttonRect.contains(pos) && button->_isEnabled) {
+ if (_vm->_isHiRes) {
+ _hitButton = button;
+ } else {
+ button->_altImage->drawImage(button->_x, button->_y);
+
+ for (int i = 0; i < 3; i++)
+ _vm->waitTOF();
+
+ button->_image->drawImage(button->_x, button->_y);
+ }
+
+ return button;
+ }
+ }
+
+ return nullptr;
+}
+
+void EventManager::attachButtonList(ButtonList *buttonList) {
+ if (_screenButtonList != buttonList)
+ _lastButtonHit = nullptr;
+
+ _screenButtonList = buttonList;
+}
+
+Button *EventManager::getButton(uint16 id) {
+ for (ButtonList::iterator buttonItr = _screenButtonList->begin(); buttonItr != _screenButtonList->end(); ++buttonItr) {
+ Button *button = *buttonItr;
+ if (button->_buttonId == id)
+ return button;
+ }
+
+ return nullptr;
+}
+
+void EventManager::updateMouse() {
+ if (!_hitButton)
+ return;
+
+ _hitButton->_altImage->drawImage(_hitButton->_x, _hitButton->_y);
+ for (int i = 0; i < 3; i++)
+ _vm->waitTOF();
+ _hitButton->_image->drawImage(_hitButton->_x, _hitButton->_y);
+
+ _hitButton = nullptr;
+ _vm->_graphics->screenUpdate();
+}
+
+void EventManager::initMouse() {
+ _vm->_system->setMouseCursor(mouseData, MOUSE_WIDTH, MOUSE_HEIGHT, 0, 0, 0);
+ _vm->_system->showMouse(false);
+
+ setMousePos(Common::Point(_vm->_graphics->_screenWidth / 2, _vm->_graphics->_screenHeight / 2));
+}
+
+void EventManager::mouseShow() {
+ _vm->_system->showMouse(true);
+}
+
+void EventManager::mouseHide() {
+ _vm->_system->showMouse(false);
+}
+
+void EventManager::setMousePos(Common::Point pos) {
+ if (_vm->_isHiRes)
+ _vm->_system->warpMouse(pos.x, pos.y);
+ else
+ _vm->_system->warpMouse(pos.x * 2, pos.y);
+}
+
+void EventManager::processInput() {
+ Common::Event event;
+ Button *curButton = nullptr;
+
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_LBUTTONDOWN:
+ if (_screenButtonList)
+ curButton = checkButtonHit(_screenButtonList, _vm->_isHiRes ? _mousePos : Common::Point(_mousePos.x / 2, _mousePos.y));
+
+ if (curButton)
+ _lastButtonHit = curButton;
+ else
+ _leftClick = true;
+ break;
+ case Common::EVENT_RBUTTONDOWN:
+ _rightClick = true;
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+ case Common::EVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_LEFTBRACKET:
+ _vm->changeVolume(-1);
+ break;
+ case Common::KEYCODE_RIGHTBRACKET:
+ _vm->changeVolume(1);
+ break;
+ case Common::KEYCODE_d:
+ if (event.kbd.hasFlags(Common::KBD_CTRL)) {
+ // Open debugger console
+ _vm->_console->attach();
+ continue;
+ }
+ // Intentional fall through
+ default:
+ _keyPressed = event.kbd;
+ break;
+ }
+ break;
+ case Common::EVENT_QUIT:
+ case Common::EVENT_RTL:
+ default:
+ break;
+ }
+ }
+
+ _vm->_system->copyRectToScreen(_vm->_graphics->_displayBuffer, _vm->_graphics->_screenWidth, 0, 0, _vm->_graphics->_screenWidth, _vm->_graphics->_screenHeight);
+ _vm->_console->onFrame();
+ _vm->_system->updateScreen();
+}
+
+Common::Point EventManager::updateAndGetMousePos() {
+ processInput();
+
+ return _mousePos;
+}
+
+void EventManager::simulateEvent() {
+ // Simulate an event by setting an unused key
+ _keyPressed = Common::KeyState(Common::KEYCODE_SEMICOLON);
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/eventman.h b/engines/lab/eventman.h
new file mode 100644
index 0000000000..f26e2eba1c
--- /dev/null
+++ b/engines/lab/eventman.h
@@ -0,0 +1,131 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_EVENTMAN_H
+#define LAB_EVENTMAN_H
+
+#include "common/events.h"
+
+namespace Lab {
+
+class LabEngine;
+class Image;
+
+struct IntuiMessage {
+ MessageClass _msgClass;
+ uint16 _code; // KeyCode or Button Id
+ uint16 _qualifier;
+ Common::Point _mouse;
+};
+
+struct Button {
+ uint16 _x, _y, _buttonId;
+ Common::KeyCode _keyEquiv; // the key which activates this button
+ bool _isEnabled;
+ Image *_image, *_altImage;
+};
+
+typedef Common::List<Button *> ButtonList;
+
+class EventManager {
+private:
+ LabEngine *_vm;
+
+ bool _leftClick;
+ bool _rightClick;
+
+ Button *_hitButton;
+ Button *_lastButtonHit;
+ ButtonList *_screenButtonList;
+ Common::Point _mousePos;
+ Common::KeyState _keyPressed;
+
+private:
+ /**
+ * Checks whether or not the cords fall within one of the buttons in a list
+ * of buttons.
+ */
+ Button *checkButtonHit(ButtonList *buttonList, Common::Point pos);
+
+ /**
+ * Checks whether or not the coords fall within one of the buttons in a list
+ * of buttons.
+ */
+ Button *checkNumButtonHit(ButtonList *buttonList, Common::KeyCode key);
+
+public:
+ EventManager (LabEngine *vm);
+
+ void attachButtonList(ButtonList *buttonList);
+ Button *createButton(uint16 x, uint16 y, uint16 id, Common::KeyCode key, Image *image, Image *altImage);
+ void toggleButton(Button *button, uint16 penColor, bool enable);
+
+ /**
+ * Draws a button list to the screen.
+ */
+ void drawButtonList(ButtonList *buttonList);
+ void freeButtonList(ButtonList *buttonList);
+ Button *getButton(uint16 id);
+
+ IntuiMessage *getMsg();
+
+ /**
+ * Initializes the mouse.
+ */
+ void initMouse();
+
+ /**
+ * Shows the mouse.
+ */
+ void mouseShow();
+
+ /**
+ * Hides the mouse.
+ */
+ void mouseHide();
+ void processInput();
+
+ /**
+ * Moves the mouse to new co-ordinates.
+ */
+ void setMousePos(Common::Point pos);
+ void updateMouse();
+ Common::Point updateAndGetMousePos();
+
+ /**
+ * Simulates an event for the game main loop, when a game is
+ * loaded or when the user teleports to a scene
+ */
+ void simulateEvent();
+};
+
+} // End of namespace Lab
+
+#endif // LAB_EVENTMAN_H
diff --git a/engines/lab/image.cpp b/engines/lab/image.cpp
new file mode 100644
index 0000000000..ec516718e8
--- /dev/null
+++ b/engines/lab/image.cpp
@@ -0,0 +1,136 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/file.h"
+
+#include "lab/lab.h"
+
+#include "lab/dispman.h"
+#include "lab/image.h"
+
+namespace Lab {
+
+Image::Image(Common::File *s, LabEngine *vm) : _vm(vm) {
+ _width = s->readUint16LE();
+ _height = s->readUint16LE();
+ s->skip(4);
+
+ uint32 size = _width * _height;
+ if (size & 1)
+ size++;
+
+ _imageData = new byte[size];
+ s->read(_imageData, size);
+ _autoFree = true;
+}
+
+Image::~Image() {
+ if (_autoFree)
+ delete[] _imageData;
+}
+
+void Image::setData(byte *d, bool autoFree) {
+ if (_autoFree)
+ delete[] _imageData;
+ _imageData = d;
+ _autoFree = autoFree;
+}
+
+void Image::blitBitmap(uint16 srcX, uint16 srcY, Image *imgDest,
+ uint16 destX, uint16 destY, uint16 width, uint16 height, byte masked) {
+ int clipWidth = width;
+ int clipHeight = height;
+ int destWidth = (imgDest) ? imgDest->_width : _vm->_graphics->_screenWidth;
+ int destHeight = (imgDest) ? imgDest->_height : _vm->_graphics->_screenHeight;
+ byte *destBuffer = (imgDest) ? imgDest->_imageData : _vm->_graphics->getCurrentDrawingBuffer();
+
+ if (destX + clipWidth > destWidth)
+ clipWidth = destWidth - destX;
+
+ if (destY + clipHeight > destHeight)
+ clipHeight = destHeight - destY;
+
+ if ((clipWidth > 0) && (clipHeight > 0)) {
+ byte *img = _imageData + srcY * _width + srcX;
+ byte *dest = destBuffer + destY * destWidth + destX;
+
+ if (!masked) {
+ for (int i = 0; i < clipHeight; i++) {
+ memcpy(dest, img, clipWidth);
+ img += _width;
+ dest += destWidth;
+ }
+ } else {
+ for (int i = 0; i < clipHeight; i++) {
+ for (int j = 0; j < clipWidth; j++) {
+ byte c = img[j];
+
+ if (c)
+ dest[j] = c - 1;
+ }
+
+ img += _width;
+ dest += destWidth;
+ }
+ }
+ }
+}
+
+void Image::drawImage(uint16 x, uint16 y) {
+ blitBitmap(0, 0, nullptr, x, y, _width, _height, false);
+}
+
+void Image::drawMaskImage(uint16 x, uint16 y) {
+ blitBitmap(0, 0, nullptr, x, y, _width, _height, true);
+}
+
+void Image::readScreenImage(uint16 x, uint16 y) {
+ int clipWidth = _width;
+ int clipHeight = _height;
+
+ if (x + clipWidth > _vm->_graphics->_screenWidth)
+ clipWidth = _vm->_graphics->_screenWidth - x;
+
+ if (y + clipHeight > _vm->_graphics->_screenHeight)
+ clipHeight = _vm->_graphics->_screenHeight - y;
+
+ if ((clipWidth > 0) && (clipHeight > 0)) {
+ byte *img = _imageData;
+ byte *screen = _vm->_graphics->getCurrentDrawingBuffer() + y * _vm->_graphics->_screenWidth + x;
+
+ while (clipHeight-- > 0) {
+ memcpy(img, screen, clipWidth);
+ img += _width;
+ screen += _vm->_graphics->_screenWidth;
+ }
+ }
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/image.h b/engines/lab/image.h
new file mode 100644
index 0000000000..0f985e09eb
--- /dev/null
+++ b/engines/lab/image.h
@@ -0,0 +1,83 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_IMAGE_H
+#define LAB_IMAGE_H
+
+namespace Common {
+ class File;
+}
+
+namespace Lab {
+
+class LabEngine;
+
+class Image {
+ LabEngine *_vm;
+
+public:
+ uint16 _width;
+ uint16 _height;
+ byte *_imageData;
+
+ Image(LabEngine *vm) : _width(0), _height(0), _imageData(nullptr), _vm(vm), _autoFree(true) {}
+ Image(int w, int h, byte *d, LabEngine *vm, bool autoFree = true) : _width(w), _height(h), _imageData(d), _vm(vm), _autoFree(autoFree) {}
+ Image(Common::File *s, LabEngine *vm);
+ ~Image();
+
+ void setData(byte *d, bool autoFree = true);
+
+ /**
+ * Draws an image to the screen.
+ */
+ void drawImage(uint16 x, uint16 y);
+
+ /**
+ * Draws an image to the screen with transparency.
+ */
+ void drawMaskImage(uint16 x, uint16 y);
+
+ /**
+ * Reads an image from the screen.
+ */
+ void readScreenImage(uint16 x, uint16 y);
+
+ /**
+ * Blits a piece of one image to another.
+ */
+ void blitBitmap(uint16 srcX, uint16 srcY, Image *imgDest, uint16 destX, uint16 destY, uint16 width, uint16 height, byte masked);
+
+private:
+ bool _autoFree; ///< Free _imageData in destructor?
+};
+
+} // End of namespace Lab
+
+#endif // LAB_IMAGE_H
diff --git a/engines/lab/interface.cpp b/engines/lab/interface.cpp
new file mode 100644
index 0000000000..30f2f13fa5
--- /dev/null
+++ b/engines/lab/interface.cpp
@@ -0,0 +1,152 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/events.h"
+
+#include "lab/lab.h"
+
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/image.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+Button *EventManager::createButton(uint16 x, uint16 y, uint16 id, Common::KeyCode key, Image *image, Image *altImage) {
+ Button *button = new Button();
+
+ if (button) {
+ button->_x = _vm->_utils->vgaScaleX(x);
+ button->_y = y;
+ button->_buttonId = id;
+ button->_keyEquiv = key;
+ button->_image = image;
+ button->_altImage = altImage;
+ button->_isEnabled = true;
+
+ return button;
+ } else
+ return nullptr;
+}
+
+void EventManager::freeButtonList(ButtonList *buttonList) {
+ for (ButtonList::iterator buttonIter = buttonList->begin(); buttonIter != buttonList->end(); ++buttonIter) {
+ Button *button = *buttonIter;
+ delete button->_image;
+ delete button->_altImage;
+ delete button;
+ }
+
+ buttonList->clear();
+}
+
+void EventManager::drawButtonList(ButtonList *buttonList) {
+ for (ButtonList::iterator button = buttonList->begin(); button != buttonList->end(); ++button) {
+ toggleButton((*button), 1, true);
+
+ if (!(*button)->_isEnabled)
+ toggleButton((*button), 1, false);
+ }
+}
+
+void EventManager::toggleButton(Button *button, uint16 disabledPenColor, bool enable) {
+ if (!enable)
+ _vm->_graphics->checkerBoardEffect(disabledPenColor, button->_x, button->_y, button->_x + button->_image->_width - 1, button->_y + button->_image->_height - 1);
+ else
+ button->_image->drawImage(button->_x, button->_y);
+
+ button->_isEnabled = enable;
+}
+
+Button *EventManager::checkNumButtonHit(ButtonList *buttonList, Common::KeyCode key) {
+ uint16 gkey = key - '0';
+
+ if (!buttonList)
+ return nullptr;
+
+ for (ButtonList::iterator buttonItr = buttonList->begin(); buttonItr != buttonList->end(); ++buttonItr) {
+ Button *button = *buttonItr;
+ if (!button->_isEnabled)
+ continue;
+
+ if ((gkey - 1 == button->_buttonId) || (gkey == 0 && button->_buttonId == 9) || (button->_keyEquiv != Common::KEYCODE_INVALID && key == button->_keyEquiv)) {
+ button->_altImage->drawImage(button->_x, button->_y);
+ _vm->_system->delayMillis(80);
+ button->_image->drawImage(button->_x, button->_y);
+ return button;
+ }
+ }
+
+ return nullptr;
+}
+
+IntuiMessage *EventManager::getMsg() {
+ static IntuiMessage message;
+
+ updateMouse();
+ processInput();
+
+ if (_lastButtonHit) {
+ updateMouse();
+ message._msgClass = kMessageButtonUp;
+ message._code = _lastButtonHit->_buttonId;
+ message._qualifier = _keyPressed.flags;
+ _lastButtonHit = nullptr;
+ return &message;
+ } else if (_leftClick || _rightClick) {
+ message._msgClass = (_leftClick) ? kMessageLeftClick : kMessageRightClick;
+ message._qualifier = 0;
+ message._mouse = _mousePos;
+ if (!_vm->_isHiRes)
+ message._mouse.x /= 2;
+ _leftClick = _rightClick = false;
+ return &message;
+ } else if (_keyPressed.keycode != Common::KEYCODE_INVALID) {
+ Button *curButton = checkNumButtonHit(_screenButtonList, _keyPressed.keycode);
+
+ if (curButton) {
+ message._msgClass = kMessageButtonUp;
+ message._code = curButton->_buttonId;
+ } else {
+ message._msgClass = kMessageRawKey;
+ message._code = _keyPressed.keycode;
+ }
+
+ message._qualifier = _keyPressed.flags;
+ message._mouse = _mousePos;
+
+ _keyPressed.keycode = Common::KEYCODE_INVALID;
+
+ return &message;
+ } else
+ return nullptr;
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/intro.cpp b/engines/lab/intro.cpp
new file mode 100644
index 0000000000..b2a1b2059e
--- /dev/null
+++ b/engines/lab/intro.cpp
@@ -0,0 +1,418 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "lab/lab.h"
+
+#include "lab/anim.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/intro.h"
+#include "lab/music.h"
+#include "lab/resource.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+Intro::Intro(LabEngine *vm) : _vm(vm) {
+ _quitIntro = false;
+ _font = _vm->_resource->getFont("F:Map.fon");
+}
+
+Intro::~Intro() {
+ _vm->_graphics->freeFont(&_font);
+}
+
+void Intro::introEatMessages() {
+ while (1) {
+ IntuiMessage *msg = _vm->_event->getMsg();
+
+ if (_vm->shouldQuit()) {
+ _quitIntro = true;
+ return;
+ }
+
+ if (!msg)
+ return;
+
+ if ((msg->_msgClass == kMessageRightClick)
+ || ((msg->_msgClass == kMessageRawKey) && (msg->_code == Common::KEYCODE_ESCAPE)))
+ _quitIntro = true;
+ }
+}
+
+void Intro::doPictText(const Common::String filename, bool isScreen) {
+ Common::String path = Common::String("Lab:rooms/Intro/") + filename;
+
+ uint timeDelay = (isScreen) ? 35 : 7;
+ _vm->updateEvents();
+
+ if (_quitIntro)
+ return;
+
+ uint32 lastMillis = 0;
+ bool drawNextText = true;
+ bool doneFl = false;
+ bool begin = true;
+
+ Common::File *textFile = _vm->_resource->openDataFile(path);
+ char *textBuffer = new char[textFile->size()];
+ textFile->read(textBuffer, textFile->size());
+ delete textFile;
+ const char *curText = textBuffer;
+
+ while (1) {
+ if (drawNextText) {
+ if (begin)
+ begin = false;
+ else if (isScreen)
+ _vm->_graphics->fade(false);
+
+ if (isScreen) {
+ _vm->_graphics->rectFillScaled(10, 10, 310, 190, 7);
+
+ curText += _vm->_graphics->flowText(_font, _vm->_isHiRes ? 0 : -1, 5, 7, false, false, true, true, _vm->_utils->vgaRectScale(14, 11, 306, 189), curText);
+ _vm->_graphics->fade(true);
+ } else
+ curText += _vm->_graphics->longDrawMessage(Common::String(curText), false);
+
+ doneFl = (*curText == 0);
+
+ drawNextText = false;
+ introEatMessages();
+
+ if (_quitIntro) {
+ if (isScreen)
+ _vm->_graphics->fade(false);
+
+ delete[] textBuffer;
+ return;
+ }
+
+ lastMillis = _vm->_system->getMillis();
+ }
+
+ IntuiMessage *msg = _vm->_event->getMsg();
+ if (_vm->shouldQuit()) {
+ _quitIntro = true;
+ return;
+ }
+
+ if (!msg) {
+ _vm->updateEvents();
+ _vm->_anim->diffNextFrame();
+
+ uint32 elapsedSeconds = (_vm->_system->getMillis() - lastMillis) / 1000;
+
+ if (elapsedSeconds > timeDelay) {
+ if (doneFl) {
+ if (isScreen)
+ _vm->_graphics->fade(false);
+
+ delete[] textBuffer;
+ return;
+ } else {
+ drawNextText = true;
+ }
+ }
+ _vm->waitTOF();
+ } else {
+ uint32 msgClass = msg->_msgClass;
+ uint16 code = msg->_code;
+
+ if ((msgClass == kMessageRightClick) ||
+ ((msgClass == kMessageRawKey) && (code == Common::KEYCODE_ESCAPE))) {
+ _quitIntro = true;
+
+ if (isScreen)
+ _vm->_graphics->fade(false);
+
+ delete[] textBuffer;
+ return;
+ } else if ((msgClass == kMessageLeftClick) || (msgClass == kMessageRightClick)) {
+ if (msgClass == kMessageLeftClick) {
+ if (doneFl) {
+ if (isScreen)
+ _vm->_graphics->fade(false);
+
+ delete[] textBuffer;
+ return;
+ } else
+ drawNextText = true;
+ }
+
+ introEatMessages();
+
+ if (_quitIntro) {
+ if (isScreen)
+ _vm->_graphics->fade(false);
+
+ delete[] textBuffer;
+ return;
+ }
+ }
+
+ if (doneFl) {
+ if (isScreen)
+ _vm->_graphics->fade(false);
+
+ delete[] textBuffer;
+ return;
+ } else
+ drawNextText = true;
+ }
+ } // while(1)
+}
+
+void Intro::nReadPict(const Common::String filename, bool playOnce, bool noPalChange, bool doBlack, int wait) {
+ Common::String finalFileName = Common::String("P:Intro/") + filename;
+
+ _vm->updateEvents();
+ introEatMessages();
+
+ if (_quitIntro)
+ return;
+
+ if (noPalChange)
+ _vm->_anim->_noPalChange = true;
+
+ _vm->_anim->_doBlack = doBlack;
+ _vm->_anim->stopDiffEnd();
+ _vm->_graphics->readPict(finalFileName, playOnce);
+
+ if (wait) {
+ for (int i = 0; i < wait / 10; i++) {
+ _vm->updateEvents();
+ introEatMessages();
+ if (_quitIntro)
+ break;
+ _vm->_system->delayMillis(10);
+ }
+ }
+
+ if (noPalChange)
+ _vm->_anim->_noPalChange = false;
+}
+
+void Intro::play() {
+ uint16 palette[16] = {
+ 0x0000, 0x0855, 0x0FF9, 0x0EE7,
+ 0x0ED5, 0x0DB4, 0x0CA2, 0x0C91,
+ 0x0B80, 0x0B80, 0x0B91, 0x0CA2,
+ 0x0CB3, 0x0DC4, 0x0DD6, 0x0EE7
+ };
+
+ if (_vm->getPlatform() == Common::kPlatformDOS) {
+ nReadPict("EA0");
+ nReadPict("EA1");
+ nReadPict("EA2");
+ nReadPict("EA3");
+ } else if (_vm->getPlatform() == Common::kPlatformWindows) {
+ nReadPict("WYRMKEEP", true, false, false, 4000);
+ }
+
+ _vm->_graphics->blackAllScreen();
+
+ if (_vm->getPlatform() != Common::kPlatformAmiga)
+ _vm->_music->changeMusic("Music:BackGrou", false, false);
+ else
+ _vm->_music->changeMusic("Music:BackGround", false, false);
+
+ if (_vm->getPlatform() == Common::kPlatformDOS)
+ nReadPict("TNDcycle.pic", true, true);
+ else
+ nReadPict("TNDcycle2.pic", true, true);
+
+ _vm->_graphics->_fadePalette = palette;
+
+ for (int i = 0; i < 16; i++) {
+ palette[i] = ((_vm->_anim->_diffPalette[i * 3] >> 2) << 8) +
+ ((_vm->_anim->_diffPalette[i * 3 + 1] >> 2) << 4) +
+ (_vm->_anim->_diffPalette[i * 3 + 2] >> 2);
+ }
+
+ _vm->updateEvents();
+ introEatMessages();
+ if (!_quitIntro)
+ _vm->_graphics->fade(true);
+
+ for (int times = 0; times < 150; times++) {
+ _vm->updateEvents();
+ introEatMessages();
+ if (_quitIntro)
+ break;
+
+ uint16 temp = palette[2];
+
+ for (int i = 2; i < 15; i++)
+ palette[i] = palette[i + 1];
+
+ palette[15] = temp;
+
+ _vm->_graphics->setAmigaPal(palette);
+ _vm->waitTOF();
+ }
+
+ if (!_quitIntro) {
+ _vm->_graphics->fade(false);
+ _vm->_graphics->blackAllScreen();
+ _vm->updateEvents();
+ introEatMessages();
+ }
+
+ nReadPict("Title.A");
+ nReadPict("AB", true, false, false, 1000);
+ nReadPict("BA");
+ nReadPict("AC", true, false, false, 1000);
+ nReadPict("CA");
+ nReadPict("AD", true, false, false, 1000);
+ nReadPict("DA");
+
+ _vm->_graphics->blackAllScreen();
+ _vm->updateEvents();
+ introEatMessages();
+
+ nReadPict("Intro.1", true, true);
+
+ for (int i = 0; i < 16; i++) {
+ palette[i] = ((_vm->_anim->_diffPalette[i * 3] >> 2) << 8) +
+ ((_vm->_anim->_diffPalette[i * 3 + 1] >> 2) << 4) +
+ (_vm->_anim->_diffPalette[i * 3 + 2] >> 2);
+ }
+
+ doPictText("i.1", true);
+ if (_vm->getPlatform() == Common::kPlatformWindows) {
+ doPictText("i.2A", true);
+ doPictText("i.2B", true);
+ }
+
+ _vm->_graphics->blackAllScreen();
+ _vm->updateEvents();
+ introEatMessages();
+
+ nReadPict("Station1", true, false, true);
+ doPictText("i.3");
+
+ nReadPict("Station2", true, false, true);
+ doPictText("i.4");
+
+ nReadPict("Stiles4", true, false, true);
+ doPictText("i.5");
+
+ nReadPict("Stiles3", true, false, true);
+ doPictText("i.6");
+
+ if (_vm->getPlatform() == Common::kPlatformWindows)
+ nReadPict("Platform2", true, false, true);
+ else
+ nReadPict("Platform", true, false, true);
+ doPictText("i.7");
+
+ nReadPict("Subway.1", true, false, true);
+ doPictText("i.8");
+
+ nReadPict("Subway.2", true, false, true);
+
+ doPictText("i.9");
+ doPictText("i.10");
+ doPictText("i.11");
+
+ for (int i = 0; i < 50; i++) {
+ _vm->updateEvents();
+ introEatMessages();
+ if (_quitIntro)
+ break;
+
+ for (int idx = (8 * 3); idx < (255 * 3); idx++)
+ _vm->_anim->_diffPalette[idx] = 255 - _vm->_anim->_diffPalette[idx];
+
+ _vm->waitTOF();
+ _vm->_graphics->setPalette(_vm->_anim->_diffPalette, 256);
+ _vm->waitTOF();
+ _vm->waitTOF();
+ }
+
+ doPictText("i.12");
+ doPictText("i.13");
+
+ nReadPict("Daed0");
+ doPictText("i.14");
+
+ nReadPict("Daed1");
+ doPictText("i.15");
+
+ nReadPict("Daed2");
+ doPictText("i.16");
+ doPictText("i.17");
+ doPictText("i.18");
+
+ nReadPict("Daed3");
+ doPictText("i.19");
+ doPictText("i.20");
+
+ nReadPict("Daed4");
+ doPictText("i.21");
+
+ nReadPict("Daed5");
+ doPictText("i.22");
+ doPictText("i.23");
+ doPictText("i.24");
+
+ nReadPict("Daed6");
+ doPictText("i.25");
+ doPictText("i.26");
+
+ nReadPict("Daed7", false);
+ doPictText("i.27");
+ doPictText("i.28");
+
+ nReadPict("Daed8");
+ doPictText("i.29");
+ doPictText("i.30");
+
+ nReadPict("Daed9");
+ doPictText("i.31");
+ doPictText("i.32");
+ doPictText("i.33");
+
+ nReadPict("Daed9a");
+ nReadPict("Daed10");
+ doPictText("i.34");
+ doPictText("i.35");
+ doPictText("i.36");
+
+ nReadPict("SubX");
+
+ if (_quitIntro) {
+ _vm->_graphics->rectFill(0, 0, _vm->_graphics->_screenWidth - 1, _vm->_graphics->_screenHeight - 1, 0);
+ _vm->_anim->_doBlack = true;
+ }
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/intro.h b/engines/lab/intro.h
new file mode 100644
index 0000000000..f86d3baf69
--- /dev/null
+++ b/engines/lab/intro.h
@@ -0,0 +1,67 @@
+/* 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.
+ *
+ */
+
+ /*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_INTRO_H
+#define LAB_INTRO_H
+
+namespace Lab {
+
+class Intro {
+public:
+ Intro(LabEngine *vm);
+ ~Intro();
+
+ /**
+ * Does the introduction sequence for Labyrinth.
+ */
+ void play();
+
+private:
+ /**
+ * Goes through, and responds to all the intuition messages currently in the
+ * message queue.
+ */
+ void introEatMessages();
+
+ /**
+ * Reads in a picture.
+ */
+ void doPictText(const Common::String filename, bool isScreen = false);
+
+ void nReadPict(const Common::String filename, bool playOnce = true, bool noPalChange = false, bool doBlack = false, int wait = 0);
+
+ LabEngine *_vm;
+ bool _quitIntro;
+ TextFont *_font;
+};
+
+} // End of namespace Lab
+
+#endif // LAB_INTRO_H
diff --git a/engines/lab/lab.cpp b/engines/lab/lab.cpp
new file mode 100644
index 0000000000..47b864d98b
--- /dev/null
+++ b/engines/lab/lab.cpp
@@ -0,0 +1,270 @@
+/* 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.
+ *
+ */
+
+ /*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/debug-channels.h"
+#include "common/error.h"
+
+#include "engines/util.h"
+#include "gui/message.h"
+
+#include "lab/lab.h"
+
+#include "lab/anim.h"
+#include "lab/console.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/image.h"
+#include "lab/music.h"
+#include "lab/processroom.h"
+#include "lab/resource.h"
+#include "lab/speciallocks.h"
+#include "lab/utils.h"
+
+namespace Lab {
+LabEngine::LabEngine(OSystem *syst, const ADGameDescription *gameDesc)
+ : Engine(syst), _gameDescription(gameDesc), _extraGameFeatures(0) {
+ _lastWaitTOFTicks = 0;
+
+ _isHiRes = false;
+ _roomNum = -1;
+ for (int i = 0; i < MAX_CRUMBS; i++) {
+ _breadCrumbs[i]._roomNum = 0;
+ _breadCrumbs[i]._direction = kDirectionNorth;
+ }
+
+ _numCrumbs = 0;
+ _droppingCrumbs = false;
+ _followingCrumbs = false;
+ _followCrumbsFast = false;
+ _isCrumbTurning = false;
+ _isCrumbWaiting = false;
+ _noUpdateDiff = false;
+ _quitLab = false;
+ _mainDisplay = true;
+
+ _numInv = 0;
+ _manyRooms = 0;
+ _direction = 0;
+ _highestCondition = 0;
+ _crumbTimestamp = 0;
+ _maxRooms = 0;
+
+ _event = nullptr;
+ _resource = nullptr;
+ _music = nullptr;
+ _anim = nullptr;
+ _closeDataPtr = nullptr;
+ _conditions = nullptr;
+ _graphics = nullptr;
+ _rooms = nullptr;
+ _roomsFound = nullptr;
+ _specialLocks = nullptr;
+ _utils = nullptr;
+ _console = nullptr;
+ _journalBackImage = nullptr;
+
+ _lastTooLong = false;
+ _interfaceOff = false;
+ _alternate = false;
+
+ for (int i = 0; i < 20; i++)
+ _moveImages[i] = nullptr;
+
+ for (int i = 0; i < 10; i++)
+ _invImages[i] = nullptr;
+
+ _curFileName = " ";
+ _msgFont = nullptr;
+ _inventory = nullptr;
+
+ _imgMap = nullptr;
+ _imgRoom = nullptr;
+ _imgUpArrowRoom = nullptr;
+ _imgDownArrowRoom = nullptr;
+ _imgBridge = nullptr;
+ _imgHRoom = nullptr;
+ _imgVRoom = nullptr;
+ _imgMaze = nullptr;
+ _imgHugeMaze = nullptr;
+ _imgPath = nullptr;
+ for (int i = 0; i < 4; i++)
+ _imgMapX[i] = nullptr;
+ _maps = nullptr;
+
+ _blankJournal = nullptr;
+ _journalFont = nullptr;
+ _journalPage = 0;
+ _lastPage = false;
+ _monitorPage = 0;
+ _monitorTextFilename = "";
+ _monitorButton = nullptr;
+ _monitorButtonHeight = 1;
+ for (int i = 0; i < 20; i++)
+ _highPalette[i] = 0;
+ _introPlaying = false;
+}
+
+LabEngine::~LabEngine() {
+ // Remove all of our debug levels here
+ DebugMan.clearAllDebugChannels();
+
+ freeMapData();
+ delete[] _rooms;
+ delete[] _inventory;
+
+ delete _conditions;
+ delete _roomsFound;
+ delete _event;
+ delete _resource;
+ delete _music;
+ delete _anim;
+ delete _graphics;
+ delete _specialLocks;
+ delete _utils;
+ delete _console;
+ delete _journalBackImage;
+}
+
+Common::Error LabEngine::run() {
+ if (getFeatures() & GF_LOWRES)
+ initGraphics(320, 200, false);
+ else
+ initGraphics(640, 480, true);
+
+ _event = new EventManager(this);
+ _resource = new Resource(this);
+ _music = new Music(this);
+ _graphics = new DisplayMan(this);
+ _anim = new Anim(this);
+ _specialLocks = new SpecialLocks(this);
+ _utils = new Utils(this);
+ _console = new Console(this);
+ _journalBackImage = new Image(this);
+
+ if (getPlatform() == Common::kPlatformWindows) {
+ // Check if this is the Wyrmkeep trial
+ Common::File roomFile;
+ bool knownVersion = true;
+ bool roomFileOpened = roomFile.open("game/rooms/48");
+
+ if (!roomFileOpened)
+ knownVersion = false;
+ else if (roomFile.size() != 892)
+ knownVersion = false;
+ else {
+ roomFile.seek(352);
+ byte checkByte = roomFile.readByte();
+ if (checkByte == 0x00) {
+ // Full Windows version
+ } else if (checkByte == 0x80) {
+ // Wyrmkeep trial version
+ _extraGameFeatures = GF_WINDOWS_TRIAL;
+
+ GUI::MessageDialog trialMessage("This is a trial Windows version of the game. To play the full version, you will need to use the original interpreter and purchase a key from Wyrmkeep");
+ trialMessage.runModal();
+ } else {
+ knownVersion = false;
+ }
+
+ roomFile.close();
+
+ if (!knownVersion) {
+ warning("Unknown Windows version found, please report this version to the ScummVM team");
+ return Common::kNoGameDataFoundError;
+ }
+ }
+ }
+
+ go();
+
+ return Common::kNoError;
+}
+
+Common::String LabEngine::generateSaveFileName(uint slot) {
+ return Common::String::format("%s.%03u", _targetName.c_str(), slot);
+}
+
+void LabEngine::drawStaticMessage(byte index) {
+ _graphics->drawMessage(_resource->getStaticText((StaticText)index), false);
+}
+
+void LabEngine::changeVolume(int delta) {
+ int sfxPrev = _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
+ int musicPrev = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
+ int sfxNew = (delta > 0) ? MIN<int>(sfxPrev + 10, Audio::Mixer::kMaxMixerVolume) : MAX<int>(sfxPrev - 10, 0);
+ int musicNew = (delta > 0) ? MIN<int>(musicPrev + 10, Audio::Mixer::kMaxMixerVolume) : MAX<int>(musicPrev - 10, 0);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, sfxNew);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, musicNew);
+}
+
+void LabEngine::waitTOF() {
+ _system->copyRectToScreen(_graphics->_displayBuffer, _graphics->_screenWidth, 0, 0, _graphics->_screenWidth, _graphics->_screenHeight);
+ _system->updateScreen();
+
+ _event->processInput();
+
+ uint32 now;
+
+ for (now = _system->getMillis(); now - _lastWaitTOFTicks <= 0xF; now = _system->getMillis() )
+ _system->delayMillis(_lastWaitTOFTicks - now + 17);
+
+ _lastWaitTOFTicks = now;
+}
+
+void LabEngine::updateEvents() {
+ _event->processInput();
+ _event->updateMouse();
+}
+
+Common::Error LabEngine::loadGameState(int slot) {
+ bool result = loadGame(slot);
+ _curFileName = " ";
+ _closeDataPtr = nullptr;
+ _mainDisplay = true;
+ _followingCrumbs = false;
+ _event->simulateEvent();
+ _graphics->_longWinInFront = false;
+ return (result) ? Common::kNoError : Common::kUserCanceled;
+}
+
+Common::Error LabEngine::saveGameState(int slot, const Common::String &desc) {
+ bool result = saveGame(slot, desc);
+ return (result) ? Common::kNoError : Common::kUserCanceled;
+}
+
+bool LabEngine::canLoadGameStateCurrently() {
+ return !_anim->isPlaying() && !_introPlaying;
+}
+
+bool LabEngine::canSaveGameStateCurrently() {
+ return !_anim->isPlaying() && !_introPlaying;
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/lab.h b/engines/lab/lab.h
new file mode 100644
index 0000000000..2c3a723f3e
--- /dev/null
+++ b/engines/lab/lab.h
@@ -0,0 +1,508 @@
+/* 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.
+ *
+ */
+
+ /*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_LAB_H
+#define LAB_LAB_H
+
+#include "common/system.h"
+#include "common/random.h"
+#include "common/rect.h"
+#include "common/savefile.h"
+#include "engines/engine.h"
+#include "engines/savestate.h"
+
+#include "lab/console.h"
+#include "lab/image.h"
+#include "lab/labsets.h"
+
+struct ADGameDescription;
+
+namespace Lab {
+
+struct MapData;
+struct Action;
+struct CloseData;
+struct Button;
+struct IntuiMessage;
+struct InventoryData;
+struct RoomData;
+struct Rule;
+struct TextFont;
+struct ViewData;
+
+class Anim;
+class DisplayMan;
+class EventManager;
+class Image;
+class Music;
+class Resource;
+class SpecialLocks;
+class Utils;
+
+struct SaveGameHeader {
+ byte _version;
+ SaveStateDescriptor _descr;
+ uint16 _roomNumber;
+ uint16 _direction;
+};
+
+enum GameFeatures {
+ GF_LOWRES = 1 << 0,
+ GF_WINDOWS_TRIAL = 1 << 1
+};
+
+typedef Common::List<Button *> ButtonList;
+
+struct CrumbData {
+ uint16 _roomNum;
+ uint16 _direction;
+};
+
+#define MAX_CRUMBS 128
+
+typedef Common::List<Rule> RuleList;
+typedef Common::List<Action> ActionList;
+typedef Common::List<CloseData> CloseDataList;
+typedef Common::List<ViewData> ViewDataList;
+
+enum Direction {
+ kDirectionNorth,
+ kDirectionSouth,
+ kDirectionEast,
+ kDirectionWest
+};
+
+enum MainButton {
+ kButtonNone = -1,
+ kButtonPickup,
+ kButtonUse,
+ kButtonOpen,
+ kButtonClose,
+ kButtonLook,
+ kButtonInventory,
+ kButtonLeft,
+ kButtonForward,
+ kButtonRight,
+ kButtonMap
+};
+
+enum MessageClass {
+ kMessageLeftClick,
+ kMessageRightClick,
+ kMessageButtonUp,
+ kMessageRawKey
+};
+
+class LabEngine : public Engine {
+ friend class Console;
+
+private:
+ bool _interfaceOff;
+ bool _isCrumbWaiting;
+ bool _lastTooLong;
+ bool _lastPage;
+ bool _mainDisplay;
+ bool _noUpdateDiff;
+ bool _quitLab;
+
+ byte *_blankJournal;
+
+ int _lastWaitTOFTicks;
+
+ uint16 _direction;
+ uint16 _highPalette[20];
+ uint16 _journalPage;
+ uint16 _maxRooms;
+ uint16 _monitorPage;
+ uint16 _monitorButtonHeight;
+
+ uint32 _extraGameFeatures;
+
+ Common::String _journalText;
+ Common::String _journalTextTitle;
+ Common::String _nextFileName;
+ Common::String _newFileName;
+ Common::String _monitorTextFilename;
+
+ const CloseData *_closeDataPtr;
+ ButtonList _journalButtonList;
+ ButtonList _mapButtonList;
+ Image *_imgMap, *_imgRoom, *_imgUpArrowRoom, *_imgDownArrowRoom, *_imgBridge;
+ Image *_imgHRoom, *_imgVRoom, *_imgMaze, *_imgHugeMaze, *_imgPath;
+ Image *_imgMapX[4];
+ InventoryData *_inventory;
+ MapData *_maps;
+ Image *_monitorButton;
+ Image *_journalBackImage;
+ TextFont *_journalFont;
+ bool _introPlaying;
+
+public:
+ bool _alternate;
+ bool _droppingCrumbs;
+ bool _followingCrumbs;
+ bool _followCrumbsFast;
+ bool _isCrumbTurning;
+ bool _isHiRes;
+
+ int _roomNum;
+
+ uint16 _highestCondition;
+ uint16 _manyRooms;
+ uint16 _numCrumbs;
+ uint16 _numInv;
+
+ uint32 _crumbTimestamp;
+
+ Common::String _curFileName;
+
+ Anim *_anim;
+ CrumbData _breadCrumbs[MAX_CRUMBS];
+ DisplayMan *_graphics;
+ EventManager *_event;
+ ButtonList _invButtonList;
+ ButtonList _moveButtonList;
+ Image *_invImages[10];
+ Image *_moveImages[20];
+ LargeSet *_conditions, *_roomsFound;
+ Music *_music;
+ Resource *_resource;
+ RoomData *_rooms;
+ TextFont *_msgFont;
+ SpecialLocks *_specialLocks;
+ Utils *_utils;
+ Console *_console;
+ GUI::Debugger *getDebugger() { return _console; }
+
+public:
+ LabEngine(OSystem *syst, const ADGameDescription *gameDesc);
+ ~LabEngine();
+
+ virtual Common::Error run();
+ void go();
+
+ const ADGameDescription *_gameDescription;
+ Common::Platform getPlatform() const;
+ uint32 getFeatures() const;
+
+ bool hasFeature(EngineFeature f) const;
+ Common::String generateSaveFileName(uint slot);
+
+ void changeVolume(int delta);
+ uint16 getDirection() { return _direction; }
+
+ /**
+ * Returns the current picture name.
+ */
+ Common::String getPictName(bool useClose);
+ uint16 getQuarters();
+ void setDirection(uint16 direction) { _direction = direction; };
+ void setQuarters(uint16 quarters);
+ void updateEvents();
+ void waitTOF();
+
+ Common::Error loadGameState(int slot);
+ Common::Error saveGameState(int slot, const Common::String &desc);
+ bool canLoadGameStateCurrently();
+ bool canSaveGameStateCurrently();
+
+private:
+ /**
+ * Checks whether all the conditions in a condition list are met.
+ */
+ bool checkConditions(const Common::Array<int16> &cond);
+
+ /**
+ * Decrements the current inventory number.
+ */
+ void decIncInv(uint16 *CurInv, bool dec);
+
+ /**
+ * Processes the action list.
+ */
+ void doActions(const ActionList &actionList);
+
+ /**
+ * Goes through the rules if an action is taken.
+ */
+ bool doActionRule(Common::Point pos, int16 action, int16 roomNum);
+
+ /**
+ * Does the work for doActionRule.
+ */
+ bool doActionRuleSub(int16 action, int16 roomNum, const CloseData *closePtr, bool allowDefaults);
+
+ /**
+ * Checks whether the close up is one of the special case closeups.
+ */
+ bool doCloseUp(const CloseData *closePtr);
+
+ /**
+ * Goes through the rules if the user tries to go forward.
+ */
+ bool doGoForward();
+
+ /**
+ * Does the journal processing.
+ */
+ void doJournal();
+
+ /**
+ * Goes through the rules if the user tries to go to the main view
+ */
+ bool doMainView();
+
+ /**
+ * Does the map processing.
+ */
+ void doMap(uint16 curRoom);
+
+ /**
+ * Does what's necessary for the monitor.
+ */
+ void doMonitor(const Common::String background, const Common::String textfile, bool isinteractive, Common::Rect textRect);
+
+ /**
+ * Does the things to properly set up the detective notes.
+ */
+ void doNotes();
+
+ /**
+ * Does the work for doActionRule.
+ */
+ bool doOperateRuleSub(int16 itemNum, int16 roomNum, const CloseData *closePtr, bool allowDefaults);
+
+ /**
+ * Goes through the rules if the user tries to operate an item on an object.
+ */
+ bool doOperateRule(Common::Point pos, int16 ItemNum);
+
+ /**
+ * Goes through the rules if the user tries to turn.
+ */
+ bool doTurn(uint16 from, uint16 to);
+
+ /**
+ * If the user hits the "Use" button; things that can get used on themselves.
+ */
+ bool doUse(uint16 curInv);
+
+ /**
+ * Does the things to properly set up the old west newspaper. Assumes that
+ * OpenHiRes already called.
+ */
+ void doWestPaper();
+
+ /**
+ * Draws the current direction to the screen.
+ */
+ void drawDirection(const CloseData *closePtr);
+
+ /**
+ * Draws the journal from page x.
+ */
+ void drawJournal(uint16 wipenum, bool needFade);
+
+ /**
+ * Draws the text to the back journal screen to the appropriate Page number
+ */
+ void drawJournalText();
+
+ /**
+ * Draws the map
+ */
+ void drawMap(uint16 curRoom, uint16 curMsg, uint16 floorNum, bool fadeIn);
+
+ /**
+ * Draws the text for the monitor.
+ */
+ void drawMonText(const char *text, TextFont *monitorFont, Common::Rect textRect, bool isinteractive);
+
+ /**
+ * Draws a room map.
+ */
+ void drawRoomMap(uint16 curRoom, bool drawMarkFl);
+
+ /**
+ * Draws the message for the room.
+ */
+ void drawRoomMessage(uint16 curInv, const CloseData *closePtr);
+ void drawStaticMessage(byte index);
+
+ /**
+ * Eats all the available messages.
+ */
+ void eatMessages();
+
+ /**
+ * Goes through the list of closeups to find a match.
+ * @note Known bug here. If there are two objects that have closeups, and
+ * some of the closeups have the same hit boxes, then this returns the first
+ * occurrence of the object with the same hit box.
+ */
+ const CloseData *findClosePtrMatch(const CloseData *closePtr, const CloseDataList &list);
+
+ /**
+ * Checks if a floor has been visited.
+ */
+ bool floorVisited(uint16 floorNum);
+
+ /**
+ * New code to allow quick(er) return navigation in game.
+ */
+ MainButton followCrumbs();
+ void freeMapData();
+ void freeScreens();
+ bool processEvent(MessageClass tmpClass, uint16 code, uint16 qualifier, Common::Point tmpPos,
+ uint16 &curInv, IntuiMessage *curMsg, bool &forceDraw, uint16 buttonId, uint16 &actionMode);
+
+ /**
+ * Gets the current inventory name.
+ */
+ Common::String getInvName(uint16 curInv);
+
+ /**
+ * Returns the floor to show when the down arrow is pressed
+ * @note The original did not show all the visited floors, but we do
+ */
+ uint16 getLowerFloor(uint16 floorNum);
+
+ /**
+ * Gets an object, if any, from the user's click on the screen.
+ */
+ const CloseData *getObject(Common::Point pos, const CloseData *closePtr);
+
+ /**
+ * Returns the floor to show when the up arrow is pressed
+ * @note The original did not show all the visited floors, but we do
+ */
+ uint16 getUpperFloor(uint16 floorNum);
+
+ /**
+ * Gets the current ViewDataPointer.
+ */
+ ViewData *getViewData(uint16 roomNum, uint16 direction);
+
+ /**
+ * Turns the interface off.
+ */
+ void interfaceOff();
+
+ /**
+ * Turns the interface on.
+ */
+ void interfaceOn();
+
+ /**
+ * Loads in the data for the journal.
+ */
+ void loadJournalData();
+
+ /**
+ * Loads in the map data.
+ */
+ void loadMapData();
+
+ /**
+ * The main game loop.
+ */
+ void mainGameLoop();
+ void showLab2Teaser();
+ void mayShowCrumbIndicator();
+ void mayShowCrumbIndicatorOff();
+
+ /**
+ * Permanently flips the imagery of a button.
+ */
+ void perFlipButton(uint16 buttonId);
+
+ /**
+ * process a arrow button movement.
+ */
+ uint16 processArrow(uint16 curDirection, uint16 arrow);
+
+ /**
+ * Processes user input.
+ */
+ void processJournal();
+
+ /**
+ * Processes the map.
+ */
+ void processMap(uint16 curRoom);
+
+ /**
+ * Processes user input.
+ */
+ void processMonitor(const char *ntext, TextFont *monitorFont, bool isInteractive, Common::Rect textRect);
+
+ /**
+ * Figures out what a room's coordinates should be.
+ */
+ Common::Rect roomCoords(uint16 curRoom);
+ bool saveRestoreGame();
+
+ /**
+ * Sets the current close up data.
+ */
+ void setCurrentClose(Common::Point pos, const CloseData **closePtrList, bool useAbsoluteCoords, bool next=false);
+
+ /**
+ * Takes the currently selected item.
+ */
+ bool takeItem(Common::Point pos);
+
+ /**
+ * Does the turn page wipe.
+ */
+ void turnPage(bool fromLeft);
+ bool processKey(IntuiMessage *curMsg, uint32 msgClass, uint16 &qualifier, Common::Point &curPos, uint16 &curInv, bool &forceDraw, uint16 code);
+ void processMainButton(uint16 &curInv, uint16 &lastInv, uint16 &oldDirection, bool &forceDraw, uint16 buttonId, uint16 &actionMode);
+ void processAltButton(uint16 &curInv, uint16 &lastInv, uint16 buttonId, uint16 &actionMode);
+ void performAction(uint16 actionMode, Common::Point curPos, uint16 &curInv);
+
+private:
+ /**
+ * Writes the game out to disk.
+ */
+ bool saveGame(int slot, const Common::String desc);
+
+ /**
+ * Reads the game from disk.
+ */
+ bool loadGame(int slot);
+ void writeSaveGameHeader(Common::OutSaveFile *out, const Common::String &saveName);
+};
+
+bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header);
+
+} // End of namespace Lab
+
+#endif // LAB_LAB_H
diff --git a/engines/lab/labsets.cpp b/engines/lab/labsets.cpp
new file mode 100644
index 0000000000..3e84275fa4
--- /dev/null
+++ b/engines/lab/labsets.cpp
@@ -0,0 +1,77 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/file.h"
+
+#include "lab/lab.h"
+
+#include "lab/labsets.h"
+#include "lab/resource.h"
+
+namespace Lab {
+
+LargeSet::LargeSet(uint16 last, LabEngine *vm) : _vm(vm) {
+ last = (((last + 15) >> 4) << 4);
+
+ _array = new uint16[last >> 3];
+ memset(_array, 0, last >> 3);
+ _lastElement = last;
+}
+
+LargeSet::~LargeSet() {
+ delete[] _array;
+}
+
+bool LargeSet::in(uint16 element) {
+ return ((1 << ((element - 1) % 16)) & (_array[(element - 1) >> 4])) > 0;
+}
+
+void LargeSet::inclElement(uint16 element) {
+ _array[(element - 1) >> 4] |= 1 << ((element - 1) % 16);
+}
+
+void LargeSet::exclElement(uint16 element) {
+ _array[(element - 1) >> 4] &= ~(1 << ((element - 1) % 16));
+}
+
+bool LargeSet::readInitialConditions(const Common::String fileName) {
+ Common::File *file = _vm->_resource->openDataFile(fileName, MKTAG('C', 'O', 'N', '0'));
+
+ uint16 conditions = file->readUint16LE();
+ for (int i = 0; i < conditions; i++) {
+ inclElement(file->readUint16LE());
+ }
+
+ delete file;
+ return true;
+}
+
+
+} // End of namespace Lab
diff --git a/engines/lab/labsets.h b/engines/lab/labsets.h
new file mode 100644
index 0000000000..afd997e9eb
--- /dev/null
+++ b/engines/lab/labsets.h
@@ -0,0 +1,61 @@
+/* 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.
+ *
+ */
+
+ /*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_LABSETS_H
+#define LAB_LABSETS_H
+
+namespace Lab {
+
+//---------------------------
+//----- From LabSets.c ------
+//---------------------------
+
+class LabEngine;
+
+class LargeSet {
+public:
+ LargeSet(uint16 last, LabEngine *vm);
+ ~LargeSet();
+ bool in(uint16 element);
+ void inclElement(uint16 element);
+ void exclElement(uint16 element);
+ bool readInitialConditions(const Common::String fileName);
+
+private:
+ LabEngine *_vm;
+
+public:
+ uint16 _lastElement;
+ uint16 *_array;
+};
+
+} // End of namespace Lab
+
+#endif // LAB_LABSETS_H
diff --git a/engines/lab/map.cpp b/engines/lab/map.cpp
new file mode 100644
index 0000000000..2b283aec4a
--- /dev/null
+++ b/engines/lab/map.cpp
@@ -0,0 +1,553 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "lab/lab.h"
+
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/image.h"
+#include "lab/labsets.h"
+#include "lab/music.h"
+#include "lab/processroom.h"
+#include "lab/resource.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+/*---------------------------------------------------------------------------*/
+/*------------------------------ The Map stuff ------------------------------*/
+/*---------------------------------------------------------------------------*/
+
+enum MapFloor {
+ kFloorNone,
+ kFloorLower,
+ kFloorMiddle,
+ kFloorUpper,
+ kFloorMedMaze,
+ kFloorHedgeMaze,
+ kFloorSurMaze,
+ kFloorCarnival
+};
+
+void LabEngine::loadMapData() {
+ Common::File *mapImages = _resource->openDataFile("P:MapImage");
+
+ _imgMap = new Image(mapImages, this);
+ _imgRoom = new Image(mapImages, this);
+ _imgUpArrowRoom = new Image(mapImages, this);
+ _imgDownArrowRoom = new Image(mapImages, this);
+ _imgHRoom = new Image(mapImages, this);
+ _imgVRoom = new Image(mapImages, this);
+ _imgMaze = new Image(mapImages, this);
+ _imgHugeMaze = new Image(mapImages, this);
+
+ _imgMapX[kDirectionNorth] = new Image(mapImages, this);
+ _imgMapX[kDirectionEast] = new Image(mapImages, this);
+ _imgMapX[kDirectionSouth] = new Image(mapImages, this);
+ _imgMapX[kDirectionWest] = new Image(mapImages, this);
+ _imgPath = new Image(mapImages, this);
+ _imgBridge = new Image(mapImages, this);
+
+ _mapButtonList.push_back(_event->createButton( 8, _utils->vgaScaleY(105), 0, Common::KEYCODE_ESCAPE, new Image(mapImages, this), new Image(mapImages, this))); // back
+ _mapButtonList.push_back(_event->createButton( 55, _utils->vgaScaleY(105), 1, Common::KEYCODE_UP, new Image(mapImages, this), new Image(mapImages, this))); // up
+ _mapButtonList.push_back(_event->createButton(101, _utils->vgaScaleY(105), 2, Common::KEYCODE_DOWN, new Image(mapImages, this), new Image(mapImages, this))); // down
+
+ delete mapImages;
+
+ Common::File *mapFile = _resource->openDataFile("Lab:Maps", MKTAG('M', 'A', 'P', '0'));
+ updateEvents();
+
+ _maxRooms = mapFile->readUint16LE();
+ _maps = new MapData[_maxRooms + 1]; // will be freed when the user exits the map
+ for (int i = 0; i <= _maxRooms; i++) {
+ _maps[i]._x = mapFile->readUint16LE();
+ _maps[i]._y = mapFile->readUint16LE();
+ _maps[i]._pageNumber = mapFile->readUint16LE();
+ _maps[i]._specialID = (SpecialRoom) mapFile->readUint16LE();
+ _maps[i]._mapFlags = mapFile->readUint32LE();
+ }
+
+ delete mapFile;
+}
+
+void LabEngine::freeMapData() {
+ _event->freeButtonList(&_mapButtonList);
+
+ delete _imgMap;
+ delete _imgRoom;
+ delete _imgUpArrowRoom;
+ delete _imgDownArrowRoom;
+ delete _imgBridge;
+ delete _imgHRoom;
+ delete _imgVRoom;
+ delete _imgMaze;
+ delete _imgHugeMaze;
+ delete _imgPath;
+ for (int i = 0; i < 4; i++)
+ delete _imgMapX[i];
+ delete[] _maps;
+
+ _imgMap = nullptr;
+ _imgRoom = nullptr;
+ _imgUpArrowRoom = nullptr;
+ _imgDownArrowRoom = nullptr;
+ _imgBridge = nullptr;
+ _imgHRoom = nullptr;
+ _imgVRoom = nullptr;
+ _imgMaze = nullptr;
+ _imgHugeMaze = nullptr;
+ _imgPath = nullptr;
+ for (int i = 0; i < 4; i++)
+ _imgMapX[i] = nullptr;
+ _maps = nullptr;
+}
+
+Common::Rect LabEngine::roomCoords(uint16 curRoom) {
+ Image *curRoomImg = nullptr;
+
+ switch (_maps[curRoom]._specialID) {
+ case kNormalRoom:
+ case kUpArrowRoom:
+ case kDownArrowRoom:
+ curRoomImg = _imgRoom;
+ break;
+ case kBridgeRoom:
+ curRoomImg = _imgBridge;
+ break;
+ case kVerticalCorridor:
+ curRoomImg = _imgVRoom;
+ break;
+ case kHorizontalCorridor:
+ curRoomImg = _imgHRoom;
+ break;
+ default:
+ // Some rooms (like the map) do not have an image
+ break;
+ }
+
+ int x1 = _utils->mapScaleX(_maps[curRoom]._x);
+ int y1 = _utils->mapScaleY(_maps[curRoom]._y);
+ int x2 = x1;
+ int y2 = y1;
+
+ if (curRoomImg) {
+ x2 += curRoomImg->_width;
+ y2 += curRoomImg->_height;
+ }
+
+ return Common::Rect(x1, y1, x2, y2);
+}
+
+void LabEngine::drawRoomMap(uint16 curRoom, bool drawMarkFl) {
+ uint16 drawX, drawY, offset;
+
+ uint16 x = _utils->mapScaleX(_maps[curRoom]._x);
+ uint16 y = _utils->mapScaleY(_maps[curRoom]._y);
+ uint32 flags = _maps[curRoom]._mapFlags;
+
+ switch (_maps[curRoom]._specialID) {
+ case kNormalRoom:
+ case kUpArrowRoom:
+ case kDownArrowRoom:
+ if (_maps[curRoom]._specialID == kNormalRoom)
+ _imgRoom->drawImage(x, y);
+ else if (_maps[curRoom]._specialID == kDownArrowRoom)
+ _imgDownArrowRoom->drawImage(x, y);
+ else
+ _imgUpArrowRoom->drawImage(x, y);
+
+ offset = (_imgRoom->_width - _imgPath->_width) / 2;
+
+ if ((kDoorLeftNorth & flags) && (y >= _imgPath->_height))
+ _imgPath->drawImage(x + offset, y - _imgPath->_height);
+
+ if (kDoorLeftSouth & flags)
+ _imgPath->drawImage(x + offset, y + _imgRoom->_height);
+
+ offset = (_imgRoom->_height - _imgPath->_height) / 2;
+
+ if (kDoorLeftEast & flags)
+ _imgPath->drawImage(x + _imgRoom->_width, y + offset);
+
+ if (kDoorLeftWest & flags)
+ _imgPath->drawImage(x - _imgPath->_width, y + offset);
+
+ drawX = x + (_imgRoom->_width - _imgMapX[_direction]->_width) / 2;
+ drawY = y + (_imgRoom->_height - _imgMapX[_direction]->_height) / 2;
+
+ break;
+
+ case kBridgeRoom:
+ _imgBridge->drawImage(x, y);
+
+ drawX = x + (_imgBridge->_width - _imgMapX[_direction]->_width) / 2;
+ drawY = y + (_imgBridge->_height - _imgMapX[_direction]->_height) / 2;
+
+ break;
+
+ case kVerticalCorridor:
+ _imgVRoom->drawImage(x, y);
+
+ offset = (_imgVRoom->_width - _imgPath->_width) / 2;
+
+ if (kDoorLeftNorth & flags)
+ _imgPath->drawImage(x + offset, y - _imgPath->_height);
+
+ if (kDoorLeftSouth & flags)
+ _imgPath->drawImage(x + offset, y + _imgVRoom->_height);
+
+ offset = (_imgRoom->_height - _imgPath->_height) / 2;
+
+ if (kDoorLeftEast & flags)
+ _imgPath->drawImage(x + _imgVRoom->_width, y + offset);
+
+ if (kDoorLeftWest & flags)
+ _imgPath->drawImage(x - _imgPath->_width, y + offset);
+
+ if (kDoorBottomEast & flags)
+ _imgPath->drawImage(x + _imgVRoom->_width, y - offset - _imgPath->_height + _imgVRoom->_height);
+
+ if (kDoorBottomWest & flags)
+ _imgPath->drawImage(x - _imgPath->_width, y - offset - _imgPath->_height + _imgVRoom->_height);
+
+ offset = (_imgVRoom->_height - _imgPath->_height) / 2;
+
+ if (kDoorMiddleEast & flags)
+ _imgPath->drawImage(x + _imgVRoom->_width, y - offset - _imgPath->_height + _imgVRoom->_height);
+
+ if (kDoorMiddleWest & flags)
+ _imgPath->drawImage(x - _imgPath->_width, y - offset - _imgPath->_height + _imgVRoom->_height);
+
+ drawX = x + (_imgVRoom->_width - _imgMapX[_direction]->_width) / 2;
+ drawY = y + (_imgVRoom->_height - _imgMapX[_direction]->_height) / 2;
+
+ break;
+
+ case kHorizontalCorridor:
+ _imgHRoom->drawImage(x, y);
+
+ offset = (_imgRoom->_width - _imgPath->_width) / 2;
+
+ if (kDoorLeftNorth & flags)
+ _imgPath->drawImage(x + offset, y - _imgPath->_height);
+
+ if (kDoorLeftSouth & flags)
+ _imgPath->drawImage(x + offset, y + _imgRoom->_height);
+
+ if (kDoorRightNorth & flags)
+ _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y - _imgPath->_height);
+
+ if (kDoorRightSouth & flags)
+ _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y + _imgRoom->_height);
+
+ offset = (_imgHRoom->_width - _imgPath->_width) / 2;
+
+ if (kDoorMiddleNorth & flags)
+ _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y - _imgPath->_height);
+
+ if (kDoorMiddleSouth & flags)
+ _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y + _imgRoom->_height);
+
+ offset = (_imgRoom->_height - _imgPath->_height) / 2;
+
+ if (kDoorLeftEast & flags)
+ _imgPath->drawImage(x + _imgHRoom->_width, y + offset);
+
+ if (kDoorLeftWest & flags)
+ _imgPath->drawImage(x - _imgPath->_width, y + offset);
+
+ drawX = x + (_imgHRoom->_width - _imgMapX[_direction]->_width) / 2;
+ drawY = y + (_imgHRoom->_height - _imgMapX[_direction]->_height) / 2;
+
+ break;
+
+ default:
+ return;
+ }
+
+ if (drawMarkFl)
+ _imgMapX[_direction]->drawImage(drawX, drawY);
+}
+
+bool LabEngine::floorVisited(uint16 floorNum) {
+ for (int i = 0; i < _maxRooms; i++) {
+ if ((_maps[i]._pageNumber == floorNum) && _roomsFound->in(i) && _maps[i]._x)
+ return true;
+ }
+
+ return false;
+}
+
+uint16 LabEngine::getUpperFloor(uint16 floorNum) {
+ if ((floorNum == kFloorCarnival) || (floorNum == kFloorNone))
+ return kFloorNone;
+
+ for (int i = floorNum; i < kFloorCarnival; i++)
+ if (floorVisited(i + 1))
+ return i + 1;
+
+ return kFloorNone;
+}
+
+uint16 LabEngine::getLowerFloor(uint16 floorNum) {
+ if ((floorNum == kFloorLower) || (floorNum == kFloorNone))
+ return kFloorNone;
+
+ for (int i = floorNum; i > kFloorLower; i--)
+ if (floorVisited(i - 1))
+ return i - 1;
+
+ return kFloorNone;
+}
+
+void LabEngine::drawMap(uint16 curRoom, uint16 curMsg, uint16 floorNum, bool fadeIn) {
+ _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1, 0);
+ _imgMap->drawImage(0, 0);
+ _event->drawButtonList(&_mapButtonList);
+
+ for (int i = 1; i <= _maxRooms; i++) {
+ if ((_maps[i]._pageNumber == floorNum) && _roomsFound->in(i) && _maps[i]._x) {
+ drawRoomMap(i, (bool)(i == curRoom));
+ }
+ }
+
+ updateEvents();
+
+ // Makes sure the X is drawn in corridors
+ // NOTE: this here on purpose just in case there's some weird
+ // condition, like the surreal maze where there are no rooms
+ if ((_maps[curRoom]._pageNumber == floorNum) && _roomsFound->in(curRoom) && _maps[curRoom]._x)
+ drawRoomMap(curRoom, true);
+
+ _event->toggleButton(_event->getButton(1), 12, (getUpperFloor(floorNum) != kFloorNone)); // up button
+ _event->toggleButton(_event->getButton(2), 12, (getLowerFloor(floorNum) != kFloorNone)); // down button
+
+ // Labyrinth specific code
+ if (floorNum == kFloorLower) {
+ if (floorVisited(kFloorSurMaze))
+ _imgMaze->drawImage(_utils->mapScaleX(538), _utils->mapScaleY(277));
+ } else if (floorNum == kFloorMiddle) {
+ if (floorVisited(kFloorCarnival))
+ _imgMaze->drawImage(_utils->mapScaleX(358), _utils->mapScaleY(72));
+
+ if (floorVisited(kFloorMedMaze))
+ _imgMaze->drawImage(_utils->mapScaleX(557), _utils->mapScaleY(325));
+ } else if (floorNum == kFloorUpper) {
+ if (floorVisited(kFloorHedgeMaze))
+ _imgHugeMaze->drawImage(_utils->mapScaleX(524), _utils->mapScaleY(97));
+ } else if (floorNum == kFloorSurMaze) {
+ Common::Rect textRect = Common::Rect(_utils->mapScaleX(360), 0, _utils->mapScaleX(660), _utils->mapScaleY(450));
+ const char *textPtr = _resource->getStaticText(kTextSurmazeMessage).c_str();
+ _graphics->flowText(_msgFont, 0, 7, 0, true, true, true, true, textRect, textPtr);
+ }
+
+ if ((floorNum >= kFloorLower) && (floorNum <= kFloorCarnival)) {
+ const char *textPrt = _resource->getStaticText(floorNum - 1).c_str();
+ _graphics->flowText(_msgFont, 0, 5, 3, true, true, true, true, _utils->vgaRectScale(14, 75, 134, 97), textPrt);
+ }
+
+ if (!_rooms[curMsg]._roomMsg.empty())
+ _graphics->flowText(_msgFont, 0, 5, 3, true, true, true, true, _utils->vgaRectScale(14, 148, 134, 186), _rooms[curMsg]._roomMsg.c_str());
+
+ if (fadeIn)
+ _graphics->fade(true);
+}
+
+void LabEngine::processMap(uint16 curRoom) {
+ byte place = 1;
+ uint16 curMsg = curRoom;
+ uint16 curFloor = _maps[curRoom]._pageNumber;
+
+ while (1) {
+ // Make sure we check the music at least after every message
+ updateEvents();
+ IntuiMessage *msg = _event->getMsg();
+ if (shouldQuit()) {
+ _quitLab = true;
+ return;
+ }
+
+ if (!msg) {
+ updateEvents();
+
+ byte newcolor[3];
+
+ if (place <= 14) {
+ newcolor[0] = 14 << 2;
+ newcolor[1] = place << 2;
+ newcolor[2] = newcolor[1];
+ } else {
+ newcolor[0] = 14 << 2;
+ newcolor[1] = (28 - place) << 2;
+ newcolor[2] = newcolor[1];
+ }
+
+ waitTOF();
+ _graphics->writeColorRegs(newcolor, 1, 1);
+ _event->updateMouse();
+ waitTOF();
+
+ place++;
+
+ if (place >= 28)
+ place = 1;
+
+ } else {
+ uint32 msgClass = msg->_msgClass;
+ uint16 msgCode = msg->_code;
+ uint16 mouseX = msg->_mouse.x;
+ uint16 mouseY = msg->_mouse.y;
+
+ if ((msgClass == kMessageRightClick) || ((msgClass == kMessageRawKey) && (msgCode == Common::KEYCODE_ESCAPE)))
+ return;
+
+ if (msgClass == kMessageButtonUp) {
+ if (msgCode == 0) {
+ // Quit menu button
+ return;
+ } else if (msgCode == 1) {
+ // Up arrow
+ uint16 upperFloor = getUpperFloor(curFloor);
+ if (upperFloor != kFloorNone) {
+ curFloor = upperFloor;
+ _graphics->fade(false);
+ drawMap(curRoom, curMsg, curFloor, false);
+ _graphics->fade(true);
+ }
+ } else if (msgCode == 2) {
+ // Down arrow
+ uint16 lowerFloor = getLowerFloor(curFloor);
+ if (lowerFloor != kFloorNone) {
+ curFloor = lowerFloor;
+ _graphics->fade(false);
+ drawMap(curRoom, curMsg, curFloor, false);
+ _graphics->fade(true);
+ }
+ }
+ } else if (msgClass == kMessageLeftClick) {
+ if ((curFloor == kFloorLower) && _utils->mapRectScale(538, 277, 633, 352).contains(mouseX, mouseY)
+ && floorVisited(kFloorSurMaze)) {
+ curFloor = kFloorSurMaze;
+
+ _graphics->fade(false);
+ drawMap(curRoom, curMsg, curFloor, false);
+ _graphics->fade(true);
+ } else if ((curFloor == kFloorMiddle) && _utils->mapRectScale(358, 71, 452, 147).contains(mouseX, mouseY)
+ && floorVisited(kFloorCarnival)) {
+ curFloor = kFloorCarnival;
+
+ _graphics->fade(false);
+ drawMap(curRoom, curMsg, curFloor, false);
+ _graphics->fade(true);
+ } else if ((curFloor == kFloorMiddle) && _utils->mapRectScale(557, 325, 653, 401).contains(mouseX, mouseY)
+ && floorVisited(kFloorMedMaze)) {
+ curFloor = kFloorMedMaze;
+
+ _graphics->fade(false);
+ drawMap(curRoom, curMsg, curFloor, false);
+ _graphics->fade(true);
+ } else if ((curFloor == kFloorUpper) && _utils->mapRectScale(524, 97, 645, 207).contains(mouseX, mouseY)
+ && floorVisited(kFloorHedgeMaze)) {
+ curFloor = kFloorHedgeMaze;
+
+ _graphics->fade(false);
+ drawMap(curRoom, curMsg, curFloor, false);
+ _graphics->fade(true);
+ } else if (mouseX > _utils->mapScaleX(314)) {
+ uint16 oldMsg = curMsg;
+ Common::Rect curCoords;
+
+ for (int i = 1; i <= _maxRooms; i++) {
+ curCoords = roomCoords(i);
+
+ if ((_maps[i]._pageNumber == curFloor)
+ && _roomsFound->in(i) && curCoords.contains(Common::Point(mouseX, mouseY))) {
+ curMsg = i;
+ }
+ }
+
+ if (oldMsg != curMsg) {
+ if (!_rooms[curMsg]._roomMsg.empty())
+ _resource->readViews(curMsg);
+
+ const char *sptr;
+ if ((sptr = _rooms[curMsg]._roomMsg.c_str())) {
+ _graphics->rectFillScaled(13, 148, 135, 186, 3);
+ _graphics->flowText(_msgFont, 0, 5, 3, true, true, true, true, _utils->vgaRectScale(14, 148, 134, 186), sptr);
+
+ if (_maps[oldMsg]._pageNumber == curFloor)
+ drawRoomMap(oldMsg, (bool)(oldMsg == curRoom));
+
+ curCoords = roomCoords(curMsg);
+ int right = (curCoords.left + curCoords.right) / 2;
+ int left = right - 1;
+ int top, bottom;
+ top = bottom = (curCoords.top + curCoords.bottom) / 2;
+
+ if ((curMsg != curRoom) && (_maps[curMsg]._pageNumber == curFloor))
+ _graphics->rectFill(left, top, right, bottom, 1);
+ }
+ }
+ }
+ }
+
+ _graphics->screenUpdate();
+ }
+ }
+}
+
+void LabEngine::doMap(uint16 curRoom) {
+ static uint16 amigaMapPalette[] = {
+ 0x0BA8, 0x0C11, 0x0A74, 0x0076,
+ 0x0A96, 0x0DCB, 0x0CCA, 0x0222,
+ 0x0444, 0x0555, 0x0777, 0x0999,
+ 0x0AAA, 0x0ED0, 0x0EEE, 0x0694
+ };
+
+ _graphics->_fadePalette = amigaMapPalette;
+
+ updateEvents();
+ loadMapData();
+ _graphics->blackAllScreen();
+ _event->attachButtonList(&_mapButtonList);
+ drawMap(curRoom, curRoom, _maps[curRoom]._pageNumber, true);
+ _event->mouseShow();
+ _graphics->screenUpdate();
+ processMap(curRoom);
+ _event->attachButtonList(nullptr);
+ _graphics->fade(false);
+ _graphics->blackAllScreen();
+ _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1, 0);
+ freeMapData();
+ _graphics->blackAllScreen();
+ _graphics->screenUpdate();
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/module.mk b/engines/lab/module.mk
new file mode 100644
index 0000000000..7bb86c8c1e
--- /dev/null
+++ b/engines/lab/module.mk
@@ -0,0 +1,30 @@
+MODULE := engines/lab
+
+MODULE_OBJS := \
+ anim.o \
+ console.o \
+ detection.o \
+ dispman.o \
+ engine.o \
+ eventman.o \
+ image.o \
+ interface.o \
+ intro.o \
+ lab.o \
+ labsets.o \
+ map.o \
+ music.o \
+ processroom.o \
+ resource.o \
+ savegame.o \
+ special.o \
+ speciallocks.o \
+ utils.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_LAB), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/lab/music.cpp b/engines/lab/music.cpp
new file mode 100644
index 0000000000..8045c51044
--- /dev/null
+++ b/engines/lab/music.cpp
@@ -0,0 +1,178 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "audio/decoders/raw.h"
+
+#include "lab/lab.h"
+
+#include "lab/anim.h"
+#include "lab/eventman.h"
+#include "lab/music.h"
+#include "lab/resource.h"
+
+namespace Lab {
+
+#define SAMPLESPEED 15000
+#define CLOWNROOM 123
+#define DIMROOM 80
+
+Music::Music(LabEngine *vm) : _vm(vm) {
+ _musicFile = nullptr;
+ _curRoomMusic = 1;
+ _storedPos = 0;
+}
+
+byte Music::getSoundFlags() {
+ byte soundFlags = Audio::FLAG_LITTLE_ENDIAN;
+ if (_vm->getPlatform() == Common::kPlatformWindows)
+ soundFlags |= Audio::FLAG_16BITS;
+ else if (_vm->getPlatform() == Common::kPlatformDOS)
+ soundFlags |= Audio::FLAG_UNSIGNED;
+
+ return soundFlags;
+}
+
+void Music::changeMusic(const Common::String filename, bool storeCurPos, bool seektoStoredPos) {
+ if (storeCurPos)
+ _storedPos = _musicFile->pos();
+
+ stopSoundEffect();
+ freeMusic();
+ _musicFile = _vm->_resource->openDataFile(filename);
+ if (seektoStoredPos)
+ _musicFile->seek(_storedPos);
+
+ Audio::SeekableAudioStream *audioStream = Audio::makeRawStream(_musicFile, SAMPLESPEED, getSoundFlags());
+ _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, new Audio::LoopingAudioStream(audioStream, 0));
+}
+
+void Music::playSoundEffect(uint16 sampleSpeed, uint32 length, bool loop, Common::File *dataFile) {
+ stopSoundEffect();
+
+ // NOTE: We need to use malloc(), cause this will be freed with free()
+ // by the music code
+ byte *soundData = (byte *)malloc(length);
+ dataFile->read(soundData, length);
+
+ Audio::SeekableAudioStream *audioStream = Audio::makeRawStream((const byte *)soundData, length, MAX<uint16>(sampleSpeed, 4000), getSoundFlags());
+ _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, new Audio::LoopingAudioStream(audioStream, (loop) ? 0 : 1));
+}
+
+void Music::stopSoundEffect() {
+ if (isSoundEffectActive())
+ _vm->_mixer->stopHandle(_sfxHandle);
+}
+
+bool Music::isSoundEffectActive() const {
+ return _vm->_mixer->isSoundHandleActive(_sfxHandle);
+}
+
+void Music::freeMusic() {
+ _vm->_mixer->stopHandle(_musicHandle);
+ _vm->_mixer->stopHandle(_sfxHandle);
+ _musicFile = nullptr;
+}
+
+void Music::checkRoomMusic() {
+ if ((_curRoomMusic == _vm->_roomNum) || !_musicFile)
+ return;
+
+ if (_vm->_roomNum == CLOWNROOM) {
+ changeMusic("Music:Laugh", true, false);
+ } else if (_vm->_roomNum == DIMROOM) {
+ changeMusic("Music:Rm81", true, false);
+ } else if (_curRoomMusic == CLOWNROOM || _curRoomMusic == DIMROOM) {
+ if (_vm->getPlatform() != Common::kPlatformAmiga)
+ changeMusic("Music:Backgrou", false, true);
+ else
+ changeMusic("Music:Background", false, true);
+ }
+
+ _curRoomMusic = _vm->_roomNum;
+}
+
+bool Music::loadSoundEffect(const Common::String filename, bool loop, bool waitTillFinished) {
+ Common::File *file = _vm->_resource->openDataFile(filename, MKTAG('D', 'I', 'F', 'F'));
+ stopSoundEffect();
+
+ if (!file)
+ return false;
+
+ _vm->_anim->_doBlack = false;
+ readSound(waitTillFinished, loop, file);
+
+ return true;
+}
+
+void Music::readSound(bool waitTillFinished, bool loop, Common::File *file) {
+ uint32 magicBytes = file->readUint32LE();
+ if (magicBytes != 1219009121) {
+ warning("readSound: Bad signature, skipping");
+ return;
+ }
+ uint32 soundTag = file->readUint32LE();
+ uint32 soundSize = file->readUint32LE();
+
+ if (soundTag == 0)
+ file->skip(soundSize); // skip the header
+ else
+ return;
+
+ while (soundTag != 65535) {
+ _vm->updateEvents();
+ soundTag = file->readUint32LE();
+ soundSize = file->readUint32LE() - 8;
+
+ if ((soundTag == 30) || (soundTag == 31)) {
+ if (waitTillFinished) {
+ while (isSoundEffectActive()) {
+ _vm->updateEvents();
+ _vm->waitTOF();
+ }
+ }
+
+ file->skip(4);
+
+ uint16 sampleRate = file->readUint16LE();
+ file->skip(2);
+ playSoundEffect(sampleRate, soundSize, loop, file);
+ } else if (soundTag == 65535) {
+ if (waitTillFinished) {
+ while (isSoundEffectActive()) {
+ _vm->updateEvents();
+ _vm->waitTOF();
+ }
+ }
+ } else
+ file->skip(soundSize);
+ }
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/music.h b/engines/lab/music.h
new file mode 100644
index 0000000000..09bb9694ac
--- /dev/null
+++ b/engines/lab/music.h
@@ -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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_MUSIC_H
+#define LAB_MUSIC_H
+
+#include "common/file.h"
+#include "audio/mixer.h"
+#include "audio/audiostream.h"
+
+namespace Lab {
+
+class LabEngine;
+
+//---------------------------
+//----- From LabMusic.c -----
+//---------------------------
+
+#define MAXBUFFERS 5
+
+class Music {
+private:
+ LabEngine *_vm;
+
+ Common::File *_musicFile;
+ uint16 _curRoomMusic;
+ uint32 _storedPos;
+
+ Audio::SoundHandle _musicHandle;
+ Audio::SoundHandle _sfxHandle;
+
+private:
+ void readSound(bool waitTillFinished, bool loop, Common::File *file);
+ byte getSoundFlags();
+
+public:
+ Music(LabEngine *vm);
+
+ /**
+ * Changes the background music to something else.
+ */
+ void changeMusic(const Common::String filename, bool storeCurPos, bool seektoStoredPos);
+
+ /**
+ * Checks the music that should be playing in a particular room.
+ */
+ void checkRoomMusic();
+
+ /**
+ * Frees up the music buffers and closes the file.
+ */
+ void freeMusic();
+
+ bool isSoundEffectActive() const;
+ void playSoundEffect(uint16 sampleSpeed, uint32 length, bool loop, Common::File *dataFile);
+
+ /**
+ * Reads in a sound effect file. Ignores any graphics.
+ */
+ bool loadSoundEffect(const Common::String filename, bool loop, bool waitTillFinished);
+
+ void stopSoundEffect();
+};
+
+} // End of namespace Lab
+
+#endif // LAB_MUSIC_H
diff --git a/engines/lab/processroom.cpp b/engines/lab/processroom.cpp
new file mode 100644
index 0000000000..491cdf39da
--- /dev/null
+++ b/engines/lab/processroom.cpp
@@ -0,0 +1,623 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "gui/message.h"
+
+#include "lab/lab.h"
+
+#include "lab/anim.h"
+#include "lab/dispman.h"
+#include "lab/labsets.h"
+#include "lab/music.h"
+#include "lab/processroom.h"
+#include "lab/resource.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+#define NOFILE "no file"
+
+bool LabEngine::checkConditions(const Common::Array<int16> &condition) {
+ for (unsigned int i = 0; i < condition.size(); ++i)
+ if (!_conditions->in(condition[i]))
+ return false;
+
+ return true;
+}
+
+ViewData *LabEngine::getViewData(uint16 roomNum, uint16 direction) {
+ if (_rooms[roomNum]._roomMsg.empty())
+ _resource->readViews(roomNum);
+
+ ViewDataList &views = _rooms[roomNum]._view[direction];
+ ViewDataList::iterator view;
+
+ for (view = views.begin(); view != views.end(); ++view) {
+ if (checkConditions(view->_condition))
+ return &(*view);
+ }
+
+ error("No view with matching condition found");
+}
+
+const CloseData *LabEngine::getObject(Common::Point pos, const CloseData *closePtr) {
+ const CloseDataList *list;
+ if (!closePtr)
+ list = &(getViewData(_roomNum, _direction)->_closeUps);
+ else
+ list = &(closePtr->_subCloseUps);
+
+ CloseDataList::const_iterator wrkClosePtr;
+
+ for (wrkClosePtr = list->begin(); wrkClosePtr != list->end(); ++wrkClosePtr) {
+ Common::Rect objRect;
+ objRect = _utils->rectScale(wrkClosePtr->_x1, wrkClosePtr->_y1, wrkClosePtr->_x2, wrkClosePtr->_y2);
+ if (objRect.contains(pos))
+ return &(*wrkClosePtr);
+ }
+
+ return nullptr;
+}
+
+const CloseData *LabEngine::findClosePtrMatch(const CloseData *closePtr, const CloseDataList &list) {
+ CloseDataList::const_iterator i;
+
+ for (i = list.begin(); i != list.end(); ++i) {
+ if ((closePtr->_x1 == i->_x1) && (closePtr->_x2 == i->_x2) &&
+ (closePtr->_y1 == i->_y1) && (closePtr->_y2 == i->_y2) &&
+ (closePtr->_depth == i->_depth))
+ return &(*i);
+
+ const CloseData *resClosePtr = findClosePtrMatch(closePtr, i->_subCloseUps);
+
+ if (resClosePtr)
+ return resClosePtr;
+ }
+
+ return nullptr;
+}
+
+Common::String LabEngine::getPictName(bool useClose) {
+ ViewData *viewPtr = getViewData(_roomNum, _direction);
+
+ if (useClose && _closeDataPtr) {
+ _closeDataPtr = findClosePtrMatch(_closeDataPtr, viewPtr->_closeUps);
+
+ if (_closeDataPtr)
+ return _closeDataPtr->_graphicName;
+ }
+
+ return viewPtr->_graphicName;
+}
+
+void LabEngine::drawDirection(const CloseData *closePtr) {
+ if (closePtr && !closePtr->_message.empty()) {
+ _graphics->drawMessage(closePtr->_message, false);
+ return;
+ }
+
+ Common::String message;
+
+ if (!_rooms[_roomNum]._roomMsg.empty())
+ message = _rooms[_roomNum]._roomMsg + ", ";
+
+ if (_direction == kDirectionNorth)
+ message += _resource->getStaticText(kTextFacingNorth);
+ else if (_direction == kDirectionEast)
+ message += _resource->getStaticText(kTextFacingEast);
+ else if (_direction == kDirectionSouth)
+ message += _resource->getStaticText(kTextFacingSouth);
+ else if (_direction == kDirectionWest)
+ message += _resource->getStaticText(kTextFacingWest);
+
+ _graphics->drawMessage(message, false);
+}
+
+uint16 LabEngine::processArrow(uint16 curDirection, uint16 arrow) {
+ if (arrow == 1) { // Forward
+ uint16 room = _rooms[_roomNum]._doors[curDirection];
+ if (room != 0)
+ _roomNum = room;
+
+ return curDirection;
+ } else if (arrow == 0) { // Left
+ if (curDirection == kDirectionNorth)
+ return kDirectionWest;
+ else if (curDirection == kDirectionWest)
+ return kDirectionSouth;
+ else if (curDirection == kDirectionSouth)
+ return kDirectionEast;
+ else
+ return kDirectionNorth;
+ } else if (arrow == 2) { // Right
+ if (curDirection == kDirectionNorth)
+ return kDirectionEast;
+ else if (curDirection == kDirectionEast)
+ return kDirectionSouth;
+ else if (curDirection == kDirectionSouth)
+ return kDirectionWest;
+ else
+ return kDirectionNorth;
+ }
+
+ // Should never reach here!
+ return curDirection;
+}
+
+void LabEngine::setCurrentClose(Common::Point pos, const CloseData **closePtrList, bool useAbsoluteCoords, bool next) {
+ const CloseDataList *list;
+
+ if (!*closePtrList)
+ list = &(getViewData(_roomNum, _direction)->_closeUps);
+ else
+ list = &((*closePtrList)->_subCloseUps);
+
+ CloseDataList::const_iterator closePtr;
+ for (closePtr = list->begin(); closePtr != list->end(); ++closePtr) {
+ Common::Rect target;
+ if (!useAbsoluteCoords)
+ target = Common::Rect(closePtr->_x1, closePtr->_y1, closePtr->_x2, closePtr->_y2);
+ else
+ target = _utils->rectScale(closePtr->_x1, closePtr->_y1, closePtr->_x2, closePtr->_y2);
+
+ if (target.contains(pos) && (next || !closePtr->_graphicName.empty())) {
+
+ if (next) {
+ // cycle to the next one
+ ++closePtr;
+ if (closePtr == list->end())
+ closePtr = list->begin();
+ }
+ *closePtrList = &(*closePtr);
+
+ return;
+ }
+ }
+
+ // If we got here, no match was found. If we want the "next" close-up,
+ // return the first one in the list, if any.
+ if (next) {
+ if (!list->empty())
+ *closePtrList = &(*list->begin());
+ }
+}
+
+bool LabEngine::takeItem(Common::Point pos) {
+ const CloseDataList *list;
+ if (!_closeDataPtr) {
+ list = &(getViewData(_roomNum, _direction)->_closeUps);
+ } else if (_closeDataPtr->_closeUpType < 0) {
+ _conditions->inclElement(abs(_closeDataPtr->_closeUpType));
+ return true;
+ } else
+ list = &(_closeDataPtr->_subCloseUps);
+
+ CloseDataList::const_iterator closePtr;
+ for (closePtr = list->begin(); closePtr != list->end(); ++closePtr) {
+ Common::Rect objRect;
+ objRect = _utils->rectScale(closePtr->_x1, closePtr->_y1, closePtr->_x2, closePtr->_y2);
+ if (objRect.contains(pos) && (closePtr->_closeUpType < 0)) {
+ _conditions->inclElement(abs(closePtr->_closeUpType));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void LabEngine::doActions(const ActionList &actionList) {
+ ActionList::const_iterator action;
+ for (action = actionList.begin(); action != actionList.end(); ++action) {
+ updateEvents();
+
+ switch (action->_actionType) {
+ case kActionPlaySound:
+ _music->loadSoundEffect(action->_messages[0], false, true);
+ break;
+
+ case kActionPlaySoundNoWait: // only used in scene 7 (street, when teleporting to the surreal maze)
+ _music->loadSoundEffect(action->_messages[0], false, false);
+ break;
+
+ case kActionPlaySoundLooping:
+ _music->loadSoundEffect(action->_messages[0], true, false);
+ break;
+
+ case kActionShowDiff:
+ _graphics->readPict(action->_messages[0], true);
+ break;
+
+ case kActionShowDiffLooping: // used in scene 44 (heart of the labyrinth, minotaur)
+ _graphics->readPict(action->_messages[0], false);
+ break;
+
+ case kActionLoadDiff:
+ if (!action->_messages[0].empty())
+ // Puts a file into memory
+ _graphics->loadPict(action->_messages[0]);
+ break;
+
+ case kActionLoadBitmap:
+ error("Unused opcode kActionLoadBitmap has been called");
+
+ case kActionShowBitmap:
+ error("Unused opcode kActionShowBitmap has been called");
+
+ case kActionTransition:
+ _graphics->doTransition((TransitionType)action->_param1, action->_messages[0].c_str());
+ break;
+
+ case kActionNoUpdate:
+ _noUpdateDiff = true;
+ _anim->_doBlack = false;
+ break;
+
+ case kActionForceUpdate:
+ _curFileName = " ";
+ break;
+
+ case kActionShowCurPict: {
+ Common::String test = getPictName(true);
+
+ if (test != _curFileName) {
+ _curFileName = test;
+ _graphics->readPict(_curFileName);
+ }
+ }
+ break;
+
+ case kActionSetElement:
+ _conditions->inclElement(action->_param1);
+ break;
+
+ case kActionUnsetElement:
+ _conditions->exclElement(action->_param1);
+ break;
+
+ case kActionShowMessage:
+ if (_graphics->_longWinInFront)
+ _graphics->longDrawMessage(action->_messages[0], true);
+ else
+ _graphics->drawMessage(action->_messages[0], true);
+ break;
+
+ case kActionCShowMessage:
+ if (!_closeDataPtr)
+ _graphics->drawMessage(action->_messages[0], true);
+ break;
+
+ case kActionShowMessages:
+ _graphics->drawMessage(action->_messages[_utils->getRandom(action->_param1)], true);
+ break;
+
+ case kActionChangeRoom:
+ if (action->_param1 & 0x8000) {
+ // This is a Wyrmkeep Windows trial version, thus stop at this
+ // point, since we can't check for game payment status
+ _graphics->readPict(getPictName(true));
+ GUI::MessageDialog trialMessage("This is the end of the trial version. You can play the full game using the original interpreter from Wyrmkeep");
+ trialMessage.runModal();
+ break;
+ }
+
+ _roomNum = action->_param1;
+ _direction = action->_param2 - 1;
+ _closeDataPtr = nullptr;
+ _anim->_doBlack = true;
+ break;
+
+ case kActionSetCloseup: {
+ Common::Point curPos = Common::Point(_utils->scaleX(action->_param1), _utils->scaleY(action->_param2));
+ const CloseData *tmpClosePtr = getObject(curPos, _closeDataPtr);
+
+ if (tmpClosePtr)
+ _closeDataPtr = tmpClosePtr;
+ }
+ break;
+
+ case kActionMainView:
+ _closeDataPtr = nullptr;
+ break;
+
+ case kActionSubInv:
+ if (_inventory[action->_param1]._quantity)
+ (_inventory[action->_param1]._quantity)--;
+
+ if (_inventory[action->_param1]._quantity == 0)
+ _conditions->exclElement(action->_param1);
+
+ break;
+
+ case kActionAddInv:
+ (_inventory[action->_param1]._quantity) += action->_param2;
+ _conditions->inclElement(action->_param1);
+ break;
+
+ case kActionShowDir:
+ _graphics->setActionMessage(false);
+ break;
+
+ case kActionWaitSecs: {
+ uint32 targetMillis = _system->getMillis() + action->_param1 * 1000;
+
+ _graphics->screenUpdate();
+
+ while (_system->getMillis() < targetMillis) {
+ updateEvents();
+ _anim->diffNextFrame();
+ }
+ }
+ break;
+
+ case kActionStopMusic: // used in scene 44 (heart of the labyrinth, minotaur)
+ _music->freeMusic();
+ break;
+
+ case kActionStartMusic: // unused
+ error("Unused opcode kActionStartMusic has been called");
+ break;
+
+ case kActionChangeMusic: // used in scene 46 (museum exhibit, for the alarm)
+ _music->changeMusic(action->_messages[0], true, false);
+ break;
+
+ case kActionResetMusic: // used in scene 45 (sheriff's office, after museum)
+ if (getPlatform() != Common::kPlatformAmiga)
+ _music->changeMusic("Music:BackGrou", false, true);
+ else
+ _music->changeMusic("Music:BackGround", false, true);
+ break;
+
+ case kActionFillMusic:
+ error("Unused opcode kActionFillMusic has been called");
+ break;
+
+ case kActionWaitSound: // used in scene 44 (heart of the labyrinth / ending)
+ while (_music->isSoundEffectActive()) {
+ updateEvents();
+ _anim->diffNextFrame();
+ waitTOF();
+ }
+ break;
+
+ case kActionClearSound:
+ _music->stopSoundEffect();
+ break;
+
+ case kActionWinMusic: // used in scene 44 (heart of the labyrinth / ending)
+ _music->freeMusic();
+ _music->changeMusic("Music:WinGame", false, false);
+ break;
+
+ case kActionWinGame: // used in scene 44 (heart of the labyrinth / ending)
+ _quitLab = true;
+ showLab2Teaser();
+ break;
+
+ case kActionLostGame:
+ error("Unused opcode kActionLostGame has been called");
+
+ case kActionResetBuffer:
+ _graphics->freePict();
+ break;
+
+ case kActionSpecialCmd:
+ if (action->_param1 == 0)
+ _anim->_doBlack = true;
+ else if (action->_param1 == 1)
+ _anim->_doBlack = (_closeDataPtr == nullptr);
+ else if (action->_param1 == 2)
+ _anim->_doBlack = (_closeDataPtr != nullptr);
+ else if (action->_param1 == 5) {
+ // inverse the palette
+ for (int idx = (8 * 3); idx < (255 * 3); idx++)
+ _anim->_diffPalette[idx] = 255 - _anim->_diffPalette[idx];
+
+ waitTOF();
+ _graphics->setPalette(_anim->_diffPalette, 256);
+ waitTOF();
+ waitTOF();
+ } else if (action->_param1 == 4) {
+ // white the palette
+ _graphics->whiteScreen();
+ waitTOF();
+ waitTOF();
+ } else if (action->_param1 == 6) {
+ // Restore the palette
+ waitTOF();
+ _graphics->setPalette(_anim->_diffPalette, 256);
+ waitTOF();
+ waitTOF();
+ } else if (action->_param1 == 7) {
+ // Quick pause
+ waitTOF();
+ waitTOF();
+ waitTOF();
+ }
+
+ break;
+ }
+ }
+
+ _music->stopSoundEffect();
+}
+
+bool LabEngine::doActionRuleSub(int16 action, int16 roomNum, const CloseData *closePtr, bool allowDefaults) {
+ action++;
+
+ if (closePtr) {
+ RuleList *rules = &(_rooms[_roomNum]._rules);
+
+ if (!rules && (roomNum == 0)) {
+ _resource->readViews(roomNum);
+ rules = &(_rooms[roomNum]._rules);
+ }
+
+ for (RuleList::iterator rule = rules->begin(); rule != rules->end(); ++rule) {
+ if ((rule->_ruleType == kRuleTypeAction) &&
+ ((rule->_param1 == action) || ((rule->_param1 == 0) && allowDefaults))) {
+ if (((rule->_param2 == closePtr->_closeUpType) ||
+ ((rule->_param2 == 0) && allowDefaults)) ||
+ ((action == 1) && (rule->_param2 == -closePtr->_closeUpType))) {
+ if (checkConditions(rule->_condition)) {
+ doActions(rule->_actionList);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool LabEngine::doActionRule(Common::Point pos, int16 action, int16 roomNum) {
+ if (roomNum)
+ _newFileName = NOFILE;
+ else
+ _newFileName = _curFileName;
+
+ const CloseData *curClosePtr = getObject(pos, _closeDataPtr);
+
+ if (doActionRuleSub(action, roomNum, curClosePtr, false))
+ return true;
+ else if (doActionRuleSub(action, roomNum, _closeDataPtr, false))
+ return true;
+ else if (doActionRuleSub(action, roomNum, curClosePtr, true))
+ return true;
+ else if (doActionRuleSub(action, roomNum, _closeDataPtr, true))
+ return true;
+
+ return false;
+}
+
+bool LabEngine::doOperateRuleSub(int16 itemNum, int16 roomNum, const CloseData *closePtr, bool allowDefaults) {
+ if (closePtr)
+ if (closePtr->_closeUpType > 0) {
+ RuleList *rules = &(_rooms[roomNum]._rules);
+
+ if (!rules && (roomNum == 0)) {
+ _resource->readViews(roomNum);
+ rules = &(_rooms[roomNum]._rules);
+ }
+
+ for (RuleList::iterator rule = rules->begin(); rule != rules->end(); ++rule) {
+ if ((rule->_ruleType == kRuleTypeOperate) &&
+ ((rule->_param1 == itemNum) || ((rule->_param1 == 0) && allowDefaults)) &&
+ ((rule->_param2 == closePtr->_closeUpType) || ((rule->_param2 == 0) && allowDefaults))) {
+ if (checkConditions(rule->_condition)) {
+ doActions(rule->_actionList);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool LabEngine::doOperateRule(Common::Point pos, int16 ItemNum) {
+ _newFileName = NOFILE;
+ const CloseData *closePtr = getObject(pos, _closeDataPtr);
+
+ if (doOperateRuleSub(ItemNum, _roomNum, closePtr, false))
+ return true;
+ else if (doOperateRuleSub(ItemNum, _roomNum, _closeDataPtr, false))
+ return true;
+ else if (doOperateRuleSub(ItemNum, _roomNum, closePtr, true))
+ return true;
+ else if (doOperateRuleSub(ItemNum, _roomNum, _closeDataPtr, true))
+ return true;
+ else {
+ _newFileName = _curFileName;
+
+ if (doOperateRuleSub(ItemNum, 0, closePtr, false))
+ return true;
+ else if (doOperateRuleSub(ItemNum, 0, _closeDataPtr, false))
+ return true;
+ else if (doOperateRuleSub(ItemNum, 0, closePtr, true))
+ return true;
+ else if (doOperateRuleSub(ItemNum, 0, _closeDataPtr, true))
+ return true;
+ }
+
+ return false;
+}
+
+bool LabEngine::doGoForward() {
+ RuleList &rules = _rooms[_roomNum]._rules;
+
+ for (RuleList::iterator rule = rules.begin(); rule != rules.end(); ++rule) {
+ if ((rule->_ruleType == kRuleTypeGoForward) && (rule->_param1 == (_direction + 1))) {
+ if (checkConditions(rule->_condition)) {
+ doActions(rule->_actionList);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool LabEngine::doTurn(uint16 from, uint16 to) {
+ from++;
+ to++;
+
+ RuleList &rules = _rooms[_roomNum]._rules;
+
+ for (RuleList::iterator rule = rules.begin(); rule != rules.end(); ++rule) {
+ if ((rule->_ruleType == kRuleTypeTurn) ||
+ ((rule->_ruleType == kRuleTypeTurnFromTo) &&
+ (rule->_param1 == from) && (rule->_param2 == to))) {
+ if (checkConditions(rule->_condition)) {
+ doActions(rule->_actionList);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool LabEngine::doMainView() {
+ RuleList &rules = _rooms[_roomNum]._rules;
+ for (RuleList::iterator rule = rules.begin(); rule != rules.end(); ++rule) {
+ if (rule->_ruleType == kRuleTypeGoMainView) {
+ if (checkConditions(rule->_condition)) {
+ doActions(rule->_actionList);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/processroom.h b/engines/lab/processroom.h
new file mode 100644
index 0000000000..1d53ce01af
--- /dev/null
+++ b/engines/lab/processroom.h
@@ -0,0 +1,190 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_PROCESSROOM_H
+#define LAB_PROCESSROOM_H
+
+namespace Lab {
+
+enum ActionType {
+ kActionPlaySound = 1,
+ kActionPlaySoundLooping = 2,
+ kActionShowDiff = 3,
+ kActionShowDiffLooping = 4,
+ kActionLoadDiff = 5,
+ kActionLoadBitmap = 6, // unused
+ kActionShowBitmap = 7, // unused
+ kActionTransition = 8,
+ kActionNoUpdate = 9,
+ kActionForceUpdate = 10,
+ kActionShowCurPict = 11,
+ kActionSetElement = 12,
+ kActionUnsetElement = 13,
+ kActionShowMessage = 14,
+ kActionShowMessages = 15,
+ kActionChangeRoom = 16,
+ kActionSetCloseup = 17,
+ kActionMainView = 18,
+ kActionSubInv = 19,
+ kActionAddInv = 20,
+ kActionShowDir = 21,
+ kActionWaitSecs = 22,
+ kActionStopMusic = 23,
+ kActionStartMusic = 24,
+ kActionChangeMusic = 25,
+ kActionResetMusic = 26,
+ kActionFillMusic = 27,
+ kActionWaitSound = 28,
+ kActionClearSound = 29,
+ kActionWinMusic = 30,
+ kActionWinGame = 31,
+ kActionLostGame = 32, // unused
+ kActionResetBuffer = 33,
+ kActionSpecialCmd = 34,
+ kActionCShowMessage = 35,
+ kActionPlaySoundNoWait = 36
+};
+
+enum RuleType {
+ kRuleTypeNone = 0,
+ kRuleTypeAction = 1,
+ kRuleTypeOperate = 2,
+ kRuleTypeGoForward = 3,
+ kRuleTypeConditions = 4, // unused?
+ kRuleTypeTurn = 5,
+ kRuleTypeGoMainView = 6,
+ kRuleTypeTurnFromTo = 7
+};
+
+enum RuleAction {
+ kRuleActionTake = 0,
+ kRuleActionMove = 1, // unused?
+ kRuleActionOpenDoor = 2, // unused?
+ kRuleActionCloseDoor = 3, // unused?
+ kRuleActionTakeDef = 4
+};
+
+enum Condition {
+ kCondBeltGlowing = 70,
+ kCondBridge1 = 104,
+ kCondNoNews = 135,
+ kCondBridge0 = 148,
+ kCondLampOn = 151,
+ kCondNoClean = 152,
+ kCondDirty = 175,
+ kCondUsedHelmet = 184
+};
+
+enum MapDoors {
+ kDoorLeftNorth = 1,
+ kDoorLeftEast = 2,
+ kDoorLeftSouth = 4,
+ kDoorLeftWest = 8,
+
+ kDoorMiddleNorth = 16,
+ kDoorRightNorth = 32,
+ kDoorMiddleSouth = 64,
+ kDoorRightSouth = 128,
+
+ kDoorMiddleEast = 16,
+ kDoorBottomEast = 32,
+ kDoorMiddleWest = 64,
+ kDoorBottomWest = 128
+};
+
+enum SpecialRoom {
+ kNormalRoom = 0,
+ kUpArrowRoom,
+ kDownArrowRoom,
+ kBridgeRoom,
+ kVerticalCorridor,
+ kHorizontalCorridor,
+ kMedMaze,
+ kHedgeMaze,
+ kSurMaze,
+ kMultiMazeF1,
+ kMultiMazeF2,
+ kMultiMazeF3
+};
+
+struct CloseData {
+ uint16 _x1, _y1, _x2, _y2;
+ int16 _closeUpType; // if > 0, an object. If < 0, an item
+ uint16 _depth; // Level of the closeup.
+ Common::String _graphicName;
+ Common::String _message;
+ CloseDataList _subCloseUps;
+};
+
+struct ViewData {
+ Common::Array<int16> _condition;
+ Common::String _graphicName;
+ CloseDataList _closeUps;
+};
+
+struct Action {
+ ActionType _actionType;
+ int16 _param1;
+ int16 _param2;
+ int16 _param3;
+ Common::Array<Common::String> _messages;
+};
+
+struct Rule {
+ RuleType _ruleType;
+ int16 _param1;
+ int16 _param2;
+ Common::Array<int16> _condition;
+ ActionList _actionList;
+};
+
+struct RoomData {
+ uint16 _doors[4];
+ byte _transitionType;
+ ViewDataList _view[4];
+ RuleList _rules;
+ Common::String _roomMsg;
+};
+
+struct InventoryData {
+ uint16 _quantity;
+ Common::String _name;
+ Common::String _bitmapName;
+};
+
+struct MapData {
+ uint16 _x, _y, _pageNumber;
+ SpecialRoom _specialID;
+ uint32 _mapFlags;
+};
+
+} // End of namespace Lab
+
+#endif // LAB_PROCESSROOM_H
diff --git a/engines/lab/resource.cpp b/engines/lab/resource.cpp
new file mode 100644
index 0000000000..8883cefe10
--- /dev/null
+++ b/engines/lab/resource.cpp
@@ -0,0 +1,309 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "lab/lab.h"
+
+#include "lab/dispman.h"
+#include "lab/music.h"
+#include "lab/processroom.h"
+#include "lab/resource.h"
+
+namespace Lab {
+
+Resource::Resource(LabEngine *vm) : _vm(vm) {
+ readStaticText();
+}
+
+void Resource::readStaticText() {
+ Common::File *labTextFile = openDataFile("Lab:Rooms/LabText");
+
+ for (int i = 0; i < 48; i++)
+ _staticText[i] = labTextFile->readLine();
+
+ delete labTextFile;
+}
+
+TextFont *Resource::getFont(const Common::String fileName) {
+ // TODO: Add support for the font format of the Amiga version
+ Common::File *dataFile = openDataFile(fileName, MKTAG('V', 'G', 'A', 'F'));
+
+ uint32 headerSize = 4 + 2 + 256 * 3 + 4;
+ uint32 fileSize = dataFile->size();
+ if (fileSize <= headerSize)
+ return nullptr;
+
+ _vm->updateEvents();
+
+ TextFont *textfont = new TextFont();
+ textfont->_dataLength = fileSize - headerSize;
+ textfont->_height = dataFile->readUint16LE();
+ dataFile->read(textfont->_widths, 256);
+ for (int i = 0; i < 256; i++)
+ textfont->_offsets[i] = dataFile->readUint16LE();
+ dataFile->skip(4);
+ textfont->_data = new byte[textfont->_dataLength + 4];
+ dataFile->read(textfont->_data, textfont->_dataLength);
+ delete dataFile;
+ return textfont;
+}
+
+Common::String Resource::getText(const Common::String fileName) {
+ Common::File *dataFile = openDataFile(fileName);
+
+ _vm->updateEvents();
+
+ uint32 count = dataFile->size();
+ byte *buffer = new byte[count];
+ byte *text = buffer;
+ dataFile->read(buffer, count);
+
+ while (text && (*text != '\0'))
+ *text++ -= (byte)95;
+
+ delete dataFile;
+
+ Common::String str = (char *)buffer;
+ delete[] buffer;
+
+ return str;
+}
+
+void Resource::readRoomData(const Common::String fileName) {
+ Common::File *dataFile = openDataFile(fileName, MKTAG('D', 'O', 'R', '1'));
+
+ _vm->_manyRooms = dataFile->readUint16LE();
+ _vm->_highestCondition = dataFile->readUint16LE();
+ _vm->_rooms = new RoomData[_vm->_manyRooms + 1];
+
+ for (int i = 1; i <= _vm->_manyRooms; i++) {
+ RoomData *curRoom = &_vm->_rooms[i];
+ curRoom->_doors[kDirectionNorth] = dataFile->readUint16LE();
+ curRoom->_doors[kDirectionSouth] = dataFile->readUint16LE();
+ curRoom->_doors[kDirectionEast] = dataFile->readUint16LE();
+ curRoom->_doors[kDirectionWest] = dataFile->readUint16LE();
+ curRoom->_transitionType = dataFile->readByte();
+ }
+
+ delete dataFile;
+}
+
+InventoryData *Resource::readInventory(const Common::String fileName) {
+ Common::File *dataFile = openDataFile(fileName, MKTAG('I', 'N', 'V', '1'));
+
+ _vm->_numInv = dataFile->readUint16LE();
+ InventoryData *inventory = new InventoryData[_vm->_numInv + 1];
+
+ for (int i = 1; i <= _vm->_numInv; i++) {
+ inventory[i]._quantity = dataFile->readUint16LE();
+ inventory[i]._name = readString(dataFile);
+ inventory[i]._bitmapName = readString(dataFile);
+ }
+
+ delete dataFile;
+ return inventory;
+}
+
+void Resource::readViews(uint16 roomNum) {
+ Common::String fileName = "LAB:Rooms/" + Common::String::format("%d", roomNum);
+ Common::File *dataFile = openDataFile(fileName, MKTAG('R', 'O', 'M', '4'));
+
+ RoomData *curRoom = &_vm->_rooms[roomNum];
+
+ curRoom->_roomMsg = readString(dataFile);
+ readView(dataFile, curRoom->_view[kDirectionNorth]);
+ readView(dataFile, curRoom->_view[kDirectionSouth]);
+ readView(dataFile, curRoom->_view[kDirectionEast]);
+ readView(dataFile, curRoom->_view[kDirectionWest]);
+ readRule(dataFile, curRoom->_rules);
+
+ _vm->updateEvents();
+ delete dataFile;
+}
+
+Common::String Resource::translateFileName(const Common::String filename) {
+ Common::String upperFilename = filename;
+ upperFilename.toUppercase();
+ Common::String fileNameStrFinal;
+
+ if (upperFilename.hasPrefix("P:") || upperFilename.hasPrefix("F:")) {
+ if (_vm->_isHiRes)
+ fileNameStrFinal = "GAME/SPICT/";
+ else
+ fileNameStrFinal = "GAME/PICT/";
+
+ if (_vm->getPlatform() == Common::kPlatformAmiga) {
+ if (upperFilename.hasPrefix("P:")) {
+ fileNameStrFinal = "PICT/";
+ } else {
+ fileNameStrFinal = "LABFONTS/";
+ upperFilename += "T"; // all the Amiga fonts have a ".FONT" suffix
+ }
+ }
+ } else if (upperFilename.hasPrefix("LAB:")) {
+ if (_vm->getPlatform() != Common::kPlatformAmiga)
+ fileNameStrFinal = "GAME/";
+ } else if (upperFilename.hasPrefix("MUSIC:")) {
+ if (_vm->getPlatform() != Common::kPlatformAmiga)
+ fileNameStrFinal = "GAME/MUSIC/";
+ else
+ fileNameStrFinal = "MUSIC/";
+ }
+
+ if (upperFilename.contains(':')) {
+ while (upperFilename[0] != ':') {
+ upperFilename.deleteChar(0);
+ }
+
+ upperFilename.deleteChar(0);
+ }
+
+ fileNameStrFinal += upperFilename;
+
+ return fileNameStrFinal;
+}
+
+Common::File *Resource::openDataFile(const Common::String fileName, uint32 fileHeader) {
+ Common::File *dataFile = new Common::File();
+ dataFile->open(translateFileName(fileName));
+ if (!dataFile->isOpen())
+ error("openDataFile: Couldn't open %s (%s)", translateFileName(fileName).c_str(), fileName.c_str());
+
+ if (fileHeader > 0) {
+ uint32 headerTag = dataFile->readUint32BE();
+ if (headerTag != fileHeader) {
+ dataFile->close();
+ error("openDataFile: Unexpected header in %s (%s) - expected: %d, got: %d", translateFileName(fileName).c_str(), fileName.c_str(), fileHeader, headerTag);
+ }
+ }
+
+ return dataFile;
+}
+
+Common::String Resource::readString(Common::File *file) {
+ byte size = file->readByte();
+ if (!size)
+ return Common::String("");
+
+ char *str = new char[size];
+ for (int i = 0; i < size; i++) {
+ char c = file->readByte();
+ // Decrypt char
+ c = (i < size - 1) ? c - 95 : '\0';
+ str[i] = c;
+ }
+
+ Common::String result = str;
+ delete[] str;
+ return result;
+}
+
+Common::Array<int16> Resource::readConditions(Common::File *file) {
+ int16 cond;
+ Common::Array<int16> list;
+
+ while ((cond = file->readUint16LE()) != 0)
+ list.push_back(cond);
+
+ if (list.size() > 24) {
+ // The original only allocated 24 elements, and silently
+ // dropped remaining parts.
+ warning("More than 24 parts in condition");
+ }
+
+ return list;
+}
+
+void Resource::readRule(Common::File *file, RuleList &rules) {
+ rules.clear();
+ while (file->readByte() == 1) {
+ rules.push_back(Rule());
+ Rule &rule = rules.back();
+
+ rule._ruleType = (RuleType)file->readSint16LE();
+ rule._param1 = file->readSint16LE();
+ rule._param2 = file->readSint16LE();
+ rule._condition = readConditions(file);
+ readAction(file, rule._actionList);
+ }
+}
+
+void Resource::readAction(Common::File *file, ActionList &list) {
+ list.clear();
+
+ while (file->readByte() == 1) {
+ list.push_back(Action());
+ Action &action = list.back();
+
+ action._actionType = (ActionType)file->readSint16LE();
+ action._param1 = file->readSint16LE();
+ action._param2 = file->readSint16LE();
+ action._param3 = file->readSint16LE();
+
+ if (action._actionType == kActionShowMessages) {
+ action._messages.reserve(action._param1);
+ for (int i = 0; i < action._param1; i++)
+ action._messages.push_back(readString(file));
+ } else {
+ action._messages.push_back(readString(file));
+ }
+ }
+}
+
+void Resource::readCloseUps(uint16 depth, Common::File *file, CloseDataList &list) {
+ list.clear();
+ while (file->readByte() != '\0') {
+ list.push_back(CloseData());
+ CloseData &closeup = list.back();
+
+ closeup._x1 = file->readUint16LE();
+ closeup._y1 = file->readUint16LE();
+ closeup._x2 = file->readUint16LE();
+ closeup._y2 = file->readUint16LE();
+ closeup._closeUpType = file->readSint16LE();
+ closeup._depth = depth;
+ closeup._graphicName = readString(file);
+ closeup._message = readString(file);
+ readCloseUps(depth + 1, file, closeup._subCloseUps);
+ }
+}
+
+void Resource::readView(Common::File *file, ViewDataList &list) {
+ list.clear();
+ while (file->readByte() == 1) {
+ list.push_back(ViewData());
+ ViewData &view = list.back();
+
+ view._condition = readConditions(file);
+ view._graphicName = readString(file);
+ readCloseUps(0, file, view._closeUps);
+ }
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/resource.h b/engines/lab/resource.h
new file mode 100644
index 0000000000..307eac3068
--- /dev/null
+++ b/engines/lab/resource.h
@@ -0,0 +1,124 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_RESOURCE_H
+#define LAB_RESOURCE_H
+
+namespace Lab {
+
+struct ViewData;
+
+enum StaticText {
+ kTextLowerFloor,
+ kTextMiddleFloor,
+ kTextUpperFloor,
+ kTextMedMazeFloor,
+ kTextHedgeMazeFloor,
+ kTextSurMazeFloor,
+ kTextCarnivalFloor,
+
+ kTextSurmazeMessage,
+
+ kTextFacingNorth,
+ kTextFacingEast,
+ kTextFacingSouth,
+ kTextFacingWest,
+
+ kTextkLampOn,
+
+ kTextTurnLeft,
+ kTextTurnRight,
+ kTextGoForward,
+ kTextNoPath,
+ kTextTakeItem,
+ kTextSave,
+ kTextLoad,
+ kTextBookmark,
+ kTextPersonal,
+ kTextDisk,
+ kTextSaveBook,
+ kTextRestoreBook,
+ kTextSaveFlash,
+ kTextRestoreFlash,
+ kTextSaveDisk,
+ kTextRestoreDisk,
+ kTextNoDiskInDrive,
+ kTextWriteProtected,
+ kTextSelectDisk,
+ kTextFormatFloppy,
+ kTextFormatting,
+
+ kTextNothing,
+ kTextUseOnWhat,
+ kTextTakeWhat,
+ kTextMoveWhat,
+ kTextOpenWhat,
+ kTextCloseWhat,
+ kTextLookWhat,
+
+ kTextUseMap,
+ kTextUseJournal,
+ kTextTurnkLampOn,
+ kTextTurnLampOff,
+ kTextUseWhiskey,
+ kTextUsePith,
+ kTextUseHelmet
+};
+
+class Resource {
+public:
+ Resource(LabEngine *vm);
+ ~Resource() {}
+
+ Common::File *openDataFile(const Common::String fileName, uint32 fileHeader = 0);
+ void readRoomData(const Common::String fileName);
+ InventoryData *readInventory(const Common::String fileName);
+ void readViews(uint16 roomNum);
+ TextFont *getFont(const Common::String fileName);
+ Common::String getText(const Common::String fileName);
+ Common::String getStaticText(byte index) const { return _staticText[index]; }
+
+private:
+ LabEngine *_vm;
+ Common::String readString(Common::File *file);
+ Common::Array<int16> readConditions(Common::File *file);
+ void readRule(Common::File *file, RuleList &rules);
+ void readAction(Common::File *file, ActionList &action);
+ void readCloseUps(uint16 depth, Common::File *file, CloseDataList &close);
+ void readView(Common::File *file, ViewDataList &view);
+ void readStaticText();
+ Common::String translateFileName(const Common::String filename);
+
+ Common::String _staticText[48];
+};
+
+} // End of namespace Lab
+
+#endif // LAB_RESOURCE_H
diff --git a/engines/lab/savegame.cpp b/engines/lab/savegame.cpp
new file mode 100644
index 0000000000..d815929c39
--- /dev/null
+++ b/engines/lab/savegame.cpp
@@ -0,0 +1,250 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/savefile.h"
+#include "common/translation.h"
+
+#include "gui/message.h"
+#include "gui/saveload.h"
+
+#include "graphics/thumbnail.h"
+#include "engines/savestate.h"
+
+#include "lab/lab.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/labsets.h"
+#include "lab/music.h"
+#include "lab/processroom.h"
+#include "lab/speciallocks.h"
+
+namespace Lab {
+
+#define SAVEGAME_ID MKTAG('L', 'O', 'T', 'S')
+#define SAVEGAME_VERSION 1
+
+void LabEngine::writeSaveGameHeader(Common::OutSaveFile *out, const Common::String &saveName) {
+ out->writeUint32BE(SAVEGAME_ID);
+
+ // Write version
+ out->writeByte(SAVEGAME_VERSION);
+
+ // Write savegame name
+ out->writeString(saveName);
+ out->writeByte(0);
+
+ // Save the game thumbnail
+ Graphics::saveThumbnail(*out);
+
+ // Creation date/time
+ TimeDate curTime;
+ _system->getTimeAndDate(curTime);
+
+ uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
+ uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
+ uint32 playTime = getTotalPlayTime() / 1000;
+
+ out->writeUint32BE(saveDate);
+ out->writeUint16BE(saveTime);
+ out->writeUint32BE(playTime);
+}
+
+bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header) {
+ uint32 id = in->readUint32BE();
+
+ // Check if it's a valid ScummVM savegame
+ if (id != SAVEGAME_ID)
+ return false;
+
+ // Read in the version
+ header._version = in->readByte();
+
+ // Check that the save version isn't newer than this binary
+ if (header._version > SAVEGAME_VERSION)
+ return false;
+
+ // Read in the save name
+ Common::String saveName;
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0')
+ saveName += ch;
+ header._descr.setDescription(saveName);
+
+ // Get the thumbnail
+ header._descr.setThumbnail(Graphics::loadThumbnail(*in));
+
+ uint32 saveDate = in->readUint32BE();
+ uint16 saveTime = in->readUint16BE();
+ uint32 playTime = in->readUint32BE();
+
+ int day = (saveDate >> 24) & 0xFF;
+ int month = (saveDate >> 16) & 0xFF;
+ int year = saveDate & 0xFFFF;
+ header._descr.setSaveDate(year, month, day);
+
+ int hour = (saveTime >> 8) & 0xFF;
+ int minutes = saveTime & 0xFF;
+ header._descr.setSaveTime(hour, minutes);
+
+ header._descr.setPlayTime(playTime * 1000);
+ if (g_engine)
+ g_engine->setTotalPlayTime(playTime * 1000);
+
+ return true;
+}
+
+bool LabEngine::saveGame(int slot, const Common::String desc) {
+ Common::String fileName = generateSaveFileName(slot);
+ Common::SaveFileManager *saveFileManager = _system->getSavefileManager();
+ Common::OutSaveFile *file = saveFileManager->openForSaving(fileName);
+
+ if (!file)
+ return false;
+
+ // Load scene pic
+ _graphics->readPict(getPictName(false));
+
+
+ writeSaveGameHeader(file, desc);
+ file->writeUint16LE(_roomNum);
+ file->writeUint16LE(getDirection());
+ file->writeUint16LE(getQuarters());
+
+ // Conditions
+ for (int i = 0; i < _conditions->_lastElement / (8 * 2); i++)
+ file->writeUint16LE(_conditions->_array[i]);
+
+ // Rooms found
+ for (int i = 0; i < _roomsFound->_lastElement / (8 * 2); i++)
+ file->writeUint16LE(_roomsFound->_array[i]);
+
+ _specialLocks->save(file);
+
+ // Breadcrumbs
+ for (uint i = 0; i < MAX_CRUMBS; i++) {
+ file->writeUint16LE(_breadCrumbs[i]._roomNum);
+ file->writeUint16LE(_breadCrumbs[i]._direction);
+ }
+
+ file->flush();
+ file->finalize();
+ delete file;
+
+ return true;
+}
+
+bool LabEngine::loadGame(int slot) {
+ Common::String fileName = generateSaveFileName(slot);
+ Common::SaveFileManager *saveFileManager = _system->getSavefileManager();
+ Common::InSaveFile *file = saveFileManager->openForLoading(fileName);
+
+ if (!file)
+ return false;
+
+ SaveGameHeader header;
+ readSaveGameHeader(file, header);
+ _roomNum = file->readUint16LE();
+ setDirection(file->readUint16LE());
+ setQuarters(file->readUint16LE());
+
+ // Conditions
+ for (int i = 0; i < _conditions->_lastElement / (8 * 2); i++)
+ _conditions->_array[i] = file->readUint16LE();
+
+ // Rooms found
+ for (int i = 0; i < _roomsFound->_lastElement / (8 * 2); i++)
+ _roomsFound->_array[i] = file->readUint16LE();
+
+ _specialLocks->load(file);
+
+ // Breadcrumbs
+ for (int i = 0; i < MAX_CRUMBS; i++) {
+ _breadCrumbs[i]._roomNum = file->readUint16LE();
+ _breadCrumbs[i]._direction = file->readUint16LE();
+ }
+
+ _droppingCrumbs = (_breadCrumbs[0]._roomNum != 0);
+ _followingCrumbs = false;
+
+ for (int i = 0; i < MAX_CRUMBS; i++) {
+ if (_breadCrumbs[i]._roomNum == 0)
+ break;
+ _numCrumbs = i;
+ }
+
+ delete file;
+
+ return true;
+}
+
+bool LabEngine::saveRestoreGame() {
+ bool isOK = false;
+
+ // The original had one screen for saving/loading. We have two.
+ // Ask the user which screen to use.
+ GUI::MessageDialog saveOrLoad(_("Would you like to save or restore a game?"), _("Save"), _("Restore"));
+
+ int choice = saveOrLoad.runModal();
+ if (choice == GUI::kMessageOK) {
+ // Save
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
+ int slot = dialog->runModalWithCurrentTarget();
+ if (slot >= 0) {
+ Common::String desc = dialog->getResultString();
+
+ if (desc.empty()) {
+ // create our own description for the saved game, the user didn't enter it
+ desc = dialog->createDefaultSaveDescription(slot);
+ }
+
+ isOK = saveGame(slot, desc);
+ }
+ delete dialog;
+ } else {
+ // Restore
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
+ int slot = dialog->runModalWithCurrentTarget();
+ if (slot >= 0) {
+ isOK = loadGame(slot);
+ if (isOK)
+ _music->checkRoomMusic();
+ }
+ delete dialog;
+ }
+
+ _alternate = false;
+ _mainDisplay = true;
+ _event->initMouse();
+ _graphics->screenUpdate();
+
+ return isOK;
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/special.cpp b/engines/lab/special.cpp
new file mode 100644
index 0000000000..43d6056125
--- /dev/null
+++ b/engines/lab/special.cpp
@@ -0,0 +1,469 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "lab/lab.h"
+
+#include "lab/anim.h"
+#include "lab/dispman.h"
+#include "lab/eventman.h"
+#include "lab/image.h"
+#include "lab/labsets.h"
+#include "lab/music.h"
+#include "lab/processroom.h"
+#include "lab/resource.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+void LabEngine::doNotes() {
+ TextFont *noteFont = _resource->getFont("F:Note.fon");
+ Common::String noteText = _resource->getText("Lab:Rooms/Notes");
+
+ Common::Rect textRect = Common::Rect(_utils->vgaScaleX(25) + _utils->svgaCord(15), _utils->vgaScaleY(50), _utils->vgaScaleX(295) - _utils->svgaCord(15), _utils->vgaScaleY(148));
+ _graphics->flowText(noteFont, -2 + _utils->svgaCord(1), 0, 0, false, false, true, true, textRect, noteText.c_str());
+ _graphics->setPalette(_anim->_diffPalette, 256);
+ _graphics->freeFont(&noteFont);
+}
+
+void LabEngine::doWestPaper() {
+ TextFont *paperFont = _resource->getFont("F:News22.fon");
+ Common::String paperText = _resource->getText("Lab:Rooms/Date");
+
+ Common::Rect textRect = Common::Rect(_utils->vgaScaleX(57), _utils->vgaScaleY(77) + _utils->svgaCord(2), _utils->vgaScaleX(262), _utils->vgaScaleY(91));
+ _graphics->flowText(paperFont, 0, 0, 0, false, true, false, true, textRect, paperText.c_str());
+ _graphics->freeFont(&paperFont);
+
+ paperFont = _resource->getFont("F:News32.fon");
+ paperText = _resource->getText("Lab:Rooms/Headline");
+
+ int fileLen = paperText.size() - 1;
+ textRect = Common::Rect(_utils->vgaScaleX(57), _utils->vgaScaleY(86) - _utils->svgaCord(2), _utils->vgaScaleX(262), _utils->vgaScaleY(118));
+ int charsPrinted = _graphics->flowText(paperFont, -8, 0, 0, false, true, false, true, textRect, paperText.c_str());
+
+ uint16 y;
+
+ if (charsPrinted < fileLen) {
+ y = 130 - _utils->svgaCord(5);
+ textRect = Common::Rect(_utils->vgaScaleX(57), _utils->vgaScaleY(86) - _utils->svgaCord(2), _utils->vgaScaleX(262), _utils->vgaScaleY(132));
+ _graphics->flowText(paperFont, -8 - _utils->svgaCord(1), 0, 0, false, true, false, true, textRect, paperText.c_str());
+ } else
+ y = 115 - _utils->svgaCord(5);
+
+ _graphics->freeFont(&paperFont);
+
+ paperFont = _resource->getFont("F:Note.fon");
+ paperText = _resource->getText("Lab:Rooms/Col1");
+ _graphics->flowText(paperFont, -4, 0, 0, false, false, false, true, _utils->vgaRectScale(45, y, 158, 148), paperText.c_str());
+
+ paperText = _resource->getText("Lab:Rooms/Col2");
+ _graphics->flowText(paperFont, -4, 0, 0, false, false, false, true, _utils->vgaRectScale(162, y, 275, 148), paperText.c_str());
+
+ _graphics->freeFont(&paperFont);
+ _graphics->setPalette(_anim->_diffPalette, 256);
+}
+
+void LabEngine::loadJournalData() {
+ if (_journalFont)
+ _graphics->freeFont(&_journalFont);
+
+ _journalFont = _resource->getFont("F:Journal.fon");
+ updateEvents();
+
+ Common::String filename = "Lab:Rooms/j";
+
+ bool bridge = _conditions->in(kCondBridge0) || _conditions->in(kCondBridge1);
+ bool dirty = _conditions->in(kCondDirty);
+ bool news = !_conditions->in(kCondNoNews);
+ bool clean = !_conditions->in(kCondNoClean);
+
+ if (bridge && clean && news)
+ filename += '8';
+ else if (clean && news)
+ filename += '9';
+ else if (bridge && clean)
+ filename += '6';
+ else if (clean)
+ filename += '7';
+ else if (bridge && dirty && news)
+ filename += '4';
+ else if (dirty && news)
+ filename += '5';
+ else if (bridge && dirty)
+ filename += '2';
+ else if (dirty)
+ filename += '3';
+ else if (bridge)
+ filename += '1';
+ else
+ filename += '0';
+
+ _journalText = _resource->getText(filename);
+ _journalTextTitle = _resource->getText("Lab:Rooms/jt");
+
+ Common::File *journalFile = _resource->openDataFile("P:JImage");
+ _journalButtonList.push_back(_event->createButton( 80, _utils->vgaScaleY(162) + _utils->svgaCord(1), 0, Common::KEYCODE_LEFT, new Image(journalFile, this), new Image(journalFile, this))); // back
+ _journalButtonList.push_back(_event->createButton(194, _utils->vgaScaleY(162) + _utils->svgaCord(1), 2, Common::KEYCODE_RIGHT, new Image(journalFile, this), new Image(journalFile, this))); // forward
+ _journalButtonList.push_back(_event->createButton(144, _utils->vgaScaleY(164) - _utils->svgaCord(1), 1, Common::KEYCODE_ESCAPE, new Image(journalFile, this), new Image(journalFile, this))); // cancel
+ delete journalFile;
+
+ _anim->_noPalChange = true;
+ _journalBackImage->setData(new byte[_graphics->_screenBytesPerPage]);
+ _graphics->readPict("P:Journal.pic", true, false, _journalBackImage->_imageData);
+ _anim->_noPalChange = false;
+
+ // Keep a copy of the blank journal
+ _blankJournal = new byte[_graphics->_screenBytesPerPage];
+ memcpy(_blankJournal, _journalBackImage->_imageData, _graphics->_screenBytesPerPage);
+}
+
+void LabEngine::drawJournalText() {
+ uint16 drawingToPage = 1;
+ const char *curText = _journalText.c_str();
+
+ assert((_journalPage & 1) == 0);
+
+ while (drawingToPage < _journalPage) {
+ updateEvents();
+
+ // flowText without output
+ curText += _graphics->flowText(_journalFont, -2, 2, 0, false, false, false, false, _utils->vgaRectScale(52, 32, 152, 148), curText);
+
+ _lastPage = (*curText == 0);
+
+ if (_lastPage) {
+ // Reset _journalPage to this page, in case it was set too high
+ _journalPage = (drawingToPage / 2) * 2;
+ break;
+ }
+
+ drawingToPage++;
+ }
+
+ if (_journalPage == 0) {
+ // draw title page centered
+ _graphics->flowText(_journalFont, -2, 2, 0, false, true, true, true, _utils->vgaRectScale(52, 32, 152, 148), _journalTextTitle.c_str(), _journalBackImage);
+ } else {
+ curText += _graphics->flowText(_journalFont, -2, 2, 0, false, false, false, true, _utils->vgaRectScale(52, 32, 152, 148), curText, _journalBackImage);
+ }
+
+ updateEvents();
+ curText += _graphics->flowText(_journalFont, -2, 2, 0, false, false, false, true, _utils->vgaRectScale(171, 32, 271, 148), curText, _journalBackImage);
+
+ _lastPage = (*curText == 0);
+}
+
+void LabEngine::turnPage(bool fromLeft) {
+ if (fromLeft) {
+ for (int i = 0; i < _graphics->_screenWidth; i += 8) {
+ updateEvents();
+ waitTOF();
+ _journalBackImage->blitBitmap(i, 0, nullptr, i, 0, 8, _graphics->_screenHeight, false);
+ }
+ } else {
+ for (int i = (_graphics->_screenWidth - 8); i > 0; i -= 8) {
+ updateEvents();
+ waitTOF();
+ _journalBackImage->blitBitmap(i, 0, nullptr, i, 0, 8, _graphics->_screenHeight, false);
+ }
+ }
+}
+
+void LabEngine::drawJournal(uint16 wipenum, bool needFade) {
+ _event->mouseHide();
+ updateEvents();
+ drawJournalText();
+ _graphics->loadBackPict("P:Journal.pic", _highPalette);
+
+ if (wipenum == 0)
+ _journalBackImage->blitBitmap(0, 0, nullptr, 0, 0, _graphics->_screenWidth, _graphics->_screenHeight, false);
+ else
+ turnPage((wipenum == 1));
+
+ _event->toggleButton(_event->getButton(0), 15, (_journalPage > 0)); // back button
+ _event->toggleButton(_event->getButton(2), 15, (!_lastPage)); // forward button
+
+ if (needFade)
+ _graphics->fade(true);
+
+ // Reset the journal background, so that all the text that has been blitted on it is erased
+ memcpy(_journalBackImage->_imageData, _blankJournal, _graphics->_screenBytesPerPage);
+
+ eatMessages();
+ _event->mouseShow();
+}
+
+void LabEngine::processJournal() {
+ while (1) {
+ // Make sure we check the music at least after every message
+ updateEvents();
+ IntuiMessage *msg = _event->getMsg();
+ if (shouldQuit()) {
+ _quitLab = true;
+ return;
+ }
+
+ if (!msg)
+ updateEvents();
+ else {
+ MessageClass msgClass = msg->_msgClass;
+
+ if ((msgClass == kMessageRightClick) ||
+ ((msgClass == kMessageRawKey) && (msg->_code == Common::KEYCODE_ESCAPE)))
+ return;
+ else if (msgClass == kMessageButtonUp) {
+ uint16 buttonId = msg->_code;
+ if (buttonId == 0) {
+ if (_journalPage >= 2) {
+ _journalPage -= 2;
+ drawJournal(1, false);
+ }
+ } else if (buttonId == 1) {
+ return;
+ } else if (buttonId == 2) {
+ if (!_lastPage) {
+ _journalPage += 2;
+ drawJournal(2, false);
+ }
+ }
+ }
+ }
+ }
+}
+
+void LabEngine::doJournal() {
+ _graphics->blackAllScreen();
+ _lastPage = false;
+
+ _journalBackImage->_width = _graphics->_screenWidth;
+ _journalBackImage->_height = _graphics->_screenHeight;
+ _journalBackImage->setData(nullptr, true);
+
+ updateEvents();
+ loadJournalData();
+ _event->attachButtonList(&_journalButtonList);
+ drawJournal(0, true);
+ _event->mouseShow();
+ processJournal();
+ _event->attachButtonList(nullptr);
+ _graphics->fade(false);
+ _event->mouseHide();
+
+ delete[] _blankJournal;
+ _blankJournal = nullptr;
+ _journalBackImage->setData(nullptr, true);
+
+ _event->freeButtonList(&_journalButtonList);
+ _graphics->freeFont(&_journalFont);
+
+ _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1, 0);
+ _graphics->blackScreen();
+}
+
+void LabEngine::drawMonText(const char *text, TextFont *monitorFont, Common::Rect textRect, bool isinteractive) {
+ uint16 drawingToPage = 0, yspacing = 0;
+ int charsDrawn = 0;
+ const char *curText = text;
+
+ _event->mouseHide();
+
+ if (*text == '%') {
+ text++;
+ uint16 numlines = (*text - '0') * 10;
+ text++;
+ numlines += (*text - '0');
+ text += 2;
+
+ uint16 fheight = _graphics->textHeight(monitorFont);
+ textRect.left = _monitorButton->_width + _utils->vgaScaleX(3);
+ _monitorButtonHeight = _monitorButton->_height + _utils->vgaScaleY(3);
+
+ if (_monitorButtonHeight > fheight)
+ yspacing = _monitorButtonHeight - fheight;
+ else
+ _monitorButtonHeight = fheight;
+
+ _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, textRect.bottom, 0);
+
+ for (int i = 0; i < numlines; i++)
+ _monitorButton->drawImage(0, i * _monitorButtonHeight);
+ } else if (isinteractive) {
+ _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, textRect.bottom, 0);
+ } else {
+ _graphics->rectFill(textRect, 0);
+ }
+
+ while (drawingToPage < _monitorPage) {
+ updateEvents();
+ curText = text + charsDrawn;
+ charsDrawn += _graphics->flowText(monitorFont, yspacing, 0, 0, false, false, false, false, textRect, curText);
+ _lastPage = (*curText == 0);
+
+ if (_lastPage)
+ _monitorPage = drawingToPage;
+ else
+ drawingToPage++;
+ }
+
+ curText = text + charsDrawn;
+ _lastPage = (*curText == 0);
+ _graphics->flowText(monitorFont, yspacing, 2, 0, false, false, false, true, textRect, curText);
+ _event->mouseShow();
+}
+
+void LabEngine::processMonitor(const char *ntext, TextFont *monitorFont, bool isInteractive, Common::Rect textRect) {
+ Common::String startFileName = _monitorTextFilename;
+ const CloseData *startClosePtr = _closeDataPtr, *lastClosePtr[10];
+ uint16 depth = 0;
+
+ lastClosePtr[0] = _closeDataPtr;
+
+ while (1) {
+ if (isInteractive) {
+ if (!_closeDataPtr)
+ _closeDataPtr = startClosePtr;
+
+ Common::String filename;
+ if (_closeDataPtr == startClosePtr)
+ filename = startFileName;
+ else
+ filename = _closeDataPtr->_graphicName;
+
+ if (filename != _monitorTextFilename) {
+ _monitorPage = 0;
+ _monitorTextFilename = filename;
+
+ Common::String text = _resource->getText(_monitorTextFilename);
+ _graphics->fade(false);
+ drawMonText(text.c_str(), monitorFont, textRect, isInteractive);
+ _graphics->fade(true);
+ }
+ }
+
+ // Make sure we check the music at least after every message
+ updateEvents();
+ IntuiMessage *msg = _event->getMsg();
+ if (shouldQuit()) {
+ _quitLab = true;
+ return;
+ }
+
+ if (!msg)
+ updateEvents();
+ else {
+ MessageClass msgClass = msg->_msgClass;
+
+ if ((msgClass == kMessageRightClick) ||
+ ((msgClass == kMessageRawKey) && (msg->_code == Common::KEYCODE_ESCAPE)))
+ return;
+
+ if (msgClass == kMessageLeftClick) {
+ int16 mouseX = msg->_mouse.x;
+ int16 mouseY = msg->_mouse.y;
+
+ if ((mouseY >= _utils->vgaScaleY(171)) && (mouseY <= _utils->vgaScaleY(200))) {
+ if (mouseX <= _utils->vgaScaleX(31))
+ return;
+
+ if (mouseX <= _utils->vgaScaleX(59)) {
+ if (isInteractive) {
+ _monitorPage = 0;
+
+ if (depth) {
+ depth--;
+ _closeDataPtr = lastClosePtr[depth];
+ }
+ } else if (_monitorPage > 0) {
+ _monitorPage = 0;
+ drawMonText(ntext, monitorFont, textRect, isInteractive);
+ }
+ } else if (mouseX < _utils->vgaScaleX(259)) {
+ return;
+ } else if (mouseX <= _utils->vgaScaleX(289)) {
+ if (!_lastPage) {
+ _monitorPage += 1;
+ drawMonText(ntext, monitorFont, textRect, isInteractive);
+ }
+ } else if (_monitorPage >= 1) {
+ // mouseX is greater than 290 (scaled)
+ _monitorPage -= 1;
+ drawMonText(ntext, monitorFont, textRect, isInteractive);
+ }
+ } else if (isInteractive) {
+ const CloseData *tmpClosePtr = _closeDataPtr;
+ mouseY = 64 + (mouseY / _monitorButtonHeight) * 42;
+ mouseX = 101;
+ setCurrentClose(Common::Point(mouseX, mouseY), &_closeDataPtr, false);
+
+ if (tmpClosePtr != _closeDataPtr) {
+ lastClosePtr[depth] = tmpClosePtr;
+ depth++;
+ }
+ }
+ }
+ }
+ }
+}
+
+void LabEngine::doMonitor(const Common::String background, const Common::String textfile, bool isinteractive, Common::Rect textRect) {
+ Common::Rect scaledRect = _utils->vgaRectScale(textRect.left, textRect.top, textRect.right, textRect.bottom);
+ _monitorTextFilename = textfile;
+
+ _graphics->blackAllScreen();
+ _graphics->readPict("P:Mon/Monitor.1");
+ _graphics->readPict("P:Mon/NWD1");
+ _graphics->readPict("P:Mon/NWD2");
+ _graphics->readPict("P:Mon/NWD3");
+ _graphics->blackAllScreen();
+
+ _monitorPage = 0;
+ _lastPage = false;
+ _graphics->_fadePalette = _highPalette;
+
+ TextFont *monitorFont = _resource->getFont("F:Map.fon");
+ Common::File *buttonFile = _resource->openDataFile("P:MonImage");
+ _monitorButton = new Image(buttonFile, this);
+ delete buttonFile;
+
+ Common::String ntext = _resource->getText(textfile);
+ _graphics->loadBackPict(background, _highPalette);
+ drawMonText(ntext.c_str(), monitorFont, scaledRect, isinteractive);
+ _event->mouseShow();
+ _graphics->fade(true);
+ processMonitor(ntext.c_str(), monitorFont, isinteractive, scaledRect);
+ _graphics->fade(false);
+ _event->mouseHide();
+ _graphics->freeFont(&monitorFont);
+
+ _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1, 0);
+ _graphics->blackAllScreen();
+ _graphics->freePict();
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/speciallocks.cpp b/engines/lab/speciallocks.cpp
new file mode 100644
index 0000000000..fe70b0f111
--- /dev/null
+++ b/engines/lab/speciallocks.cpp
@@ -0,0 +1,394 @@
+/* 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.
+ *
+ */
+
+ /*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/file.h"
+
+#include "gui/message.h"
+
+#include "lab/lab.h"
+#include "lab/anim.h"
+#include "lab/dispman.h"
+#include "lab/image.h"
+#include "lab/labsets.h"
+#include "lab/resource.h"
+#include "lab/speciallocks.h"
+#include "lab/utils.h"
+
+namespace Lab {
+
+#define BRICKOPEN 115
+#define COMBINATIONUNLOCKED 130
+
+enum TileScroll {
+ kScrollLeft = 1,
+ kScrollRight = 2,
+ kScrollUp = 3,
+ kScrollDown = 4
+};
+
+const uint16 INIT_TILE[4][4] = {
+ { 1, 5, 9, 13 },
+ { 2, 6, 10, 14 },
+ { 3, 7, 11, 15 },
+ { 4, 8, 12, 0 }
+};
+
+const uint16 SOLUTION[4][4] = {
+ { 7, 1, 8, 3 },
+ { 2, 11, 15, 4 },
+ { 9, 5, 14, 6 },
+ { 10, 13, 12, 0 }
+};
+
+const int COMBINATION_X[6] = { 45, 83, 129, 166, 211, 248 };
+
+SpecialLocks::SpecialLocks(LabEngine *vm) : _vm(vm) {
+ for (int i = 0; i < 16; i++)
+ _tiles[i] = nullptr;
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++)
+ _curTile[i][j] = INIT_TILE[i][j];
+ }
+
+ for (int i = 0; i < 6; i++)
+ _combination[i] = 0;
+
+ for (int i = 0; i < 10; i++)
+ _numberImages[i] = nullptr;
+}
+
+SpecialLocks::~SpecialLocks() {
+ for (int i = 0; i < 16; i++)
+ delete _tiles[i];
+
+ for (int imgIdx = 0; imgIdx < 10; imgIdx++) {
+ delete _numberImages[imgIdx];
+ _numberImages[imgIdx] = nullptr;
+ }
+}
+
+void SpecialLocks::tileClick(Common::Point pos) {
+ Common::Point realPos = _vm->_utils->vgaUnscale(pos);
+
+ if ((realPos.x < 101) || (realPos.y < 26))
+ return;
+
+ int tileX = (realPos.x - 101) / 30;
+ int tileY = (realPos.y - 26) / 25;
+
+ if ((tileX < 4) && (tileY < 4))
+ changeTile(tileX, tileY);
+}
+
+void SpecialLocks::changeTile(uint16 col, uint16 row) {
+ int16 scrolltype = -1;
+
+ if (row > 0) {
+ if (_curTile[col][row - 1] == 0) {
+ _curTile[col][row - 1] = _curTile[col][row];
+ _curTile[col][row] = 0;
+ scrolltype = kScrollDown;
+ }
+ }
+
+ if (col > 0) {
+ if (_curTile[col - 1][row] == 0) {
+ _curTile[col - 1][row] = _curTile[col][row];
+ _curTile[col][row] = 0;
+ scrolltype = kScrollRight;
+ }
+ }
+
+ if (row < 3) {
+ if (_curTile[col][row + 1] == 0) {
+ _curTile[col][row + 1] = _curTile[col][row];
+ _curTile[col][row] = 0;
+ scrolltype = kScrollUp;
+ }
+ }
+
+ if (col < 3) {
+ if (_curTile[col + 1][row] == 0) {
+ _curTile[col + 1][row] = _curTile[col][row];
+ _curTile[col][row] = 0;
+ scrolltype = kScrollLeft;
+ }
+ }
+
+ if (scrolltype != -1) {
+ if (_vm->getFeatures() & GF_WINDOWS_TRIAL) {
+ GUI::MessageDialog trialMessage("This puzzle is not available in the trial version of the game");
+ trialMessage.runModal();
+ return;
+ }
+
+ doTileScroll(col, row, scrolltype);
+ bool check = true;
+ row = 0;
+ col = 0;
+
+ while (row < 4) {
+ while (col < 4) {
+ check &= (_curTile[row][col] == SOLUTION[row][col]);
+ col++;
+ }
+
+ row++;
+ col = 0;
+ }
+
+ if (check) {
+ // unlocked combination
+ _vm->_conditions->inclElement(BRICKOPEN);
+ _vm->_anim->_doBlack = true;
+ _vm->_graphics->readPict("p:Up/BDOpen");
+ }
+ }
+}
+
+void SpecialLocks::combinationClick(Common::Point pos) {
+ Common::Point realPos = _vm->_utils->vgaUnscale(pos);
+
+ if (!Common::Rect(44, 63, 285, 99).contains(realPos))
+ return;
+
+ uint16 number = 0;
+ if (realPos.x < 83)
+ number = 0;
+ else if (realPos.x < 127)
+ number = 1;
+ else if (realPos.x < 165)
+ number = 2;
+ else if (realPos.x < 210)
+ number = 3;
+ else if (realPos.x < 245)
+ number = 4;
+ else if (realPos.x < 286)
+ number = 5;
+
+ changeCombination(number);
+}
+
+void SpecialLocks::doTile(bool showsolution) {
+ uint16 row = 0, col = 0, rowm, colm, num;
+ int16 rows, cols;
+
+ if (showsolution) {
+ rowm = _vm->_utils->vgaScaleY(23);
+ colm = _vm->_utils->vgaScaleX(27);
+
+ rows = _vm->_utils->vgaScaleY(31);
+ cols = _vm->_utils->vgaScaleX(105);
+ } else {
+ _vm->_graphics->rectFillScaled(97, 22, 220, 126, 0);
+
+ rowm = _vm->_utils->vgaScaleY(25);
+ colm = _vm->_utils->vgaScaleX(30);
+
+ rows = _vm->_utils->vgaScaleY(25);
+ cols = _vm->_utils->vgaScaleX(100);
+ }
+
+ while (row < 4) {
+ while (col < 4) {
+ if (showsolution)
+ num = SOLUTION[col][row];
+ else
+ num = _curTile[col][row];
+
+ if (showsolution || num)
+ _tiles[num]->drawImage(cols + (col * colm), rows + (row * rowm));
+
+ col++;
+ }
+
+ row++;
+ col = 0;
+ }
+}
+
+void SpecialLocks::showTileLock(const Common::String filename, bool showSolution) {
+ _vm->_anim->_doBlack = true;
+ _vm->_anim->_noPalChange = true;
+ _vm->_graphics->readPict(filename);
+ _vm->_anim->_noPalChange = false;
+ _vm->_graphics->blackScreen();
+
+ Common::File *tileFile = _vm->_resource->openDataFile(showSolution ? "P:TileSolution" : "P:Tile");
+
+ int start = showSolution ? 0 : 1;
+
+ for (int curBit = start; curBit < 16; curBit++)
+ _tiles[curBit] = new Image(tileFile, _vm);
+
+ delete tileFile;
+
+ doTile(showSolution);
+ _vm->_graphics->setPalette(_vm->_anim->_diffPalette, 256);
+}
+
+void SpecialLocks::doTileScroll(uint16 col, uint16 row, uint16 scrolltype) {
+ int16 dX = 0, dY = 0, dx = 0, dy = 0, sx = 0, sy = 0;
+ int last = 0;
+
+ if (scrolltype == kScrollLeft) {
+ dX = _vm->_utils->vgaScaleX(5);
+ sx = _vm->_utils->vgaScaleX(5);
+ last = 6;
+ } else if (scrolltype == kScrollRight) {
+ dX = _vm->_utils->vgaScaleX(-5);
+ dx = _vm->_utils->vgaScaleX(-5);
+ sx = _vm->_utils->vgaScaleX(5);
+ last = 6;
+ } else if (scrolltype == kScrollUp) {
+ dY = _vm->_utils->vgaScaleY(5);
+ sy = _vm->_utils->vgaScaleY(5);
+ last = 5;
+ } else if (scrolltype == kScrollDown) {
+ dY = _vm->_utils->vgaScaleY(-5);
+ dy = _vm->_utils->vgaScaleY(-5);
+ sy = _vm->_utils->vgaScaleY(5);
+ last = 5;
+ }
+
+ sx += _vm->_utils->svgaCord(2);
+
+ uint16 x1 = _vm->_utils->vgaScaleX(100) + (col * _vm->_utils->vgaScaleX(30)) + dx;
+ uint16 y1 = _vm->_utils->vgaScaleY(25) + (row * _vm->_utils->vgaScaleY(25)) + dy;
+
+ byte *buffer = new byte[_tiles[1]->_width * _tiles[1]->_height * 2];
+
+ for (int i = 0; i < last; i++) {
+ _vm->waitTOF();
+ scrollRaster(dX, dY, x1, y1, x1 + _vm->_utils->vgaScaleX(28) + sx, y1 + _vm->_utils->vgaScaleY(23) + sy, buffer);
+ x1 += dX;
+ y1 += dY;
+ }
+
+ delete[] buffer;
+}
+
+void SpecialLocks::scrollRaster(int16 dx, int16 dy, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer) {
+ if (dx)
+ _vm->_graphics->scrollDisplayX(dx, x1, y1, x2, y2, buffer);
+
+ if (dy)
+ _vm->_graphics->scrollDisplayY(dy, x1, y1, x2, y2, buffer);
+}
+
+void SpecialLocks::changeCombination(uint16 number) {
+ const int solution[6] = { 0, 4, 0, 8, 7, 2 };
+
+ Image display(_vm);
+
+ if (_combination[number] < 9)
+ (_combination[number])++;
+ else
+ _combination[number] = 0;
+
+ uint16 combnum = _combination[number];
+
+ display.setData(_vm->_graphics->getCurrentDrawingBuffer(), false);
+ display._width = _vm->_graphics->_screenWidth;
+ display._height = _vm->_graphics->_screenHeight;
+
+ byte *buffer = new byte[_numberImages[1]->_width * _numberImages[1]->_height * 2];
+
+ for (int i = 1; i <= (_numberImages[combnum]->_height / 2); i++) {
+ if (_vm->_isHiRes) {
+ if (i & 1)
+ _vm->waitTOF();
+ }
+ else
+ _vm->waitTOF();
+
+ display.setData(_vm->_graphics->getCurrentDrawingBuffer(), false);
+ _vm->_graphics->scrollDisplayY(2, _vm->_utils->vgaScaleX(COMBINATION_X[number]), _vm->_utils->vgaScaleY(65), _vm->_utils->vgaScaleX(COMBINATION_X[number]) + (_numberImages[combnum])->_width - 1, _vm->_utils->vgaScaleY(65) + (_numberImages[combnum])->_height, buffer);
+ _numberImages[combnum]->blitBitmap(0, (_numberImages[combnum])->_height - (2 * i), &(display), _vm->_utils->vgaScaleX(COMBINATION_X[number]), _vm->_utils->vgaScaleY(65), (_numberImages[combnum])->_width, 2, false);
+ }
+
+ delete[] buffer;
+
+ bool unlocked = true;
+ for (int i = 0; i < 6; i++)
+ unlocked &= (_combination[i] == solution[i]);
+
+ if (unlocked)
+ _vm->_conditions->inclElement(COMBINATIONUNLOCKED);
+ else
+ _vm->_conditions->exclElement(COMBINATIONUNLOCKED);
+}
+
+void SpecialLocks::showCombinationLock(const Common::String filename) {
+ _vm->_anim->_doBlack = true;
+ _vm->_anim->_noPalChange = true;
+ _vm->_graphics->readPict(filename);
+ _vm->_anim->_noPalChange = false;
+
+ _vm->_graphics->blackScreen();
+
+ Common::File *numFile = _vm->_resource->openDataFile("P:Numbers");
+
+ for (int i = 0; i < 10; i++) {
+ _numberImages[i] = new Image(numFile, _vm);
+ }
+
+ delete numFile;
+
+ for (int i = 0; i <= 5; i++)
+ _numberImages[_combination[i]]->drawImage(_vm->_utils->vgaScaleX(COMBINATION_X[i]), _vm->_utils->vgaScaleY(65));
+
+ _vm->_graphics->setPalette(_vm->_anim->_diffPalette, 256);
+}
+
+void SpecialLocks::save(Common::OutSaveFile *file) {
+ // Combination lock
+ for (int i = 0; i < 6; i++)
+ file->writeByte(_combination[i]);
+
+ // Tiles
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ file->writeUint16LE(_curTile[i][j]);
+}
+
+void SpecialLocks::load(Common::InSaveFile *file) {
+ // Combination lock
+ for (int i = 0; i < 6; i++)
+ _combination[i] = file->readByte();
+
+ // Tiles
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ _curTile[i][j] = file->readUint16LE();
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/speciallocks.h b/engines/lab/speciallocks.h
new file mode 100644
index 0000000000..424eba242a
--- /dev/null
+++ b/engines/lab/speciallocks.h
@@ -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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_TILEPUZZLE_H
+#define LAB_TILEPUZZLE_H
+
+#include "common/savefile.h"
+
+namespace Lab {
+
+class LabEngine;
+
+class SpecialLocks {
+private:
+ LabEngine *_vm;
+ Image *_tiles[16];
+ Image *_numberImages[10];
+ uint16 _curTile[4][4];
+ byte _combination[6];
+
+public:
+ SpecialLocks(LabEngine *vm);
+ ~SpecialLocks();
+
+ void showTileLock(const Common::String filename, bool showSolution);
+
+ /**
+ * Processes mouse clicks and changes tile positions.
+ */
+ void tileClick(Common::Point pos);
+
+ void showCombinationLock(const Common::String filename);
+
+ /**
+ * Processes mouse clicks and changes the door combination.
+ */
+ void combinationClick(Common::Point pos);
+
+ void save(Common::OutSaveFile *file);
+ void load(Common::InSaveFile *file);
+
+private:
+ /**
+ * Changes the combination number of one of the slots
+ */
+ void changeCombination(uint16 number);
+
+ /**
+ * Changes the tile positions in the tile puzzle
+ */
+ void changeTile(uint16 col, uint16 row);
+
+ /**
+ * Draws the images of the combination lock to the display bitmap.
+ */
+ void doTile(bool showsolution);
+
+ /**
+ * Does the scrolling for the tiles on the tile puzzle.
+ */
+ void doTileScroll(uint16 col, uint16 row, uint16 scrolltype);
+ void scrollRaster(int16 dx, int16 dy, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer);
+};
+
+} // End of namespace Lab
+
+#endif // LAB_TILEPUZZLE_H
diff --git a/engines/lab/utils.cpp b/engines/lab/utils.cpp
new file mode 100644
index 0000000000..a1409d231b
--- /dev/null
+++ b/engines/lab/utils.cpp
@@ -0,0 +1,272 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#include "common/file.h"
+
+#include "lab/lab.h"
+#include "lab/utils.h"
+
+namespace Lab {
+Utils::Utils(LabEngine *vm) : _vm(vm), _rnd("lab") {
+ _dataBytesPerRow = 0;
+}
+
+uint16 Utils::scaleX(uint16 x) {
+ if (_vm->_isHiRes)
+ return (uint16)((x * 16) / 9);
+ else
+ return (uint16)((x * 8) / 9);
+}
+
+uint16 Utils::scaleY(uint16 y) {
+ if (_vm->_isHiRes)
+ return (y + (y / 14));
+ else
+ return ((y * 10) / 24);
+}
+
+Common::Rect Utils::rectScale(int16 x1, int16 y1, int16 x2, int16 y2) {
+ return Common::Rect(scaleX(x1), scaleY(y1), scaleX(x2), scaleY(y2));
+}
+
+uint16 Utils::mapScaleX(uint16 x) {
+ if (_vm->_isHiRes)
+ return (x - 45);
+ else
+ return ((x - 45) >> 1);
+}
+
+uint16 Utils::mapScaleY(uint16 y) {
+ if (_vm->_isHiRes)
+ return y;
+ else
+ return ((y - 35) >> 1) - (y >> 6);
+}
+
+Common::Rect Utils::mapRectScale(int16 x1, int16 y1, int16 x2, int16 y2) {
+ return Common::Rect(mapScaleX(x1), mapScaleY(y1), mapScaleX(x2), mapScaleY(y2));
+}
+
+int16 Utils::vgaScaleX(int16 x) {
+ if (_vm->_isHiRes)
+ return (x * 2);
+ else
+ return x;
+}
+
+int16 Utils::vgaScaleY(int16 y) {
+ if (_vm->_isHiRes)
+ return ((y * 12) / 5);
+ else
+ return y;
+}
+
+Common::Rect Utils::vgaRectScale(int16 x1, int16 y1, int16 x2, int16 y2) {
+ return Common::Rect(vgaScaleX(x1), vgaScaleY(y1), vgaScaleX(x2), vgaScaleY(y2));
+}
+
+uint16 Utils::svgaCord(uint16 cord) {
+ if (_vm->_isHiRes)
+ return cord;
+ else
+ return 0;
+}
+
+Common::Point Utils::vgaUnscale(Common::Point pos) {
+ Common::Point result;
+ if (_vm->_isHiRes) {
+ result.x = pos.x / 2;
+ result.y = (pos.y * 5) / 12;
+ } else
+ result = pos;
+
+ return result;
+}
+
+template<typename T>
+void Utils::unDiff(T *dest, Common::File *sourceFile) {
+ byte bytesPerWord = sizeof(T);
+
+ while (1) {
+ uint16 skip = sourceFile->readByte();
+ uint16 copy = sourceFile->readByte();
+
+ if (skip == 255) {
+ if (copy == 0) {
+ skip = sourceFile->readUint16LE();
+ copy = sourceFile->readUint16LE();
+ } else if (copy == 255)
+ return;
+ }
+
+ dest += skip;
+
+ if (bytesPerWord == 1) {
+ sourceFile->read(dest, copy);
+ dest += copy;
+ } else {
+ while (copy) {
+ *dest = sourceFile->readUint16LE();
+ dest++;
+ copy--;
+ }
+ }
+ }
+}
+
+template<typename T>
+void Utils::verticalUnDiff(T *dest, Common::File *sourceFile, uint16 bytesPerRow) {
+ uint16 counter = 0;
+ byte bytesPerWord = sizeof(T);
+ uint16 wordsPerRow = bytesPerRow / bytesPerWord;
+
+ while (counter < wordsPerRow) {
+ T *curPtr = dest + counter;
+
+ for (;;) {
+ uint16 skip = sourceFile->readByte();
+ uint16 copy = sourceFile->readByte();
+
+ if (skip == 255) {
+ counter += copy;
+ break;
+ } else {
+ curPtr += (skip * wordsPerRow);
+
+ while (copy) {
+ if (bytesPerWord == 1)
+ *curPtr++ = sourceFile->readByte();
+ else if (bytesPerWord == 2)
+ *curPtr = sourceFile->readUint16LE();
+ else if (bytesPerWord == 4)
+ *curPtr = sourceFile->readUint32LE();
+ else
+ error("verticalUnDiff: Invalid bytesPerWord (%d)", bytesPerWord);
+ curPtr += wordsPerRow;
+ copy--;
+ }
+ }
+ }
+ }
+}
+
+void Utils::runLengthDecode(byte *dest, Common::File *sourceFile) {
+ int8 num;
+ int16 count;
+
+ while (1) {
+ num = sourceFile->readSByte();
+
+ if (num == 127) {
+ return;
+ } else if (num > '\0') {
+ sourceFile->read(dest, num);
+ dest += num;
+ } else {
+ count = (int16)(-num);
+ num = sourceFile->readSByte();
+
+ while (count) {
+ *dest = num;
+ dest++;
+ count--;
+ }
+ }
+ }
+}
+
+void Utils::verticalRunLengthDecode(byte *dest, Common::File *sourceFile, uint16 bytesPerRow) {
+ int16 count;
+ byte *top = dest;
+
+ for (int i = 0; i < _dataBytesPerRow; i++) {
+ dest = top;
+ dest += i;
+
+ int8 num = sourceFile->readSByte();
+
+ while (num != 127) {
+ if (num > '\0') {
+ while (num) {
+ *dest = sourceFile->readByte();
+ dest += bytesPerRow;
+ num--;
+ }
+ } else {
+ count = (int16)(-num);
+ num = sourceFile->readSByte();
+
+ while (count) {
+ *dest = num;
+ dest += bytesPerRow;
+ count--;
+ }
+ }
+
+ num = sourceFile->readSByte();
+ }
+ }
+}
+
+void Utils::unDiff(byte *newBuf, byte *oldBuf, Common::File *sourceFile, uint16 bytesPerRow, bool isVertical) {
+ sourceFile->skip(1);
+ byte bufType = sourceFile->readByte();
+
+ if (isVertical) {
+ if (bufType == 0)
+ verticalUnDiff<byte>(newBuf, sourceFile, bytesPerRow);
+ else if (bufType == 1)
+ verticalUnDiff<uint16>((uint16 *)newBuf, sourceFile, bytesPerRow);
+ else if (bufType == 3)
+ verticalUnDiff<uint32>((uint32 *)newBuf, sourceFile, bytesPerRow);
+ else
+ error("Unexpected variable compression scheme %d", bufType);
+ } else {
+ if (bufType == 0)
+ unDiff<byte>(newBuf, sourceFile);
+ else if (bufType == 1)
+ unDiff<uint16>((uint16 *)newBuf, sourceFile);
+ else
+ error("Unexpected compression scheme %d", bufType);
+ }
+}
+
+void Utils::setBytesPerRow(int num) {
+ _dataBytesPerRow = num;
+}
+
+uint16 Utils::getRandom(uint16 max) {
+ if (max > 1)
+ return _rnd.getRandomNumber(max - 1);
+ else
+ return 0;
+}
+
+} // End of namespace Lab
diff --git a/engines/lab/utils.h b/engines/lab/utils.h
new file mode 100644
index 0000000000..a7bb42007e
--- /dev/null
+++ b/engines/lab/utils.h
@@ -0,0 +1,105 @@
+/* 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.
+ *
+ */
+
+/*
+ * This code is based on Labyrinth of Time code with assistance of
+ *
+ * Copyright (c) 1993 Terra Nova Development
+ * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
+ *
+ */
+
+#ifndef LAB_UTILS_H
+#define LAB_UTILS_H
+
+namespace Lab {
+
+class Utils {
+private:
+ LabEngine *_vm;
+ uint16 _dataBytesPerRow;
+
+ /**
+ * Undiffs a piece of memory based on the header size.
+ */
+ template<typename T>
+ void unDiff(T *dest, Common::File *sourceFile);
+
+ /**
+ * Undiffs a piece of memory when header size is a byte, and copy/skip size
+ * is a byte or a word or a double word.
+ */
+ template<typename T>
+ void verticalUnDiff(T *dest, Common::File *sourceFile, uint16 bytesPerRow);
+
+public:
+ Utils(LabEngine *vm);
+
+ Common::RandomSource _rnd;
+
+ /**
+ * Scales the x co-ordinates to that of the new display. In the room parser
+ * file, co-ordinates are set up on a 360x336 display.
+ */
+ uint16 scaleX(uint16 x);
+
+ /**
+ * Scales the y co-ordinates to that of the new display. In the room parser
+ * file, co-ordinates are set up on a 368x336 display.
+ */
+ uint16 scaleY(uint16 y);
+ Common::Rect rectScale(int16 x1, int16 y1, int16 x2, int16 y2);
+
+ /**
+ * Scales the VGA x coords to SVGA if necessary; otherwise, returns VGA coords.
+ */
+ int16 vgaScaleX(int16 x);
+
+ /**
+ * Scales the VGA y coords to SVGA if necessary; otherwise, returns VGA coords.
+ */
+ int16 vgaScaleY(int16 y);
+ Common::Rect vgaRectScale(int16 x1, int16 y1, int16 x2, int16 y2);
+ uint16 svgaCord(uint16 cord);
+ uint16 mapScaleX(uint16 x);
+ uint16 mapScaleY(uint16 y);
+ Common::Rect mapRectScale(int16 x1, int16 y1, int16 x2, int16 y2);
+
+ /**
+ * Converts SVGA coords to VGA if necessary, otherwise returns VGA coords.
+ */
+ Common::Point vgaUnscale(Common::Point pos);
+
+ /**
+ * Does the undiffing between the bitmaps.
+ */
+ void unDiff(byte *newBuf, byte *oldBuf, Common::File *sourceFile, uint16 bytesPerRow, bool isVertical);
+ void runLengthDecode(byte *dest, Common::File *sourceFile);
+ void verticalRunLengthDecode(byte *dest, Common::File *sourceFile, uint16 bytesPerRow);
+ void setBytesPerRow(int num);
+ uint16 getRandom(uint16 max);
+};
+
+
+} // End of namespace Lab
+
+#endif // LAB_UTILS_H