diff options
Diffstat (limited to 'engines/lab')
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(¬eFont); +} + +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 |