diff options
author | Willem Jan Palenstijn | 2015-12-23 21:43:35 +0100 |
---|---|---|
committer | Willem Jan Palenstijn | 2015-12-23 21:43:35 +0100 |
commit | ff93e55afd6467d7a53c836db931de786f8f7d63 (patch) | |
tree | c5d0474e99fbb92c48c2295beba8f7b65fbb610c /engines | |
parent | 9b53626ce096931a86422dbe56ea3e53ba6e9502 (diff) | |
parent | f9641a6d669ba5e361dffb91e832863bc1758812 (diff) | |
download | scummvm-rg350-ff93e55afd6467d7a53c836db931de786f8f7d63.tar.gz scummvm-rg350-ff93e55afd6467d7a53c836db931de786f8f7d63.tar.bz2 scummvm-rg350-ff93e55afd6467d7a53c836db931de786f8f7d63.zip |
Merge pull request #636 from sev-/lab
This is a pull request for the game The Labyrinth of Time. This game is
currently available on www.wyrmkeep.com and on GOG.com. The game should
be completable: it was completable a month ago and we did regularly
regression testing. All the work has been based on sources kindly
provided by Wyrmkeep.
The DOS and the Windows versions are supported by this engine.
This is a manual merge of the PR, with some history fixups.
Diffstat (limited to 'engines')
34 files changed, 9611 insertions, 0 deletions
diff --git a/engines/lab/anim.cpp b/engines/lab/anim.cpp new file mode 100644 index 0000000000..5f469d8188 --- /dev/null +++ b/engines/lab/anim.cpp @@ -0,0 +1,350 @@ +/* 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; + _curBit = 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; +} + +Anim::~Anim() { + delete[] _vm->_anim->_scrollScreenBuffer; + _vm->_anim->_scrollScreenBuffer = nullptr; +} + +void Anim::diffNextFrame(bool onlyDiffData) { + if (_lastBlockHeader == 65535) + // Already done. + return; + + BitMap *disp = _vm->_graphics->_dispBitMap; + if (disp->_drawOnScreen) + disp->_planes[0] = _vm->_graphics->getCurrentDrawingBuffer(); + + disp->_planes[1] = disp->_planes[0] + 0x10000; + disp->_planes[2] = disp->_planes[1] + 0x10000; + disp->_planes[3] = disp->_planes[2] + 0x10000; + disp->_planes[4] = disp->_planes[3] + 0x10000; + + _vm->_event->mouseHide(); + + while (1) { + if (_curBit >= _numChunks) { + _vm->_event->mouseShow(); + + 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); + _curBit = 0; + + if (disp->_drawOnScreen) + _vm->_graphics->screenUpdate(); + + // done with the next frame. + return; + } + + _vm->updateMusicAndEvents(); + _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(disp->_planes[_curBit], _size); + } + _curBit++; + break; + + case 11: + curPos = _diffFile->pos(); + _diffFile->skip(4); + _vm->_utils->runLengthDecode(disp->_planes[_curBit], _diffFile); + _curBit++; + _diffFile->seek(curPos + _size, SEEK_SET); + break; + + case 12: + curPos = _diffFile->pos(); + _diffFile->skip(4); + _vm->_utils->verticalRunLengthDecode(disp->_planes[_curBit], _diffFile, disp->_bytesPerRow); + _curBit++; + _diffFile->seek(curPos + _size, SEEK_SET); + break; + + case 20: + curPos = _diffFile->pos(); + _vm->_utils->unDiff(disp->_planes[_curBit], disp->_planes[_curBit], _diffFile, disp->_bytesPerRow, false); + _curBit++; + _diffFile->seek(curPos + _size, SEEK_SET); + break; + + case 21: + curPos = _diffFile->pos(); + _vm->_utils->unDiff(disp->_planes[_curBit], disp->_planes[_curBit], _diffFile, disp->_bytesPerRow, true); + _curBit++; + _diffFile->seek(curPos + _size, SEEK_SET); + break; + + case 25: + _curBit++; + break; + + case 26: + _curBit++; + break; + + case 30: + case 31: + if (_waitForEffect) { + while (_vm->_music->isSoundEffectActive()) { + _vm->updateMusicAndEvents(); + _vm->waitTOF(); + } + } + + _size -= 8; + + _diffFile->skip(4); + _sampleSpeed = _diffFile->readUint16LE(); + _diffFile->skip(2); + + _vm->_music->playSoundEffect(_sampleSpeed, _size, _diffFile); + break; + + case 65535: + if ((_frameNum == 1) || _playOnce || _stopPlayingEnd) { + bool didTOF = false; + + if (_waitForEffect) { + while (_vm->_music->isSoundEffectActive()) { + _vm->updateMusicAndEvents(); + _vm->waitTOF(); + + if (disp->_drawOnScreen) + didTOF = true; + } + } + + _isPlaying = false; + _vm->_event->mouseShow(); + + 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->updateMusicAndEvents(); + diffNextFrame(); + } +} + +void Anim::readDiff(Common::File *diffFile, bool playOnce, bool onlyDiffData) { + _playOnce = playOnce; + _delayMicros = 0; + _curBit = 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); + + _numChunks = (((int32)_diffWidth) * _diffHeight) / 0x10000; + + if ((uint32)(_numChunks * 0x10000) < (uint32)(((int32)_diffWidth) * _diffHeight)) + _numChunks++; + + assert(_numChunks < 16); + + 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..db2e23e9b5 --- /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; +}; + +struct BitMap { + uint16 _bytesPerRow; + bool _drawOnScreen; + byte *_planes[16]; +}; + +class Anim { +private: + LabEngine *_vm; + + uint32 _lastBlockHeader; + uint16 _curBit; + 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; + +public: + Anim(LabEngine *vm); + virtual ~Anim(); + + DIFFHeader _headerdata; + 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 readDiff(Common::File *diffFile, bool playOnce, bool onlyDiffData = false); + void diffNextFrame(bool onlyDiffData = false); + + /** + * Stops an animation from running. + */ + void stopDiff(); + + /** + * Stops an animation from running. + */ + void stopDiffEnd(); +}; + +} // 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..0a6167951d --- /dev/null +++ b/engines/lab/console.cpp @@ -0,0 +1,130 @@ +/* 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/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]); + + 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"); + + while (rule->_actionList) { + Action *action = rule->_actionList; + debugPrintf(" - %s ('%s', %d, %d, %d)\n", actionTypes[action->_actionType], action->_messages[0].c_str(), action->_param1, action->_param2, action->_param3); + rule->_actionList = rule->_actionList->_nextAction; + } + } + + 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) { + while (rule->_actionList) { + if (rule->_actionList->_actionType == actionId && + (rule->_actionList->_param1 == param1 || param1 == -1) && + (rule->_actionList->_param2 == param2 || param2 == -1) && + (rule->_actionList->_param3 == param3 || param3 == -1)) { + debugPrintf("Found at script %d\n", i); + } + + rule->_actionList = rule->_actionList->_nextAction; + } + } + } + + 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..0810c4cb44 --- /dev/null +++ b/engines/lab/detection.cpp @@ -0,0 +1,252 @@ +/* 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 + { "notes11", 0, "63e873f659f8f46f9809d16a2bf653c7", 3562 }, // fonts/notes11 + { "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 + { "notes11", 0, "63e873f659f8f46f9809d16a2bf653c7", 3562 }, // fonts/notes11 + { "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 + { "notes11", 0, "63e873f659f8f46f9809d16a2bf653c7", 3562 }, // fonts/notes11 + { "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[] = { + "fonts", + "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..8def025629 --- /dev/null +++ b/engines/lab/dispman.cpp @@ -0,0 +1,1005 @@ +/* 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 = 65536; + _curPen = 0; + _curBitmap = nullptr; + _displayBuffer = nullptr; + _currentDisplayBuffer = nullptr; + _fadePalette = nullptr; + + _screenWidth = 0; + _screenHeight = 0; + + for (int i = 0; i < 256 * 3; i++) + _curvgapal[i] = 0; + + _dispBitMap = new BitMap; +} + +DisplayMan::~DisplayMan() { + freePict(); + delete _dispBitMap; + 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, uint16 maxHeight) { + _vm->_anim->stopDiff(); + loadPict(filename); + _vm->updateMusicAndEvents(); + + if (!_vm->_music->_loopSoundEffect) + _vm->_music->stopSoundEffect(); + + _dispBitMap->_bytesPerRow = _screenWidth; + _dispBitMap->_drawOnScreen = (memoryBuffer == nullptr); + if (memoryBuffer) + _dispBitMap->_planes[0] = 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; + bool doit = true; + + lineWidth += textLength(tf, " "); + + while ((*mainBuffer)[0] && doit) { + Common::String wordBuffer = getWord(*mainBuffer) + " "; + + if ((curWidth + textLength(tf, wordBuffer)) <= lineWidth) { + result += wordBuffer; + (*mainBuffer) += wordBuffer.size() - 1; + + if ((*mainBuffer)[0] == '\n') + doit = false; + + if ((*mainBuffer)[0]) + (*mainBuffer)++; + + curWidth = textLength(tf, result); + } else + doit = false; + } + + 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) { + if (fillBack) { + setPen(backPen); + rectFill(textRect); + } + + if (!str) + return 0; + + setPen(penColor); + + TextFont *msgFont = font; + uint16 fontHeight = textHeight(msgFont) + spacing; + uint16 numLines = (textRect.height() + 1) / fontHeight; + uint16 width = textRect.width() + 1; + uint16 y = textRect.top; + Common::String lineBuffer; + + if (centerv && output) { + const char *temp = str; + uint16 actlines = 0; + + while (temp[0]) { + lineBuffer = getLine(msgFont, &temp, width); + actlines++; + } + + if (actlines <= numLines) + y += ((textRect.height() + 1) - (actlines * fontHeight)) / 2; + } + + int len = 0; + while (numLines && str[0]) { + lineBuffer = getLine(msgFont, &str, width); + + uint16 x = textRect.left; + len += lineBuffer.size(); + + if (centerh) + x += (width - textLength(msgFont, lineBuffer)) / 2; + + if (output) + drawText(msgFont, x, y, penColor, lineBuffer); + + numLines--; + y += fontHeight; + } + + len--; + + return len; +} + +int DisplayMan::flowTextToMem(Image *destIm, TextFont *font, int16 spacing, byte penColor, + byte backPen, bool fillBack, bool centerh, bool centerv, bool output, Common::Rect textRect, + const char *str) { + byte *saveDisplayBuffer = _currentDisplayBuffer; + uint32 bytesPerPage = _screenBytesPerPage; + + _currentDisplayBuffer = destIm->_imageData; + _screenBytesPerPage = (uint32)destIm->_width * (int32)destIm->_height; + + int res = flowText(font, spacing, penColor, backPen, fillBack, centerh, centerv, output, textRect, str); + + _screenBytesPerPage = bytesPerPage; + _currentDisplayBuffer = saveDisplayBuffer; + + return res; +} + +void DisplayMan::createBox(uint16 y2) { + // Message box area + setPen(7); + rectFillScaled(4, 154, 315, y2 - 2); + + // Box around message area + setPen(0); + drawHLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleX(317)); + drawVLine(_vm->_utils->vgaScaleX(317), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleY(y2)); + drawHLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(y2), _vm->_utils->vgaScaleX(317)); + drawVLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleY(y2)); +} + +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); + _vm->_event->mouseHide(); + + if (!_longWinInFront) { + _longWinInFront = true; + // Clear Area + setPen(3); + rectFill(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), _vm->_utils->vgaScaleY(199)); + } + + createBox(198); + _vm->_event->mouseShow(); + + 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(); + } + + _vm->_event->mouseHide(); + createBox(168); + drawText(_vm->_msgFont, _vm->_utils->vgaScaleX(7), _vm->_utils->vgaScaleY(155) + _vm->_utils->svgaCord(2), 1, str); + _vm->_event->mouseShow(); + _lastMessageLong = false; + } +} + +void DisplayMan::drawPanel() { + _vm->_event->mouseHide(); + + // Clear Area + setPen(3); + rectFill(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), _vm->_utils->vgaScaleY(199)); + + // First Line + setPen(0); + drawHLine(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319)); + // Second Line + setPen(5); + drawHLine(0, _vm->_utils->vgaScaleY(149) + 1 + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319)); + // Button Separators + setPen(0); + // First black line to separate buttons + drawHLine(0, _vm->_utils->vgaScaleY(170), _vm->_utils->vgaScaleX(319)); + + if (!_vm->_alternate) { + setPen(4); + // The horizontal lines under the black one + drawHLine(0, _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(319)); + _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)); + drawVLine(_vm->_utils->vgaScaleX(194), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199)); + } else { + // Vertical Black lines + drawVLine(_vm->_utils->vgaScaleX(90), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199)); + drawVLine(_vm->_utils->vgaScaleX(160), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199)); + drawVLine(_vm->_utils->vgaScaleX(230), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199)); + } + + setPen(4); + // The horizontal lines under the black one + drawHLine(0, _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(122)); + drawHLine(_vm->_utils->vgaScaleX(126), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(192)); + drawHLine(_vm->_utils->vgaScaleX(196), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(319)); + // The vertical high light lines + drawVLine(_vm->_utils->vgaScaleX(1), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198)); + + if (_vm->getPlatform() != Common::kPlatformWindows) { + drawVLine(_vm->_utils->vgaScaleX(126), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198)); + drawVLine(_vm->_utils->vgaScaleX(196), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198)); + } else { + drawVLine(_vm->_utils->vgaScaleX(92), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198)); + drawVLine(_vm->_utils->vgaScaleX(162), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198)); + drawVLine(_vm->_utils->vgaScaleX(232), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198)); + } + + _vm->_event->drawButtonList(&_vm->_invButtonList); + } + + _vm->_event->mouseShow(); +} + +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, 't', moveImages[0], moveImages[1])); + moveButtonList->push_back(e->createButton( 33, y, 1, 'm', moveImages[2], moveImages[3])); + moveButtonList->push_back(e->createButton( 65, y, 2, 'o', moveImages[4], moveImages[5])); + moveButtonList->push_back(e->createButton( 97, y, 3, 'c', moveImages[6], moveImages[7])); + moveButtonList->push_back(e->createButton(129, y, 4, 'l', moveImages[8], moveImages[9])); + moveButtonList->push_back(e->createButton(161, y, 5, 'i', moveImages[12], moveImages[13])); + moveButtonList->push_back(e->createButton(193, y, 6, VKEY_LTARROW, moveImages[14], moveImages[15])); + moveButtonList->push_back(e->createButton(225, y, 7, VKEY_UPARROW, moveImages[16], moveImages[17])); + moveButtonList->push_back(e->createButton(257, y, 8, VKEY_RTARROW, moveImages[18], moveImages[19])); + moveButtonList->push_back(e->createButton(289, y, 9, '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, 'm', invImages[0], invImages[1])); + invButtonList->push_back(e->createButton( 56, y, 1, 'g', invImages[2], invImages[3])); + invButtonList->push_back(e->createButton( 94, y, 2, 'u', invImages[4], invImages[5])); + invButtonList->push_back(e->createButton(126, y, 3, 'l', moveImages[8], moveImages[9])); + invButtonList->push_back(e->createButton(164, y, 4, VKEY_LTARROW, moveImages[14], moveImages[15])); + invButtonList->push_back(e->createButton(196, y, 5, VKEY_RTARROW, 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, 'b', invImages[6], invImages[7])); + invButtonList->push_back(e->createButton(266, y, 7, 'f', invImages[8], invImages[9])); + } + + delete invFile; +} + +void DisplayMan::setPen(byte penNum) { + _curPen = penNum; +} + +void DisplayMan::rectFill(Common::Rect fillRect) { + 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)) { + char *d = (char *)getCurrentDrawingBuffer() + fillRect.top * _screenWidth + fillRect.left; + + while (height-- > 0) { + char *dd = d; + int ww = width; + + while (ww-- > 0) { + *dd++ = _curPen; + } + + d += _screenWidth; + } + } +} + +void DisplayMan::rectFill(uint16 x1, uint16 y1, uint16 x2, uint16 y2) { + rectFill(Common::Rect(x1, y1, x2, y2)); +} + +void DisplayMan::rectFillScaled(uint16 x1, uint16 y1, uint16 x2, uint16 y2) { + rectFill(_vm->_utils->vgaRectScale(x1, y1, x2, y2)); +} + +void DisplayMan::drawVLine(uint16 x, uint16 y1, uint16 y2) { + rectFill(x, y1, x, y2); +} + +void DisplayMan::drawHLine(uint16 x1, uint16 y, uint16 x2) { + rectFill(x1, y, x2, y); +} + +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]; +} + +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) { + byte tmp[256 * 3]; + + for (int i = 0; i < 256 * 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 = (byte *)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::closeFont(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 = (byte *)(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->updateMusicAndEvents(); + _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->updateMusicAndEvents(); + _vm->waitTOF(); + } + + readPict(filename, true, true); + setPalette(_vm->_anim->_diffPalette, 256); + byte *mem = _vm->_anim->_scrollScreenBuffer; + + _vm->updateMusicAndEvents(); + uint16 by = _vm->_utils->vgaScaleX(3); + uint16 nheight = height; + uint16 startLine = 0, onRow = 0; + + while (onRow < _vm->_anim->_headerdata._height) { + _vm->updateMusicAndEvents(); + + if ((by > nheight) && nheight) + by = nheight; + + if ((startLine + by) > (_vm->_anim->_headerdata._height - 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->updateMusicAndEvents(); + int startLine = _vm->_anim->_headerdata._height - height - 1; + + for (int i = 0; i < 5; i++) { + _vm->updateMusicAndEvents(); + startLine -= (5 - i) * multiplier; + copyPage(width, height, 0, startLine, mem); + _vm->waitTOF(); + } + + for (int i = 8; i > 0; i--) { + _vm->updateMusicAndEvents(); + startLine += offsets[i - 1] * multiplier; + copyPage(width, height, 0, startLine, mem); + _vm->waitTOF(); + } + + _vm->_event->mouseShow(); +} + +void DisplayMan::doTransWipe(CloseDataPtr *closePtrList, 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->updateMusicAndEvents(); + _vm->waitTOF(); + linesDone = 0; + } + + if (j == 9) + checkerboardEffect(0, 0, curY, _screenWidth - 1, curY + 1); + else + rectFill(0, curY, _screenWidth - 1, curY + 1); + curY += 4; + linesDone++; + } // while + } // for i + + setPen(0); + } // for j + + if (filename.empty()) + _vm->_curFileName = _vm->getPictName(closePtrList); + else if (filename[0] > ' ') + _vm->_curFileName = filename; + else + _vm->_curFileName = _vm->getPictName(closePtrList); + + byte *bitMapBuffer = new byte[_screenWidth * (lastY + 5)]; + readPict(_vm->_curFileName, true, false, bitMapBuffer, lastY + 5); + + setPalette(_vm->_anim->_diffPalette, 256); + + Image imSource(_vm); + imSource._width = _screenWidth; + imSource._height = lastY; + imSource._imageData = bitMapBuffer; + + Image imDest(_vm); + imDest._width = _screenWidth; + imDest._height = _screenHeight; + imDest._imageData = getCurrentDrawingBuffer(); + + 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->updateMusicAndEvents(); + _vm->waitTOF(); + linesDone = 0; + } + + imDest._imageData = getCurrentDrawingBuffer(); + + if (j == 0) { + imSource.blitBitmap(0, curY, &imDest, 0, curY, _screenWidth, 2, false); + checkerboardEffect(0, 0, curY, _screenWidth - 1, curY + 1); + } else { + uint16 bitmapHeight = (curY == lastY) ? 1 : 2; + imSource.blitBitmap(0, curY, &imDest, 0, curY, _screenWidth, bitmapHeight, false); + } + curY += 4; + linesDone++; + } // while + } // for i + } // for j + + delete[] bitMapBuffer; +} + +void DisplayMan::doTransition(TransitionType transitionType, CloseDataPtr *closePtrList, const Common::String filename) { + switch (transitionType) { + case kTransitionWipe: + case kTransitionTransporter: + doTransWipe(closePtrList, filename); + break; + case kTransitionScrollWipe: + doScrollWipe(filename); + break; + case kTransitionScrollBlack: + doScrollBlack(); + break; + case kTransitionScrollBounce: + doScrollBounce(); + break; + case kTransitionReadFirstFrame: + readPict(filename, false); + break; + case kTransitionReadNextFrame: + _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 im(_vm); + im._imageData = buffer; + + if (x1 > x2) + SWAP<uint16>(x1, x2); + + if (y1 > y2) + SWAP<uint16>(y1, y2); + + if (dx > 0) { + im._width = x2 - x1 + 1 - dx; + im._height = y2 - y1 + 1; + + im.readScreenImage(x1, y1); + im.drawImage(x1 + dx, y1); + + setPen(0); + rectFill(x1, y1, x1 + dx - 1, y2); + } else if (dx < 0) { + im._width = x2 - x1 + 1 + dx; + im._height = y2 - y1 + 1; + + im.readScreenImage(x1 - dx, y1); + im.drawImage(x1, y1); + + setPen(0); + rectFill(x2 + dx + 1, y1, x2, y2); + } + + // Prevent the Image destructor from deleting the external buffer + im._imageData = nullptr; +} + +void DisplayMan::scrollDisplayY(int16 dy, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer) { + Image im(_vm); + im._imageData = buffer; + + if (x1 > x2) + SWAP<uint16>(x1, x2); + + if (y1 > y2) + SWAP<uint16>(y1, y2); + + if (dy > 0) { + im._width = x2 - x1 + 1; + im._height = y2 - y1 + 1 - dy; + + im.readScreenImage(x1, y1); + im.drawImage(x1, y1 + dy); + + setPen(0); + rectFill(x1, y1, x2, y1 + dy - 1); + } else if (dy < 0) { + im._width = x2 - x1 + 1; + im._height = y2 - y1 + 1 + dy; + + im.readScreenImage(x1, y1 - dy); + im.drawImage(x1, y1); + + setPen(0); + rectFill(x1, y2 + dy + 1, x2, y2); + } + + // Prevent the Image destructor from deleting the external buffer + im._imageData = nullptr; +} + +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 res) { + 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], 0x00F & res, i)) + + (0x0F0 & fadeNumIn(0x0F0 & _fadePalette[palIdx], 0x0F0 & res, i)) + + (0xF00 & fadeNumIn(0xF00 & _fadePalette[palIdx], 0xF00 & res, i)); + else + newPal[palIdx] = (0x00F & fadeNumOut(0x00F & _fadePalette[palIdx], 0x00F & res, i)) + + (0x0F0 & fadeNumOut(0x0F0 & _fadePalette[palIdx], 0x0F0 & res, i)) + + (0xF00 & fadeNumOut(0xF00 & _fadePalette[palIdx], 0xF00 & res, i)); + } + + setAmigaPal(newPal); + _vm->waitTOF(); + _vm->updateMusicAndEvents(); + } +} + +} // End of namespace Lab diff --git a/engines/lab/dispman.h b/engines/lab/dispman.h new file mode 100644 index 0000000000..b77a178bf8 --- /dev/null +++ b/engines/lab/dispman.h @@ -0,0 +1,297 @@ +/* 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 { + +struct BitMap; +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); + + byte _curPen; + Common::File *_curBitmap; + byte _curvgapal[256 * 3]; + +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, uint16 maxHeight = 0); + void freePict(); + + /** + * 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(CloseDataPtr *closePtrList, const Common::String filename); + + /** + * Does a certain number of pre-programmed wipes. + */ + void doTransition(TransitionType transitionType, CloseDataPtr *closePtrList, 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(); + void createBox(uint16 y2); + + /** + * Draws the control panel display. + */ + void drawPanel(); + + /** + * Sets up the Labyrinth screens, and opens up the initial windows. + */ + void setUpScreens(); + + int32 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; } + + /** + * Sets the pen number to use on all the drawing operations. + */ + void setPen(byte pennum); + + /** + * Fills in a rectangle. + */ + void rectFill(uint16 x1, uint16 y1, uint16 x2, uint16 y2); + void rectFill(Common::Rect fillRect); + void rectFillScaled(uint16 x1, uint16 y1, uint16 x2, uint16 y2); + /** + * 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); + + /** + * Calls flowText, but flows it to memory. Same restrictions as flowText. + * @param destIm Destination buffer + * @param font Pointer on the font used + * @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 flowTextToMem(Image *destIm, TextFont *font, int16 spacing, byte penColor, byte backPen, + bool fillBack, bool centerh, bool centerv, bool output, Common::Rect textRect, + const char *text); + + /** + * Draws a vertical line. + */ + void drawHLine(uint16 x, uint16 y1, uint16 y2); + + /** + * Draws a horizontal line. + */ + void drawVLine(uint16 x1, uint16 y, uint16 x2); + void screenUpdate(); + + /** + * Sets up either a low-res or a high-res 256 color screen. + */ + void createScreen(bool hiRes); + + /** + * 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, uint16 res); + + /** + * Closes a font and frees all memory associated with it. + */ + void closeFont(TextFont **font); + + /** + * Returns the length of a text in the specified font. + */ + uint16 textLength(TextFont *font, const Common::String text); + + /** + * Returns the height of a specified font. + */ + uint16 textHeight(TextFont *tf); + + /** + * 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); + + bool _longWinInFront; + bool _lastMessageLong; + bool _actionMessageShown; + uint32 _screenBytesPerPage; + int _screenWidth; + int _screenHeight; + byte *_displayBuffer; + byte *_currentDisplayBuffer; + uint16 *_fadePalette; + BitMap *_dispBitMap; +}; + +} // 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..cbac66e855 --- /dev/null +++ b/engines/lab/engine.cpp @@ -0,0 +1,1190 @@ +/* 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/intro.h" +#include "lab/labsets.h" +#include "lab/music.h" +#include "lab/processroom.h" +#include "lab/resource.h" +#include "lab/tilepuzzle.h" +#include "lab/utils.h" + +namespace Lab { + +// LAB: Labyrinth specific code for the special puzzles +#define SPECIALLOCK 100 +#define SPECIALBRICK 101 +#define SPECIALBRICKNOMOUSE 102 + +enum Items { + kItemHelmet = 1, + kItemBelt = 3, + kItemPithHelmet = 7, + kItemJournal = 9, + kItemNotes = 12, + kItemWestPaper = 18, + kItemWhiskey = 25, + kItemLamp = 27, + kItemMap = 28, + kItemQuarter = 30 +}; + +#define kCondLampOn 151 +#define kCondBeltGlowing 70 +#define kCondUsedHelmet 184 + +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 MainButtons { + kButtonPickup, + kButtonUse, + kButtonOpen, + kButtonClose, + kButtonLook, + kButtonInventory, + kButtonLeft, + kButtonForward, + kButtonRight, + kButtonMap +}; + +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, CloseDataPtr 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; + } +} + +void LabEngine::perFlipButton(uint16 buttonId) { + for (ButtonList::iterator button = _moveButtonList.begin(); button != _moveButtonList.end(); ++button) { + Button *topButton = *button; + if (topButton->_buttonId == buttonId) { + Image *tmpImage = topButton->_image; + topButton->_image = topButton->_altImage; + topButton->_altImage = tmpImage; + + if (!_alternate) { + _event->mouseHide(); + topButton->_image->drawImage(topButton->_x, topButton->_y); + _event->mouseShow(); + } + + break; + } + } +} + +void LabEngine::eatMessages() { + IntuiMessage *msg; + + do { + msg = _event->getMsg(); + } while (msg && !g_engine->shouldQuit()); +} + +bool LabEngine::doCloseUp(CloseDataPtr closePtr) { + if (!closePtr) + return false; + + int luteRight; + Common::Rect textRect; + + if (getPlatform() != Common::kPlatformWindows) { + textRect.left = 0; + textRect.right = 319; + textRect.top = 0; + textRect.bottom = 165; + luteRight = 124; + } else { + textRect.left = 2; + textRect.right = 317; + textRect.top = 2; + textRect.bottom = 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(nullptr, false); + _graphics->drawPanel(); + return true; + case kItemJournal: + drawStaticMessage(kTextUseJournal); + interfaceOff(); + _anim->stopDiff(); + _curFileName = " "; + _closeDataPtr = nullptr; + doJournal(); + _graphics->drawPanel(); + _graphics->drawMessage(nullptr, 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() { + uint16 actionMode = 4; + uint16 curInv = kItemMap; + + bool forceDraw = false; + bool gotMessage = true; + + _graphics->setPalette(initColors, 8); + + _closeDataPtr = nullptr; + _roomNum = 1; + _direction = NORTH; + + _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(); + + perFlipButton(actionMode); + + // Set up initial picture. + while (1) { + _event->processInput(); + _system->delayMillis(10); + + if (gotMessage) { + if (_quitLab || g_engine->shouldQuit()) { + _anim->stopDiff(); + break; + } + + _music->resumeBackMusic(); + + // 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(&_closeDataPtr); + + 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) { + switch (_closeDataPtr->_closeUpType) { + case SPECIALLOCK: + if (_mainDisplay) + _tilePuzzle->showCombination(_curFileName); + break; + case SPECIALBRICK: + case SPECIALBRICKNOMOUSE: + if (_mainDisplay) + _tilePuzzle->showTile(_curFileName, (_closeDataPtr->_closeUpType == SPECIALBRICKNOMOUSE)); + 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 + updateMusicAndEvents(); + interfaceOn(); + IntuiMessage *curMsg = _event->getMsg(); + if (g_engine->shouldQuit()) { + _quitLab = true; + return; + } + + if (!curMsg) { + // Does music load and next animation frame when you've run out of messages + gotMessage = false; + _music->checkRoomMusic(); + updateMusicAndEvents(); + _anim->diffNextFrame(); + + if (_followingCrumbs) { + int result = followCrumbs(); + + if (result != 0) { + uint16 code = 0; + switch (result) { + case VKEY_UPARROW: + code = kButtonForward; + break; + case VKEY_LTARROW: + code = kButtonLeft; + break; + case VKEY_RTARROW: + code = kButtonRight; + break; + default: + break; + } + + gotMessage = true; + mayShowCrumbIndicator(); + _graphics->screenUpdate(); + if (!fromCrumbs(kMessageButtonUp, code, 0, _event->updateAndGetMousePos(), curInv, curMsg, forceDraw, code, actionMode)) + break; + } + } + + mayShowCrumbIndicator(); + _graphics->screenUpdate(); + } else { + gotMessage = true; + _followingCrumbs = false; + if (!fromCrumbs(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++) { + updateMusicAndEvents(); + waitTOF(); + } + + _graphics->readPict("P:End/L2In.9"); + _graphics->readPict("P:End/Lost"); + + while (!_event->getMsg() && !shouldQuit()) { + updateMusicAndEvents(); + _anim->diffNextFrame(); + waitTOF(); + } +} + +bool LabEngine::fromCrumbs(uint32 tmpClass, uint16 code, uint16 qualifier, Common::Point tmpPos, + uint16 &curInv, IntuiMessage *curMsg, bool &forceDraw, uint16 buttonId, uint16 &actionMode) { + uint32 msgClass = tmpClass; + Common::Point curPos = tmpPos; + + uint16 oldDirection = 0; + uint16 lastInv = kItemMap; + CloseDataPtr wrkClosePtr = nullptr; + bool leftButtonClick = false; + bool rightButtonClick = false; + + _anim->_doBlack = false; + + if (g_engine->shouldQuit()) + return false; + + if ((msgClass == kMessageRawKey) && !_graphics->_longWinInFront) { + if (!processKey(curMsg, msgClass, qualifier, curPos, curInv, forceDraw, code)) + return false; + } + + leftButtonClick = (msgClass == kMessageLeftClick); + rightButtonClick = (msgClass == kMessageRightClick); + + if (_graphics->_longWinInFront) { + if ((msgClass == kMessageRawKey) || (leftButtonClick || rightButtonClick)) { + _graphics->_longWinInFront = false; + _graphics->drawPanel(); + drawRoomMessage(curInv, _closeDataPtr); + _graphics->screenUpdate(); + } + } else if ((msgClass == kMessageButtonUp) && !_alternate) { + processMainButton(curInv, lastInv, oldDirection, forceDraw, buttonId, actionMode); + } else if ((msgClass == kMessageButtonUp) && _alternate) { + processAltButton(curInv, lastInv, buttonId, actionMode); + } else if (leftButtonClick && _mainDisplay) { + interfaceOff(); + _mainDisplay = true; + + if (_closeDataPtr) { + switch (_closeDataPtr->_closeUpType) { + case SPECIALLOCK: + if (_mainDisplay) + _tilePuzzle->mouseCombination(curPos); + break; + case SPECIALBRICK: + if (_mainDisplay) + _tilePuzzle->mouseTile(curPos); + break; + default: + performAction(actionMode, curPos, curInv); + break; + } + } 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(); + } else if (msgClass == kMessageDeltaMove) { + ViewData *vptr = getViewData(_roomNum, _direction); + CloseDataPtr oldClosePtr = vptr->_closeUps; + CloseDataPtr tmpClosePtr = _closeDataPtr; + setCurrentClose(curPos, &tmpClosePtr, true); + + if (!tmpClosePtr || (tmpClosePtr == _closeDataPtr)) { + if (!_closeDataPtr) + wrkClosePtr = oldClosePtr; + else + wrkClosePtr = _closeDataPtr->_subCloseUps; + } else + wrkClosePtr = tmpClosePtr->_nextCloseUp; + + + if (!wrkClosePtr) { + if (!_closeDataPtr) + wrkClosePtr = oldClosePtr; + else + wrkClosePtr = _closeDataPtr->_subCloseUps; + } + + if (wrkClosePtr) + _event->setMousePos(Common::Point(_utils->scaleX((wrkClosePtr->_x1 + wrkClosePtr->_x2) / 2), _utils->scaleY((wrkClosePtr->_y1 + wrkClosePtr->_y2) / 2))); + } + + return true; +} + +bool LabEngine::processKey(IntuiMessage *curMsg, uint32 &msgClass, uint16 &qualifier, Common::Point &curPos, uint16 &curInv, bool &forceDraw, uint16 code) { + if (code == Common::KEYCODE_RETURN) { + // The return key + msgClass = kMessageLeftClick; + qualifier = 0; + curPos = _event->getMousePos(); + } else if ((getPlatform() == Common::kPlatformWindows) && (code == Common::KEYCODE_b)) { + // Start bread crumbs + _breadCrumbs[0]._roomNum = 0; + _numCrumbs = 0; + _droppingCrumbs = true; + mayShowCrumbIndicator(); + _graphics->screenUpdate(); + } else if ((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 + updateMusicAndEvents(); + curMsg = _event->getMsg(); + + if (g_engine->shouldQuit()) + return false; + + if (!curMsg) { + // Does music load and next animation frame when you've run out of messages + updateMusicAndEvents(); + _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_TAB) + msgClass = kMessageDeltaMove; + else if (code == Common::KEYCODE_ESCAPE) + _closeDataPtr = nullptr; + + eatMessages(); + + return true; +} + +void LabEngine::processMainButton(uint16 &curInv, uint16 &lastInv, uint16 &oldDirection, bool &forceDraw, uint16 buttonId, uint16 &actionMode) { + uint16 newDir; + uint16 oldRoomNum; + + switch (buttonId) { + case kButtonPickup: + case kButtonUse: + case kButtonOpen: + case kButtonClose: + case kButtonLook: + if ((actionMode == 4) && (buttonId == kButtonLook) && _closeDataPtr) { + doMainView(&_closeDataPtr); + + _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; + + newDir = processArrow(_direction, buttonId - 6); + doTurn(_direction, newDir, &_closeDataPtr); + _anim->_doBlack = true; + _direction = newDir; + forceDraw = true; + mayShowCrumbIndicator(); + break; + + case kButtonForward: + _closeDataPtr = nullptr; + oldRoomNum = _roomNum; + + if (doGoForward(&_closeDataPtr)) { + 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) { + bool saveRestoreSuccessful = true; + + _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 = " "; + + 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, &_closeDataPtr)) + _curFileName = _newFileName; + else if (takeItem(curPos, &_closeDataPtr)) + drawStaticMessage(kTextTakeItem); + else if (doActionRule(curPos, kRuleActionTakeDef, _roomNum, &_closeDataPtr)) + _curFileName = _newFileName; + else if (doActionRule(curPos, kRuleActionTake, 0, &_closeDataPtr)) + _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, &_closeDataPtr)) + _curFileName = _newFileName; + else if (!doActionRule(curPos, actionMode, 0, &_closeDataPtr)) { + if (curPos.y < (_utils->vgaScaleY(149) + _utils->svgaCord(2))) + drawStaticMessage(kTextNothing); + } + break; + + case 4: { + // Look at closeups + CloseDataPtr 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, &_closeDataPtr)) { + _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->closeFont(&_msgFont); + + if (getPlatform() != Common::kPlatformAmiga) + _msgFont = _resource->getFont("F:AvanteG.12"); + else + _msgFont = _resource->getFont("F:Map.fon"); + _event->mouseHide(); + + Intro *intro = new Intro(this); + intro->introSequence(); + delete intro; + + _event->mouseShow(); + mainGameLoop(); + + _graphics->closeFont(&_msgFont); + _graphics->freePict(); + + freeScreens(); + + _music->freeMusic(); +} + +int LabEngine::followCrumbs() { + // NORTH, SOUTH, EAST, WEST + int movement[4][4] = { + { VKEY_UPARROW, VKEY_RTARROW, VKEY_RTARROW, VKEY_LTARROW }, + { VKEY_RTARROW, VKEY_UPARROW, VKEY_LTARROW, VKEY_RTARROW }, + { VKEY_LTARROW, VKEY_RTARROW, VKEY_UPARROW, VKEY_RTARROW }, + { VKEY_RTARROW, VKEY_LTARROW, VKEY_RTARROW, VKEY_UPARROW } + }; + + if (_isCrumbWaiting) { + if (_system->getMillis() <= _crumbTimestamp) + return 0; + + _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 0; + } + + int exitDir; + // which direction is last crumb + if (_breadCrumbs[_numCrumbs]._direction == EAST) + exitDir = WEST; + else if (_breadCrumbs[_numCrumbs]._direction == WEST) + exitDir = EAST; + else if (_breadCrumbs[_numCrumbs]._direction == NORTH) + exitDir = SOUTH; + else + exitDir = NORTH; + + int moveDir = movement[_direction][exitDir]; + + if (_numCrumbs == 0) { + _isCrumbTurning = false; + _breadCrumbs[0]._roomNum = 0; + _droppingCrumbs = false; + _followingCrumbs = false; + } else { + _isCrumbTurning = (moveDir != VKEY_UPARROW); + _isCrumbWaiting = true; + + int theDelay = (_followCrumbsFast ? 1000 / 4 : 1000); + _crumbTimestamp = theDelay + _system->getMillis(); + } + + return moveDir; +} + + +void LabEngine::mayShowCrumbIndicator() { + static Image dropCrumbsImage(24, 24, nullptr, this); + if (getPlatform() != Common::kPlatformWindows) + return; + + if (_droppingCrumbs && _mainDisplay) { + _event->mouseHide(); + dropCrumbsImage.drawMaskImage(612, 4); + _event->mouseShow(); + } +} + +void LabEngine::mayShowCrumbIndicatorOff() { + static Image dropCrumbsOffImage(24, 24, nullptr, this); + + if (getPlatform() != Common::kPlatformWindows) + return; + + if (_mainDisplay) { + _event->mouseHide(); + dropCrumbsOffImage.drawMaskImage(612, 4); + _event->mouseShow(); + } +} + +} // End of namespace Lab diff --git a/engines/lab/eventman.cpp b/engines/lab/eventman.cpp new file mode 100644 index 0000000000..4947991b87 --- /dev/null +++ b/engines/lab/eventman.cpp @@ -0,0 +1,283 @@ +/* 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 + +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 { + mouseHide(); + button->_altImage->drawImage(button->_x, button->_y); + mouseShow(); + + for (int i = 0; i < 3; i++) + _vm->waitTOF(); + + mouseHide(); + button->_image->drawImage(button->_x, button->_y); + mouseShow(); + } + + 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; +} + +EventManager::EventManager(LabEngine *vm) : _vm(vm) { + _leftClick = false; + _rightClick = false; + + _mouseHidden = true; + _lastButtonHit = nullptr; + _screenButtonList = nullptr; + _hitButton = nullptr; + _mousePos = Common::Point(0, 0); + + _nextKeyIn = 0; + _nextKeyOut = 0; + + for (int i = 0; i < 64; i++) + _keyBuf[i] = Common::KEYCODE_INVALID; + +} + +void EventManager::updateMouse() { + bool doUpdateDisplay = false; + + if (!_mouseHidden) + doUpdateDisplay = true; + + if (_hitButton) { + mouseHide(); + _hitButton->_altImage->drawImage(_hitButton->_x, _hitButton->_y); + mouseShow(); + + for (int i = 0; i < 3; i++) + _vm->waitTOF(); + + mouseHide(); + _hitButton->_image->drawImage(_hitButton->_x, _hitButton->_y); + mouseShow(); + doUpdateDisplay = true; + _hitButton = nullptr; + } + + if (doUpdateDisplay) + _vm->_graphics->screenUpdate(); +} + +void EventManager::initMouse() { + _vm->_system->setMouseCursor(mouseData, MOUSE_WIDTH, MOUSE_HEIGHT, 0, 0, 0); + _vm->_system->showMouse(false); + + setMousePos(Common::Point(0, 0)); +} + +void EventManager::mouseShow() { + if (_mouseHidden) { + processInput(); + _mouseHidden = false; + } + + _vm->_system->showMouse(true); +} + +void EventManager::mouseHide() { + if (!_mouseHidden) { + _mouseHidden = true; + + _vm->_system->showMouse(false); + } +} + +Common::Point EventManager::getMousePos() { + if (_vm->_isHiRes) + return _mousePos; + else + return Common::Point(_mousePos.x / 2, _mousePos.y); +} + +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); + + if (!_mouseHidden) + processInput(); +} + +bool EventManager::keyPress(Common::KeyCode *keyCode) { + if (haveNextChar()) { + *keyCode = getNextChar(); + return true; + } + + return false; +} + +bool EventManager::haveNextChar() { + processInput(); + return _nextKeyIn != _nextKeyOut; +} + +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_z: + //saveSettings(); + break; + case Common::KEYCODE_d: + if (event.kbd.hasFlags(Common::KBD_CTRL)) { + // Open debugger console + _vm->_console->attach(); + continue; + } + // Intentional fall through + default: { + int n = (_nextKeyIn + 1) % 64; + if (n != _nextKeyOut) { + _keyBuf[_nextKeyIn] = event.kbd.keycode; + _nextKeyIn = n; + } + } + } + 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::KeyCode EventManager::getNextChar() { + Common::KeyCode chr = Common::KEYCODE_INVALID; + + processInput(); + if (_nextKeyIn != _nextKeyOut) { + chr = _keyBuf[_nextKeyOut]; + _nextKeyOut = (_nextKeyOut + 1) % 64; + } + + return chr; +} + +Common::Point EventManager::updateAndGetMousePos() { + processInput(); + + return _mousePos; +} + +} // End of namespace Lab diff --git a/engines/lab/eventman.h b/engines/lab/eventman.h new file mode 100644 index 0000000000..963972165d --- /dev/null +++ b/engines/lab/eventman.h @@ -0,0 +1,161 @@ +/* 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; + +enum MessageClasses { + kMessageLeftClick, + kMessageRightClick, + kMessageButtonUp, + kMessageRawKey, + kMessageDeltaMove +}; + +#define VKEY_UPARROW 273 +#define VKEY_DNARROW 274 +#define VKEY_RTARROW 275 +#define VKEY_LTARROW 276 + +struct IntuiMessage { + uint32 _msgClass; + uint16 _code; // KeyCode or Button Id + uint16 _qualifier; + Common::Point _mouse; +}; + + +struct Button { + uint16 _x, _y, _buttonId; + uint16 _keyEquiv; // if not zero, a key that activates button + bool _isEnabled; + Image *_image, *_altImage; +}; + +typedef Common::List<Button *> ButtonList; + +class EventManager { +private: + LabEngine *_vm; + + bool _leftClick; + bool _rightClick; + bool _mouseHidden; + + uint16 _nextKeyIn; + uint16 _nextKeyOut; + Common::KeyCode _keyBuf[64]; + + 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 a key has been pressed. + */ + bool keyPress(Common::KeyCode *keyCode); + bool haveNextChar(); + Common::KeyCode getNextChar(); + + /** + * Checks whether or not the coords fall within one of the buttons in a list + * of buttons. + */ + Button *checkNumButtonHit(ButtonList *buttonList, uint16 key); + + /** + * Make a key press have the right case for a button KeyEquiv value. + */ + uint16 makeButtonKeyEquiv(uint16 key); + +public: + EventManager (LabEngine *vm); + + void attachButtonList(ButtonList *buttonList); + Button *createButton(uint16 x, uint16 y, uint16 id, uint16 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); + + /** + * Gets the current mouse co-ordinates. NOTE: On IBM version, will scale + * from virtual to screen co-ordinates automatically. + */ + Common::Point getMousePos(); + 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(); +}; + +} // 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..ce0d5431b6 --- /dev/null +++ b/engines/lab/image.cpp @@ -0,0 +1,133 @@ +/* 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); +} + +Image::~Image() { + delete[] _imageData; +} + +void Image::blitBitmap(uint16 xs, uint16 ys, Image *imDest, + uint16 xd, uint16 yd, uint16 width, uint16 height, byte masked) { + int w = width; + int h = height; + int destWidth = (imDest) ? imDest->_width : _vm->_graphics->_screenWidth; + int destHeight = (imDest) ? imDest->_height : _vm->_graphics->_screenHeight; + byte *destBuffer = (imDest) ? imDest->_imageData : _vm->_graphics->getCurrentDrawingBuffer(); + + if (xd + w > destWidth) + w = destWidth - xd; + + if (yd + h > destHeight) + h = destHeight - yd; + + if ((w > 0) && (h > 0)) { + byte *s = _imageData + ys * _width + xs; + byte *d = destBuffer + yd * destWidth + xd; + + if (!masked) { + while (h-- > 0) { + memcpy(d, s, w); + s += _width; + d += destWidth; + } + } else { + while (h-- > 0) { + byte *ss = s; + byte *dd = d; + int ww = w; + + while (ww-- > 0) { + byte c = *ss++; + + if (c) + *dd++ = c - 1; + else + dd++; + } + + s += _width; + d += 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 w = _width; + int h = _height; + + if (x + w > _vm->_graphics->_screenWidth) + w = _vm->_graphics->_screenWidth - x; + + if (y + h > _vm->_graphics->_screenHeight) + h = _vm->_graphics->_screenHeight - y; + + if ((w > 0) && (h > 0)) { + byte *s = _imageData; + byte *d = _vm->_graphics->getCurrentDrawingBuffer() + y * _vm->_graphics->_screenWidth + x; + + while (h-- > 0) { + memcpy(s, d, w); + s += _width; + d += _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..bac32cd763 --- /dev/null +++ b/engines/lab/image.h @@ -0,0 +1,78 @@ +/* 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) {} + Image(int w, int h, byte *d, LabEngine *vm) : _width(w), _height(h), _imageData(d), _vm(vm) {} + Image(Common::File *s, LabEngine *vm); + virtual ~Image(); + + /** + * 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 xs, uint16 ys, Image *ImDest, uint16 xd, uint16 yd, uint16 width, uint16 height, byte masked); +}; + +} // 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..0d03d21fd1 --- /dev/null +++ b/engines/lab/interface.cpp @@ -0,0 +1,154 @@ +/* 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, uint16 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; +} + +uint16 EventManager::makeButtonKeyEquiv(uint16 key) { + if (Common::isAlnum(key)) + key = tolower(key); + + return key; +} + +Button *EventManager::checkNumButtonHit(ButtonList *buttonList, uint16 key) { + uint16 gkey = key - '0'; + + if (!buttonList) + return nullptr; + + for (ButtonList::iterator buttonItr = buttonList->begin(); buttonItr != buttonList->end(); ++buttonItr) { + Button *button = *buttonItr; + if (((gkey - 1 == button->_buttonId) || ((gkey == 0) && (button->_buttonId == 9)) + || ((button->_keyEquiv != 0) && (makeButtonKeyEquiv(key) == button->_keyEquiv))) + && button->_isEnabled) { + 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(); + + Common::KeyCode curKey; + + 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 (keyPress(&curKey)) { + message._code = curKey; + Button *curButton = checkNumButtonHit(_screenButtonList, message._code); + + if (curButton) { + message._msgClass = kMessageButtonUp; + message._code = curButton->_buttonId; + } else + message._msgClass = kMessageRawKey; + + message._qualifier = _keyPressed.flags; + 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..1b4310ac07 --- /dev/null +++ b/engines/lab/intro.cpp @@ -0,0 +1,441 @@ +/* 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; + _introDoBlack = false; +} + +void Intro::introEatMessages() { + while (1) { + IntuiMessage *msg = _vm->_event->getMsg(); + + if (g_engine->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, TextFont *msgFont, bool isScreen) { + Common::String path = Common::String("Lab:rooms/Intro/") + filename; + + uint timeDelay = (isScreen) ? 35 : 7; + _vm->updateMusicAndEvents(); + + if (_quitIntro) + return; + + uint32 lastMillis = 0; + bool drawNextText = true; + bool doneFl = false; + bool begin = true; + + Common::File *textFile = _vm->_resource->openDataFile(path); + byte *textBuffer = new byte[textFile->size()]; + textFile->read(textBuffer, textFile->size()); + delete textFile; + byte *curText = textBuffer; + + while (1) { + if (drawNextText) { + if (begin) + begin = false; + else if (isScreen) + _vm->_graphics->fade(false, 0); + + int charDrawn = 0; + if (isScreen) { + _vm->_graphics->setPen(7); + _vm->_graphics->rectFillScaled(10, 10, 310, 190); + + charDrawn = _vm->_graphics->flowText(msgFont, (!_vm->_isHiRes) * -1, 5, 7, false, false, true, true, _vm->_utils->vgaRectScale(14, 11, 306, 189), (char *)curText); + _vm->_graphics->fade(true, 0); + } else + charDrawn = _vm->_graphics->longDrawMessage(Common::String((char *)curText), false); + + curText += charDrawn; + doneFl = (*curText == 0); + + drawNextText = false; + introEatMessages(); + + if (_quitIntro) { + if (isScreen) + _vm->_graphics->fade(false, 0); + + delete[] textBuffer; + return; + } + + lastMillis = _vm->_system->getMillis(); + } + + IntuiMessage *msg = _vm->_event->getMsg(); + if (g_engine->shouldQuit()) { + _quitIntro = true; + return; + } + + if (!msg) { + _vm->updateMusicAndEvents(); + _vm->_anim->diffNextFrame(); + + uint32 elapsedSeconds = (_vm->_system->getMillis() - lastMillis) / 1000; + + if (elapsedSeconds > timeDelay) { + if (doneFl) { + if (isScreen) + _vm->_graphics->fade(false, 0); + + 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, 0); + + delete[] textBuffer; + return; + } else if ((msgClass == kMessageLeftClick) || (msgClass == kMessageRightClick)) { + if (msgClass == kMessageLeftClick) { + if (doneFl) { + if (isScreen) + _vm->_graphics->fade(false, 0); + + delete[] textBuffer; + return; + } else + drawNextText = true; + } + + introEatMessages(); + + if (_quitIntro) { + if (isScreen) + _vm->_graphics->fade(false, 0); + + delete[] textBuffer; + return; + } + } + + if (doneFl) { + if (isScreen) + _vm->_graphics->fade(false, 0); + + delete[] textBuffer; + return; + } else + drawNextText = true; + } + } // while(1) +} + +void Intro::musicDelay() { + _vm->updateMusicAndEvents(); + + if (_quitIntro) + return; + + for (int i = 0; i < 20; i++) { + _vm->updateMusicAndEvents(); + _vm->waitTOF(); + _vm->waitTOF(); + _vm->waitTOF(); + } +} + +void Intro::nReadPict(const Common::String filename, bool playOnce) { + Common::String finalFileName = Common::String("P:Intro/") + filename; + + _vm->updateMusicAndEvents(); + introEatMessages(); + + if (_quitIntro) + return; + + _vm->_anim->_doBlack = _introDoBlack; + _vm->_anim->stopDiffEnd(); + _vm->_graphics->readPict(finalFileName, playOnce); +} + +void Intro::introSequence() { + uint16 palette[16] = { + 0x0000, 0x0855, 0x0FF9, 0x0EE7, + 0x0ED5, 0x0DB4, 0x0CA2, 0x0C91, + 0x0B80, 0x0B80, 0x0B91, 0x0CA2, + 0x0CB3, 0x0DC4, 0x0DD6, 0x0EE7 + }; + + _vm->_anim->_doBlack = true; + + if (_vm->getPlatform() == Common::kPlatformDOS) { + nReadPict("EA0"); + nReadPict("EA1"); + nReadPict("EA2"); + nReadPict("EA3"); + } else if (_vm->getPlatform() == Common::kPlatformWindows) { + nReadPict("WYRMKEEP"); + // Wait 4 seconds + for (int i = 0; i < 4 * 1000 / 10; i++) { + introEatMessages(); + if (_quitIntro) + break; + _vm->_system->delayMillis(10); + } + } + + _vm->_graphics->blackAllScreen(); + + if (_vm->getPlatform() == Common::kPlatformAmiga) + _vm->_music->initMusic("Music:BackGround"); + else + _vm->_music->initMusic("Music:BackGrou"); + + _vm->_anim->_noPalChange = true; + if (_vm->getPlatform() == Common::kPlatformDOS) + nReadPict("TNDcycle.pic"); + else + nReadPict("TNDcycle2.pic"); + _vm->_anim->_noPalChange = false; + + _vm->_graphics->_fadePalette = palette; + + for (int i = 0; i < 16; i++) { + if (_quitIntro) + break; + + 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->updateMusicAndEvents(); + _vm->_graphics->fade(true, 0); + + for (int times = 0; times < 150; times++) { + if (_quitIntro) + break; + + _vm->updateMusicAndEvents(); + 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(); + } + + _vm->_graphics->fade(false, 0); + _vm->_graphics->blackAllScreen(); + _vm->updateMusicAndEvents(); + + nReadPict("Title.A"); + nReadPict("AB"); + musicDelay(); + nReadPict("BA"); + nReadPict("AC"); + musicDelay(); + + if (_vm->getPlatform() == Common::kPlatformWindows) + musicDelay(); // more credits on this page now + + nReadPict("CA"); + nReadPict("AD"); + musicDelay(); + + if (_vm->getPlatform() == Common::kPlatformWindows) + musicDelay(); // more credits on this page now + + nReadPict("DA"); + musicDelay(); + + _vm->updateMusicAndEvents(); + _vm->_graphics->blackAllScreen(); + _vm->updateMusicAndEvents(); + + TextFont *msgFont = _vm->_resource->getFont("F:Map.fon"); + + _vm->_anim->_noPalChange = true; + nReadPict("Intro.1"); + _vm->_anim->_noPalChange = false; + + 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", msgFont, true); + if (_vm->getPlatform() == Common::kPlatformWindows) { + doPictText("i.2A", msgFont, true); + doPictText("i.2B", msgFont, true); + } + + _vm->_graphics->blackAllScreen(); + _vm->updateMusicAndEvents(); + + _introDoBlack = true; + nReadPict("Station1"); + doPictText("i.3", msgFont, false); + + nReadPict("Station2"); + doPictText("i.4", msgFont, false); + + nReadPict("Stiles4"); + doPictText("i.5", msgFont, false); + + nReadPict("Stiles3"); + doPictText("i.6", msgFont, false); + + if (_vm->getPlatform() == Common::kPlatformWindows) + nReadPict("Platform2"); + else + nReadPict("Platform"); + doPictText("i.7", msgFont, false); + + nReadPict("Subway.1"); + doPictText("i.8", msgFont, false); + + nReadPict("Subway.2"); + + doPictText("i.9", msgFont, false); + doPictText("i.10", msgFont, false); + doPictText("i.11", msgFont, false); + + if (!_quitIntro) + for (int i = 0; i < 50; i++) { + for (int idx = (8 * 3); idx < (255 * 3); idx++) + _vm->_anim->_diffPalette[idx] = 255 - _vm->_anim->_diffPalette[idx]; + + _vm->updateMusicAndEvents(); + _vm->waitTOF(); + _vm->_graphics->setPalette(_vm->_anim->_diffPalette, 256); + _vm->waitTOF(); + _vm->waitTOF(); + } + + doPictText("i.12", msgFont, false); + doPictText("i.13", msgFont, false); + + _introDoBlack = false; + nReadPict("Daed0"); + doPictText("i.14", msgFont, false); + + nReadPict("Daed1"); + doPictText("i.15", msgFont, false); + + nReadPict("Daed2"); + doPictText("i.16", msgFont, false); + doPictText("i.17", msgFont, false); + doPictText("i.18", msgFont, false); + + nReadPict("Daed3"); + doPictText("i.19", msgFont, false); + doPictText("i.20", msgFont, false); + + nReadPict("Daed4"); + doPictText("i.21", msgFont, false); + + nReadPict("Daed5"); + doPictText("i.22", msgFont, false); + doPictText("i.23", msgFont, false); + doPictText("i.24", msgFont, false); + + nReadPict("Daed6"); + doPictText("i.25", msgFont, false); + doPictText("i.26", msgFont, false); + + nReadPict("Daed7", false); + doPictText("i.27", msgFont, false); + doPictText("i.28", msgFont, false); + _vm->_anim->stopDiffEnd(); + + nReadPict("Daed8"); + doPictText("i.29", msgFont, false); + doPictText("i.30", msgFont, false); + + nReadPict("Daed9"); + doPictText("i.31", msgFont, false); + doPictText("i.32", msgFont, false); + doPictText("i.33", msgFont, false); + + nReadPict("Daed9a"); + nReadPict("Daed10"); + doPictText("i.34", msgFont, false); + doPictText("i.35", msgFont, false); + doPictText("i.36", msgFont, false); + + nReadPict("SubX"); + + if (_quitIntro) { + _vm->_graphics->setPen(0); + _vm->_graphics->rectFill(0, 0, _vm->_graphics->_screenWidth - 1, _vm->_graphics->_screenHeight - 1); + _vm->_anim->_doBlack = true; + } + + _vm->_graphics->closeFont(&msgFont); +} + +} // End of namespace Lab diff --git a/engines/lab/intro.h b/engines/lab/intro.h new file mode 100644 index 0000000000..fd72190b61 --- /dev/null +++ b/engines/lab/intro.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + /* + * 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); + + /** + * Does the introduction sequence for Labyrinth. + */ + void introSequence(); + +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, TextFont *msgFont, bool isScreen); + + /** + * Does a one second delay, but checks the music while doing it. + */ + void musicDelay(); + void nReadPict(const Common::String filename, bool playOnce = true); + + LabEngine *_vm; + bool _quitIntro, _introDoBlack; +}; + +} // 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..a1fd4f5c94 --- /dev/null +++ b/engines/lab/lab.cpp @@ -0,0 +1,252 @@ +/* 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/tilepuzzle.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 = NORTH; + } + + _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; + _graphics = nullptr; + _rooms = nullptr; + _tilePuzzle = nullptr; + _utils = nullptr; + _console = nullptr; + _journalBackImage = nullptr; + _screenImage = 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; + + //const Common::FSNode gameDataDir(ConfMan.get("path")); + //SearchMan.addSubDirectoryMatching(gameDataDir, "game"); + //SearchMan.addSubDirectoryMatching(gameDataDir, "game/pict"); + //SearchMan.addSubDirectoryMatching(gameDataDir, "game/spict"); + //SearchMan.addSubDirectoryMatching(gameDataDir, "music"); +} + +LabEngine::~LabEngine() { + // Remove all of our debug levels here + DebugMan.clearAllDebugChannels(); + + freeMapData(); + for (int i = 1; i <= _manyRooms; i++) + _resource->freeViews(i); + delete[] _rooms; + delete[] _inventory; + + delete _conditions; + delete _roomsFound; + delete _event; + delete _resource; + delete _music; + delete _anim; + delete _graphics; + delete _tilePuzzle; + delete _utils; + delete _console; + delete _journalBackImage; + // _screenImage->_imageData is always pointing to the current drawing buffer. + // It shouldn't be deleted there. + _screenImage->_imageData = nullptr; + delete _screenImage; +} + +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); + _tilePuzzle = new TilePuzzle(this); + _utils = new Utils(this); + _console = new Console(this); + _journalBackImage = new Image(this); + _screenImage = 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) { + warning("STUB: changeVolume()"); +} + +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::updateMusicAndEvents() { + _event->processInput(); + _event->updateMouse(); + _music->updateMusic(); +} + +} // End of namespace Lab diff --git a/engines/lab/lab.h b/engines/lab/lab.h new file mode 100644 index 0000000000..ba642f5ddb --- /dev/null +++ b/engines/lab/lab.h @@ -0,0 +1,477 @@ +/* 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 TilePuzzle; +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 CloseData *CloseDataPtr; +typedef Common::List<Rule> RuleList; + +// Direction defines +#define NORTH 0 +#define SOUTH 1 +#define EAST 2 +#define WEST 3 + +class LabEngine : public Engine { +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; + + CloseDataPtr _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; + Image *_screenImage; + TextFont *_journalFont; + +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; + TilePuzzle *_tilePuzzle; + 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(CloseDataPtr *closePtrList); + uint16 getQuarters(); + void setDirection(uint16 direction) { _direction = direction; }; + void setQuarters(uint16 quarters); + void updateMusicAndEvents(); + void waitTOF(); + +private: + /** + * Checks whether all the conditions in a condition list are met. + */ + bool checkConditions(int16 *condition); + + /** + * Decrements the current inventory number. + */ + void decIncInv(uint16 *CurInv, bool dec); + + /** + * Processes the action list. + */ + void doActions(Action *actionList, CloseDataPtr *closePtrList); + + /** + * Goes through the rules if an action is taken. + */ + bool doActionRule(Common::Point pos, int16 action, int16 roomNum, CloseDataPtr *closePtrList); + + /** + * Does the work for doActionRule. + */ + bool doActionRuleSub(int16 action, int16 roomNum, CloseDataPtr closePtr, CloseDataPtr *setCloseList, bool allowDefaults); + + /** + * Checks whether the close up is one of the special case closeups. + */ + bool doCloseUp(CloseDataPtr closePtr); + + /** + * Goes through the rules if the user tries to go forward. + */ + bool doGoForward(CloseDataPtr *closePtrList); + + /** + * Does the journal processing. + */ + void doJournal(); + + /** + * Goes through the rules if the user tries to go to the main view + */ + bool doMainView(CloseDataPtr *closePtrList); + + /** + * 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, CloseDataPtr closePtr, CloseDataPtr *setCloseList, bool allowDefaults); + + /** + * Goes through the rules if the user tries to operate an item on an object. + */ + bool doOperateRule(Common::Point pos, int16 ItemNum, CloseDataPtr *closePtrList); + + /** + * Goes through the rules if the user tries to turn. + */ + bool doTurn(uint16 from, uint16 to, CloseDataPtr *closePtrList); + + /** + * 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(CloseDataPtr 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 fadeOut, bool fadeIn); + + /** + * Draws the text for the monitor. + */ + void drawMonText(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, CloseDataPtr 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. + */ + CloseDataPtr findClosePtrMatch(CloseDataPtr closePtr, CloseDataPtr closePtrList); + + /** + * Checks if a floor has been visited. + */ + bool floorVisited(uint16 floorNum); + + /** + * New code to allow quick(er) return navigation in game. + */ + int followCrumbs(); + void freeMapData(); + void freeScreens(); + bool fromCrumbs(uint32 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. + */ + CloseData *getObject(Common::Point pos, CloseDataPtr 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(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, CloseDataPtr *closePtrList, bool useAbsoluteCoords); + + /** + * Takes the currently selected item. + */ + bool takeItem(Common::Point pos, CloseDataPtr *closePtrList); + + /** + * 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..e06fefdd92 --- /dev/null +++ b/engines/lab/map.cpp @@ -0,0 +1,577 @@ +/* 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[NORTH] = new Image(mapImages, this); + _imgMapX[EAST] = new Image(mapImages, this); + _imgMapX[SOUTH] = new Image(mapImages, this); + _imgMapX[WEST] = new Image(mapImages, this); + _imgPath = new Image(mapImages, this); + _imgBridge = new Image(mapImages, this); + + _mapButtonList.push_back(_event->createButton( 8, _utils->vgaScaleY(105), 0, VKEY_LTARROW, new Image(mapImages, this), new Image(mapImages, this))); // back + _mapButtonList.push_back(_event->createButton( 55, _utils->vgaScaleY(105), 1, VKEY_UPARROW, new Image(mapImages, this), new Image(mapImages, this))); // up + _mapButtonList.push_back(_event->createButton(101, _utils->vgaScaleY(105), 2, VKEY_DNARROW, new Image(mapImages, this), new Image(mapImages, this))); // down + + delete mapImages; + + Common::File *mapFile = _resource->openDataFile("Lab:Maps", MKTAG('M', 'A', 'P', '0')); + updateMusicAndEvents(); + if (!_music->_loopSoundEffect) + _music->stopSoundEffect(); + + _maxRooms = mapFile->readUint16LE(); + _maps = new MapData[_maxRooms + 1]; // will be freed when the user exits the map + for (int i = 1; i <= _maxRooms; i++) { + _maps[i]._x = mapFile->readUint16LE(); + _maps[i]._y = mapFile->readUint16LE(); + _maps[i]._pageNumber = mapFile->readUint16LE(); + _maps[i]._specialID = 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 NORMAL: + case UPARROWROOM: + case DOWNARROWROOM: + curRoomImg = _imgRoom; + break; + case BRIDGEROOM: + curRoomImg = _imgBridge; + break; + case VCORRIDOR: + curRoomImg = _imgVRoom; + break; + case HCORRIDOR: + 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 NORMAL: + case UPARROWROOM: + case DOWNARROWROOM: + if (_maps[curRoom]._specialID == NORMAL) + _imgRoom->drawImage(x, y); + else if (_maps[curRoom]._specialID == DOWNARROWROOM) + _imgDownArrowRoom->drawImage(x, y); + else + _imgUpArrowRoom->drawImage(x, y); + + offset = (_imgRoom->_width - _imgPath->_width) / 2; + + if ((NORTHDOOR & flags) && (y >= _imgPath->_height)) + _imgPath->drawImage(x + offset, y - _imgPath->_height); + + if (SOUTHDOOR & flags) + _imgPath->drawImage(x + offset, y + _imgRoom->_height); + + offset = (_imgRoom->_height - _imgPath->_height) / 2; + + if (EASTDOOR & flags) + _imgPath->drawImage(x + _imgRoom->_width, y + offset); + + if (WESTDOOR & 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 BRIDGEROOM: + _imgBridge->drawImage(x, y); + + drawX = x + (_imgBridge->_width - _imgMapX[_direction]->_width) / 2; + drawY = y + (_imgBridge->_height - _imgMapX[_direction]->_height) / 2; + + break; + + case VCORRIDOR: + _imgVRoom->drawImage(x, y); + + offset = (_imgVRoom->_width - _imgPath->_width) / 2; + + if (NORTHDOOR & flags) + _imgPath->drawImage(x + offset, y - _imgPath->_height); + + if (SOUTHDOOR & flags) + _imgPath->drawImage(x + offset, y + _imgVRoom->_height); + + offset = (_imgRoom->_height - _imgPath->_height) / 2; + + if (EASTDOOR & flags) + _imgPath->drawImage(x + _imgVRoom->_width, y + offset); + + if (WESTDOOR & flags) + _imgPath->drawImage(x - _imgPath->_width, y + offset); + + if (EASTBDOOR & flags) + _imgPath->drawImage(x + _imgVRoom->_width, y - offset - _imgPath->_height + _imgVRoom->_height); + + if (WESTBDOOR & flags) + _imgPath->drawImage(x - _imgPath->_width, y - offset - _imgPath->_height + _imgVRoom->_height); + + offset = (_imgVRoom->_height - _imgPath->_height) / 2; + + if (EASTMDOOR & flags) + _imgPath->drawImage(x + _imgVRoom->_width, y - offset - _imgPath->_height + _imgVRoom->_height); + + if (WESTMDOOR & 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 HCORRIDOR: + _imgHRoom->drawImage(x, y); + + offset = (_imgRoom->_width - _imgPath->_width) / 2; + + if (NORTHDOOR & flags) + _imgPath->drawImage(x + offset, y - _imgPath->_height); + + if (SOUTHDOOR & flags) + _imgPath->drawImage(x + offset, y + _imgRoom->_height); + + if (NORTHRDOOR & flags) + _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y - _imgPath->_height); + + if (SOUTHRDOOR & flags) + _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y + _imgRoom->_height); + + offset = (_imgHRoom->_width - _imgPath->_width) / 2; + + if (NORTHMDOOR & flags) + _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y - _imgPath->_height); + + if (SOUTHMDOOR & flags) + _imgPath->drawImage(x - offset - _imgPath->_width + _imgHRoom->_width, y + _imgRoom->_height); + + offset = (_imgRoom->_height - _imgPath->_height) / 2; + + if (EASTDOOR & flags) + _imgPath->drawImage(x + _imgHRoom->_width, y + offset); + + if (WESTDOOR & 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 fadeOut, bool fadeIn) { + _event->mouseHide(); + + if (fadeOut) + _graphics->fade(false, 0); + + _graphics->setPen(0); + _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1); + + _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)); + updateMusicAndEvents(); + } + } + + // 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, 0); + + _event->mouseShow(); +} + +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 + updateMusicAndEvents(); + IntuiMessage *msg = _event->getMsg(); + if (g_engine->shouldQuit()) { + _quitLab = true; + return; + } + + if (!msg) { + updateMusicAndEvents(); + + 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(); + _event->updateMouse(); + waitTOF(); + _event->updateMouse(); + waitTOF(); + _event->updateMouse(); + + 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, 0); + drawMap(curRoom, curMsg, curFloor, false, false); + _graphics->fade(true, 0); + } + } else if (msgCode == 2) { + // Down arrow + uint16 lowerFloor = getLowerFloor(curFloor); + if (lowerFloor != kFloorNone) { + curFloor = lowerFloor; + _graphics->fade(false, 0); + drawMap(curRoom, curMsg, curFloor, false, false); + _graphics->fade(true, 0); + } + } + } else if (msgClass == kMessageLeftClick) { + if ((curFloor == kFloorLower) && _utils->mapRectScale(538, 277, 633, 352).contains(mouseX, mouseY) + && floorVisited(kFloorSurMaze)) { + curFloor = kFloorSurMaze; + + _graphics->fade(false, 0); + drawMap(curRoom, curMsg, curFloor, false, false); + _graphics->fade(true, 0); + } else if ((curFloor == kFloorMiddle) && _utils->mapRectScale(358, 71, 452, 147).contains(mouseX, mouseY) + && floorVisited(kFloorCarnival)) { + curFloor = kFloorCarnival; + + _graphics->fade(false, 0); + drawMap(curRoom, curMsg, curFloor, false, false); + _graphics->fade(true, 0); + } else if ((curFloor == kFloorMiddle) && _utils->mapRectScale(557, 325, 653, 401).contains(mouseX, mouseY) + && floorVisited(kFloorMedMaze)) { + curFloor = kFloorMedMaze; + + _graphics->fade(false, 0); + drawMap(curRoom, curMsg, curFloor, false, false); + _graphics->fade(true, 0); + } else if ((curFloor == kFloorUpper) && _utils->mapRectScale(524, 97, 645, 207).contains(mouseX, mouseY) + && floorVisited(kFloorHedgeMaze)) { + curFloor = kFloorHedgeMaze; + + _graphics->fade(false, 0); + drawMap(curRoom, curMsg, curFloor, false, false); + _graphics->fade(true, 0); + } 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())) { + _event->mouseHide(); + _graphics->setPen(3); + _graphics->rectFillScaled(13, 148, 135, 186); + _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->setPen(1); + _graphics->rectFill(left, top, right, bottom); + } + + _event->mouseShow(); + } + } + } + } + + _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; + + updateMusicAndEvents(); + loadMapData(); + _graphics->blackAllScreen(); + _event->attachButtonList(&_mapButtonList); + drawMap(curRoom, curRoom, _maps[curRoom]._pageNumber, false, true); + _event->mouseShow(); + _graphics->screenUpdate(); + processMap(curRoom); + _event->attachButtonList(nullptr); + _graphics->fade(false, 0); + _graphics->blackAllScreen(); + _event->mouseHide(); + _graphics->setPen(0); + _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1); + freeMapData(); + _graphics->blackAllScreen(); + _event->mouseShow(); + _graphics->screenUpdate(); +} + +} // End of namespace Lab diff --git a/engines/lab/module.mk b/engines/lab/module.mk new file mode 100644 index 0000000000..a619cba6ed --- /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 \ + tilepuzzle.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..95581aec5c --- /dev/null +++ b/engines/lab/music.cpp @@ -0,0 +1,336 @@ +/* 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 MUSICBUFSIZE (2 * 65536) +#define SAMPLESPEED 15000 + +#define CLOWNROOM 123 +#define DIMROOM 80 + +Music::Music(LabEngine *vm) : _vm(vm) { + _file = 0; + _tFile = 0; + _musicPaused = false; + + _oldMusicOn = false; + _tLeftInFile = 0; + + _leftInFile = 0; + + _musicOn = false; + _loopSoundEffect = false; + _queuingAudioStream = nullptr; + _lastMusicRoom = 1; + _doReset = true; +} + +void Music::updateMusic() { + if (!_musicOn || (getPlayingBufferCount() >= MAXBUFFERS)) + return; + + // NOTE: We need to use malloc(), cause this will be freed with free() + // by the music code + byte *musicBuffer = (byte *)malloc(MUSICBUFSIZE); + fillbuffer(musicBuffer); + + // Queue a music block, and start the music, if needed + bool startMusicFlag = false; + + if (!_queuingAudioStream) { + _queuingAudioStream = Audio::makeQueuingAudioStream(SAMPLESPEED, false); + startMusicFlag = true; + } + + 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; + + _queuingAudioStream->queueBuffer(musicBuffer, MUSICBUFSIZE, DisposeAfterUse::YES, soundFlags); + + if (startMusicFlag) + _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, _queuingAudioStream); +} + +uint16 Music::getPlayingBufferCount() { + return (_queuingAudioStream) ? _queuingAudioStream->numQueuedStreams() : 0; +} + +void Music::playSoundEffect(uint16 sampleSpeed, uint32 length, Common::File *dataFile) { + pauseBackMusic(); + stopSoundEffect(); + + if (sampleSpeed < 4000) + sampleSpeed = 4000; + + byte soundFlags = Audio::FLAG_LITTLE_ENDIAN; + if (_vm->getPlatform() == Common::kPlatformWindows) + soundFlags |= Audio::FLAG_16BITS; + else + soundFlags |= Audio::FLAG_UNSIGNED; + + // 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, sampleSpeed, soundFlags); + uint loops = (_loopSoundEffect) ? 0 : 1; + Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, loops); + _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, loopingAudioStream); +} + +void Music::stopSoundEffect() { + if (isSoundEffectActive()) + _vm->_mixer->stopHandle(_sfxHandle); +} + +bool Music::isSoundEffectActive() const { + return _vm->_mixer->isSoundHandleActive(_sfxHandle); +} + +void Music::fillbuffer(byte *musicBuffer) { + if (MUSICBUFSIZE < _leftInFile) { + _file->read(musicBuffer, MUSICBUFSIZE); + _leftInFile -= MUSICBUFSIZE; + } else { + _file->read(musicBuffer, _leftInFile); + + memset((char *)musicBuffer + _leftInFile, 0, MUSICBUFSIZE - _leftInFile); + + _file->seek(0); + _leftInFile = _file->size(); + } +} + +void Music::startMusic(bool restartFl) { + if (!_musicOn) + return; + + stopSoundEffect(); + + if (restartFl) { + _file->seek(0); + _leftInFile = _file->size(); + } + + _musicOn = true; + _vm->updateMusicAndEvents(); +} + +bool Music::initMusic(const Common::String filename) { + _musicOn = true; + _musicPaused = false; + _file = _vm->_resource->openDataFile(filename); + startMusic(true); + return true; +} + +void Music::freeMusic() { + _musicOn = false; + + _vm->_mixer->stopHandle(_musicHandle); + _queuingAudioStream = nullptr; + _vm->_mixer->stopHandle(_sfxHandle); + + delete _file; + _file = nullptr; +} + +void Music::pauseBackMusic() { + if (!_musicPaused && _musicOn) { + _vm->updateMusicAndEvents(); + _musicOn = false; + stopSoundEffect(); + + _vm->_mixer->pauseHandle(_musicHandle, true); + + _musicPaused = true; + } +} + +void Music::resumeBackMusic() { + if (_musicPaused) { + stopSoundEffect(); + _musicOn = true; + + _vm->_mixer->pauseHandle(_musicHandle, false); + + _vm->updateMusicAndEvents(); + _musicPaused = false; + } +} + +void Music::setMusic(bool on) { + stopSoundEffect(); + + if (on && !_musicOn) { + _musicOn = true; + startMusic(true); + } else if (!on && _musicOn) { + _musicOn = false; + _vm->updateMusicAndEvents(); + } else + _musicOn = on; +} + +void Music::checkRoomMusic() { + if ((_lastMusicRoom == _vm->_roomNum) || !_musicOn) + return; + + if (_vm->_roomNum == CLOWNROOM) + changeMusic("Music:Laugh"); + else if (_vm->_roomNum == DIMROOM) + changeMusic("Music:Rm81"); + else if (_doReset) + resetMusic(); + + _lastMusicRoom = _vm->_roomNum; +} + +void Music::changeMusic(const Common::String filename) { + if (!_tFile) { + _tFile = _file; + _oldMusicOn = _musicOn; + _tLeftInFile = _leftInFile + 65536; + + if (_tLeftInFile > (uint32)_tFile->size()) + _tLeftInFile = _leftInFile; + } + + _file = _vm->_resource->openDataFile(filename); + // turn music off + _musicOn = true; + setMusic(false); + + // turn it back on + _musicOn = false; + setMusic(true); +} + +void Music::resetMusic() { + if (!_tFile) + return; + + if (_file->isOpen()) + _file->close(); + + _file = _tFile; + _leftInFile = _tLeftInFile; + + _file->seek(_file->size() - _leftInFile); + + _musicOn = true; + setMusic(false); + _vm->updateMusicAndEvents(); + + if (!_oldMusicOn) { + _tFile = 0; + return; + } + + _musicOn = _oldMusicOn; + startMusic(false); + + _tFile = 0; +} + +bool Music::readMusic(const Common::String filename, bool waitTillFinished) { + Common::File *file = _vm->_resource->openDataFile(filename, MKTAG('D', 'I', 'F', 'F')); + _vm->updateMusicAndEvents(); + if (!_loopSoundEffect) + stopSoundEffect(); + + if (!file) + return false; + + _vm->_anim->_doBlack = false; + readSound(waitTillFinished, file); + + return true; +} + +void Music::readSound(bool waitTillFinished, 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->updateMusicAndEvents(); + soundTag = file->readUint32LE(); + soundSize = file->readUint32LE() - 8; + + if ((soundTag == 30) || (soundTag == 31)) { + if (waitTillFinished) { + while (isSoundEffectActive()) { + _vm->updateMusicAndEvents(); + _vm->waitTOF(); + } + } + + file->skip(4); + + uint16 sampleRate = file->readUint16LE(); + file->skip(2); + playSoundEffect(sampleRate, soundSize, file); + } else if (soundTag == 65535) { + if (waitTillFinished) { + while (isSoundEffectActive()) { + _vm->updateMusicAndEvents(); + _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..42fdf41d67 --- /dev/null +++ b/engines/lab/music.h @@ -0,0 +1,143 @@ +/* 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 *_file; + Common::File *_tFile; + + bool _doReset; + bool _musicOn; + bool _musicPaused; + bool _oldMusicOn; + + uint16 _lastMusicRoom ; + + uint32 _tLeftInFile; + uint32 _leftInFile; + + Audio::SoundHandle _musicHandle; + Audio::SoundHandle _sfxHandle; + Audio::QueuingAudioStream *_queuingAudioStream; + +private: + void fillbuffer(byte *musicBuffer); + uint16 getPlayingBufferCount(); + + /** + * Pauses the background music. + */ + void pauseBackMusic(); + void readSound(bool waitTillFinished, Common::File *file); + + /** + * Starts up the music initially. + */ + void startMusic(bool restartFl); + +public: + bool _loopSoundEffect; + +public: + Music(LabEngine *vm); + + /** + * Changes the background music to something else. + */ + void changeMusic(const Common::String filename); + + /** + * Checks the music that should be playing in a particular room. + */ + void checkRoomMusic(); + + /** + * Frees up the music buffers and closes the file. + */ + void freeMusic(); + + /** + * Initializes the music buffers. + */ + bool initMusic(const Common::String filename); + bool isSoundEffectActive() const; + void playSoundEffect(uint16 sampleSpeed, uint32 length, Common::File *dataFile); + + /** + * Reads in a music file. Ignores any graphics. + */ + bool readMusic(const Common::String filename, bool waitTillFinished); + + /** + * Changes the background music to the original piece playing. + */ + void resetMusic(); + + /** + * Resumes the paused background music. + */ + void resumeBackMusic(); + + /** + * Turns the music on and off. + */ + void setMusic(bool on); + void setMusicReset(bool reset) { _doReset = reset; } + void stopSoundEffect(); + + /** + * Figures out which buffer is currently playing based on messages sent to + * it from the Audio device. + */ + void updateMusic(); +}; + +} // 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..ad8db8499f --- /dev/null +++ b/engines/lab/processroom.cpp @@ -0,0 +1,640 @@ +/* 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(int16 *condition) { + if (!condition) + return true; + + if (condition[0] == 0) + return true; + + int counter = 1; + bool res = _conditions->in(condition[0]); + + while (condition[counter] && res) { + res = _conditions->in(condition[counter]); + counter++; + } + + return res; +} + +ViewData *LabEngine::getViewData(uint16 roomNum, uint16 direction) { + if (_rooms[roomNum]._roomMsg.empty()) + _resource->readViews(roomNum); + + ViewData *view = _rooms[roomNum]._view[direction]; + + do { + if (checkConditions(view->_condition)) + break; + + view = view->_nextCondition; + } while (true); + + return view; +} + +CloseData *LabEngine::getObject(Common::Point pos, CloseDataPtr closePtr) { + CloseDataPtr wrkClosePtr; + if (!closePtr) + wrkClosePtr = getViewData(_roomNum, _direction)->_closeUps; + else + wrkClosePtr = closePtr->_subCloseUps; + + Common::Rect objRect; + while (wrkClosePtr) { + objRect = _utils->rectScale(wrkClosePtr->_x1, wrkClosePtr->_y1, wrkClosePtr->_x2, wrkClosePtr->_y2); + if (objRect.contains(pos)) + return wrkClosePtr; + + wrkClosePtr = wrkClosePtr->_nextCloseUp; + } + + return nullptr; +} + +CloseDataPtr LabEngine::findClosePtrMatch(CloseDataPtr closePtr, CloseDataPtr closePtrList) { + CloseDataPtr resClosePtr; + + while (closePtrList) { + if ((closePtr->_x1 == closePtrList->_x1) && (closePtr->_x2 == closePtrList->_x2) && + (closePtr->_y1 == closePtrList->_y1) && (closePtr->_y2 == closePtrList->_y2) && + (closePtr->_depth == closePtrList->_depth)) + return closePtrList; + + resClosePtr = findClosePtrMatch(closePtr, closePtrList->_subCloseUps); + + if (resClosePtr) + return resClosePtr; + else + closePtrList = closePtrList->_nextCloseUp; + } + + return nullptr; +} + +Common::String LabEngine::getPictName(CloseDataPtr *closePtrList) { + ViewData *viewPtr = getViewData(_roomNum, _direction); + + if (*closePtrList) { + *closePtrList = findClosePtrMatch(*closePtrList, viewPtr->_closeUps); + + if (*closePtrList) + return (*closePtrList)->_graphicName; + } + + return viewPtr->_graphicName; +} + +void LabEngine::drawDirection(CloseDataPtr 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 == NORTH) + message += _resource->getStaticText(kTextFacingNorth); + else if (_direction == EAST) + message += _resource->getStaticText(kTextFacingEast); + else if (_direction == SOUTH) + message += _resource->getStaticText(kTextFacingSouth); + else if (_direction == WEST) + 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 == NORTH) + return WEST; + else if (curDirection == WEST) + return SOUTH; + else if (curDirection == SOUTH) + return EAST; + else + return NORTH; + } else if (arrow == 2) { // Right + if (curDirection == NORTH) + return EAST; + else if (curDirection == EAST) + return SOUTH; + else if (curDirection == SOUTH) + return WEST; + else + return NORTH; + } + + // Should never reach here! + return curDirection; +} + +void LabEngine::setCurrentClose(Common::Point pos, CloseDataPtr *closePtrList, bool useAbsoluteCoords) { + CloseDataPtr closePtr; + + if (!*closePtrList) + closePtr = getViewData(_roomNum, _direction)->_closeUps; + else + closePtr = (*closePtrList)->_subCloseUps; + + Common::Rect target; + while (closePtr) { + 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) && !closePtr->_graphicName.empty()) { + *closePtrList = closePtr; + return; + } + + closePtr = closePtr->_nextCloseUp; + } +} + +bool LabEngine::takeItem(Common::Point pos, CloseDataPtr *closePtrList) { + CloseDataPtr closePtr; + + if (!*closePtrList) { + closePtr = getViewData(_roomNum, _direction)->_closeUps; + } else if ((*closePtrList)->_closeUpType < 0) { + _conditions->inclElement(abs((*closePtrList)->_closeUpType)); + return true; + } else + closePtr = (*closePtrList)->_subCloseUps; + + Common::Rect objRect; + while (closePtr) { + 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; + } + + closePtr = closePtr->_nextCloseUp; + } + + return false; +} + +void LabEngine::doActions(Action *actionList, CloseDataPtr *closePtrList) { + while (actionList) { + updateMusicAndEvents(); + + switch (actionList->_actionType) { + case kActionPlaySound: + _music->_loopSoundEffect = false; + _music->readMusic(actionList->_messages[0], true); + break; + + case kActionPlaySoundNoWait: + _music->_loopSoundEffect = false; + _music->readMusic(actionList->_messages[0], false); + break; + + case kActionPlaySoundLooping: + _music->_loopSoundEffect = true; + _music->readMusic(actionList->_messages[0], false); + break; + + case kActionShowDiff: + _graphics->readPict(actionList->_messages[0], true); + break; + + case kActionShowDiffLooping: + _graphics->readPict(actionList->_messages[0], false); + break; + + case kActionLoadDiff: + if (!actionList->_messages[0].empty()) + // Puts a file into memory + _graphics->loadPict(actionList->_messages[0]); + + break; + + case kActionTransition: + _graphics->doTransition((TransitionType)actionList->_param1, closePtrList, actionList->_messages[0].c_str()); + break; + + case kActionNoUpdate: + _noUpdateDiff = true; + _anim->_doBlack = false; + break; + + case kActionForceUpdate: + _curFileName = " "; + break; + + case kActionShowCurPict: { + Common::String test = getPictName(closePtrList); + + if (test != _curFileName) { + _curFileName = test; + _graphics->readPict(_curFileName); + } + } + break; + + case kActionSetElement: + _conditions->inclElement(actionList->_param1); + break; + + case kActionUnsetElement: + _conditions->exclElement(actionList->_param1); + break; + + case kActionShowMessage: + if (_graphics->_longWinInFront) + _graphics->longDrawMessage(actionList->_messages[0], true); + else + _graphics->drawMessage(actionList->_messages[0], true); + break; + + case kActionCShowMessage: + if (!*closePtrList) + _graphics->drawMessage(actionList->_messages[0], true); + break; + + case kActionShowMessages: + _graphics->drawMessage(actionList->_messages[_utils->getRandom(actionList->_param1)], true); + break; + + case kActionChangeRoom: + if (actionList->_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(closePtrList)); + actionList = nullptr; + 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(); + continue; + } + + _roomNum = actionList->_param1; + _direction = actionList->_param2 - 1; + *closePtrList = nullptr; + _anim->_doBlack = true; + break; + + case kActionSetCloseup: { + Common::Point curPos = Common::Point(_utils->scaleX(actionList->_param1), _utils->scaleY(actionList->_param2)); + CloseDataPtr tmpClosePtr = getObject(curPos, *closePtrList); + + if (tmpClosePtr) + *closePtrList = tmpClosePtr; + } + break; + + case kActionMainView: + *closePtrList = nullptr; + break; + + case kActionSubInv: + if (_inventory[actionList->_param1]._quantity) + (_inventory[actionList->_param1]._quantity)--; + + if (_inventory[actionList->_param1]._quantity == 0) + _conditions->exclElement(actionList->_param1); + + break; + + case kActionAddInv: + (_inventory[actionList->_param1]._quantity) += actionList->_param2; + _conditions->inclElement(actionList->_param1); + break; + + case kActionShowDir: + _graphics->setActionMessage(false); + break; + + case kActionWaitSecs: { + uint32 targetMillis = _system->getMillis() + actionList->_param1 * 1000; + + _graphics->screenUpdate(); + + while (_system->getMillis() < targetMillis) { + updateMusicAndEvents(); + _anim->diffNextFrame(); + } + } + break; + + case kActionStopMusic: + _music->setMusic(false); + break; + + case kActionStartMusic: + _music->setMusic(true); + break; + + case kActionChangeMusic: + _music->changeMusic(actionList->_messages[0]); + _music->setMusicReset(false); + break; + + case kActionResetMusic: + _music->resetMusic(); + _music->setMusicReset(true); + break; + + case kActionFillMusic: + updateMusicAndEvents(); + break; + + case kActionWaitSound: + while (_music->isSoundEffectActive()) { + updateMusicAndEvents(); + _anim->diffNextFrame(); + waitTOF(); + } + + break; + + case kActionClearSound: + if (_music->_loopSoundEffect) { + _music->_loopSoundEffect = false; + _music->stopSoundEffect(); + } else if (_music->isSoundEffectActive()) + _music->stopSoundEffect(); + + break; + + case kActionWinMusic: + _music->freeMusic(); + _music->initMusic("Music:WinGame"); + break; + + case kActionWinGame: + _quitLab = true; + showLab2Teaser(); + break; + + case kActionLostGame: + // This seems to be unused? + error("Unused opcode LOSTGAME has been called"); + break; + + case kActionResetBuffer: + _graphics->freePict(); + break; + + case kActionSpecialCmd: + if (actionList->_param1 == 0) + _anim->_doBlack = true; + else if (actionList->_param1 == 1) + _anim->_doBlack = (_closeDataPtr == nullptr); + else if (actionList->_param1 == 2) + _anim->_doBlack = (_closeDataPtr != nullptr); + else if (actionList->_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 (actionList->_param1 == 4) { + // white the palette + _graphics->whiteScreen(); + waitTOF(); + waitTOF(); + } else if (actionList->_param1 == 6) { + // Restore the palette + waitTOF(); + _graphics->setPalette(_anim->_diffPalette, 256); + waitTOF(); + waitTOF(); + } else if (actionList->_param1 == 7) { + // Quick pause + waitTOF(); + waitTOF(); + waitTOF(); + } + + break; + } + + actionList = actionList->_nextAction; + } + + if (_music->_loopSoundEffect) { + _music->_loopSoundEffect = false; + _music->stopSoundEffect(); + } else { + while (_music->isSoundEffectActive()) { + updateMusicAndEvents(); + _anim->diffNextFrame(); + waitTOF(); + } + } +} + +bool LabEngine::doActionRuleSub(int16 action, int16 roomNum, CloseDataPtr closePtr, CloseDataPtr *setCloseList, 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, setCloseList); + return true; + } + } + } + } + } + + return false; +} + +bool LabEngine::doActionRule(Common::Point pos, int16 action, int16 roomNum, CloseDataPtr *closePtrList) { + if (roomNum) + _newFileName = NOFILE; + else + _newFileName = _curFileName; + + CloseDataPtr curClosePtr = getObject(pos, *closePtrList); + + if (doActionRuleSub(action, roomNum, curClosePtr, closePtrList, false)) + return true; + else if (doActionRuleSub(action, roomNum, *closePtrList, closePtrList, false)) + return true; + else if (doActionRuleSub(action, roomNum, curClosePtr, closePtrList, true)) + return true; + else if (doActionRuleSub(action, roomNum, *closePtrList, closePtrList, true)) + return true; + + return false; +} + +bool LabEngine::doOperateRuleSub(int16 itemNum, int16 roomNum, CloseDataPtr closePtr, CloseDataPtr *setCloseList, 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, setCloseList); + return true; + } + } + } + } + + return false; +} + +bool LabEngine::doOperateRule(Common::Point pos, int16 ItemNum, CloseDataPtr *closePtrList) { + _newFileName = NOFILE; + CloseDataPtr closePtr = getObject(pos, *closePtrList); + + if (doOperateRuleSub(ItemNum, _roomNum, closePtr, closePtrList, false)) + return true; + else if (doOperateRuleSub(ItemNum, _roomNum, *closePtrList, closePtrList, false)) + return true; + else if (doOperateRuleSub(ItemNum, _roomNum, closePtr, closePtrList, true)) + return true; + else if (doOperateRuleSub(ItemNum, _roomNum, *closePtrList, closePtrList, true)) + return true; + else { + _newFileName = _curFileName; + + if (doOperateRuleSub(ItemNum, 0, closePtr, closePtrList, false)) + return true; + else if (doOperateRuleSub(ItemNum, 0, *closePtrList, closePtrList, false)) + return true; + else if (doOperateRuleSub(ItemNum, 0, closePtr, closePtrList, true)) + return true; + else if (doOperateRuleSub(ItemNum, 0, *closePtrList, closePtrList, true)) + return true; + } + + return false; +} + +bool LabEngine::doGoForward(CloseDataPtr *closePtrList) { + 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, closePtrList); + return true; + } + } + } + + return false; +} + +bool LabEngine::doTurn(uint16 from, uint16 to, CloseDataPtr *closePtrList) { + 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, closePtrList); + return true; + } + } + } + + return false; +} + +bool LabEngine::doMainView(CloseDataPtr *closePtrList) { + 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, closePtrList); + 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..3726435390 --- /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, + 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 +}; + +#if defined(WIN32) +#pragma pack(push, 1) +#endif + +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; + CloseData *_nextCloseUp; + CloseData *_subCloseUps; +}; + +struct ViewData { + int16 *_condition; + Common::String _graphicName; + ViewData *_nextCondition; + CloseDataPtr _closeUps; +}; + +struct Action { + ActionType _actionType; + int16 _param1; + int16 _param2; + int16 _param3; + Common::String *_messages; + Action *_nextAction; +}; + +struct Rule { + RuleType _ruleType; + int16 _param1; + int16 _param2; + int16 *_condition; + Action *_actionList; +}; + +struct RoomData { + uint16 _doors[4]; + byte _transitionType; + ViewData *_view[4]; + RuleList *_rules; + Common::String _roomMsg; +}; + +struct InventoryData { + uint16 _quantity; + Common::String _name; + Common::String _bitmapName; +}; + +// Map Flags + +// Where the doors are; in a corridor, assumed to be left doors +#define NORTHDOOR 1 +#define EASTDOOR 2 +#define SOUTHDOOR 4 +#define WESTDOOR 8 + +// Where the doors are in corridors; M means middle, R means right, B means bottom +#define NORTHMDOOR 16 +#define NORTHRDOOR 32 +#define SOUTHMDOOR 64 +#define SOUTHRDOOR 128 + +#define EASTMDOOR 16 +#define EASTBDOOR 32 +#define WESTMDOOR 64 +#define WESTBDOOR 128 + +// Special Map ID's +#define NORMAL 0 +#define UPARROWROOM 1 +#define DOWNARROWROOM 2 +#define BRIDGEROOM 3 +#define VCORRIDOR 4 +#define HCORRIDOR 5 +#define MEDMAZE 6 +#define HEDGEMAZE 7 +#define SURMAZE 8 +#define MULTIMAZEF1 9 +#define MULTIMAZEF2 10 +#define MULTIMAZEF3 11 + +struct MapData { + uint16 _x, _y, _pageNumber, _specialID; + uint32 _mapFlags; +}; + +#if defined(WIN32) +#pragma pack(pop) +#endif + +} // 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..b9239a2068 --- /dev/null +++ b/engines/lab/resource.cpp @@ -0,0 +1,397 @@ +/* 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->updateMusicAndEvents(); + + 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); + return textfont; +} + +Common::String Resource::getText(const Common::String fileName) { + Common::File *dataFile = openDataFile(fileName); + + _vm->updateMusicAndEvents(); + + 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]; + memset(_vm->_rooms, 0, (_vm->_manyRooms + 1) * sizeof(RoomData)); + + for (int i = 1; i <= _vm->_manyRooms; i++) { + RoomData *curRoom = &_vm->_rooms[i]; + curRoom->_doors[NORTH] = dataFile->readUint16LE(); + curRoom->_doors[SOUTH] = dataFile->readUint16LE(); + curRoom->_doors[EAST] = dataFile->readUint16LE(); + curRoom->_doors[WEST] = dataFile->readUint16LE(); + curRoom->_transitionType = dataFile->readByte(); + + curRoom->_view[NORTH] = nullptr; + curRoom->_view[SOUTH] = nullptr; + curRoom->_view[EAST] = nullptr; + curRoom->_view[WEST] = nullptr; + curRoom->_rules = nullptr; + curRoom->_roomMsg = ""; + } + + 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')); + + freeViews(roomNum); + RoomData *curRoom = &_vm->_rooms[roomNum]; + + curRoom->_roomMsg = readString(dataFile); + curRoom->_view[NORTH] = readView(dataFile); + curRoom->_view[SOUTH] = readView(dataFile); + curRoom->_view[EAST] = readView(dataFile); + curRoom->_view[WEST] = readView(dataFile); + curRoom->_rules = readRule(dataFile); + + _vm->updateMusicAndEvents(); + delete dataFile; +} + +void Resource::freeViews(uint16 roomNum) { + if (!_vm->_rooms) + return; + + for (int i = 0; i < 4; i++) + freeView(_vm->_rooms[roomNum]._view[i]); + + freeRule(_vm->_rooms[roomNum]._rules); +} + +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; +} + +int16 *Resource::readConditions(Common::File *file) { + int16 i = 0, cond; + int16 *list = new int16[25]; + memset(list, 0, 25 * sizeof(int16)); + + do { + cond = file->readUint16LE(); + if (i < 25) + list[i++] = cond; + } while (cond); + + return list; +} + +RuleList *Resource::readRule(Common::File *file) { + RuleList *rules = new RuleList(); + + while (file->readByte() == 1) { + Rule rule; + rule._ruleType = (RuleType)file->readSint16LE(); + rule._param1 = file->readSint16LE(); + rule._param2 = file->readSint16LE(); + rule._condition = readConditions(file); + rule._actionList = readAction(file); + rules->push_back(rule); + } + + return rules; +} + +void Resource::freeRule(RuleList *ruleList) { + if (!ruleList) + return; + + for (RuleList::iterator rule = ruleList->begin(); rule != ruleList->end(); ++rule) { + freeAction(rule->_actionList); + delete[] rule->_condition; + } + + delete ruleList; + ruleList = nullptr; +} + +Action *Resource::readAction(Common::File *file) { + Action *action = nullptr; + Action *prev = nullptr; + Action *head = nullptr; + + while (file->readByte() == 1) { + action = new Action(); + if (!head) + head = action; + if (prev) + prev->_nextAction = action; + action->_actionType = (ActionType)file->readSint16LE(); + action->_param1 = file->readSint16LE(); + action->_param2 = file->readSint16LE(); + action->_param3 = file->readSint16LE(); + + if (action->_actionType == kActionShowMessages) { + action->_messages = new Common::String[action->_param1]; + + for (int i = 0; i < action->_param1; i++) + action->_messages[i] = readString(file); + } else { + action->_messages = new Common::String[1]; + action->_messages[0] = readString(file); + } + + action->_nextAction = nullptr; + prev = action; + } + + return head; +} + +void Resource::freeAction(Action *action) { + while (action) { + Action *nextAction = action->_nextAction; + delete[] action->_messages; + delete action; + action = nextAction; + } +} + +CloseData *Resource::readCloseUps(uint16 depth, Common::File *file) { + CloseData *closeup = nullptr; + CloseData *prev = nullptr; + CloseData *head = nullptr; + + while (file->readByte() != '\0') { + closeup = new CloseData(); + if (!head) + head = closeup; + if (prev) + prev->_nextCloseUp = closeup; + 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); + closeup->_subCloseUps = readCloseUps(depth + 1, file); + closeup->_nextCloseUp = nullptr; + prev = closeup; + } + + return head; +} + +void Resource::freeCloseUps(CloseData *closeUps) { + while (closeUps) { + CloseData *nextCloseUp = closeUps->_nextCloseUp; + freeCloseUps(closeUps->_subCloseUps); + delete closeUps; + closeUps = nextCloseUp; + } +} + +ViewData *Resource::readView(Common::File *file) { + ViewData *view = nullptr; + ViewData *prev = nullptr; + ViewData *head = nullptr; + + while (file->readByte() == 1) { + view = new ViewData(); + if (!head) + head = view; + if (prev) + prev->_nextCondition = view; + view->_condition = readConditions(file); + view->_graphicName = readString(file); + view->_closeUps = readCloseUps(0, file); + view->_nextCondition = nullptr; + prev = view; + } + + return head; +} + +void Resource::freeView(ViewData *view) { + while (view) { + ViewData *nextView = view->_nextCondition; + delete[] view->_condition; + freeCloseUps(view->_closeUps); + delete view; + view = nextView; + } +} + +} // End of namespace Lab diff --git a/engines/lab/resource.h b/engines/lab/resource.h new file mode 100644 index 0000000000..dcb7491c75 --- /dev/null +++ b/engines/lab/resource.h @@ -0,0 +1,129 @@ +/* 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); + void freeViews(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); + int16 *readConditions(Common::File *file); + RuleList *readRule(Common::File *file); + void freeRule(RuleList *ruleList); + Action *readAction(Common::File *file); + void freeAction(Action *action); + CloseData *readCloseUps(uint16 depth, Common::File *file); + void freeCloseUps(CloseData *closeUps); + ViewData *readView(Common::File *file); + void freeView(ViewData *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..f70d73b75c --- /dev/null +++ b/engines/lab/savegame.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 "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/tilepuzzle.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 = g_engine->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); + 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 + CloseDataPtr closePtr = nullptr; + _graphics->readPict(getPictName(&closePtr)); + + 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]); + + _tilePuzzle->save(file); + + // Breadcrumbs + for (uint i = 0; i < sizeof(_breadCrumbs); 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(); + + _tilePuzzle->load(file); + + // Breadcrumbs + for (int i = 0; i < 128; i++) { + _breadCrumbs[i]._roomNum = file->readUint16LE(); + _breadCrumbs[i]._direction = file->readUint16LE(); + } + + _droppingCrumbs = (_breadCrumbs[0]._roomNum != 0); + _followingCrumbs = false; + + for (int i = 0; i < 128; 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->resetMusic(); + } + 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..649c725102 --- /dev/null +++ b/engines/lab/special.cpp @@ -0,0 +1,478 @@ +/* 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 { +#define BRIDGE0 148 +#define BRIDGE1 104 +#define DIRTY 175 +#define NONEWS 135 +#define NOCLEAN 152 + +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->closeFont(¬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->closeFont(&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->closeFont(&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->closeFont(&paperFont); + _graphics->setPalette(_anim->_diffPalette, 256); +} + +void LabEngine::loadJournalData() { + if (_journalFont) + _graphics->closeFont(&_journalFont); + + _journalFont = _resource->getFont("F:Journal.fon"); + updateMusicAndEvents(); + + Common::String filename = "Lab:Rooms/j0"; + + bool bridge = _conditions->in(BRIDGE0) || _conditions->in(BRIDGE1); + bool dirty = _conditions->in(DIRTY); + bool news = !_conditions->in(NONEWS); + bool clean = !_conditions->in(NOCLEAN); + + 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'; + + _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, VKEY_LTARROW, new Image(journalFile, this), new Image(journalFile, this))); // back + _journalButtonList.push_back(_event->createButton(194, _utils->vgaScaleY(162) + _utils->svgaCord(1), 2, 0, new Image(journalFile, this), new Image(journalFile, this))); // cancel + _journalButtonList.push_back(_event->createButton(144, _utils->vgaScaleY(164) - _utils->svgaCord(1), 1, VKEY_RTARROW, new Image(journalFile, this), new Image(journalFile, this))); // forward + delete journalFile; + + _anim->_noPalChange = true; + _journalBackImage->_imageData = new byte[_graphics->_screenWidth * _graphics->_screenHeight]; + _graphics->readPict("P:Journal.pic", true, false, _journalBackImage->_imageData); + _anim->_noPalChange = false; + + // Keep a copy of the blank journal + _blankJournal = new byte[_graphics->_screenWidth * _graphics->_screenHeight]; + memcpy(_blankJournal, _journalBackImage->_imageData, _graphics->_screenWidth * _graphics->_screenHeight); + + _screenImage->_imageData = _graphics->getCurrentDrawingBuffer(); +} + +void LabEngine::drawJournalText() { + uint16 drawingToPage = 1; + int charsDrawn = 0; + const char *curText = _journalText.c_str(); + + while (drawingToPage < _journalPage) { + updateMusicAndEvents(); + curText = _journalText.c_str() + charsDrawn; + charsDrawn += _graphics->flowText(_journalFont, -2, 2, 0, false, false, false, false, _utils->vgaRectScale(52, 32, 152, 148), curText); + + _lastPage = (*curText == 0); + + if (_lastPage) + _journalPage = (drawingToPage / 2) * 2; + else + drawingToPage++; + } + + if (_journalPage <= 1) { + curText = _journalTextTitle.c_str(); + _graphics->flowTextToMem(_journalBackImage, _journalFont, -2, 2, 0, false, true, true, true, _utils->vgaRectScale(52, 32, 152, 148), curText); + } else { + curText = _journalText.c_str() + charsDrawn; + charsDrawn += _graphics->flowTextToMem(_journalBackImage, _journalFont, -2, 2, 0, false, false, false, true, _utils->vgaRectScale(52, 32, 152, 148), curText); + } + + updateMusicAndEvents(); + curText = _journalText.c_str() + charsDrawn; + _lastPage = (*curText == 0); + _graphics->flowTextToMem(_journalBackImage, _journalFont, -2, 2, 0, false, false, false, true, _utils->vgaRectScale(171, 32, 271, 148), curText); +} + +void LabEngine::turnPage(bool fromLeft) { + if (fromLeft) { + for (int i = 0; i < _graphics->_screenWidth; i += 8) { + updateMusicAndEvents(); + waitTOF(); + _screenImage->_imageData = _graphics->getCurrentDrawingBuffer(); + _journalBackImage->blitBitmap(i, 0, _screenImage, i, 0, 8, _graphics->_screenHeight, false); + } + } else { + for (int i = (_graphics->_screenWidth - 8); i > 0; i -= 8) { + updateMusicAndEvents(); + waitTOF(); + _screenImage->_imageData = _graphics->getCurrentDrawingBuffer(); + _journalBackImage->blitBitmap(i, 0, _screenImage, i, 0, 8, _graphics->_screenHeight, false); + } + } +} + +void LabEngine::drawJournal(uint16 wipenum, bool needFade) { + _event->mouseHide(); + updateMusicAndEvents(); + drawJournalText(); + _graphics->loadBackPict("P:Journal.pic", _highPalette); + + if (wipenum == 0) + _journalBackImage->blitBitmap(0, 0, _screenImage, 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, 0); + + // Reset the journal background, so that all the text that has been blitted on it is erased + memcpy(_journalBackImage->_imageData, _blankJournal, _graphics->_screenWidth * _graphics->_screenHeight); + + eatMessages(); + _event->mouseShow(); +} + +void LabEngine::processJournal() { + while (1) { + // Make sure we check the music at least after every message + updateMusicAndEvents(); + IntuiMessage *msg = _event->getMsg(); + if (g_engine->shouldQuit()) { + _quitLab = true; + return; + } + + if (!msg) + updateMusicAndEvents(); + else { + uint32 msgClass = msg->_msgClass; + uint16 buttonId = msg->_code; + + if ((msgClass == kMessageRightClick) || + ((msgClass == kMessageRawKey) && (buttonId == Common::KEYCODE_ESCAPE))) + return; + else if (msgClass == kMessageButtonUp) { + 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; + + _screenImage->_width = _journalBackImage->_width = _graphics->_screenWidth; + _screenImage->_height = _journalBackImage->_height = _graphics->_screenHeight; + _journalBackImage->_imageData = nullptr; + _screenImage->_imageData = _graphics->getCurrentDrawingBuffer(); + + updateMusicAndEvents(); + loadJournalData(); + _event->attachButtonList(&_journalButtonList); + drawJournal(0, true); + _event->mouseShow(); + processJournal(); + _event->attachButtonList(nullptr); + _graphics->fade(false, 0); + _event->mouseHide(); + + delete[] _blankJournal; + delete[] _journalBackImage->_imageData; + _blankJournal = _journalBackImage->_imageData = nullptr; + + _event->freeButtonList(&_journalButtonList); + _graphics->closeFont(&_journalFont); + + _screenImage->_imageData = _graphics->getCurrentDrawingBuffer(); + + _graphics->setPen(0); + _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1); + _graphics->blackScreen(); +} + +void LabEngine::drawMonText(char *text, TextFont *monitorFont, Common::Rect textRect, bool isinteractive) { + uint16 drawingToPage = 0, yspacing = 0; + int charsDrawn = 0; + 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->setPen(0); + _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, textRect.bottom); + + for (int i = 0; i < numlines; i++) + _monitorButton->drawImage(0, i * _monitorButtonHeight); + } else if (isinteractive) { + _graphics->setPen(0); + _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, textRect.bottom); + } else { + _graphics->setPen(0); + _graphics->rectFill(textRect); + } + + while (drawingToPage < _monitorPage) { + updateMusicAndEvents(); + 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); + charsDrawn = _graphics->flowText(monitorFont, yspacing, 2, 0, false, false, false, true, textRect, curText); + _event->mouseShow(); +} + +void LabEngine::processMonitor(char *ntext, TextFont *monitorFont, bool isInteractive, Common::Rect textRect) { + Common::String startFileName = _monitorTextFilename; + CloseDataPtr startClosePtr = _closeDataPtr, lastClosePtr[10]; + uint16 depth = 0; + + lastClosePtr[0] = _closeDataPtr; + + while (1) { + if (isInteractive) { + if (!_closeDataPtr) + _closeDataPtr = startClosePtr; + + Common::String test; + if (_closeDataPtr == startClosePtr) + test = startFileName; + else + test = _closeDataPtr->_graphicName; + + if (test != _monitorTextFilename) { + _monitorPage = 0; + _monitorTextFilename = test; + + Common::String text = _resource->getText(_monitorTextFilename); + _graphics->fade(false, 0); + drawMonText((char *)text.c_str(), monitorFont, textRect, isInteractive); + _graphics->fade(true, 0); + } + } + + // Make sure we check the music at least after every message + updateMusicAndEvents(); + IntuiMessage *msg = _event->getMsg(); + if (g_engine->shouldQuit()) { + _quitLab = true; + return; + } + + if (!msg) { + updateMusicAndEvents(); + } else { + uint32 msgClass = msg->_msgClass; + uint16 mouseX = msg->_mouse.x; + uint16 mouseY = msg->_mouse.y; + uint16 code = msg->_code; + + if ((msgClass == kMessageRightClick) || + ((msgClass == kMessageRawKey) && (code == Common::KEYCODE_ESCAPE))) + return; + else if (msgClass == kMessageLeftClick) { + if ((mouseY >= _utils->vgaScaleY(171)) && (mouseY <= _utils->vgaScaleY(200))) { + if (mouseX <= _utils->vgaScaleX(31)) { + return; + } else 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 between 290 and 320 (scaled) + _monitorPage -= 1; + drawMonText(ntext, monitorFont, textRect, isInteractive); + } + } else if (isInteractive) { + CloseDataPtr 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((char *)ntext.c_str(), monitorFont, scaledRect, isinteractive); + _event->mouseShow(); + _graphics->fade(true, 0); + processMonitor((char *)ntext.c_str(), monitorFont, isinteractive, scaledRect); + _graphics->fade(false, 0); + _event->mouseHide(); + _graphics->closeFont(&monitorFont); + + _graphics->setPen(0); + _graphics->rectFill(0, 0, _graphics->_screenWidth - 1, _graphics->_screenHeight - 1); + _graphics->blackAllScreen(); + _graphics->freePict(); +} + +} // End of namespace Lab diff --git a/engines/lab/tilepuzzle.cpp b/engines/lab/tilepuzzle.cpp new file mode 100644 index 0000000000..d39612d49a --- /dev/null +++ b/engines/lab/tilepuzzle.cpp @@ -0,0 +1,397 @@ +/* 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/tilepuzzle.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 }; + +TilePuzzle::TilePuzzle(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; +} + +TilePuzzle::~TilePuzzle() { + for (int i = 0; i < 16; i++) + delete _tiles[i]; + + for (int imgIdx = 0; imgIdx < 10; imgIdx++) { + delete _numberImages[imgIdx]; + _numberImages[imgIdx] = nullptr; + } +} + +void TilePuzzle::mouseTile(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 TilePuzzle::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 TilePuzzle::mouseCombination(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 TilePuzzle::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->setPen(0); + _vm->_graphics->rectFillScaled(97, 22, 220, 126); + + 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 TilePuzzle::showTile(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 TilePuzzle::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 TilePuzzle::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._imageData = _vm->_graphics->getCurrentDrawingBuffer(); + display._width = _vm->_graphics->_screenWidth; + display._height = _vm->_graphics->_screenHeight; + + byte *buffer = new byte[_tiles[1]->_width * _tiles[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._imageData = _vm->_graphics->getCurrentDrawingBuffer(); + _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 TilePuzzle::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 TilePuzzle::doCombination() { + for (int i = 0; i <= 5; i++) + _numberImages[_combination[i]]->drawImage(_vm->_utils->vgaScaleX(COMBINATION_X[i]), _vm->_utils->vgaScaleY(65)); +} + +void TilePuzzle::showCombination(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 CurBit = 0; CurBit < 10; CurBit++) + _numberImages[CurBit] = new Image(numFile, _vm); + + delete numFile; + + doCombination(); + + _vm->_graphics->setPalette(_vm->_anim->_diffPalette, 256); +} + +void TilePuzzle::save(Common::OutSaveFile *file) { + // Combination lock and tile stuff + 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 TilePuzzle::load(Common::InSaveFile *file) { + // Combination lock and tile stuff + 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/tilepuzzle.h b/engines/lab/tilepuzzle.h new file mode 100644 index 0000000000..dd4abba8ec --- /dev/null +++ b/engines/lab/tilepuzzle.h @@ -0,0 +1,104 @@ +/* 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 TilePuzzle { +private: + LabEngine *_vm; + Image *_tiles[16]; + Image *_numberImages[10]; + uint16 _curTile[4][4]; + byte _combination[6]; + +public: + TilePuzzle(LabEngine *vm); + virtual ~TilePuzzle(); + + /** + * Processes mouse clicks and changes the combination. + */ + void mouseTile(Common::Point pos); + + /** + * Processes mouse clicks and changes the combination. + */ + void mouseCombination(Common::Point pos); + + /** + * Reads in a backdrop picture. + */ + void showCombination(const Common::String filename); + + /** + * Reads in a backdrop picture. + */ + void showTile(const Common::String filename, bool showSolution); + 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 combination number of one of the slots + */ + void changeTile(uint16 col, uint16 row); + + /** + * Draws the images of the combination lock to the display bitmap. + */ + void doCombination(); + + /** + * 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 |