diff options
author | Eugene Sandulenko | 2016-09-03 10:41:31 +0200 |
---|---|---|
committer | GitHub | 2016-09-03 10:41:31 +0200 |
commit | 9d4d4f6803252383b4488638092f004e6cc7a214 (patch) | |
tree | ef491b2d3620b91989e5f6c43292b2b5cb87b058 | |
parent | e93b52416f110e15bd67367d9cfed8fea3851a2c (diff) | |
parent | 31e1e02ad6f589d927c361026722cfaf7ac74d94 (diff) | |
download | scummvm-rg350-9d4d4f6803252383b4488638092f004e6cc7a214.tar.gz scummvm-rg350-9d4d4f6803252383b4488638092f004e6cc7a214.tar.bz2 scummvm-rg350-9d4d4f6803252383b4488638092f004e6cc7a214.zip |
Merge pull request #807 from blorente/macventure-clean
MACVENTURE: Add MacVenture engine.
47 files changed, 8706 insertions, 0 deletions
diff --git a/Makefile.common b/Makefile.common index df24d397de..762b9051d0 100644 --- a/Makefile.common +++ b/Makefile.common @@ -285,6 +285,9 @@ endif ifdef ENABLE_WINTERMUTE DIST_FILES_ENGINEDATA+=wintermute.zip endif +ifdef ENABLE_MACVENTURE +DIST_FILES_ENGINEDATA+=macventure.dat +endif DIST_FILES_ENGINEDATA:=$(addprefix $(srcdir)/dists/engine-data/,$(DIST_FILES_ENGINEDATA)) # pred.dic is currently only used for the AGI engine diff --git a/devtools/create_macventure/AltBox_act.bmp b/devtools/create_macventure/AltBox_act.bmp Binary files differnew file mode 100644 index 0000000000..aa9fa7e841 --- /dev/null +++ b/devtools/create_macventure/AltBox_act.bmp diff --git a/devtools/create_macventure/AltBox_inac.bmp b/devtools/create_macventure/AltBox_inac.bmp Binary files differnew file mode 100644 index 0000000000..aa9fa7e841 --- /dev/null +++ b/devtools/create_macventure/AltBox_inac.bmp diff --git a/devtools/create_macventure/InvWindow_act.bmp b/devtools/create_macventure/InvWindow_act.bmp Binary files differnew file mode 100644 index 0000000000..a38b0a9834 --- /dev/null +++ b/devtools/create_macventure/InvWindow_act.bmp diff --git a/devtools/create_macventure/InvWindow_inac.bmp b/devtools/create_macventure/InvWindow_inac.bmp Binary files differnew file mode 100644 index 0000000000..f3f2cfaa8b --- /dev/null +++ b/devtools/create_macventure/InvWindow_inac.bmp diff --git a/devtools/create_macventure/NoGrowDoc_act.bmp b/devtools/create_macventure/NoGrowDoc_act.bmp Binary files differnew file mode 100644 index 0000000000..3570e798d9 --- /dev/null +++ b/devtools/create_macventure/NoGrowDoc_act.bmp diff --git a/devtools/create_macventure/NoGrowDoc_inac.bmp b/devtools/create_macventure/NoGrowDoc_inac.bmp Binary files differnew file mode 100644 index 0000000000..f3f97652e5 --- /dev/null +++ b/devtools/create_macventure/NoGrowDoc_inac.bmp diff --git a/devtools/create_macventure/PlainDBox_act.bmp b/devtools/create_macventure/PlainDBox_act.bmp Binary files differnew file mode 100644 index 0000000000..6521080588 --- /dev/null +++ b/devtools/create_macventure/PlainDBox_act.bmp diff --git a/devtools/create_macventure/PlainDBox_inac.bmp b/devtools/create_macventure/PlainDBox_inac.bmp Binary files differnew file mode 100644 index 0000000000..6521080588 --- /dev/null +++ b/devtools/create_macventure/PlainDBox_inac.bmp diff --git a/devtools/create_macventure/RDoc4_act.bmp b/devtools/create_macventure/RDoc4_act.bmp Binary files differnew file mode 100644 index 0000000000..ab78661359 --- /dev/null +++ b/devtools/create_macventure/RDoc4_act.bmp diff --git a/devtools/create_macventure/RDoc4_inac.bmp b/devtools/create_macventure/RDoc4_inac.bmp Binary files differnew file mode 100644 index 0000000000..ab78661359 --- /dev/null +++ b/devtools/create_macventure/RDoc4_inac.bmp diff --git a/devtools/create_macventure/ZoomDoc_act.bmp b/devtools/create_macventure/ZoomDoc_act.bmp Binary files differnew file mode 100644 index 0000000000..b2ccc9c602 --- /dev/null +++ b/devtools/create_macventure/ZoomDoc_act.bmp diff --git a/devtools/create_macventure/ZoomDoc_inac.bmp b/devtools/create_macventure/ZoomDoc_inac.bmp Binary files differnew file mode 100644 index 0000000000..963949b392 --- /dev/null +++ b/devtools/create_macventure/ZoomDoc_inac.bmp diff --git a/devtools/create_macventure/create_macventure.sh b/devtools/create_macventure/create_macventure.sh new file mode 100755 index 0000000000..1408179039 --- /dev/null +++ b/devtools/create_macventure/create_macventure.sh @@ -0,0 +1,8 @@ +printf "Creating border file...\n" + +zip -r macventure.zip *.bmp +mv macventure.zip macventure.dat + +echo done + +ls -l macventure.dat diff --git a/engines/macventure/configure.engine b/engines/macventure/configure.engine new file mode 100644 index 0000000000..dc7cf7912c --- /dev/null +++ b/engines/macventure/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 macventure "MacVenture" no diff --git a/engines/macventure/container.cpp b/engines/macventure/container.cpp new file mode 100644 index 0000000000..ff98d6961f --- /dev/null +++ b/engines/macventure/container.cpp @@ -0,0 +1,187 @@ +/* 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 "macventure/container.h" + +namespace MacVenture { + +Container::Container(Common::String filename) { + _filename = filename; + + if (!_file.open(_filename)) { + error("CONTAINER: Could not open %s", _filename.c_str()); + } + + _res = _file.readStream(_file.size()); + _header = _res->readUint32BE(); + _simplified = false; + + for (uint i = 0; i < 16; ++i) { + _huff.push_back(0); + } + + for (uint i = 0; i < 16; ++i) { + _lens.push_back(0); + } + + if (!(_header & 0x80000000)) { + // Is simplified container + _simplified = true; + int dataLen = _res->size() - sizeof(_header); + _lenObjs = _header; + _numObjs = dataLen / _lenObjs; + } else { + _header &= 0x7fffffff; + _res->seek(_header, SEEK_SET); + _numObjs = _res->readUint16BE(); + + for (uint i = 0; i < 15; ++i) { + _huff[i] = _res->readUint16BE(); + } + + for (uint i = 0; i < 16; ++i) { + _lens[i] = _res->readByte(); + } + + // Read groups + uint numGroups = _numObjs / 64; + if ((_numObjs % 64) > 0) { + numGroups++; + } + + for (uint i = 0; i < numGroups; ++i) { + ItemGroup group; + + // Place myself in the correct position to read group + _res->seek(_header + (i * 6) + 0x30, SEEK_SET); + byte b1, b2, b3; + b1 = _res->readByte(); + b2 = _res->readByte(); + b3 = _res->readByte(); + group.bitOffset = (b1 << 16) + (b2 << 8) + (b3 << 0); + + b1 = _res->readByte(); + b2 = _res->readByte(); + b3 = _res->readByte(); + group.offset = (b1 << 16) + (b2 << 8) + (b3 << 0); + + // Place the bit reader in the correct position + // group.bitOffset indicates the offset from the start of the subHeader + _res->seek(_header + (group.bitOffset >> 3), SEEK_SET); + uint32 bits = group.bitOffset & 7; + + for (uint j = 0; j < 64; ++j) { + uint32 length = 0; + uint32 mask = 0; + mask = _res->readUint32BE(); + mask >>= (16 - bits); + mask &= 0xFFFF; + debugC(4, kMVDebugContainer, "Load mask of object &%d:%d is %x", i, j, mask); + _res->seek(-4, SEEK_CUR); + // Look in the Huffman table + int x = 0; + for (x = 0; x < 16; x++) { + if (_huff[x] > mask) { + break; + } + } + + // I will opt to copy the code from webventure, + // But according to the docs, this call should suffice: + // length = bitStream.getBits(_lens[x]); + // The problem is that _lens[] usually contains values larger + // Than 32, so we have to read them with the method below + + //This code below, taken from the implementation, seems to give the same results. + + uint32 bitSize = _lens[x]; + bits += bitSize & 0xF; + if (bits & 0x10) { + bits &= 0xF; + _res->seek(2, SEEK_CUR); + } + bitSize >>= 4; + if (bitSize) { + length = _res->readUint32BE(); + _res->seek(-4, SEEK_CUR); + bitSize--; + if (bitSize == 0) { + length = 0; + } else { + length >>= (32 - bitSize) - bits; + } + length &= (1 << bitSize) - 1; + length |= 1 << bitSize; + bits += bitSize; + if (bits & 0x10) { + bits &= 0xF; + _res->seek(2, SEEK_CUR); + } + } + group.lengths[j] = length; + debugC(4, kMVDebugContainer, "Load legth of object %d:%d is %d", i, j, length); + } + _groups.push_back(group); + } + } +} + +Container::~Container() { + + if (_file.isOpen()) + _file.close(); + + if (_res) + delete _res; +} + +uint32 Container::getItemByteSize(uint32 id) { + if (_simplified) { + return _lenObjs; + } else { + uint32 groupID = (id >> 6); + uint32 objectIndex = id & 0x3f; // Index within the group + return _groups[groupID].lengths[objectIndex]; + } +} + +Common::SeekableReadStream *Container::getItem(uint32 id) { + if (_simplified) { + _res->seek((id * _lenObjs) + sizeof(_header), SEEK_SET); + } else { + uint32 groupID = (id >> 6); + uint32 objectIndex = id & 0x3f; // Index within the group + + uint32 offset = 0; + for (uint i = 0; i < objectIndex; i++) { + offset += _groups[groupID].lengths[i]; + } + _res->seek(_groups[groupID].offset + offset + sizeof(_header), SEEK_SET); + } + + // HACK Should Limit the size of the stream returned + Common::SeekableReadStream *res = _res->readStream(_res->size() - _res->pos() + 1); + return res; +} + + +} // End of namespace MacVenture diff --git a/engines/macventure/container.h b/engines/macventure/container.h new file mode 100644 index 0000000000..e630bdf1be --- /dev/null +++ b/engines/macventure/container.h @@ -0,0 +1,81 @@ +/* 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 MACVENTURE_CONTAINER_H +#define MACVENTURE_CONTAINER_H + +#include "macventure/macventure.h" + +#include "common/file.h" +#include "common/fs.h" +#include "common/bitstream.h" + +namespace MacVenture { + +struct ItemGroup { + uint32 bitOffset; //It's really uint24 + uint32 offset; //It's really uint24 + uint32 lengths[64]; +}; + +typedef uint32 ContainerHeader; + +class Container { + +public: + Container(Common::String filename); + ~Container(); + +public: + /** + * Must be called before retrieving an object. + */ + uint32 getItemByteSize(uint32 id); + + /** + * getItemByteSize should be called before this one + */ + Common::SeekableReadStream *getItem(uint32 id); + +protected: + + bool _simplified; + + uint _lenObjs; // In the case of simple container, lenght of an object + uint _numObjs; + + ContainerHeader _header; + + Common::Array<uint16> _huff; // huffman masks + Common::Array<uint8> _lens; // huffman lengths + Common::Array<ItemGroup> _groups; + + Common::String _filename; + Common::File _file; + Common::SeekableReadStream *_res; + +}; + + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/controls.cpp b/engines/macventure/controls.cpp new file mode 100644 index 0000000000..078ea88dce --- /dev/null +++ b/engines/macventure/controls.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. +* +*/ + +#include "macventure/gui.h" + +namespace MacVenture { +CommandButton::CommandButton() { + _gui = NULL; +} + +CommandButton::CommandButton(ControlData data, Gui *g) { + _data = data; + _gui = g; + _selected = false; +} + +void CommandButton::draw(Graphics::ManagedSurface &surface) const { + + uint colorFill = _selected ? kColorBlack : kColorWhite; + uint colorText = _selected ? kColorWhite : kColorBlack; + + surface.fillRect(_data.bounds, colorFill); + surface.frameRect(_data.bounds, kColorBlack); + + if (_data.titleLength > 0) { + const Graphics::Font &font = _gui->getCurrentFont(); + Common::String title(_data.title); + font.drawString( + &surface, + title, + _data.bounds.left, + _data.bounds.top, + _data.bounds.right - _data.bounds.left, + colorText, + Graphics::kTextAlignCenter); + } +} + +bool CommandButton::isInsideBounds(const Common::Point point) const { + return _data.bounds.contains(point); +} + +const ControlData &CommandButton::getData() const { + return _data; +} + +void CommandButton::select() { + _selected = true; +} + +void CommandButton::unselect() { + _selected = false; +} + +bool CommandButton::isSelected() { + return _selected; +} +} diff --git a/engines/macventure/controls.h b/engines/macventure/controls.h new file mode 100644 index 0000000000..81b5278396 --- /dev/null +++ b/engines/macventure/controls.h @@ -0,0 +1,106 @@ +/* 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 MACVENTURE_CONTROLS_H +#define MACVENTURE_CONTROLS_H + +namespace MacVenture { + +class Gui; + +enum ControlType { // HACK, should correspond exactly with the types of controls (sliders etc) + kControlExitBox = 0, + kControlExamine = 1, + kControlOpen = 2, + kControlClose = 3, + kControlSpeak = 4, + kControlOperate = 5, + kControlGo = 6, + kControlHit = 7, + kControlConsume = 8, + kControlClickToContinue = 9 +}; + +enum ControlAction { + kNoCommand = 0, + kStartOrResume = 1, + kClose = 2, + kTick = 3, + kActivateObject = 4, + kMoveObject = 5, + kConsume = 6, + kExamine = 7, + kGo = 8, + kHit = 9, + kOpen = 10, + kOperate = 11, + kSpeak = 12, + kBabble = 13, + kTargetName = 14, + kDebugObject = 15, + kClickToContinue = 16 +}; + +struct ControlData { + Common::Rect bounds; + uint16 scrollValue; + uint8 visible; + uint16 scrollMax; + uint16 scrollMin; + uint16 cdef; + ControlAction refcon; + ControlType type; + uint8 titleLength; + Common::String title; + uint16 border; +}; + +class CommandButton { + +enum { + kCommandsLeftPadding = 0, + kCommandsTopPadding = 0 +}; + +public: + + CommandButton(); + + CommandButton(ControlData data, Gui *g); + ~CommandButton() {} + + void draw(Graphics::ManagedSurface &surface) const; + bool isInsideBounds(const Common::Point point) const; + const ControlData &getData() const; + void select(); + void unselect(); + bool isSelected(); + +private: + bool _selected; + ControlData _data; + Gui *_gui; +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/cursor.cpp b/engines/macventure/cursor.cpp new file mode 100644 index 0000000000..655615a523 --- /dev/null +++ b/engines/macventure/cursor.cpp @@ -0,0 +1,128 @@ +/* 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 "macventure/gui.h" + +namespace MacVenture { + +static void cursorTimerHandler(void *refCon); + +static const ClickState _transitionTable[kCursorStateCount][kCursorInputCount] = { + /* Button down, Button Up, Tick */ + /* Idle */ {kCursorSCStart, kCursorIdle, kCursorIdle }, + /* SC Start */ {kCursorSCStart, kCursorDCStart, kCursorSCDrag}, + /* SC Do */ {kCursorSCDrag, kCursorIdle, kCursorSCDrag}, + /* DC Start */ {kCursorDCDo, kCursorDCStart, kCursorSCSink}, + /* DC Do */ {kCursorDCDo, kCursorIdle, kCursorDCDo }, + /* SC Sink */ {kCursorIdle, kCursorIdle, kCursorIdle } +}; + +Cursor::Cursor(Gui *gui) { + _gui = gui; + _state = kCursorIdle; +} +Cursor::~Cursor() {} + +void Cursor::tick() { + changeState(kTickCol); +} + +bool Cursor::processEvent(const Common::Event &event) { + if (event.type == Common::EVENT_MOUSEMOVE) { + _pos = event.mouse; + return true; + } + if (event.type == Common::EVENT_LBUTTONDOWN) { + changeState(kButtonDownCol); + return true; + } + if (event.type == Common::EVENT_LBUTTONUP) { + changeState(kButtonUpCol); + return true; + } + + return false; +} + +Common::Point Cursor::getPos() { + return _pos; +} + +bool Cursor::canSelectDraggable() { + return _state == kCursorSCDrag; +} + +void Cursor::changeState(CursorInput input) { + debugC(3, kMVDebugGUI, "Change cursor state: [%d] -> [%d]", _state, _transitionTable[_state][input]); + if (_state != _transitionTable[_state][input]) { + executeStateOut(); + _state = _transitionTable[_state][input]; + executeStateIn(); + } +} + +void Cursor::executeStateIn() { + switch (_state) { + case kCursorSCStart: + g_system->getTimerManager()->installTimerProc(&cursorTimerHandler, 300000, this, "macVentureCursor"); + _gui->selectForDrag(_pos); + break; + case kCursorDCStart: + g_system->getTimerManager()->installTimerProc(&cursorTimerHandler, 300000, this, "macVentureCursor"); + break; + case kCursorSCSink: + _gui->handleSingleClick(); + changeState(kTickCol); + break; + default: + break; + } +} + +void Cursor::executeStateOut() { + switch (_state) { + case kCursorIdle: + break; + case kCursorSCStart: + g_system->getTimerManager()->removeTimerProc(&cursorTimerHandler); + break; + case kCursorSCDrag: + _gui->handleSingleClick(); + break; + case kCursorDCStart: + g_system->getTimerManager()->removeTimerProc(&cursorTimerHandler); + break; + case kCursorDCDo: + _gui->handleDoubleClick(); + break; + default: + break; + } +} + +static void cursorTimerHandler(void *refCon) { + Cursor *cursor = (Cursor *)refCon; + cursor->tick(); +} + + +} // End of namespace MacVenture diff --git a/engines/macventure/datafiles.cpp b/engines/macventure/datafiles.cpp new file mode 100644 index 0000000000..2c17b66209 --- /dev/null +++ b/engines/macventure/datafiles.cpp @@ -0,0 +1,88 @@ +/* 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 MACVENTURE_DATAFILES_H +#define MACVENTURE_DATAFILES_H + +#include "macventure/macventure.h" +#include "macventure/windows.h" + +#include "common/unzip.h" + +namespace MacVenture { + +#define MACVENTURE_DATA_BUNDLE Common::String("macventure.dat") + +struct BorderName { + MVWindowType type; + const char *name; +}; + +static const BorderName g_borderNames[] = { + {kDocument, "Document"}, + {kDBox, "DBox"}, + {kPlainDBox, "PlainDBox"}, + {kAltBox, "AltBox"}, + {kNoGrowDoc, "NoGrowDoc"}, + {kMovableDBox, "MovableDBox"}, + {kZoomDoc, "ZoomDoc"}, + {kZoomNoGrow, "ZoomNoGrow"}, + {kInvWindow, "InvWindow"}, + {kRDoc16, "RDoc16"}, + {kRDoc4, "RDoc4"}, + {kRDoc6, "RDoc6"}, + {kRDoc10, "RDoc10"}, + {kNoType, "No type"} +}; + +Common::String windowTypeName(MVWindowType windowType) { + int i = 0; + while (g_borderNames[i].type != kNoType) { + i++; + if (g_borderNames[i].type == windowType) { + return g_borderNames[i].name; + } + } + return ""; +} + +void MacVentureEngine::loadDataBundle() { + _dataBundle = Common::makeZipArchive(MACVENTURE_DATA_BUNDLE); + if (!_dataBundle) { + error("ENGINE: Couldn't load data bundle '%s'.", MACVENTURE_DATA_BUNDLE.c_str()); + } +} + +Common::SeekableReadStream *MacVentureEngine::getBorderFile(MVWindowType windowType, bool isActive) { + Common::String filename = windowTypeName(windowType); + filename += (isActive ? "_act.bmp" : "_inac.bmp"); + if (!_dataBundle->hasFile(filename)) { + warning("Missing border file '%s' in data bundle", filename.c_str()); + return NULL; + } + + return _dataBundle->createReadStreamForMember(filename); +} + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/debug.h b/engines/macventure/debug.h new file mode 100644 index 0000000000..c223d01fe4 --- /dev/null +++ b/engines/macventure/debug.h @@ -0,0 +1,38 @@ +/* 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 MACVENTURE_DEBUG_H +#define MACVENTURE_DEBUG_H + +namespace MacVenture { +enum MacVentureDebugChannel { + kMVDebugMain = 1 << 0, + kMVDebugGUI = 1 << 1, + kMVDebugImage = 1 << 2, + kMVDebugText = 1 << 3, + kMVDebugScript = 1 << 4, + kMVDebugSound = 1 << 5, + kMVDebugContainer = 1 << 6 +}; +} // End namespace MacVenture + +#endif diff --git a/engines/macventure/detection.cpp b/engines/macventure/detection.cpp new file mode 100644 index 0000000000..10676d5b25 --- /dev/null +++ b/engines/macventure/detection.cpp @@ -0,0 +1,179 @@ +/* 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 "base/plugins.h" + +#include "engines/advancedDetector.h" +#include "common/system.h" + +#include "macventure/macventure.h" + +namespace MacVenture { + +#define ADGF_DEFAULT (ADGF_DROPLANGUAGE|ADGF_DROPPLATFORM|ADGF_MACRESFORK) + +#define BASEGAME(n, v, f, md5, s) {n, v, AD_ENTRY1s(f, md5, s), Common::EN_ANY, Common::kPlatformMacintosh, ADGF_DEFAULT, GUIO0()} + +static const ADGameDescription gameDescriptions[] = { + BASEGAME("shadowgate", "Zojoi Rerelease", "Shadowgate.bin", "ebbfbcbf93938bd2900cb0c0213b19ad", 68974), // Zojoi Rerelease + BASEGAME("deja_vu", "Zojoi Rerelease", "Deja Vu.bin", "5e9f5a8e3c8eb29ed02b34ae5937354f", 69034), // Zojoi Rerelease + BASEGAME("deja_vu2", "Zojoi Rerelease", "Lost in Las Vegas.bin", "8f8e1d8d41f577ee0fbc03847969af0d", 66520), // Zojoi Rerelease + AD_TABLE_END_MARKER +}; + +const char *MacVentureEngine::getGameFileName() const { + return _gameDescription->filesDescriptions[0].fileName; +} +} // End of namespace MacVenture + +static const PlainGameDescriptor macventureGames[] = { + { "shadowgate", "Shadowgate" }, + { "deja_vu", "Deja Vu"}, + { "deja_vu2", "Deja Vu II"}, + { 0, 0 } +}; + +namespace MacVenture { + +SaveStateDescriptor loadMetaData(Common::SeekableReadStream *s, int slot); + +class MacVentureMetaEngine : public AdvancedMetaEngine { +public: + MacVentureMetaEngine() : AdvancedMetaEngine(MacVenture::gameDescriptions, sizeof(ADGameDescription), macventureGames) { + _guiOptions = GUIO0(); + _md5Bytes = 5000000; // TODO: Upper limit, adjust it once all games are added + } + + virtual const char *getName() const override { + return "MacVenture"; + } + virtual const char *getOriginalCopyright() const override { + return "(C) ICOM Simulations"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual bool hasFeature(MetaEngineFeature f) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + virtual void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool MacVentureMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail) || + (f == kSavesSupportCreationDate) || + (f == kSavesSupportPlayTime); +} + +bool MacVentureEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +SaveStateList MacVentureMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String pattern = target; + pattern += ".###"; + + filenames = saveFileMan->listSavefiles(pattern); + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + int slotNum = atoi(file->c_str() + file->size() - 3); + SaveStateDescriptor desc; + // Do not allow save slot 0 (used for auto-saving) to be deleted or + // overwritten. + desc.setDeletableFlag(slotNum != 0); + desc.setWriteProtectedFlag(slotNum == 0); + + if (slotNum >= 0 && slotNum <= getMaximumSaveSlot()) { + Common::InSaveFile *in = saveFileMan->openForLoading(*file); + if (in) { + desc = loadMetaData(in, slotNum); + if (desc.getSaveSlot() != slotNum) { + // invalid + delete in; + continue; + } + saveList.push_back(desc); + delete in; + } + } + } + + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); + return saveList; +} + +int MacVentureMetaEngine::getMaximumSaveSlot() const { return 999; } + +bool MacVentureMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *game) const { + if (game) { + *engine = new MacVenture::MacVentureEngine(syst, game); + } + return game != 0; +} + +void MacVentureMetaEngine::removeSaveState(const char *target, int slot) const { + g_system->getSavefileManager()->removeSavefile(Common::String::format("%s.%03d", target, slot)); +} + + +SaveStateDescriptor MacVentureMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + SaveStateDescriptor desc; + Common::String saveFileName; + Common::String pattern = target; + pattern += ".###"; + Common::StringArray filenames = saveFileMan->listSavefiles(pattern); + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + int slotNum = atoi(file->c_str() + file->size() - 3); + if (slotNum == slot) { + saveFileName = *file; + } + } + + Common::InSaveFile *in = saveFileMan->openForLoading(saveFileName); + if (in) { + desc = loadMetaData(in, slot); + delete in; + return desc; + } + return SaveStateDescriptor(-1, ""); +} + +} // End of namespace MacVenture + +#if PLUGIN_ENABLED_DYNAMIC(MACVENTURE) + REGISTER_PLUGIN_DYNAMIC(MACVENTURE, PLUGIN_TYPE_ENGINE, MacVenture::MacVentureMetaEngine); +#else + REGISTER_PLUGIN_STATIC(MACVENTURE, PLUGIN_TYPE_ENGINE, MacVenture::MacVentureMetaEngine); +#endif diff --git a/engines/macventure/dialog.cpp b/engines/macventure/dialog.cpp new file mode 100644 index 0000000000..7827bddffd --- /dev/null +++ b/engines/macventure/dialog.cpp @@ -0,0 +1,253 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/system.h" + +#include "macventure/dialog.h" +namespace MacVenture { + + +Dialog::Dialog(Gui *gui, Common::Point pos, uint width, uint height) : + _gui(gui), _bounds(Common::Rect(pos.x, pos.y, pos.x + width, pos.y + height)) {} + +Dialog::Dialog(Gui *gui, PrebuiltDialogs prebuilt) { + _gui = gui; + const PrebuiltDialog &dialog = g_prebuiltDialogs[prebuilt]; + calculateBoundsFromPrebuilt(dialog.bounds); + for (int i = 0; dialog.elements[i].type != kDEEnd; i++) { + addPrebuiltElement(dialog.elements[i]); + } +} + +Dialog::~Dialog() { + for (Common::Array<DialogElement*>::iterator it = _elements.begin(); it != _elements.end(); it++) { + delete *it; + } +} + +void Dialog::handleDialogAction(DialogElement *trigger, DialogAction action) { + switch(action) { + case kDACloseDialog: + _gui->closeDialog(); + break; + case kDASubmit: + _gui->setTextInput(_userInput); + _gui->closeDialog(); + break; + case kDASaveAs: + _gui->saveGame(); + _gui->closeDialog(); + break; + case kDALoadGame: + _gui->loadGame(); + _gui->closeDialog(); + break; + case kDANewGame: + _gui->newGame(); + _gui->closeDialog(); + break; + case kDAQuit: + _gui->quitGame(); + _gui->closeDialog(); + break; + default: + break; + } +} + +const Graphics::Font &Dialog::getFont() { + return _gui->getCurrentFont(); +} + +bool Dialog::processEvent(Common::Event event) { + for (Common::Array<DialogElement*>::iterator it = _elements.begin(); it != _elements.end(); it++) { + if ((*it)->processEvent(this, event)) { + return true; + } + } + return false; +} + +void Dialog::addButton(Common::String title, MacVenture::DialogAction action, Common::Point position, uint width, uint height) { + _elements.push_back(new DialogButton(this, title, action, position, width, height)); +} + +void Dialog::addText(Common::String content, Common::Point position) { + _elements.push_back(new DialogPlainText(this, content, position)); +} + +void Dialog::addTextInput(Common::Point position, int width, int height) { + _elements.push_back(new DialogTextInput(this, position, width, height)); +} + +void Dialog::draw() { + Graphics::ManagedSurface compose; + // Compose the surface + compose.create(_bounds.width(), _bounds.height()); + Common::Rect base(0, 0, _bounds.width(), _bounds.height()); + compose.fillRect(base, kColorWhite); + compose.frameRect(base, kColorBlack); + for (Common::Array<DialogElement*>::iterator it = _elements.begin(); it != _elements.end(); it++) { + (*it)->draw(this, compose); + } + + g_system->copyRectToScreen(compose.getPixels(), compose.pitch, + _bounds.left, _bounds.top, _bounds.width(), _bounds.height()); + } + +void Dialog::localize(Common::Point &point) { + point.x -= _bounds.left; + point.y -= _bounds.top; +} + +void Dialog::setUserInput(Common::String content) { + _userInput = content; +} + +void Dialog::addPrebuiltElement(const MacVenture::PrebuiltDialogElement &element) { + Common::Point position(element.left, element.top); + switch(element.type) { + case kDEButton: + addButton(element.title, element.action, position, element.width, element.height); + break; + case kDEPlainText: + addText(element.title, position); + break; + case kDETextInput: + addTextInput(position, element.width, element.height); + break; + default: + break; + } +} + +// Dialog Element + +DialogElement::DialogElement(Dialog *dialog, Common::String title, DialogAction action, Common::Point position, uint width, uint height) : + _text(title), _action(action) { + if (width == 0) { + width = dialog->getFont().getStringWidth(title); + } + if (height == 0) { + height = dialog->getFont().getFontHeight(); + } + _bounds = Common::Rect(position.x, position.y, position.x + width, position.y + height); +} + +bool DialogElement::processEvent(MacVenture::Dialog *dialog, Common::Event event) { + return doProcessEvent(dialog, event); +} + +void DialogElement::draw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target) { + doDraw(dialog, target); +} + +const Common::String &DialogElement::getText() { + return doGetText(); +} + +const Common::String &DialogElement::doGetText() { + return _text; +} + +// CONCRETE DIALOG ELEMENTS + +DialogButton::DialogButton(Dialog *dialog, Common::String title, DialogAction action, Common::Point position, uint width, uint height): + DialogElement(dialog, title, action, position, width, height) {} + +bool DialogButton::doProcessEvent(MacVenture::Dialog *dialog, Common::Event event) { + Common::Point mouse = event.mouse; + if (event.type == Common::EVENT_LBUTTONDOWN) { + dialog->localize(mouse); + if (_bounds.contains(mouse)) { + debugC(2, kMVDebugGUI, "Click! Button: %s", _text.c_str()); + dialog->handleDialogAction(this, _action); + return true; + } + } + return false; +} + +void DialogButton::doDraw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target) { + target.fillRect(_bounds, kColorWhite); + target.frameRect(_bounds, kColorBlack); + // Draw title + dialog->getFont().drawString( + &target, _text, _bounds.left, _bounds.top, _bounds.width(), kColorBlack, Graphics::kTextAlignCenter); +} + +DialogPlainText::DialogPlainText(Dialog *dialog, Common::String content, Common::Point position) : + DialogElement(dialog, content, kDANone, position, 0, 0) { } + +DialogPlainText::~DialogPlainText() {} + +bool DialogPlainText::doProcessEvent(MacVenture::Dialog *dialog, Common::Event event) { + return false; +} + +void DialogPlainText::doDraw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target) { + // Draw contents + dialog->getFont().drawString( + &target, _text, _bounds.left, _bounds.top, _bounds.width(), kColorBlack, Graphics::kTextAlignCenter); + +} + +DialogTextInput::DialogTextInput(Dialog *dialog, Common::Point position, uint width, uint height) : + DialogElement(dialog, "", kDANone, position, width, height) {} +DialogTextInput::~DialogTextInput() {} + +bool DialogTextInput::doProcessEvent(Dialog *dialog, Common::Event event) { + if (event.type == Common::EVENT_KEYDOWN) { + switch (event.kbd.keycode) { + case Common::KEYCODE_BACKSPACE: + if (!_text.empty()) { + _text.deleteLastChar(); + dialog->setUserInput(_text); + return true; + } + break; + default: + if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7f) { + _text += (char)event.kbd.ascii; + dialog->setUserInput(_text); + return true; + } + break; + } + } + return false; +} + +void DialogTextInput::doDraw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target) { + target.fillRect(_bounds, kColorWhite); + target.frameRect(_bounds, kColorBlack); + dialog->getFont().drawString(&target, _text, _bounds.left, _bounds.top, _bounds.width(), kColorBlack); +} + +void Dialog::calculateBoundsFromPrebuilt(const PrebuiltDialogBounds &bounds) { + _bounds = Common::Rect( + bounds.left, + bounds.top, + bounds.right, + bounds.bottom); +} +} // End of namespace MacVenture diff --git a/engines/macventure/dialog.h b/engines/macventure/dialog.h new file mode 100644 index 0000000000..67331d7eb3 --- /dev/null +++ b/engines/macventure/dialog.h @@ -0,0 +1,123 @@ +/* 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 MACVENTURE_DIALOG_H +#define MACVENTURE_DIALOG_H + +#include "graphics/macgui/macwindowmanager.h" + +#include "macventure/macventure.h" +#include "macventure/prebuilt_dialogs.h" + +namespace MacVenture { + +using namespace Graphics::MacGUIConstants; +class Gui; +class DialogElement; + +class Dialog { +public: + Dialog(Gui *gui, Common::Point pos, uint width, uint height); + Dialog(Gui *gui, PrebuiltDialogs prebuilt); + + ~Dialog(); + + bool processEvent(Common::Event event); + void draw(); + void localize(Common::Point &point); + void handleDialogAction(DialogElement *trigger, DialogAction action); + + const Graphics::Font &getFont(); + + void addButton(Common::String title, DialogAction action, Common::Point position, uint width = 0, uint height = 0); + void addText(Common::String content, Common::Point position); + void addTextInput(Common::Point position, int width, int height); + + void setUserInput(Common::String content); + +private: + void addPrebuiltElement(const PrebuiltDialogElement &element); + + void calculateBoundsFromPrebuilt(const PrebuiltDialogBounds &bounds); + +private: + Gui *_gui; + + Common::String _userInput; + Common::Array<DialogElement*> _elements; + Common::Rect _bounds; +}; + +class DialogElement { +public: + DialogElement(Dialog *dialog, Common::String title, DialogAction action, Common::Point position, uint width = 0, uint height = 0); + virtual ~DialogElement() {} + + bool processEvent(Dialog *dialog, Common::Event event); + void draw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target); + const Common::String &getText(); + +private: + virtual bool doProcessEvent(Dialog *dialog, Common::Event event) = 0; + virtual void doDraw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target) = 0; + virtual const Common::String &doGetText(); + +protected: + Common::String _text; + Common::Rect _bounds; + DialogAction _action; +}; + +// Dialog elements +class DialogButton : public DialogElement { +public: + DialogButton(Dialog *dialog, Common::String title, DialogAction action, Common::Point position, uint width = 0, uint height = 0); + ~DialogButton() {} + +private: + bool doProcessEvent(Dialog *dialog, Common::Event event); + void doDraw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target); +}; + +class DialogPlainText : public DialogElement { +public: + DialogPlainText(Dialog *dialog, Common::String content, Common::Point position); + ~DialogPlainText(); + +private: + bool doProcessEvent(Dialog *dialog, Common::Event event); + void doDraw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target); +}; + +class DialogTextInput : public DialogElement { +public: + DialogTextInput(Dialog *dialog, Common::Point position, uint width, uint height); + ~DialogTextInput(); + +private: + bool doProcessEvent(Dialog *dialog, Common::Event event); + void doDraw(MacVenture::Dialog *dialog, Graphics::ManagedSurface &target); +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/gui.cpp b/engines/macventure/gui.cpp new file mode 100644 index 0000000000..dff41b3c23 --- /dev/null +++ b/engines/macventure/gui.cpp @@ -0,0 +1,1464 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/file.h" +#include "common/system.h" +#include "common/debug-channels.h" +#include "common/debug.h" +#include "image/bmp.h" + +#include "macventure/gui.h" +#include "macventure/dialog.h" + +namespace MacVenture { + +enum MenuAction; + +enum { + kCursorWidth = 2, + kCursorHeight = 2 +}; + +enum { + kExitButtonWidth = 10, + kExitButtonHeight = 10 +}; + +enum { + kMenuHighLevel = -1, + kMenuAbout = 0, + kMenuFile = 1, + kMenuEdit = 2, + kMenuSpecial = 3 +}; + +enum { + kCommandNum = 8 +}; + +enum { + kDragThreshold = 5 +}; + +const bool kLoadStaticMenus = true; + +static const Graphics::MenuData menuSubItems[] = { + { kMenuHighLevel, "File", 0, 0, false }, + { kMenuHighLevel, "Edit", 0, 0, false }, + { kMenuHighLevel, "Special", 0, 0, false }, + { kMenuHighLevel, "Font", 0, 0, false }, + { kMenuHighLevel, "FontSize", 0, 0, false }, + + //{ kMenuAbout, "About", kMenuActionAbout, 0, true}, + + { kMenuFile, "New", kMenuActionNew, 0, true }, + { kMenuFile, NULL, 0, 0, false }, + { kMenuFile, "Open...", kMenuActionOpen, 0, true }, + { kMenuFile, "Save", kMenuActionSave, 0, true }, + { kMenuFile, "Save as...", kMenuActionSaveAs, 0, true }, + { kMenuFile, NULL, 0, 0, false }, + { kMenuFile, "Quit", kMenuActionQuit, 0, true }, + + { kMenuEdit, "Undo", kMenuActionUndo, 'Z', false }, + { kMenuEdit, NULL, 0, 0, false }, + { kMenuEdit, "Cut", kMenuActionCut, 'K', false }, + { kMenuEdit, "Copy", kMenuActionCopy, 'C', false }, + { kMenuEdit, "Paste", kMenuActionPaste, 'V', false }, + { kMenuEdit, "Clear", kMenuActionClear, 'B', false }, + + { kMenuSpecial, "Clean Up", kMenuActionCleanUp, 0, false }, + { kMenuSpecial, "Mess Up", kMenuActionMessUp, 0, false }, + + { 0, NULL, 0, 0, false } +}; + + +bool commandsWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); +bool mainGameWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); +bool outConsoleWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); +bool selfWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); +bool exitsWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); +bool diplomaWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); +bool inventoryWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); + +void menuCommandsCallback(int action, Common::String &text, void *data); + +Gui::Gui(MacVentureEngine *engine, Common::MacResManager *resman) { + _engine = engine; + _resourceManager = resman; + _windowData = NULL; + _controlData = NULL; + _draggedObj.id = 0; + _draggedObj.pos = Common::Point(0, 0); + _dialog = NULL; + + _cursor = new Cursor(this); + + _consoleText = new ConsoleText(this); + _graphics = NULL; + + initGUI(); +} + +Gui::~Gui() { + + if (_windowData) + delete _windowData; + + if (_controlData) + delete _controlData; + + if (_exitsData) + delete _exitsData; + + if (_cursor) + delete _cursor; + + if (_consoleText) + delete _consoleText; + + if (_dialog) + delete _dialog; + + clearAssets(); + + if (_graphics) + delete _graphics; +} + +void Gui::initGUI() { + _screen.create(kScreenWidth, kScreenHeight, Graphics::PixelFormat::createFormatCLUT8()); + _wm.setScreen(&_screen); + + // Menu + _menu = _wm.addMenu(); + if (!loadMenus()) + error("GUI: Could not load menus"); + _menu->setCommandsCallback(menuCommandsCallback, this); + _menu->calcDimensions(); + + loadGraphics(); + + if (!loadWindows()) + error("GUI: Could not load windows"); + + initWindows(); + + assignObjReferences(); + + if (!loadControls()) + error("GUI: Could not load controls"); + + draw(); + +} + +void Gui::reloadInternals() { + clearAssets(); + loadGraphics(); +} + +void Gui::draw() { + // Will be performance-improved after the milestone + _wm.setFullRefresh(true); + + drawWindows(); + + _wm.draw(); + + drawDraggedObject(); + drawDialog(); + // TODO: When window titles with custom borders are in MacGui, this should be used. + //drawWindowTitle(kMainGameWindow, _mainGameWindow->getSurface()); +} + +void Gui::drawMenu() { + _menu->draw(&_screen); +} + +void Gui::drawTitle() { + warning("drawTitle hasn't been tested yet"); +} + +void Gui::clearControls() { + if (!_controlData) + return; + + Common::Array<CommandButton>::iterator it = _controlData->begin(); + for (; it != _controlData->end(); ++it) { + it->unselect(); + } +} + +void Gui::initWindows() { + // Game Controls Window + _controlsWindow = _wm.addWindow(false, false, false); + _controlsWindow->setDimensions(getWindowData(kCommandsWindow).bounds); + _controlsWindow->setActive(false); + _controlsWindow->setCallback(commandsWindowCallback, this); + loadBorders(_controlsWindow, findWindowData(kCommandsWindow).type); + + // Main Game Window + _mainGameWindow = _wm.addWindow(false, false, false); + _mainGameWindow->setDimensions(getWindowData(kMainGameWindow).bounds); + _mainGameWindow->setActive(false); + _mainGameWindow->setCallback(mainGameWindowCallback, this); + loadBorders(_mainGameWindow, findWindowData(kMainGameWindow).type); + + // In-game Output Console + _outConsoleWindow = _wm.addWindow(true, true, false); + // HACK We have to hand-create the dimensions, otherwise they don't fit + const WindowData &wd = getWindowData(kOutConsoleWindow); + Common::Rect dimensions = wd.bounds; + dimensions.setWidth(dimensions.width() - borderBounds(wd.type).rightOffset); + _outConsoleWindow->setDimensions(dimensions); + _outConsoleWindow->setActive(false); + _outConsoleWindow->setCallback(outConsoleWindowCallback, this); + loadBorders(_outConsoleWindow, findWindowData(kOutConsoleWindow).type); + + // Self Window + _selfWindow = _wm.addWindow(false, true, false); + _selfWindow->setDimensions(getWindowData(kSelfWindow).bounds); + _selfWindow->setActive(false); + _selfWindow->setCallback(selfWindowCallback, this); + loadBorders(_selfWindow, findWindowData(kSelfWindow).type); + + // Exits Window + _exitsWindow = _wm.addWindow(false, false, false); + _exitsWindow->setDimensions(getWindowData(kExitsWindow).bounds); + _exitsWindow->setActive(false); + _exitsWindow->setCallback(exitsWindowCallback, this); + loadBorders(_exitsWindow, findWindowData(kExitsWindow).type); +} + +const WindowData &Gui::getWindowData(WindowReference reference) { + return findWindowData(reference); +} + +const Graphics::Font &Gui::getCurrentFont() { + return *_wm.getFont("Chicago-12", Graphics::FontManager::kBigGUIFont); +} + +void Gui::bringToFront(WindowReference winID) { + findWindow(winID)->setActive(true); +} + +void Gui::setWindowTitle(WindowReference winID, Common::String string) { + findWindowData(winID).title = string; + findWindowData(winID).titleLength = string.size(); +} + +void Gui::updateWindowInfo(WindowReference ref, ObjID objID, const Common::Array<ObjID> &children) { + if (ref == kNoWindow) { + return; + } + WindowData &data = findWindowData(ref); + data.children.clear(); + data.objRef = objID; + uint32 originx = 0x7fff; + uint32 originy = 0x7fff; + for (uint i = 0; i < children.size(); i++) { + if (children[i] != 1) { + ObjID child = children[i]; + if (ref != kMainGameWindow) { + Common::Point childPos = _engine->getObjPosition(child); + originx = originx > (uint)childPos.x ? (uint)childPos.x : originx; + originy = originy > (uint)childPos.y ? (uint)childPos.y : originy; + } + data.children.push_back(DrawableObject(child, kBlitBIC)); + } + } + if (originx != 0x7fff) { + data.bounds.left = originx; + } + if (originy != 0x7fff) { + data.bounds.top = originy; + } + if (ref != kMainGameWindow) { + data.updateScroll = true; + } +} + +void Gui::addChild(WindowReference target, ObjID child) { + findWindowData(target).children.push_back(DrawableObject(child, kBlitBIC)); +} + +void Gui::removeChild(WindowReference target, ObjID child) { + WindowData &data = findWindowData(target); + uint index = 0; + for (;index < data.children.size(); index++) { + if (data.children[index].obj == child) { + break; + } + } + + if (index < data.children.size()) + data.children.remove_at(index); +} + +void Gui::assignObjReferences() { + findWindowData(kSelfWindow).objRef = 0; +} + +WindowReference Gui::createInventoryWindow(ObjID objRef) { + Graphics::MacWindow *newWindow = _wm.addWindow(true, true, true); + WindowData newData; + GlobalSettings settings = _engine->getGlobalSettings(); + newData.refcon = (WindowReference)ABS(_inventoryWindows.size() + kInventoryStart); // This is a HACK + + if (_windowData->back().refcon < 0x80) { // There is already another inventory window + newData.bounds = _windowData->back().bounds; // Inventory windows are always last + newData.bounds.translate(newData.bounds.left + settings._invOffsetX, newData.bounds.top + settings._invOffsetY); + } else { + BorderBounds bbs = borderBounds(kInvWindow); + newData.bounds = Common::Rect( + settings._invLeft - bbs.leftOffset, + settings._invTop - bbs.topOffset, + settings._invLeft + settings._invWidth, + settings._invTop + settings._invHeight); + } + newData.type = kInvWindow; + newData.hasCloseBox = true; + newData.visible = true; + newData.objRef = objRef; + _windowData->push_back(newData); + + newWindow->setDimensions(newData.bounds); + newWindow->setCallback(inventoryWindowCallback, this); + newWindow->setCloseable(true); + loadBorders(newWindow, newData.type); + _inventoryWindows.push_back(newWindow); + + debugC(1, kMVDebugGUI, "Create new inventory window. Reference: %d", newData.refcon); + return newData.refcon; +} + +void Gui::loadBorders(Graphics::MacWindow *target, MVWindowType type) { + loadBorder(target, type, false); + loadBorder(target, type, true); +} + +void Gui::loadBorder(Graphics::MacWindow *target, MVWindowType type, bool active) { + + Common::SeekableReadStream *stream = _engine->getBorderFile(type, active); + + if (stream) { + BorderBounds bbs = borderBounds(type); + target->loadBorder(*stream, active, bbs.leftOffset, bbs.rightOffset, bbs.topOffset, bbs.bottomOffset); + + delete stream; + } +} + +void Gui::loadGraphics() { + if (_graphics) + delete _graphics; + _graphics = new Container(_engine->getFilePath(kGraphicPathID)); +} + +void Gui::clearAssets() { + Common::HashMap<ObjID, ImageAsset*>::const_iterator it = _assets.begin(); + for (; it != _assets.end(); it++) { + delete it->_value; + } + _assets.clear(); +} + +bool Gui::loadMenus() { + + if (kLoadStaticMenus) { + // We assume that, if there are static menus, we don't need dynamic ones + _menu->addStaticMenus(menuSubItems); + return true; + } + + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + Common::MacResIDArray::const_iterator iter; + + if ((resArray = _resourceManager->getResIDArray(MKTAG('M', 'E', 'N', 'U'))).size() == 0) + return false; + + _menu->addMenuSubItem(0, "Abb", kMenuActionAbout, 0, 'A', true); + + int i = 1; + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = _resourceManager->getResource(MKTAG('M', 'E', 'N', 'U'), *iter); + uint16 key; + uint16 style; + uint8 titleLength; + char *title; + + /* Skip menuID, width, height, resourceID, placeholder */ + for (int skip = 0; skip < 5; skip++) { + res->readUint16BE(); + } + titleLength = res->readByte(); + title = new char[titleLength + 1]; + res->read(title, titleLength); + title[titleLength] = '\0'; + + if (titleLength > 1) { + _menu->addMenuItem(title); + + // Read submenu items + while ((titleLength = res->readByte())) { + title = new char[titleLength + 1]; + res->read(title, titleLength); + title[titleLength] = '\0'; + // Skip icon + res->readUint16BE(); + // Read key + key = res->readUint16BE(); + // Skip mark + res->readUint16BE(); + // Read style + style = res->readUint16BE(); + _menu->addMenuSubItem(i, title, 0, style, key, false); + } + } + + i++; + delete res; + } + + return true; +} + +bool Gui::loadWindows() { + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + Common::MacResIDArray::const_iterator iter; + + _windowData = new Common::List<WindowData>(); + + if ((resArray = _resourceManager->getResIDArray(MKTAG('W', 'I', 'N', 'D'))).size() == 0) + return false; + + uint32 id = kCommandsWindow; + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = _resourceManager->getResource(MKTAG('W', 'I', 'N', 'D'), *iter); + WindowData data; + uint16 top, left, bottom, right; + top = res->readUint16BE(); + left = res->readUint16BE(); + bottom = res->readUint16BE(); + right = res->readUint16BE(); + data.type = (MVWindowType)res->readUint16BE(); + BorderBounds bbs = borderBounds(data.type); + data.bounds = Common::Rect( + left - bbs.leftOffset, + top - bbs.topOffset, + right + bbs.rightOffset, + bottom + bbs.bottomOffset); + + data.visible = res->readUint16BE(); + data.hasCloseBox = res->readUint16BE(); + data.refcon = (WindowReference)id; id++; + res->readUint32BE(); // Skip the true id. For some reason it's reading 0 + data.titleLength = res->readByte(); + if (data.titleLength) { + char *newTitle = new char[data.titleLength + 1]; + res->read(newTitle, data.titleLength); + newTitle[data.titleLength] = '\0'; + data.title = Common::String(newTitle); + delete[] newTitle; + } + data.scrollPos = Common::Point(0, 0); + + debugC(1, kMVDebugGUI, "Window loaded: %s", data.title.c_str()); + + _windowData->push_back(data); + + delete res; + } + + return true; +} + +bool Gui::loadControls() { + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + Common::MacResIDArray::const_iterator iter; + + _controlData = new Common::Array<CommandButton>(); + _exitsData = new Common::Array<CommandButton>(); + + if ((resArray = _resourceManager->getResIDArray(MKTAG('C', 'N', 'T', 'L'))).size() == 0) + return false; + + uint32 id = kControlExitBox; + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = _resourceManager->getResource(MKTAG('C', 'N', 'T', 'L'), *iter); + ControlData data; + uint16 top, left, bottom, right; + top = res->readUint16BE(); + left = res->readUint16BE(); + bottom = res->readUint16BE(); + right = res->readUint16BE(); + data.scrollValue = res->readUint16BE(); + data.visible = res->readByte(); + res->readByte(); // Unused + data.scrollMax = res->readUint16BE(); + data.scrollMin = res->readUint16BE(); + data.cdef = res->readUint16BE(); + data.refcon = (ControlAction)res->readUint32BE(); + data.type = (ControlType)id; id++; + data.titleLength = res->readByte(); + if (data.titleLength) { + char *title = new char[data.titleLength + 1]; + res->read(title, data.titleLength); + title[data.titleLength] = '\0'; + data.title = Common::String(title); + delete[] title; + } + if (data.type != kControlExitBox) { + BorderBounds bbs = borderBounds(getWindowData(kCommandsWindow).type); + // We just want to move the button, not change it's size + data.bounds = Common::Rect(left + bbs.leftOffset, top + bbs.topOffset, right + bbs.leftOffset, bottom + bbs.topOffset); + } else { + data.bounds = Common::Rect(left, top, right, bottom); + } + + + _controlData->push_back(CommandButton(data, this)); + + delete res; + } + + return true; +} + +void Gui::drawWindows() { + + drawCommandsWindow(); + drawMainGameWindow(); + drawSelfWindow(); + drawInventories(); + drawExitsWindow(); + drawConsoleWindow(); + +} + +void Gui::drawCommandsWindow() { + if (_engine->needsClickToContinue()) { + Graphics::ManagedSurface *srf = _controlsWindow->getSurface(); + WindowData data = getWindowData(kCommandsWindow); + srf->fillRect(Common::Rect(0, 0, srf->w, srf->h), kColorWhite); + getCurrentFont().drawString( + srf, + _engine->getCommandsPausedString(), + 0, + (srf->h / 2) - getCurrentFont().getFontHeight(), + data.bounds.right - data.bounds.left, + kColorBlack, + Graphics::kTextAlignCenter); + } else { + Common::Array<CommandButton>::const_iterator it = _controlData->begin(); + for (; it != _controlData->end(); ++it) { + CommandButton button = *it; + if (button.getData().type != kControlExitBox) + button.draw(*_controlsWindow->getSurface()); + } + } +} + +void Gui::drawMainGameWindow() { + const WindowData &data = getWindowData(kMainGameWindow); + BorderBounds border = borderBounds(data.type); + ObjID objRef = data.objRef; + + _mainGameWindow->setDirty(true); + + if (data.objRef > 0 && data.objRef < 2000) { + ensureAssetLoaded(objRef); + + _assets[objRef]->blitInto( + _mainGameWindow->getSurface(), + border.leftOffset, + border.topOffset, + kBlitDirect); + } + + drawObjectsInWindow(data, _mainGameWindow->getSurface()); + + if (DebugMan.isDebugChannelEnabled(kMVDebugGUI)) { + Graphics::MacWindow *win = findWindow(data.refcon); + Common::Rect innerDims = win->getInnerDimensions(); + int x = win->getDimensions().left; + int y = win->getDimensions().top; + innerDims.translate(-x, -y); + win->getSurface()->frameRect(innerDims, kColorGreen); + } + + findWindow(kMainGameWindow)->setDirty(true); +} + +void Gui::drawSelfWindow() { + drawObjectsInWindow(getWindowData(kSelfWindow), _selfWindow->getSurface()); + if (_engine->isObjSelected(1)) { + invertWindowColors(kSelfWindow); + } + findWindow(kSelfWindow)->setDirty(true); +} + +void Gui::drawInventories() { + + Graphics::ManagedSurface *srf; + for (uint i = 0; i < _inventoryWindows.size(); i++) { + const WindowData &data = getWindowData((WindowReference)(kInventoryStart + i)); + Graphics::MacWindow *win = findWindow(data.refcon); + srf = win->getSurface(); + srf->clear(kColorGreen); + srf->fillRect(srf->getBounds(), kColorWhite); + drawObjectsInWindow(data, srf); + + if (DebugMan.isDebugChannelEnabled(kMVDebugGUI)) { + Common::Rect innerDims = win->getInnerDimensions(); + int x = win->getDimensions().left; + int y = win->getDimensions().top; + innerDims.translate(-x, -y); + srf->frameRect(innerDims, kColorGreen); + } + + findWindow(data.refcon)->setDirty(true); + } + +} + +void Gui::drawExitsWindow() { + + Graphics::ManagedSurface *srf = _exitsWindow->getSurface(); + BorderBounds border = borderBounds(getWindowData(kExitsWindow).type); + + srf->fillRect(Common::Rect( + border.leftOffset, + border.topOffset, + srf->w + border.rightOffset, + srf->h + border.bottomOffset), kColorWhite); + + Common::Array<CommandButton>::const_iterator it = _exitsData->begin(); + for (; it != _exitsData->end(); ++it) { + CommandButton button = *it; + button.draw(*_exitsWindow->getSurface()); + } + + findWindow(kExitsWindow)->setDirty(true); +} + +void Gui::drawConsoleWindow() { + + Graphics::ManagedSurface *srf = _outConsoleWindow->getSurface(); + BorderBounds bounds = borderBounds(getWindowData(kOutConsoleWindow).type); + _consoleText->renderInto(srf, bounds, kConsoleLeftOffset); +} + +void Gui::drawObjectsInWindow(const WindowData &targetData, Graphics::ManagedSurface *surface) { + BorderBounds border = borderBounds(targetData.type); + Common::Point pos; + ObjID child; + BlitMode mode; + + if (targetData.children.size() == 0) { + return; + } + + Graphics::ManagedSurface composeSurface; + createInnerSurface(&composeSurface, surface, border); + assert(composeSurface.w <= surface->w && + composeSurface.h <= surface->h); + composeSurface.clear(kColorGreen); + + for (uint i = 0; i < targetData.children.size(); i++) { + child = targetData.children[i].obj; + mode = (BlitMode)targetData.children[i].mode; + pos = _engine->getObjPosition(child); + pos -= targetData.scrollPos; + ensureAssetLoaded(child); + + _assets[child]->blitInto( + &composeSurface, + pos.x, + pos.y, + mode); + + if (_engine->isObjVisible(child)) { + if (_engine->isObjSelected(child) || + child == _draggedObj.id) { + + _assets[child]->blitInto( + &composeSurface, pos.x, pos.y, kBlitOR); + } + } + + if (DebugMan.isDebugChannelEnabled(kMVDebugGUI)) { + Common::Rect testBounds = _engine->getObjBounds(child); + testBounds.translate(-targetData.scrollPos.x, -targetData.scrollPos.y); + surface->frameRect(testBounds, kColorGreen); + } + } + Common::Point composePosition = Common::Point(border.leftOffset, border.topOffset); + surface->transBlitFrom(composeSurface, composePosition, kColorGreen); +} + +void Gui::drawWindowTitle(WindowReference target, Graphics::ManagedSurface *surface) { + // TODO: Implement when MacGui supports titles in windows with custom borders. +} + +void Gui::drawDraggedObject() { + if (_draggedObj.id != 0 && + _engine->isObjVisible(_draggedObj.id)) { + ensureAssetLoaded(_draggedObj.id); + ImageAsset *asset = _assets[_draggedObj.id]; + + // In case of overflow from the right/top + uint w = asset->getWidth() + MIN((int16)0, _draggedObj.pos.x); + uint h = asset->getHeight() + MIN((int16)0, _draggedObj.pos.y); + + // In case of overflow from the bottom/left + if (_draggedObj.pos.x > 0 && _draggedObj.pos.x + w > kScreenWidth) { + w = kScreenWidth - _draggedObj.pos.x; + } + if (_draggedObj.pos.y > 0 && _draggedObj.pos.y + h > kScreenHeight) { + h = kScreenHeight - _draggedObj.pos.y; + } + + Common::Point target = _draggedObj.pos; + if (target.x < 0) { + target.x = 0; + } + if (target.y < 0) { + target.y = 0; + } + + _draggedSurface.create(w, h, _screen.format); + _draggedSurface.blitFrom( + _screen, + Common::Rect( + target.x, + target.y, + target.x + _draggedSurface.w, + target.y + _draggedSurface.h), + Common::Point(0, 0)); + asset->blitInto(&_draggedSurface, MIN((int16)0, _draggedObj.pos.x), MIN((int16)0, _draggedObj.pos.y), kBlitBIC); + + g_system->copyRectToScreen( + _draggedSurface.getBasePtr(0, 0), + _draggedSurface.pitch, + target.x, + target.y, + _draggedSurface.w, + _draggedSurface.h + ); + } +} + +void Gui::drawDialog() { + if (_dialog) { + _dialog->draw(); + } +} + +void Gui::updateWindow(WindowReference winID, bool containerOpen) { + if (winID == kNoWindow) { + return; + } + if (winID == kSelfWindow || containerOpen) { + WindowData &data = findWindowData(winID); + if (winID == kCommandsWindow) { + Common::Array<CommandButton>::iterator it = _controlData->begin(); + for (; it != _controlData->end(); ++it) { + it->unselect(); + } + } + Common::Array<DrawableObject> &children = data.children; + for (uint i = 0; i < children.size(); i++) { + uint flag = 0; + ObjID child = children[i].obj; + BlitMode mode = kBlitDirect; + bool off = !_engine->isObjVisible(child); + if (flag || !off || !_engine->isObjClickable(child)) { + mode = kBlitBIC; + if (off || flag) { + mode = kBlitXOR; + } else if (!off && _engine->isObjSelected(child)) { + mode = kBlitOR; + } + children[i] = DrawableObject(child, mode); + } else { + children[i] = DrawableObject(child, kBlitXOR); + } + } + if (winID == kMainGameWindow) { + drawMainGameWindow(); + } else { + Graphics::MacWindow *winRef = findWindow(winID); + winRef->getSurface()->fillRect(data.bounds, kColorGray); + } + if (data.type == kZoomDoc && data.updateScroll) { + warning("Unimplemented: update scroll"); + } + } +} + +void Gui::clearExits() { + _exitsData->clear(); +} + +void Gui::unselectExits() { + Common::Array<CommandButton>::const_iterator it = _exitsData->begin(); + for (; it != _exitsData->end(); ++it) { + CommandButton button = *it; + button.unselect(); + } +} + +void Gui::updateExit(ObjID obj) { + if (!_engine->isObjExit(obj)) { + return; + } + BorderBounds border = borderBounds(getWindowData(kExitsWindow).type); + + int ctl = -1; + int i = 0; + Common::Array<CommandButton>::const_iterator it = _exitsData->begin(); + for (;it != _exitsData->end(); it++) { + if (it->getData().refcon == obj) + ctl = i; + else + i++; + } + + if (ctl != -1) + _exitsData->remove_at(ctl); + + if (!_engine->isHiddenExit(obj) && + _engine->getParent(obj) == _engine->getParent(1)) { + ControlData data; + data.titleLength = 0; + data.refcon = (ControlAction)obj; // Objects can be exits (actions) + Common::Point pos = _engine->getObjExitPosition(obj); + pos.x += border.leftOffset; + pos.y += border.topOffset; + data.bounds = Common::Rect(pos.x, pos.y, pos.x + kExitButtonWidth, pos.y + kExitButtonHeight); + data.visible = true; + + _exitsData->push_back(CommandButton(data, this)); + } +} + +void Gui::printText(const Common::String &text) { + debugC(1, kMVDebugGUI, "Print Text: %s", text.c_str()); + _consoleText->printLine(text, _outConsoleWindow->getDimensions().width()); +} + +void Gui::showPrebuiltDialog(PrebuiltDialogs type) { + closeDialog(); + _dialog = new Dialog(this, type); +} + +bool Gui::isDialogOpen() { + return _dialog != NULL; +} + +void Gui::setTextInput(Common::String str) { + _engine->setTextInput(str); +} + + +void Gui::closeDialog() { + delete _dialog; + _dialog = NULL; +} + +void Gui::getTextFromUser() { + if (_dialog) { + delete _dialog; + } + showPrebuiltDialog(kSpeakDialog); +} + +void Gui::loadGame() { + _engine->scummVMSaveLoadDialog(false); +} + +void Gui::saveGame() { + _engine->scummVMSaveLoadDialog(true); +} + +void Gui::newGame() { + _engine->newGame(); +} + +void Gui::quitGame() { + _engine->requestQuit(); +} + +void Gui::createInnerSurface(Graphics::ManagedSurface *innerSurface, Graphics::ManagedSurface *outerSurface, const BorderBounds &borders) { + innerSurface->create( + outerSurface->w - borders.leftOffset - borders.rightOffset, + outerSurface->h - borders.topOffset - borders.bottomOffset, + outerSurface->format); +} + +void Gui::moveDraggedObject(Common::Point target) { + ensureAssetLoaded(_draggedObj.id); + _draggedObj.pos = target + _draggedObj.mouseOffset; + + // TODO FInd more elegant way of making pow2 + _draggedObj.hasMoved = (_draggedObj.startPos.sqrDist(_draggedObj.pos) >= (kDragThreshold * kDragThreshold)); + + debugC(4, kMVDebugGUI, "Dragged obj position: (%d, %d), mouse offset: (%d, %d), hasMoved: %d, dist: %d, threshold: %d", + _draggedObj.pos.x, _draggedObj.pos.y, + _draggedObj.mouseOffset.x, _draggedObj.mouseOffset.y, + _draggedObj.hasMoved, + _draggedObj.startPos.sqrDist(_draggedObj.pos), + kDragThreshold * kDragThreshold + ); + +} + +WindowReference Gui::findWindowAtPoint(Common::Point point) { + Common::List<WindowData>::iterator it; + Graphics::MacWindow *win; + for (it = _windowData->begin(); it != _windowData->end(); it++) { + win = findWindow(it->refcon); + if (win && it->refcon != kDiplomaWindow) { //HACK, diploma should be cosnidered + if (win->getDimensions().contains(point)) { + return it->refcon; + } + } + } + return kNoWindow; +} + +Common::Point Gui::getGlobalScrolledSurfacePosition(WindowReference reference) { + const WindowData &data = getWindowData(reference); + BorderBounds border = borderBounds(data.type); + Graphics::MacWindow *win = findWindow(reference); + if (!win) { + return Common::Point(0, 0); + } + return Common::Point( + win->getDimensions().left + border.leftOffset - data.scrollPos.x, + win->getDimensions().top + border.topOffset - data.scrollPos.y); +} + +WindowData &Gui::findWindowData(WindowReference reference) { + assert(_windowData); + + Common::List<WindowData>::iterator iter = _windowData->begin(); + while (iter->refcon != reference && iter != _windowData->end()) { + iter++; + } + + if (iter->refcon == reference) + return *iter; + + error("GUI: Could not locate the desired window data"); +} + +Graphics::MacWindow *Gui::findWindow(WindowReference reference) { + if (reference < 0x80 && reference >= kInventoryStart) { // It's an inventory window + return _inventoryWindows[reference - kInventoryStart]; + } + switch (reference) { + case MacVenture::kNoWindow: + return NULL; + case MacVenture::kCommandsWindow: + return _controlsWindow; + case MacVenture::kMainGameWindow: + return _mainGameWindow; + case MacVenture::kOutConsoleWindow: + return _outConsoleWindow; + case MacVenture::kSelfWindow: + return _selfWindow; + case MacVenture::kExitsWindow: + return _exitsWindow; + case MacVenture::kDiplomaWindow: + return _diplomaWindow; + default: + return NULL; + } + return NULL; +} + +void Gui::ensureInventoryOpen(WindowReference reference, ObjID id) { + assert(reference < 0x80 && reference >= kInventoryStart); + if (reference - kInventoryStart == (int)_inventoryWindows.size()) { + createInventoryWindow(id); + } +} + +WindowReference Gui::getObjWindow(ObjID objID) { + switch (objID) { + case 0xfffc: return kExitsWindow; + case 0xfffd: return kSelfWindow; + case 0xfffe: return kOutConsoleWindow; + case 0xffff: return kCommandsWindow; + } + + return findObjWindow(objID); +} + +WindowReference Gui::findObjWindow(ObjID objID) { + // This is a bit of a HACK, we take advantage of the consecutive nature of references + for (uint i = kCommandsWindow; i <= kDiplomaWindow; i++) { + const WindowData &data = getWindowData((WindowReference)i); + if (data.objRef == objID) { + return data.refcon; + } + } + + for (uint i = kInventoryStart; i < _inventoryWindows.size() + kInventoryStart; i++) { + const WindowData &data = getWindowData((WindowReference)i); + if (data.objRef == objID) { + return data.refcon; + } + } + + return kNoWindow; +} + +void Gui::checkSelect(const WindowData &data, Common::Point pos, const Common::Rect &clickRect, WindowReference ref) { + ObjID child = 0; + for (Common::Array<DrawableObject>::const_iterator it = data.children.begin(); it != data.children.end(); it++) { + if (canBeSelected((*it).obj, clickRect, ref)) { + child = (*it).obj; + } + } + if (child != 0) { + selectDraggable(child, ref, pos); + bringToFront(ref); + } +} + +bool Gui::canBeSelected(ObjID obj, const Common::Rect &clickRect, WindowReference ref) { + return (_engine->isObjClickable(obj) && + isRectInsideObject(clickRect, obj)); +} + +bool Gui::isRectInsideObject(Common::Rect target, ObjID obj) { + ensureAssetLoaded(obj); + Common::Rect bounds = _engine->getObjBounds(obj); + Common::Rect intersection = bounds.findIntersectingRect(target); + // We translate it to the image's coord system + intersection = Common::Rect( + intersection.left - bounds.left, + intersection.top - bounds.top, + intersection.left - bounds.left + intersection.width(), + intersection.top - bounds.top + intersection.height()); + + return _assets[obj]->isRectInside(intersection); +} + +void Gui::selectDraggable(ObjID child, WindowReference origin, Common::Point click) { + if (_engine->isObjClickable(child) && _draggedObj.id == 0) { + _draggedObj.hasMoved = false; + _draggedObj.id = child; + _draggedObj.startWin = origin; + Common::Point localizedClick = click - getGlobalScrolledSurfacePosition(origin); + _draggedObj.mouseOffset = _engine->getObjPosition(child) - localizedClick; + _draggedObj.pos = click + _draggedObj.mouseOffset; + _draggedObj.startPos = _draggedObj.pos; + } +} + +void Gui::handleDragRelease(bool shiftPressed, bool isDoubleClick) { + if (_draggedObj.id != 0) { + WindowReference destinationWindow = findWindowAtPoint(_draggedObj.pos); + if (destinationWindow == kNoWindow) { + return; + } + if (_draggedObj.hasMoved) { + const WindowData &destinationWindowData = getWindowData(destinationWindow); + ObjID destObject = destinationWindowData.objRef; + Common::Point dropPosition = _draggedObj.pos - _draggedObj.startPos; + dropPosition = localizeTravelledDistance(dropPosition, _draggedObj.startWin, destinationWindow); + debugC(3, kMVDebugGUI, "Drop the object %d at obj %d, pos (%d, %d)", _draggedObj.id, destObject, dropPosition.x, dropPosition.y); + + _engine->handleObjectDrop(_draggedObj.id, dropPosition, destObject); + } + _engine->handleObjectSelect(_draggedObj.id, destinationWindow, shiftPressed, isDoubleClick); + _draggedObj.id = 0; + _draggedObj.hasMoved = false; + } +} + +Common::Rect Gui::calculateClickRect(Common::Point clickPos, Common::Rect windowBounds) { + int left = clickPos.x - windowBounds.left; + int top = clickPos.y - windowBounds.top; + return Common::Rect(left - kCursorWidth, top - kCursorHeight, left + kCursorWidth, top + kCursorHeight); +} + +Common::Point Gui::localizeTravelledDistance(Common::Point point, WindowReference origin, WindowReference target) { + if (origin != target) { + // ori.local to global + point += getGlobalScrolledSurfacePosition(origin); + if (findWindow(target)) { + // dest.globalToLocal + point -= getGlobalScrolledSurfacePosition(target); + } + } + return point; +} + +void Gui::removeInventoryWindow(WindowReference ref) { + _inventoryWindows.remove_at(ref - kInventoryStart); + bool found = false; + Common::List<WindowData>::iterator it; + for (it = _windowData->begin(); it != _windowData->end() && !found; it++) { + if (it->refcon == ref) { + _windowData->erase(it); + found = true; + } + } +} + + +/* HANDLERS */ +void Gui::handleMenuAction(MenuAction action) { + switch (action) { + case MacVenture::kMenuActionAbout: + warning("Unimplemented MacVenture Menu Action: About"); + break; + case MacVenture::kMenuActionNew: + _engine->newGame(); + break; + case MacVenture::kMenuActionOpen: + loadGame(); + break; + case MacVenture::kMenuActionSave: + saveGame(); + break; + case MacVenture::kMenuActionSaveAs: + saveGame(); + break; + case MacVenture::kMenuActionQuit: + _engine->requestQuit(); + break; + case MacVenture::kMenuActionUndo: + warning("Unimplemented MacVenture Menu Action: Undo"); + break; + case MacVenture::kMenuActionCut: + warning("Unimplemented MacVenture Menu Action: Cut"); + break; + case MacVenture::kMenuActionCopy: + warning("Unimplemented MacVenture Menu Action: Copy"); + break; + case MacVenture::kMenuActionPaste: + warning("Unimplemented MacVenture Menu Action: Paste"); + break; + case MacVenture::kMenuActionClear: + warning("Unimplemented MacVenture Menu Action: Clear"); + break; + case MacVenture::kMenuActionCleanUp: + warning("Unimplemented MacVenture Menu Action: Clean Up"); + break; + case MacVenture::kMenuActionMessUp: + warning("Unimplemented MacVenture Menu Action: Mess Up"); + break; + case MacVenture::kMenuActionCommand: + warning("Unimplemented MacVenture Menu Action: GENERIC"); + break; + default: + break; + } +} + +/* CALLBACKS */ + +bool commandsWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { + Gui *g = (Gui*)gui; + return g->processCommandEvents(click, event); +} + +bool mainGameWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { + Gui *g = (Gui*)gui; + return g->processMainGameEvents(click, event); +} + +bool outConsoleWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { + Gui *g = (Gui*)gui; + return g->processOutConsoleEvents(click, event); +} + +bool selfWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { + Gui *g = (Gui*)gui; + + return g->processSelfEvents(click, event); +} + +bool exitsWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { + Gui *g = (Gui*)gui; + + return g->processExitsEvents(click, event); +} + +bool diplomaWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { + Gui *g = (Gui*)gui; + + return g->processDiplomaEvents(click, event); +} + +bool inventoryWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { + Gui *g = (Gui*)gui; + + return g->processInventoryEvents(click, event); +} + +void menuCommandsCallback(int action, Common::String &text, void *data) { + Gui *g = (Gui *)data; + + g->handleMenuAction((MenuAction)action); +} + + +void Gui::invertWindowColors(WindowReference winID) { + Graphics::ManagedSurface *srf = findWindow(winID)->getSurface(); + for (uint y = 0; y < srf->h; y++) { + for (uint x = 0; x < srf->w; x++) { + byte p = *(byte *)srf->getBasePtr(x, y); + *(byte *)srf->getBasePtr(x, y) = + (p == kColorWhite) ? kColorBlack : kColorGray; + } + } +} + +bool Gui::tryCloseWindow(WindowReference winID) { + WindowData data = findWindowData(winID); + Graphics::MacWindow *win = findWindow(winID); + _wm.removeWindow(win); + if (winID < 0x80) { + removeInventoryWindow(winID); + } + return true; +} + +Common::Point Gui::getObjMeasures(ObjID obj) { + ensureAssetLoaded(obj); + int w = _assets[obj]->getWidth(); + int h = _assets[obj]->getHeight(); + return Common::Point(w, h); +} + +bool Gui::processEvent(Common::Event &event) { + bool processed = false; + + processed |= _cursor->processEvent(event); + + if (_dialog && _dialog->processEvent(event)) { + return true; + } + + if (event.type == Common::EVENT_MOUSEMOVE) { + if (_draggedObj.id != 0) { + moveDraggedObject(event.mouse); + } + processed = true; + } + + processed |= _wm.processEvent(event); + return (processed); +} + +bool Gui::processCommandEvents(WindowClick click, Common::Event &event) { + if (event.type == Common::EVENT_LBUTTONUP) { + if (_engine->needsClickToContinue()) { + _engine->selectControl(kClickToContinue); + return true; + } + + Common::Point position( + event.mouse.x - _controlsWindow->getDimensions().left, + event.mouse.y - _controlsWindow->getDimensions().top); + + CommandButton data; + if (!_controlData) + return false; + + Common::Array<CommandButton>::iterator it = _controlData->begin(); + for (; it != _controlData->end(); ++it) { + if (it->isInsideBounds(position)) { + it->select(); + data = *it; + } else { + it->unselect(); + } + } + + _engine->selectControl(data.getData().refcon); + _engine->refreshReady(); + _engine->preparedToRun(); + } + return false; +} + +bool MacVenture::Gui::processMainGameEvents(WindowClick click, Common::Event &event) { + if (_engine->needsClickToContinue()) + return true; + + return false; +} + +bool MacVenture::Gui::processOutConsoleEvents(WindowClick click, Common::Event &event) { + if (_engine->needsClickToContinue()) + return true; + + if (click == kBorderScrollUp && event.type == Common::EVENT_LBUTTONDOWN) { + _consoleText->scrollUp(); + return true; + } + if (click == kBorderScrollDown && event.type == Common::EVENT_LBUTTONDOWN) { + _consoleText->scrollDown(); + return true; + } + + return getWindowData(kOutConsoleWindow).visible; +} + +bool MacVenture::Gui::processSelfEvents(WindowClick click, Common::Event &event) { + if (_engine->needsClickToContinue()) + return true; + + if (event.type == Common::EVENT_LBUTTONUP) { + _engine->handleObjectSelect(1, kSelfWindow, false, false); + } + return true; +} + +bool MacVenture::Gui::processExitsEvents(WindowClick click, Common::Event &event) { + if (event.type == Common::EVENT_LBUTTONUP) { + if (_engine->needsClickToContinue()) { + return true; + } + + Common::Point position( + event.mouse.x - _exitsWindow->getDimensions().left, + event.mouse.y - _exitsWindow->getDimensions().top); + + CommandButton button; + if (!_exitsData) + return false; + + Common::Array<CommandButton>::iterator it = _exitsData->begin(); + for (; it != _exitsData->end(); ++it) { + if (it->isInsideBounds(position)) { + it->select(); + button = *it; + _engine->handleObjectSelect(button.getData().refcon, kExitsWindow, false, false); + return true; + } else { + it->unselect(); + } + } + + } + return getWindowData(kExitsWindow).visible; +} + +bool MacVenture::Gui::processDiplomaEvents(WindowClick click, Common::Event &event) { + if (_engine->needsClickToContinue()) + return true; + + return getWindowData(kDiplomaWindow).visible; +} + +bool Gui::processInventoryEvents(WindowClick click, Common::Event &event) { + if (event.type == Common::EVENT_LBUTTONDOWN && click == kBorderCloseButton) { + WindowReference ref = findWindowAtPoint(event.mouse); + if (ref == kNoWindow) { + return false; + } + + if (click == kBorderCloseButton) { + removeInventoryWindow(ref); + return true; + } + } + + if (_engine->needsClickToContinue()) + return true; + + if (event.type == Common::EVENT_LBUTTONDOWN) { + // Find the appropriate window + WindowReference ref = findWindowAtPoint(event.mouse); + if (ref == kNoWindow) { + return false; + } + + WindowData &data = findWindowData((WindowReference) ref); + + if (click == kBorderScrollUp) { + data.scrollPos.y = MAX(0, data.scrollPos.y - kScrollAmount); + } + if (click == kBorderScrollDown) { + data.scrollPos.y += kScrollAmount; + } + if (click == kBorderScrollLeft) { + data.scrollPos.x = MAX(0, data.scrollPos.x - kScrollAmount); + } + if (click == kBorderScrollRight) { + data.scrollPos.x += kScrollAmount; + } + } + return true; +} + +void Gui::selectForDrag(Common::Point cursorPosition) { + WindowReference ref = findWindowAtPoint(cursorPosition); + if (ref == kNoWindow) { + return; + } + + Graphics::MacWindow *win = findWindow(ref); + WindowData &data = findWindowData((WindowReference) ref); + + Common::Rect clickRect = calculateClickRect(cursorPosition + data.scrollPos, win->getDimensions()); + checkSelect(data, cursorPosition, clickRect, (WindowReference)ref); +} + +void Gui::handleSingleClick() { + debugC(2, kMVDebugGUI, "Registered Single Click"); + // HACK THERE HAS TO BE A MORE ELEGANT WAY + if (_dialog) { + return; + } + handleDragRelease(false, false); +} + +void Gui::handleDoubleClick() { + debugC(2, kMVDebugGUI, "Registered Double Click"); + if (_dialog) { + return; + } + handleDragRelease(false, true); +} + +void Gui::ensureAssetLoaded(ObjID obj) { + if (!_assets.contains(obj)) { + _assets[obj] = new ImageAsset(obj, _graphics); + } +} + + +} // End of namespace MacVenture diff --git a/engines/macventure/gui.h b/engines/macventure/gui.h new file mode 100644 index 0000000000..8ae4a1534d --- /dev/null +++ b/engines/macventure/gui.h @@ -0,0 +1,369 @@ +/* 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 MACVENTURE_GUI_H +#define MACVENTURE_GUI_H + +#include "graphics/macgui/macwindowmanager.h" +#include "graphics/macgui/macwindow.h" +#include "graphics/macgui/macmenu.h" + +#include "graphics/font.h" + +#include "common/timer.h" + +#include "macventure/macventure.h" +#include "macventure/container.h" +#include "macventure/image.h" +#include "macventure/prebuilt_dialogs.h" +#include "macventure/dialog.h" +#include "macventure/controls.h" +#include "macventure/windows.h" + +namespace MacVenture { + +using namespace Graphics::MacGUIConstants; +using namespace Graphics::MacWindowConstants; +class MacVentureEngine; +typedef uint32 ObjID; + +class Cursor; +class ConsoleText; +class CommandButton; +class ImageAsset; +class Dialog; + +BorderBounds borderBounds(MVWindowType type); + +enum MenuAction { + kMenuActionAbout, + kMenuActionNew, + kMenuActionOpen, + kMenuActionSave, + kMenuActionSaveAs, + kMenuActionQuit, + kMenuActionUndo, + kMenuActionCut, + kMenuActionCopy, + kMenuActionPaste, + kMenuActionClear, + kMenuActionCleanUp, + kMenuActionMessUp, + + kMenuActionCommand +}; + +struct DraggedObj { + ObjID id; + Common::Point pos; + Common::Point mouseOffset; + Common::Point startPos; + WindowReference startWin; + bool hasMoved; +}; + +class Gui { + +public: + Gui(MacVentureEngine *engine, Common::MacResManager *resman); + ~Gui(); + + void reloadInternals(); + + void draw(); + void drawMenu(); + void drawTitle(); + + void clearControls(); + bool processEvent(Common::Event &event); + void handleMenuAction(MenuAction action); + void updateWindow(WindowReference winID, bool containerOpen); + void invertWindowColors(WindowReference winID); + + WindowReference createInventoryWindow(ObjID objRef); + bool tryCloseWindow(WindowReference winID); + + Common::Point getObjMeasures(ObjID obj); + + WindowReference getObjWindow(ObjID objID); + WindowReference findObjWindow(ObjID objID); + + // Event processors + bool processCommandEvents(WindowClick click, Common::Event &event); + bool processMainGameEvents(WindowClick click, Common::Event &event); + bool processOutConsoleEvents(WindowClick click, Common::Event &event); + bool processSelfEvents(WindowClick click, Common::Event &event); + bool processExitsEvents(WindowClick click, Common::Event &event); + bool processDiplomaEvents(WindowClick click, Common::Event &event); + bool processInventoryEvents(WindowClick click, Common::Event &event); + + const WindowData& getWindowData(WindowReference reference); + + const Graphics::Font& getCurrentFont(); + + // Clicks + void selectForDrag(Common::Point cursorPosition); + void handleSingleClick(); + void handleDoubleClick(); + + // Modifiers + void bringToFront(WindowReference window); + void setWindowTitle(WindowReference winID, Common::String string); + void updateWindowInfo(WindowReference ref, ObjID objID, const Common::Array<ObjID> &children); + void ensureInventoryOpen(WindowReference reference, ObjID id); + + void addChild(WindowReference target, ObjID child); + void removeChild(WindowReference target, ObjID child); + + void clearExits(); + void unselectExits(); + void updateExit(ObjID id); + + void printText(const Common::String &text); + + //Dialog interactions + void showPrebuiltDialog(PrebuiltDialogs type); + bool isDialogOpen(); + + void getTextFromUser(); + void setTextInput(Common::String str); + void closeDialog(); + + void loadGame(); + void saveGame(); + void newGame(); + void quitGame(); + + void createInnerSurface(Graphics::ManagedSurface *innerSurface, Graphics::ManagedSurface *outerSurface, const BorderBounds &borders); + + +private: // Attributes + + MacVentureEngine *_engine; + Common::MacResManager *_resourceManager; + + Graphics::ManagedSurface _screen; + Graphics::MacWindowManager _wm; + + Common::List<WindowData> *_windowData; + Common::Array<CommandButton> *_controlData; + Common::Array<CommandButton> *_exitsData; + + Graphics::MacWindow *_controlsWindow; + Graphics::MacWindow *_mainGameWindow; + Graphics::MacWindow *_outConsoleWindow; + Graphics::MacWindow *_selfWindow; + Graphics::MacWindow *_exitsWindow; + Graphics::MacWindow *_diplomaWindow; + Common::Array<Graphics::MacWindow*> _inventoryWindows; + Graphics::Menu *_menu; + Dialog *_dialog; + + Container *_graphics; + Common::HashMap<ObjID, ImageAsset*> _assets; + + Graphics::ManagedSurface _draggedSurface; + DraggedObj _draggedObj; + + Cursor *_cursor; + + ConsoleText *_consoleText; + +private: // Methods + + // Initializers + void initGUI(); + void initWindows(); + void assignObjReferences(); // Mainly guesswork + + // Loaders + bool loadMenus(); + bool loadWindows(); + bool loadControls(); + void loadBorders(Graphics::MacWindow *target, MVWindowType type); + void loadBorder(Graphics::MacWindow *target, MVWindowType type, bool active); + void loadGraphics(); + void clearAssets(); + + // Drawers + void drawWindows(); + void drawCommandsWindow(); + void drawMainGameWindow(); + void drawSelfWindow(); + void drawInventories(); + void drawExitsWindow(); + void drawConsoleWindow(); + + void drawDraggedObject(); + void drawObjectsInWindow(const WindowData &targetData, Graphics::ManagedSurface *surface); + void drawWindowTitle(WindowReference target, Graphics::ManagedSurface *surface); + void drawDialog(); + + void moveDraggedObject(Common::Point target); + + // Finders + WindowReference findWindowAtPoint(Common::Point point); + Common::Point getGlobalScrolledSurfacePosition(WindowReference reference); + WindowData& findWindowData(WindowReference reference); + Graphics::MacWindow *findWindow(WindowReference reference); + + // Utils + void checkSelect(const WindowData &data, Common::Point pos, const Common::Rect &clickRect, WindowReference ref); + bool canBeSelected(ObjID obj, const Common::Rect &clickRect, WindowReference ref); + bool isRectInsideObject(Common::Rect target, ObjID obj); + void selectDraggable(ObjID child, WindowReference origin, Common::Point startPos); + void handleDragRelease(bool shiftPressed, bool isDoubleClick); + Common::Rect calculateClickRect(Common::Point clickPos, Common::Rect windowBounds); + Common::Point localizeTravelledDistance(Common::Point point, WindowReference origin, WindowReference target); + void removeInventoryWindow(WindowReference ref); + + void ensureAssetLoaded(ObjID obj); + +}; + +enum ClickState { + kCursorIdle = 0, + kCursorSCStart = 1, + kCursorSCDrag = 2, + kCursorDCStart = 3, + kCursorDCDo = 4, + kCursorSCSink = 5, + kCursorStateCount +}; + +enum CursorInput { // Columns for the FSM transition table + kButtonDownCol = 0, + kButtonUpCol = 1, + kTickCol = 2, + kCursorInputCount +}; + +class Cursor { + +public: + Cursor(Gui *gui); + ~Cursor(); + + void tick(); + bool processEvent(const Common::Event &event); + Common::Point getPos(); + bool canSelectDraggable(); + +private: + + void changeState(CursorInput input); + void executeStateIn(); + void executeStateOut(); + + +private: + Gui *_gui; + + Common::Point _pos; + ClickState _state; +}; + + + +enum { + kConsoleLeftOffset = 2 +}; + +class ConsoleText { + +public: + + ConsoleText(Gui *gui) { + _gui = gui; + _lines.push_back(""); + updateScroll(); + } + + ~ConsoleText() { + + } + + void printLine(const Common::String &str, int maxW) { + Common::StringArray wrappedLines; + int textW = maxW; + const Graphics::Font *font = &_gui->getCurrentFont(); + + font->wordWrapText(str, textW, wrappedLines); + + if (wrappedLines.empty()) // Sometimes we have empty lines + _lines.push_back(""); + + for (Common::StringArray::const_iterator j = wrappedLines.begin(); j != wrappedLines.end(); ++j) { + _lines.push_back(*j); + } + + updateScroll(); + } + + void renderInto(Graphics::ManagedSurface *target, const BorderBounds borders, int textOffset) { + target->fillRect(target->getBounds(), kColorWhite); + + Graphics::ManagedSurface *composeSurface = new Graphics::ManagedSurface(); + _gui->createInnerSurface(composeSurface, target, borders); + composeSurface->clear(kColorGreen); + + const Graphics::Font *font = &_gui->getCurrentFont(); + uint y = target->h - font->getFontHeight(); + for (uint i = _scrollPos; i != 0; i--) { + font->drawString(target, _lines[i], textOffset, y, font->getStringWidth(_lines[i]), kColorBlack); + y -= font->getFontHeight(); + } + + Common::Point composePosition = Common::Point(borders.leftOffset, borders.topOffset); + target->transBlitFrom(*composeSurface, composePosition, kColorGreen); + delete composeSurface; + } + + void updateScroll() { + _scrollPos = _lines.size() - 1; + } + + void scrollDown() { + if (_scrollPos < (int)(_lines.size() - 1)) { + _scrollPos++; + } + } + + void scrollUp() { + if (_scrollPos > 0) { + _scrollPos--; + } + } + + +private: + + Gui *_gui; + + Common::StringArray _lines; + int _scrollPos; + +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/hufflists.h b/engines/macventure/hufflists.h new file mode 100644 index 0000000000..1ea4f21535 --- /dev/null +++ b/engines/macventure/hufflists.h @@ -0,0 +1,57 @@ +/* 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 MACVENTURE_HUFFLIST_H +#define MACVENTURE_HUFFLIST_H + +namespace MacVenture { + +// The engine uses a <= comparison instead of ==, so I can't use Common::Huffman +class HuffmanLists { +public: + HuffmanLists() { + _numEntries = 0; + } + HuffmanLists(uint32 num, uint32 *lens, uint32 *masks, uint32 *symbols) { + _numEntries = num; + _lens = Common::Array<uint32>(lens, num); + _masks = Common::Array<uint32>(masks, num); + _symbols = Common::Array<uint32>(symbols, num); + } + ~HuffmanLists() {} + + + uint32 getNumEntries() const { return _numEntries; } + uint32 getLength(uint32 index) const { return _lens[index]; } + uint32 getMask(uint32 index) const { return _masks[index]; } + uint32 getSymbol(uint32 index) const { return _symbols[index]; } + +private: + uint32 _numEntries; + Common::Array<uint32> _lens; + Common::Array<uint32> _masks; + Common::Array<uint32> _symbols; +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/image.cpp b/engines/macventure/image.cpp new file mode 100644 index 0000000000..172121af0b --- /dev/null +++ b/engines/macventure/image.cpp @@ -0,0 +1,553 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "macventure/image.h" + +namespace MacVenture { + +static const PPICHuff PPIC1Huff = { + // Masks + { 0x0000,0x2000,0x4000,0x5000,0x6000,0x7000,0x8000,0x9000,0xa000, + 0xb000,0xc000,0xd000,0xd800,0xe000,0xe800,0xf000,0xf800 }, + // Lens + { 3,3,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5 }, + // Symbols + { 0x00,0x0f,0x03,0x05,0x06,0x07,0x08,0x09,0x0a,0x0c,0xff,0x01, + 0x02,0x04,0x0b,0x0d,0xe } +}; + +static const PPICHuff PPIC2Huff = { + // Masks + { 0x0000,0x4000,0x8000,0xc000,0xc800,0xd000,0xd800,0xe000,0xe800, + 0xf000,0xf400,0xf600,0xf800,0xfa00,0xfc00,0xfe00,0xff00 }, + // Lens + { 2,2,2,5,5,5,5,5,5,6,7,7,7,7,7,8,8 }, + // Symbols + { 0xff,0x00,0x0f,0x01,0x03,0x07,0x0e,0x0c,0x08,0x06,0x02,0x04, + 0x09,0x0d,0x0b,0x0a,0x05 } +}; + +// Used to load the huffman table in PPIC3 decoding +static const byte loadBits[] = { + 0x08, 0x0f, 0x02, 0xff, 0x00, + 0x04, 0xff, 0x01, + 0x07, 0x09, 0x08, 0xff, 0x03, + 0x04, 0xff, 0x04, + 0x0a, 0x07, 0x0a, 0x0b, 0x06, 0xff, 0x05, + 0x06, 0x06, 0x0b, 0xff, 0x07, + 0x03, 0xff, 0x09, + 0x04, 0x03, 0x0e, 0xff, 0x0c, + 0x02, 0xff, 0x0d, + 0x01, 0xff, 0x0f, + 0xff +}; + +ImageAsset::ImageAsset(ObjID original, Container *container) { + _id = (original * 2); + _mask = (original * 2) + 1; + + uint imgRowBytes = 0; + uint imgBitWidth = 0; + uint imgBitHeight = 0; + uint maskRowBytes = 0; + uint maskBitWidth = 0; + uint maskBitHeight = 0; + + _container = container; + decodePPIC(_id, _imgData, imgBitHeight, imgBitWidth, imgRowBytes); + _imgRowBytes = imgRowBytes; + _imgBitWidth = imgBitWidth; + _imgBitHeight = imgBitHeight; + + if (_container->getItemByteSize(_mask)) { + decodePPIC(_mask, _maskData, maskBitHeight, maskBitWidth, maskRowBytes); + } + _maskRowBytes = maskRowBytes; + _maskBitWidth = maskBitWidth; + _maskBitHeight = maskBitHeight; +} + +ImageAsset::~ImageAsset() { + debugC(3, kMVDebugImage, "~ImageAsset(%d)", _id / 2); +} + +void ImageAsset::decodePPIC(ObjID id, Common::Array<byte> &data, uint &bitHeight, uint &bitWidth, uint &rowBytes) { + ObjID realID = id; + uint32 size = _container->getItemByteSize(id); + if (size < 2) { + rowBytes = 0; + bitHeight = 0; + bitWidth = 0; + return; + } + if (size == 2) { + Common::SeekableReadStream *newItemStream = _container->getItem(id); + realID = newItemStream->readUint16BE(); + delete newItemStream; + } + Common::SeekableReadStream *baseStream = _container->getItem(realID); + Common::BitStream32BEMSB stream(baseStream); + + uint8 mode = stream.getBits(3); + int w, h; + if (stream.getBit()) { + h = stream.getBits(10); + } else { + h = stream.getBits(6); + } + + if (stream.getBit()) { + w = stream.getBits(10); + } else { + w = stream.getBits(6); + } + + rowBytes = ((w + 0xF) >> 3) & 0xFFFE; + bitWidth = w; + bitHeight = h; + + for (uint i = 0; i < rowBytes * h; i++) { + data.push_back(0); + } + + switch (mode) { + case MacVenture::kPPIC0: + decodePPIC0(stream, data, bitHeight, bitWidth, rowBytes); + break; + case MacVenture::kPPIC1: + decodePPIC1(stream, data, bitHeight, bitWidth, rowBytes); + break; + case MacVenture::kPPIC2: + decodePPIC2(stream, data, bitHeight, bitWidth, rowBytes); + break; + case MacVenture::kPPIC3: + decodePPIC3(stream, data, bitHeight, bitWidth, rowBytes); + break; + } + + delete baseStream; +} + +void ImageAsset::decodePPIC0(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + uint words = bitWidth >> 4; + uint bytes = bitWidth & 0xF; + uint v = 0; + uint p = 0; + for (uint y = 0; y < bitHeight; y++) { + for (uint x = 0; x < words; x++) { + v = stream.peekBits(32); + stream.skip(16); + v >>= 16 - (stream.pos() % 8); + data[p] = (v >> 8) & 0xff; p++; + data[p] = v & 0xff; p++; + } + if (bytes) { + v = stream.getBits(bytes); + v <<= 16 - bytes; + data[p] = (v >> 8) & 0xff; p++; + data[p] = v & 0xff; p++; + } + } + +} + +void ImageAsset::decodePPIC1(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + decodeHuffGraphic(PPIC1Huff, stream, data, bitHeight, bitWidth, rowBytes); +} + +void ImageAsset::decodePPIC2(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + decodeHuffGraphic(PPIC2Huff, stream, data, bitHeight, bitWidth, rowBytes); +} + +void ImageAsset::decodePPIC3(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + // We need to load the huffman from the PPIC itself + PPICHuff huff; + uint16 v, bits; + uint16 load = 0; + while ((bits = loadBits[load++]) != 0xFF) { + v = stream.getBits(bits); + while ((bits = loadBits[load++]) != 0xFF) { + huff.symbols[loadBits[load++]] = v % bits; + v = (bits != 0) ? (v / bits) : 0; + } + huff.symbols[loadBits[load++]] = v; + } + huff.symbols[0x10] = 0; + for (uint i = 0x10; i > 0; i--) { + for (uint j = i; j <= 0x10; j++) { + if (huff.symbols[j] >= huff.symbols[i - 1]) { + huff.symbols[j]++; + } + } + } + + for (int i = 0x10; i >= 0; i--) { + if (huff.symbols[i] == 0x10) { + huff.symbols[i] = 0xff; + break; + } + } + + bits = stream.getBits(2) + 1; + uint16 mask = 0; + for (uint i = 0; i < 0xf; i++) { + if (i) { + while (!stream.getBit()) { + bits++; + } + } + huff.lens[i] = bits; + huff.masks[i] = mask; + mask += 1 << (16 - bits); + } + huff.masks[0xf] = mask; + while (mask&(1 << (16 - bits))) { + bits++; + } + huff.masks[0x10] = mask | (1 << (16 - bits)); + huff.lens[0xf] = bits; + huff.lens[0x10] = bits; + + decodeHuffGraphic(huff, stream, data, bitHeight, bitWidth, rowBytes); +} + +void ImageAsset::decodeHuffGraphic(const PPICHuff &huff, Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + byte flags = 0; + _walkRepeat = 0; + _walkLast = 0; + if (bitWidth & 3) { + flags = stream.getBits(5); + } else { + flags = stream.getBits(4) << 1; + } + + byte odd = 0; + byte blank = bitWidth & 0xf; + if (blank) { + blank >>= 2; + odd = blank & 1; + blank = 2 - (blank >> 1); + } + + uint16 pos = 0; + for (uint y = 0; y < bitHeight; y++) { + uint16 x = 0; + for (; x < bitWidth >> 3; x++) { + byte hi = walkHuff(huff, stream) << 4; + data[pos++] = walkHuff(huff, stream) | hi; + } + if (odd) { + data[pos] = walkHuff(huff, stream) << 4; + } + pos += blank; + } + + uint16 edge = bitWidth & 3; + if (edge) { + pos = rowBytes - blank; + uint16 bits = 0; + uint16 val = 0; + uint16 v; + for (uint y = 0; y < bitHeight; y++) { + if (flags & 1) { + if (bits < edge) { + v = walkHuff(huff, stream) << 4; + val |= v >> bits; + bits += 4; + } + bits -= edge; + v = val; + val <<= edge; + val &= 0xFF; + } else { + v = stream.getBits(edge); + v <<= 8 - edge; + } + if (odd) + v >>= 4; + + data[pos] |= v & 0xff; + pos += rowBytes; + } + } + if (flags & 8) { + pos = 0; + for (uint y = 0; y < bitHeight; y++) { + uint16 v = 0; + if (flags & 2) { + for (uint x = 0; x < rowBytes; x++) { + data[pos] ^= v; + v = data[pos]; + pos++; + } + } else { + for (uint x = 0; x < rowBytes; x++) { + uint16 val = data[pos] ^ v; + val ^= (val >> 4) & 0xf; + data[pos] = val; + pos++; + v = (val << 4) & 0xff; + } + } + } + } + if (flags & 4) { + uint16 delta = rowBytes * 4; + if (flags & 2) { + delta *= 2; + } + pos = 0; + uint q = delta; + for (uint i = 0; i < bitHeight * rowBytes - delta; i++) { + data[q] ^= data[pos]; + q++; + pos++; + } + } +} + +byte ImageAsset::walkHuff(const PPICHuff &huff, Common::BitStream &stream) { + if (_walkRepeat) { + _walkRepeat--; + _walkLast = ((_walkLast << 8) & 0xFF00) | (_walkLast >> 8); + return _walkLast & 0xFF; + } + uint16 dw = stream.peekBits(16); + uint16 i = 0; + for (;i < 16; i++) { + if (huff.masks[i + 1] > dw) { + break; + } + } + stream.skip(huff.lens[i]); + uint8 val = huff.symbols[i]; + if (val == 0xFF) { + if (!stream.getBit()) { + _walkLast &= 0xFF; + _walkLast |= _walkLast << 8; + } + _walkRepeat = stream.getBits(3); + if (_walkRepeat < 3) { + _walkRepeat <<= 4; + _walkRepeat |= stream.getBits(4); + if (_walkRepeat < 8) { + _walkRepeat <<= 8; + _walkRepeat |= stream.getBits(8); + } + } + _walkRepeat -= 2; + _walkLast = ((_walkLast << 8) & 0xFF00) | (_walkLast >> 8); + return _walkLast & 0xFF; + } else { + _walkLast <<= 8; + _walkLast |= val; + _walkLast &= 0xFFFF; + } + return val; +} + +void ImageAsset::blitInto(Graphics::ManagedSurface *target, int x, int y, BlitMode mode) { + if (mode == kBlitDirect) { + blitDirect(target, x, y, _imgData, _imgBitHeight, _imgBitWidth, _imgRowBytes); + } else if (mode < kBlitXOR) { + if (_container->getItemByteSize(_mask)) { // Has mask + switch (mode) { + case MacVenture::kBlitBIC: + blitBIC(target, x, y, _maskData, _maskBitHeight, _maskBitWidth, _maskRowBytes); + break; + case MacVenture::kBlitOR: + blitOR(target, x, y, _maskData, _maskBitHeight, _maskBitWidth, _maskRowBytes); + break; + default: + break; + } + } else if (_container->getItemByteSize(_id)) { + switch (mode) { + case MacVenture::kBlitBIC: + target->fillRect(Common::Rect(x, y, x + _imgBitWidth, y + _imgBitHeight), kColorWhite); + break; + case MacVenture::kBlitOR: + target->fillRect(Common::Rect(x, y, x + _imgBitWidth, y + _imgBitHeight), kColorBlack); + break; + default: + break; + } + } + + if (_container->getItemByteSize(_id) && mode > 0) { + blitXOR(target, x, y, _imgData, _imgBitHeight, _imgBitWidth, _imgRowBytes); + } + } +} + +bool ImageAsset::isPointInside(Common::Point point) { + if (point.x >= _maskBitWidth || point.y >= _maskBitHeight) { + return false; + } + if (_maskData.empty()) { + return false; + } + // We see if the point lands on the mask. + uint pix = _maskData[(point.y * _maskRowBytes) + (point.x >> 3)] & (1 << (7 - (point.x & 7))); + return pix != 0; +} + +bool ImageAsset::isRectInside(Common::Rect rect) { + if (_maskData.empty()) { + return (rect.width() > 0 && rect.height() > 0); + } + + for (int y = rect.top; y < rect.top + rect.height(); y++) { + uint bmpofs = y * _maskRowBytes; + byte pix; + for (int x = rect.left; x < rect.left + rect.width(); x++) { + pix = _maskData[bmpofs + (x >> 3)] & (1 << (7 - (x & 7))); + if (pix) { + return true; + } + } + } + return false; +} + +int ImageAsset::getWidth() { + if (_imgData.size() == 0) { + return 0; + } + return MAX(0, (int)_imgBitWidth); +} + +int ImageAsset::getHeight() { + if (_imgData.size() == 0) { + return 0; + } + return MAX(0, (int)_imgBitHeight); +} + +void ImageAsset::blitDirect(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + uint sx, sy, w, h; + calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h); + + for (uint y = 0; y < h; y++) { + uint bmpofs = (y + sy) * rowBytes; + byte pix = 0; + for (uint x = 0; x < w; x++) { + assert(ox + x <= target->w); + assert(oy + y <= target->h); + pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7))); + pix = pix ? kColorBlack : kColorWhite; + *((byte *)target->getBasePtr(ox + x, oy + y)) = pix; + } + } +} + +void ImageAsset::blitBIC(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + uint sx, sy, w, h; + calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h); + + for (uint y = 0; y < h; y++) { + uint bmpofs = (y + sy) * rowBytes; + byte pix = 0; + for (uint x = 0; x < w; x++) { + assert(ox + x <= target->w); + assert(oy + y <= target->h); + pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7))); + if (pix) { + *((byte *)target->getBasePtr(ox + x, oy + y)) = kColorWhite; + } + } + } +} + +void ImageAsset::blitOR(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + uint sx, sy, w, h; + calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h); + + for (uint y = 0; y < h; y++) { + uint bmpofs = (y + sy) * rowBytes; + byte pix = 0; + for (uint x = 0; x < w; x++) { + assert(ox + x <= target->w); + assert(oy + y <= target->h); + pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7))); + if (pix) { + *((byte *)target->getBasePtr(ox + x, oy + y)) = kColorBlack; + } + } + } +} + +void ImageAsset::blitXOR(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) { + uint sx, sy, w, h; + calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h); + + for (uint y = 0; y < h; y++) { + uint bmpofs = (y + sy) * rowBytes; + byte pix = 0; + for (uint x = 0; x < w; x++) { + pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7))); + if (pix) { // We need to xor + assert(ox + x <= target->w); + assert(oy + y <= target->h); + byte p = *((byte *)target->getBasePtr(ox + x, oy + y)); + *((byte *)target->getBasePtr(ox + x, oy + y)) = + (p == kColorWhite) ? kColorBlack : kColorWhite; + } + } + } +} + +void ImageAsset::calculateSectionToDraw(Graphics::ManagedSurface *target, int &ox, int &oy, uint bitWidth, uint bitHeight, uint &sx, uint &sy, uint &w, uint &h) { + + calculateSectionInDirection(target->w, bitWidth, ox, sx, w); + calculateSectionInDirection(target->h, bitHeight, oy, sy, h); + + assert(w <= target->w); + assert((int)w >= 0); + assert(w <= bitWidth); + assert(h <= target->h); + assert((int)h >= 0); + assert(h <= bitHeight); +} + +void ImageAsset::calculateSectionInDirection(uint targetWhole, uint originWhole, int &originPosition, uint &startPosition, uint &blittedWhole) { + startPosition = 0; + blittedWhole = originWhole; + if (originPosition < 0) { + if (ABS(originPosition) > (int)blittedWhole) { + blittedWhole = 0; + } else { + blittedWhole -= -originPosition; + } + startPosition = -originPosition; + originPosition = 0; + } + if (originPosition + blittedWhole > targetWhole) { + if (originPosition > (int)targetWhole) { + blittedWhole = 0; + } else { + blittedWhole = targetWhole - originPosition; + } + } +} + +} // End of namespace MacVenture diff --git a/engines/macventure/image.h b/engines/macventure/image.h new file mode 100644 index 0000000000..6f6b9600ac --- /dev/null +++ b/engines/macventure/image.h @@ -0,0 +1,108 @@ +/* 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 MACVENTURE_IMAGE_H +#define MACVENTURE_IMAGE_H + +#include "macventure/macventure.h" +#include "macventure/container.h" + +namespace MacVenture { + +typedef uint32 ObjID; +class Container; + + +enum BlitMode { + kBlitDirect = 0, + kBlitBIC = 1, + kBlitOR = 2, + kBlitXOR = 3 +}; + +enum GraphicsEncoding { + kPPIC0 = 0, + kPPIC1 = 1, + kPPIC2 = 2, + kPPIC3 = 3 +}; + +struct PPICHuff { + uint16 masks[17]; + uint16 lens[17]; + uint8 symbols[17]; +}; + +class ImageAsset { +public: + ImageAsset(ObjID original, Container *container); + ~ImageAsset(); + + void blitInto(Graphics::ManagedSurface *target, int x, int y, BlitMode mode); + + bool isPointInside(Common::Point point); + bool isRectInside(Common::Rect rect); + + int getWidth(); + int getHeight(); + +private: + void decodePPIC(ObjID id, Common::Array<byte> &data, uint &bitHeight, uint &bitWidth, uint &rowBytes); + + void decodePPIC0(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + void decodePPIC1(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + void decodePPIC2(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + void decodePPIC3(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + + void decodeHuffGraphic(const PPICHuff &huff, Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + byte walkHuff(const PPICHuff &huff, Common::BitStream &stream); + + void blitDirect(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + void blitBIC(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + void blitOR(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + void blitXOR(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes); + + void calculateSectionToDraw(Graphics::ManagedSurface *target, int &ox, int &oy, uint bitWidth, uint bitHeight, uint &sx, uint &sy, uint &w, uint &h); + void calculateSectionInDirection(uint targetWhole, uint originWhole, int &originPosition, uint &startPosition, uint &blittedWhole); + +private: + ObjID _id; + ObjID _mask; + Container *_container; + + uint16 _walkRepeat; + uint16 _walkLast; + + Common::Array<byte> _imgData; + uint16 _imgRowBytes; + uint16 _imgBitWidth; + uint16 _imgBitHeight; + + Common::Array<byte> _maskData; + uint16 _maskRowBytes; + uint16 _maskBitWidth; + uint16 _maskBitHeight; +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/macventure.cpp b/engines/macventure/macventure.cpp new file mode 100644 index 0000000000..826409f30b --- /dev/null +++ b/engines/macventure/macventure.cpp @@ -0,0 +1,1185 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/system.h" +#include "common/debug-channels.h" +#include "common/debug.h" +#include "common/error.h" +#include "common/config-manager.h" +#include "engines/util.h" + +#include "macventure/macventure.h" + +// To move +#include "common/file.h" + +namespace MacVenture { + +// HACK, see below +void toASCII(Common::String &str) { + debugC(3, kMVDebugMain, "toASCII: %s", str.c_str()); + Common::String::iterator it = str.begin(); + for (; it != str.end(); it++) { + if (*it == '\216') { + str.replace(it, it + 1, "e"); + } + if (*it == '\210') { + str.replace(it, it + 1, "a"); + } + } +} + +enum { + kMaxMenuTitleLength = 30 +}; + +MacVentureEngine::MacVentureEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst) { + _gameDescription = gameDesc; + _rnd = new Common::RandomSource("macventure"); + + initDebugChannels(); + + _debugger = NULL; + _resourceManager = NULL; + _globalSettings = NULL; + _gui = NULL; + _world = NULL; + _scriptEngine = NULL; + _filenames = NULL; + + _decodingDirectArticles = NULL; + _decodingNamingArticles = NULL; + _decodingIndirectArticles = NULL; + _textHuffman = NULL; + + _soundManager = NULL; + + _dataBundle = NULL; + + debug("MacVenture::MacVentureEngine()"); +} + +MacVentureEngine::~MacVentureEngine() { + debug("MacVenture::~MacVentureEngine()"); + + DebugMan.clearAllDebugChannels(); + + if (_rnd) + delete _rnd; + + if (_debugger) + delete _debugger; + + if (_resourceManager) + delete _resourceManager; + + if (_globalSettings) + delete _globalSettings; + + if (_gui) + delete _gui; + + if (_world) + delete _world; + + if (_scriptEngine) + delete _scriptEngine; + + if (_filenames) + delete _filenames; + + if (_decodingDirectArticles) + delete _decodingDirectArticles; + + if (_decodingNamingArticles) + delete _decodingNamingArticles; + + if (_decodingIndirectArticles) + delete _decodingIndirectArticles; + + if (_textHuffman) + delete _textHuffman; + + if (_soundManager) + delete _soundManager; + + if (_dataBundle) + delete _dataBundle; +} + +void MacVentureEngine::initDebugChannels() { + DebugMan.addDebugChannel(kMVDebugMain, "main", "Engine state"); + DebugMan.addDebugChannel(kMVDebugGUI, "gui", "Gui"); + DebugMan.addDebugChannel(kMVDebugText, "text", "Text decoders and printers"); + DebugMan.addDebugChannel(kMVDebugImage, "image", "Image decoders and renderers"); + DebugMan.addDebugChannel(kMVDebugScript, "script", "Script engine"); + DebugMan.addDebugChannel(kMVDebugSound, "sound", "Sound decoders"); + DebugMan.addDebugChannel(kMVDebugContainer, "container", "Containers"); +} + +Common::Error MacVentureEngine::run() { + debug("MacVenture::MacVentureEngine::init()"); + initGraphics(kScreenWidth, kScreenHeight, true); + + _debugger = new Console(this); + + // Additional setup. + debug("MacVentureEngine::init"); + + _resourceManager = new Common::MacResManager(); + if (!_resourceManager->open(getGameFileName())) + error("ENGINE: Could not open %s as a resource fork", getGameFileName()); + + // Engine-wide loading + if (!loadGlobalSettings()) + error("ENGINE: Could not load the engine settings"); + + _oldTextEncoding = !loadTextHuffman(); + + _filenames = new StringTable(this, _resourceManager, kFilenamesStringTableID); + _decodingDirectArticles = new StringTable(this, _resourceManager, kCommonArticlesStringTableID); + _decodingNamingArticles = new StringTable(this, _resourceManager, kNamingArticlesStringTableID); + _decodingIndirectArticles = new StringTable(this, _resourceManager, kIndirectArticlesStringTableID); + + loadDataBundle(); + + // Big class instantiation + _gui = new Gui(this, _resourceManager); + _world = new World(this, _resourceManager); + _scriptEngine = new ScriptEngine(this, _world); + + _soundManager = new SoundManager(this, _mixer); + + setInitialFlags(); + + int directSaveSlotLoading = ConfMan.getInt("save_slot"); + if (directSaveSlotLoading >= 0) { + if (loadGameState(directSaveSlotLoading).getCode() != Common::kNoError) { + error("ENGINE: Could not load game from slot '%d'", directSaveSlotLoading); + } + } else { + setNewGameState(); + } + selectControl(kStartOrResume); + + _gui->addChild(kSelfWindow, 1); + _gui->updateWindow(kSelfWindow, false); + + while (_gameState != kGameStateQuitting) { + processEvents(); + + if (_gameState != kGameStateQuitting && !_gui->isDialogOpen()) { + + if (_prepared) { + _prepared = false; + + if (!_halted) + updateState(false); + + if (_cmdReady || _halted) { + _halted = false; + if (runScriptEngine()) { + _halted = true; + _paused = true; + } else { + _paused = false; + updateState(true); + updateControls(); + updateExits(); + } + } + + if (_gameState == kGameStateWinnig || _gameState == kGameStateLosing) { + endGame(); + } + } + } + refreshScreen(); + } + + return Common::kNoError; +} + +void MacVentureEngine::refreshScreen() { + _gui->draw(); + g_system->updateScreen(); + g_system->delayMillis(50); +} + +void MacVentureEngine::newGame() { + _world->startNewGame(); + reset(); + setInitialFlags(); + setNewGameState(); +} + +void MacVentureEngine::setInitialFlags() { + _paused = false; + _halted = false; + _cmdReady = false; + _haltedAtEnd = false; + _haltedInSelection = false; + _clickToContinue = true; + _gameState = kGameStateInit; + _destObject = 0; + _prepared = true; +} + +void MacVentureEngine::setNewGameState() { + _cmdReady = true; + ObjID playerParent = _world->getObjAttr(1, kAttrParentObject); + _currentSelection.push_back(playerParent);// Push the parent of the player + _world->setObjAttr(playerParent, kAttrContainerOpen, 1); +} + +void MacVentureEngine::reset() { + resetInternals(); + resetGui(); +} + +void MacVentureEngine::resetInternals() { + _scriptEngine->reset(); + _currentSelection.clear(); + _objQueue.clear(); + _textQueue.clear(); +} + +void MacVentureEngine::resetGui() { + _gui->reloadInternals(); + _gui->updateWindowInfo(kMainGameWindow, getParent(1), _world->getChildren(getParent(1), true)); + // HACK! should update all inventories + _gui->ensureInventoryOpen(kInventoryStart, 1); + _gui->updateWindowInfo(kInventoryStart, 1, _world->getChildren(1, true)); + updateControls(); + updateExits(); + refreshScreen(); +} + +void MacVentureEngine::requestQuit() { + // TODO: Display save game dialog and such + _gameState = kGameStateQuitting; +} + +void MacVentureEngine::requestUnpause() { + _paused = false; + _gameState = kGameStatePlaying; +} + +void MacVentureEngine::selectControl(ControlAction id) { + debugC(2, kMVDebugMain, "Select control %x", id); + if (id == kClickToContinue) { + _clickToContinue = false; + _paused = true; + return; + } + + _selectedControl = id; + refreshReady(); +} + +void MacVentureEngine::refreshReady() { + switch (getInvolvedObjects()) { + case 0: // No selected object + _cmdReady = true; + break; + case 1: // We have some selected object + _cmdReady = _currentSelection.size() != 0; + break; + case 2: + if (_destObject > 0) // We have a destination seleted + _cmdReady = true; + break; + } +} + +void MacVentureEngine::preparedToRun() { + _prepared = true; +} + +void MacVentureEngine::gameChanged() { + _gameChanged = true; +} + +void MacVentureEngine::winGame() { + _gui->showPrebuiltDialog(kWinGameDialog); + _gameState = kGameStateWinnig; +} + +void MacVentureEngine::loseGame() { + _gui->showPrebuiltDialog(kLoseGameDialog); + _paused = true; + //_gameState = kGameStateLosing; +} + +void MacVentureEngine::clickToContinue() { + _clickToContinue = true; +} + +void MacVentureEngine::enqueueObject(ObjectQueueID type, ObjID objID, ObjID target) { + QueuedObject obj; + obj.id = type; + + if (type == kUpdateObject && isObjEnqueued(objID)) { + return; + } + + if (type == kUpdateWindow) { + obj.target = target; + } + + if (type != kHightlightExits) { + obj.object = objID; + obj.parent = _world->getObjAttr(objID, kAttrParentObject); + obj.x = _world->getObjAttr(objID, kAttrPosX); + obj.y = _world->getObjAttr(objID, kAttrPosY); + obj.exitx = _world->getObjAttr(objID, kAttrExitX); + obj.exity = _world->getObjAttr(objID, kAttrExitY); + obj.hidden = _world->getObjAttr(objID, kAttrHiddenExit); + obj.offscreen = _world->getObjAttr(objID, kAttrInvisible); + obj.invisible = _world->getObjAttr(objID, kAttrUnclickable); + } + _objQueue.push_back(obj); +} + +void MacVentureEngine::enqueueText(TextQueueID type, ObjID target, ObjID source, ObjID text) { + QueuedText newText; + newText.id = type; + newText.destination = target; + newText.source = source; + newText.asset = text; + _textQueue.push_back(newText); +} + +void MacVentureEngine::enqueueSound(SoundQueueID type, ObjID target) { + QueuedSound newSound; + newSound.id = type; + newSound.reference = target; + _soundQueue.push_back(newSound); +} + +void MacVentureEngine::handleObjectSelect(ObjID objID, WindowReference win, bool shiftPressed, bool isDoubleClick) { + if (win == kExitsWindow) { + win = kMainGameWindow; + } + + const WindowData &windata = _gui->getWindowData(win); + + if (shiftPressed) { + // TODO: Implement shift functionality. + } else { + if (_selectedControl && _currentSelection.size() > 0 && getInvolvedObjects() > 1) { + if (objID == 0) { + selectPrimaryObject(windata.objRef); + } else { + selectPrimaryObject(objID); + } + preparedToRun(); + } else { + if (objID == 0) { + unselectAll(); + objID = win; + } + if (objID > 0) { + int currentObjectIndex = findObjectInArray(objID, _currentSelection); + + if (currentObjectIndex >= 0) + unselectAll(); + + if (isDoubleClick) { + selectObject(objID); + _destObject = objID; + setDeltaPoint(Common::Point(0, 0)); + if (!_cmdReady) { + selectControl(kActivateObject); + _cmdReady = true; + } + } else { + selectObject(objID); + if (getInvolvedObjects() == 1) + _cmdReady = true; + } + preparedToRun(); + } + } + } +} + +void MacVentureEngine::handleObjectDrop(ObjID objID, Common::Point delta, ObjID newParent) { + _destObject = newParent; + setDeltaPoint(delta); + selectControl(kMoveObject); + refreshReady(); + preparedToRun(); +} + +void MacVentureEngine::setDeltaPoint(Common::Point newPos) { + debugC(4, kMVDebugMain, "Update delta: Old(%d, %d), New(%d, %d)", + _deltaPoint.x, _deltaPoint.y, + newPos.x, newPos.y); + _deltaPoint = newPos; +} + +void MacVentureEngine::focusObjWin(ObjID objID) { + _gui->bringToFront(getObjWindow(objID)); +} + +void MacVentureEngine::updateWindow(WindowReference winID) { + _gui->updateWindow(winID, true); +} + +bool MacVentureEngine::showTextEntry(ObjID text, ObjID srcObj, ObjID destObj) { + debugC(3, kMVDebugMain, "Showing speech dialog, asset %d from %d to %d", text, srcObj, destObj); + _gui->getTextFromUser(); + + _prepared = false; + warning("Show text entry: not fully tested"); + return true; +} + +void MacVentureEngine::setTextInput(Common::String content) { + _prepared = true; + _userInput = content; + _clickToContinue = false; +} + +Common::String MacVentureEngine::getUserInput() { + return _userInput; +} + + +Common::String MacVentureEngine::getStartGameFileName() { + Common::SeekableReadStream *res; + res = _resourceManager->getResource(MKTAG('S', 'T', 'R', ' '), kStartGameFilenameID); + if (!res) + return ""; + + byte length = res->readByte(); + char *fileName = new char[length + 1]; + res->read(fileName, length); + fileName[length] = '\0'; + Common::String result = Common::String(fileName, length); + // HACK, see definition of toASCII + toASCII(result); + + delete[] fileName; + delete res; + + return result; +} + +const GlobalSettings& MacVentureEngine::getGlobalSettings() const { + return *_globalSettings; +} + +// Private engine methods +void MacVentureEngine::processEvents() { + Common::Event event; + + while (_eventMan->pollEvent(event)) { + if (_gui->processEvent(event)) + continue; + + switch (event.type) { + case Common::EVENT_QUIT: + _gameState = kGameStateQuitting; + break; + default: + break; + } + } +} + +bool MacVenture::MacVentureEngine::runScriptEngine() { + debugC(3, kMVDebugMain, "Running script engine"); + if (_haltedAtEnd) { + _haltedAtEnd = false; + if (_scriptEngine->resume(false)) { + _haltedAtEnd = true; + return true; + } + return false; + } + + if (_haltedInSelection) { + _haltedInSelection = false; + if (_scriptEngine->resume(false)) { + _haltedInSelection = true; + return true; + } + updateState(true); + } + + while (!_currentSelection.empty()) { + ObjID obj = _currentSelection.front(); + _currentSelection.remove_at(0); + if (isGameRunning() && _world->isObjActive(obj)) { + if (_scriptEngine->runControl(_selectedControl, obj, _destObject, _deltaPoint)) { + _haltedInSelection = true; + return true; + } + updateState(true); + } + } + if (_selectedControl == 1) { + _gameChanged = false; + } else if (isGameRunning()) { + if (_scriptEngine->runControl(kTick, _selectedControl, _destObject, _deltaPoint)) { + _haltedAtEnd = true; + return true; + } + } + return false; +} + +void MacVentureEngine::endGame() { + requestQuit(); +} + +void MacVentureEngine::updateState(bool pause) { + _prepared = false; + runObjQueue(); + printTexts(); + playSounds(pause); +} + +void MacVentureEngine::revert() { + _gui->invertWindowColors(kMainGameWindow); + preparedToRun(); +} + +void MacVentureEngine::runObjQueue() { + while (!_objQueue.empty()) { + uint32 biggest = 0; + uint32 index = 0; + uint32 temp; + for (uint i = 0; i < _objQueue.size(); i++) { + temp = _objQueue[i].id; + if (temp > biggest) { + biggest = temp; + index = i; + } + } + QueuedObject obj = _objQueue[index]; + _objQueue.remove_at(index); + switch (obj.id) { + case 0x2: + focusObjectWindow(obj.object); + break; + case 0x3: + openObject(obj.object); + break; + case 0x4: + closeObject(obj.object); + break; + case 0x7: + checkObject(obj); + break; + case 0x8: + reflectSwap(obj.object, obj.target); + break; + case 0xc: + _world->setObjAttr(_gui->getWindowData(kMainGameWindow).refcon, kAttrContainerOpen, 0); + _world->setObjAttr(_world->getObjAttr(1, kAttrParentObject), kAttrContainerOpen, 1); + break; + case 0xd: + toggleExits(); + break; + case 0xe: + zoomObject(obj.object); + break; + } + } +} + +void MacVentureEngine::printTexts() { + for (uint i = 0; i < _textQueue.size(); i++) { + QueuedText text = _textQueue.front(); + _textQueue.remove_at(0); + switch (text.id) { + case kTextNumber: + _gui->printText(Common::String(text.asset)); + gameChanged(); + break; + case kTextNewLine: + _gui->printText(Common::String("")); + gameChanged(); + break; + case kTextPlain: + _gui->printText(_world->getText(text.asset, text.source, text.destination)); + gameChanged(); + break; + } + } +} + +void MacVentureEngine::playSounds(bool pause) { + int delay = 0; + while (!_soundQueue.empty()) { + QueuedSound item = _soundQueue.front(); + _soundQueue.remove_at(0); + switch (item.id) { + case kSoundPlay: + _soundManager->playSound(item.reference); + break; + case kSoundPlayAndWait: + delay = _soundManager->playSound(item.reference); + break; + case kSoundWait: + // Empty in the original. + break; + } + } + if (pause && delay > 0) { + warning("Sound pausing not yet tested. Pausing for %d", delay); + g_system->delayMillis(delay); + preparedToRun(); + } +} + +void MacVentureEngine::updateControls() { + selectControl(kNoCommand); + _gui->clearControls(); + toggleExits(); + resetVars(); +} + +void MacVentureEngine::resetVars() { + selectControl(kNoCommand); + _currentSelection.clear(); + _destObject = 0; + setDeltaPoint(Common::Point(0, 0)); + _cmdReady = false; +} + +void MacVentureEngine::unselectAll() { + while (!_currentSelection.empty()) { + unselectObject(_currentSelection.front()); + } +} + +void MacVentureEngine::selectObject(ObjID objID) { + if (!_currentSelection.empty()) { + if (findParentWindow(objID) != findParentWindow(_currentSelection[0])) { + // TODO: Needs further testing, but it doesn't seem necessary. + //unselectAll(); + } + } + if (findObjectInArray(objID, _currentSelection) == -1) { + _currentSelection.push_back(objID); + highlightExit(objID); + } +} + +void MacVentureEngine::unselectObject(ObjID objID) { + int idxCur = findObjectInArray(objID, _currentSelection); + if (idxCur != -1) { + _currentSelection.remove_at(idxCur); + highlightExit(objID); + } +} + + +void MacVentureEngine::updateExits() { + _gui->clearExits(); + _gui->unselectExits(); + + Common::Array<ObjID> exits = _world->getChildren(_world->getObjAttr(1, kAttrParentObject), true); + for (uint i = 0; i < exits.size(); i++) + _gui->updateExit(exits[i]); + +} + +int MacVentureEngine::findObjectInArray(ObjID objID, const Common::Array<ObjID> &list) { + // Find the object in the current selection + bool found = false; + uint i = 0; + while (i < list.size() && !found) { + if (list[i] == objID) { + found = true; + } else { + i++; + } + } + // HACK, should use iterator + return found ? i : -1; +} + +uint MacVentureEngine::getPrefixNdx(ObjID obj) { + return _world->getObjAttr(obj, kAttrPrefixes); +} + +Common::String MacVentureEngine::getPrefixString(uint flag, ObjID obj) { + uint ndx = getPrefixNdx(obj); + ndx = ((ndx) >> flag) & 3; + return _decodingNamingArticles->getString(ndx); +} + +Common::String MacVentureEngine::getNoun(ObjID ndx) { + return _decodingIndirectArticles->getString(ndx); +} + +void MacVentureEngine::highlightExit(ObjID objID) { + // TODO: It seems unnecessary since the GUI checks whether an object + // is selected, which includes exits. + warning("STUB: highlightExit"); +} + +void MacVentureEngine::selectPrimaryObject(ObjID objID) { + if (objID == _destObject) { + return; + } + int idx; + debugC(4, kMVDebugMain, "Select primary object (%d)", objID); + if (_destObject > 0 && + (idx = findObjectInArray(_destObject, _currentSelection)) != -1) { + unselectAll(); + } + _destObject = objID; + if (findObjectInArray(_destObject, _currentSelection) == -1) { + selectObject(_destObject); + } + + _cmdReady = true; +} + +void MacVentureEngine::focusObjectWindow(ObjID objID) { + if (objID) { + WindowReference win = getObjWindow(objID); + if (win) + _gui->bringToFront(win); + } +} + +void MacVentureEngine::openObject(ObjID objID) { + debugC(3, kMVDebugMain, "Open Object[%d] parent[%d] x[%d] y[%d]", + objID, + _world->getObjAttr(objID, kAttrParentObject), + _world->getObjAttr(objID, kAttrPosX), + _world->getObjAttr(objID, kAttrPosY)); + + if (getObjWindow(objID)) { + return; + } + if (objID == _world->getObjAttr(1, kAttrParentObject)) { + _gui->updateWindowInfo(kMainGameWindow, objID, _world->getChildren(objID, true)); + _gui->updateWindow(kMainGameWindow, _world->getObjAttr(objID, kAttrContainerOpen)); + updateExits(); + _gui->setWindowTitle(kMainGameWindow, _world->getText(objID, objID, objID)); // it ignores source and target in the original + } else { // Open inventory window + Common::Point p(_world->getObjAttr(objID, kAttrPosX), _world->getObjAttr(objID, kAttrPosY)); + WindowReference invID = _gui->createInventoryWindow(objID); + _gui->setWindowTitle(invID, _world->getText(objID, objID, objID)); + _gui->updateWindowInfo(invID, objID, _world->getChildren(objID, true)); + _gui->updateWindow(invID, _world->getObjAttr(objID, kAttrContainerOpen)); + } +} + +void MacVentureEngine::closeObject(ObjID objID) { + warning("closeObject: not fully implemented"); + _gui->tryCloseWindow(getObjWindow(objID)); + return; +} + +void MacVentureEngine::checkObject(QueuedObject old) { + bool hasChanged = false; + debugC(3, kMVDebugMain, "Check Object[%d] parent[%d] x[%d] y[%d]", + old.object, + old.parent, + old.x, + old.y); + ObjID id = old.object; + if (id == 1) { + if (old.parent != _world->getObjAttr(id, kAttrParentObject)) { + enqueueObject(kSetToPlayerParent, id); + } + if (old.offscreen != _world->getObjAttr(id, kAttrInvisible) || + old.invisible != _world->getObjAttr(id, kAttrUnclickable)) { + updateWindow(findParentWindow(id)); + } + } else if (old.parent != _world->getObjAttr(id, kAttrParentObject) || + old.x != _world->getObjAttr(id, kAttrPosX) || + old.y != _world->getObjAttr(id, kAttrPosY)) { + WindowReference oldWin = getObjWindow(old.parent); + if (oldWin) { + _gui->removeChild(oldWin, id); + hasChanged = true; + } + + WindowReference newWin = findParentWindow(id); + if (newWin) { + _gui->addChild(newWin, id); + hasChanged = true; + } + } else if (old.offscreen != _world->getObjAttr(id, kAttrInvisible) || + old.invisible != _world->getObjAttr(id, kAttrUnclickable)) { + updateWindow(findParentWindow(id)); + } + + if (_world->getObjAttr(id, kAttrIsExit)) { + if (hasChanged || + old.hidden != _world->getObjAttr(id, kAttrHiddenExit) || + old.exitx != _world->getObjAttr(id, kAttrExitX) || + old.exity != _world->getObjAttr(id, kAttrExitY)) + _gui->updateExit(id); + } + WindowReference win = getObjWindow(id); + ObjID cur = id; + ObjID root = _world->getObjAttr(1, kAttrParentObject); + while (cur != root) { + if (cur == 0 || !_world->getObjAttr(cur, kAttrContainerOpen)) { + break; + } + cur = _world->getObjAttr(cur, kAttrParentObject); + } + if (cur == root) { + if (win) { + return; + } + enqueueObject(kOpenWindow, id); //open + } else { + if (!win) { + return; + } + enqueueObject(kCloseWindow, id); //close + } + + // Update children + Common::Array<ObjID> children = _world->getChildren(id, true); + for (uint i = 0; i < children.size(); i++) { + enqueueObject(kUpdateObject, children[i]); + } +} + +void MacVentureEngine::reflectSwap(ObjID fromID, ObjID toID) { + WindowReference from = getObjWindow(fromID); + WindowReference to = getObjWindow(toID); + WindowReference tmp = to; + debugC(3, kMVDebugMain, "Swap Object[%d] to Object[%d], from win[%d] to win[%d] ", + fromID, toID, from, to); + + if (!to) { + tmp = from; + } + if (tmp) { + Common::String newTitle = _world->getText(toID, 0, 0); // Ignores src and targ in the original + _gui->setWindowTitle(tmp, newTitle); + _gui->updateWindowInfo(tmp, toID, _world->getChildren(toID, true)); + updateWindow(tmp); + } +} + +void MacVentureEngine::toggleExits() { + Common::Array<ObjID> exits = _currentSelection; + while (!exits.empty()) { + ObjID obj = exits.front(); + exits.remove_at(0); + highlightExit(obj); + updateWindow(findParentWindow(obj)); + } +} + +void MacVentureEngine::zoomObject(ObjID objID) { + warning("zoomObject: unimplemented"); +} + +bool MacVentureEngine::isObjEnqueued(ObjID objID) { + Common::Array<QueuedObject>::const_iterator it; + for (it = _objQueue.begin(); it != _objQueue.end(); it++) { + if ((*it).object == objID) { + return true; + } + } + return false; +} + +bool MacVentureEngine::isGameRunning() { + return (_gameState == kGameStateInit || _gameState == kGameStatePlaying); +} + +ControlAction MacVenture::MacVentureEngine::referenceToAction(ControlType id) { + switch (id) { + case MacVenture::kControlExitBox: + return kActivateObject;//?? Like this in the original + case MacVenture::kControlExamine: + return kExamine; + case MacVenture::kControlOpen: + return kOpen; + case MacVenture::kControlClose: + return kClose; + case MacVenture::kControlSpeak: + return kSpeak; + case MacVenture::kControlOperate: + return kOperate; + case MacVenture::kControlGo: + return kGo; + case MacVenture::kControlHit: + return kHit; + case MacVenture::kControlConsume: + return kConsume; + default: + return kNoCommand; + } +} + +// Data retrieval + +bool MacVentureEngine::isPaused() { + return _paused; +} + +bool MacVentureEngine::needsClickToContinue() { + return _clickToContinue; +} + +Common::String MacVentureEngine::getCommandsPausedString() const { + return Common::String("Click to continue"); +} + +Common::String MacVentureEngine::getFilePath(FilePathID id) const { + if (id <= 3) { // We don't want a file in the subdirectory + return _filenames->getString(id); + } else { // We want a game file + return _filenames->getString(3) + "/" + _filenames->getString(id); + } +} + +bool MacVentureEngine::isOldText() const { + return _oldTextEncoding; +} + +const HuffmanLists *MacVentureEngine::getDecodingHuffman() const { + return _textHuffman; +} + +uint32 MacVentureEngine::randBetween(uint32 min, uint32 max) { + return _rnd->getRandomNumber(max - min) + min; +} + +uint32 MacVentureEngine::getInvolvedObjects() { + // If there is no valid control selected, we return a number too big + // to be useful. There is no control that uses that many objects. + return (_selectedControl ? getGlobalSettings()._cmdArgCnts[_selectedControl - 1] : 3000); +} + +Common::Point MacVentureEngine::getObjPosition(ObjID objID) { + return Common::Point(_world->getObjAttr(objID, kAttrPosX), _world->getObjAttr(objID, kAttrPosY)); +} + +bool MacVentureEngine::isObjVisible(ObjID objID) { + return _world->getObjAttr(objID, kAttrInvisible) == 0; +} + +bool MacVentureEngine::isObjClickable(ObjID objID) { + return _world->getObjAttr(objID, kAttrUnclickable) == 0; +} + +bool MacVentureEngine::isObjSelected(ObjID objID) { + int idx = findObjectInArray(objID, _currentSelection); + return idx != -1; +} + +bool MacVentureEngine::isObjExit(ObjID objID) { + return _world->getObjAttr(objID, kAttrIsExit); +} + +bool MacVentureEngine::isHiddenExit(ObjID objID) { + return _world->getObjAttr(objID, kAttrHiddenExit); +} + +Common::Point MacVentureEngine::getObjExitPosition(ObjID objID) { + uint x = _world->getObjAttr(objID, kAttrExitX); + uint y = _world->getObjAttr(objID, kAttrExitY); + return Common::Point(x, y); +} + +ObjID MacVentureEngine::getParent(ObjID objID) { + return _world->getObjAttr(objID, kAttrParentObject); +} + +Common::Rect MacVentureEngine::getObjBounds(ObjID objID) { + Common::Point pos = getObjPosition(objID); + + WindowReference win = findParentWindow(objID); + if (win != kNoWindow) { // If it's not in a window YET, we don't really care about the border + BorderBounds bounds = borderBounds(_gui->getWindowData(win).type); // HACK + pos.x += bounds.leftOffset; + pos.y += bounds.topOffset; + } + Common::Point measures = _gui->getObjMeasures(objID); + uint w = measures.x; + uint h = measures.y; + return Common::Rect(pos.x, pos.y, pos.x + w, pos.y + h); +} + +uint MacVentureEngine::getOverlapPercent(ObjID one, ObjID other) { + // If it's not the same parent, there's 0 overlap + if (_world->getObjAttr(one, kAttrParentObject) != + _world->getObjAttr(other, kAttrParentObject)) + return 0; + + Common::Rect oneBounds = getObjBounds(one); + Common::Rect otherBounds = getObjBounds(other); + if (otherBounds.intersects(oneBounds) || + oneBounds.intersects(otherBounds)) { + uint areaOne = oneBounds.width() * oneBounds.height(); + uint areaOther = otherBounds.width() * otherBounds.height(); + return (areaOne != 0) ? (areaOther * 100 / areaOne) : 0; + } + return 0; +} + +WindowReference MacVentureEngine::getObjWindow(ObjID objID) { + return _gui->getObjWindow(objID); +} + +WindowReference MacVentureEngine::findParentWindow(ObjID objID) { + if (objID == 1) { + return kSelfWindow; + } + ObjID parent = _world->getObjAttr(objID, kAttrParentObject); + if (parent == 0) { + return kNoWindow; + } + return getObjWindow(parent); +} + +Common::Point MacVentureEngine::getDeltaPoint() { + return _deltaPoint; +} + +ObjID MacVentureEngine::getDestObject() { + return _destObject; +} + +ControlAction MacVentureEngine::getSelectedControl() { + return _selectedControl; +} + +// Data loading + +bool MacVentureEngine::loadGlobalSettings() { + Common::MacResIDArray resArray; + + if ((resArray = _resourceManager->getResIDArray(MKTAG('G', 'N', 'R', 'L'))).size() == 0) + return false; + + Common::SeekableReadStream *res; + res = _resourceManager->getResource(MKTAG('G', 'N', 'R', 'L'), kGlobalSettingsID); + if (res) { + _globalSettings = new GlobalSettings(); + _globalSettings->loadSettings(res); + delete res; + return true; + } + return false; +} + +bool MacVentureEngine::loadTextHuffman() { + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + + if ((resArray = _resourceManager->getResIDArray(MKTAG('G', 'N', 'R', 'L'))).size() == 0) + return false; + + res = _resourceManager->getResource(MKTAG('G', 'N', 'R', 'L'), kTextHuffmanTableID); + if (res) { + uint32 numEntries = res->readUint16BE(); + res->readUint16BE(); // Skip + + uint32 *masks = new uint32[numEntries]; + for (uint i = 0; i < numEntries - 1; i++) { + // For some reason there are one lass mask than entries + masks[i] = res->readUint16BE(); + } + + uint32 *lengths = new uint32[numEntries]; + for (uint i = 0; i < numEntries; i++) { + lengths[i] = res->readByte(); + } + + uint32 *values = new uint32[numEntries]; + for (uint i = 0; i < numEntries; i++) { + values[i] = res->readByte(); + } + + _textHuffman = new HuffmanLists(numEntries, lengths, masks, values); + debugC(4, kMVDebugMain, "Text is huffman-encoded"); + + delete res; + delete[] masks; + delete[] lengths; + delete[] values; + return true; + } + return false; +} + +// Global Settings +GlobalSettings::GlobalSettings() { +} + +GlobalSettings::~GlobalSettings() { + +} + +void GlobalSettings::loadSettings(Common::SeekableReadStream *dataStream) { + _numObjects = dataStream->readUint16BE(); + _numGlobals = dataStream->readUint16BE(); + _numCommands = dataStream->readUint16BE(); + _numAttributes = dataStream->readUint16BE(); + _numGroups = dataStream->readUint16BE(); + dataStream->readUint16BE(); // unknown + _invTop = dataStream->readUint16BE(); + _invLeft = dataStream->readUint16BE(); + _invWidth = dataStream->readUint16BE(); + _invHeight = dataStream->readUint16BE(); + _invOffsetY = dataStream->readUint16BE(); + _invOffsetX = dataStream->readSint16BE(); + _defaultFont = dataStream->readUint16BE(); + _defaultSize = dataStream->readUint16BE(); + + uint8 *attrIndices = new uint8[_numAttributes]; + dataStream->read(attrIndices, _numAttributes); + _attrIndices = Common::Array<uint8>(attrIndices, _numAttributes); + delete[] attrIndices; + + for (int i = 0; i < _numAttributes; i++) { + _attrMasks.push_back(dataStream->readUint16BE()); + } + + uint8 *attrShifts = new uint8[_numAttributes]; + dataStream->read(attrShifts, _numAttributes); + _attrShifts = Common::Array<uint8>(attrShifts, _numAttributes); + delete[] attrShifts; + + uint8 *cmdArgCnts = new uint8[_numCommands]; + dataStream->read(cmdArgCnts, _numCommands); + _cmdArgCnts = Common::Array<uint8>(cmdArgCnts, _numCommands); + delete[] cmdArgCnts; + + uint8 *commands = new uint8[_numCommands]; + dataStream->read(commands, _numCommands); + _commands = Common::Array<uint8>(commands, _numCommands); + delete[] commands; +} + +} // End of namespace MacVenture diff --git a/engines/macventure/macventure.h b/engines/macventure/macventure.h new file mode 100644 index 0000000000..6908bc6050 --- /dev/null +++ b/engines/macventure/macventure.h @@ -0,0 +1,369 @@ +/* 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 MACVENTURE_MACVENTURE_H +#define MACVENTURE_MACVENTURE_H + +#include "engines/engine.h" +#include "common/scummsys.h" +#include "common/system.h" +#include "common/debug.h" +#include "common/random.h" +#include "common/macresman.h" +#include "common/huffman.h" +#include "common/savefile.h" + +#include "gui/debugger.h" + +#include "macventure/debug.h" +#include "macventure/gui.h" +#include "macventure/world.h" +#include "macventure/hufflists.h" +#include "macventure/stringtable.h" +#include "macventure/script.h" +#include "macventure/controls.h" +#include "macventure/windows.h" +#include "macventure/sound.h" + +struct ADGameDescription; + +namespace MacVenture { + +class SaveFileManager; + +class Console; +class World; +class ScriptEngine; + +class SoundManager; + +typedef uint32 ObjID; + +// HACK, until I find a way to translate correctly +extern void toASCII(Common::String &str); + +enum { + kScreenWidth = 512, + kScreenHeight = 342 +}; + +enum { + kGlobalSettingsID = 0x80, + kDiplomaGeometryID = 0x81, + kTextHuffmanTableID = 0x83 +}; + +enum { + kSaveGameStrID = 0x82, + kDiplomaFilenameID = 0x83, + kClickToContinueTextID = 0x84, + kStartGameFilenameID = 0x85 +}; + +enum FilePathID { + kMCVID = 1, + kTitlePathID = 2, + kSubdirPathID = 3, + kObjectPathID = 4, + kFilterPathID = 5, + kTextPathID = 6, + kGraphicPathID = 7, + kSoundPathID = 8 +}; + + +class GlobalSettings { +public: + GlobalSettings(); + ~GlobalSettings(); + + void loadSettings(Common::SeekableReadStream *dataStream); + +// HACK MAybe this should be private, but the class is only here to handle +// memory allocation/deallocation +public: + uint16 _numObjects; // number of game objects defined + uint16 _numGlobals; // number of globals defined + uint16 _numCommands; // number of commands defined + uint16 _numAttributes; // number of attributes + uint16 _numGroups; // number of object groups + uint16 _invTop; // inventory window bounds + uint16 _invLeft; + uint16 _invHeight; + uint16 _invWidth; + uint16 _invOffsetY; // positioning offset for + uint16 _invOffsetX; // new inventory windows + uint16 _defaultFont; // default font + uint16 _defaultSize; // default font size + Common::Array<uint8> _attrIndices; // attribute indices into attribute table + Common::Array<uint16> _attrMasks; // attribute masks + Common::Array<uint8> _attrShifts; // attribute bit shifts + Common::Array<uint8> _cmdArgCnts; // command argument counts + Common::Array<uint8> _commands; // command buttons +}; + +enum GameState { + kGameStateInit, + kGameStatePlaying, + kGameStateWinnig, + kGameStateLosing, + kGameStateQuitting +}; + +enum ObjectQueueID { + kFocusWindow = 2, + kOpenWindow = 3, + kCloseWindow = 4, + kUpdateObject = 7, + kUpdateWindow = 8, + kSetToPlayerParent = 12, + kHightlightExits = 13, + kAnimateBack = 14 +}; + +struct QueuedObject { + ObjectQueueID id; + ObjID object; + ObjID parent; + uint x; + uint y; + uint exitx; + uint exity; + bool hidden; + bool offscreen; + bool invisible; + ObjID target; // For swapping +}; + +enum TextQueueID { + kTextNumber = 1, + kTextNewLine = 2, + kTextPlain = 3 +}; + +struct QueuedText { + TextQueueID id; + ObjID source; + ObjID destination; + ObjID asset; +}; + +enum SoundQueueID { + kSoundPlay = 1, + kSoundPlayAndWait = 2, + kSoundWait = 3 +}; + +struct QueuedSound { + SoundQueueID id; + ObjID reference; +}; + +class MacVentureEngine : public Engine { + +public: + MacVentureEngine(OSystem *syst, const ADGameDescription *gameDesc); + ~MacVentureEngine(); + + virtual bool hasFeature(EngineFeature f) const; + + virtual Common::Error run(); + + bool scummVMSaveLoadDialog(bool isSave); + bool canLoadGameStateCurrently(); + bool canSaveGameStateCurrently(); + virtual Common::Error loadGameState(int slot); + virtual Common::Error saveGameState(int slot, const Common::String &desc); + void newGame(); + void setInitialFlags(); + void setNewGameState(); + + void initDebugChannels(); + void reset(); + void resetInternals(); + void resetGui(); + void refreshScreen(); + + // datafiles.cpp + void loadDataBundle(); + Common::SeekableReadStream *getBorderFile(MVWindowType windowType, bool isActive); + + void requestQuit(); + void requestUnpause(); + void selectControl(ControlAction action); + void refreshReady(); + void preparedToRun(); + void gameChanged(); + void winGame(); + void loseGame(); + void clickToContinue(); + + void updateState(bool pause); + void revert(); + + void enqueueObject(ObjectQueueID type, ObjID objID, ObjID target = 0); + void enqueueText(TextQueueID type, ObjID target, ObjID source, ObjID text); + void enqueueSound(SoundQueueID type, ObjID target); + + void runObjQueue(); + void printTexts(); + void playSounds(bool pause); + + void handleObjectSelect(ObjID objID, WindowReference win, bool shiftPressed, bool isDoubleClick); + void handleObjectDrop(ObjID objID, Common::Point delta, ObjID newParent); + void setDeltaPoint(Common::Point newPos); + void focusObjWin(ObjID objID); + void updateWindow(WindowReference winID); + + bool showTextEntry(ObjID text, ObjID srcObj, ObjID destObj); + void setTextInput(Common::String content); + Common::String getUserInput(); + + // Data retrieval + Common::String getStartGameFileName(); + bool isPaused(); + bool needsClickToContinue(); + Common::String getCommandsPausedString() const; + const GlobalSettings &getGlobalSettings() const; + Common::String getFilePath(FilePathID id) const; + bool isOldText() const; + const HuffmanLists *getDecodingHuffman() const; + uint32 randBetween(uint32 min, uint32 max); + uint32 getInvolvedObjects(); + int findObjectInArray(ObjID objID, const Common::Array<ObjID> &list); + uint getPrefixNdx(ObjID obj); + Common::String getPrefixString(uint flag, ObjID obj); + Common::String getNoun(ObjID ndx); + + // Attributes consult + Common::Point getObjPosition(ObjID objID); + bool isObjVisible(ObjID objID); + bool isObjClickable(ObjID objID); + bool isObjSelected(ObjID objID); + bool isObjExit(ObjID objID); + bool isHiddenExit(ObjID objID); + Common::Point getObjExitPosition(ObjID objID); + ObjID getParent(ObjID objID); + + // Utils + ControlAction referenceToAction(ControlType id); + + // Encapsulation HACK + Common::Rect getObjBounds(ObjID objID); + uint getOverlapPercent(ObjID one, ObjID other); + + WindowReference getObjWindow(ObjID objID); + WindowReference findParentWindow(ObjID objID); + + Common::Point getDeltaPoint(); + ObjID getDestObject(); + ControlAction getSelectedControl(); + +private: + void processEvents(); + + bool runScriptEngine(); + void endGame(); + void updateControls(); + void resetVars(); + + void unselectAll(); + void selectObject(ObjID objID); + void unselectObject(ObjID objID); + void highlightExit(ObjID objID); + void selectPrimaryObject(ObjID objID); + void updateExits(); + + // Object queue methods + void focusObjectWindow(ObjID objID); + void openObject(ObjID objID); + void closeObject(ObjID objID); + void checkObject(QueuedObject objID); + void reflectSwap(ObjID fromID, ObjID toID); + void toggleExits(); + void zoomObject(ObjID objID); + + bool isObjEnqueued(ObjID obj); + + bool isGameRunning(); + + // Data loading + bool loadGlobalSettings(); + bool loadTextHuffman(); + + const char *getGameFileName() const; + +private: // Attributes + + const ADGameDescription *_gameDescription; + Common::RandomSource *_rnd; + + Common::MacResManager *_resourceManager; + + Console *_debugger; + Gui *_gui; + World *_world; + ScriptEngine *_scriptEngine; + + // String tables + StringTable *_filenames; + StringTable *_decodingDirectArticles; + StringTable *_decodingNamingArticles; + StringTable *_decodingIndirectArticles; + + SoundManager *_soundManager; + + Common::Archive *_dataBundle; + + // Engine state + GameState _gameState; + GlobalSettings *_globalSettings; + HuffmanLists *_textHuffman; + bool _oldTextEncoding; + bool _paused, _halted, _cmdReady, _prepared; + bool _haltedAtEnd, _haltedInSelection; + bool _gameChanged; + bool _clickToContinue; + + Common::Array<QueuedObject> _objQueue; + Common::Array<QueuedObject> _inQueue; + Common::Array<QueuedSound> _soundQueue; + Common::Array<QueuedText> _textQueue; + + // Selections + ObjID _destObject; + ControlAction _selectedControl; + Common::Array<ObjID> _currentSelection; + Common::Point _deltaPoint; + Common::String _userInput; + +}; + + +class Console : public GUI::Debugger { +public: + Console(MacVentureEngine *vm) {} + virtual ~Console(void) {} +}; +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/module.mk b/engines/macventure/module.mk new file mode 100644 index 0000000000..227eb41e28 --- /dev/null +++ b/engines/macventure/module.mk @@ -0,0 +1,31 @@ +MODULE := engines/macventure + +MODULE_OBJS := \ + container.o \ + controls.o \ + cursor.o \ + datafiles.o \ + detection.o \ + dialog.o \ + gui.o \ + image.o \ + macventure.o \ + prebuilt_dialogs.o \ + saveload.o \ + script.o \ + sound.o \ + text.o \ + windows.o \ + world.o + + +MODULE_DIRS += \ + engines/macventure + +# This module can be built as a plugin +ifeq ($(ENABLE_MACVENTURE), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/macventure/prebuilt_dialogs.cpp b/engines/macventure/prebuilt_dialogs.cpp new file mode 100644 index 0000000000..6137fed219 --- /dev/null +++ b/engines/macventure/prebuilt_dialogs.cpp @@ -0,0 +1,76 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "macventure/prebuilt_dialogs.h" + +namespace MacVenture { + +const PrebuiltDialog g_prebuiltDialogs[kPrebuiltDialogCount] = { + + {/* kSaveAsDialog */ + {0, 146, 456, 254}, + { + {kDEButton, "YES", kDASaveAs, 24, 68, 120, 22}, + {kDEButton, "NO", kDACloseDialog, 168, 68, 120, 22}, + {kDEButton, "CANCEL", kDACloseDialog, 312, 68, 120, 22}, + {kDEPlainText, "Save As...", kDANone, 100, 10, 340, 38}, + {kDETextInput, "", kDANone, 100, 30, 340, 20}, + {kDEEnd, "", kDANone, 0, 0, 0, 0} + } + }, + + { /* kSpeakDialog */ + {20, 92, 400, 200}, + { + {kDEButton, "OK", kDASubmit, 10, 70, 50, 20}, + {kDEButton, "CANCEL", kDACloseDialog, 96, 70, 50, 20}, + {kDEPlainText, "What would you like to say?", kDANone, 10, 10, 400, 20}, + {kDETextInput, "", kDANone, 10, 25, 350, 40}, + {kDEEnd, "", kDANone, 0, 0, 0, 0} + } + }, + + { /* kWinGameDialog */ + {20, 100, 320, 200}, + { + {kDEPlainText, "You Won!", kDANone, 20, 16, 280, 20}, + {kDEPlainText, "What do you want to do?", kDANone, 20, 30, 280, 20}, + {kDEButton, "New Game", kDANewGame, 20, 60, 70, 20}, + {kDEButton, "Load", kDALoadGame, 110, 60, 70, 20}, + {kDEButton, "Quit", kDAQuit, 200, 60, 70, 20}, + {kDEEnd, "", kDANone, 0, 0, 0, 0} + } + }, + + { /* kLoseGameDialog */ + {20, 100, 320, 200}, + { + {kDEPlainText, "You Died", kDANone, 20, 16, 280, 20}, + {kDEPlainText, "What do you want to do?", kDANone, 20, 30, 280, 20}, + {kDEButton, "New Game", kDANewGame, 20, 60, 70, 20}, + {kDEButton, "Load", kDALoadGame, 110, 60, 70, 20}, + {kDEButton, "Quit", kDAQuit, 200, 60, 70, 20}, + {kDEEnd, "", kDANone, 0, 0, 0, 0} + } + } +}; +} // End of namespace MacVenture diff --git a/engines/macventure/prebuilt_dialogs.h b/engines/macventure/prebuilt_dialogs.h new file mode 100644 index 0000000000..9cd8f4c1a4 --- /dev/null +++ b/engines/macventure/prebuilt_dialogs.h @@ -0,0 +1,87 @@ +/* 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 MACVENTURE_PREBUIT_DIALOGS_H +#define MACVENTURE_PREBUIT_DIALOGS_H + +#include "common/rect.h" + +namespace MacVenture { + +enum DialogAction { + kDANone, + kDACloseDialog, + kDASubmit, + kDASaveAs, + kDALoadGame, + kDAQuit, + kDANewGame +}; + +enum PrebuiltDialogs { + kSaveAsDialog = 0, //TODO: Currently unused, we are using ScummVM dialogs instead. + kSpeakDialog = 1, + kWinGameDialog = 2, + kLoseGameDialog = 3, + kPrebuiltDialogCount +}; + +enum PrebuiltElementType { + kDEPlainText, + kDEButton, + kDETextInput, + kDEEnd +}; + +struct PrebuiltDialogBounds { + uint left; + uint top; + uint right; + uint bottom; +}; + +struct PrebuiltDialogElement { + PrebuiltElementType type; + const char *title; + DialogAction action; + uint left; + uint top; + uint width; + uint height; +}; + +// Prebuilt dialogs +enum { + // HACK + kMaxPrebuiltDialogElements = 10 +}; + +struct PrebuiltDialog { + PrebuiltDialogBounds bounds; + PrebuiltDialogElement elements[kMaxPrebuiltDialogElements]; +}; + +extern const PrebuiltDialog g_prebuiltDialogs[]; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/saveload.cpp b/engines/macventure/saveload.cpp new file mode 100644 index 0000000000..53736302d2 --- /dev/null +++ b/engines/macventure/saveload.cpp @@ -0,0 +1,197 @@ +/* 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 "macventure/macventure.h" + +#include "common/error.h" +#include "common/savefile.h" +#include "engines/savestate.h" +#include "gui/saveload.h" +#include "graphics/thumbnail.h" + +namespace MacVenture { + +#define MACVENTURE_SAVE_HEADER MKTAG('M', 'V', 'S', 'S') // (M)ac(V)enture (S)cummVM (S)ave (0x4d565353, uint32) +#define MACVENTURE_SAVE_VERSION 1 //1 BYTE +#define MACVENTURE_DESC_LENGTH 4 //4 BYTE for the metadata length + +SaveStateDescriptor loadMetaData(Common::SeekableReadStream *s, int slot) { + // Metadata is stored at the end of the file + // |THUMBNAIL | + // | | + // |DESCSIZE| DESCRIPTION | + // |HEADER |VERSION|DESCLEN| + s->seek(-(5 + MACVENTURE_DESC_LENGTH), SEEK_END); + uint32 sig = s->readUint32BE(); + byte version = s->readByte(); + + SaveStateDescriptor desc(-1, ""); // init to an invalid save slot + + if (sig != MACVENTURE_SAVE_HEADER || version > MACVENTURE_SAVE_VERSION) + return desc; + + // Save is valid, set its slot number + desc.setSaveSlot(slot); + + // Depends on MACVENTURE_DESC_LENGTH + uint32 metaSize = s->readUint32BE(); + s->seek(-(5 + MACVENTURE_DESC_LENGTH + metaSize), SEEK_END); + + // Load the thumbnail + Graphics::Surface *thumb = Graphics::loadThumbnail(*s); + desc.setThumbnail(thumb); + + // Load the description + Common::String name; + uint32 descSize = s->readUint32BE(); + for (uint32 i = 0; i < descSize; ++i) { + name += s->readByte(); + } + desc.setDescription(name); + + // Load date + uint32 saveDate = s->readUint32LE(); + int day = (saveDate >> 24) & 0xFF; + int month = (saveDate >> 16) & 0xFF; + int year = saveDate & 0xFFFF; + desc.setSaveDate(year, month, day); + + uint16 saveTime = s->readUint16LE(); + int hour = (saveTime >> 8) & 0xFF; + int minutes = saveTime & 0xFF; + desc.setSaveTime(hour, minutes); + + // Load playtime + uint32 playTime = s->readUint32LE(); + desc.setPlayTime(playTime * 1000); + + return desc; +} + +uint saveCurrentDate(Common::OutSaveFile *file) { + TimeDate curTime; + g_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); + + file->writeUint32LE(saveDate); + file->writeUint16LE(saveTime); + + // Return the number of bytes occupied + return 6; +} + +uint savePlayTime(Common::OutSaveFile *file) { + uint32 playTime = g_engine->getTotalPlayTime() / 1000; + file->writeUint32LE(playTime); + // Return the number of bytes occupied + return 4; +} + +void writeMetaData(Common::OutSaveFile *file, Common::String desc) { + + // Write thumbnail + uint thumbSize = file->pos(); + Graphics::saveThumbnail(*file); + thumbSize = file->pos() - thumbSize; + + // Write description + file->writeUint32BE(desc.size()); + file->writeString(desc); + + uint dateSize = saveCurrentDate(file); + uint playTimeSize = savePlayTime(file); + + file->writeUint32BE(MACVENTURE_SAVE_HEADER); + file->writeByte(MACVENTURE_SAVE_VERSION); + file->writeUint32BE(4 + desc.size() + dateSize + playTimeSize + thumbSize); +} + +Common::Error MacVentureEngine::loadGameState(int slot) { + Common::String saveFileName = Common::String::format("%s.%03d", _targetName.c_str(), slot); + Common::InSaveFile *file; + if(!(file = getSaveFileManager()->openForLoading(saveFileName))) { + error("ENGINE: Missing savegame file %s", saveFileName.c_str()); + } + _world->loadGameFrom(file); + reset(); + return Common::kNoError; +} + +Common::Error MacVentureEngine::saveGameState(int slot, const Common::String &desc) { + Common::String saveFileName = Common::String::format("%s.%03d", _targetName.c_str(), slot); + Common::SaveFileManager *manager = getSaveFileManager(); + // HACK Get a real name! + Common::OutSaveFile *file = manager->openForSaving(saveFileName); + _world->saveGameInto(file); + writeMetaData(file, desc); + + file->finalize(); + if (file->err()) { + warning("Could not save '%s' correctly.", saveFileName.c_str()); + } + delete file; + return Common::kNoError; +} + +bool MacVentureEngine::scummVMSaveLoadDialog(bool isSave) { + if (!isSave) { + // do loading + GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(Common::String("Load game:"), Common::String("Load"), false); + int slot = dialog.runModalWithCurrentTarget(); + + if (slot < 0) + return true; + + return loadGameState(slot).getCode() == Common::kNoError; + } + + // do saving + GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(Common::String("Save game:"), Common::String("Save"), true); + int slot = dialog.runModalWithCurrentTarget(); + Common::String desc = dialog.getResultString(); + + if (desc.empty()) { + // create our own description for the saved game, the user didnt enter it + desc = dialog.createDefaultSaveDescription(slot); + } + + /* + if (desc.size() > (1 << MACVENTURE_DESC_LENGTH * 8) - 1) + desc = Common::String(desc.c_str(), (1 << MACVENTURE_DESC_LENGTH * 8) - 1); + */ + if (slot < 0) + return true; + + return saveGameState(slot, desc).getCode() == Common::kNoError; +} + +bool MacVentureEngine::canLoadGameStateCurrently() { + return true; +} + +bool MacVentureEngine::canSaveGameStateCurrently() { + return true; +} + +} // End of namespace MacVenture diff --git a/engines/macventure/script.cpp b/engines/macventure/script.cpp new file mode 100644 index 0000000000..d3731489e4 --- /dev/null +++ b/engines/macventure/script.cpp @@ -0,0 +1,1251 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/system.h" + +#include "macventure/macventure.h" +#include "macventure/script.h" +#include "macventure/world.h" +#include "macventure/container.h" + +namespace MacVenture { + +ScriptEngine::ScriptEngine(MacVentureEngine *engine, World *world) { + _engine = engine; + _world = world; + _scripts = new Container(_engine->getFilePath(kFilterPathID)); +} + +ScriptEngine::~ScriptEngine() { + if (_scripts) + delete _scripts; +} + +bool ScriptEngine::runControl(ControlAction action, ObjID source, ObjID destination, Common::Point delta) { + EngineFrame frame; + frame.action = action; + frame.src = source; + frame.dest = destination; + frame.x = delta.x; + frame.y = delta.y; + frame.haltedInSaves = false; + frame.haltedInFirst = false; + frame.haltedInFamily = false; + _frames.push_back(frame); + debugC(3, kMVDebugScript, "Stored frame %d, action: %d src: %d dest: %d point: (%d, %d)", + _frames.size() - 1, frame.action, frame.src, frame.dest, frame.x, frame.y); + + return resume(true); +} + +bool ScriptEngine::resume(bool execAll) { + debugC(3, kMVDebugScript, "Resume Script"); + while (_frames.size()) { + bool fail = execFrame(execAll); + if (fail) { + return true; + } + } + return false; +} + +void ScriptEngine::reset() { + _frames.clear(); +} + +bool ScriptEngine::execFrame(bool execAll) { + bool doFirst = execAll; + bool doFamily = false; + bool fail; + + EngineFrame *frame = &_frames.front(); + + // Do first dispatch script (script 0) + if (frame->haltedInFirst || doFirst) { // We were stuck or it's the first time + frame->haltedInFirst = false; + if (doFirst) { + fail = loadScript(frame, 0); + } else { + fail = resumeFunc(frame); + } + if (fail) { + frame->haltedInFirst = true; + _engine->preparedToRun(); + return true; + } + doFamily = true; + frame->familyIdx = 0; + } + + // Do scripts in the family of player (ObjID 1) + if (frame->haltedInFamily || doFamily) { // We have to do the family or we were stuck here + frame->haltedInFamily = false; + Common::Array<ObjID> family = _world->getFamily(_world->getObjAttr(1, kAttrParentObject), false); + for (uint32 i = frame->familyIdx; i < family.size(); i++) { + if (doFamily) { + fail = loadScript(frame, family[i]); + } else { + fail = resumeFunc(frame); + } + if (fail) { // We are stuck, so we don't shift the frame + frame->haltedInFamily = true; + frame->familyIdx = i; + _engine->preparedToRun(); + return true; + } + doFamily = true; + } + } + + // Halted in saves + if (frame->haltedInSaves) { + frame->haltedInSaves = false; + if (resumeFunc(frame)) { + frame->haltedInSaves = true; + _engine->preparedToRun(); + return true; + } + } + + int highest = 0; + uint localHigh = 0; + do { // Saved function calls + highest = 0; + for (uint i = 0; i < frame->saves.size(); i++) { + if (highest < frame->saves[i].rank) { + highest = frame->saves[i].rank; + localHigh = i; + } + } + if (highest) { + frame->saves[localHigh].rank = 0; + if (loadScript(frame, frame->saves[localHigh].func)) { + frame->haltedInSaves = true; + _engine->preparedToRun(); + return true; + } + } + } while (highest); + + _frames.pop_front(); + return false; +} + +bool ScriptEngine::loadScript(EngineFrame *frame, uint32 scriptID) { + if (_scripts->getItemByteSize(scriptID) > 0) { + debugC(2, kMVDebugScript, "Loading function %d", scriptID); + // Insert the new script at the front + frame->scripts.push_front(ScriptAsset(scriptID, _scripts)); + return runFunc(frame); + } + return false; +} + +bool ScriptEngine::resumeFunc(EngineFrame *frame) { + bool fail = runFunc(frame); + if (fail) { + return fail; + } + frame->scripts.pop_front(); + if (frame->scripts.size()) + return resumeFunc(frame); + return false; +} + +bool ScriptEngine::runFunc(EngineFrame *frame) { + ScriptAsset &script = frame->scripts.front(); + EngineState *state = &frame->state; + byte op; + while (script.hasNext()) { + op = script.fetch(); + debugC(4, kMVDebugScript, "Running operation %d", op); + if (!(op & 0x80)) { + state->push(op); + } else { + switch (op) { + case 0x80: //get attribute + op80GATT(state, frame); + break; + case 0x81: //set attribute + op81SATT(state, frame); + break; + case 0x82: //sum children attribute + op82SUCH(state, frame); + break; + case 0x83: //push selected control + op83PUCT(state, frame); + break; + case 0x84: //push selected object + op84PUOB(state, frame); + break; + case 0x85: //push target + op85PUTA(state, frame); + break; + case 0x86: //push deltax + op86PUDX(state, frame); + break; + case 0x87: //push deltay + op87PUDY(state, frame); + break; + case 0x88: //push immediate.b + op88PUIB(state, frame, &script); + break; + case 0x89: //push immediate + op89PUI(state, frame, &script); + break; + case 0x8a: //get global + op8aGGLO(state, frame); + break; + case 0x8b: //set global + op8bSGLO(state, frame); + break; + case 0x8c: //random + op8cRAND(state, frame); + break; + case 0x8d: //copy + op8dCOPY(state, frame); + break; + case 0x8e: //copyn + op8eCOPYN(state, frame); + break; + case 0x8f: //swap + op8fSWAP(state, frame); + break; + case 0x90: //swapn + op90SWAPN(state, frame); + break; + case 0x91: //pop + op91POP(state, frame); + break; + case 0x92: //copy+1 + op92COPYP(state, frame); + break; + case 0x93: //copy+n + op93COPYPN(state, frame); + break; + case 0x94: //shuffle + op94SHUFF(state, frame); + break; + case 0x95: //sort + op95SORT(state, frame); + break; + case 0x96: //clear stack + op96CLEAR(state, frame); + break; + case 0x97: //get stack size + op97SIZE(state, frame); + break; + case 0x98: //add + op98ADD(state, frame); + break; + case 0x99: //subtract + op99SUB(state, frame); + break; + case 0x9a: //multiply + op9aMUL(state, frame); + break; + case 0x9b: //divide + op9bDIV(state, frame); + break; + case 0x9c: //mod + op9cMOD(state, frame); + break; + case 0x9d: //divmod + op9dDMOD(state, frame); + break; + case 0x9e: //abs + op9eABS(state, frame); + break; + case 0x9f: //neg + op9fNEG(state, frame); + break; + case 0xa0: //and + opa0AND(state, frame); + break; + case 0xa1: //or + opa1OR(state, frame); + break; + case 0xa2: //xor + opa2XOR(state, frame); + break; + case 0xa3: //not + opa3NOT(state, frame); + break; + case 0xa4: //logical and + opa4LAND(state, frame); + break; + case 0xa5: //logical or + opa5LOR(state, frame); + break; + case 0xa6: //logical xor + opa6LXOR(state, frame); + break; + case 0xa7: //logical not + opa7LNOT(state, frame); + break; + case 0xa8: //gt? unsigned + opa8GTU(state, frame); + break; + case 0xa9: //lt? unsigned + opa9LTU(state, frame); + break; + case 0xaa: //gt? signed + opaaGTS(state, frame); + break; + case 0xab: //lt? signed + opabLTS(state, frame); + break; + case 0xac: //eq? + opacEQ(state, frame); + break; + case 0xad: //eq string? + opadEQS(state, frame); + break; + case 0xae: //contains + opaeCONT(state, frame); + break; + case 0xaf: //contains word + opafCONTW(state, frame); + break; + case 0xb0: //bra + opb0BRA(state, frame, &script); + break; + case 0xb1: //bra.b + opb1BRAB(state, frame, &script); + break; + case 0xb2: //beq + opb2BEQ(state, frame, &script); + break; + case 0xb3: //beq.b + opb3BEQB(state, frame, &script); + break; + case 0xb4: //bne + opb4BNE(state, frame, &script); + break; + case 0xb5: //bne.b + opb5BNEB(state, frame, &script); + break; + case 0xb6: //call later + opb6CLAT(state, frame); + break; + case 0xb7: //cancel call + opb7CCA(state, frame); + break; + case 0xb8: //cancel low priority + opb8CLOW(state, frame); + break; + case 0xb9: //cancel high priority + opb9CHI(state, frame); + break; + case 0xba: //cancel priority range + opbaCRAN(state, frame); + break; + case 0xbb: //fork + if (opbbFORK(state, frame)) + return true; + break; + case 0xbc: //call + if (opbcCALL(state, frame, script)) + return true; + break; + case 0xbd: //focus object + opbdFOOB(state, frame); + break; + case 0xbe: //swap objects + opbeSWOB(state, frame); + break; + case 0xbf: //snap object + opbfSNOB(state, frame); + break; + case 0xc0: //toggle exits + opc0TEXI(state, frame); + break; + case 0xc1: //print text + opc1PTXT(state, frame); + break; + case 0xc2: //print newline + opc2PNEW(state, frame); + break; + case 0xc3: //print text+nl + opc3PTNE(state, frame); + break; + case 0xc4: //print nl+text+nl + opc4PNTN(state, frame); + break; + case 0xc5: //print number + opc5PNUM(state, frame); + break; + case 0xc6: //push 2 + opc6P2(state, frame); + break; + case 0xc7: //play sound in background + opc7PLBG(state, frame); + break; + case 0xc8: //play sound and wait + opc8PLAW(state, frame); + break; + case 0xc9: //wait for sound to finish? + opc9WAIT(state, frame); + break; + case 0xca: //get current time + opcaTIME(state, frame); + break; + case 0xcb: //get current day + opcbDAY(state, frame); + break; + case 0xcc: //get children + opccCHLD(state, frame); + break; + case 0xcd: //get num children + opcdNCHLD(state, frame); + break; + case 0xce: //get engine version + opceVERS(state, frame); + break; + case 0xcf: //push scenario number + opcfPSCE(state, frame); + break; + case 0xd0: //push 1 + opd0P1(state, frame); + break; + case 0xd1: //get object dimensions + opd1GOBD(state, frame); + break; + case 0xd2: //get overlap percent + opd2GOVP(state, frame); + break; + case 0xd3: //capture children + opd3CAPC(state, frame); + break; + case 0xd4: //release children + opd4RELC(state, frame); + break; + case 0xd5: //show speech dialog + opd5DLOG(state, frame); + return true; + case 0xd6: //activate command + opd6ACMD(state, frame); + break; + case 0xd7: //lose game + opd7LOSE(state, frame); + break; + case 0xd8: //win game + opd8WIN(state, frame); + break; + case 0xd9: //sleep + opd9SLEEP(state, frame); + return true; + case 0xda: //click to continue + opdaCLICK(state, frame); + return true; + case 0xdb: //run queue + opdbROBQ(state, frame); + break; + case 0xdc: //run sound queue + opdcRSQ(state, frame); + break; + case 0xdd: //run text queue + opddRTQ(state, frame); + break; + case 0xde: //update screen + opdeUPSC(state, frame); + break; + case 0xdf: //flash main window + opdfFMAI(state, frame); + return true; + case 0xe0: //cache graphic and object + ope0CHGR(state, frame); + break; + case 0xe1: //cache sound + ope1CHSO(state, frame); + break; + case 0xe2: //muldiv + ope2MDIV(state, frame); + break; + case 0xe3: //update object + ope3UPOB(state, frame); + break; + case 0xe4: //currently playing event? + ope4PLEV(state, frame); + break; + case 0xe5: //wait for event to finish + ope5WEV(state, frame); + break; + case 0xe6: //get fibonacci (joke) + ope6GFIB(state, frame); + break; + case 0xe7: //calc fibonacci + ope7CFIB(state, frame); + break; + default: + op00NOOP(op); + } + } + } + return false; +} + +int16 ScriptEngine::neg16(int16 val) { + if (val & 0x8000) + val = -((val ^ 0xFFFF) + 1); + return val; +} + +int16 ScriptEngine::neg8(int16 val) { + if (val & 0x80) + val = -((val ^ 0xff) + 1); + return val; +} + +int16 ScriptEngine::sumChildrenAttr(int16 obj, int16 attr, bool recursive) { + int16 sum = 0; + Common::Array<ObjID> children = _world->getChildren(obj, recursive); + for (Common::Array<ObjID>::const_iterator it = children.begin(); it != children.end(); it++) { + sum += _world->getObjAttr(*it, attr); + } + return sum; +} + +void ScriptEngine::ensureNonzeroDivisor(int16 divisor, byte opcode) { + // TODO Untested, since that occassion rarely comes up. + if (divisor == 0) { + error("SCRIPT: Attempt to divide by 0 in operation %x", opcode); + } +} + +void MacVenture::ScriptEngine::op80GATT(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + int16 attr = state->pop(); + state->push(_world->getObjAttr(obj, attr)); +} + +void ScriptEngine::op81SATT(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + int16 attr = state->pop(); + int16 val = neg16(state->pop()); + _world->setObjAttr(obj, attr, val); +} + +void ScriptEngine::op82SUCH(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + int16 attr = state->pop(); + int16 recursive = neg16(state->pop()); + state->push(sumChildrenAttr(obj, attr, recursive)); +} + +void ScriptEngine::op83PUCT(EngineState *state, EngineFrame *frame) { + state->push(frame->action); +} + +void ScriptEngine::op84PUOB(EngineState *state, EngineFrame *frame) { + state->push(frame->src); +} + +void ScriptEngine::op85PUTA(EngineState *state, EngineFrame *frame) { + state->push(frame->dest); +} + +void ScriptEngine::op86PUDX(EngineState *state, EngineFrame *frame) { + state->push(frame->x); +} + +void ScriptEngine::op87PUDY(EngineState *state, EngineFrame *frame) { + state->push(frame->y); +} + +void ScriptEngine::op88PUIB(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + state->push(script->fetch()); +} + +void ScriptEngine::op89PUI(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + int16 val = script->fetch(); + val <<= 8; + val = val | script->fetch(); + state->push(val); +} + +void ScriptEngine::op8aGGLO(EngineState *state, EngineFrame *frame) { + int16 idx = state->pop(); + state->push(_world->getGlobal(idx)); +} + +void ScriptEngine::op8bSGLO(EngineState *state, EngineFrame *frame) { + int16 idx = state->pop(); + int16 val = neg16(state->pop()); + _world->setGlobal(idx, val); + _engine->gameChanged(); +} + +void ScriptEngine::op8cRAND(EngineState *state, EngineFrame *frame) { + int16 max = state->pop(); + state->push(_engine->randBetween(0, max)); +} + +void ScriptEngine::op8dCOPY(EngineState *state, EngineFrame *frame) { + int16 val = state->pop(); + state->push(val); + state->push(val); +} + +void ScriptEngine::op8eCOPYN(EngineState *state, EngineFrame *frame) { + int16 n = state->pop(); + int16 offs = n - 1; + int16 val; + while (n) { + val = state->peek(offs); + state->push(val); + n--; + } +} + +void ScriptEngine::op8fSWAP(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(b); + state->push(a); +} + +void ScriptEngine::op90SWAPN(EngineState *state, EngineFrame *frame) { + int16 idx = state->pop(); + int16 a = state->peek(idx); + int16 b = state->peek(0); + state->poke(idx, b); + state->poke(0, a); +} + +void ScriptEngine::op91POP(EngineState *state, EngineFrame *frame) { + state->pop(); +} + +void ScriptEngine::op92COPYP(EngineState *state, EngineFrame *frame) { + int16 val = state->peek(1); + state->push(val); +} + +void ScriptEngine::op93COPYPN(EngineState *state, EngineFrame *frame) { + int16 idx = state->pop(); + int16 val = state->peek(idx); + state->push(val); +} + +void ScriptEngine::op94SHUFF(EngineState *state, EngineFrame *frame) { + int16 a = state->pop(); + int16 b = state->pop(); + int16 c = state->pop(); + state->push(a); + state->push(c); + state->push(b); +} + +void ScriptEngine::op95SORT(EngineState *state, EngineFrame *frame) { + int16 step = neg16(state->pop()); + int16 num = neg16(state->pop()); + step %= num; + if (step < 0) { + step += num; + } + int16 end = 0; + int16 start = 0; + for (int16 i = 1; i < num; i++) { + start += step; + if (start >= num) { + start -= num; + } + if (start == end) { + end++; + start = end; + } else { + int16 a = state->peek(end); + int16 b = state->peek(start); + state->poke(end, b); + state->poke(start, a); + } + } +} + +void ScriptEngine::op96CLEAR(EngineState *state, EngineFrame *frame) { + state->clear(); +} + +void ScriptEngine::op97SIZE(EngineState *state, EngineFrame *frame) { + state->push(state->size()); +} + +void ScriptEngine::op98ADD(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(a + b); +} + +void ScriptEngine::op99SUB(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(a - b); +} + +void ScriptEngine::op9aMUL(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(a * b); +} + +void ScriptEngine::op9bDIV(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + ensureNonzeroDivisor(b, 0x9b); + state->push(a / b); +} + +void ScriptEngine::op9cMOD(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(a % b); +} + +void ScriptEngine::op9dDMOD(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + ensureNonzeroDivisor(b, 0x9d); + state->push(a % b); + state->push(a / b); +} + +void ScriptEngine::op9eABS(EngineState *state, EngineFrame *frame) { + int16 val = neg16(state->pop()); + if (val < 0) { + val = -val; + } + state->push(val); +} + +void ScriptEngine::op9fNEG(EngineState *state, EngineFrame *frame) { + int16 val = -neg16(state->pop()); + state->push(val); +} + +void ScriptEngine::opa0AND(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(a & b); +} + +void ScriptEngine::opa1OR(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(a | b); +} + +void ScriptEngine::opa2XOR(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(a ^ b); +} + +void ScriptEngine::opa3NOT(EngineState *state, EngineFrame *frame) { + int16 a = state->pop(); + state->push(a ^ 0xFFFF); +} + +void ScriptEngine::opa4LAND(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push((a && b) ? 0xFFFF : 0); +} + +void ScriptEngine::opa5LOR(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push((a || b) ? 0xFFFF : 0); +} + +void ScriptEngine::opa6LXOR(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push((!a != !b) ? 0xFFFF : 0); +} + +void ScriptEngine::opa7LNOT(EngineState *state, EngineFrame *frame) { + int16 a = state->pop(); + state->push((a == 0) ? 0xFFFF : 0); +} + +void ScriptEngine::opa8GTU(EngineState *state, EngineFrame *frame) { + uint16 b = state->pop(); + uint16 a = state->pop(); + state->push((a > b) ? 0xFFFF : 0); +} + +void ScriptEngine::opa9LTU(EngineState *state, EngineFrame *frame) { + uint16 b = state->pop(); + uint16 a = state->pop(); + state->push((a < b) ? 0xFFFF : 0); +} + +void ScriptEngine::opaaGTS(EngineState *state, EngineFrame *frame) { + // HACK !!! May not need the neg16, since it's already a signed int!! + int16 b = neg16(state->pop()); + int16 a = neg16(state->pop()); + state->push((a > b) ? 0xFFFF : 0); +} + +void ScriptEngine::opabLTS(EngineState *state, EngineFrame *frame) { + // HACK !!! May not need the neg16, since it's already a signed int!! + int16 b = neg16(state->pop()); + int16 a = neg16(state->pop()); + state->push((a < b) ? 0xFFFF : 0); +} + +void ScriptEngine::opacEQ(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push((a == b) ? 0xFFFF : 0); +} + +void ScriptEngine::opadEQS(EngineState *state, EngineFrame *frame) { + Common::String b = _world->getText(state->pop(), 0, 0); // HACK, these destinations might be wrong + Common::String a = _world->getText(state->pop(), 0, 0); + state->push((a == b) ? 1 : 0); +} + +void ScriptEngine::opaeCONT(EngineState *state, EngineFrame *frame) { + Common::String needle = _world->getText(state->pop(), 0, 0); + Common::String haystack = _world->getText(state->pop(), 0, 0); + haystack.toLowercase(); + state->push(haystack.contains(needle) ? 1 : 0); +} + +void ScriptEngine::opafCONTW(EngineState *state, EngineFrame *frame) { + Common::String needle = _world->getText(state->pop(), 0, 0); + Common::String haystack = _world->getText(state->pop(), 0, 0); + haystack.toLowercase(); + state->push(haystack.contains(needle) ? 1 : 0); +} + +void ScriptEngine::opb0BRA(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + int16 val = script->fetch(); + val <<= 8; + val = val | script->fetch(); + val = neg16(val); + script->branch(val); +} + +void ScriptEngine::opb1BRAB(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + int16 val = script->fetch(); + val = neg8(val); + script->branch(val); +} + +void ScriptEngine::opb2BEQ(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + int16 val = script->fetch(); + val <<= 8; + val = val | script->fetch(); + val = neg16(val); + int16 b = state->pop(); + if (b != 0) { + script->branch(val); + } +} + +void ScriptEngine::opb3BEQB(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + int16 val = script->fetch(); + val = neg8(val); + int16 b = state->pop(); + if (b != 0) { + script->branch(val); + } +} + +void ScriptEngine::opb4BNE(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + int16 val = script->fetch(); + val <<= 8; + val = val | script->fetch(); + val = neg16(val); + int16 b = state->pop(); + if (b == 0) { + script->branch(val); + } +} + +void ScriptEngine::opb5BNEB(EngineState *state, EngineFrame *frame, ScriptAsset *script) { + int16 val = script->fetch(); + val = neg8(val); + int16 b = state->pop(); + if (b == 0) { + script->branch(val); + } +} + +void ScriptEngine::opb6CLAT(EngineState *state, EngineFrame *frame) { + int16 rank = state->pop(); + int16 func = state->pop(); + frame->saves.push_back(FunCall(func, rank)); +} + +void ScriptEngine::opb7CCA(EngineState *state, EngineFrame *frame) { + int16 func = state->pop(); + for (uint i = 0; i < frame->saves.size(); i++) { + if (frame->saves[i].func == func) + frame->saves[i].rank = 0; + } +} + +void ScriptEngine::opb8CLOW(EngineState *state, EngineFrame *frame) { + int16 hi = state->pop(); + for (uint i = 0; i < frame->saves.size(); i++) + if (frame->saves[i].rank <= hi) + frame->saves[i].rank = 0; +} + +void ScriptEngine::opb9CHI(EngineState *state, EngineFrame *frame) { + int16 lo = state->pop(); + for (uint i = 0; i < frame->saves.size(); i++) { + if (frame->saves[i].rank >= lo) { + frame->saves[i].rank = 0; + } + } +} + +void ScriptEngine::opbaCRAN(EngineState *state, EngineFrame *frame) { + int16 hi = state->pop(); + int16 lo = state->pop(); + for (uint i = 0; i < frame->saves.size(); i++) { + if (frame->saves[i].rank >= lo && + frame->saves[i].rank <= hi) { + frame->saves[i].rank = 0; + } + } +} + +bool ScriptEngine::opbbFORK(EngineState *state, EngineFrame *frame) { + EngineFrame newframe; + newframe.action = (ControlAction)state->pop(); + newframe.src = state->pop(); + newframe.dest = state->pop(); + newframe.x = state->pop(); + newframe.y = state->pop(); + newframe.haltedInFamily = false; + newframe.haltedInFirst = false; + newframe.haltedInSaves = false; + _frames.push_front(newframe); + if (execFrame(true)) { + return true; + } + return false; +} + +bool ScriptEngine::opbcCALL(EngineState *state, EngineFrame *frame, ScriptAsset &script) { + int16 id = state->pop(); + ScriptAsset newfun = ScriptAsset(id, _scripts); + ScriptAsset current = script; + debugC(2, kMVDebugScript, "Call function: %d", id); + if (loadScript(frame, id)) + return true; + frame->scripts.pop_front(); + script = frame->scripts.front(); + debugC(2, kMVDebugScript, "Return from fuction %d", id); + return false; +} + +void ScriptEngine::opbdFOOB(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + _engine->enqueueObject(kFocusWindow, obj); +} + +void ScriptEngine::opbeSWOB(EngineState *state, EngineFrame *frame) { + ObjID from = state->pop(); + ObjID to = state->pop(); + _engine->enqueueObject(kUpdateWindow, from, to); + _world->setObjAttr(to, kAttrContainerOpen, _world->getObjAttr(from, 6)); + _world->setObjAttr(from, kAttrContainerOpen, 0); + Common::Array<ObjID> children = _world->getChildren(from, true); + for (uint i = 0; i < children.size(); i++) { + _world->setObjAttr(children[i], 0, to); + } +} + +void ScriptEngine::opbfSNOB(EngineState *state, EngineFrame *frame) { + _engine->enqueueObject(kAnimateBack, frame->src); +} + +void ScriptEngine::opc0TEXI(EngineState *state, EngineFrame *frame) { + _engine->enqueueObject(kHightlightExits, 0); +} + +void ScriptEngine::opc1PTXT(EngineState *state, EngineFrame *frame) { + int16 tid = state->pop(); + _engine->enqueueText(kTextPlain, frame->dest, frame->src, tid); +} + +void ScriptEngine::opc2PNEW(EngineState *state, EngineFrame *frame) { + _engine->enqueueText(kTextNewLine, frame->dest, frame->src, 0); +} + +void ScriptEngine::opc3PTNE(EngineState *state, EngineFrame *frame) { + int16 tid = state->pop(); + _engine->enqueueText(kTextPlain, frame->dest, frame->src, tid); + _engine->enqueueText(kTextNewLine, frame->dest, frame->src, 0); +} + +void ScriptEngine::opc4PNTN(EngineState *state, EngineFrame *frame) { + int16 tid = state->pop(); + _engine->enqueueText(kTextNewLine, frame->dest, frame->src, 0); + _engine->enqueueText(kTextPlain, frame->dest, frame->src, tid); + _engine->enqueueText(kTextNewLine, frame->dest, frame->src, 0); +} + +void ScriptEngine::opc5PNUM(EngineState *state, EngineFrame *frame) { + int16 tid = state->pop(); + _engine->enqueueText(kTextNumber, frame->dest, frame->src, tid); +} + +void ScriptEngine::opc6P2(EngineState *state, EngineFrame *frame) { + state->push(2); +} + +void ScriptEngine::opc7PLBG(EngineState *state, EngineFrame *frame) { + int16 target = state->pop(); + _engine->enqueueSound(kSoundPlay, target); +} + +void ScriptEngine::opc8PLAW(EngineState *state, EngineFrame *frame) { + int16 target = state->pop(); + _engine->enqueueSound(kSoundPlayAndWait, target); +} + +void ScriptEngine::opc9WAIT(EngineState *state, EngineFrame *frame) { + _engine->enqueueSound(kSoundWait, 0); +} + +void ScriptEngine::opcaTIME(EngineState *state, EngineFrame *frame) { + for (uint i = 0; i < 3; i++) {// We skip year, month and date + state->push(0x00); + } + + uint32 totalPlayTime = _engine->getTotalPlayTime() / 1000; // In seconds + int16 hours = totalPlayTime / 3600; + totalPlayTime %= 3600; + state->push(hours); + int16 minutes = totalPlayTime / 60; + totalPlayTime %= 60; + state->push(minutes); + state->push(totalPlayTime); + debugC(2, kMVDebugScript, "Saved time: h[%d] m[%d] s[%d]", hours, minutes, totalPlayTime); +} + +void ScriptEngine::opcbDAY(EngineState *state, EngineFrame *frame) { + // Probaby irrelevant, so we push Day [9] + state->push(9); +} + +void ScriptEngine::opccCHLD(EngineState *state, EngineFrame *frame) { + bool recursive = state->pop() != 0; + int16 obj = state->pop(); + Common::Array<ObjID> children = _world->getChildren(obj, recursive); + for (Common::Array<ObjID>::const_iterator it = children.begin(); it != children.end(); it++) { + state->push(*it); + } + state->push(children.size()); +} + +void ScriptEngine::opcdNCHLD(EngineState *state, EngineFrame *frame) { + bool recursive = state->pop() != 0; + int16 obj = state->pop(); + Common::Array<ObjID> children = _world->getChildren(obj, recursive); + state->push(children.size()); +} + +void ScriptEngine::opceVERS(EngineState *state, EngineFrame *frame) { + state->push(86); // Engine version is irrelevant. Like this in the original. +} + +void ScriptEngine::opcfPSCE(EngineState *state, EngineFrame *frame) { + state->push(0); // Any value greater than 0 indicates "Release". +} + +void ScriptEngine::opd0P1(EngineState *state, EngineFrame *frame) { + state->push(1); +} + +void ScriptEngine::opd1GOBD(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + Common::Rect bounds = _engine->getObjBounds(obj); + state->push(bounds.width()); + state->push(bounds.height()); +} + +void ScriptEngine::opd2GOVP(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + state->push(_engine->getOverlapPercent(b, a)); +} + +void ScriptEngine::opd3CAPC(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + _world->captureChildren(obj); +} + +void ScriptEngine::opd4RELC(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + _world->releaseChildren(obj); +} + +void ScriptEngine::opd5DLOG(EngineState *state, EngineFrame *frame) { + int16 txt = state->pop(); + if (_engine->showTextEntry(txt, frame->src, frame->dest)) { + state->push(0xFF); + } else { + state->push(0x00); + } +} + +void ScriptEngine::opd6ACMD(EngineState *state, EngineFrame *frame) { + _engine->selectControl((ControlAction)state->pop()); +} + +void ScriptEngine::opd7LOSE(EngineState *state, EngineFrame *frame) { + _engine->loseGame(); +} + +void ScriptEngine::opd8WIN(EngineState *state, EngineFrame *frame) { + _engine->winGame(); +} + +void ScriptEngine::opd9SLEEP(EngineState *state, EngineFrame *frame) { + int16 ticks = state->pop(); + g_system->delayMillis((ticks / 60) * 1000); + _engine->preparedToRun(); +} + +void ScriptEngine::opdaCLICK(EngineState *state, EngineFrame *frame) { + _engine->updateState(false); + _engine->clickToContinue(); +} + +void ScriptEngine::opdbROBQ(EngineState *state, EngineFrame *frame) { + _engine->runObjQueue(); +} + +void ScriptEngine::opdcRSQ(EngineState *state, EngineFrame *frame) { + _engine->playSounds(true); +} + +void ScriptEngine::opddRTQ(EngineState *state, EngineFrame *frame) { + _engine->printTexts(); +} + +void ScriptEngine::opdeUPSC(EngineState *state, EngineFrame *frame) { + _engine->updateState(true); +} + +void ScriptEngine::opdfFMAI(EngineState *state, EngineFrame *frame) { + int16 ticks = state->pop(); + g_system->delayMillis((ticks / 60) * 1000); + _engine->revert(); +} + +void ScriptEngine::ope0CHGR(EngineState *state, EngineFrame *frame) { + state->pop(); +} + +void ScriptEngine::ope1CHSO(EngineState *state, EngineFrame *frame) { + state->pop(); +} + +void ScriptEngine::ope2MDIV(EngineState *state, EngineFrame *frame) { + int16 b = state->pop(); + int16 a = state->pop(); + a *= b; + int16 c = state->pop(); + ensureNonzeroDivisor(c, 0xe2); + a /= c; + state->push(a); +} + +void ScriptEngine::ope3UPOB(EngineState *state, EngineFrame *frame) { + int16 obj = state->pop(); + _world->updateObj(obj); +} + +void ScriptEngine::ope4PLEV(EngineState *state, EngineFrame *frame) { + state->push(0); +} + +void ScriptEngine::ope5WEV(EngineState *state, EngineFrame *frame) { + op00NOOP(0xe5); +} + +void ScriptEngine::ope6GFIB(EngineState *state, EngineFrame *frame) { + state->push(0); + op00NOOP(0xe6); +} + +void ScriptEngine::ope7CFIB(EngineState *state, EngineFrame *frame) { + state->pop(); + op00NOOP(0xe7); +} + +void ScriptEngine::op00NOOP(byte op) { + warning("SCRIPT: Opcode not implemented => %x", op); +} + + + +ScriptAsset::ScriptAsset(ObjID id, Container *container) { + _id = id; + _container = container; + _ip = 0x0; + loadInstructions(); +} + +void ScriptAsset::reset() { + _ip = 0x0; +} + +uint8 ScriptAsset::fetch() { + uint8 ins = _instructions[_ip]; + _ip++; + return ins; +} + +bool ScriptAsset::hasNext() { + return _ip < _instructions.size(); +} + +void ScriptAsset::branch(int16 amount) { + _ip += amount; +} + +ObjID ScriptAsset::getId() { + return _id; +} + +void ScriptAsset::loadInstructions() { + uint32 amount = _container->getItemByteSize(_id); + Common::SeekableReadStream *res = _container->getItem(_id); + for (uint i = 0; i < amount; i++) { + _instructions.push_back(res->readByte()); + } + delete res; + debugC(2, kMVDebugScript, "Load %d instructions for script %d", amount, _id); +} + +} // End of namespace MacVenture diff --git a/engines/macventure/script.h b/engines/macventure/script.h new file mode 100644 index 0000000000..0e81e41168 --- /dev/null +++ b/engines/macventure/script.h @@ -0,0 +1,285 @@ +/* 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 MACVENTURE_SCRIPT_H +#define MACVENTURE_SCRIPT_H + +#include "macventure/container.h" +#include "macventure/world.h" +#include "macventure/macventure.h" +#include "macventure/controls.h" + +namespace MacVenture { + +class Container; +class World; + +typedef uint32 ObjID; + +class ScriptAsset { +public: + ScriptAsset(ObjID id, Container *container); + ~ScriptAsset() {} + + void reset(); + uint8 fetch(); + bool hasNext(); + void branch(int16 amount); + + ObjID getId(); + +private: + + void loadInstructions(); + +private: + ObjID _id; + Container *_container; + + Common::Array<uint8> _instructions; + uint32 _ip; // Instruction pointer +}; + +class EngineState { +public: + EngineState() { + clear(); + } + + void push(int16 data) { + sp--; + stack[sp] = unneg16(data); + } + + int16 pop() { + int16 v = stack[sp]; + sp++; + return v; + } + + int16 peek(int16 off) { + return stack[sp + off]; + } + + void poke(int16 off, int16 val) { + stack[sp + off] = unneg16(val); + } + + void clear() { + sp = 0x80; + for (int i = 0; i < sp; i++) { + stack[i] = 0; + } + } + + int16 size() { + return 0x80 - sp; + } + +private: + int16 unneg16(int16 data) { + if (data < 0) + data = ((-data) ^ 0xFFFF) + 1; + + return data; + } + +private: + + int16 stack[0x80]; + int16 sp; +}; + +struct FunCall { + int16 func; + int16 rank; + + FunCall(int16 f, int16 r) { + func = f; + rank = r; + } +}; + +struct EngineFrame { + ControlAction action; + ObjID src; + ObjID dest; + int x; + int y; + EngineState state; + Common::List<ScriptAsset> scripts; + Common::Array<FunCall> saves; + uint32 familyIdx; + + bool haltedInFirst; + bool haltedInFamily; + bool haltedInSaves; +}; + +class ScriptEngine { +public: + ScriptEngine(MacVentureEngine *engine, World *world); + ~ScriptEngine(); + +public: + bool runControl(ControlAction action, ObjID source, ObjID destination, Common::Point delta); + bool resume(bool execAll); + void reset(); + +private: + bool execFrame(bool execAll); + bool loadScript(EngineFrame *frame, uint32 scriptID); + bool resumeFunc(EngineFrame *frame); + bool runFunc(EngineFrame *frame); + +private: + + // Aux + int16 neg16(int16 val); + int16 neg8(int16 val); + int16 sumChildrenAttr(int16 obj, int16 attr, bool recursive); + void ensureNonzeroDivisor(int16 divisor, byte opcode); + + // Opcodes + void op80GATT(EngineState *state, EngineFrame *frame); //get attribute + void op81SATT(EngineState *state, EngineFrame *frame); //set attribute + void op82SUCH(EngineState *state, EngineFrame *frame); //sum children attribute + void op83PUCT(EngineState *state, EngineFrame *frame); //push selected control + void op84PUOB(EngineState *state, EngineFrame *frame); //push selected object + void op85PUTA(EngineState *state, EngineFrame *frame); //push target + void op86PUDX(EngineState *state, EngineFrame *frame); //push deltax + void op87PUDY(EngineState *state, EngineFrame *frame); //push deltay + void op88PUIB(EngineState *state, EngineFrame *frame, ScriptAsset *script);//push immediate.b + void op89PUI(EngineState *state, EngineFrame *frame, ScriptAsset *script);//push immediate + void op8aGGLO(EngineState *state, EngineFrame *frame); //get global + void op8bSGLO(EngineState *state, EngineFrame *frame); //set global + void op8cRAND(EngineState *state, EngineFrame *frame); //random + void op8dCOPY(EngineState *state, EngineFrame *frame); //copy + void op8eCOPYN(EngineState *state, EngineFrame *frame); //copyn + void op8fSWAP(EngineState *state, EngineFrame *frame); //swap + + void op90SWAPN(EngineState *state, EngineFrame *frame); //swapn + void op91POP(EngineState *state, EngineFrame *frame); //pop + void op92COPYP(EngineState *state, EngineFrame *frame); //copy+1 + void op93COPYPN(EngineState *state, EngineFrame *frame);//copy+n + void op94SHUFF(EngineState *state, EngineFrame *frame); //shuffle + void op95SORT(EngineState *state, EngineFrame *frame); //sort + void op96CLEAR(EngineState *state, EngineFrame *frame); //clear stack + void op97SIZE(EngineState *state, EngineFrame *frame); //get stack size + void op98ADD(EngineState *state, EngineFrame *frame); //add + void op99SUB(EngineState *state, EngineFrame *frame); //subtract + void op9aMUL(EngineState *state, EngineFrame *frame); //multiply + void op9bDIV(EngineState *state, EngineFrame *frame); //divide + void op9cMOD(EngineState *state, EngineFrame *frame); //mod + void op9dDMOD(EngineState *state, EngineFrame *frame); //divmod + void op9eABS(EngineState *state, EngineFrame *frame); //abs + void op9fNEG(EngineState *state, EngineFrame *frame); //neg + + void opa0AND(EngineState *state, EngineFrame *frame); //and + void opa1OR(EngineState *state, EngineFrame *frame); //or + void opa2XOR(EngineState *state, EngineFrame *frame); //xor + void opa3NOT(EngineState *state, EngineFrame *frame); //not + void opa4LAND(EngineState *state, EngineFrame *frame); //logical and + void opa5LOR(EngineState *state, EngineFrame *frame); //logical or + void opa6LXOR(EngineState *state, EngineFrame *frame); //logical xor + void opa7LNOT(EngineState *state, EngineFrame *frame); //logical not + void opa8GTU(EngineState *state, EngineFrame *frame); //gt? unsigned + void opa9LTU(EngineState *state, EngineFrame *frame); //lt? unsigned + void opaaGTS(EngineState *state, EngineFrame *frame); //gt? signed + void opabLTS(EngineState *state, EngineFrame *frame); //lt? signed + void opacEQ(EngineState *state, EngineFrame *frame); //eq? + void opadEQS(EngineState *state, EngineFrame *frame); //eq string? + void opaeCONT(EngineState *state, EngineFrame *frame); //contains + void opafCONTW(EngineState *state, EngineFrame *frame); //contains word + + void opb0BRA(EngineState *state, EngineFrame *frame, ScriptAsset *script); //bra + void opb1BRAB(EngineState *state, EngineFrame *frame, ScriptAsset *script); //bra.b + void opb2BEQ(EngineState *state, EngineFrame *frame, ScriptAsset *script); //beq + void opb3BEQB(EngineState *state, EngineFrame *frame, ScriptAsset *script); //beq.b + void opb4BNE(EngineState *state, EngineFrame *frame, ScriptAsset *script); //bne + void opb5BNEB(EngineState *state, EngineFrame *frame, ScriptAsset *script); //bne.b + void opb6CLAT(EngineState *state, EngineFrame *frame); //call later + void opb7CCA(EngineState *state, EngineFrame *frame); //cancel call + void opb8CLOW(EngineState *state, EngineFrame *frame); //cancel low priority + void opb9CHI(EngineState *state, EngineFrame *frame); //cancel high priority + void opbaCRAN(EngineState *state, EngineFrame *frame); //cancel priority range + bool opbbFORK(EngineState *state, EngineFrame *frame); //fork + bool opbcCALL(EngineState *state, EngineFrame *frame, ScriptAsset &script); //call + void opbdFOOB(EngineState *state, EngineFrame *frame); //focus object + void opbeSWOB(EngineState *state, EngineFrame *frame); //swap objects + void opbfSNOB(EngineState *state, EngineFrame *frame); //snap object + + void opc0TEXI(EngineState *state, EngineFrame *frame); //toggle exits + void opc1PTXT(EngineState *state, EngineFrame *frame); //print text + void opc2PNEW(EngineState *state, EngineFrame *frame); //print newline + void opc3PTNE(EngineState *state, EngineFrame *frame); //print text+nl + void opc4PNTN(EngineState *state, EngineFrame *frame); //print nl+text+nl + void opc5PNUM(EngineState *state, EngineFrame *frame); //print number + void opc6P2(EngineState *state, EngineFrame *frame); //push 2 + void opc7PLBG(EngineState *state, EngineFrame *frame); //play sound in background + void opc8PLAW(EngineState *state, EngineFrame *frame); //play sound and wait + void opc9WAIT(EngineState *state, EngineFrame *frame); //wait for sound to finish? + void opcaTIME(EngineState *state, EngineFrame *frame); //get current time + void opcbDAY(EngineState *state, EngineFrame *frame); //get current day + void opccCHLD(EngineState *state, EngineFrame *frame); //get children + void opcdNCHLD(EngineState *state, EngineFrame *frame); //get num children + void opceVERS(EngineState *state, EngineFrame *frame); //get engine version + void opcfPSCE(EngineState *state, EngineFrame *frame); //push scenario number + + void opd0P1(EngineState *state, EngineFrame *frame); //push 1 + void opd1GOBD(EngineState *state, EngineFrame *frame); //get object dimensions + void opd2GOVP(EngineState *state, EngineFrame *frame); //get overlap percent + void opd3CAPC(EngineState *state, EngineFrame *frame); //capture children + void opd4RELC(EngineState *state, EngineFrame *frame); //release children + void opd5DLOG(EngineState *state, EngineFrame *frame); //show speech dialog + void opd6ACMD(EngineState *state, EngineFrame *frame); //activate command + void opd7LOSE(EngineState *state, EngineFrame *frame); //lose game + void opd8WIN(EngineState *state, EngineFrame *frame); //win game + void opd9SLEEP(EngineState *state, EngineFrame *frame); //sleep + void opdaCLICK(EngineState *state, EngineFrame *frame); //click to continue + void opdbROBQ(EngineState *state, EngineFrame *frame); //run queue + void opdcRSQ(EngineState *state, EngineFrame *frame); //run sound queue + void opddRTQ(EngineState *state, EngineFrame *frame); //run text queue + void opdeUPSC(EngineState *state, EngineFrame *frame); //update screen + void opdfFMAI(EngineState *state, EngineFrame *frame); //flash main window + + void ope0CHGR(EngineState *state, EngineFrame *frame); //cache graphic and object + void ope1CHSO(EngineState *state, EngineFrame *frame); //cache sound + void ope2MDIV(EngineState *state, EngineFrame *frame); //muldiv + void ope3UPOB(EngineState *state, EngineFrame *frame); //update object + void ope4PLEV(EngineState *state, EngineFrame *frame); //currently playing event? + void ope5WEV(EngineState *state, EngineFrame *frame); //wait for event to finish + void ope6GFIB(EngineState *state, EngineFrame *frame); //get fibonacci (joke) + void ope7CFIB(EngineState *state, EngineFrame *frame); //calc fibonacci + + void op00NOOP(byte op); + +private: + MacVentureEngine *_engine; + World *_world; + Common::List<EngineFrame> _frames; + Container *_scripts; +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/sound.cpp b/engines/macventure/sound.cpp new file mode 100644 index 0000000000..422e365790 --- /dev/null +++ b/engines/macventure/sound.cpp @@ -0,0 +1,282 @@ +/* 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 "macventure/sound.h" + +#include "audio/mixer.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +namespace MacVenture { + +// SoundManager +SoundManager::SoundManager(MacVentureEngine *engine, Audio::Mixer *mixer) { + _container = NULL; + Common::String filename = engine->getFilePath(kSoundPathID); + _container = new Container(filename); + _mixer = mixer; + debugC(1, kMVDebugSound, "Created sound manager with file %s", filename.c_str()); +} + +SoundManager::~SoundManager() { + if (_container) + delete _container; + + Common::HashMap<ObjID, SoundAsset*>::iterator it; + Common::HashMap<ObjID, SoundAsset*>::iterator end = _assets.end(); + for (it = _assets.begin(); it != end; it++) { + delete it->_value; + } +} + +uint32 SoundManager::playSound(ObjID sound) { + ensureLoaded(sound); + _assets[sound]->play(_mixer, &_handle); + return _assets[sound]->getPlayLength(); +} + +void SoundManager::ensureLoaded(ObjID sound) { + if (!_assets.contains(sound)) + _assets[sound] = new SoundAsset(_container, sound); +} + +SoundAsset::SoundAsset(Container *container, ObjID id) : + _container(container), _id(id), _length(0), _frequency(1) { + if (_container->getItemByteSize(_id) == 0) + warning("Trying to load an empty sound asset (%d).", _id); + + Common::SeekableReadStream *stream = _container->getItem(_id); + + stream->seek(5, SEEK_SET); + SoundType type = (SoundType)stream->readByte(); + debugC(2, kMVDebugSound, "Decoding sound of type %x", type); + switch(type) { + case kSound10: + decode10(stream); + break; + case kSound12: + decode12(stream); + break; + case kSound18: + decode18(stream); + break; + case kSound1a: + decode1a(stream); + break; + case kSound44: + decode44(stream); + break; + case kSound78: + decode78(stream); + break; + case kSound7e: + decode7e(stream); + break; + default: + warning("Unrecognized sound type: %x", type); + break; + } + + delete stream; +} + +SoundAsset::~SoundAsset() { + debugC(3, kMVDebugSound, "~SoundAsset(%d)", _id); +} + +void SoundAsset::play(Audio::Mixer *mixer, Audio::SoundHandle *soundHandle) { + if (_data.size() == 0) { + return; + } + Audio::AudioStream *sound = Audio::makeRawStream(&_data.front(), _length, _frequency, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO); + mixer->playStream(Audio::Mixer::kPlainSoundType, soundHandle, sound); +} + +uint32 SoundAsset::getPlayLength() { + // Transform to milliseconds + return _length * 1000 / _frequency; +} + +void SoundAsset::decode10(Common::SeekableReadStream *stream) { + warning("Decode sound 0x10 untested"); + Common::Array<byte> wavtable; + stream->seek(0x198, SEEK_SET); + for (uint i = 0; i < 16; i++) { + wavtable.push_back(stream->readByte()); + } + _length = stream->readUint32BE() * 2; + //Unused + stream->readUint16BE(); + _frequency = (stream->readUint32BE() * 22100 / 0x10000); + byte ch = 0; + for (uint i = 0; i < _length; i++) { + if (i & 1) { + ch >>= 4; + } else { + ch = stream->readByte(); + } + _data.push_back(wavtable[ch & 0xf]); + } +} + +void SoundAsset::decode12(Common::SeekableReadStream *stream) { + warning("Decode sound 0x12 untested"); + stream->seek(0xc, SEEK_SET); + uint32 repeat = stream->readUint16BE(); + stream->seek(0x34, SEEK_SET); + uint32 base = stream->readUint16BE() + 0x34; + stream->seek(base, SEEK_SET); + _length = stream->readUint32BE() - 6; + stream->readUint16BE(); + _frequency = (stream->readUint32BE() * 22100 / 0x10000); + stream->seek(0xe2, SEEK_SET); + // TODO: Possible source of bugs, the original just assigns the seek to the scales + uint32 scales = stream->pos() + 0xe2; + for (uint i = 0; i < repeat; i++) { + stream->seek(scales + i * 2, SEEK_SET); + uint32 scale = stream->readUint16BE(); + stream->seek(base + 0xa, SEEK_SET); + for (uint j = 0; j < _length; j++) { + byte ch = stream->readByte(); + if (ch & 0x80) { + ch -= 0x80; + uint32 env = ch * scale; + ch = (env >> 8) & 0xff; + if (ch & 0x80) { + ch = 0x7f; + } + ch += 0x80; + } else { + ch = (ch ^ 0xff) + 1; + ch -= 0x80; + uint32 env = ch * scale; + ch = (env >> 8) & 0xff; + if (ch & 0x80) { + ch = 0x7f; + } + ch += 0x80; + ch = (ch ^ 0xff) + 1; + } + _data.push_back(ch); + } + } +} + +void SoundAsset::decode18(Common::SeekableReadStream *stream) { + warning("Decode sound 0x18 untested"); + Common::Array<byte> wavtable; + stream->seek(0x252, SEEK_SET); + for (uint i = 0; i < 16; i++) { + wavtable.push_back(stream->readByte()); + } + _length = stream->readUint32BE() * 2; + //Unused + stream->readUint16BE(); + // TODO: It had `| 0` at the end of this line, possible source of bugs. + _frequency = (stream->readUint32BE() * 22100 / 0x10000); + byte ch = 0; + for (uint i = 0; i < _length; i++) { + if (i & 1) { + ch >>= 4; + } else { + ch = stream->readByte(); + } + _data.push_back(wavtable[ch & 0xf]); + } +} + +void SoundAsset::decode1a(Common::SeekableReadStream *stream) { + warning("Decode sound 0x1a untested"); + Common::Array<byte> wavtable; + stream->seek(0x220, SEEK_SET); + for (uint i = 0; i < 16; i++) { + wavtable.push_back(stream->readByte()); + } + _length = stream->readUint32BE(); + //Unused + stream->readUint16BE(); + _frequency = (stream->readUint32BE() * 22100 / 0x10000); + byte ch = 0; + for (uint i = 0; i < _length; i++) { + if (i & 1) { + ch >>= 4; + } else { + ch = stream->readByte(); + } + _data.push_back(wavtable[ch & 0xf]); + } +} + +void SoundAsset::decode44(Common::SeekableReadStream *stream) { + stream->seek(0x5e, SEEK_SET); + _length = stream->readUint32BE(); + _frequency = (stream->readUint32BE() * 22100 / 0x10000); + for (uint i = 0; i < _length; i++) { + _data.push_back(stream->readByte()); + } +} + +void SoundAsset::decode78(Common::SeekableReadStream *stream) { + Common::Array<byte> wavtable; + stream->seek(0xba, SEEK_SET); + for (uint i = 0; i < 16; i++) { + wavtable.push_back(stream->readByte()); + } + //Unused + stream->readUint32BE(); + _length = stream->readUint32BE(); + _frequency = (stream->readUint32BE() * 22100 / 0x10000); + byte ch = 0; + for (uint i = 0; i < _length; i++) { + if (i & 1) { + ch <<= 4; + } else { + ch = stream->readByte(); + } + _data.push_back(wavtable[(ch >> 4) & 0xf]); + } +} + +void SoundAsset::decode7e(Common::SeekableReadStream *stream) { + Common::Array<byte> wavtable; + stream->seek(0xc2, SEEK_SET); + for (uint i = 0; i < 16; i++) { + wavtable.push_back(stream->readByte()); + } + //Unused + stream->readUint32BE(); + _length = stream->readUint32BE(); + _frequency = (stream->readUint32BE() * 22100 / 0x10000); + uint32 last = 0x80; + byte ch = 0; + for (uint i = 0; i < _length; i++) { + if (i & 1) { + ch <<= 4; + } else { + ch = stream->readByte(); + } + last += wavtable[(ch >> 4) & 0xf]; + _data.push_back(last & 0xff); + } +} + +} //End of namespace MacVenture diff --git a/engines/macventure/sound.h b/engines/macventure/sound.h new file mode 100644 index 0000000000..d1b1bb8a37 --- /dev/null +++ b/engines/macventure/sound.h @@ -0,0 +1,95 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#ifndef MACVENTURE_SOUND_H +#define MACVENTURE_SOUND_H + +#include "macventure/macventure.h" +#include "macventure/container.h" + +#include "common/file.h" +#include "common/hashmap.h" + +#include "audio/mixer.h" + +namespace MacVenture { + +enum SoundType { + kSound10 = 0x10, + kSound12 = 0x12, + kSound18 = 0x18, + kSound1a = 0x1a, + kSound44 = 0x44, + kSound78 = 0x78, + kSound7e = 0x7e +}; + +class SoundAsset { + +public: + SoundAsset(Container *container, ObjID id); + ~SoundAsset(); + + void play(Audio::Mixer *mixer, Audio::SoundHandle *soundHandle); + uint32 getPlayLength(); + +private: + + void decode10(Common::SeekableReadStream *stream); + void decode12(Common::SeekableReadStream *stream); + void decode18(Common::SeekableReadStream *stream); + void decode1a(Common::SeekableReadStream *stream); + void decode44(Common::SeekableReadStream *stream); + void decode78(Common::SeekableReadStream *stream); + void decode7e(Common::SeekableReadStream *stream); + +private: + + Container *_container; + ObjID _id; + + Common::Array<byte> _data; + uint32 _length; + uint32 _frequency; +}; + +class SoundManager { +public: + SoundManager(MacVentureEngine *engine, Audio::Mixer *mixer); + ~SoundManager(); + + uint32 playSound(ObjID sound); + +private: + void ensureLoaded(ObjID sound); + +private: + + Container *_container; + Common::HashMap<ObjID, SoundAsset*> _assets; + Audio::SoundHandle _handle; + Audio::Mixer *_mixer; + +}; +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/stringtable.h b/engines/macventure/stringtable.h new file mode 100644 index 0000000000..2f1d671696 --- /dev/null +++ b/engines/macventure/stringtable.h @@ -0,0 +1,109 @@ +/* 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 MACVENTURE_STRINGTABLE_H +#define MACVENTURE_STRINGTABLE_H + +#include "macventure/macventure.h" + +#include "common/file.h" + +namespace MacVenture { + +extern void toASCII(Common::String &str); + +enum StringTableID { + kErrorStringTableID = 0x80, + kFilenamesStringTableID = 0x81, + kCommonArticlesStringTableID = 0x82, + kNamingArticlesStringTableID = 0x83, + kIndirectArticlesStringTableID = 0x84 +}; + +class StringTable { +public: + StringTable(MacVentureEngine *engine, Common::MacResManager *resMan, StringTableID id) { + _engine = engine; + _resourceManager = resMan; + _id = id; + + if (!loadStrings()) + error("ENGINE: Could not load string table %x", id); + } + + ~StringTable() { + + } + + const Common::Array<Common::String> &getStrings() { + return _strings; + } + + Common::String getString(uint ndx) { + assert(ndx < _strings.size()); + return _strings[ndx]; + } + +private: + + bool loadStrings() { + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + + if ((resArray = _resourceManager->getResIDArray(MKTAG('S', 'T', 'R', '#'))).size() == 0) + return false; + + res = _resourceManager->getResource(MKTAG('S', 'T', 'R', '#'), _id); + + _strings.push_back("dummy"); // String tables are 1-indexed + uint16 numStrings = res->readUint16BE(); + uint8 strLength = 0; + for (uint i = 0; i < numStrings; ++i) { + strLength = res->readByte(); + char *str = new char[strLength + 1]; + res->read(str, strLength); + str[strLength] = '\0'; + // HACK until a proper special char implementation is found, this will have to do. + Common::String result = Common::String(str); + toASCII(result); + debugC(4, kMVDebugText, "Loaded string %s", str); + _strings.push_back(Common::String(result)); + delete[] str; + } + + delete res; + return true; + } + +private: + + MacVentureEngine *_engine; + Common::MacResManager *_resourceManager; + + StringTableID _id; + + Common::Array<Common::String> _strings; +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/text.cpp b/engines/macventure/text.cpp new file mode 100644 index 0000000000..6671c28750 --- /dev/null +++ b/engines/macventure/text.cpp @@ -0,0 +1,201 @@ +/* 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 "macventure/text.h" + +namespace MacVenture { +TextAsset::TextAsset(MacVentureEngine *engine, ObjID objid, ObjID source, ObjID target, Container *container, bool isOld, const HuffmanLists *huffman) { + _id = objid; + _sourceObj = source; + _targetObj = target; + _container = container; + _huffman = huffman; + _isOld = isOld; + _engine = engine; + + if (_isOld) { + decodeOld(); + } else { + decodeHuffman(); + } +} + +void TextAsset::decodeOld() { + Common::SeekableReadStream *res = _container->getItem(_id); + uint16 strLen = res->readUint16BE(); + Common::BitStream32BELSB stream(res, true); + char *str = new char[strLen + 1]; + bool lowercase = false; + char c; + for (uint16 i = 0; i < strLen; i++) { + char val = stream.getBits(5); + if (val == 0x0) { // Space + c = ' '; + } else if (val >= 0x1 && val <= 0x1A) { + if (lowercase) { // Ascii a-z + c = val + 0x60; + } else { // Ascii A-Z + c = val + 0x40; + } + lowercase = true; + } else if (val == 0x1B) { + if (lowercase) { + c = '.'; + } else { + c = ','; + } + lowercase = true; + } else if (val == 0x1C) { + if (lowercase) { + c = '\''; + } else { + c = '"'; + } + lowercase = true; + } else if (val == 0x1D) { // Composite + ObjID subval = stream.getBits(16); + Common::String child; + if (subval & 0x8000) { + // Composite object id + subval ^= 0xFFFF; + child = getNoun(subval); + } else { + // Just another id + // HACK, see below in getNoun() + child = *TextAsset(_engine, subval, _sourceObj, _targetObj, _container, _isOld, _huffman).decode(); + } + if (child.size() > 0) { + c = '?'; // HACK Will fix later, should append + } + lowercase = true; + } else if (val == 0x1E) { + c = stream.getBits(8); + lowercase = true; + } else if (val == 0x1F) { + lowercase = !lowercase; + } else { + warning("Unrecognized char in old text %d, pos %d", _id, i); + } + str[i] = c; + } + + str[strLen] = '\0'; + debugC(3, kMVDebugText, "Decoded string [%d] (old encoding): %s", _id, str); + _decoded = Common::String(str); +} + +void TextAsset::decodeHuffman() { + _decoded = Common::String(""); + Common::SeekableReadStream *res = _container->getItem(_id); + Common::BitStream8MSB stream(res, true); + uint16 strLen = 0; + if (stream.getBit()) { + strLen = stream.getBits(15); + } else { + strLen = stream.getBits(7); + } + uint32 mask = 0; + uint32 symbol = 0; + char c; + for (uint16 i = 0; i < strLen; i++) { + mask = stream.peekBits(16); + + uint32 entry; + // Find the length index + for (entry = 0; entry < _huffman->getNumEntries(); entry++) { + if (mask < _huffman->getMask(entry)) { + break; + } + } + + stream.skip(_huffman->getLength(entry)); + + symbol = _huffman->getSymbol(entry); + + if (symbol == 1) { // 7-bit ascii + c = stream.getBits(7); + _decoded += c; + } else if (symbol == 2) { // Composite + if (stream.getBit()) { // TextID + ObjID embedId = stream.getBits(15); + uint pos = stream.pos(); // HACK, part 1 + TextAsset embedded(_engine, embedId, _sourceObj, _targetObj, _container, _isOld, _huffman); + stream.rewind();// HACK, part 2 + stream.skip(pos); + + _decoded.replace(_decoded.end(), _decoded.end(), *embedded.decode()); + + // Another HACK, to get around that EOS char I insert at the end + _decoded.replace(_decoded.end() - 1, _decoded.end(), ""); + } else { //Composite obj string + ObjID embedId = stream.getBits(8); + uint pos = stream.pos(); // HACK, part 1 + + _decoded.replace(_decoded.end(), _decoded.end(), getNoun(embedId)); + stream.rewind();// HACK, part 2 + stream.skip(pos); + + // Another HACK, to get around that EOS char I insert at the end + _decoded.replace(_decoded.end() - 1, _decoded.end(), ""); + } + } else { // Plain ascii + c = symbol & 0xFF; + _decoded.replace(_decoded.end(), _decoded.end(), Common::String(c)); + } + } + _decoded += '\0'; + debugC(3, kMVDebugText, "Decoded string [%d] (new encoding): %s", _id, _decoded.c_str()); +} +Common::String TextAsset::getNoun(ObjID subval) { + ObjID obj; + Common::String name; + if (subval & 8) { + obj = _targetObj; + } else { + obj = _sourceObj; + } + if ((subval & 3) == 1) { + uint idx = _engine->getPrefixNdx(obj); + idx = ((idx >> 4) & 3) + 1; + name = _engine->getNoun(idx); + } else { + // HACK, there should be a pool of assets or something like in the GUI + name = *TextAsset(_engine, obj, _sourceObj, _targetObj, _container, _isOld, _huffman).decode(); + switch (subval & 3) { + case 2: + name = _engine->getPrefixString(0, obj) + name; + break; + case 3: + name = _engine->getPrefixString(2, obj) + name; + break; + } + } + if (name.size() && (subval & 4)) { + Common::String tmp = name; + name.toUppercase(); + name.replace(1, name.size() - 1, tmp, 1, tmp.size() - 1); + } + + return name; +} + +} // End of namespace MacVenture diff --git a/engines/macventure/text.h b/engines/macventure/text.h new file mode 100644 index 0000000000..88dd112681 --- /dev/null +++ b/engines/macventure/text.h @@ -0,0 +1,64 @@ +/* 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 MACVENTURE_TEXT_H +#define MACVENTURE_TEXT_H + +#include "macventure/macventure.h" +#include "macventure/hufflists.h" + +namespace MacVenture { +typedef uint32 ObjID; +class MacVentureEngine; + +class TextAsset { +public: + TextAsset(MacVentureEngine *engine, ObjID objid, ObjID source, ObjID target, Container *container, bool isOld, const HuffmanLists *huffman); + ~TextAsset() {} + + const Common::String *decode() { + return &_decoded; + } + +private: + void decodeOld(); + void decodeHuffman(); + + Common::String getNoun(ObjID id); + +private: + MacVentureEngine *_engine; + + Container *_container; + ObjID _id; + ObjID _targetObj; + ObjID _sourceObj; + const HuffmanLists *_huffman; + bool _isOld; + + Common::String _decoded; + +}; + +} // End of namespace MacVenture + +#endif diff --git a/engines/macventure/windows.cpp b/engines/macventure/windows.cpp new file mode 100644 index 0000000000..b3d544924f --- /dev/null +++ b/engines/macventure/windows.cpp @@ -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. +* +*/ + +#include "macventure/windows.h" + +namespace MacVenture { + +BorderBounds borderBounds(MVWindowType type) { + switch (type) { + case MacVenture::kDocument: + break; + case MacVenture::kDBox: + break; + case MacVenture::kPlainDBox: + return BorderBounds(3, 3, 3, 3); + case MacVenture::kAltBox: + return BorderBounds(0, 0, 0, 0); // Hand-tested + case MacVenture::kNoGrowDoc: + return BorderBounds(1, 20, 1, 1); + case MacVenture::kMovableDBox: + break; + case MacVenture::kZoomDoc: + return BorderBounds(1, 20, 17, 1); + case MacVenture::kZoomNoGrow: + break; + case MacVenture::kInvWindow: + return BorderBounds(1, 20, 17, 17); + case MacVenture::kRDoc16: + break; + case MacVenture::kRDoc4: + return BorderBounds(1, 20, 1, 1); + case MacVenture::kRDoc6: + break; + case MacVenture::kRDoc10: + break; + default: + break; + } + + return BorderBounds(0, 0, 0, 0); +} +} // End of namespace MacVenture diff --git a/engines/macventure/windows.h b/engines/macventure/windows.h new file mode 100644 index 0000000000..fd3acefa7b --- /dev/null +++ b/engines/macventure/windows.h @@ -0,0 +1,99 @@ +/* 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 MACVENTURE_WINDOWS_H +#define MACVENTURE_WINDOWS_H + +#include "common/rect.h" +#include "common/array.h" + +namespace MacVenture { +// massive HACK +typedef uint32 ObjID; + +enum WindowReference { + kNoWindow = 0, + kInventoryStart = 1, + kCommandsWindow = 0x80, + kMainGameWindow = 0x81, + kOutConsoleWindow = 0x82, + kSelfWindow = 0x83, + kExitsWindow = 0x84, + kDiplomaWindow = 0x85 +}; + +enum MVWindowType { + kDocument = 0x00, + kDBox = 0x01, + kPlainDBox = 0x02, + kAltBox = 0x03, + kNoGrowDoc = 0x04, + kMovableDBox = 0x05, + kZoomDoc = 0x08, + kZoomNoGrow = 0x0c, + // WebVenture assigns arbitrary kinds post-loading + kInvWindow = 0x0e, + kRDoc16 = 0x10, + kRDoc4 = 0x12, + kRDoc6 = 0x14, + kRDoc10 = 0x16, + kNoType = 0xFF +}; + +struct DrawableObject { + ObjID obj; + byte mode; + DrawableObject(ObjID id, byte md) { + obj = id; + mode = md; + } +}; + +enum { + kScrollAmount = 10 +}; + +struct WindowData { + Common::Rect bounds; + MVWindowType type; + ObjID objRef; + uint16 visible; + uint16 hasCloseBox; + WindowReference refcon; + uint8 titleLength; + Common::String title; + Common::Array<DrawableObject> children; + bool updateScroll; + Common::Point scrollPos; +}; + +struct BorderBounds { + uint16 leftOffset; + uint16 topOffset; + uint16 rightOffset; + uint16 bottomOffset; + + BorderBounds(uint16 l, uint16 t, uint16 r, uint16 b) : + leftOffset(l), topOffset(t), rightOffset(r), bottomOffset(b) {} +}; +} +#endif diff --git a/engines/macventure/world.cpp b/engines/macventure/world.cpp new file mode 100644 index 0000000000..7c14618f3e --- /dev/null +++ b/engines/macventure/world.cpp @@ -0,0 +1,350 @@ +#include "macventure/world.h" +#include "macventure/macventure.h" + +#include "common/file.h" + +namespace MacVenture { + +World::World(MacVentureEngine *engine, Common::MacResManager *resMan) { + _resourceManager = resMan; + _engine = engine; + _saveGame = NULL; + _gameText = NULL; + + startNewGame(); + + _objectConstants = new Container(_engine->getFilePath(kObjectPathID)); + calculateObjectRelations(); + + _gameText = new Container(_engine->getFilePath(kTextPathID)); +} + + +World::~World() { + + if (_saveGame) + delete _saveGame; + + if (_objectConstants) + delete _objectConstants; + + if (_gameText) + delete _gameText; +} + +void World::startNewGame() { + if (_saveGame) + delete _saveGame; + + if ((_startGameFileName = _engine->getStartGameFileName()) == "") + error("WORLD: Could not load initial game configuration"); + + Common::File saveGameFile; + if (!saveGameFile.open(_startGameFileName)) + error("WORLD: Could not load initial game configuration"); + + debugC(2, kMVDebugMain, "Loading save game state from %s", _startGameFileName.c_str()); + Common::SeekableReadStream *saveGameRes = saveGameFile.readStream(saveGameFile.size()); + + _saveGame = new SaveGame(_engine, saveGameRes); + + calculateObjectRelations(); + + delete saveGameRes; + saveGameFile.close(); +} + +uint32 World::getObjAttr(ObjID objID, uint32 attrID) { + uint res; + uint32 index = _engine->getGlobalSettings()._attrIndices[attrID]; + // HACK, but if I try to initialize it in the else clause, it goes out of scope and segfaults + Common::SeekableReadStream *objStream = _objectConstants->getItem(objID); + if (!(index & 0x80)) { // It's not a constant + res = _saveGame->getAttr(objID, index); + } else { + index &= 0x7F; + if (objStream->size() == 0) { + return 0; + } + // Look for the right attribute inside the object + objStream->skip(index * 2); + res = objStream->readByte() << 8; + res |= objStream->readByte(); + } + res &= _engine->getGlobalSettings()._attrMasks[attrID]; + res >>= _engine->getGlobalSettings()._attrShifts[attrID]; + if (res & 0x8000) + res = -((res ^ 0xffff) + 1); + debugC(5, kMVDebugMain, "Attribute %x from object %x is %x", attrID, objID, res); + delete objStream; + return res; +} + +void World::setObjAttr(ObjID objID, uint32 attrID, Attribute value) { + if (attrID == kAttrPosX || attrID == kAttrPosY) { + // Round to scale + // Intentionally empty, we don't seem to require this functionality + } + + if (attrID == kAttrParentObject) + setParent(objID, value); + + if (attrID < kAttrOtherDoor) + _engine->enqueueObject(kUpdateObject, objID); + + uint32 idx = _engine->getGlobalSettings()._attrIndices[attrID]; + value <<= _engine->getGlobalSettings()._attrShifts[attrID]; + value &= _engine->getGlobalSettings()._attrMasks[attrID]; + Attribute oldVal = _saveGame->getAttr(objID, idx); + oldVal &= ~_engine->getGlobalSettings()._attrMasks[attrID]; + _saveGame->setAttr(idx, objID, (value | oldVal)); + _engine->gameChanged(); +} + +bool MacVenture::World::isObjActive(ObjID obj) { + ObjID destObj = _engine->getDestObject(); + Common::Point p = _engine->getDeltaPoint(); + ControlAction selectedControl = _engine->getSelectedControl(); + if (!getAncestor(obj)) { + return false; // If our ancestor is the garbage (obj 0), we're inactive + } + if (_engine->getInvolvedObjects() >= 2 && // If (we need > 1 objs for the command) && + destObj > 0 && // we have a destination object && + !getAncestor(destObj)) { // but that destination object is in the garbage + return false; + } + if (selectedControl != kMoveObject) { + return true; // We only need one + } + // Handle move object + if (!isObjDraggable(obj)) { + return false; // We can't move it + } + if (getObjAttr(1, kAttrParentObject) != destObj) { + return true; // if the target is not the player's parent, we can go + } + Common::Rect rect(kScreenWidth, kScreenHeight); + rect.top -= getObjAttr(obj, kAttrPosY) + p.y; + rect.left -= getObjAttr(obj, kAttrPosX) + p.x; + return intersects(obj, rect); +} + +ObjID World::getAncestor(ObjID objID) { + ObjID root = getObjAttr(1, kAttrParentObject); + while (objID != 0 && objID != 1 && objID != root) { + objID = getObjAttr(objID, kAttrParentObject); + } + return objID; +} + +Common::Array<ObjID> World::getFamily(ObjID objID, bool recursive) { + Common::Array<ObjID> res; + res.push_back(objID); + res.push_back(getChildren(objID, recursive)); + return res; +} + +Common::Array<ObjID> World::getChildren(ObjID objID, bool recursive) { + Common::Array<ObjID> res; + ObjID child = _relations[objID * 2]; + while (child) { + res.push_back(child); + if (!recursive) + res.push_back(getChildren(child, false)); + child = _relations[child * 2 + 1]; + } + return res; +} + +Attribute World::getGlobal(uint32 attrID) { + return _saveGame->getGlobals()[attrID]; +} + +void World::setGlobal(uint32 attrID, Attribute value) { + _saveGame->setGlobal(attrID, value); +} + +void World::updateObj(ObjID objID) { + WindowReference win; + if (getObjAttr(1, kAttrParentObject) == objID) { + win = kMainGameWindow; + } else { + win = _engine->getObjWindow(objID); + } + if (win) { + _engine->focusObjWin(objID); + _engine->runObjQueue(); + _engine->updateWindow(win); + } +} + +void World::captureChildren(ObjID objID) { + warning("Capture children unimplemented!"); +} + +void World::releaseChildren(ObjID objID) { + warning("Release children unimplemented!"); +} + +Common::String World::getText(ObjID objID, ObjID source, ObjID target) { + if (objID & 0x8000) { + return _engine->getUserInput(); + } + TextAsset text = TextAsset(_engine, objID, source, target, _gameText, _engine->isOldText(), _engine->getDecodingHuffman()); + + return *text.decode(); +} + + +bool World::isObjDraggable(ObjID objID) { + return (getObjAttr(objID, kAttrInvisible) == 0 && + getObjAttr(objID, kAttrUnclickable) == 0 && + getObjAttr(objID, kAttrUndraggable) == 0); +} + +bool World::intersects(ObjID objID, Common::Rect rect) { + return _engine->getObjBounds(objID).intersects(rect); +} + +void World::calculateObjectRelations() { + _relations.clear(); + ObjID val, next; + uint32 numObjs = _engine->getGlobalSettings()._numObjects; + const AttributeGroup &parents = *_saveGame->getGroup(0); + for (uint i = 0; i < numObjs * 2; i++) { + _relations.push_back(0); + } + for (uint i = numObjs - 1; i > 0; i--) { + val = parents[i]; + next = _relations[val * 2]; + if (next) { + _relations[i * 2 + 1] = next; + } + _relations[val * 2] = i; + } +} + +void World::setParent(ObjID child, ObjID newParent) { + ObjID old = _saveGame->getAttr(child, kAttrParentObject); + if (newParent == child) + return; + + ObjID oldNdx = old * 2; + old = _relations[oldNdx]; + while (old != child) { + oldNdx = (old * 2) + 1; + old = _relations[oldNdx]; + } + _relations[oldNdx] = _relations[(old * 2) + 1]; + oldNdx = newParent * 2; + old = _relations[oldNdx]; + while (old && old <= child) { + oldNdx = (old * 2) + 1; + old = _relations[oldNdx]; + } + _relations[child * 2 + 1] = old; + _relations[oldNdx] = child; +} + +void World::loadGameFrom(Common::InSaveFile *file) { + if (_saveGame) { + delete _saveGame; + } + _saveGame = new SaveGame(_engine, file); + calculateObjectRelations(); +} + +void World::saveGameInto(Common::OutSaveFile *file) { + _saveGame->saveInto(file); +} + +// SaveGame +SaveGame::SaveGame(MacVentureEngine *engine, Common::SeekableReadStream *res) { + _groups = Common::Array<AttributeGroup>(); + loadGroups(engine, res); + _globals = Common::Array<uint16>(); + loadGlobals(engine, res); + _text = Common::String(); + loadText(engine, res); +} + +SaveGame::~SaveGame() { +} + + +Attribute SaveGame::getAttr(ObjID objID, uint32 attrID) { + return _groups[attrID][objID]; +} + +void SaveGame::setAttr(uint32 attrID, ObjID objID, Attribute value) { + _groups[attrID][objID] = value; +} + +const Common::Array<AttributeGroup> &MacVenture::SaveGame::getGroups() { + return _groups; +} + +const AttributeGroup *SaveGame::getGroup(uint32 groupID) { + assert(groupID < _groups.size()); + return &(_groups[groupID]); +} + +void SaveGame::setGlobal(uint32 attrID, Attribute value) { + _globals[attrID] = value; +} + +const Common::Array<uint16> &SaveGame::getGlobals() { + return _globals; +} + +const Common::String &SaveGame::getText() { + return _text; +} + +void SaveGame::saveInto(Common::OutSaveFile *file) { + warning("Saving the game not yet tested!"); + // Save attibutes + Common::Array<AttributeGroup>::const_iterator itg; + for (itg = _groups.begin(); itg != _groups.end(); itg++) { + Common::Array<Attribute>::const_iterator ita; + for (ita = itg->begin(); ita != itg->end(); ita++) { + file->writeUint16BE((*ita)); + } + } + // Save globals + Common::Array<uint16>::const_iterator global; + for (global = _globals.begin(); global != _globals.end(); global++) { + file->writeUint16BE((*global)); + } + // Save text + // TODO: Insert text from GUI console + _text = "Hello"; + file->write(_text.c_str(), _text.size()); +} + +void SaveGame::loadGroups(MacVentureEngine *engine, Common::SeekableReadStream *res) { + GlobalSettings settings = engine->getGlobalSettings(); + for (int i = 0; i < settings._numGroups; ++i) { + AttributeGroup g; + for (int j = 0; j < settings._numObjects; ++j) { + g.push_back(res->readUint16BE()); + } + + _groups.push_back(g); + } +} + +void SaveGame::loadGlobals(MacVentureEngine *engine, Common::SeekableReadStream *res) { + GlobalSettings settings = engine->getGlobalSettings(); + for (int i = 0; i < settings._numGlobals; ++i) { + _globals.push_back(res->readUint16BE()); + } +} + +void SaveGame::loadText(MacVentureEngine *engine, Common::SeekableReadStream *res) { + // TODO: Load console text. For now, the GUI doesn't even look at this. + _text = "Placeholder Console Text"; +} + + +} // End of namespace MacVenture diff --git a/engines/macventure/world.h b/engines/macventure/world.h new file mode 100644 index 0000000000..e78ab7f0db --- /dev/null +++ b/engines/macventure/world.h @@ -0,0 +1,139 @@ +/* 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 MACVENTURE_WORLD_H +#define MACVENTURE_WORLD_H + +#include "macventure/container.h" +#include "macventure/text.h" + +namespace MacVenture { + +typedef uint32 ObjID; +typedef uint16 Attribute; +typedef Common::Array<Attribute> AttributeGroup; +class TextAsset; + +enum ObjectAttributeID { + kAttrParentObject = 0, + kAttrPosX = 1, + kAttrPosY = 2, + kAttrInvisible = 3, + kAttrUnclickable = 4, + kAttrUndraggable = 5, + kAttrContainerOpen = 6, + kAttrPrefixes = 7, + kAttrIsExit = 8, + kAttrExitX = 9, + kAttrExitY = 10, + kAttrHiddenExit = 11, + kAttrOtherDoor = 12, + kAttrIsOpen = 13, + kAttrIsLocked = 14, + kAttrWeight = 16, + kAttrSize = 17, + kAttrHasDescription = 19, + kAttrIsDoor = 20, + kAttrIsContainer = 22, + kAttrIsOperable = 23, + kAttrIsEnterable = 24, + kAttrIsEdible = 25 +}; + +class SaveGame { +public: + SaveGame(MacVentureEngine *engine, Common::SeekableReadStream *res); + ~SaveGame(); + + Attribute getAttr(ObjID objID, uint32 attrID); + void setAttr(uint32 attrID, ObjID objID, Attribute value); + + void setGlobal(uint32 attrID, Attribute value); + const Common::Array<uint16> &getGlobals(); + + const Common::Array<AttributeGroup> &getGroups(); + const AttributeGroup *getGroup(uint32 groupID); + const Common::String &getText(); + + void saveInto(Common::OutSaveFile *file); + +private: + void loadGroups(MacVentureEngine *engine, Common::SeekableReadStream *res); + void loadGlobals(MacVentureEngine *engine, Common::SeekableReadStream *res); + void loadText(MacVentureEngine *engine, Common::SeekableReadStream *res); + +private: + Common::Array<AttributeGroup> _groups; + Common::Array<uint16> _globals; + Common::String _text; +}; + +class World { +public: + World(MacVentureEngine *engine, Common::MacResManager *resMan); + ~World(); + + void startNewGame(); + + void setObjAttr(ObjID objID, uint32 attrID, Attribute value); + void setGlobal(uint32 attrID, Attribute value); + void updateObj(ObjID objID); + void captureChildren(ObjID objID); + void releaseChildren(ObjID objID); + + uint32 getObjAttr(ObjID objID, uint32 attrID); + Attribute getGlobal(uint32 attrID); + Common::String getText(ObjID objID, ObjID source, ObjID target); + + bool isObjActive(ObjID objID); + + ObjID getAncestor(ObjID objID); + Common::Array<ObjID> getFamily(ObjID objID, bool recursive); + Common::Array<ObjID> getChildren(ObjID objID, bool recursive); + + void loadGameFrom(Common::InSaveFile *file); + void saveGameInto(Common::OutSaveFile *file); + +private: + bool isObjDraggable(ObjID objID); + bool intersects(ObjID objID, Common::Rect rect); + + void calculateObjectRelations(); + void setParent(ObjID child, ObjID newParent); + +private: + MacVentureEngine *_engine; + Common::MacResManager *_resourceManager; + + Common::String _startGameFileName; + + SaveGame *_saveGame; + + Container *_objectConstants; + Container *_gameText; + + Common::Array<ObjID> _relations; // Parent-child relations, stored in Williams Heap format +}; + +} // End of namespace MacVenture + +#endif |