aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilippos Karapetis2008-04-20 14:47:37 +0000
committerFilippos Karapetis2008-04-20 14:47:37 +0000
commit7ca439f410ac1c46a387567b30271ae4e4a2ed30 (patch)
tree4d4154169b074293581ad6a11ee821290418f4fb
parentd0590a09eac68d5cde64d37fb2e5bbd1471a676a (diff)
downloadscummvm-rg350-7ca439f410ac1c46a387567b30271ae4e4a2ed30.tar.gz
scummvm-rg350-7ca439f410ac1c46a387567b30271ae4e4a2ed30.tar.bz2
scummvm-rg350-7ca439f410ac1c46a387567b30271ae4e4a2ed30.zip
Initial import of the work in progress M4 engine
svn-id: r31600
-rw-r--r--engines/m4/actor.cpp202
-rw-r--r--engines/m4/actor.h117
-rw-r--r--engines/m4/animation.cpp210
-rw-r--r--engines/m4/animation.h69
-rw-r--r--engines/m4/assets.cpp544
-rw-r--r--engines/m4/assets.h184
-rw-r--r--engines/m4/burger_data.h85
-rw-r--r--engines/m4/compression.cpp189
-rw-r--r--engines/m4/compression.h82
-rw-r--r--engines/m4/console.cpp287
-rw-r--r--engines/m4/console.h64
-rw-r--r--engines/m4/converse.cpp1211
-rw-r--r--engines/m4/converse.h200
-rw-r--r--engines/m4/detection.cpp345
-rw-r--r--engines/m4/events.cpp351
-rw-r--r--engines/m4/events.h132
-rw-r--r--engines/m4/font.cpp267
-rw-r--r--engines/m4/font.h91
-rw-r--r--engines/m4/globals.cpp447
-rw-r--r--engines/m4/globals.h221
-rw-r--r--engines/m4/graphics.cpp1074
-rw-r--r--engines/m4/graphics.h222
-rw-r--r--engines/m4/gui.cpp1217
-rw-r--r--engines/m4/gui.h446
-rw-r--r--engines/m4/hotspot.cpp293
-rw-r--r--engines/m4/hotspot.h120
-rw-r--r--engines/m4/m4.cpp555
-rw-r--r--engines/m4/m4.h184
-rw-r--r--engines/m4/m4_menus.cpp727
-rw-r--r--engines/m4/m4_menus.h106
-rw-r--r--engines/m4/m4_views.cpp345
-rw-r--r--engines/m4/m4_views.h118
-rw-r--r--engines/m4/mads_anim.cpp705
-rw-r--r--engines/m4/mads_anim.h118
-rw-r--r--engines/m4/mads_menus.cpp586
-rw-r--r--engines/m4/mads_menus.h91
-rw-r--r--engines/m4/midi.cpp359
-rw-r--r--engines/m4/midi.h99
-rw-r--r--engines/m4/module.mk42
-rw-r--r--engines/m4/rails.cpp358
-rw-r--r--engines/m4/rails.h97
-rw-r--r--engines/m4/resource.cpp436
-rw-r--r--engines/m4/resource.h141
-rw-r--r--engines/m4/saveload.cpp168
-rw-r--r--engines/m4/saveload.h57
-rw-r--r--engines/m4/scene.cpp669
-rw-r--r--engines/m4/scene.h124
-rw-r--r--engines/m4/script.cpp1406
-rw-r--r--engines/m4/script.h457
-rw-r--r--engines/m4/scripttab.h136
-rw-r--r--engines/m4/sound.cpp283
-rw-r--r--engines/m4/sound.h112
-rw-r--r--engines/m4/sprite.cpp174
-rw-r--r--engines/m4/sprite.h122
-rw-r--r--engines/m4/viewmgr.cpp436
-rw-r--r--engines/m4/viewmgr.h191
-rw-r--r--engines/m4/woodscript.cpp398
-rw-r--r--engines/m4/woodscript.h351
-rw-r--r--engines/m4/ws_machine.cpp422
-rw-r--r--engines/m4/ws_sequence.cpp764
60 files changed, 20007 insertions, 0 deletions
diff --git a/engines/m4/actor.cpp b/engines/m4/actor.cpp
new file mode 100644
index 0000000000..c7c90ac3ae
--- /dev/null
+++ b/engines/m4/actor.cpp
@@ -0,0 +1,202 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/system.h"
+#include "common/array.h"
+#include "m4/actor.h"
+#include "m4/m4_views.h"
+#include "m4/assets.h"
+
+namespace M4 {
+
+#define WALKER_BURGER "Wilbur0%i" // wilbur, with a number denoting his current direction
+
+Actor::Actor(M4Engine *vm) : _vm(vm) {
+ _scaling = 100;
+ _direction = 5;
+ _walkerSprites.resize(10);
+ loadWalkers();
+}
+
+Actor::~Actor() {
+ unloadWalkers();
+}
+
+int Actor::getWalkerWidth() { return _walkerSprites[kFacingSouth]->getFrame(0)->w; }
+int Actor::getWalkerHeight() { return _walkerSprites[kFacingSouth]->getFrame(0)->h; }
+
+void Actor::placeWalkerSpriteAt(int spriteNum, int x, int y) {
+ if (_direction < 1 || _direction > 9) {
+ warning("Direction is %i, fixing", _direction);
+ _direction = 1; // TODO: this is a temporary fix
+ }
+ SpriteInfo info;
+ info.sprite = _walkerSprites[_direction]->getFrame(spriteNum);
+ info.hotX = info.hotY = 0;
+ info.width = info.sprite->w;
+ info.height = info.sprite->h;
+ info.scaleX = info.scaleY = _scaling;
+ info.palette = _walkerSprites[_direction]->getPalette();
+ info.inverseColorTable = _vm->_scene->getInverseColorTable();
+
+ _vm->_scene->drawSprite(x, y, info, Common::Rect(640, 400));
+}
+
+void Actor::loadWalkers() {
+ for (uint8 i = 1; i < 10; i++) {
+ if (i == 6)
+ continue; // walker sprite 6 is unused
+ loadWalkerDirection(i);
+ }
+}
+
+void Actor::loadWalkerDirection(uint8 direction) {
+ char name[20];
+ Common::SeekableReadStream *walkerS;
+
+ if (_vm->getGameType() == GType_Burger) {
+ sprintf(name, WALKER_BURGER, direction);
+ } else {
+ //warning("Actor::loadWalkerDirection: unspecified walker type, not loading walker");
+ // TODO: Master Lu walkers
+ return;
+ }
+
+ walkerS = _vm->res()->get(name);
+ _walkerSprites.insert_at(direction, new SpriteAsset(_vm, walkerS, walkerS->size(), name));
+ _vm->res()->toss(name);
+}
+
+void Actor::unloadWalkers() {
+ for (uint8 i = 9; i > 0; i--) {
+ if (i == 6)
+ continue; // walker sprite 6 is unused
+ SpriteAsset *tempSprite = _walkerSprites[i];
+ _walkerSprites.remove_at(i);
+ if (tempSprite)
+ delete tempSprite;
+ }
+}
+
+void Actor::setWalkerPalette() {
+ _vm->_palette->setPalette(_walkerSprites[kFacingSouthEast]->getPalette(), 0,
+ _walkerSprites[kFacingSouthEast]->getColorCount());
+}
+
+Inventory::Inventory(M4Engine *vm) : _vm(vm) {
+}
+
+Inventory::~Inventory() {
+ _inventory.clear();
+}
+
+void Inventory::registerObject(char* name, int32 scene, int32 icon) {
+ InventoryObject *newObject = new InventoryObject();
+ int newObjectIndex = 0;
+
+ // Capitalize registered inventory object names
+ str_upper(name);
+
+ newObject->name = strdup(name);
+ newObject->scene = scene;
+ newObject->icon = icon;
+
+ newObjectIndex = _inventory.size();
+
+ _inventory.push_back(newObject);
+
+ if (scene == BACKPACK)
+ addToBackpack(newObjectIndex);
+}
+
+void Inventory::moveObject(char* name, int32 scene) {
+ uint i = 0;
+
+ for (i = 0; i < _inventory.size(); i++) {
+ if (!scumm_stricmp(_inventory[i]->name, name)) {
+ if (_inventory[i]->scene == BACKPACK && scene != BACKPACK)
+ removeFromBackpack(i);
+
+ _inventory[i]->scene = scene;
+
+ if (scene == BACKPACK)
+ addToBackpack(i);
+
+ return;
+ }
+ }
+}
+
+void Inventory::addToBackpack(uint32 objectIndex) {
+ _vm->_interfaceView->inventoryAdd(_inventory[objectIndex]->name, "", _inventory[objectIndex]->icon);
+}
+
+void Inventory::removeFromBackpack(uint32 objectIndex) {
+ _vm->_interfaceView->inventoryRemove(_inventory[objectIndex]->name);
+}
+
+bool Inventory::isInCurrentScene(char* name) {
+ return (getScene(name) == _vm->_scene->getCurrentScene());
+}
+
+int Inventory::getScene(char* name) {
+ uint i = 0;
+
+ for (i = 0; i < _inventory.size(); i++) {
+ if (!scumm_stricmp(_inventory[i]->name, name))
+ return _inventory[i]->scene;
+ }
+ return UNKNOWN_OBJECT;
+}
+
+int Inventory::getIcon(char* name) {
+ uint i = 0;
+
+ for (i = 0; i < _inventory.size(); i++) {
+ if (!scumm_stricmp(_inventory[i]->name, name))
+ return _inventory[i]->icon;
+ }
+ return UNKNOWN_OBJECT;
+}
+
+int Inventory::getIndex(char* name) {
+ uint i = 0;
+
+ for (i = 0; i < _inventory.size(); i++) {
+ if (!scumm_stricmp(_inventory[i]->name, name))
+ return i;
+ }
+ return UNKNOWN_OBJECT;
+}
+
+void Inventory::clear() {
+ for (uint i = 0; i < _inventory.size(); i++) {
+ delete _inventory[i]->name;
+ delete _inventory[i];
+ _inventory.remove_at(i);
+ }
+}
+
+} // End of namespace M4
diff --git a/engines/m4/actor.h b/engines/m4/actor.h
new file mode 100644
index 0000000000..a39398db55
--- /dev/null
+++ b/engines/m4/actor.h
@@ -0,0 +1,117 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_ACTOR_H
+#define M4_ACTOR_H
+
+#include "common/array.h"
+
+#include "m4/m4.h"
+#include "m4/scene.h"
+#include "m4/graphics.h"
+#include "m4/assets.h"
+
+namespace M4 {
+
+struct InventoryObject {
+ const char* name;
+ int32 scene;
+ int32 icon;
+};
+
+enum inventoryObjectFlags {
+ UNKNOWN_OBJECT = 997,
+ BACKPACK = 998,
+ NOWHERE = 999
+};
+
+enum WalkerDirection {
+ kFacingNorth = 1, // has shadow
+ kFacingNorthEast = 2, // has shadow
+ kFacingEast = 3, // has shadow
+ kFacingSouthEast = 4, // has shadow
+ kFacingSouth = 5, // has shadow
+ // 6 is unused
+ kFacingSouthAlt = 7, // no shadow
+ kFacingSouthWest = 8, // no shadow
+ kFacingWest = 9 // no shadow
+};
+
+class Actor {
+public:
+ Actor(M4Engine *vm);
+ ~Actor();
+ void placeWalkerSpriteAt(int spriteNum, int x, int y);
+ void setWalkerScaling(int scaling) { _scaling = scaling; }
+ int getWalkerScaling() { return _scaling; }
+ void setWalkerDirection(uint8 direction) { _direction = direction; }
+ uint8 getWalkerDirection() { return _direction; }
+ void setWalkerPalette();
+ int getWalkerWidth();
+ int getWalkerHeight();
+private:
+ M4Engine *_vm;
+ int _scaling;
+ uint8 _direction;
+ Common::Array<SpriteAsset*> _walkerSprites;
+
+ void loadWalkers();
+ void loadWalkerDirection(uint8 direction);
+ void unloadWalkers();
+};
+
+// TODO: perhaps the inventory and its view could be merged?
+// TODO: the original game capitalizes all inventory object names
+// internally, which we do as well, but perhaps we could make sure
+// that all object names are parsed with the same case and avoid
+// case-insensitive string comparing through scumm_stricmp, using
+// the normal strcmp method instead
+class Inventory {
+public:
+ Inventory(M4Engine *vm);
+ ~Inventory();
+ void clear();
+ void registerObject(char* name, int32 scene, int32 icon);
+ void moveObject(char* name, int32 scene);
+ void giveToPlayer(char* name) { moveObject(name, BACKPACK); }
+ void addToBackpack(uint32 objectIndex);
+ void removeFromBackpack(uint32 objectIndex);
+ bool isInBackpack(char* name) { return (getScene(name) == BACKPACK); }
+ bool isInScene(char* name, int32 scene) { return (getScene(name) == scene); }
+ bool isInCurrentScene(char* name);
+ int getScene(char* name);
+ int getIcon(char* name);
+ int getIndex(char* name);
+ int getTotalItems() { return _inventory.size(); }
+
+private:
+ M4Engine *_vm;
+ Common::Array<InventoryObject *> _inventory;
+};
+
+} // End of namespace M4
+
+
+#endif
diff --git a/engines/m4/animation.cpp b/engines/m4/animation.cpp
new file mode 100644
index 0000000000..6c9d90a3d2
--- /dev/null
+++ b/engines/m4/animation.cpp
@@ -0,0 +1,210 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/assets.h"
+#include "m4/animation.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+// TODO: this code needs cleanup
+
+Animation::Animation(M4Engine *vm) {
+ _vm = vm;
+ _playing = false;
+}
+
+void Animation::loadFullScreen(const char *filename) {
+ _vm->_palette->deleteAllRanges();
+ load(filename);
+}
+
+void Animation::load(const char *filename) {
+ MadsPack anim(filename, _vm);
+ char buffer[20];
+
+ // Chunk 1: header
+ // header
+ // TODO: there are some unknown fields here, plus we don't read
+ // the entire chunk
+ Common::SeekableReadStream *animStream = anim.getItemStream(0);
+ Common::SeekableReadStream *spriteSeriesStream;
+ //printf("Chunk 0, size %i\n", animStream->size());
+ _seriesCount = animStream->readUint16LE();
+ _frameCount = animStream->readUint16LE();
+ _frameEntryCount = animStream->readUint16LE();
+
+ // Unknown
+ for (int i = 0; i < 43; i++)
+ animStream->readByte();
+
+ _spriteSeriesNames = new Common::String[_seriesCount];
+ printf("%i sprite series\n", _seriesCount);
+
+ // TODO: for now, we only load the first sprite series
+ if (_seriesCount > 1)
+ printf("TODO: Anim has %i sprite series, for now, we only load the first one\n", _seriesCount);
+ _seriesCount = 1; // TODO
+
+ for (int i = 0; i < _seriesCount; i++) {
+ animStream->read(buffer, 13);
+ _spriteSeriesNames[i] = Common::String(buffer);
+ //printf("%03d: %s\n", i, _spriteSeriesNames[i].c_str());
+
+ spriteSeriesStream = _vm->res()->get(_spriteSeriesNames[i].c_str());
+ _spriteSeries = new SpriteAsset(_vm, spriteSeriesStream,
+ spriteSeriesStream->size(), _spriteSeriesNames[i].c_str());
+ _vm->res()->toss(_spriteSeriesNames[i].c_str());
+
+ // Adjust the palette of the sprites in the sprite series
+ // so that they can be displayed on screen correctly
+ RGBList *palData = new RGBList(_spriteSeries->getColorCount(), _spriteSeries->getPalette(), true);
+ _vm->_palette->addRange(palData);
+
+ for (int k = 0; k < _spriteSeries->getCount(); k++) {
+ M4Sprite *spr = _spriteSeries->getFrame(k);
+ spr->translate(palData); // sprite pixel translation
+ }
+ }
+
+ //printf("End pos: %i\n", animStream->pos());
+
+ delete animStream;
+
+ // ------------------
+
+ // Chunk 2: anim info
+ AnimationFrame frame;
+ animStream = anim.getItemStream(1);
+ //printf("Chunk 1, size %i\n", animStream->size());
+
+ _frameEntries = new AnimationFrame[_frameEntryCount];
+
+ for (int i = 0; i < _frameEntryCount; i++) {
+
+ frame.animFrameIndex = animStream->readUint16LE();
+ frame.u = animStream->readByte();
+ frame.seriesIndex = animStream->readByte();
+ frame.seriesFrameIndex = animStream->readUint16LE();
+ frame.x = animStream->readUint16LE();
+ frame.y = animStream->readUint16LE();
+ frame.v = animStream->readByte();
+ frame.w = animStream->readByte();
+
+ _frameEntries[i] = frame;
+
+ /*
+ printf(
+ "animFrameIndex = %4d, "
+ "u = %3d, "
+ "seriesIndex = %3d, "
+ "seriesFrameIndex = %6d, "
+ "x = %3d, "
+ "y = %3d, "
+ "v = %3d, "
+ "w = %3d\n",
+
+ frame.animFrameIndex,
+ frame.u,
+ frame.seriesIndex,
+ frame.seriesFrameIndex,
+ frame.x,
+ frame.y,
+ frame.v,
+ frame.w
+ );
+ */
+ }
+ //printf("End pos: %i\n", animStream->pos());
+
+ delete animStream;
+
+ // Chunk 3: unknown (seems to be sound data?)
+ // TODO
+}
+
+Animation::~Animation() {
+ //delete[] _spriteSeriesNames;
+ //delete[] _spriteSeries;
+ //delete[] _frameEntries;
+}
+
+void Animation::start() {
+ _curFrame = 0;
+ _curFrameEntry = 0;
+ //for (int i = 0; i < _seriesCount; i++) {
+ //_spriteSeries[i] = new SpriteSeries((char*)_spriteSeriesNames[i].c_str());
+ //}
+ _playing = true;
+ updateAnim();
+}
+
+bool Animation::updateAnim() {
+ if (!_playing)
+ return true;
+
+ // Get the scene background surface
+ M4Surface *bg = _vm->_scene->getBackgroundSurface();
+
+ while (_frameEntries[_curFrameEntry].animFrameIndex == _curFrame) {
+ AnimationFrame *frame = &_frameEntries[_curFrameEntry];
+ int seriesFrameIndex = (frame->seriesFrameIndex & 0x7FFF) - 1;
+
+ // Write the sprite onto the screen
+ M4Sprite *spr = _spriteSeries->getFrame(seriesFrameIndex);
+
+ // FIXME: We assume that the transparent color is the color of the top left pixel
+ byte *transparentColor = (byte *)spr->pixels;
+
+ // FIXME: correct x, y
+ spr->copyTo(bg, frame->x, frame->y, (int)*transparentColor);
+
+ // HACK: wait a bit
+ g_system->delayMillis(100);
+
+ //printf("_curFrameEntry = %d\n", _curFrameEntry);
+ _curFrameEntry++;
+ }
+
+ //printf("_curFrame = %d\n", _curFrame);
+
+ _curFrame++;
+ if (_curFrame >= _frameCount) // anim done
+ stop();
+
+ return _curFrame >= _frameCount;
+}
+
+void Animation::stop() {
+ _playing = false;
+
+ for (int i = 0; i < _seriesCount; i++) {
+ // TODO: cleanup
+ //delete _spriteSeries[i];
+ //_spriteSeries[i] = NULL;
+ }
+}
+
+} // End of namespace M4
diff --git a/engines/m4/animation.h b/engines/m4/animation.h
new file mode 100644
index 0000000000..6aafc53310
--- /dev/null
+++ b/engines/m4/animation.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_ANIMATION_H
+#define M4_ANIMATION_H
+
+#include "m4/m4.h"
+#include "m4/graphics.h"
+#include "m4/assets.h"
+
+namespace M4 {
+
+struct AnimationFrame {
+ uint16 animFrameIndex;
+ byte u;
+ byte seriesIndex;
+ uint16 seriesFrameIndex;
+ uint16 x, y;
+ byte v, w;
+};
+
+class Animation {
+ public:
+ Animation(M4Engine *vm);
+ ~Animation();
+
+ void load(const char *filename);
+ void loadFullScreen(const char *filename);
+ void start();
+ bool updateAnim();
+ void stop();
+
+ private:
+ bool _playing;
+ M4Engine *_vm;
+ int _seriesCount;
+ int _frameCount;
+ int _frameEntryCount;
+ AnimationFrame *_frameEntries;
+ Common::String *_spriteSeriesNames;
+ SpriteAsset *_spriteSeries;
+ int _curFrame, _curFrameEntry;
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/assets.cpp b/engines/m4/assets.cpp
new file mode 100644
index 0000000000..55decc2f00
--- /dev/null
+++ b/engines/m4/assets.cpp
@@ -0,0 +1,544 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/assets.h"
+#include "m4/globals.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+BaseAsset::BaseAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : _vm(vm) {
+}
+
+BaseAsset::~BaseAsset() {
+}
+
+MachineAsset::MachineAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : BaseAsset(vm, stream, size, name) {
+ uint32 stateCount = stream->readUint32LE();
+ for (uint32 curState = 0; curState < stateCount; curState++) {
+ uint32 stateOffset = stream->readUint32LE();
+ _stateTable.push_back(stateOffset);
+ }
+ _codeSize = size - 4 - 4 * stateCount;
+ _code = new byte[_codeSize];
+ stream->read(_code, _codeSize);
+}
+
+MachineAsset::~MachineAsset() {
+ delete[] _code;
+}
+
+void MachineAsset::getCode(byte *&code, uint32 &codeSize) {
+ code = _code;
+ codeSize = _codeSize;
+}
+
+uint32 MachineAsset::getStateOffset(uint32 state) {
+ assert(state < _stateTable.size());
+ return _stateTable[state];
+}
+
+SequenceAsset::SequenceAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : BaseAsset(vm, stream, size, name) {
+ _localVarCount = stream->readUint32LE();
+ _codeSize = size - 4;
+ _code = new byte[_codeSize];
+ stream->read(_code, _codeSize);
+}
+
+SequenceAsset::~SequenceAsset() {
+ delete[] _code;
+}
+
+void SequenceAsset::getCode(byte *&code, uint32 &codeSize) {
+ code = _code;
+ codeSize = _codeSize;
+}
+
+
+DataAsset::DataAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : BaseAsset(vm, stream, size, name) {
+
+ _recCount = stream->readUint32LE();
+ _recSize = stream->readUint32LE();
+ _dataSize = _recCount * _recSize;
+ _data = new long[_dataSize];
+ for (uint32 i = 0; i < _dataSize; i++)
+ _data[i] = (long)stream->readUint32LE();
+
+}
+
+DataAsset::~DataAsset() {
+ delete _data;
+}
+
+long *DataAsset::getRow(int index) {
+ assert(index < _recCount);
+ return &_data[_recSize * index];
+}
+
+SpriteAsset::SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name, bool asStream) : BaseAsset(vm, stream, size, name) {
+ _stream = stream;
+
+ if (_vm->isM4()) {
+ loadM4SpriteAsset(vm, stream, asStream);
+ } else {
+ loadMadsSpriteAsset(vm, stream);
+ }
+}
+
+void SpriteAsset::loadM4SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, bool asStream) {
+ bool isBigEndian = false;
+ uint32 frameOffset;
+
+ uint32 header = _stream->readUint32LE();
+ if (header == HEAD_M4SS) {
+ printf("LE-encoded sprite\n");
+ } else {
+ printf("BE-encoded sprite\n");
+ isBigEndian = true;
+ }
+
+ _srcSize = parseSprite(isBigEndian);
+
+ _stream->readUint32LE();
+ _frameRate = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ _pixelSpeed = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ _maxWidth = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ _maxHeight = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ _stream->skip(6 * 4);
+ _frameCount = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+
+ printf("SpriteAsset::SpriteAsset() srcSize = %d; frameRate = %04X; pixelSpeed = %04X; maxWidth = %d; maxHeight = %d; frameCount = %d\n", _srcSize, _frameRate, _pixelSpeed, _maxWidth, _maxHeight, _frameCount);
+
+ for (int curFrame = 0; curFrame < _frameCount; curFrame++) {
+ frameOffset = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ _frameOffsets.push_back(frameOffset);
+ }
+ _frameOffsets.push_back(_srcSize - 48 - _frameCount * 4);
+
+ _frameStartOffset = _stream->pos();
+
+ // We don't need to load frames when streaming
+ if (asStream)
+ return;
+
+ for (int curFrame = 0; curFrame < _frameCount; curFrame++) {
+ frameOffset = _frameStartOffset + _frameOffsets[curFrame];
+ _stream->seek(frameOffset);
+
+ SpriteAssetFrame frame;
+ loadFrameHeader(frame, isBigEndian);
+
+ // Load & unpack RLE data if it's not a streaming animation
+ if (frame.stream != 1) {
+
+ frame.frame = new M4Sprite(stream, frame.x, frame.y, frame.w, frame.h, true, frame.comp);
+#if 0
+ char fn[512];
+ sprintf(fn, "%04d.raw", curFrame);
+ FILE *h = fopen(fn, "wb");
+ fwrite((byte*)frame.frame->getData(), frame.w * frame.h, 1, h);
+ fclose(h);
+#endif
+ }
+
+ _frames.push_back(frame);
+
+ }
+
+}
+
+void SpriteAsset::loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream) {
+ int curFrame = 0;
+ uint32 frameOffset = 0;
+ MadsPack sprite(stream);
+ _frameRate = 0;
+ _pixelSpeed = 0;
+ _maxWidth = 0;
+ _maxHeight = 0;
+
+ Common::SeekableReadStream *spriteStream = sprite.getItemStream(0);
+ for (int i = 0; i < 19; i++) {
+ spriteStream->readUint16LE();
+ }
+ _frameCount = spriteStream->readUint16LE();
+ // we skip the rest of the data
+ delete spriteStream;
+
+ // Get the palette data
+ spriteStream = sprite.getItemStream(2);
+ int numColors = 0;
+ RGB8 *palData = Palette::decodeMadsPalette(spriteStream, &numColors);
+ Common::copy(palData, &palData[numColors], &_palette[0]);
+ if (numColors < 256)
+ Common::set_to((byte *)&_palette[numColors], (byte *)&_palette[256], 0);
+ _colorCount = numColors;
+ delete[] palData;
+ delete spriteStream;
+
+ spriteStream = sprite.getItemStream(1);
+ Common::SeekableReadStream *spriteDataStream = sprite.getItemStream(3);
+ SpriteAssetFrame frame;
+ for (curFrame = 0; curFrame < _frameCount; curFrame++) {
+ frame.comp = 0;
+ frameOffset = spriteStream->readUint32LE();
+ _frameOffsets.push_back(frameOffset);
+ spriteStream->readUint32LE(); // frame size
+ frame.x = spriteStream->readUint16LE();
+ frame.y = spriteStream->readUint16LE();
+ frame.w = spriteStream->readUint16LE();
+ frame.h = spriteStream->readUint16LE();
+ if (curFrame == 0)
+ printf("%i frames, x = %i, y = %i, w = %i, h = %i\n", _frameCount, frame.x, frame.y, frame.w, frame.h);
+
+ frame.frame = new M4Sprite(spriteDataStream, frame.x, frame.y, frame.w, frame.h, false);
+ _frames.push_back(frame);
+ }
+ delete spriteStream;
+ delete spriteDataStream;
+}
+
+SpriteAsset::~SpriteAsset() {
+ for (Common::Array<SpriteAssetFrame>::iterator it = _frames.begin(); it != _frames.end(); it++) {
+ delete (*it).frame;
+ }
+}
+
+int32 SpriteAsset::parseSprite(bool isBigEndian) {
+
+ uint32 format, chunkType, chunkSize = 0;
+
+ _colorCount = 0;
+
+ format = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+
+ chunkType = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+
+ if (chunkType == CELS__PAL) {
+ chunkSize = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ uint32 numColors = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ // TODO
+ //if (palette) {
+ // TODO: A sprite set palette specifies the indexes, which need not start at
+ // index 0. For now, I'm simply preloading the currently active palette
+ // before starting to replace existing entries
+
+ _vm->_palette->grabPalette((byte *) _palette, 0, 256);
+ _colorCount = 0;
+
+ for (uint32 i = 0; i < numColors; i++) {
+ uint32 paletteEntry = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ uint index = (paletteEntry >> 24) & 0xFF;
+
+ _palette[index].r = ((paletteEntry >> 16) & 0xFF) << 2;
+ _palette[index].g = ((paletteEntry >> 8) & 0xFF) << 2;
+ _palette[index].b = (paletteEntry & 0xFF) << 2;
+
+ _colorCount = MAX(_colorCount, index);
+ }
+
+ /*
+ } else {
+ stream.seek(colorCount, )
+ data += colorCount * 4;
+ }
+ */
+ chunkType = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ }
+
+ if (chunkType == CELS___SS) {
+ chunkSize = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ } else {
+ warning("SpriteAsset::parseSprite() Expected chunk type %08X, got %08X", CELS___SS, chunkType);
+ }
+
+ return chunkSize;
+
+}
+
+void SpriteAsset::loadFrameHeader(SpriteAssetFrame &frameHeader, bool isBigEndian) {
+ _stream->readUint32LE();
+ frameHeader.stream = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ frameHeader.x = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ frameHeader.y = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ frameHeader.w = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ frameHeader.h = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ frameHeader.comp = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE();
+ frameHeader.frame = NULL;
+ _stream->seek(8 * 4, SEEK_CUR);
+ //printf("SpriteAsset::loadFrameHeader() stream = %d; x = %d; y = %d; w = %d; h = %d; comp = %d\n", frameHeader.stream, frameHeader.x, frameHeader.y, frameHeader.w, frameHeader.h, frameHeader.comp);
+}
+
+M4Sprite *SpriteAsset::getFrame(int frameIndex) {
+ return _frames[frameIndex].frame;
+}
+
+void SpriteAsset::loadStreamingFrame(M4Sprite *frame, int frameIndex, int destX, int destY) {
+ uint32 frameOffset = _frameStartOffset + _frameOffsets[frameIndex];
+ _stream->seek(frameOffset);
+
+ SpriteAssetFrame frameHeader;
+ loadFrameHeader(frameHeader);
+
+ if (frameHeader.w > 0 && frameHeader.h > 0) {
+ Common::MemoryReadStream *frameData = _stream->readStream(getFrameSize(frameIndex));
+ if (frameHeader.stream) {
+ frame->loadDeltaRle(frameData, destX - frameHeader.x, destY - frameHeader.y);
+ } else {
+ frame->loadRle(frameData);
+ }
+ delete frameData;
+ }
+
+}
+
+RGBList *SpriteAsset::getRgbList() {
+ RGBList *result = new RGBList(_colorCount);
+ Common::copy((byte *)&_palette[0], (byte *)&_palette[_colorCount], (byte *)result->data());
+ return result;
+}
+
+void SpriteAsset::translate(RGBList *list, bool isTransparent) {
+ for (int frameIndex = 0; frameIndex < _frameCount; ++frameIndex)
+ _frames[frameIndex].frame->translate(list, isTransparent);
+}
+
+int32 SpriteAsset::getFrameSize(int index) {
+ /*
+ if (index + 1 == _frameCount) {
+ } else {
+
+ }
+ */
+ return _frameOffsets[index + 1] - _frameOffsets[index];
+}
+
+AssetManager::AssetManager(M4Engine *vm) {
+
+ _vm = vm;
+
+ /* Initialize asset arrays */
+ for (int i = 0; i < 256; i++) {
+ _MACH[i] = NULL;
+ _SEQU[i] = NULL;
+ _DATA[i] = NULL;
+ _CELS[i] = NULL;
+ }
+
+}
+
+AssetManager::~AssetManager() {
+ // unload all remaining assets
+ clearAssets(kAssetTypeMACH, 0, 255);
+ clearAssets(kAssetTypeSEQU, 0, 255);
+ clearAssets(kAssetTypeCELS, 0, 255);
+ clearAssets(kAssetTypeDATA, 0, 255);
+}
+
+bool AssetManager::clearAssets(AssetType assetType, int32 minHash, int32 maxHash) {
+
+ minHash = MAX(0, minHash);
+ maxHash = MIN(maxHash, 255);
+
+ switch (assetType) {
+ case kAssetTypeMACH:
+ for (int i = minHash; i <= maxHash; i++)
+ if (_MACH[i]) {
+ delete _MACH[i];
+ _MACH[i] = NULL;
+ }
+ break;
+ case kAssetTypeSEQU:
+ for (int i = minHash; i <= maxHash; i++)
+ if (_SEQU[i]) {
+ delete _SEQU[i];
+ _SEQU[i] = NULL;
+ }
+ break;
+ case kAssetTypeDATA:
+ for (int i = minHash; i <= maxHash; i++)
+ if (_DATA[i]) {
+ delete _DATA[i];
+ _DATA[i] = NULL;
+ }
+ break;
+ case kAssetTypeCELS:
+ for (int i = minHash; i <= maxHash; i++)
+ if (_CELS[i]) {
+ delete _CELS[i];
+ _CELS[i] = NULL;
+ }
+ break;
+ }
+
+ // FIXME: no value is returned, returning true for now
+ return true;
+}
+
+bool AssetManager::loadAsset(const char *assetName, RGB8 *palette) {
+
+ printf("AssetManager::loadAsset() %s\n", assetName);
+
+ // TODO, better use MemoryReadStreamEndian?
+ //convertAssetToLE(assetData, assetSize);
+
+ Common::SeekableReadStream *assetS = _vm->res()->get(assetName);
+
+ while (assetS->pos() + 12 < assetS->size()) {
+ uint32 chunkType, chunkSize, chunkHash;
+
+ chunkType = assetS->readUint32LE();
+ chunkSize = assetS->readUint32LE() - 12; // sub 12 for the chunk header
+ chunkHash = assetS->readUint32LE();
+
+ printf("hash = %d\n", chunkHash);
+
+ // Until loading code is complete, so that chunks not fully read are skipped correctly
+ uint32 nextOfs = assetS->pos() + chunkSize;
+
+ switch (chunkType) {
+ case CHUNK_MACH:
+ printf("MACH\n");
+ clearAssets(kAssetTypeMACH, chunkHash, chunkHash);
+ _MACH[chunkHash] = new MachineAsset(_vm, assetS, chunkSize, assetName);
+ break;
+ case CHUNK_SEQU:
+ printf("SEQU\n");
+ clearAssets(kAssetTypeSEQU, chunkHash, chunkHash);
+ _SEQU[chunkHash] = new SequenceAsset(_vm, assetS, chunkSize, assetName);
+ break;
+ case CHUNK_DATA:
+ printf("DATA\n");
+ clearAssets(kAssetTypeDATA, chunkHash, chunkHash);
+ _DATA[chunkHash] = new DataAsset(_vm, assetS, chunkSize, assetName);
+ break;
+ case CHUNK_CELS:
+ printf("CELS\n");
+ clearAssets(kAssetTypeCELS, chunkHash, chunkHash);
+ _CELS[chunkHash] = new SpriteAsset(_vm, assetS, chunkSize, assetName);
+ break;
+ default:
+ printf("AssetManager::loadAsset() Unknown chunk type %08X\n", chunkType);
+ }
+
+ // Until loading code is complete (see above)
+ assetS->seek(nextOfs);
+
+ }
+
+ _vm->res()->toss(assetName);
+
+ // FIXME: no value is returned, returning true for now
+ return true;
+}
+
+int32 AssetManager::addSpriteAsset(const char *assetName, int32 hash, RGB8 *palette) {
+
+ bool alreadyLoaded = false;
+
+ if (hash < 0) {
+ for (int i = 0; i <= 255; i++) {
+ if (_CELS[i] != NULL) {
+ if (_CELS[i]->getName() == assetName) {
+ alreadyLoaded = true;
+ hash = i;
+ break;
+ }
+ } else {
+ hash = i;
+ break;
+ }
+ }
+ } else {
+ alreadyLoaded = _CELS[hash] != NULL && _CELS[hash]->getName() == assetName;
+ }
+
+ /* Not loaded and no empty slots */
+ if (hash < 0)
+ return -1;
+
+ if (!alreadyLoaded) {
+
+ printf("AssetManager::addSpriteAsset() asset %s not loaded, loading into %d\n", assetName, hash);
+
+ clearAssets(kAssetTypeCELS, hash, hash);
+
+ Common::SeekableReadStream *assetS = _vm->res()->get(assetName);
+ _CELS[hash] = new SpriteAsset(_vm, assetS, assetS->size(), assetName);
+ _vm->res()->toss(assetName);
+
+ } else {
+
+ printf("AssetManager::addSpriteAsset() asset %s already loaded in %d\n", assetName, hash);
+
+ /* TODO/FIXME
+ if (_CELS[hash].palOffset >= 0 && palette)
+ restorePalette(palette, _CELS[hash].data + _CELS[hash].palOffset);
+ */
+
+ }
+
+ return hash;
+
+}
+
+void AssetManager::restorePalette(RGB8 *palette, byte *data) {
+ // TODO
+}
+
+void AssetManager::convertAssetToLE(byte *assetData, uint32 assetSize) {
+
+}
+
+MachineAsset *AssetManager::getMachine(int32 hash) {
+ assert(_MACH[hash] != NULL);
+ return _MACH[hash];
+}
+
+SequenceAsset *AssetManager::getSequence(int32 hash) {
+ assert(_SEQU[hash] != NULL);
+ return _SEQU[hash];
+}
+
+DataAsset *AssetManager::getData(int32 hash) {
+ assert(_DATA[hash] != NULL);
+ return _DATA[hash];
+}
+
+SpriteAsset *AssetManager::getSprite(int32 hash) {
+ assert(_CELS[hash] != NULL);
+ return _CELS[hash];
+}
+
+M4Sprite *AssetManager::getSpriteFrame(int32 hash, int frameIndex) {
+ assert(_CELS[hash] != NULL);
+ return _CELS[hash]->getFrame(frameIndex);
+}
+
+int32 AssetManager::getSpriteFrameCount(int32 hash) {
+ assert(_CELS[hash] != NULL);
+ return _CELS[hash]->getCount();
+}
+
+} // End of namespace M4
diff --git a/engines/m4/assets.h b/engines/m4/assets.h
new file mode 100644
index 0000000000..efc9afed83
--- /dev/null
+++ b/engines/m4/assets.h
@@ -0,0 +1,184 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+#ifndef M4_ASSETS_H
+#define M4_ASSETS_H
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+
+#include "m4/sprite.h"
+
+namespace M4 {
+
+// Sequence chunks
+#define CHUNK_SCEN MKID_BE('SCEN')
+#define CHUNK_MACH MKID_BE('MACH')
+#define CHUNK_SEQU MKID_BE('SEQU')
+#define CHUNK_DATA MKID_BE('DATA')
+#define CHUNK_CELS MKID_BE('CELS')
+
+// Sprite chunks
+#define HEAD_M4SS MKID_BE('M4SS') //'M4SS'
+#define CELS__PAL MKID_BE(' PAL') //' PAL'
+#define CELS___SS MKID_BE(' SS') //' SS'
+
+class M4Engine;
+
+class BaseAsset {
+public:
+ BaseAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name);
+ ~BaseAsset();
+ const Common::String getName() const { return _name; }
+protected:
+ M4Engine *_vm;
+ Common::String _name;
+};
+
+class MachineAsset : public BaseAsset {
+public:
+ MachineAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name);
+ ~MachineAsset();
+ void getCode(byte *&code, uint32 &codeSize);
+ uint32 getStateOffset(uint32 state);
+protected:
+ Common::Array<uint32> _stateTable;
+ byte *_code;
+ uint32 _codeSize;
+};
+
+class SequenceAsset : public BaseAsset {
+public:
+ SequenceAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name);
+ ~SequenceAsset();
+ void getCode(byte *&code, uint32 &codeSize);
+ int localVarCount() const { return _localVarCount; }
+protected:
+ int _localVarCount;
+ byte *_code;
+ uint32 _codeSize;
+};
+
+class DataAsset : public BaseAsset {
+public:
+ DataAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name);
+ ~DataAsset();
+ int getCount() const { return _recCount; }
+ long *getRow(int index);
+protected:
+ long *_data;
+ uint32 _recSize, _dataSize;
+ int _recCount;
+};
+
+struct SpriteAssetFrame {
+ uint32 stream;
+ int x, y, w, h;
+ uint32 comp;
+ M4Sprite *frame;
+};
+
+class SpriteAsset : public BaseAsset {
+public:
+ SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name, bool asStream = false);
+ ~SpriteAsset();
+ void loadM4SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, bool asStream);
+ void loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream);
+ int32 getCount() { return _frameCount; }
+ int32 getFrameRate() const { return _frameRate; }
+ int32 getPixelSpeed() const { return _pixelSpeed; }
+ int32 getFrameWidth(int index);
+ int32 getFrameHeight(int index);
+ int32 getMaxFrameWidth() const { return _maxWidth; }
+ int32 getMaxFrameHeight() const { return _maxHeight; }
+ M4Sprite *getFrame(int frameIndex);
+ void loadStreamingFrame(M4Sprite *frame, int frameIndex, int destX, int destY);
+ RGB8* getPalette() { return _palette; }
+ int getColorCount() { return _colorCount; }
+ RGBList *getRgbList();
+ void translate(RGBList *list, bool isTransparent = false);
+ int32 getFrameSize(int index);
+ M4Sprite *operator[](int index) { return getFrame(index); }
+protected:
+ RGB8 _palette[256];
+ uint32 _colorCount;
+ uint32 _srcSize;
+ int32 _frameRate, _pixelSpeed;
+ int _maxWidth, _maxHeight;
+ int _frameCount;
+ Common::Array<uint32> _frameOffsets;
+ Common::Array<SpriteAssetFrame> _frames;
+ uint32 _frameStartOffset;
+ Common::SeekableReadStream *_stream;
+ int32 parseSprite(bool isBigEndian = false);
+ void loadFrameHeader(SpriteAssetFrame &frameHeader, bool isBigEndian = false);
+};
+
+enum AssetType {
+ kAssetTypeMACH,
+ kAssetTypeSEQU,
+ kAssetTypeDATA,
+ kAssetTypeCELS
+};
+
+enum CallbackHandlers {
+ kCallbackTriggerDispatch
+};
+
+class AssetManager {
+public:
+
+ AssetManager(M4Engine *vm);
+ ~AssetManager();
+
+ bool clearAssets(AssetType assetType, int32 minHash, int32 maxHash);
+ bool loadAsset(const char *assetName, RGB8 *palette);
+ int32 addSpriteAsset(const char *assetName, int32 hash, RGB8 *palette);
+
+ // TODO: Move to Palette class
+ void restorePalette(RGB8 *palette, byte *data);
+
+ MachineAsset *getMachine(int32 hash);
+ SequenceAsset *getSequence(int32 hash);
+ DataAsset *getData(int32 hash);
+ SpriteAsset *getSprite(int32 hash);
+ M4Sprite *getSpriteFrame(int32 hash, int frameIndex);
+ int32 getSpriteFrameCount(int32 hash);
+
+protected:
+ // TODO: Check if we need _vm
+ M4Engine *_vm;
+
+ MachineAsset *_MACH[256];
+ SequenceAsset *_SEQU[256];
+ DataAsset *_DATA[256];
+ SpriteAsset *_CELS[256];
+
+ void convertAssetToLE(byte *assetData, uint32 assetSize);
+
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/burger_data.h b/engines/m4/burger_data.h
new file mode 100644
index 0000000000..000d0a9654
--- /dev/null
+++ b/engines/m4/burger_data.h
@@ -0,0 +1,85 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_BURGER_DATA_H
+#define M4_BURGER_DATA_H
+
+#include "m4/graphics.h"
+#include "m4/actor.h"
+
+namespace M4 {
+
+InventoryObject burger_inventory [] = {
+ // name scene icon
+ //-------------------- ----- -----
+ { "empty jug", 303, 14 },
+ { "distilled juice", 999, 15 },
+ { "broken puz dispenser", 999, 16 },
+ { "puz dispenser", 999, 17 },
+ { "broken mouse trap", 999, 18 },
+ { "mouse trap", 999, 19 },
+ { "kindling", 999, 20 },
+ { "burning kindling", 999, 21 },
+ { "lights", 508, 22 },
+ { "lights on", 508, 23 },
+ { "bottle", 999, 24 },
+ { "carrot juice", 999, 25 },
+ { "soapy water", 999, 26 },
+ { "iron filings", 999, 27 },
+ { "waxed hair", 999, 28 },
+ { "fish", 999, 29 },
+ { "hook", 999, 30 },
+ { "keys", 999, 31 },
+ { "records", 999, 32 },
+ { "collar", 999, 33 },
+ { "amp", 999, 34 },
+ { "rubber gloves", 999, 35 },
+ { "sock", 504, 36 },
+ { "jaws of life", 999, 37 },
+ { "deed", 999, 38 },
+ { "burger morsel", 999, 39 },
+ { "whistle", 999, 40 },
+ { "coin", 999, 41 },
+ { "matches", 999, 42 },
+ { "phone cord", 999, 43 },
+ { "kibble", 602, 44 }, // picked up from tray
+ { "pantyhose", 999, 45 },
+ { "fan belt", 999, 46 },
+ { "spring", 999, 47 },
+ { "mirror", 999, 48 },
+ { "grate", 999, 49 },
+ { "ray gun", 604, 50 }, // given to Wilbur when he enters 604
+ { "grasshoppers", 999, 51 },
+ { "rolling pin", 999, 52 },
+ { "rubber duck", 999, 53 },
+ { "ladder", 999, 54 },
+ { "money", 999, 55 },
+ { "crow bar", 999, 56 },
+ { "Wilbur", 999, 57 }
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/compression.cpp b/engines/m4/compression.cpp
new file mode 100644
index 0000000000..53c801e0b7
--- /dev/null
+++ b/engines/m4/compression.cpp
@@ -0,0 +1,189 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/compression.h"
+#include "m4/m4.h"
+
+namespace M4 {
+
+const char *madsPackString = "MADSPACK";
+
+bool MadsPack::isCompressed(Common::SeekableReadStream *stream) {
+ // Check whether the passed stream is packed
+
+ char tempBuffer[8];
+ stream->seek(0);
+ if (stream->read(tempBuffer, 8) == 8) {
+ if (!strncmp(tempBuffer, madsPackString, 8))
+ return true;
+ }
+
+ return false;
+}
+
+MadsPack::MadsPack(Common::SeekableReadStream *stream) {
+ initialise(stream);
+}
+
+MadsPack::MadsPack(const char *resourceName, M4Engine* vm) {
+ Common::SeekableReadStream *stream = vm->_resourceManager->get(resourceName);
+ initialise(stream);
+ vm->_resourceManager->toss(resourceName);
+}
+
+void MadsPack::initialise(Common::SeekableReadStream *stream) {
+ if (!MadsPack::isCompressed(stream))
+ error("Attempted to decompress a resource that was not MadsPacked");
+
+ stream->seek(14);
+ _count = stream->readUint16LE();
+ _items = new MadsPackEntry[_count];
+
+ byte *headerData = new byte[0xA0];
+ byte *header = headerData;
+ stream->read(headerData, 0xA0);
+
+ for (int i = 0; i < _count; ++i, header += 10) {
+ // Get header data
+ _items[i].hash = READ_LE_UINT16(header);
+ _items[i].size = READ_LE_UINT32(header + 2);
+ _items[i].compressedSize = READ_LE_UINT32(header + 6);
+
+ _items[i].data = new byte[_items[i].size];
+ if (_items[i].size == _items[i].compressedSize) {
+ // Entry isn't compressed
+ stream->read(_items[i].data, _items[i].size);
+ } else {
+ // Decompress the entry
+ byte *compressedData = new byte[_items[i].compressedSize];
+ stream->read(compressedData, _items[i].compressedSize);
+
+ FabDecompressor fab;
+ fab.decompress(compressedData, _items[i].compressedSize, _items[i].data, _items[i].size);
+ delete[] compressedData;
+ }
+ }
+
+ delete[] headerData;
+ _dataOffset = stream->pos();
+}
+
+MadsPack::~MadsPack() {
+ for (int i = 0; i < _count; ++i)
+ delete[] _items[i].data;
+ delete[] _items;
+}
+
+//--------------------------------------------------------------------------
+
+const char *FabInputExceededError = "FabDecompressor - Passed end of input buffer during decompression";
+const char *FabOutputExceededError = "FabDecompressor - Decompressed data exceeded specified size";
+
+void FabDecompressor::decompress(const byte *srcData, int srcSize, byte *destData, int destSize) {
+ byte copyLen, copyOfsShift, copyOfsMask, copyLenMask;
+ unsigned long copyOfs;
+ byte *destP;
+
+ // Validate that the data starts with the FAB header
+ if (strncmp((const char *)srcData, "FAB", 3) != 0)
+ error("FabDecompressor - Invalid compressed data");
+
+ int shiftVal = srcData[3];
+ if ((shiftVal < 10) || (shiftVal > 13))
+ error("FabDecompressor - Invalid shift start");
+
+ copyOfsShift = 16 - shiftVal;
+ copyOfsMask = 0xFF << (shiftVal - 8);
+ copyLenMask = (1 << copyOfsShift) - 1;
+ copyOfs = 0xFFFF0000;
+ destP = destData;
+
+ // Initialise data fields
+ _srcData = srcData;
+ _srcP = _srcData + 6;
+ _srcSize = srcSize;
+ _bitsLeft = 16;
+ _bitBuffer = READ_LE_UINT16(srcData + 4);
+
+ for (;;) {
+ if (getBit() == 0) {
+ if (getBit() == 0) {
+ copyLen = ((getBit() << 1) | getBit()) + 2;
+ copyOfs = *_srcP++ | 0xFFFFFF00;
+ } else {
+ copyOfs = (((_srcP[1] >> copyOfsShift) | copyOfsMask) << 8) | _srcP[0];
+ copyLen = _srcP[1] & copyLenMask;
+ _srcP += 2;
+ if (copyLen == 0) {
+ copyLen = *_srcP++;
+ if (copyLen == 0)
+ break;
+ else if (copyLen == 1)
+ continue;
+ else
+ copyLen++;
+ } else {
+ copyLen += 2;
+ }
+ copyOfs |= 0xFFFF0000;
+ }
+ while (copyLen-- > 0) {
+ if (destP - destData == destSize)
+ error(FabOutputExceededError);
+
+ *destP = destP[(signed int)copyOfs];
+ destP++;
+ }
+ } else {
+ if (_srcP - srcData == srcSize)
+ error(FabInputExceededError);
+ if (destP - destData == destSize)
+ error(FabOutputExceededError);
+
+ *destP++ = *_srcP++;
+ }
+ }
+
+ if (destP - destData != destSize)
+ error("FabDecompressor - Decompressed data does not match header decompressed size");
+}
+
+int FabDecompressor::getBit() {
+ _bitsLeft--;
+ if (_bitsLeft == 0) {
+ if (_srcP - _srcData == _srcSize)
+ error(FabInputExceededError);
+
+ _bitBuffer = (READ_LE_UINT16(_srcP) << 1) | (_bitBuffer & 1);
+ _srcP += 2;
+ _bitsLeft = 16;
+ }
+
+ int bit = _bitBuffer & 1;
+ _bitBuffer >>= 1;
+ return bit;
+}
+
+} // End of namespace M4
diff --git a/engines/m4/compression.h b/engines/m4/compression.h
new file mode 100644
index 0000000000..11e419951e
--- /dev/null
+++ b/engines/m4/compression.h
@@ -0,0 +1,82 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_COMPRESSION_H
+#define M4_COMPRESSION_H
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/endian.h"
+
+#include "m4/m4.h"
+
+namespace M4 {
+
+struct MadsPackEntry {
+public:
+ uint16 hash;
+ uint32 size;
+ uint32 compressedSize;
+ byte *data;
+};
+
+class MadsPack {
+private:
+ MadsPackEntry *_items;
+ int _count;
+ int _dataOffset;
+
+ void initialise(Common::SeekableReadStream *stream);
+public:
+ static bool isCompressed(Common::SeekableReadStream *stream);
+ MadsPack(Common::SeekableReadStream *stream);
+ MadsPack(const char *resourceName, M4Engine* _vm);
+ ~MadsPack();
+
+ int getCount() const { return _count; }
+ MadsPackEntry &getItem(int index) const { return _items[index]; }
+ MadsPackEntry &operator[](int index) const { return _items[index]; }
+ Common::MemoryReadStream *getItemStream(int index) {
+ return new Common::MemoryReadStream(_items[index].data, _items[index].size, false);
+ }
+ int getDataOffset() const { return _dataOffset; }
+};
+
+class FabDecompressor {
+private:
+ int _bitsLeft;
+ uint32 _bitBuffer;
+ const byte *_srcData, *_srcP;
+ int _srcSize;
+
+ int getBit();
+public:
+ void decompress(const byte *srcData, int srcSize, byte *destData, int destSize);
+};
+
+} // End of namespace M4
+
+
+#endif
diff --git a/engines/m4/console.cpp b/engines/m4/console.cpp
new file mode 100644
index 0000000000..ccf6d98f33
--- /dev/null
+++ b/engines/m4/console.cpp
@@ -0,0 +1,287 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/m4.h"
+#include "m4/console.h"
+#include "m4/scene.h"
+
+namespace M4 {
+
+Console::Console(M4Engine *vm) : GUI::Debugger() {
+ _vm = vm;
+
+ DCmd_Register("scene", WRAP_METHOD(Console, cmdLoadScene));
+ DCmd_Register("start", WRAP_METHOD(Console, cmdStartingScene));
+ DCmd_Register("scene_info", WRAP_METHOD(Console, cmdSceneInfo));
+ DCmd_Register("show_hotspots", WRAP_METHOD(Console, cmdShowHotSpots));
+ DCmd_Register("list_hotspots", WRAP_METHOD(Console, cmdListHotSpots));
+ DCmd_Register("play_sound", WRAP_METHOD(Console, cmdPlaySound));
+ DCmd_Register("play_dsr_sound", WRAP_METHOD(Console, cmdPlayDSRSound));
+ DCmd_Register("show_resources", WRAP_METHOD(Console, cmdShowResources));
+ DCmd_Register("show_codes", WRAP_METHOD(Console, cmdShowCodes));
+ DCmd_Register("dump_file", WRAP_METHOD(Console, cmdDumpFile));
+ DCmd_Register("sprite", WRAP_METHOD(Console, cmdShowSprite));
+ DCmd_Register("start_conv", WRAP_METHOD(Console, cmdStartConversation));
+ DCmd_Register("textview", WRAP_METHOD(Console, cmdShowTextview));
+ DCmd_Register("animview", WRAP_METHOD(Console, cmdShowAnimview));
+ DCmd_Register("anim", WRAP_METHOD(Console, cmdPlayAnimation));
+}
+
+Console::~Console() {
+}
+
+bool Console::cmdLoadScene(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s <scene number>\n", argv[0]);
+ return true;
+ } else {
+ if (_vm->isM4())
+ _vm->_kernel->newRoom = atoi(argv[1]);
+ else
+ _vm->_scene->loadScene(atoi(argv[1]));
+ return false;
+ }
+}
+
+bool Console::cmdStartingScene(int argc, const char **argv) {
+ if (_vm->getGameType() != GType_Riddle) {
+ if (_vm->isM4())
+ _vm->_kernel->newRoom = FIRST_SCENE;
+ else
+ _vm->_scene->loadScene(FIRST_SCENE);
+ return false;
+ } else {
+ DebugPrintf("%s: Riddle of Master Lu is not supported", argv[0]);
+ return true;
+ }
+}
+
+bool Console::cmdShowHotSpots(int argc, const char **argv) {
+ _vm->_scene->showHotSpots();
+ return false;
+}
+
+bool Console::cmdSceneInfo(int argc, const char **argv) {
+ DebugPrintf("Current scene is: %i\n", _vm->_scene->getCurrentScene());
+ if (_vm->isM4()) {
+ DebugPrintf("Scene resources:\n");
+ DebugPrintf("artBase: %s\n", _vm->_scene->getSceneResources().artBase);
+ DebugPrintf("pictureBase: %s\n", _vm->_scene->getSceneResources().pictureBase);
+ DebugPrintf("hotspotCount: %i\n", _vm->_scene->getSceneResources().hotspotCount);
+ DebugPrintf("parallaxCount: %i\n", _vm->_scene->getSceneResources().parallaxCount);
+ DebugPrintf("propsCount: %i\n", _vm->_scene->getSceneResources().propsCount);
+ DebugPrintf("frontY: %i\n", _vm->_scene->getSceneResources().frontY);
+ DebugPrintf("backY: %i\n", _vm->_scene->getSceneResources().backY);
+ DebugPrintf("frontScale: %i\n", _vm->_scene->getSceneResources().frontScale);
+ DebugPrintf("backScale: %i\n", _vm->_scene->getSceneResources().backScale);
+ DebugPrintf("depthTable: ");
+ for (uint i = 0; i < 16; i++)
+ DebugPrintf("%i ", _vm->_scene->getSceneResources().depthTable[i]);
+ DebugPrintf("\n");
+ DebugPrintf("railNodeCount: %i\n", _vm->_scene->getSceneResources().railNodeCount);
+ }
+ return true;
+}
+
+bool Console::cmdListHotSpots(int argc, const char **argv) {
+ DebugPrintf("Scene hotspots\n");
+ _vm->_scene->getSceneResources().hotspots->dump();
+ if (_vm->isM4()) {
+ DebugPrintf("Scene parallax\n");
+ _vm->_scene->getSceneResources().parallax->dump();
+ DebugPrintf("Scene props\n");
+ _vm->_scene->getSceneResources().props->dump();
+ }
+ return true;
+}
+
+bool Console::cmdPlaySound(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s <sound file>\n", argv[0]);
+ } else {
+ _vm->_sound->playSound(argv[1], 255, false);
+ }
+ return true;
+}
+
+bool Console::cmdPlayDSRSound(int argc, const char **argv) {
+ if (argc != 2 && argc != 3) {
+ DebugPrintf("Usage: %s <sound index> <DSR file>\n", argv[0]);
+ DebugPrintf("The DSR file parameter is optional, and specifies which DSR to load\n", argv[0]);
+ } else {
+ if (argc == 3)
+ _vm->_sound->loadDSRFile(argv[2]);
+ _vm->_sound->playDSRSound(atoi(argv[1]), 255, false);
+ }
+ return true;
+}
+
+bool Console::cmdShowResources(int argc, const char **argv) {
+ _vm->res()->dump();
+ return true;
+}
+
+bool Console::cmdShowCodes(int argc, const char **argv) {
+ if (_vm->getGameType() != GType_RexNebular)
+ _vm->_scene->showCodes();
+ else
+ DebugPrintf("Pathfinding codes not done yet for Rex Nebular");
+ return false;
+}
+
+bool Console::cmdDumpFile(int argc, const char **argv) {
+ if (argc != 2 && argc != 3) {
+ DebugPrintf("Usage: %s <file> <uncompress>\n", argv[0]);
+ DebugPrintf("If uncompress is 1, the file is uncompressed (for MADS games)\n");
+ } else {
+ if (argc == 2) {
+ _vm->dumpFile(strdup(argv[1]));
+ } else {
+ if (argc == 3 && atoi(argv[2]) == 1)
+ _vm->dumpFile(strdup(argv[1]), true);
+ else
+ _vm->dumpFile(strdup(argv[1]));
+ }
+ }
+ return true;
+}
+
+bool Console::cmdShowSprite(int argc, const char **argv) {
+ View *view = _vm->_viewManager->getView(VIEWID_SCENE);
+ if (view == NULL)
+ DebugPrintf("The scene view isn't currently active\n");
+ else if (argc < 2)
+ DebugPrintf("Usage: %s resource_name\n", argv[0]);
+ else {
+ char resourceName[20];
+ strncpy(resourceName, argv[1], 15);
+ resourceName[15] = '\0';
+ if (!strchr(resourceName, '.'))
+ strcat(resourceName, ".SS");
+
+ _vm->_viewManager->moveToFront(view);
+ Common::SeekableReadStream *data = _vm->res()->get(resourceName);
+ SpriteAsset *asset = new SpriteAsset(_vm, data, data->size(), resourceName);
+ _vm->res()->toss(resourceName);
+
+ RGBList *palData = new RGBList(asset->getColorCount(), asset->getPalette(), true);
+ _vm->_palette->addRange(palData);
+
+ // Get the scene background surface
+ M4Surface *bg = _vm->_scene->getBackgroundSurface();
+
+ // Write the sprite onto the screen
+ int x = 0, y = 0, yMax = 0;
+ for (int index = 0; index < asset->getCount(); index++) {
+ M4Sprite *spr = asset->getFrame(index);
+ spr->translate(palData); // sprite pixel translation
+
+ if ((x + spr->width() >= bg->width()) && (yMax != 0)) {
+ x = 0;
+ y += yMax;
+ yMax = 0;
+ }
+
+ if (y >= bg->height())
+ break;
+
+ // FIXME: We assume that the transparent color is the color of the top left pixel
+ byte *transparentColor = (byte *)spr->pixels;
+
+ spr->copyTo(bg, x, y, (int)*transparentColor);
+
+ x += spr->width();
+ yMax = MAX(yMax, spr->height());
+ }
+
+ view->restore(0, 0, view->width(), view->height());
+ return false;
+ }
+
+ return true;
+}
+
+bool Console::cmdStartConversation(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s <conversation file name>\n", argv[0]);
+ return true;
+ } else {
+ _vm->_converse->startConversation(argv[1]);
+ return false;
+ }
+}
+
+bool Console::cmdShowTextview(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s <txr resource>\n", argv[0]);
+ return true;
+ }
+
+ _vm->_viewManager->showTextView(argv[1], false);
+ return false;
+}
+
+bool Console::cmdShowAnimview(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s <res resource>\n", argv[0]);
+ return true;
+ }
+
+ char resName[80];
+ strcpy(resName, "@");
+ strcat(resName, *argv[1] == '@' ? argv[1] + 1 : argv[1]);
+
+ _vm->_viewManager->showAnimView(resName, false);
+ return false;
+}
+
+bool Console::cmdPlayAnimation(int argc, const char **argv) {
+ View *view = _vm->_viewManager->getView(VIEWID_SCENE);
+ if (view == NULL) {
+ DebugPrintf("The scene view isn't currently active\n");
+ } else if (argc != 2 && argc != 3) {
+ DebugPrintf("Usage: %s <anim resource (*.aa)> <fullscreen>\n", argv[0]);
+ DebugPrintf("If fullscreen is 1, the screen palette is replaced with the palette of the animation\n");
+ } else {
+ char resourceName[20];
+ strncpy(resourceName, argv[1], 15);
+ resourceName[15] = '\0';
+ if (!strchr(resourceName, '.'))
+ strcat(resourceName, ".AA");
+
+ _vm->_viewManager->moveToFront(view);
+ if (argc == 3 && atoi(argv[2]) == 1)
+ _vm->_animation->loadFullScreen(resourceName);
+ else
+ _vm->_animation->load(resourceName);
+ _vm->_animation->start();
+ view->restore(0, 0, view->width(), view->height());
+ return false;
+ }
+
+ return true;
+}
+
+} // End of namespace M4
diff --git a/engines/m4/console.h b/engines/m4/console.h
new file mode 100644
index 0000000000..45c9d1abec
--- /dev/null
+++ b/engines/m4/console.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_CONSOLE_H
+#define M4_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace M4 {
+
+class M4Engine;
+
+class Console : public GUI::Debugger {
+public:
+ Console(M4Engine *vm);
+ virtual ~Console(void);
+
+private:
+ bool cmdLoadScene(int argc, const char **argv);
+ bool cmdStartingScene(int argc, const char **argv);
+ bool cmdSceneInfo(int argc, const char **argv);
+ bool cmdShowHotSpots(int argc, const char **argv);
+ bool cmdListHotSpots(int argc, const char **argv);
+ bool cmdPlaySound(int argc, const char **argv);
+ bool cmdPlayDSRSound(int argc, const char **argv);
+ bool cmdShowResources(int argc, const char **argv);
+ bool cmdShowCodes(int argc, const char **argv);
+ bool cmdDumpFile(int argc, const char **argv);
+ bool cmdShowSprite(int argc, const char **argv);
+ bool cmdStartConversation(int argc, const char **argv);
+ bool cmdShowTextview(int argc, const char **argv);
+ bool cmdShowAnimview(int argc, const char **argv);
+ bool cmdPlayAnimation(int argc, const char **argv);
+
+private:
+ M4Engine *_vm;
+};
+
+} // End of namespace M4
+
+
+#endif
diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp
new file mode 100644
index 0000000000..68daa08942
--- /dev/null
+++ b/engines/m4/converse.cpp
@@ -0,0 +1,1211 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/array.h"
+#include "common/hashmap.h"
+
+#include "m4/converse.h"
+#include "m4/resource.h"
+#include "m4/globals.h"
+#include "m4/m4_views.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+#define CONV_ENTRIES_X_OFFSET 20
+#define CONV_ENTRIES_Y_OFFSET 4
+#define CONV_ENTRIES_HEIGHT 20
+#define CONV_MAX_SHOWN_ENTRIES 5
+
+#define CONVERSATION_ENTRY_HIGHLIGHTED 22
+#define CONVERSATION_ENTRY_NORMAL 3
+
+// Conversation chunks
+// Header
+#define HEAD_CONV MKID_BE('CONV') // conversation
+#define CHUNK_DECL MKID_BE('DECL') // declaration
+#define CHUNK_NODE MKID_BE('NODE') // node
+#define CHUNK_LNOD MKID_BE('LNOD') // linear node
+#define CHUNK_ETRY MKID_BE('ETRY') // entry
+#define CHUNK_TEXT MKID_BE('TEXT') // text
+#define CHUNK_MESG MKID_BE('MESG') // message
+// Conversation chunks - entry related (unconditional)
+#define CHUNK_RPLY MKID_BE('RPLY') // reply
+#define CHUNK_HIDE MKID_BE('HIDE') // hide entry
+#define CHUNK_UHID MKID_BE('UHID') // unhide entry
+#define CHUNK_DSTR MKID_BE('DSTR') // destroy entry
+// Conversation chunks - entry related (conditional)
+#define CHUNK_CRPL MKID_BE('CRPL') // reply
+#define CHUNK_CHDE MKID_BE('CHDE') // hide entry
+#define CHUNK_CUHD MKID_BE('CUHD') // unhide entry
+#define CHUNK_CDST MKID_BE('DDTS') // destroy entry
+// Conversation chunks - branching and logic (unconditional)
+#define CHUNK_ASGN MKID_BE('ASGN') // assign
+#define CHUNK_GOTO MKID_BE('GOTO') // goto chunk
+#define CHUNK_EXIT MKID_BE('EXIT') // exit/return from goto
+// Conversation chunks - branching and logic (conditional)
+#define CHUNK_CASN MKID_BE('CASN') // assign
+#define CHUNK_CCGO MKID_BE('CCGO') // goto chunk
+#define CHUNK_CEGO MKID_BE('CEGO') // exit/return from goto
+// Others
+#define CHUNK_FALL MKID_BE('FALL') // fallthrough
+#define CHUNK_WRPL MKID_BE('WRPL') // weighted reply chunk
+#define CHUNK_WPRL MKID_BE('WPRL') // weighted preply chunk
+
+
+ConversationView::ConversationView(M4Engine *vm): View(vm, Common::Rect(0,
+ vm->_screen->height() - INTERFACE_HEIGHT, vm->_screen->width(), vm->_screen->height())) {
+
+ _screenType = VIEWID_CONVERSATION;
+ _screenFlags.layer = LAYER_INTERFACE;
+ _screenFlags.visible = false;
+ _screenFlags.get = SCREVENT_MOUSE;
+ _conversationState = kNoConversation;
+ _currentHandle = NULL;
+}
+
+ConversationView::~ConversationView() {
+ _activeItems.clear();
+}
+
+void ConversationView::setNode(int32 nodeIndex) {
+ _highlightedIndex = -1;
+ _xEnd = CONV_ENTRIES_X_OFFSET;
+ _vm->_font->setFont(FONT_CONVERSATION);
+
+ // TODO: Conversation styles and colors
+ _vm->_font->setColors(2, 1, 3);
+
+ _currentNodeIndex = nodeIndex;
+
+ _activeItems.clear();
+
+ if (nodeIndex != -1) {
+ ConvEntry *node = _vm->_converse->getNode(nodeIndex);
+
+ for (uint i = 0; i < node->entries.size(); ++i) {
+ if (!node->entries[i]->visible)
+ continue;
+
+ if ((int)_activeItems.size() > CONV_MAX_SHOWN_ENTRIES) {
+ warning("TODO: scrolling. Max shown entries are %i, skipping entry %i",
+ CONV_MAX_SHOWN_ENTRIES, i);
+ }
+
+ // Add node to active items list
+ _activeItems.push_back(node->entries[i]);
+
+ if (node->entries[i]->autoSelect || strlen(node->entries[i]->text) == 0) {
+ //printf("Auto selecting entry %i of node %i\n", i, nodeIndex);
+ selectEntry(i);
+ return;
+ }
+
+ // Figure out the longest string to determine where option highlighting ends
+ int tempX = _vm->_font->getWidth(node->entries[i]->text, 0) +
+ CONV_ENTRIES_X_OFFSET + 10;
+ _xEnd = MAX(_xEnd, tempX);
+ }
+
+ // Make sure that there aren't too many entries
+ //assert((int)_activeItems.size() < (height() - CONV_ENTRIES_Y_OFFSET) / CONV_ENTRIES_HEIGHT);
+
+ // Fallthrough
+ if (node->fallthroughMinEntries >= 0 && node->fallthroughOffset >= 0) {
+ //printf("Current node falls through node at offset %i when entries are less or equal than %i\n",
+ // node->fallthroughOffset, node->fallthroughMinEntries);
+ if (_activeItems.size() <= (uint32)node->fallthroughMinEntries) {
+ const EntryInfo *entryInfo = _vm->_converse->getEntryInfo(node->fallthroughOffset);
+ //printf("Entries are less than or equal to %i, falling through to node at offset %i, index %i\n",
+ // node->fallthroughMinEntries, node->fallthroughOffset, entryInfo->nodeIndex);
+ setNode(entryInfo->nodeIndex);
+ return;
+ }
+ }
+
+ _entriesShown = true;
+ _conversationState = kConversationOptionsShown;
+ }
+}
+
+void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) {
+ //if (!this->isVisible())
+ // return;
+ empty();
+
+ if (_entriesShown) {
+ // Write out the conversation options
+ _vm->_font->setFont(FONT_CONVERSATION);
+ for (int i = 0; i < (int)_activeItems.size(); ++i) {
+ // TODO: scrolling
+ if (i > CONV_MAX_SHOWN_ENTRIES - 1)
+ break;
+
+ _vm->_font->setColor((_highlightedIndex == i) ? CONVERSATION_ENTRY_HIGHLIGHTED :
+ CONVERSATION_ENTRY_NORMAL);
+
+ _vm->_font->writeString(this, _activeItems[i]->text, CONV_ENTRIES_X_OFFSET,
+ CONV_ENTRIES_Y_OFFSET + CONV_ENTRIES_HEIGHT * i, 0, 0);
+ }
+ }
+ View::onRefresh(rects, destSurface);
+}
+
+bool ConversationView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ //if (!this->isVisible())
+ // return false;
+ if (!_entriesShown)
+ return false;
+ if (eventType == KEVENT_KEY)
+ return false;
+
+ int localY = y - _coords.top;
+ int selectedIndex = _highlightedIndex;
+
+ switch (eventType) {
+ case MEVENT_MOVE:
+ if ((x < CONV_ENTRIES_X_OFFSET) || (x >= _xEnd) || (localY < CONV_ENTRIES_Y_OFFSET))
+ _highlightedIndex = -1;
+ else {
+ int index = (localY - CONV_ENTRIES_Y_OFFSET) / CONV_ENTRIES_HEIGHT;
+ _highlightedIndex = (index >= (int)_activeItems.size()) ? -1 : index;
+ }
+ break;
+
+ case MEVENT_LEFT_RELEASE:
+ if (_highlightedIndex != -1) {
+ selectEntry(selectedIndex);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void ConversationView::selectEntry(int entryIndex) {
+ char buffer[20];
+ sprintf(buffer, "%s.raw", _activeItems[entryIndex]->voiceFile);
+
+ _entriesShown = false;
+ _conversationState = kEntryIsActive;
+ _vm->_player->setCommandsAllowed(false);
+ // Necessary, as entries can be selected programmatically
+ _highlightedIndex = entryIndex;
+
+ // Play the selected entry's voice
+ if (strlen(_activeItems[entryIndex]->voiceFile) > 0) {
+ _currentHandle = _vm->_sound->getHandle();
+ _vm->_sound->playVoice(buffer, 255);
+ } else {
+ _currentHandle = NULL;
+ }
+
+ // Hide selected entry, unless it has a persistent flag set
+ if (!(_activeItems[entryIndex]->flags & kEntryPersists)) {
+ //printf("Hiding selected entry\n");
+ _vm->_converse->getNode(_currentNodeIndex)->entries[entryIndex]->visible = false;
+ } else {
+ //printf("Selected entry is persistent, not hiding it\n");
+ }
+}
+
+void ConversationView::updateState() {
+ switch(_conversationState) {
+ case kConversationOptionsShown:
+ return;
+ case kEntryIsActive:
+ case kReplyIsActive:
+ // FIXME: for now, we determine whether a conversation entry is
+ // finished by waiting for its associated speech file to finish playing
+ if (_currentHandle != NULL && _vm->_sound->isHandleActive(_currentHandle)) {
+ return;
+ } else {
+ playNextReply();
+ } // end else
+ break;
+ case kNoConversation:
+ return;
+ default:
+ error("Unknown converstation state");
+ break;
+ }
+}
+
+void ConversationView::playNextReply() {
+ char buffer[20];
+
+ assert(_highlightedIndex >= 0);
+
+ // Start playing the first reply
+ for (uint32 i = 0; i < _activeItems[_highlightedIndex]->entries.size(); i++) {
+ ConvEntry *currentEntry = _activeItems[_highlightedIndex]->entries[i];
+
+ if (currentEntry->isConditional) {
+ if (!_vm->_converse->evaluateCondition(
+ _vm->_converse->getValue(currentEntry->condition.offset),
+ currentEntry->condition.op, currentEntry->condition.val))
+ continue; // don't play this reply
+ }
+
+ if (currentEntry->entryType != kWeightedReply) {
+ sprintf(buffer, "%s.raw", currentEntry->voiceFile);
+ if (strlen(currentEntry->voiceFile) > 0) {
+ _currentHandle = _vm->_sound->getHandle();
+ _vm->_sound->playVoice(buffer, 255);
+ // Remove reply from the list of replies
+ _activeItems[_highlightedIndex]->entries.remove_at(i);
+ _conversationState = kReplyIsActive;
+ return;
+ } else {
+ _currentHandle = NULL;
+ }
+ } else {
+ int selectedWeight = _vm->_random->getRandomNumber(currentEntry->totalWeight - 1) + 1;
+ //printf("Selected weight: %i\n", selectedWeight);
+ int previousWeight = 1;
+ int currentWeight = 0;
+
+ for(uint32 j = 0; j < currentEntry->entries.size(); j++) {
+ currentWeight += currentEntry->entries[j]->weight;
+ if (selectedWeight >= previousWeight && selectedWeight <= currentWeight) {
+ sprintf(buffer, "%s.raw", currentEntry->entries[j]->voiceFile);
+ if (strlen(currentEntry->entries[j]->voiceFile) > 0) {
+ _currentHandle = _vm->_sound->getHandle();
+ _vm->_sound->playVoice(buffer, 255);
+ // Remove reply from the list of replies
+ _activeItems[_highlightedIndex]->entries.remove_at(i);
+ _conversationState = kReplyIsActive;
+ return;
+ } else {
+ _currentHandle = NULL;
+ }
+ break;
+ }
+ previousWeight += currentWeight;
+ } // end for j
+ } // end if
+ } // end for i
+
+ // If we reached here, there are no more replies, so perform the active entry's actions
+
+ //printf("Current selection does %i actions\n", _activeItems[entryIndex]->actions.size());
+ for (uint32 i = 0; i < _activeItems[_highlightedIndex]->actions.size(); i++) {
+ if (!_vm->_converse->performAction(_activeItems[_highlightedIndex]->actions[i]))
+ break;
+ } // end for
+
+ // Refresh the conversation node, in case it hasn't changed
+ setNode(_currentNodeIndex);
+
+ _entriesShown = true;
+ _vm->_player->setCommandsAllowed(true);
+
+ // Check if the conversation has been ended
+ if (_currentNodeIndex == -1) {
+ _conversationState = kNoConversation;
+ }
+}
+
+//--------------------------------------------------------------------------
+
+void Converse::startConversation(const char *convName, bool showConverseBox, ConverseStyle style) {
+ if (_vm->isM4())
+ loadConversation(convName);
+ else
+ loadConversationMads(convName);
+
+ if (!_vm->isM4()) showConverseBox = false; // TODO: remove
+
+ _playerCommandsAllowed = _vm->_player->commandsAllowed;
+ if (_vm->isM4()) // TODO: remove (interface not implemented yet in MADS games)
+ _interfaceWasVisible = _vm->_interfaceView->isVisible();
+ _vm->_player->setCommandsAllowed(false);
+ _style = style;
+
+ if (showConverseBox) {
+ _vm->_conversationView->show();
+ _vm->_mouse->lockCursor(CURSOR_ARROW);
+
+ if (_interfaceWasVisible)
+ _vm->_interfaceView->hide();
+
+ _vm->_conversationView->setNode(0);
+ _vm->_conversationView->show();
+ }
+}
+
+void Converse::endConversation() {
+ _vm->_conversationView->setNode(-1);
+ _vm->_conversationView->hide();
+ // TODO: do a more proper cleanup here
+ _convNodes.clear();
+ _variables.clear();
+ _offsetMap.clear();
+ _vm->_player->setCommandsAllowed(_playerCommandsAllowed);
+ if (_interfaceWasVisible)
+ _vm->_interfaceView->show();
+}
+
+void Converse::loadConversation(const char *convName) {
+ char name[40];
+ char buffer[256];
+ sprintf(name, "%s.chk", convName);
+ Common::SeekableReadStream *convS = _vm->res()->get(name);
+ uint32 header = convS->readUint32LE();
+ uint32 size;
+ uint32 chunk;
+ uint32 data;
+ uint32 i;
+ ConvEntry* curEntry = NULL;
+ ConvEntry* replyEntry = NULL;
+ int32 currentWeightedEntry = -1;
+ EntryAction* curAction = NULL;
+ uint32 curNode = 0;
+ uint32 chunkPos = 0;
+ uint32 val;
+ int32 autoSelectIndex = -1;
+ int returnAddress = -1;
+
+ bool debugFlag = false; // set to true for debug messages
+
+ // Conversation *.chk files contain a 'CONV' header in LE format
+ if (header != HEAD_CONV) {
+ warning("Unexpected conversation file external header");
+ return;
+ }
+ size = convS->readUint32LE(); // is this used at all?
+ if (debugFlag) printf("Conv chunk size (external header): %i\n", size);
+
+ // Conversation name, which is the conversation file's name
+ // without the extension
+ convS->read(buffer, 8);
+ if (debugFlag) printf("Conversation name: %s\n", buffer);
+
+ while(!convS->eos()) {
+ chunkPos = convS->pos();
+ if (debugFlag) printf("***** Pos: %i -> ", chunkPos);
+ chunk = convS->readUint32LE(); // read chunk
+ switch(chunk) {
+ case CHUNK_DECL: // Declare
+ if (debugFlag) printf("DECL chunk\n");
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Tag: %i\n", data);
+ if (data > 0)
+ warning("Tag > 0 in DECL chunk, value is: %i", data); // TODO
+ val = convS->readUint32LE();
+ if (debugFlag) printf("Value: %i\n", val);
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Flags: %i\n", data);
+ if (data > 0)
+ warning("Flags != 0 in DECL chunk, value is %i", data); // TODO
+ setValue(chunkPos, val);
+ break;
+ // ----------------------------------------------------------------------------
+ case CHUNK_NODE: // Node
+ case CHUNK_LNOD: // Linear node
+ // Create new node
+ curEntry = new ConvEntry();
+ curEntry->offset = chunkPos;
+ curEntry->entryType = (chunk == CHUNK_NODE) ? kNode : kLinearNode;
+ curEntry->fallthroughMinEntries = -1;
+ curEntry->fallthroughOffset = -1;
+ if (chunk == CHUNK_NODE) {
+ if (debugFlag) printf("NODE chunk\n");
+ } else {
+ if (debugFlag) printf("LNOD chunk\n");
+ }
+ curNode = convS->readUint32LE();
+ if (debugFlag) printf("Node number: %i\n", curNode);
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Tag: %i\n", data);
+ if (chunk == CHUNK_LNOD) {
+ autoSelectIndex = convS->readUint32LE();
+ if (debugFlag) printf("Autoselect entry number: %i\n", autoSelectIndex);
+ }
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Number of entries: %i\n", size);
+ for (i = 0; i < size; i++) {
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Entry %i: %i\n", i + 1, data);
+ }
+ _convNodes.push_back(curEntry);
+ setEntryInfo(curEntry->offset, curEntry->entryType, curNode, -1);
+ break;
+ case CHUNK_ETRY: // Entry
+ // Create new entry
+ curEntry = new ConvEntry();
+ curEntry->offset = chunkPos;
+ curEntry->entryType = kEntry;
+ strcpy(curEntry->voiceFile, "");
+ strcpy(curEntry->text, "");
+ if (debugFlag) printf("ETRY chunk\n");
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Unknown (attributes perhaps?): %i\n", data);
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Entry flags: ");
+ if (debugFlag) if (data & kEntryInitial) printf ("Initial ");
+ if (debugFlag) if (data & kEntryPersists) printf ("Persists ");
+ if (debugFlag) printf("\n");
+ curEntry->flags = data;
+ curEntry->visible = (curEntry->flags & kEntryInitial) ? true : false;
+ if (autoSelectIndex >= 0) {
+ if (_convNodes[curNode]->entries.size() == (uint32)autoSelectIndex) {
+ curEntry->autoSelect = true;
+ autoSelectIndex = -1;
+ } else {
+ curEntry->autoSelect = false;
+ }
+ } else {
+ curEntry->autoSelect = false;
+ }
+ _convNodes[curNode]->entries.push_back(curEntry);
+ setEntryInfo(curEntry->offset, curEntry->entryType,
+ curNode, _convNodes[curNode]->entries.size() - 1);
+ replyEntry = NULL;
+ break;
+ case CHUNK_WPRL: // Weighted preply
+ // WPRL chunks are random entries that the character would say, not an NPC
+ // They don't seem to be used in Orion Burger
+ warning("WPRL chunk - treating as WRPL chunk");
+ case CHUNK_WRPL: // Weighted reply
+ case CHUNK_CRPL: // Conditional reply
+ case CHUNK_RPLY: // Reply
+ {
+ ConvEntry* weightedEntry = NULL;
+ // Create new reply
+ replyEntry = new ConvEntry();
+ replyEntry->offset = chunkPos;
+ strcpy(replyEntry->voiceFile, "");
+
+ // Conditional part
+ if (chunk == CHUNK_CRPL) {
+ replyEntry->isConditional = true;
+ replyEntry->condition.offset = convS->readUint32LE();
+ replyEntry->condition.op = convS->readUint32LE();
+ replyEntry->condition.val = convS->readUint32LE();
+ } else {
+ replyEntry->isConditional = false;
+ }
+
+ if (chunk == CHUNK_WPRL || chunk == CHUNK_WRPL) {
+ replyEntry->entryType = kWeightedReply;
+ replyEntry->totalWeight = 0;
+ if (debugFlag) printf("WRPL chunk\n");
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Weighted reply %i - %i entries:\n", i, size);
+ for (i = 0; i < size; i++) {
+ weightedEntry = new ConvEntry();
+ weightedEntry->offset = chunkPos;
+ strcpy(weightedEntry->voiceFile, "");
+ weightedEntry->entryType = kWeightedReply;
+ data = convS->readUint32LE();
+ if (debugFlag) printf("- Weight: %i ", data);
+ weightedEntry->weight = data;
+ replyEntry->totalWeight += weightedEntry->weight;
+ data = convS->readUint32LE();
+ if (debugFlag) printf("offset: %i\n", data);
+ replyEntry->entries.push_back(weightedEntry);
+ }
+ currentWeightedEntry = 0;
+ } else {
+ replyEntry->entryType = kReply;
+ if (debugFlag) printf("RPLY chunk\n");
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Reply data offset: %i\n", data);
+ }
+
+ curEntry->entries.push_back(replyEntry);
+ setEntryInfo(replyEntry->offset, replyEntry->entryType,
+ curNode, _convNodes[curNode]->entries.size() - 1);
+ // Seek to chunk data (i.e. TEXT/MESG tag, which is usually right
+ // after this chunk but it can be further on in conditional reply chunks
+ assert(data >= convS->pos());
+ // If the entry's data is not right after the entry, remember the position
+ // to return to after the data is read
+ if (chunk == CHUNK_CRPL && data != convS->pos())
+ returnAddress = convS->pos();
+ convS->seek(data, SEEK_SET);
+ }
+ break;
+ // ----------------------------------------------------------------------------
+ case CHUNK_TEXT: // Text (contains text and voice)
+ case CHUNK_MESG: // Message (contains voice only)
+ {
+ ConvEntry* parentEntry = NULL;
+ if (chunk == CHUNK_TEXT) {
+ if (debugFlag) printf("TEXT chunk\n");
+ } else {
+ if (debugFlag) printf("MESG chunk\n");
+ }
+
+ if (replyEntry == NULL) {
+ parentEntry = curEntry;
+ } else if (replyEntry != NULL && replyEntry->entryType != kWeightedReply) {
+ parentEntry = replyEntry;
+ } else if (replyEntry != NULL && replyEntry->entryType == kWeightedReply) {
+ parentEntry = replyEntry->entries[currentWeightedEntry];
+ currentWeightedEntry++;
+ }
+
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Entry data size: %i\n", size);
+ convS->read(buffer, 8);
+ size -= 8; // subtract maximum length of voice file name
+ // If the file name is 8 characters, it will not be 0-terminated, so use strncpy
+ strncpy(parentEntry->voiceFile, buffer, 8);
+ parentEntry->voiceFile[8] = '\0';
+ if (debugFlag) printf("Voice file: %s\n", parentEntry->voiceFile);
+
+ if (chunk == CHUNK_TEXT) {
+ convS->read(buffer, size);
+ if (debugFlag) printf("Text: %s\n", buffer);
+ sprintf(parentEntry->text, "%s", buffer);
+ } else {
+ while (size > 0) {
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Unknown: %i\n", data); // TODO
+ size -= 4;
+ }
+ }
+ // Now that the data chunk has been read, if we skipped a reply node,
+ // jump back to it
+ if (returnAddress != -1) {
+ convS->seek(returnAddress, SEEK_SET);
+ returnAddress = -1;
+ }
+ }
+ break;
+ // ----------------------------------------------------------------------------
+ // Entry action chunks
+ case CHUNK_CASN: // Conditional assign
+ case CHUNK_ASGN: // Assign
+ curAction = new EntryAction();
+ if (debugFlag) printf("ASGN chunk\n");
+ curAction->actionType = kAssignValue;
+
+ // Conditional part
+ if (chunk == CHUNK_CASN) {
+ curAction->isConditional = true;
+ curAction->condition.offset = convS->readUint32LE();
+ curAction->condition.op = convS->readUint32LE();
+ curAction->condition.val = convS->readUint32LE();
+ } else {
+ curAction->isConditional = false;
+ }
+
+ curAction->targetOffset = convS->readUint32LE();
+ assert(convS->readUint32LE() == kOpAssign);
+ curAction->value = convS->readUint32LE();
+ break;
+ case CHUNK_CCGO: // Conditional go to entry
+ case CHUNK_CHDE: // Conditional hide entry
+ case CHUNK_CUHD: // Conditional unhide entry
+ case CHUNK_CDST: // Conditional destroy entry
+ case CHUNK_CEGO: // Conditional exit conversation / go to
+
+ case CHUNK_GOTO: // Go to entry
+ case CHUNK_HIDE: // Hide entry
+ case CHUNK_UHID: // Unhide entry
+ case CHUNK_DSTR: // Destroy entry
+ case CHUNK_EXIT: // Exit conversation
+ curAction = new EntryAction();
+
+ // Conditional part
+ if (chunk == CHUNK_CCGO || chunk == CHUNK_CHDE || chunk == CHUNK_CUHD ||
+ chunk == CHUNK_CDST || chunk == CHUNK_CEGO) {
+ curAction->isConditional = true;
+ curAction->condition.offset = convS->readUint32LE();
+ curAction->condition.op = convS->readUint32LE();
+ curAction->condition.val = convS->readUint32LE();
+ } else {
+ curAction->isConditional = false;
+ }
+
+ if (chunk == CHUNK_GOTO || chunk == CHUNK_CCGO) {
+ curAction->actionType = kGotoEntry;
+ if (debugFlag) printf("GOTO chunk\n");
+ } else if (chunk == CHUNK_HIDE || chunk == CHUNK_CHDE) {
+ curAction->actionType = kHideEntry;
+ if (debugFlag) printf("HIDE chunk\n");
+ } else if (chunk == CHUNK_UHID || chunk == CHUNK_CUHD) {
+ curAction->actionType = kUnhideEntry;
+ if (debugFlag) printf("UHID chunk\n");
+ } else if (chunk == CHUNK_DSTR || chunk == CHUNK_CDST) {
+ curAction->actionType = kDestroyEntry;
+ if (debugFlag) printf("DSTR chunk\n");
+ } else if (chunk == CHUNK_EXIT || chunk == CHUNK_CEGO) {
+ curAction->actionType = kExitConv;
+ if (debugFlag) printf("EXIT chunk\n");
+ }
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Offset: %i\n", data);
+ curAction->targetOffset = data;
+ curEntry->actions.push_back(curAction);
+ break;
+ case CHUNK_FALL: // Fallthrough
+ if (debugFlag) printf("FALL chunk\n");
+ size = convS->readUint32LE();
+ if (debugFlag) printf("Minimum nodes: %i\n", size);
+ _convNodes[curNode]->fallthroughMinEntries = size;
+ data = convS->readUint32LE();
+ if (debugFlag) printf("Offset: %i\n", data);
+ _convNodes[curNode]->fallthroughOffset = data;
+ break;
+ // ----------------------------------------------------------------------------
+ default:
+ // Should never happen
+ error("Unknown conversation chunk");
+ break;
+ }
+ }
+
+ _vm->res()->toss(name);
+}
+
+void Converse::loadConversationMads(const char *convName) {
+ char name[40];
+ char buffer[256];
+ char *buf;
+ Common::SeekableReadStream *convS;
+ int curPos = 0;
+ int unk = 0;
+ uint32 stringIndex = 0;
+ uint32 stringCount = 0;
+ int offset = 0;
+ int flags = 0;
+ int count = 0;
+ uint32 i, j;
+ ConvEntry* curEntry = NULL;
+ MessageEntry *curMessage;
+ Common::Array<char *> messageList;
+ // TODO
+
+ // CND file
+ sprintf(name, "%s.cnd", convName);
+ MadsPack convDataD(name, _vm);
+
+ // ------------------------------------------------------------
+ // Chunk 0
+ convS = convDataD.getItemStream(0);
+ printf("Chunk 0\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ // ------------------------------------------------------------
+ // Chunk 1
+ convS = convDataD.getItemStream(1);
+ printf("Chunk 1\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ // ------------------------------------------------------------
+ // Chunk 2
+ convS = convDataD.getItemStream(2);
+ printf("Chunk 2\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ // ------------------------------------------------------------
+ // CNV file
+ sprintf(name, "%s.cnv", convName);
+ MadsPack convData(name, _vm);
+ // *.cnv files have 7 chunks
+ // Here is the chunk output of conv001.cnv (from the dump_file command)
+ /*
+ Dumping conv001.cnv, size: 3431
+ Dumping compressed chunk 1 of 7, size is 150
+ Dumping compressed chunk 2 of 7, size is 130
+ Dumping compressed chunk 3 of 7, size is 224
+ Dumping compressed chunk 4 of 7, size is 92
+ Dumping compressed chunk 5 of 7, size is 168
+ Dumping compressed chunk 6 of 7, size is 4064
+ Dumping compressed chunk 7 of 7, size is 2334
+ */
+
+ // ------------------------------------------------------------
+ // TODO: finish this
+ // Chunk 0
+ convS = convData.getItemStream(0);
+ printf("Chunk 0\n");
+ printf("Conv stream size: %i\n", convS->size());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("%i ", convS->readUint16LE());
+ printf("\n");
+ count = convS->readUint16LE(); // conversation face count (usually 2)
+ printf("Conversation faces: %i\n", count);
+ for (i = 0; i < 5; i++) {
+ convS->read(buffer, 16);
+ printf("Face %i: %s ", i + 1, buffer);
+ }
+ printf("\n");
+
+ // 5 face slots
+ // 1 = face slot has a face (with the filename specified above)
+ // 0 = face slot doesn't contain face data
+ for (i = 0; i < 5; i++) {
+ printf("%i ", convS->readUint16LE());
+ }
+ printf("\n");
+
+ convS->read(buffer, 14); // speech file
+ printf("Speech file: %s\n", buffer);
+
+ while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ }
+ printf("\n");
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 1: Conversation nodes
+ convS = convData.getItemStream(1);
+ printf("Chunk 1: conversation nodes\n");
+ printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ curEntry = new ConvEntry();
+ curEntry->id = convS->readUint16LE();
+ curEntry->entryCount = convS->readUint16LE();
+ curEntry->flags = convS->readUint16LE();
+ if (curEntry->entryCount == 1 && curEntry->flags != 65535) {
+ warning("Entry count is 1 and flags is not 65535 (it's %i)", flags);
+ } else if (curEntry->entryCount != 1 && flags != 0) {
+ warning("Entry count > 1 and flags is not 0 (it's %i)", flags);
+ }
+ unk = convS->readUint16LE();
+ assert (unk == 65535);
+ unk = convS->readUint16LE();
+ assert (unk == 65535);
+ _convNodes.push_back(curEntry);
+ printf("Node %i, ID %i, entries %i\n", _convNodes.size(), curEntry->id, curEntry->entryCount);
+ // flags = 0: node has more than 1 entry
+ // flags = 65535: node has 1 entry
+ }
+ printf("Conversation has %i nodes\n", _convNodes.size());
+ printf("\n");
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 4 contains the conversation string offsets of chunk 5
+ // (unused, as it's unneeded - we find the offsets ourselves)
+
+ // ------------------------------------------------------------
+ // Chunk 5 contains the conversation strings
+ convS = convData.getItemStream(5);
+ //printf("Chunk 5: conversation strings\n");
+ //printf("Conv stream size: %i\n", convS->size());
+
+ *buffer = 0;
+
+ while(!convS->eos()) {
+ //if (curPos == 0)
+ // printf("%i: Offset %i: ", _convStrings.size(), convS->pos());
+ buffer[curPos++] = convS->readByte();
+ if (buffer[curPos - 1] == '~') { // filter out special characters
+ curPos--;
+ continue;
+ }
+ if (buffer[curPos - 1] == '\0') {
+ // end of string
+ //printf("%s\n", buffer);
+ buf = new char[strlen(buffer)];
+ sprintf(buf, "%s", buffer);
+ _convStrings.push_back(buf);
+ curPos = 0;
+ *buffer = 0;
+ }
+ }
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 2: entry data
+ convS = convData.getItemStream(2);
+ //printf("Chunk 2 - entry data\n");
+ //printf("Conv stream size: %i\n", convS->size());
+
+ for (i = 0; i < _convNodes.size(); i++) {
+ for (j = 0; j < _convNodes[i]->entryCount; j++) {
+ curEntry = new ConvEntry();
+ stringIndex = convS->readUint16LE();
+ if (stringIndex != 65535)
+ sprintf(curEntry->text, "%s", _convStrings[stringIndex]);
+ else
+ *curEntry->text = 0;
+ curEntry->id = convS->readUint16LE();
+ curEntry->offset = convS->readUint16LE();
+ curEntry->size = convS->readUint16LE();
+
+ _convNodes[i]->entries.push_back(curEntry);
+ //printf("Node %i, entry %i, id %i, offset %i, size %i, text: %s\n",
+ // i, j, curEntry->id, curEntry->offset, curEntry->size, curEntry->text);
+ }
+ }
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // Chunk 3: message (MESG) chunks, created from the strings of chunk 5
+ convS = convData.getItemStream(3);
+ //printf("Chunk 3 - MESG chunk data\n");
+ //printf("Conv stream size: %i\n", convS->size());
+
+ while(!convS->eos()) {
+ curMessage = new MessageEntry();
+ stringIndex = convS->readUint16LE();
+ stringCount = convS->readUint16LE();
+ *buffer = 0;
+ //printf("Message: %i\n", _madsMessageList.size());
+ for (i = stringIndex; i < stringIndex + stringCount; i++) {
+ //printf("%i: %s\n", i, _convStrings[i]);
+ curMessage->messageStrings.push_back(_convStrings[i]);
+ }
+ _madsMessageList.push_back(curMessage);
+ //printf("----------\n");
+ }
+ //printf("\n");
+
+ delete convS;
+
+ // ------------------------------------------------------------
+ // TODO: finish this
+ // Chunk 6: conversation script
+ convS = convData.getItemStream(6);
+ printf("Chunk 6\n");
+ printf("Conv stream size: %i\n", convS->size());
+ /*while(!convS->eos()) {
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("%i ", convS->readByte());
+ printf("\n");
+ }
+ return;*/
+
+ for (i = 0; i < _convNodes.size(); i++) {
+ for (j = 0; j < _convNodes[i]->entryCount; j++) {
+ printf("*** Node %i entry %i data size %i\n", i, j, _convNodes[i]->entries[j]->size);
+ printf("Entry ID %i, text %s\n", _convNodes[i]->entries[j]->id, _convNodes[i]->entries[j]->text);
+ Common::SubReadStream *entryStream = new Common::SubReadStream(convS, _convNodes[i]->entries[j]->size);
+ readConvEntryActions(entryStream, _convNodes[i]->entries[j]);
+ delete entryStream;
+ printf("--------------------\n");
+ }
+ }
+
+ delete convS;
+}
+
+void Converse::readConvEntryActions(Common::SubReadStream *convS, ConvEntry *curEntry) {
+ uint8 chunk;
+ uint8 type; // 255: normal, 11: conditional
+ uint8 hasText1, hasText2;
+ int target;
+ int count = 0;
+ int var, val;
+ int messageIndex = 0;
+ int unk = 0;
+
+ while(!convS->eos()) {
+ chunk = convS->readByte();
+ type = convS->readByte();
+
+ switch (chunk) {
+ case 1:
+ printf("TODO: chunk type %i\n", chunk);
+ break;
+ case 2:
+ printf("HIDE\n");
+ convS->readByte();
+ count = convS->readByte();
+ printf("%i entries: ", count);
+ for (int i = 0; i < count; i++)
+ printf("%i %d", i, convS->readUint16LE());
+ printf("\n");
+ break;
+ case 3:
+ printf("UNHIDE\n");
+ convS->readByte();
+ count = convS->readByte();
+ printf("%i entries: ", count);
+ for (int i = 0; i < count; i++)
+ printf("%i %d", i, convS->readUint16LE());
+ printf("\n");
+ break;
+ case 4: // MESSAGE
+ printf("MESSAGE\n");
+
+ if (type == 255) {
+ //printf("unconditional\n");
+ } else if (type == 11) {
+ //printf("conditional\n");
+ } else {
+ printf("unknown type: %i\n", type);
+ }
+
+ // Conditional part
+ if (type == 11) {
+ unk = convS->readUint16LE(); // 1
+ if (unk != 1)
+ printf("Message: unk != 1 (it's %i)\n", unk);
+
+ var = convS->readUint16LE();
+ val = convS->readUint16LE();
+ printf("Var %i == %i\n", var, val);
+ }
+ unk = convS->readUint16LE(); // 256
+ if (unk != 256)
+ printf("Message: unk != 256 (it's %i)\n", unk);
+
+ // it seems that the first text entry is set when the message
+ // chunk is supposed to be shown unconditionally, whereas the second text
+ // entry is set when the message is supposed to be shown conditionally
+ hasText1 = convS->readByte();
+ hasText2 = convS->readByte();
+
+ if (hasText1 == 1) {
+ messageIndex = convS->readUint16LE();
+ printf("Message 1 index: %i, text:\n", messageIndex);
+ for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
+ printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
+ }
+ }
+
+ if (hasText2 == 1) {
+ messageIndex = convS->readUint16LE();
+ if (hasText1 == 0) {
+ printf("Message 2 index: %i, text:\n", messageIndex);
+ for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
+ printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
+ }
+ }
+ }
+
+ break;
+ case 5: // AUTO
+ printf("AUTO\n");
+ for (int k = 0; k < 4; k++)
+ convS->readByte();
+ messageIndex = convS->readUint16LE();
+ printf("Message index: %i, text:\n", messageIndex);
+ for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) {
+ printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]);
+ }
+
+ convS->readUint16LE();
+ break;
+ case 6:
+ printf("TODO: chunk type %i\n", chunk);
+ break;
+ case 7: // GOTO
+ unk = convS->readUint32LE(); // 0
+ if (unk != 0 && unk != 1)
+ printf("Goto: unk != 0 or 1 (it's %i)\n", unk);
+
+ target = convS->readUint16LE();
+ convS->readUint16LE(); // 255
+
+ if (unk != 0)
+ printf("Goto: unk != 0 (it's %i)\n", unk);
+
+ if (target != 65535)
+ printf("GOTO node %i\n", target);
+ else
+ printf("GOTO exit\n");
+ break;
+ case 8:
+ printf("TODO: chunk type %i\n", chunk);
+ break;
+ case 9: // ASSIGN
+ //printf("ASSIGN\n");
+ unk = convS->readUint32LE(); // 0
+
+ if (unk != 0)
+ printf("Assign: unk != 0 (it's %i)\n", unk);
+
+ val = convS->readUint16LE();
+ var = convS->readUint16LE();
+ //printf("Var %i = %i\n", var, val);
+ break;
+ default:
+ printf ("Unknown chunk type! (%i)\n", chunk);
+ break;
+ }
+ }
+ printf("\n");
+}
+
+void Converse::setEntryInfo(int32 offset, EntryType type, int32 nodeIndex, int32 entryIndex) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ EntryInfo info;
+ info.targetType = type;
+ info.nodeIndex = nodeIndex;
+ info.entryIndex = entryIndex;
+ _offsetMap[hashOffset] = info;
+ //printf("Set entry info: offset %i, type %i, node %i, entry %i\n", offset, type, nodeIndex, entryIndex);
+}
+
+const EntryInfo* Converse::getEntryInfo(int32 offset) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ OffsetHashMap::const_iterator entry = _offsetMap.find(hashOffset);
+ if (entry != _offsetMap.end())
+ return &(entry->_value);
+ else
+ error("Undeclared entry offset: %i", offset);
+}
+
+void Converse::setValue(int32 offset, int32 value) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ _variables[hashOffset] = value;
+}
+
+const int32 Converse::getValue(int32 offset) {
+ char hashOffset[10];
+ sprintf(hashOffset, "%i", offset);
+ ConvVarHashMap::const_iterator entry = _variables.find(hashOffset);
+ if (entry != _variables.end())
+ return entry->_value;
+ else
+ error("Undeclared variable offset: %i", offset);
+}
+
+bool Converse::evaluateCondition(int32 leftVal, int32 op, int32 rightVal) {
+ switch (op) {
+ case kOpPercent:
+ return (leftVal % rightVal == 0);
+ case kOpGreaterOrEqual:
+ return leftVal >= rightVal;
+ case kOpLessOrEqual:
+ return leftVal <= rightVal;
+ case kOpGreaterThan:
+ return leftVal > rightVal;
+ case kOpLessThan:
+ return leftVal < rightVal;
+ case kOpNotEqual:
+ case kOpCondNotEqual:
+ return leftVal != rightVal;
+ case kOpAssign:
+ return leftVal == rightVal;
+ case kOpAnd:
+ return leftVal && rightVal;
+ case kOpOr:
+ return leftVal || rightVal;
+ default:
+ error("Unknown conditional operator: %i", op);
+ }
+}
+
+bool Converse::performAction(EntryAction *action) {
+ if (action->isConditional) {
+ if (!evaluateCondition(getValue(action->condition.offset),
+ action->condition.op, action->condition.val))
+ return true; // don't perform this action
+ }
+
+ if (action->actionType == kAssignValue) {
+ //printf("Assigning variable at offset %i to value %i\n",
+ // action->targetOffset, action->value);
+ setValue(action->targetOffset, action->value);
+ return true; // nothing else to do in an assignment action
+ }
+
+ const EntryInfo *entryInfo = getEntryInfo(action->targetOffset);
+ ConvEntry *targetEntry;
+
+ if (entryInfo->nodeIndex >= 0 && entryInfo->entryIndex >= 0)
+ targetEntry = getNode(entryInfo->nodeIndex)->entries[entryInfo->entryIndex];
+ else if (entryInfo->nodeIndex >= 0)
+ targetEntry = getNode(entryInfo->nodeIndex);
+ else
+ error("Target node id is negative");
+
+ switch (action->actionType) {
+ case kGotoEntry:
+ //printf("Goto entry at offset %i. Associated node is %i, entry %i\n",
+ // action->targetOffset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ _vm->_conversationView->setNode(entryInfo->nodeIndex);
+ if (entryInfo->entryIndex >= 0)
+ _vm->_conversationView->selectEntry(entryInfo->entryIndex);
+ return false;
+ case kHideEntry:
+ //printf("Hide entry at offset %i. Associated node is %i, entry %i\n",
+ // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ targetEntry->visible = false;
+ return true;
+ case kUnhideEntry:
+ //printf("Show entry at offset %i. Associated node is %i, entry %i\n",
+ // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ targetEntry->visible = true;
+ return true;
+ case kDestroyEntry:
+ //printf("Destroy entry at offset %i. Associated node is %i, entry %i\n",
+ // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex);
+ if (entryInfo->entryIndex >= 0)
+ getNode(entryInfo->nodeIndex)->entries.remove_at(entryInfo->entryIndex);
+ else
+ warning("Target entry is a node, not destroying it");
+ targetEntry->visible = true;
+ return true;
+ case kExitConv:
+ //printf("Exit conversation\n");
+ endConversation();
+ return false;
+ default:
+ warning("Unknown entry action");
+ return false;
+ } // end switch
+}
+
+} // End of namespace M4
diff --git a/engines/m4/converse.h b/engines/m4/converse.h
new file mode 100644
index 0000000000..bea90d9ddf
--- /dev/null
+++ b/engines/m4/converse.h
@@ -0,0 +1,200 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_CONVERSE_H
+#define M4_CONVERSE_H
+
+#include "common/array.h"
+#include "common/hashmap.h"
+
+#include "m4/globals.h"
+#include "m4/m4.h"
+#include "m4/viewmgr.h"
+
+namespace M4 {
+
+enum ConversationState {
+ kConversationOptionsShown = 0,
+ kEntryIsActive = 1,
+ kReplyIsActive = 2,
+ kNoConversation = 3
+};
+
+enum EntryType {
+ kVariable = 0,
+ kNode = 1,
+ kLinearNode = 2,
+ kEntry = 3,
+ kReply = 4,
+ kWeightedReply = 5
+};
+
+// Flags are:
+// bit 0: if it's 1, the entry is "initial", i.e. not hidden when the dialog starts
+// bit 1: if it's 1, the entry persists if selected
+enum EntryFlags {
+ kEntryInitial = 1, // byte 0
+ kEntryPersists = 2 // byte 1
+};
+
+enum EntryActionType {
+ kUnknownAction = 0,
+ kGotoEntry = 1,
+ kHideEntry = 2,
+ kUnhideEntry = 3,
+ kDestroyEntry = 4,
+ kAssignValue = 5,
+ kExitConv = 6
+};
+
+enum LogicOp {
+ kOpPercent = 405,
+ kOpGreaterOrEqual = 421,
+ kOpLessOrEqual = 420,
+ kOpGreaterThan = 413,
+ kOpLessThan = 412,
+ kOpNotEqual = 422,
+ kOpCondNotEqual = 448,
+ kOpAssign = 407,
+ kOpAnd = 444,
+ kOpOr = 445
+};
+
+struct Condition {
+ int32 offset;
+ int32 op;
+ int32 val;
+};
+
+struct EntryAction {
+ int32 targetOffset; // absolute offset (inside the *.chk file) of the action's target
+ int32 value; // the value set by assignment chunks
+ EntryActionType actionType;
+ EntryType targetType;
+ int32 nodeId;
+ int32 entryId;
+ bool isConditional;
+ Condition condition;
+};
+
+struct ConvEntry {
+ EntryType entryType;
+ int32 id;
+ int32 offset; // absolute offset inside the *.chk file, referenced by other chunks
+ int32 size; // entry action data size (for MADS games)
+ int32 flags;
+ int32 fallthroughMinEntries;
+ int32 fallthroughOffset;
+ int32 weight; // weight for weighted replies
+ int32 totalWeight;
+ uint16 entryCount; // entries inside this node (for MADS games)
+ char voiceFile[10];
+ char text[512];
+ bool autoSelect;
+ bool visible;
+ bool isConditional;
+ Condition condition;
+ Common::Array<EntryAction*>actions;
+ Common::Array<ConvEntry*>entries;
+};
+
+struct EntryInfo {
+ EntryType targetType;
+ int32 nodeIndex;
+ int32 entryIndex;
+};
+
+struct MessageEntry {
+ Common::Array<char*>messageStrings;
+};
+
+enum ConverseStyle {CONVSTYLE_EARTH, CONVSTYLE_SPACE};
+
+typedef Common::HashMap<Common::String,EntryInfo,Common::IgnoreCase_Hash,Common::IgnoreCase_EqualTo> OffsetHashMap;
+typedef Common::HashMap<Common::String,int32,Common::IgnoreCase_Hash,Common::IgnoreCase_EqualTo> ConvVarHashMap;
+
+class ConversationView: public View {
+public:
+ ConversationView(M4Engine *vm);
+ ~ConversationView();
+ void setNode(int32 nodeIndex);
+
+ void onRefresh(RectList *rects, M4Surface *destSurface);
+ bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents);
+ int32 getCurrentNodeIndex() { return _currentNodeIndex; }
+ void selectEntry(int entryIndex);
+
+private:
+ void updateState();
+ void playNextReply();
+
+ int32 _currentNodeIndex;
+ Common::Array<ConvEntry *> _activeItems;
+ int _highlightedIndex;
+ int _xEnd;
+ bool _entriesShown;
+ ConversationState _conversationState;
+ SndHandle *_currentHandle;
+};
+
+class Converse {
+
+public:
+ Converse(M4Engine *vm) : _vm(vm) {}
+ ~Converse() {}
+
+ void startConversation(const char *convName, bool showConversebox = true, ConverseStyle style = CONVSTYLE_EARTH );
+ void endConversation();
+ const EntryInfo* getEntryInfo(int32 offset);
+ ConvEntry *getNode(int32 index) { return _convNodes[index]; }
+
+ void setValue(int32 offset, int32 value);
+ const int32 getValue(int32 offset);
+ bool evaluateCondition(int32 leftVal, int32 op, int32 rightVal);
+ bool performAction(EntryAction *action);
+ /*
+ void resume() { play(); }
+ void play();
+ */
+private:
+ M4Engine *_vm;
+ Common::Array<ConvEntry*>_convNodes;
+ Common::Array<MessageEntry*>_madsMessageList;
+ Common::Array<char *>_convStrings;
+ bool _playerCommandsAllowed;
+ bool _interfaceWasVisible;
+ ConverseStyle _style;
+ OffsetHashMap _offsetMap;
+ ConvVarHashMap _variables;
+
+ void loadConversation(const char *convName);
+ void loadConversationMads(const char *convName);
+ void readConvEntryActions(Common::SubReadStream *convS, ConvEntry *curEntry);
+ void setEntryInfo(int32 offset, EntryType type, int32 nodeIndex, int32 entryIndex);
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/detection.cpp b/engines/m4/detection.cpp
new file mode 100644
index 0000000000..7aee6167b3
--- /dev/null
+++ b/engines/m4/detection.cpp
@@ -0,0 +1,345 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "base/plugins.h"
+
+#include "common/advancedDetector.h"
+
+#include "m4/m4.h"
+#include "m4/resource.h"
+
+namespace M4 {
+
+struct M4GameDescription {
+ Common::ADGameDescription desc;
+
+ int gameType;
+ uint32 features;
+};
+
+int M4Engine::getGameType() const { return _gameDescription->gameType; }
+uint32 M4Engine::getFeatures() const { return _gameDescription->features; }
+Common::Language M4Engine::getLanguage() const { return _gameDescription->desc.language; }
+Common::Platform M4Engine::getPlatform() const { return _gameDescription->desc.platform; }
+
+}
+
+static const PlainGameDescriptor m4Games[] = {
+ {"m4", "MADS/M4 engine game"},
+ {"riddle", "Riddle of Master Lu: Believe it or Not!"},
+ {"burger", "Orion Burger"},
+ {"rex", "Rex Nebular and the Cosmic Gender Bender"},
+ {"dragon", "DragonSphere"},
+ {"dragoncd", "DragonSphere CD"},
+ {"phantom", "Return of the Phantom"},
+ {"phantomcd", "Return of the Phantom CD"},
+ {0, 0}
+};
+
+namespace M4 {
+
+const char *M4Engine::getGameFile(int fileType) {
+ for (int i = 0; _gameDescription->desc.filesDescriptions[i].fileName; i++) {
+ if (_gameDescription->desc.filesDescriptions[i].fileType == fileType)
+ return _gameDescription->desc.filesDescriptions[i].fileName;
+ }
+ return NULL;
+}
+
+static const M4GameDescription gameDescriptions[] = {
+ {
+ {
+ "burger",
+ "",
+ {
+ { "burger.has", kFileTypeHash, "10c8064e9c771072122f50737ac97245", 730771},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Burger,
+ kFeaturesCD
+ },
+ {
+ {
+ "burger",
+ "",
+ {
+ { "burger.has", kFileTypeHash, "55be8693a4c36e7efcdca0f0c77758ae", 724191},
+ { NULL, 0, NULL, 0}
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Burger,
+ kFeaturesCD
+ },
+ {
+ {
+ "riddle",
+ "",
+ {
+ { "ripley.has", kFileTypeHash, "056d517360c89eb4c297a319f21a7071", 700469},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Riddle,
+ kFeaturesCD
+ },
+ {
+ {
+ "riddle",
+ "",
+ {
+ { "ripley.has", kFileTypeHash, "d073582c9011d44dd0d7e2ede317a86d", 700469},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Riddle,
+ kFeaturesCD
+ },
+ {
+ {
+ "riddle",
+ "",
+ {
+ { "ripley.has", kFileTypeHash, "d9e9f8befec432a047b1047fb2bc7aea", 710997},
+ { NULL, 0, NULL, 0}
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Riddle,
+ kFeaturesCD
+ },
+ {
+ {
+ "riddle",
+ "",
+ {
+ { "ripley.has", kFileTypeHash, "3d48c5700785d11e6a5bc832b95be918", 701973},
+ { NULL, 0, NULL, 0}
+ },
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Riddle,
+ kFeaturesCD
+ },
+ { // Demo
+ {
+ "riddle",
+ "Demo",
+ {
+ { "ripley.has", kFileTypeHash, "3a90dd7052860b6e246ec7e0aaf202f6", 104527},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GType_Riddle,
+ kFeaturesDemo
+ },
+ {
+ {
+ "rex",
+ "",
+ {
+ { "global.hag", kFileTypeHAG, "0530cbeee109fc79cc24421128dea1ce", 2083078},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_RexNebular,
+ kFeaturesNone
+ },
+ { // Demo
+ {
+ "rex",
+ "Demo",
+ {
+ { "global.hag", kFileTypeHAG, "d5a481d14bc1bda66e46965a39badcc7", 220429},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GType_RexNebular,
+ kFeaturesDemo
+ },
+ {
+ {
+ "dragon",
+ "",
+ {
+ { "global.hag", kFileTypeHAG, "c3a6877665e7f21bf3d2b1e667155562", 2320567},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_DragonSphere,
+ kFeaturesNone
+ },
+ { // CD version
+ {
+ "dragoncd",
+ "",
+ {
+ { "global.hag", kFileTypeHAG, "c3a6877665e7f21bf3d2b1e667155562", 2320567},
+ { "speech.hag", kFileTypeHAG, NULL, -1},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_DragonSphere,
+ kFeaturesCD
+ },
+ { // Demo
+ {
+ "dragon",
+ "Demo",
+ {
+ { "global.hag", kFileTypeHAG, "541e12d8e9aad0c65d65f150de47582e", 212718},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GType_DragonSphere,
+ kFeaturesDemo
+ },
+ {
+ {
+ "phantom",
+ "",
+ {
+ { "global.hag", kFileTypeHAG, "bdce9ca93a015f0883d1bc0fabd0cdfa", 812150},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Phantom,
+ kFeaturesNone
+ },
+ { // CD version
+ {
+ "phantomcd",
+ "",
+ {
+ { "global.hag", kFileTypeHAG, "8a51c984eb4c64e8b30a7e6670f6bddb", 101154000},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_Phantom,
+ kFeaturesCD
+ },
+ { // CD version
+ {
+ "phantom",
+ "Demo",
+ {
+ { "global.hag", kFileTypeHAG, "e810adbc6fac77ac2fec841a9ec5e20e", 115266},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GType_Phantom,
+ kFeaturesDemo
+ },
+ { { NULL, NULL, { { NULL, 0, NULL, 0 } }, Common::UNK_LANG, Common::kPlatformUnknown, Common::ADGF_NO_FLAGS }, 0, 0 }
+};
+
+}
+
+static const Common::ADParams detectionParams = {
+ // Pointer to ADGameDescription or its superset structure
+ (const byte *)M4::gameDescriptions,
+ // Size of that superset structure
+ sizeof(M4::M4GameDescription),
+ // Number of bytes to compute MD5 sum for
+ 5000,
+ // List of all engine targets
+ m4Games,
+ // Structure for autoupgrading obsolete targets
+ 0,
+ // Name of single gameid (optional)
+ "m4",
+ // List of files for file-based fallback detection (optional)
+ 0,
+ // Flags
+ 0
+};
+
+class M4MetaEngine : public Common::AdvancedMetaEngine {
+public:
+ M4MetaEngine() : Common::AdvancedMetaEngine(detectionParams) {}
+
+ virtual const char *getName() const {
+ return "MADS/M4 engine";
+ }
+
+ virtual const char *getCopyright() const {
+ return "Riddle of Master Lu & Orion Burger (C) Sanctuary Woods";
+ }
+
+ virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const;
+};
+
+bool M4MetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const {
+ const M4::M4GameDescription *gd = (const M4::M4GameDescription *)desc;
+ if (gd) {
+ *engine = new M4::M4Engine(syst, gd);
+ }
+ return gd != 0;
+}
+
+REGISTER_PLUGIN(M4, PLUGIN_TYPE_ENGINE, M4MetaEngine);
diff --git a/engines/m4/events.cpp b/engines/m4/events.cpp
new file mode 100644
index 0000000000..101a41015f
--- /dev/null
+++ b/engines/m4/events.cpp
@@ -0,0 +1,351 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// TODO: There is a 'please_hyperwalk' variable that gets accessed that is meant to be global, but
+// at the moment it's implemented as a local variable
+
+#include "graphics/cursorman.h"
+
+#include "m4/events.h"
+#include "m4/graphics.h"
+#include "m4/scene.h"
+#include "m4/viewmgr.h"
+#include "m4/m4.h"
+
+namespace M4 {
+
+bool please_hyperwalk = false;
+
+/*--------------------------------------------------------------------------*
+ * Events *
+ * *
+ * Implements an interface to the event system *
+ *--------------------------------------------------------------------------*/
+
+Events::Events(M4Engine *vm) : _vm(vm) {
+ _mouseState = MSTATE_NO_EVENT;
+ quitFlag = false;
+ _keyCode = 0;
+ _console = new Console(_vm);
+ _mouseButtons = 0;
+}
+
+M4EventType Events::handleEvents() {
+ static int oldX = -1, oldY = -1;
+ static uint32 dclickTime = 0;
+
+ // Handle event types
+ while (g_system->getEventManager()->pollEvent(_event)) {
+ switch (_event.type) {
+ case Common::EVENT_QUIT:
+ quitFlag = true;
+ break;
+ case Common::EVENT_KEYDOWN:
+ if (_event.kbd.flags == Common::KBD_CTRL) {
+ if (_event.kbd.keycode == Common::KEYCODE_d)
+ _console->attach();
+ _console->onFrame();
+ }
+ _keyCode = (int)_event.kbd.keycode;
+
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ case Common::EVENT_MBUTTONDOWN:
+ case Common::EVENT_MBUTTONUP:
+ case Common::EVENT_MOUSEMOVE:
+ case Common::EVENT_WHEELUP:
+ case Common::EVENT_WHEELDOWN:
+ _vm->_mouse->handleEvent(_event);
+ break;
+ default:
+ break;
+ }
+ }
+
+ _mouseButtons = g_system->getEventManager()->getButtonState();
+
+ // State machine for moving between states
+ switch (_mouseState) {
+ case MSTATE_NO_EVENT:
+ if (_mouseButtons & LEFT_BUTTON_DOWN) {
+ if ((dclickTime != 0) && (g_system->getMillis() < dclickTime)) {
+ _mouseState = MSTATE_DOUBLECLICK_DOWN;
+ dclickTime = 0;
+ return MEVENT_DOUBLECLICK;
+ }
+ dclickTime = 0;
+ _mouseState = MSTATE_LEFT_CLICK_DOWN;
+ return MEVENT_LEFT_CLICK;
+ }
+ if (_mouseButtons & RIGHT_BUTTON_DOWN) {
+ _mouseState = MSTATE_RIGHT_CLICK_DOWN;
+ return MEVENT_RIGHT_CLICK;
+ }
+ if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
+ oldX = _event.mouse.x; oldY = _event.mouse.y;
+ return MEVENT_MOVE;
+ }
+ return MEVENT_NO_EVENT;
+
+ case MSTATE_LEFT_CLICK_DOWN:
+ if (!(_mouseButtons & LEFT_BUTTON_DOWN)) {
+ dclickTime = g_system->getMillis() + 1000 * 15 / 60;
+ _mouseState = MSTATE_NO_EVENT;
+ return MEVENT_LEFT_RELEASE;
+ }
+ if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
+ oldX = _event.mouse.x; oldY = _event.mouse.y;
+ return MEVENT_LEFT_DRAG;
+ }
+ return MEVENT_LEFT_HOLD;
+
+ case MSTATE_RIGHT_CLICK_DOWN:
+ if (!(_mouseButtons & RIGHT_BUTTON_DOWN)) {
+ _mouseState = MSTATE_NO_EVENT;
+ please_hyperwalk = true;
+ return MEVENT_RIGHT_RELEASE;
+ }
+ if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
+ oldX = _event.mouse.x; oldY = _event.mouse.y;
+ return MEVENT_RIGHT_DRAG;
+ }
+ return MEVENT_RIGHT_HOLD;
+
+ case MSTATE_DOUBLECLICK_DOWN:
+ if (!(_mouseButtons & LEFT_BUTTON_DOWN)) {
+ _mouseState = MSTATE_NO_EVENT;
+ return MEVENT_DOUBLECLICK_RELEASE;
+ }
+ if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) {
+ oldX = _event.mouse.x; oldY = _event.mouse.y;
+ return MEVENT_DOUBLECLICK_DRAG;
+ }
+ return MEVENT_DOUBLECLICK_HOLD;
+
+ default:
+ return MEVENT_NO_EVENT;
+ }
+}
+
+bool Events::kbdCheck(uint32 &keyCode) {
+ if (_keyCode == 0)
+ return false;
+
+ keyCode = _keyCode;
+ _keyCode = 0;
+ return true;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Mouse *
+ * *
+ * Implements an interface to the mouse *
+ *--------------------------------------------------------------------------*/
+
+Mouse::Mouse(M4Engine *vm) : _vm(vm) {
+ _locked = false;
+ _cursorOn = false;
+ _cursor = NULL;
+ _cursorSprites = NULL;
+ resetMouse();
+}
+
+Mouse::~Mouse() {
+ if (_cursorSprites)
+ delete _cursorSprites;
+}
+
+bool Mouse::init(const char *seriesName, RGB8 *palette) {
+ Common::SeekableReadStream *stream = _vm->res()->get(seriesName);
+ int colorCount = 0;
+ RGB8* cursorPalette;
+
+ _cursorSprites = new SpriteAsset(_vm, stream, stream->size(), seriesName);
+
+ // Remove cursor special pixels and set the mouse cursor hotspot in MADS games
+ if (!_vm->isM4()) {
+ byte *data = NULL;
+ for (int i = 0; i < _cursorSprites->getCount(); i++) {
+ bool hotSpotSet = false;
+
+ for (int x = 0; x < _cursorSprites->getFrame(i)->width(); x++) {
+ for (int y = 0; y < _cursorSprites->getFrame(i)->height(); y++) {
+ data = _cursorSprites->getFrame(i)->getBasePtr(x, y);
+ if (*data == 1) {
+ // It seems that some cursors have more than one hotspot
+ // In such a case, the first hotspot seems to set the x and
+ // the second one the y hotspot offset
+ if (!hotSpotSet) {
+ _cursorSprites->getFrame(i)->xOffset = x;
+ _cursorSprites->getFrame(i)->yOffset = y;
+ hotSpotSet = true;
+ } else {
+ _cursorSprites->getFrame(i)->yOffset = y;
+ }
+ *data = 0;
+ }
+ } // for y
+ } // for x
+ } // for i
+ }
+
+ colorCount = _cursorSprites->getColorCount();
+ cursorPalette = _cursorSprites->getPalette();
+ _vm->_palette->setPalette(cursorPalette, 0, colorCount);
+
+ //printf("Cursor count: %d\n", _cursorSprites->getCount());
+
+ _vm->res()->toss(seriesName);
+
+ _currentCursor = -1;
+ return true;
+}
+
+bool Mouse::setCursorNum(int cursorIndex) {
+ if ((cursorIndex < 0) || (cursorIndex >= (int)_cursorSprites->getCount()))
+ return false;
+
+ _lockedCursor = cursorIndex;
+ if (_locked)
+ // Cursor is locked, so don't go ahead with changing cursor
+ return true;
+
+ _currentCursor = _lockedCursor;
+ _cursor = _cursorSprites->getFrame(cursorIndex);
+
+ // Set the cursor to the sprite
+ CursorMan.replaceCursor((const byte *)_cursor->getData(), _cursor->w, _cursor->h, _cursor->xOffset, _cursor->yOffset, 0);
+
+ return true;
+}
+
+int Mouse::cursorCount() {
+ return _cursorSprites->getCount();
+}
+
+void Mouse::cursorOn() {
+ _cursorOn = true;
+ CursorMan.showMouse(!inHideArea());
+}
+
+void Mouse::cursorOff() {
+ _cursorOn = false;
+ CursorMan.showMouse(false);
+}
+
+void Mouse::lockCursor(int cursorIndex) {
+ _locked = false;
+ setCursorNum(cursorIndex);
+ _locked = true;
+}
+
+void Mouse::unlockCursor() {
+ _locked = false;
+ setCursorNum(_lockedCursor);
+}
+
+const char *Mouse::getVerb() {
+ switch (_vm->_mouse->getCursorNum()) {
+ case CURSOR_LOOK:
+ return "LOOK AT";
+ case CURSOR_TAKE:
+ return "TAKE";
+ case CURSOR_USE:
+ return "GEAR";
+ default:
+ return NULL;
+ }
+}
+
+void Mouse::resetMouse() {
+ _hideRect.left = -1;
+ _hideRect.top = -1;
+ _hideRect.right = -1;
+ _hideRect.bottom = -1;
+ _showRect.left = -1;
+ _showRect.top = -1;
+ _showRect.right = -1;
+ _showRect.bottom = -1;
+}
+
+void Mouse::setHideRect(Common::Rect &r) {
+ _hideRect = r;
+}
+
+void Mouse::setShowRect(Common::Rect &r) {
+ _showRect = r;
+}
+
+const Common::Rect *Mouse::getHideRect() {
+ return &_hideRect;
+}
+
+const Common::Rect *Mouse::getShowRect() {
+ if ((_showRect.top == -1) || (_showRect.left == -1)) {
+ // Show rectangle uninitialised - set it to current screen dimensions
+ _showRect.top = 0;
+ _showRect.left = 0;
+ _showRect.right = _vm->_screen->width() - 1;
+ _showRect.bottom = _vm->_screen->height() -1;
+ }
+
+ return &_showRect;
+}
+
+void Mouse::handleEvent(Common::Event &event) {
+ _currentPos.x = event.mouse.x;
+ _currentPos.y = event.mouse.y;
+
+ // If mouse is turned on, check to see if the position is in the hide rect, or outside the show rect.
+ // If so, handle toggling the visibility of the mouse
+ bool showFlag = !inHideArea();
+ if (_cursorOn && (CursorMan.isVisible() != showFlag)) {
+ CursorMan.showMouse(showFlag);
+ }
+}
+
+bool Mouse::inHideArea() {
+ // Returns true if the mouse is inside a specified hide rect, or if a show rect is specified and
+ // the mouse is currently outside it
+ if ((_currentPos.x >= _hideRect.left) && (_currentPos.x <= _hideRect.right) &&
+ (_currentPos.y >= _hideRect.top) && (_currentPos.y <= _hideRect.bottom))
+ // Inside a hide area
+ return true;
+
+
+ if ((_showRect.top == -1) && (_showRect.left == -1))
+ // No show rect defined
+ return false;
+
+ // Return true if the mouse is outside the show area
+ return (_currentPos.x < _showRect.left) || (_currentPos.x > _showRect.right) ||
+ (_currentPos.y < _showRect.top) || (_currentPos.y > _showRect.bottom);
+}
+
+} // End of namespace M4
diff --git a/engines/m4/events.h b/engines/m4/events.h
new file mode 100644
index 0000000000..d261f29013
--- /dev/null
+++ b/engines/m4/events.h
@@ -0,0 +1,132 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_EVENTS_H
+#define M4_EVENTS_H
+
+#include "common/events.h"
+#include "common/rect.h"
+
+#include "m4/globals.h"
+#include "m4/assets.h"
+#include "m4/sprite.h"
+#include "m4/graphics.h"
+#include "m4/console.h"
+
+namespace M4 {
+
+#define LEFT_BUTTON_DOWN 1 << 0
+#define RIGHT_BUTTON_DOWN 1 << 1
+
+enum M4EventType {
+ MEVENT_NO_EVENT, MEVENT_MOVE,
+ MEVENT_LEFT_CLICK, MEVENT_LEFT_HOLD, MEVENT_LEFT_DRAG, MEVENT_LEFT_RELEASE,
+ MEVENT_RIGHT_CLICK, MEVENT_RIGHT_HOLD, MEVENT_RIGHT_DRAG, MEVENT_RIGHT_RELEASE,
+ MEVENT_BOTH_CLICK, MEVENT_BOTH_HOLD, MEVENT_BOTH_DRAG, MEVENT_BOTH_RELEASE,
+ MEVENT_DOUBLECLICK, MEVENT_DOUBLECLICK_HOLD, MEVENT_DOUBLECLICK_DRAG, MEVENT_DOUBLECLICK_RELEASE,
+ KEVENT_KEY
+};
+
+enum M4MouseState {
+ MSTATE_NO_EVENT, MSTATE_LEFT_CLICK_DOWN, MSTATE_RIGHT_CLICK_DOWN, MSTATE_BOTH_CLICK_DOWN,
+ MSTATE_DOUBLECLICK_DOWN
+};
+
+enum M4CommonCursors {
+ CURSOR_ARROW = 0,
+ CURSOR_HOURGLASS = 5,
+ CURSOR_LOOK = 6,
+ CURSOR_TAKE = 8,
+ CURSOR_USE = 9
+};
+
+class M4Sprite;
+class SpriteAsset;
+
+class Events {
+private:
+ M4Engine *_vm;
+ Common::Event _event;
+ M4MouseState _mouseState;
+ int _keyCode;
+ int _mouseButtons;
+ Console *_console;
+public:
+ bool quitFlag;
+ Events(M4Engine *vm);
+
+ Common::Event &event() { return _event; }
+ Common::EventType type() { return _event.type; }
+
+ // M4-centric methods
+ M4EventType handleEvents();
+ bool kbdCheck(uint32 &keyCode);
+ int getMouseButtonsState() { return _mouseButtons; }
+ Console* getConsole() { return _console; }
+};
+
+
+class Mouse {
+private:
+ M4Engine *_vm;
+ int _currentCursor, _lockedCursor;
+ bool _locked;
+ bool _cursorOn;
+ M4Sprite *_cursor;
+ SpriteAsset *_cursorSprites;
+ Common::Rect _hideRect, _showRect;
+ Common::Point _currentPos;
+
+ void handleEvent(Common::Event &event);
+ bool inHideArea();
+ friend class Events;
+public:
+ Mouse(M4Engine *vm);
+ ~Mouse();
+
+ bool init(const char *seriesName, RGB8 *palette);
+ bool setCursorNum(int cursorIndex);
+ int getCursorNum() { return _currentCursor; }
+ int cursorCount();
+ Common::Point currentPos() const { return _currentPos; }
+ M4Sprite *cursor() { return _cursor; }
+ void cursorOn();
+ void cursorOff();
+ bool getCursorOn() { return _cursorOn; }
+ void lockCursor(int cursorIndex);
+ void unlockCursor();
+
+ const char *getVerb();
+
+ void resetMouse();
+ void setHideRect(Common::Rect &r);
+ void setShowRect(Common::Rect &r);
+ const Common::Rect *getHideRect();
+ const Common::Rect *getShowRect();
+};
+
+}
+
+#endif
diff --git a/engines/m4/font.cpp b/engines/m4/font.cpp
new file mode 100644
index 0000000000..c9b9a44f23
--- /dev/null
+++ b/engines/m4/font.cpp
@@ -0,0 +1,267 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/font.h"
+#include "m4/m4.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+Font::Font(M4Engine *vm) : _vm(vm) {
+ _sysFont = true;
+ _filename = NULL;
+ //TODO: System font
+ _fontColors[0] = _vm->_palette->BLACK;
+ _fontColors[1] = _vm->_palette->WHITE;
+ _fontColors[2] = _vm->_palette->BLACK;
+ _fontColors[3] = _vm->_palette->DARK_GRAY;
+}
+
+void Font::setFont(const char *filename) {
+ if ((_filename != NULL) && (strcmp(filename, _filename) == 0))
+ // Already using specified font, so don't bother reloading
+ return;
+
+ _sysFont = false;
+ _filename = filename;
+
+ if (_vm->isM4())
+ setFontM4(filename);
+ else
+ setFontMads(filename);
+}
+
+void Font::setFontM4(const char *filename) {
+ Common::SeekableReadStream *fontFile = _vm->res()->openFile(filename);
+
+ if (fontFile->readUint32LE() != MKID_BE('FONT')) {
+ printf("Font::Font: FONT tag expected\n");
+ return;
+ }
+
+ _maxHeight = fontFile->readByte();
+ _maxWidth = fontFile->readByte();
+ uint32 fontSize = fontFile->readUint32LE();
+
+ //printf("Font::Font: _maxWidth = %d, _maxHeight = %d, fontSize = %d\n", _maxWidth, _maxHeight, fontSize);
+
+ if (fontFile->readUint32LE() != MKID_BE('WIDT')) {
+ printf("Font::Font: WIDT tag expected\n");
+ return;
+ }
+
+ _charWidths = new uint8[256];
+ fontFile->read(_charWidths, 256);
+
+ if (fontFile->readUint32LE() != MKID_BE('OFFS')) {
+ printf("Font::Font: OFFS tag expected\n");
+ return;
+ }
+
+ _charOffs = new uint16[256];
+
+ for (int i = 0; i < 256; i++)
+ _charOffs[i] = fontFile->readUint16LE();
+
+ if (fontFile->readUint32LE() != MKID_BE('PIXS')) {
+ printf("Font::Font: PIXS tag expected\n");
+ return;
+ }
+
+ _charData = new uint8[fontSize];
+ fontFile->read(_charData, fontSize);
+
+ _vm->res()->toss(filename);
+}
+
+void Font::setFontMads(const char *filename) {
+ MadsPack fontData(filename, _vm);
+ Common::SeekableReadStream *fontFile = fontData.getItemStream(0);
+
+ _maxHeight = fontFile->readByte();
+ _maxWidth = fontFile->readByte();
+
+ _charWidths = new uint8[128];
+ // Char data is shifted by 1
+ _charWidths[0] = 0;
+ fontFile->read(_charWidths + 1, 127);
+ fontFile->readByte(); // remainder
+
+ _charOffs = new uint16[128];
+
+ uint32 startOffs = 2 + 128 + 256;
+ uint32 fontSize = fontFile->size() - startOffs;
+
+ // Char data is shifted by 1
+ _charOffs[0] = 0;
+ for (int i = 1; i < 128; i++)
+ _charOffs[i] = fontFile->readUint16LE() - startOffs;
+ fontFile->readUint16LE(); // remainder
+
+ _charData = new uint8[fontSize];
+ fontFile->read(_charData, fontSize);
+
+ delete fontFile;
+}
+
+Font::~Font() {
+ if (!_sysFont) {
+ delete[] _charWidths;
+ delete[] _charOffs;
+ delete[] _charData;
+ }
+}
+
+void Font::setColor(uint8 color) {
+ if (_sysFont)
+ _fontColors[1] = color;
+ else
+ _fontColors[3] = color;
+}
+
+void Font::setColors(uint8 alt1, uint8 alt2, uint8 foreground) {
+ if (_sysFont)
+ _fontColors[1] = foreground;
+ else {
+ _fontColors[1] = alt1;
+ _fontColors[2] = alt2;
+ _fontColors[3] = foreground;
+ }
+}
+
+int32 Font::write(M4Surface *surface, const char *text, int x, int y, int width, int spaceWidth, uint8 colors[]) {
+
+ /*TODO
+ if (custom_ascii_converter) { // if there is a function to convert the extended ASCII characters
+ custom_ascii_converter(out_string); // call it with the string
+ }
+ */
+
+ if (width > 0)
+ width = MIN(surface->width(), x + width);
+ else
+ width = surface->width();
+
+ x++;
+ y++;
+
+ int skipY = 0;
+ if (y < 0) {
+ skipY = -y;
+ y = 0;
+ }
+
+ int height = MAX(0, _maxHeight - skipY);
+ if (height == 0)
+ return x;
+
+ int bottom = y + height - 1;
+ if (bottom > surface->height() - 1) {
+ height -= MIN(height, bottom - (surface->height() - 1));
+ }
+
+ if (height <= 0)
+ return x;
+
+ uint8 *destPtr = (uint8*)surface->getBasePtr(x, y);
+ uint8 *oldDestPtr = destPtr;
+
+ int xPos = x;
+
+ while (*text) {
+
+ char theChar = (*text++) & 0x7F;
+ int charWidth = _charWidths[theChar];
+
+ if (charWidth > 0) {
+
+ if (xPos + charWidth >= width)
+ return xPos;
+
+ uint8 *charData = &_charData[_charOffs[theChar]];
+ int bpp = charWidth / 4 + 1;
+
+ if (!_vm->isM4()) {
+ if (charWidth > 12)
+ bpp = 4;
+ else if (charWidth > 8)
+ bpp = 3;
+ else if (charWidth > 4)
+ bpp = 2;
+ else
+ bpp = 1;
+ }
+
+ if (skipY != 0)
+ charData += bpp * skipY;
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < bpp; j++) {
+ if (*charData & 0xc0)
+ *destPtr = colors[(*charData & 0xc0) >> 6];
+ destPtr++;
+ if (*charData & 0x30)
+ *destPtr = colors[(*charData & 0x30) >> 4];
+ destPtr++;
+ if (*charData & 0x0C)
+ *destPtr = colors[(*charData & 0x0C) >> 2];
+ destPtr++;
+ if (*charData & 0x03)
+ *destPtr = colors[*charData & 0x03];
+ destPtr++;
+ charData++;
+ }
+
+ destPtr += surface->width() - bpp * 4;
+
+ }
+
+ destPtr = oldDestPtr + charWidth + spaceWidth;
+ oldDestPtr = destPtr;
+
+ }
+
+ xPos += charWidth + spaceWidth;
+
+ }
+
+ surface->freeData();
+ return xPos;
+
+}
+
+int32 Font::getWidth(char *text, int spaceWidth) {
+ /*
+ if (custom_ascii_converter) { // if there is a function to convert the extended ASCII characters
+ custom_ascii_converter(out_string); // call it with the string
+ }
+ */
+ int width = 0;
+ while (*text)
+ width += _charWidths[*text++ & 0x7F] + spaceWidth;
+ return width;
+}
+
+} // End of namespace M4
diff --git a/engines/m4/font.h b/engines/m4/font.h
new file mode 100644
index 0000000000..75321d820c
--- /dev/null
+++ b/engines/m4/font.h
@@ -0,0 +1,91 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_FONT_H
+#define M4_FONT_H
+
+#include "common/util.h"
+#include "common/endian.h"
+
+#include "m4/graphics.h"
+
+/*
+ TODO:
+ - make a FontSystem class that creates/manages the fonts
+ (similar to FileSystem)
+*/
+
+namespace M4 {
+
+#define FONT_MENU "fontmenu.fnt"
+#define FONT_INTERFACE "fontintr.fnt"
+#define FONT_TINY "small.fnt"
+#define FONT_SMALL "small.fnt"
+#define FONT_MEDIUM "medium.fnt"
+#define FONT_LINE "fontline.fnt"
+#define FONT_CONVERSATION "fontconv.fnt"
+#define FONT_4X6 "4x6pp.fnt"
+#define FONT_5X6 "5x6pp.fnt"
+
+#define FONT_CONVERSATION_MADS "fontconv.ff"
+#define FONT_INTERFACE_MADS "fontintr.ff"
+#define FONT_MAIN_MADS "fontmain.ff"
+#define FONT_MENU_MADS "fontmenu.ff" // Not in Rex (uses bitmap files for menu strings)
+#define FONT_MISC_MADS "fontmisc.ff"
+#define FONT_TELE_MADS "fonttele.ff" // Not in Phantom
+#define FONT_PHAN_MADS "fontphan.ff" // Phantom only
+
+class Font {
+public:
+ Font(M4Engine *vm);
+ ~Font();
+ void setFont(const char *filename);
+ void setColor(uint8 color);
+ void setColors(uint8 alt1, uint8 alt2, uint8 foreground);
+
+ int32 getWidth(char *text, int spaceWidth = -1);
+ int32 getHeight() const { return _maxHeight; }
+ int32 write(M4Surface *surface, const char *text, int x, int y, int width, int spaceWidth, uint8 colors[]);
+ int32 writeString(M4Surface *surface, const char *text, int x, int y, int width = 0, int spaceWidth = -1) {
+ return write(surface, text, x, y, width, spaceWidth, _fontColors);
+ }
+
+private:
+ void setFontM4(const char *filename);
+ void setFontMads(const char *filename);
+
+ M4Engine *_vm;
+ uint8 _maxWidth, _maxHeight;
+ uint8 *_charWidths;
+ uint16 *_charOffs;
+ uint8 *_charData;
+ bool _sysFont;
+ const char *_filename;
+ uint8 _fontColors[4];
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/globals.cpp b/engines/m4/globals.cpp
new file mode 100644
index 0000000000..12d9a24d37
--- /dev/null
+++ b/engines/m4/globals.cpp
@@ -0,0 +1,447 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/m4.h"
+#include "m4/globals.h"
+#include "m4/graphics.h"
+#include "m4/gui.h"
+#include "m4/viewmgr.h"
+#include "m4/script.h"
+#include "m4/m4_views.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+Kernel::Kernel(M4Engine *vm) : _vm(vm) {
+ daemonTriggerAvailable = true;
+ firstFadeColorIndex = 0;
+ paused = false;
+ betweenRooms = false;
+ currentSection = 0;
+ newSection = 0;
+ previousSection = 0;
+ currentRoom = 0;
+ newRoom = 0;
+ previousRoom = 0;
+ trigger = 0;
+ triggerMode = KT_DAEMON;
+
+ _globalDaemonFn = NULL;
+ _globalParserFn = NULL;
+
+ _sectionInitFn = NULL;
+ _sectionDaemonFn = NULL;
+ _sectionParserFn = NULL;
+
+ _roomInitFn = NULL;
+ _roomDaemonFn = NULL;
+ _roomPreParserFn = NULL;
+ _roomParserFn = NULL;
+
+}
+
+int32 Kernel::createTrigger(int32 triggerNum) {
+ if (triggerNum < 0)
+ return triggerNum;
+ else
+ return triggerNum | (currentRoom << 16) | (triggerMode << 28);
+}
+
+bool Kernel::sendTrigger(int32 triggerNum) {
+ return handleTrigger(createTrigger(triggerNum));
+}
+
+bool Kernel::handleTrigger(int32 triggerNum) {
+
+ printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, triggerNum);
+
+ if (betweenRooms)
+ return true;
+
+ if (triggerNum < 0)
+ return false;
+
+ KernelTriggerType saveTriggerMode = triggerMode;
+ int32 saveTrigger = trigger;
+ bool result = false;
+
+ int room = (triggerNum >> 16) & 0xFFF;
+
+ printf("room = %d; currentRoom = %d\n", room, currentRoom); fflush(stdout);
+
+ if (room != currentRoom) {
+ printf("Kernel::handleTrigger() Trigger from another room\n");
+ return false;
+ }
+
+ trigger = triggerNum & 0xFFFF;
+ KernelTriggerType mode = (KernelTriggerType)(triggerNum >> 28);
+
+ switch (mode) {
+
+ case KT_PREPARSE:
+ if (trigger < 32000) {
+ triggerMode = KT_PREPARSE;
+ roomPreParser();
+ result = true;
+ }
+ break;
+
+ case KT_PARSE:
+ if (trigger < 32000) {
+ triggerMode = KT_PARSE;
+ // TODO player.commandReady = TRUE;
+ roomParser();
+ /* TODO
+ if (player.commandReady)
+ globalParser();
+ */
+ result = true;
+ }
+ break;
+
+ case KT_DAEMON:
+ printf("KT_DAEMON\n");
+ fflush(stdout);
+ triggerMode = KT_DAEMON;
+ daemonTriggerAvailable = false;
+ roomDaemon();
+ if (daemonTriggerAvailable) {
+ daemonTriggerAvailable = false;
+ sectionDaemon();
+ }
+ if (daemonTriggerAvailable) {
+ daemonTriggerAvailable = false;
+ globalDaemon();
+ }
+
+ break;
+
+ default:
+ printf("Kernel::handleTrigger() Unknown trigger mode %d\n", mode);
+
+ }
+
+ triggerMode = saveTriggerMode;
+ trigger = saveTrigger;
+
+ return result;
+}
+
+void Kernel::loadGlobalScriptFunctions() {
+ _globalDaemonFn = _vm->_script->loadFunction("global_daemon");
+ _globalParserFn = _vm->_script->loadFunction("global_parser");
+}
+
+void Kernel::loadSectionScriptFunctions() {
+ char tempFnName[128];
+ snprintf(tempFnName, 128, "section_init_%d", currentSection);
+ _sectionInitFn = _vm->_script->loadFunction(tempFnName);
+ snprintf(tempFnName, 128, "section_daemon_%d", currentSection);
+ _sectionDaemonFn = _vm->_script->loadFunction(tempFnName);
+ snprintf(tempFnName, 128, "section_parser_%d", currentSection);
+ _sectionParserFn = _vm->_script->loadFunction(tempFnName);
+}
+
+void Kernel::loadRoomScriptFunctions() {
+ char tempFnName[128];
+ snprintf(tempFnName, 128, "room_init_%d", currentRoom);
+ _roomInitFn = _vm->_script->loadFunction(tempFnName);
+ snprintf(tempFnName, 128, "room_daemon_%d", currentRoom);
+ _roomDaemonFn = _vm->_script->loadFunction(tempFnName);
+ snprintf(tempFnName, 128, "room_pre_parser_%d", currentRoom);
+ _roomPreParserFn = _vm->_script->loadFunction(tempFnName);
+ snprintf(tempFnName, 128, "room_parser_%d", currentRoom);
+ _roomParserFn = _vm->_script->loadFunction(tempFnName);
+}
+
+void Kernel::globalDaemon() {
+ if (_globalDaemonFn)
+ _vm->_script->runFunction(_globalDaemonFn);
+ else {
+ printf("Kernel::globalDaemon() _globalDaemonFn is NULL\n");
+ }
+}
+
+void Kernel::globalParser() {
+ if (_globalParserFn)
+ _vm->_script->runFunction(_globalParserFn);
+ else {
+ printf("Kernel::globalParser() _globalParserFn is NULL\n");
+ }
+}
+
+void Kernel::sectionInit() {
+ if (_sectionInitFn)
+ _vm->_script->runFunction(_sectionInitFn);
+ else {
+ printf("Kernel::sectionInit() _sectionInitFn is NULL\n");
+ }
+}
+
+void Kernel::sectionDaemon() {
+ if (_sectionDaemonFn)
+ _vm->_script->runFunction(_sectionDaemonFn);
+ else {
+ printf("Kernel::sectionDaemon() _sectionDaemonFn is NULL\n");
+ }
+}
+
+void Kernel::sectionParser() {
+ if (_sectionParserFn)
+ _vm->_script->runFunction(_sectionParserFn);
+ else {
+ printf("Kernel::sectionParser() _sectionParserFn is NULL\n");
+ }
+}
+
+void Kernel::roomInit() {
+ if (_roomInitFn)
+ _vm->_script->runFunction(_roomInitFn);
+ else {
+ printf("Kernel::roomInit() _roomInitFn is NULL\n");
+ }
+}
+
+void Kernel::roomDaemon() {
+ if (_roomDaemonFn)
+ _vm->_script->runFunction(_roomDaemonFn);
+ else {
+ printf("Kernel::roomDaemon() _roomDaemonFn is NULL\n");
+ }
+}
+
+void Kernel::roomPreParser() {
+ if (_roomPreParserFn)
+ _vm->_script->runFunction(_roomPreParserFn);
+ else {
+ printf("Kernel::roomPreParser() _roomPreParserFn is NULL\n");
+ }
+}
+
+void Kernel::roomParser() {
+ if (_roomParserFn)
+ _vm->_script->runFunction(_roomParserFn);
+ else {
+ printf("Kernel::roomParser() _roomParserFn is NULL\n");
+ }
+}
+
+void Kernel::pauseGame(bool value) {
+ paused = value;
+
+ if (paused) pauseEngines();
+ else unpauseEngines();
+}
+
+void Kernel::pauseEngines() {
+ // TODO: A proper implementation of game pausing. At the moment I'm using a hard-coded
+ // check in events.cpp on Kernel::paused to prevent any events going to the scene
+}
+
+void Kernel::unpauseEngines() {
+ // TODO: A proper implementation of game unpausing
+}
+
+//--------------------------------------------------------------------------
+
+Globals::Globals(M4Engine *vm): _vm(vm) {
+}
+
+Globals::~Globals() {
+ for(uint32 i = 0; i < _madsVocab.size(); i++)
+ free(_madsVocab[i]);
+ _madsVocab.clear();
+
+ for(uint32 i = 0; i < _madsQuotes.size(); i++)
+ free(_madsQuotes[i]);
+ _madsQuotes.clear();
+
+ _madsMessages.clear();
+}
+
+bool Globals::isInterfaceVisible() {
+ return _vm->_interfaceView->isVisible();
+}
+
+void Globals::loadMadsVocab() {
+ Common::SeekableReadStream *vocabS = _vm->res()->get("vocab.dat");
+ int curPos = 0;
+
+ char buffer[30];
+ strcpy(buffer, "");
+
+ while(!vocabS->eos()) {
+ buffer[curPos++] = vocabS->readByte();
+ if (buffer[curPos - 1] == '\0') {
+ // end of string, add it to the strings list
+ _madsVocab.push_back(strdup(buffer));
+ curPos = 0;
+ strcpy(buffer, "");
+ }
+ }
+
+ _vm->res()->toss("vocab.dat");
+}
+
+void Globals::loadMadsQuotes() {
+ Common::SeekableReadStream *quoteS = _vm->res()->get("quotes.dat");
+ int curPos = 0;
+
+ char buffer[128];
+ strcpy(buffer, "");
+
+ while(!quoteS->eos()) {
+ buffer[curPos++] = quoteS->readByte();
+ if (buffer[curPos - 1] == '\0') {
+ // end of string, add it to the strings list
+ _madsQuotes.push_back(strdup(buffer));
+ curPos = 0;
+ strcpy(buffer, "");
+ }
+ }
+
+ _vm->res()->toss("quotes.dat");
+}
+
+void Globals::loadMadsMessagesInfo() {
+ Common::SeekableReadStream *messageS = _vm->res()->get("messages.dat");
+
+ int16 count = messageS->readUint16LE();
+ //printf("%i messages\n", count);
+
+ for (int i = 0; i < count; i++) {
+ MessageItem *curMessage = new MessageItem();
+ curMessage->id = messageS->readUint32LE();
+ curMessage->offset = messageS->readUint32LE();
+ curMessage->uncompSize = messageS->readUint16LE();
+
+ if (i > 0)
+ _madsMessages[i - 1]->compSize = curMessage->offset - _madsMessages[i - 1]->offset;
+
+ if (i == count - 1)
+ curMessage->compSize = messageS->size() - curMessage->offset;
+
+ //printf("id: %i, offset: %i, uncomp size: %i\n", curMessage->id, curMessage->offset, curMessage->uncompSize);
+ _madsMessages.push_back(curMessage);
+ }
+
+ _vm->res()->toss("messages.dat");
+}
+
+char* Globals::loadMessage(uint32 index) {
+ if (index > _madsMessages.size() - 1) {
+ warning("Invalid message index: %i", index);
+ return NULL;
+ }
+
+ FabDecompressor fab;
+ byte *compData = new byte[_madsMessages[index]->compSize];
+ byte *buffer = new byte[_madsMessages[index]->uncompSize];
+
+ Common::SeekableReadStream *messageS = _vm->res()->get("messages.dat");
+ messageS->seek(_madsMessages[index]->offset, SEEK_SET);
+ messageS->read(compData, _madsMessages[index]->compSize);
+ fab.decompress(compData, _madsMessages[index]->compSize, buffer, _madsMessages[index]->uncompSize);
+
+ for (int i = 0; i < _madsMessages[index]->uncompSize - 1; i++)
+ if (buffer[i] == '\0') buffer[i] = '\n';
+
+ _vm->res()->toss("messages.dat");
+
+ return (char*)buffer;
+}
+
+//--------------------------------------------------------------------------
+
+Player::Player(M4Engine *vm) : _vm(vm) {
+ commandsAllowed = true;
+ needToWalk = false;
+ readyToWalk = false;
+ waitingForWalk = false;
+ commandReady = false;
+ strcpy(verb, "");
+ strcpy(noun, "");
+ strcpy(prep, "");
+ strcpy(object, "");
+}
+
+void Player::setCommandsAllowed(bool value) {
+ setCommandsAllowedFlag = true;
+ commandsAllowed = value;
+ if (value) {
+ // Player commands are enabled again
+ _vm->_mouse->lockCursor(CURSOR_ARROW);
+ //_vm->_interfaceView->cancelSentence();
+ } else {
+ // Player commands are disabled, so show hourglass cursor
+ _vm->_mouse->lockCursor(CURSOR_HOURGLASS);
+ }
+}
+
+bool Player::said(const char *word1, const char *word2, const char *word3) {
+ const char *words[3];
+ words[0] = word1;
+ words[1] = word2;
+ words[2] = word2;
+ for (int i = 0; i < 3; i++) {
+ if (words[i])
+ if ((scumm_stricmp(noun, words[i])) &&
+ (scumm_stricmp(object, words[i])) &&
+ (scumm_stricmp(verb, words[i])))
+ return false;
+ }
+ return true;
+}
+
+bool Player::saidAny(const char *word1, const char *word2, const char *word3,
+ const char *word4, const char *word5, const char *word6, const char *word7,
+ const char *word8, const char *word9, const char *word10) {
+ const char *words[10];
+ words[0] = word1;
+ words[1] = word2;
+ words[2] = word3;
+ words[3] = word4;
+ words[4] = word5;
+ words[5] = word6;
+ words[6] = word7;
+ words[7] = word8;
+ words[8] = word9;
+ words[9] = word10;
+ for (int i = 0; i < 10; i++) {
+ if (words[i]) {
+ if (!scumm_stricmp(noun, words[i]))
+ return true;
+ if (!scumm_stricmp(object, words[i]))
+ return true;
+ if (!scumm_stricmp(verb, words[i]))
+ return true;
+ }
+ }
+ return false;
+}
+
+
+} // End of namespace M4
diff --git a/engines/m4/globals.h b/engines/m4/globals.h
new file mode 100644
index 0000000000..a0133db2d6
--- /dev/null
+++ b/engines/m4/globals.h
@@ -0,0 +1,221 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_GLOBALS_H
+#define M4_GLOBALS_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/scummsys.h"
+
+namespace M4 {
+
+class M4Engine;
+class ScriptInterpreter;
+class ScriptFunction;
+
+// Globals
+enum WoodScriptGlobals {
+ kGlobTime = 0,
+ kGlobTimeDelta = 1,
+ kGlobMinY = 2,
+ kGlobMaxY = 3,
+ kGlobMinScale = 4,
+ kGlobMaxScale = 5,
+ kGlobScaler = 6,
+ kGlobTemp1 = 7,
+ kGlobTemp2 = 8,
+ kGlobTemp3 = 9,
+ kGlobTemp4 = 10,
+ kGlobTemp5 = 11,
+ kGlobTemp6 = 12,
+ kGlobTemp7 = 13,
+ kGlobTemp8 = 14,
+ kGlobTemp9 = 15,
+ kGlobTemp10 = 16,
+ kGlobTemp11 = 17,
+ kGlobTemp12 = 18,
+ kGlobTemp13 = 19,
+ kGlobTemp14 = 20,
+ kGlobTemp15 = 21,
+ kGlobTemp16 = 22,
+ kGlobTemp17 = 23,
+ kGlobTemp18 = 24,
+ kGlobTemp19 = 25,
+ kGlobTemp20 = 26,
+ kGlobTemp21 = 27,
+ kGlobTemp22 = 28,
+ kGlobTemp23 = 29,
+ kGlobTemp24 = 30,
+ kGlobTemp25 = 31,
+ kGlobTemp26 = 32,
+ kGlobTemp27 = 33,
+ kGlobTemp28 = 34,
+ kGlobTemp29 = 35,
+ kGlobTemp30 = 36,
+ kGlobTemp31 = 37,
+ kGlobTemp32 = 38
+};
+
+const uint32 SERIES_FORWARD = 0;
+const uint32 SERIES_PINGPONG = 1;
+const uint32 SERIES_BACKWARD = 2;
+const uint32 SERIES_RANDOM = 4;
+const uint32 SERIES_NO_TOSS = 8;
+const uint32 SERIES_STICK = 16;
+const uint32 SERIES_LOOP_TRIGGER = 32;
+const uint32 SERIES_LOAD_PALETTE = 64;
+const uint32 SERIES_HORZ_FLIP =128;
+
+enum KernelTriggerType {
+ KT_PARSE = 1,
+ KT_DAEMON,
+ KT_PREPARSE
+};
+
+class Kernel {
+private:
+ M4Engine *_vm;
+ ScriptFunction *_globalDaemonFn, *_globalParserFn;
+ ScriptFunction *_sectionInitFn, *_sectionDaemonFn, *_sectionParserFn;
+ ScriptFunction *_roomInitFn, *_roomDaemonFn, *_roomPreParserFn, *_roomParserFn;
+ void pauseEngines();
+ void unpauseEngines();
+public:
+ Kernel(M4Engine *vm);
+
+ // TODO: Move to some palette/fading class
+ int fadeUpDuration, firstFadeColorIndex;
+ int minPalEntry, maxPalEntry;
+
+ bool paused;
+ //machine* myWalker;
+ bool repeatedlyCallDeamon;
+ bool daemonTriggerAvailable;
+ bool betweenRooms;
+ int currentSection, newSection, previousSection;
+ int currentRoom, newRoom, previousRoom;
+
+ int32 trigger;
+ KernelTriggerType triggerMode;
+
+ int32 createTrigger(int32 triggerNum);
+ bool sendTrigger(int32 triggerNum);
+ bool handleTrigger(int32 triggerNum);
+
+ void loadGlobalScriptFunctions();
+ void loadSectionScriptFunctions();
+ void loadRoomScriptFunctions();
+
+ void globalDaemon();
+ void globalParser();
+
+ void sectionInit();
+ void sectionDaemon();
+ void sectionParser();
+
+ void roomInit();
+ void roomDaemon();
+ void roomPreParser();
+ void roomParser();
+
+ void pauseGame(bool value);
+};
+
+#define TOTAL_NUM_VARIABLES 256
+
+class Globals {
+private:
+ struct MessageItem {
+ uint32 id;
+ uint32 offset;
+ uint16 uncompSize;
+ uint16 compSize;
+ };
+
+ M4Engine *_vm;
+ Common::Array<char* > _madsVocab;
+ Common::Array<char* > _madsQuotes;
+ Common::Array<MessageItem* > _madsMessages;
+public:
+ Globals(M4Engine *vm);
+ ~Globals();
+ bool isInterfaceVisible();
+
+ // M4 variables
+ bool invSuppressClickSound;
+
+ void loadMadsVocab();
+ uint32 getVocabSize() { return _madsVocab.size(); }
+ char* getVocab(uint32 index) { return _madsVocab[index]; }
+
+ void loadMadsQuotes();
+ uint32 getQuotesSize() { return _madsQuotes.size(); }
+ char* getQuote(uint32 index) { return _madsQuotes[index]; }
+
+ void loadMadsMessagesInfo();
+ uint32 getMessagesSize() { return _madsMessages.size(); }
+ char* loadMessage(uint32 index);
+};
+
+#define PLAYER_FIELD_LENGTH 40
+
+class Player {
+public:
+ Player(M4Engine *vm);
+ void setCommandsAllowed(bool value);
+
+ // Variables
+ Common::Point position; // Player's current position
+ int facing; // Facing direction
+
+ char verb[PLAYER_FIELD_LENGTH]; // Action strings
+ char noun[PLAYER_FIELD_LENGTH];
+ char prep[PLAYER_FIELD_LENGTH];
+ char object[PLAYER_FIELD_LENGTH];
+ Common::String assetName, shadowName;
+ int walkerType, shadowType;
+ bool needToWalk, readyToWalk, waitingForWalk;
+ bool commandsAllowed;
+ bool commandReady;
+ bool visible;
+ bool beenInRoomBefore;
+ bool walkerInCurrentRoom;
+ int32 walkerTriggerNum;
+ int walkFacing;
+ bool setCommandsAllowedFlag;
+
+ bool said(const char *word1, const char *word2 = NULL, const char *word3 = NULL);
+ bool saidAny(const char *word1, const char *word2, const char *word3,
+ const char *word4, const char *word5, const char *word6, const char *word7,
+ const char *word8, const char *word9, const char *word10);
+
+private:
+ M4Engine *_vm;
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/graphics.cpp b/engines/m4/graphics.cpp
new file mode 100644
index 0000000000..beda178344
--- /dev/null
+++ b/engines/m4/graphics.cpp
@@ -0,0 +1,1074 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/file.h"
+#include "common/endian.h"
+#include "common/system.h"
+#include "common/util.h"
+#include "common/ptr.h"
+
+#include "m4/globals.h"
+#include "m4/graphics.h"
+#include "m4/sprite.h"
+#include "m4/m4.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+RGBList::RGBList(int numEntries, RGB8 *srcData, bool freeData) {
+ _size = numEntries;
+ assert(numEntries <= 256);
+
+ if (srcData == NULL) {
+ _data = new RGB8[numEntries];
+ _freeData = true;
+ } else {
+ _data = srcData;
+ _freeData = freeData;
+ }
+
+ _palIndexes = new byte[numEntries];
+ Common::set_to(&_palIndexes[0], &_palIndexes[numEntries], 0);
+}
+
+RGBList::~RGBList() {
+ if (_freeData)
+ delete[] _data;
+ delete[] _palIndexes;
+}
+
+//--------------------------------------------------------------------------
+
+#define VGA_COLOR_TRANS(x) (x == 0x3f ? 255 : x << 2)
+
+void M4Surface::loadCodesM4(Common::SeekableReadStream *source) {
+ if (!source) {
+ free();
+ return;
+ }
+
+ uint16 widthVal = source->readUint16LE();
+ uint16 heightVal = source->readUint16LE();
+
+ create(widthVal, heightVal, 1);
+ source->read(pixels, widthVal * heightVal);
+}
+
+void M4Surface::loadCodesMads(Common::SeekableReadStream *source) {
+ if (!source) {
+ free();
+ return;
+ }
+
+ uint16 widthVal = 320;
+ uint16 heightVal = 156;
+ byte *walkMap = new byte[source->size()];
+
+ create(widthVal, heightVal, 1);
+ source->read(walkMap, source->size());
+
+ byte *ptr = (byte *)getBasePtr(0, 0);
+
+ for (int y = 0; y < heightVal; y++) {
+ for (int x = 0; x < widthVal; x++) {
+ int ofs = x + (y * widthVal);
+ if ((walkMap[ofs / 8] << (ofs % 8)) & 0x80)
+ *ptr++ = 1; // walkable
+ else
+ *ptr++ = 0;
+ }
+ }
+}
+
+// Sprite related methods
+
+void M4Surface::vLine(int x, int y1, int y2) {
+ Graphics::Surface::vLine(x, y1, y2, _color);
+}
+
+void M4Surface::hLine(int x1, int x2, int y) {
+ Graphics::Surface::hLine(x1, y, x2, _color);
+}
+
+void M4Surface::vLineXor(int x, int y1, int y2) {
+ // Clipping
+ if (x < 0 || x >= w)
+ return;
+
+ if (y2 < y1)
+ SWAP(y2, y1);
+
+ if (y1 < 0)
+ y1 = 0;
+ if (y2 >= h)
+ y2 = h - 1;
+
+ byte *ptr = (byte *)getBasePtr(x, y1);
+ while (y1++ <= y2) {
+ *ptr ^= 0xFF;
+ ptr += pitch;
+ }
+
+}
+
+void M4Surface::hLineXor(int x1, int x2, int y) {
+ // Clipping
+ if (y < 0 || y >= h)
+ return;
+
+ if (x2 < x1)
+ SWAP(x2, x1);
+
+ if (x1 < 0)
+ x1 = 0;
+ if (x2 >= w)
+ x2 = w - 1;
+
+ if (x2 < x1)
+ return;
+
+ byte *ptr = (byte *)getBasePtr(x1, y);
+ while (x1++ <= x2)
+ *ptr++ ^= 0xFF;
+
+}
+
+void M4Surface::line(int x1, int y1, int x2, int y2, byte color) {
+ Graphics::Surface::drawLine(x1, y1, x2, y2, color);
+}
+
+
+void M4Surface::frameRect(int x1, int y1, int x2, int y2) {
+ Graphics::Surface::frameRect(Common::Rect(x1, y1, x2, y2), _color);
+}
+
+void M4Surface::fillRect(int x1, int y1, int x2, int y2) {
+ Graphics::Surface::fillRect(Common::Rect(x1, y1, x2, y2), _color);
+}
+
+void M4Surface::drawSprite(int x, int y, SpriteInfo &info, const Common::Rect &clipRect) {
+
+ enum {
+ kStatusSkip,
+ kStatusScale,
+ kStatusDraw
+ };
+
+ // NOTE: The current clipping code assumes that the top left corner of the clip
+ // rectangle is always 0, 0
+ assert(clipRect.top == 0 && clipRect.left == 0);
+
+ // TODO: Put err* and scaled* into SpriteInfo
+ int errX = info.hotX * info.scaleX % 100;
+ int errY = info.hotY * info.scaleY % 100;
+ int scaledWidth = scaleValue(info.width, info.scaleX, errX);
+ int scaledHeight = scaleValue(info.height, info.scaleY, errY);
+
+ /*
+ printf("M4Surface::drawSprite() info.width = %d; info.scaleX = %d; info.height = %d; info.scaleY = %d; scaledWidth = %d; scaledHeight = %d\n",
+ info.width, info.scaleX, info.height, info.scaleY, scaledWidth, scaledHeight); fflush(stdout);
+ */
+
+ int clipX = 0, clipY = 0;
+ // Clip the sprite's width and height according to the clip rectangle's dimensions
+ // This clips the sprite to the bottom and right
+ if (x >= 0) {
+ scaledWidth = MIN<int>(x + scaledWidth, clipRect.right) - x;
+ } else {
+ clipX = x;
+ scaledWidth = x + scaledWidth;
+ }
+ if (y >= 0) {
+ scaledHeight = MIN<int>(y + scaledHeight, clipRect.bottom) - y;
+ } else {
+ clipY = y;
+ scaledHeight = y + scaledHeight;
+ }
+
+ //printf("M4Surface::drawSprite() width = %d; height = %d; scaledWidth = %d; scaledHeight = %d\n", info.width, info.height, scaledWidth, scaledHeight); fflush(stdout);
+
+ // Check if sprite is inside the screen. If it's not, there's no need to draw it
+ if (scaledWidth + x <= 0 || scaledHeight + y <= 0) // check left and top (in case x,y are negative)
+ return;
+ if (scaledWidth <= 0 || scaledHeight <= 0) // check right and bottom
+ return;
+ int heightAmt = scaledHeight;
+
+ byte *src = info.sprite->getData();
+ byte *dst = getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY);
+
+ int status = kStatusSkip;
+ byte *scaledLineBuf = new byte[scaledWidth];
+
+ while (heightAmt > 0) {
+
+ if (status == kStatusSkip) {
+ // Skip line
+ errY -= info.scaleY;
+ if (errY < 0)
+ status = kStatusScale;
+ else
+ src += info.width;
+ } else {
+
+ if (status == kStatusScale) {
+ // Scale current line
+ byte *lineDst = scaledLineBuf;
+ int curErrX = errX;
+ int widthVal = scaledWidth;
+ byte *tempSrc = src;
+ int startX = clipX;
+ while (widthVal > 0) {
+ byte pixel = *tempSrc++;
+ curErrX -= info.scaleX;
+ while (curErrX < 0) {
+ if (startX == 0) {
+ *lineDst++ = pixel;
+ widthVal--;
+ } else {
+ startX++;
+ }
+ curErrX += 100;
+ }
+ }
+ src += info.width;
+ status = kStatusDraw;
+ }
+
+ if (status == kStatusDraw && clipY == 0) {
+ // Draw previously scaled line
+ // TODO Implement different drawing types (depth, shadow etc.)
+ byte *tempDst = dst;
+ for (int lineX = 0; lineX < scaledWidth; lineX++) {
+ byte pixel = scaledLineBuf[lineX];
+
+ if (info.encoding & 0x80) {
+
+ if (pixel == 0x80) {
+ pixel = 0;
+ } else {
+ byte destPixel = *tempDst;
+ byte r, g, b;
+ r = CLIP((info.palette[destPixel].r * pixel) >> 10, 0, 31);
+ g = CLIP((info.palette[destPixel].g * pixel) >> 10, 0, 31);
+ b = CLIP((info.palette[destPixel].b * pixel) >> 10, 0, 31);
+ pixel = info.inverseColorTable[(b << 10) | (g << 5) | r];
+ }
+ }
+
+ if (pixel)
+ *tempDst = pixel;
+
+ tempDst++;
+ }
+ dst += pitch;
+ heightAmt--;
+ // TODO depth etc.
+ //depthAddress += Destination -> Width;
+
+ errY += 100;
+ if (errY >= 0)
+ status = kStatusSkip;
+ } else if (status == kStatusDraw && clipY < 0) {
+ clipY++;
+
+ errY += 100;
+ if (errY >= 0)
+ status = kStatusSkip;
+ }
+
+ }
+
+ }
+
+ delete[] scaledLineBuf;
+
+}
+
+// Surface methods
+
+byte *M4Surface::getData() {
+ return (byte *)pixels;
+}
+
+byte *M4Surface::getBasePtr(int x, int y) {
+ return (byte *)Graphics::Surface::getBasePtr(x, y);
+}
+
+void M4Surface::freeData() {
+}
+
+void M4Surface::empty() {
+ Common::set_to((byte *) pixels, (byte *) pixels + w * h, _vm->_palette->BLACK);
+}
+
+void M4Surface::frameRect(const Common::Rect &r, uint8 color) {
+ Graphics::Surface::frameRect(r, color);
+}
+
+void M4Surface::fillRect(const Common::Rect &r, uint8 color) {
+ Graphics::Surface::fillRect(r, color);
+}
+
+void M4Surface::copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY,
+ int transparentColor) {
+ // Validation of the rectangle and position
+ if ((destX >= w) || (destY >= h))
+ return;
+
+ Common::Rect copyRect = srcBounds;
+ if (destX < 0) {
+ copyRect.left += -destX;
+ destX = 0;
+ } else if (destX + copyRect.width() > w) {
+ copyRect.right -= destX + copyRect.width() - w;
+ }
+ if (destY < 0) {
+ copyRect.top += -destY;
+ destY = 0;
+ } else if (destY + copyRect.height() > h) {
+ copyRect.bottom -= destY + copyRect.height() - h;
+ }
+
+ if (!copyRect.isValidRect())
+ return;
+
+ // Copy the specified area
+
+ byte *data = src->getData();
+ byte *srcPtr = data + (src->width() * copyRect.top + copyRect.left);
+ byte *destPtr = (byte *)pixels + (destY * width()) + destX;
+
+ for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) {
+ if (transparentColor == -1)
+ // No transparency, so copy line over
+ Common::copy(srcPtr, srcPtr + copyRect.width(), destPtr);
+ else {
+ // Copy each byte one at a time checking for the transparency color
+ for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr)
+ if (srcPtr[xCtr] != transparentColor) destPtr[xCtr] = srcPtr[xCtr];
+ }
+
+ srcPtr += src->width();
+ destPtr += width();
+ }
+
+ src->freeData();
+}
+
+void M4Surface::loadBackgroundRiddle(const char *sceneName) {
+ char resourceName[20];
+ Common::SeekableReadStream *stream;
+ // Loads a Riddle scene
+ sprintf(resourceName, "%s.tt", sceneName);
+ stream = _vm->_resourceManager->get(resourceName);
+ m4LoadBackground(stream);
+ _vm->_resourceManager->toss(resourceName);
+}
+
+void M4Surface::loadBackground(int sceneNumber, RGBList **palData) {
+ this->empty(); // clear previous scene
+
+ if (_vm->isM4() || (_vm->getGameType() == GType_RexNebular)) {
+ char resourceName[20];
+ Common::SeekableReadStream *stream;
+
+ if (_vm->getGameType() == GType_RexNebular) {
+ // Load Rex Nebular screen
+ sprintf(resourceName, "rm%d.art", sceneNumber);
+ stream = _vm->_resourceManager->get(resourceName);
+ rexLoadBackground(stream, palData);
+ } else {
+ // Loads M4 game scene
+ if (palData)
+ *palData = NULL;
+ sprintf(resourceName, "%i.tt", sceneNumber);
+ stream = _vm->_resourceManager->get(resourceName);
+ m4LoadBackground(stream);
+ }
+
+ _vm->_resourceManager->toss(resourceName);
+
+ } else {
+ madsLoadBackground(sceneNumber, palData);
+ }
+}
+
+void M4Surface::madsLoadBackground(int roomNumber, RGBList **palData) {
+ // Get a MadsPack reference to the tile set and mapping
+ char resourceName[20];
+ int i;
+
+ // Uncompressed tile map resource
+ sprintf(resourceName, "rm%d.mm", roomNumber);
+ MadsPack tileMapFile(resourceName, _vm);
+ Common::SeekableReadStream *mapStream = tileMapFile.getItemStream(0);
+
+ // Get the details of the tiles and map
+ mapStream->readUint32LE();
+ int tileCountX = mapStream->readUint16LE();
+ int tileCountY = mapStream->readUint16LE();
+ int tileWidthMap = mapStream->readUint16LE();
+ int tileHeightMap = mapStream->readUint16LE();
+ int screenWidth = mapStream->readUint16LE();
+ int screenHeight = mapStream->readUint16LE();
+ int tileCountMap = tileCountX * tileCountY;
+ delete mapStream;
+
+ // Obtain tile map information
+ typedef Common::List<Common::SharedPtr<M4Surface> > TileSetList;
+ typedef TileSetList::iterator TileSetIterator;
+ TileSetList tileSet;
+ uint16 *tileMap = new uint16[tileCountMap];
+ mapStream = tileMapFile.getItemStream(1);
+ for (i = 0; i < tileCountMap; ++i)
+ tileMap[i] = mapStream->readUint16LE();
+ delete mapStream;
+ _vm->res()->toss(resourceName);
+
+ // --------------------------------------------------------------------------------
+
+ // Tile map data, which needs to be kept compressed, as the tile offsets refer to
+ // the compressed data. Each tile is then uncompressed separately
+ sprintf(resourceName, "rm%d.tt", roomNumber);
+ Common::SeekableReadStream *tileDataComp = _vm->_resourceManager->get(resourceName);
+ MadsPack tileData(tileDataComp);
+ Common::SeekableReadStream *tileDataUncomp = tileData.getItemStream(0);
+
+ // Validate that the data matches between the tiles and tile map file and is valid
+ int tileCount = tileDataUncomp->readUint16LE();
+ int tileWidth = tileDataUncomp->readUint16LE();
+ int tileHeight = tileDataUncomp->readUint16LE();
+ delete tileDataUncomp;
+ assert(tileCountMap == tileCount);
+ assert(tileWidth == tileWidthMap);
+ assert(tileHeight == tileHeightMap);
+ assert(screenWidth == _vm->_screen->width());
+ assert(screenHeight <= _vm->_screen->height());
+
+ // --------------------------------------------------------------------------------
+
+ // Get the palette to use
+ tileDataUncomp = tileData.getItemStream(2);
+ // Set palette
+ if (!palData) {
+ _vm->_palette->setMadsPalette(tileDataUncomp, 4);
+ } else {
+ int numColors;
+ RGB8 *rgbList = _vm->_palette->decodeMadsPalette(tileDataUncomp, &numColors);
+ *palData = new RGBList(numColors, rgbList, true);
+ }
+ delete tileDataUncomp;
+
+ // --------------------------------------------------------------------------------
+
+ // Get tile data
+
+ tileDataUncomp = tileData.getItemStream(1);
+ FabDecompressor fab;
+ uint32 compressedTileDataSize = 0;
+
+ for (i = 0; i < tileCount; i++) {
+ tileDataUncomp->seek(i * 4, SEEK_SET);
+ uint32 tileOfs = tileDataUncomp->readUint32LE();
+ M4Surface* newTile = new M4Surface(tileWidth, tileHeight);
+
+ if (i == tileCount - 1)
+ compressedTileDataSize = tileDataComp->size() - tileOfs;
+ else
+ compressedTileDataSize = tileDataUncomp->readUint32LE() - tileOfs;
+
+ //printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize);
+
+ newTile->empty();
+
+ byte *compressedTileData = new byte[compressedTileDataSize];
+
+ tileDataComp->seek(tileData.getDataOffset() + tileOfs, SEEK_SET);
+ tileDataComp->read(compressedTileData, compressedTileDataSize);
+
+ fab.decompress(compressedTileData, compressedTileDataSize, (byte*)newTile->pixels, tileWidth * tileHeight);
+ tileSet.push_back(TileSetList::value_type(newTile));
+ delete[] compressedTileData;
+ }
+
+ delete tileDataUncomp;
+
+ // --------------------------------------------------------------------------------
+
+ // Loop through the mapping data to place the tiles on the screen
+
+ uint16 *tIndex = &tileMap[0];
+ for (int y = 0; y < tileCountY; y++) {
+ for (int x = 0; x < tileCountX; x++) {
+ int tileIndex = *tIndex++;
+ assert(tileIndex < tileCount);
+ TileSetIterator tile = tileSet.begin();
+ for (i = 0; i < tileIndex; i++)
+ ++tile;
+ ((*tile).get())->copyTo(this, x * tileWidth, y * tileHeight);
+ }
+ }
+ tileSet.clear();
+ _vm->res()->toss(resourceName);
+}
+
+void M4Surface::rexLoadBackground(Common::SeekableReadStream *source, RGBList **palData) {
+ MadsPack packData(source);
+ Common::MemoryReadStream *sourceUnc = packData.getItemStream(0);
+
+ int sceneWidth = sourceUnc->readUint16LE();
+ int sceneHeight = sourceUnc->readUint16LE();
+ int sceneSize = sceneWidth * sceneHeight;
+ if (sceneWidth > this->width()) {
+ warning("Background width is %i, too large to fit in screen. Setting it to %i", sceneWidth, this->width());
+ sceneWidth = this->width();
+ sceneSize = sceneWidth * sceneHeight;
+ }
+ if (sceneHeight > this->height()) {
+ warning("Background height is %i, too large to fit in screen.Setting it to %i", sceneHeight, this->height());
+ sceneHeight = this->height();
+ sceneSize = sceneWidth * sceneHeight;
+ }
+
+ // Set palette
+ if (!palData) {
+ _vm->_palette->setMadsPalette(sourceUnc, 4);
+ } else {
+ int numColors;
+ RGB8 *rgbList = _vm->_palette->decodeMadsPalette(sourceUnc, &numColors);
+ *palData = new RGBList(numColors, rgbList, true);
+ }
+ delete sourceUnc;
+
+ // Get the raw data for the background
+ sourceUnc = packData.getItemStream(1);
+ assert((int)sourceUnc->size() >= sceneSize);
+
+ byte *pData = (byte *)pixels;
+ sourceUnc->read(pData, sceneSize);
+
+ freeData();
+ delete sourceUnc;
+}
+
+#undef COL_TRANS
+
+void M4Surface::m4LoadBackground(Common::SeekableReadStream *source) {
+ M4Surface *tileBuffer = new M4Surface();
+ uint curTileX = 0, curTileY = 0;
+ int clipX = 0, clipY = 0;
+ RGB8 palette[256];
+
+ source->readUint32LE(); // magic, unused
+ /*uint32 size =*/ source->readUint32LE();
+ uint32 widthVal = source->readUint32LE();
+ uint32 heightVal = source->readUint32LE();
+ uint32 tilesX = source->readUint32LE();
+ uint32 tilesY = source->readUint32LE();
+ uint32 tileWidth = source->readUint32LE();
+ uint32 tileHeight = source->readUint32LE();
+ uint8 blackIndex = 0;
+
+ // Debug
+ //printf("loadBackground(): %dx%d picture (%d bytes) - %dx%d tiles of size %dx%d\n",
+ // widthVal, heightVal, size, tilesX, tilesY, tileWidth, tileHeight);
+
+ // BGR data, which is converted to RGB8
+ for (uint i = 0; i < 256; i++) {
+ palette[i].b = source->readByte() << 2;
+ palette[i].g = source->readByte() << 2;
+ palette[i].r = source->readByte() << 2;
+ palette[i].u = source->readByte() << 2;
+
+ if ((blackIndex == 0) && !palette[i].r && !palette[i].g && !palette[i].b)
+ blackIndex = i;
+ }
+
+ _vm->_palette->setPalette(palette, 0, 256);
+
+ // resize or create the surface
+ // note that the height of the scene in game scenes is smaller than 480, as the bottom part of the
+ // screen is the inventory
+ assert(width() == (int)widthVal);
+ //printf("width(): %d, widthVal: %d, height(): %d, heightVal: %d\n", width(), widthVal, height(), heightVal);
+
+ tileBuffer->create(tileWidth, tileHeight, 1);
+
+ for (curTileY = 0; curTileY < tilesY; curTileY++) {
+ clipY = MIN(heightVal, (1 + curTileY) * tileHeight) - (curTileY * tileHeight);
+
+ for (curTileX = 0; curTileX < tilesX; curTileX++) {
+ clipX = MIN(widthVal, (1 + curTileX) * tileWidth) - (curTileX * tileWidth);
+
+ // Read a tile and copy it to the destination surface
+ source->read(tileBuffer->pixels, tileWidth * tileHeight);
+ Common::Rect srcBounds(0, 0, clipX, clipY);
+ copyFrom(tileBuffer, srcBounds, curTileX * tileWidth, curTileY * tileHeight);
+ }
+ }
+
+ if (heightVal < (uint)height())
+ fillRect(Common::Rect(0, heightVal, width(), height()), blackIndex);
+
+ delete tileBuffer;
+}
+
+void M4Surface::madsloadInterface(int index, RGBList **palData) {
+ char resourceName[20];
+ sprintf(resourceName, "i%d.int", index);
+ MadsPack intFile(resourceName, _vm);
+ RGB8 *palette = new RGB8[16];
+
+ // Chunk 0, palette
+ Common::SeekableReadStream *intStream = intFile.getItemStream(0);
+
+ for (int i = 0; i < 16; i++) {
+ palette[i].r = intStream->readByte() << 2;
+ palette[i].g = intStream->readByte() << 2;
+ palette[i].b = intStream->readByte() << 2;
+ intStream->readByte();
+ intStream->readByte();
+ intStream->readByte();
+ }
+ *palData = new RGBList(16, palette, true);
+ delete intStream;
+
+ // Chunk 1, data
+ intStream = intFile.getItemStream(1);
+ create(320, 44, 1);
+ intStream->read(pixels, 320 * 44);
+ delete intStream;
+}
+
+void M4Surface::translate(RGBList *list, bool isTransparent) {
+ byte *p = getBasePtr(0, 0);
+ byte *palIndexes = list->palIndexes();
+
+ for (int i = 0; i < width() * height(); ++i, ++p) {
+ if (!isTransparent || (*p != 0)) {
+ assert(*p < list->size());
+ *p = palIndexes[*p];
+ }
+ }
+
+ freeData();
+}
+
+//--------------------------------------------------------------------------
+// Palette class
+//
+
+#define GREEN_START 32
+#define NUM_GREENS 32
+#define GREEN_END (GREEN_START + NUM_GREENS - 1)
+#define NORMAL_START 64
+#define NORMAL_END 255
+#define NUM_NORMAL (NORMAL_END - NORMAL_START + 1)
+
+// Support function for creating a list of palette indexes to change entries in the shaded range to
+
+static void makeTranslationList(RGB8 *palData, byte transList[NUM_GREENS]) {
+ int i, j, minDistance;
+ byte bestIndex;
+
+ for (i = 0; i < NUM_GREENS; ++i) {
+ bestIndex = NORMAL_START;
+ minDistance = 255;
+
+ uint8 findCol = palData[GREEN_START + i].g;
+
+ // Find the closest matching palette color
+ for (j = NORMAL_START; j <= NORMAL_END; ++j) {
+ int greenVal = palData[j].g;
+ if (ABS(findCol - greenVal) < minDistance) {
+ minDistance = ABS(findCol - greenVal);
+ bestIndex = j;
+ }
+
+ if (minDistance == 0)
+ break;
+ }
+
+ transList[i] = bestIndex;
+ }
+}
+
+// Support function for fading in or out
+
+static void fadeRange(M4Engine *vm, RGB8 *srcPal, RGB8 *destPal, int startIndex, int endIndex,
+ int numSteps, uint delayAmount) {
+ RGB8 tempPal[256];
+
+ // perform the fade
+ for(int stepCtr = 1; stepCtr <= numSteps; ++stepCtr) {
+ // Delay the specified amount
+ uint32 startTime = g_system->getMillis();
+ while ((g_system->getMillis() - startTime) < delayAmount) {
+ vm->_events->handleEvents();
+ g_system->delayMillis(10);
+ }
+
+ for (int i = startIndex; i <= endIndex; ++i) {
+ // Handle the intermediate rgb values for fading
+ tempPal[i].r = (byte) (srcPal[i].r + (destPal[i].r - srcPal[i].r) * stepCtr / numSteps);
+ tempPal[i].g = (byte) (srcPal[i].g + (destPal[i].g - srcPal[i].g) * stepCtr / numSteps);
+ tempPal[i].b = (byte) (srcPal[i].b + (destPal[i].b - srcPal[i].b) * stepCtr / numSteps);
+ }
+
+ vm->_palette->setPalette(&tempPal[startIndex], startIndex, endIndex - startIndex + 1);
+ vm->_viewManager->refreshAll();
+ }
+
+ // Make sure the end palette exactly matches what is wanted
+ vm->_palette->setPalette(&destPal[startIndex], startIndex, endIndex - startIndex + 1);
+}
+
+Palette::Palette(M4Engine *vm) : _vm(vm) {
+ reset();
+ _fading_in_progress = false;
+ Common::set_to(&_usageCount[0], &_usageCount[256], 0);
+}
+
+void Palette::setPalette(const byte *colors, uint start, uint num) {
+ g_system->setPalette(colors, start, num);
+ reset();
+}
+
+void Palette::setPalette(const RGB8 *colors, uint start, uint num) {
+ g_system->setPalette((const byte *)colors, start, num);
+ reset();
+}
+
+void Palette::grabPalette(byte *colors, uint start, uint num) {
+ g_system->grabPalette(colors, start, num);
+ reset();
+}
+
+uint8 Palette::palIndexFromRgb(byte r, byte g, byte b, RGB8 *paletteData) {
+ byte index = 0;
+ int32 minDist = 0x7fffffff;
+ RGB8 palData[256];
+ int Rdiff, Gdiff, Bdiff;
+
+ if (paletteData == NULL) {
+ g_system->grabPalette((byte *)palData, 0, 256);
+ paletteData = &palData[0];
+ }
+
+ for (int palIndex = 0; palIndex < 256; ++palIndex) {
+ Rdiff = r - paletteData[palIndex].r;
+ Gdiff = g - paletteData[palIndex].g;
+ Bdiff = b - paletteData[palIndex].b;
+
+ if (Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff < minDist) {
+ minDist = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff;
+ index = (uint8)palIndex;
+ }
+ }
+
+ return (uint8)index;
+}
+
+void Palette::reset() {
+ RGB8 palData[256];
+ g_system->grabPalette((byte *)palData, 0, 256);
+
+ BLACK = palIndexFromRgb(0, 0, 0, palData);
+ BLUE = palIndexFromRgb(0, 0, 255, palData);
+ GREEN = palIndexFromRgb(0, 255, 0, palData);
+ CYAN = palIndexFromRgb(0, 255, 255, palData);
+ RED = palIndexFromRgb(255, 0, 0, palData);
+ VIOLET = palIndexFromRgb(255, 0, 255, palData);
+ BROWN = palIndexFromRgb(168, 84, 84, palData);
+ LIGHT_GRAY = palIndexFromRgb(168, 168, 168, palData);
+ DARK_GRAY = palIndexFromRgb(84, 84, 84, palData);
+ LIGHT_BLUE = palIndexFromRgb(0, 0, 127, palData);
+ LIGHT_GREEN = palIndexFromRgb(0, 127, 0, palData);
+ LIGHT_CYAN = palIndexFromRgb(0, 127, 127, palData);
+ LIGHT_RED = palIndexFromRgb(84, 0, 0, palData);
+ PINK = palIndexFromRgb(84, 0, 0, palData);
+ YELLOW = palIndexFromRgb(0, 84, 84, palData);
+ WHITE = palIndexFromRgb(255, 255, 255, palData);
+}
+
+void Palette::fadeToGreen(int numSteps, uint delayAmount) {
+ if (_fading_in_progress)
+ return;
+ _fading_in_progress = true;
+ byte translationList[NUM_GREENS];
+
+ int i;
+ byte *tempP;
+ uint8 greenAmount = 0;
+ RGB8 *srcPalette = (RGB8 *) &_originalPalette[0];
+ RGB8 *destPalette = (RGB8 *) &_fadedPalette[0];
+
+ _vm->_palette->grabPalette(srcPalette, 0, 256);
+
+ // Create the destination 'greenish' palette to fade to by setting the green component
+ // to the average of the RGB bytes, and leaving the Red and Blue parts as 0
+
+ Common::copy(&srcPalette[0], &srcPalette[256], &destPalette[0]);
+ for (i = 32; i < 256; ++i) {
+ byte luminance = (byte)((destPalette[i].r + destPalette[i].g + destPalette[i].b) / 3);
+ destPalette[i].g = MIN((byte)255, luminance);
+ destPalette[i].r = destPalette[i].b = 0;
+ }
+
+ // Handle the actual fading
+ fadeRange(_vm, srcPalette, destPalette, 21, 255, numSteps, delayAmount);
+
+ // Create a translation table to be used in translating pixels in the game surface
+ // using palette indexes in the range the range #32-63 into values from #64-255
+
+ makeTranslationList(destPalette, translationList);
+
+ // Use palette indexes from #32-63 for the range of possible shades
+
+ for (i = GREEN_START; i <= GREEN_END; ++i, greenAmount += 8) {
+ destPalette[i].g = greenAmount;
+ destPalette[i].r = destPalette[i].b = 0;
+ }
+
+ // Remap all pixels into the #32-63 range
+
+ tempP = _vm->_scene->getData();
+ for (int pixelCtr = 0; pixelCtr < _vm->_scene->width() * _vm->_scene->height();
+ ++pixelCtr, ++tempP) {
+ // If pixel is in #32-63 range already, remap to higher palette entries
+ if ((*tempP >= GREEN_START) && (*tempP <= GREEN_END))
+ *tempP = translationList[*tempP - GREEN_START];
+
+ *tempP = (uint8) (GREEN_START + (destPalette[*tempP].g >> 3));
+ }
+
+ _vm->_palette->setPalette(&destPalette[GREEN_START], GREEN_START, NUM_GREENS);
+ _vm->_viewManager->refreshAll();
+ _fading_in_progress = false;
+}
+
+void Palette::fadeFromGreen(int numSteps, uint delayAmount, bool fadeToBlack) {
+ if (_fading_in_progress)
+ return;
+ _fading_in_progress = true;
+ RGB8 blackPalette[256];
+ RGB8 *fadedPalette = (RGB8 *) &_fadedPalette[0];
+ RGB8 *destPalette = (RGB8 *) &_originalPalette[0];
+
+ if (fadeToBlack) {
+ Common::set_to((byte *)&blackPalette[0], (byte *)&blackPalette[256], 0);
+ destPalette = &blackPalette[0];
+ }
+
+ // Initially restore the faded palette
+ _vm->_palette->setPalette(fadedPalette, 0, 256);
+ _vm->_viewManager->refreshAll();
+
+ // Restore the pixel data from the original screen
+ _vm->_scene->update();
+
+ // Handle the actual fading
+ fadeRange(_vm, fadedPalette, destPalette, GREEN_START, NORMAL_END, numSteps, delayAmount);
+
+ _fading_in_progress = false;
+}
+
+void Palette::fadeIn(int numSteps, uint delayAmount, RGBList *destPalette) {
+ fadeIn(numSteps, delayAmount, destPalette->data(), destPalette->size());
+}
+
+void Palette::fadeIn(int numSteps, uint delayAmount, RGB8 *destPalette, int numColors) {
+ if (_fading_in_progress)
+ return;
+
+ _fading_in_progress = true;
+ RGB8 blackPalette[256];
+ Common::set_to((byte *)&blackPalette[0], (byte *)&blackPalette[256], 0);
+
+ // Initially set the black palette
+ _vm->_palette->setPalette(blackPalette, 0, numColors);
+
+ // Handle the actual fading
+ fadeRange(_vm, blackPalette, destPalette, 0, numColors - 1, numSteps, delayAmount);
+
+ _fading_in_progress = false;
+}
+
+RGB8 *Palette::decodeMadsPalette(Common::SeekableReadStream *palStream, int *numColors) {
+ *numColors = palStream->readUint16LE();
+ assert(*numColors <= 252);
+
+ RGB8 *palData = new RGB8[*numColors];
+ Common::set_to((byte *)&palData[0], (byte *)&palData[*numColors], 0);
+
+ for (int i = 0; i < *numColors; ++i) {
+ byte r = palStream->readByte();
+ byte g = palStream->readByte();
+ byte b = palStream->readByte();
+ palData[i].r = VGA_COLOR_TRANS(r);
+ palData[i].g = VGA_COLOR_TRANS(g);
+ palData[i].b = VGA_COLOR_TRANS(b);
+
+ // The next 3 bytes are unused
+ palStream->skip(3);
+ }
+
+ return palData;
+}
+
+int Palette::setMadsPalette(Common::SeekableReadStream *palStream, int indexStart) {
+ int colorCount;
+ RGB8 *palData = Palette::decodeMadsPalette(palStream, &colorCount);
+ _vm->_palette->setPalette(palData, indexStart, colorCount);
+ delete palData;
+ return colorCount;
+}
+
+void Palette::setMadsSystemPalette() {
+ // Rex Nebular default system palette
+ resetColorCounts();
+
+ RGB8 palData[4];
+ palData[0].r = palData[0].g = palData[0].b = 0;
+ palData[1].r = palData[1].g = palData[1].b = 0x54;
+ palData[2].r = palData[2].g = palData[2].b = 0xb4;
+ palData[3].r = palData[3].g = palData[3].b = 0xff;
+
+ setPalette(palData, 0, 4);
+ blockRange(0, 4);
+}
+
+void Palette::resetColorCounts() {
+ Common::set_to(&_usageCount[0], &_usageCount[256], 0);
+}
+
+void Palette::blockRange(int startIndex, int size) {
+ // Use a reference count of -1 to signal a palette index shouldn't be used
+ Common::set_to(&_usageCount[startIndex], &_usageCount[startIndex + size], -1);
+}
+
+void Palette::addRange(RGBList *list) {
+ RGB8 *data = list->data();
+ byte *palIndexes = list->palIndexes();
+ RGB8 palData[256];
+ g_system->grabPalette((byte *)&palData[0], 0, 256);
+ bool paletteChanged = false;
+
+ for (int colIndex = 0; colIndex < list->size(); ++colIndex) {
+ // Scan through for an existing copy of the RGB value
+ int palIndex = -1;
+ while (++palIndex < 256) {
+ if (_usageCount[palIndex] <= 0)
+ // Palette index is to be skipped
+ continue;
+
+ if ((palData[palIndex].r == data[colIndex].r) &&
+ (palData[palIndex].g == data[colIndex].g) &&
+ (palData[palIndex].b == data[colIndex].b))
+ // Match found
+ break;
+ }
+
+ if (palIndex == 256) {
+ // No match found, so find a free slot to use
+ palIndex = -1;
+ while (++palIndex < 256) {
+ if (_usageCount[palIndex] == 0)
+ break;
+ }
+
+ if (palIndex == 256)
+ error("addRange - Ran out of palette space to allocate");
+
+ palData[palIndex].r = data[colIndex].r;
+ palData[palIndex].g = data[colIndex].g;
+ palData[palIndex].b = data[colIndex].b;
+ paletteChanged = true;
+ }
+
+ palIndexes[colIndex] = palIndex;
+ ++_usageCount[palIndex];
+ }
+
+ if (paletteChanged) {
+ g_system->setPalette((byte *)&palData[0], 0, 256);
+ reset();
+ }
+}
+
+void Palette::deleteRange(RGBList *list) {
+ // Release the reference count on each of the palette entries
+ for (int colIndex = 0; colIndex < list->size(); ++colIndex) {
+ int palIndex = list->palIndexes()[colIndex];
+ assert(_usageCount[palIndex] > 0);
+ --_usageCount[palIndex];
+ }
+}
+
+void Palette::deleteAllRanges() {
+ for (int colIndex = 0; colIndex < 255; ++colIndex)
+ _usageCount[colIndex] = 0;
+}
+
+//--------------------------------------------------------------------------
+// Support methods
+
+void decompressRle(byte *rleData, int rleSize, byte *celData, int w, int h) {
+ byte *src = rleData;
+ byte *dst = celData;
+ byte len;
+ while (1) {
+ len = *src++;
+ if (len == 0) {
+ len = *src++;
+ if (len <= 2) {
+ if (len == 1) // end of sprite marker
+ break;
+ } else {
+ while (len--)
+ *dst++ = *src++;
+ }
+ } else {
+ while (len--)
+ *dst++ = *src;
+ *src++;
+ }
+ }
+}
+
+int scaleValue(int value, int scale, int err) {
+ int scaled = 0;
+ while (value--) {
+ err -= scale;
+ while (err < 0) {
+ scaled++;
+ err += 100;
+ }
+ }
+ return scaled;
+}
+
+} // End of namespace M4
diff --git a/engines/m4/graphics.h b/engines/m4/graphics.h
new file mode 100644
index 0000000000..60e608c148
--- /dev/null
+++ b/engines/m4/graphics.h
@@ -0,0 +1,222 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_GRAPHICS_H
+#define M4_GRAPHICS_H
+
+#include "common/rect.h"
+#include "common/system.h"
+#include "common/stream.h"
+#include "graphics/surface.h"
+
+#include "m4/globals.h"
+
+namespace M4 {
+
+struct BGR8 {
+ uint8 b, g, r;
+};
+
+struct RGB8 {
+ uint8 r, g, b, u;
+};
+
+//later use ScummVM's Rect?
+struct M4Rect {
+ int32 x1, y1, x2, y2;
+};
+
+class M4Surface;
+
+// RGBList
+// Implements a list of RGB entries
+
+class RGBList {
+private:
+ int _size;
+ RGB8 *_data;
+ byte *_palIndexes;
+ bool _freeData;
+public:
+ RGBList(int numEntries = 256, RGB8 *srcData = NULL, bool freeData = true);
+ ~RGBList();
+
+ RGB8 *data() { return _data; }
+ byte *palIndexes() { return _palIndexes; }
+ int size() { return _size; }
+};
+
+// M4Surface
+// Class representing either a buffered surface or the physical screen.
+
+class M4Sprite;
+
+struct SpriteInfo {
+ M4Sprite *sprite;
+ int hotX, hotY;
+ int width, height;
+ int scaleX, scaleY;
+ uint8 encoding;
+ byte *inverseColorTable;
+ RGB8 *palette;
+};
+
+class M4Surface : public Graphics::Surface {
+private:
+ byte _color;
+ bool _isScreen;
+
+ void rexLoadBackground(Common::SeekableReadStream *source, RGBList **palData = NULL);
+ void madsLoadBackground(int roomNumber, RGBList **palData = NULL);
+ void m4LoadBackground(Common::SeekableReadStream *source);
+public:
+ M4Surface(bool isScreen = false) {
+ create(g_system->getWidth(), g_system->getHeight(), 1);
+ _isScreen = isScreen;
+ }
+ M4Surface(int Width, int Height) { create(Width, Height, 1); _isScreen = false; }
+
+ // loads a .COD file into the M4Surface
+ // TODO: maybe move this to the rail system? check where it makes sense
+ // The sprite drawing needs this, too, so should be more global.
+ void loadCodesM4(Common::SeekableReadStream *source);
+ void loadCodesMads(Common::SeekableReadStream *source);
+
+ // loads the specified background
+ void loadBackground(int sceneNumber, RGBList **palData = NULL);
+ void loadBackgroundRiddle(const char *sceneName);
+ void madsloadInterface(int index, RGBList **palData);
+
+ void setColor(byte value) { _color = value; }
+ byte getColor() { return _color; }
+ void vLine(int x, int y1, int y2);
+ void hLine(int x1, int x2, int y);
+ void vLineXor(int x, int y1, int y2);
+ void hLineXor(int x1, int x2, int y);
+ void line(int x1, int y1, int x2, int y2, byte color);
+ void frameRect(int x1, int y1, int x2, int y2);
+ void fillRect(int x1, int y1, int x2, int y2);
+
+ void drawSprite(int x, int y, SpriteInfo &info, const Common::Rect &clipRect);
+
+ // Surface methods
+ int width() { return w; }
+ int height() { return h; }
+ void setSize(int sizeX, int sizeY) { create(sizeX, sizeY, 1); }
+ byte *getData();
+ byte *getBasePtr(int x, int y);
+ void freeData();
+ void empty();
+ void frameRect(const Common::Rect &r, uint8 color);
+ void fillRect(const Common::Rect &r, uint8 color);
+ void copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY,
+ int transparentColor = -1);
+
+ void update() {
+ if (_isScreen) {
+ g_system->copyRectToScreen((const byte *)pixels, pitch, 0, 0, w, h);
+ g_system->updateScreen();
+ }
+ }
+
+ // copyTo methods
+ void copyTo(M4Surface *dest, int transparentColor = -1) {
+ dest->copyFrom(this, Common::Rect(width(), height()), 0, 0, transparentColor);
+ }
+ void copyTo(M4Surface *dest, int x, int y, int transparentColor = -1) {
+ dest->copyFrom(this, Common::Rect(width(), height()), x, y, transparentColor);
+ }
+ void copyTo(M4Surface *dest, const Common::Rect &srcBounds, int destX, int destY,
+ int transparentColor = -1) {
+ dest->copyFrom(this, srcBounds, destX, destY, transparentColor);
+ }
+
+ void translate(RGBList *list, bool isTransparent = false);
+};
+
+enum FadeType {FT_TO_GREY, FT_TO_COLOR, FT_TO_BLOCK};
+
+class Palette {
+private:
+ M4Engine *_vm;
+ bool _colorsChanged;
+ bool _fading_in_progress;
+ byte _originalPalette[256 * 4];
+ byte _fadedPalette[256 * 4];
+ int _usageCount[256];
+
+ void reset();
+public:
+ Palette(M4Engine *vm);
+
+ void setPalette(const byte *colors, uint start, uint num);
+ void setPalette(const RGB8 *colors, uint start, uint num);
+ void grabPalette(byte *colors, uint start, uint num);
+ void grabPalette(RGB8 *colors, uint start, uint num) {
+ grabPalette((byte *)colors, start, num);
+ }
+ uint8 palIndexFromRgb(byte r, byte g, byte b, RGB8 *paletteData = NULL);
+
+ void fadeToGreen(int numSteps, uint delayAmount);
+ void fadeFromGreen(int numSteps, uint delayAmount, bool fadeToBlack);
+ void fadeIn(int numSteps, uint delayAmount, RGB8 *destPalette, int numColors);
+ void fadeIn(int numSteps, uint delayAmount, RGBList *destPalette);
+ static RGB8 *decodeMadsPalette(Common::SeekableReadStream *palStream, int *numColors);
+ int setMadsPalette(Common::SeekableReadStream *palStream, int indexStart = 0);
+ void setMadsSystemPalette();
+
+ // Methods used for reference counting color usage
+ void resetColorCounts();
+ void blockRange(int startIndex, int size);
+ void addRange(RGBList *list);
+ void deleteRange(RGBList *list);
+ void deleteAllRanges();
+
+ // Color indexes
+ uint8 BLACK;
+ uint8 BLUE;
+ uint8 GREEN;
+ uint8 CYAN;
+ uint8 RED;
+ uint8 VIOLET;
+ uint8 BROWN;
+ uint8 LIGHT_GRAY;
+ uint8 DARK_GRAY;
+ uint8 LIGHT_BLUE;
+ uint8 LIGHT_GREEN;
+ uint8 LIGHT_CYAN;
+ uint8 LIGHT_RED;
+ uint8 PINK;
+ uint8 YELLOW;
+ uint8 WHITE;
+};
+
+void decompressRle(byte *rleData, int rleSize, byte *celData, int w, int h);
+void decompressRle(Common::SeekableReadStream &rleData, byte *celData, int w, int h);
+int scaleValue(int value, int scale, int err);
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/gui.cpp b/engines/m4/gui.cpp
new file mode 100644
index 0000000000..67c1c7a1a7
--- /dev/null
+++ b/engines/m4/gui.cpp
@@ -0,0 +1,1217 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/events.h"
+#include "common/keyboard.h"
+
+#include "m4/globals.h"
+#include "m4/events.h"
+#include "m4/font.h"
+#include "m4/graphics.h"
+#include "m4/viewmgr.h"
+#include "m4/gui.h"
+#include "m4/midi.h"
+#include "m4/scene.h"
+#include "m4/m4.h"
+
+namespace M4 {
+
+//--------------------------------------------------------------------------
+// DialogView class
+//
+// Defines a generic base class for dialogs, that some of the classes
+// in the object hierharchy require as a parent
+//--------------------------------------------------------------------------
+
+void DialogView::close() {
+ // Default to simply destroying the given dialog
+ _vm->_viewManager->deleteView(this);
+}
+
+//--------------------------------------------------------------------------
+// GUIObject class
+//
+// Defines a generic object that appears in a view
+//--------------------------------------------------------------------------
+
+GUIObject::GUIObject(View *owner, const Common::Rect &bounds) {
+ _parent = owner;
+ _bounds = bounds;
+}
+
+//--------------------------------------------------------------------------
+// MenuObject class
+//
+// Defines a specialised GUI object that appears in a dialog
+//--------------------------------------------------------------------------
+
+MenuObject::MenuObject(DialogView *owner, int objectId, int xs, int ys, int width, int height,
+ bool greyed, bool transparent):
+ GUIObject(owner, Common::Rect(xs, ys, xs + width, ys + height)) {
+
+ _objectId = objectId;
+ _bounds.top = ys;
+ _bounds.bottom = ys + height - 1;
+ _bounds.left = xs;
+ _bounds.right = xs + width - 1;
+ _transparent = transparent;
+ _objectState = greyed ? OS_GREYED : OS_NORMAL;
+ _callback = NULL;
+
+ if (transparent) {
+ _background = new M4Surface(width, height);
+ Common::Rect srcBounds(xs, ys, xs + width - 1, ys + height - 1);
+ _background->copyFrom(owner, srcBounds, 0, 0);
+ } else {
+ _background = NULL;
+ }
+}
+
+MenuObject::~MenuObject() {
+ if (_background)
+ delete _background;
+}
+
+void MenuObject::onExecute() {
+ // If a callback function has been specified, then execute it
+ if (_callback)
+ _callback(parent(), this);
+}
+
+//--------------------------------------------------------------------------
+// MenuButton class
+//
+// Defines a button object
+//--------------------------------------------------------------------------
+
+MenuButton::MenuButton(DialogView *owner, int buttonId, int xs, int ys, int width, int height,
+ MenuButton::Callback callbackFn, bool greyed, bool transparent,
+ ObjectType buttonType):
+ MenuObject(owner, buttonId, xs, ys, width, height, greyed, transparent) {
+
+ _objectType = buttonType;
+ _callback = callbackFn;
+}
+
+bool MenuButton::onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem) {
+ bool redrawFlag = false;
+ bool callbackFlag = false;
+ bool handledFlag = true;
+
+ if (_objectState == OS_GREYED)
+ return false;
+
+ switch (event) {
+ case MEVENT_LEFT_CLICK:
+ case MEVENT_DOUBLECLICK:
+ if (isInside(x, y)) {
+ _objectState = OS_PRESSED;
+ if (currentItem != NULL)
+ currentItem = this;
+ redrawFlag = true;
+ } else {
+ currentItem = NULL;
+ if (_objectState != OS_NORMAL) {
+ _objectState = OS_PRESSED;
+ redrawFlag = true;
+ }
+ }
+ break;
+
+ case MEVENT_LEFT_DRAG:
+ case MEVENT_DOUBLECLICK_DRAG:
+ if (!currentItem) {
+ return true;
+ }
+ if (isInside(x, y)) {
+ if (_objectState != OS_PRESSED) {
+ _objectState = OS_PRESSED;
+ redrawFlag = true;
+ }
+ }
+ else {
+ if (_objectState != OS_MOUSEOVER) {
+ _objectState = OS_MOUSEOVER;
+ redrawFlag = true;
+ }
+ }
+ break;
+
+ case MEVENT_LEFT_RELEASE:
+ case MEVENT_DOUBLECLICK_RELEASE:
+ if (isInside(x, y)) {
+ if (currentItem) {
+ callbackFlag = true;
+ if (_objectType == OBJTYPE_OM_SWITCH_ON)
+ _objectType = OBJTYPE_OM_SWITCH_OFF;
+ else if (_objectType == OBJTYPE_OM_SWITCH_OFF)
+ _objectType = OBJTYPE_OM_SWITCH_ON;
+ }
+ else {
+ currentItem = this;
+ }
+
+ _objectState = OS_MOUSEOVER;
+ redrawFlag = true;
+
+ } else {
+ currentItem = NULL;
+ _objectState = OS_MOUSEOVER;
+ redrawFlag = true;
+ handledFlag = false;
+ }
+ break;
+
+ case MEVENT_MOVE:
+ if (isInside(x, y)) {
+ currentItem = this;
+ if (_objectState != OS_MOUSEOVER) {
+ _objectState = OS_MOUSEOVER;
+ redrawFlag = true;
+ }
+ }
+ else {
+ currentItem = NULL;
+ if (_objectState != OS_NORMAL) {
+ _objectState = OS_NORMAL;
+ redrawFlag = true;
+ handledFlag = false;
+ }
+ }
+ break;
+
+ case MEVENT_LEFT_HOLD:
+ case MEVENT_DOUBLECLICK_HOLD:
+ break;
+
+ default:
+ break;
+ }
+
+ //see if we need to redraw the button
+ if (redrawFlag) {
+ onRefresh();
+
+ // TODO: There may be a more efficient mechanism than refreshing the entire screen
+ // when a menu object refreshes itself
+ if (parent()->screenFlags().visible)
+ _vm->_viewManager->refreshAll();
+ }
+
+ // If a callback is flagged, then handle it
+
+ if (callbackFlag)
+ onExecute();
+
+ return handledFlag;
+}
+
+void MenuButton::onRefresh() {
+ M4Sprite *sprite = NULL;
+ SpriteAsset &sprites = *parent()->sprites();
+
+ // Switch handling for the various button types
+ switch (_objectType) {
+ case OBJTYPE_BUTTON:
+ sprite = sprites[GM_BUTTON_GREYED + _objectState];
+ break;
+
+ case OBJTYPE_OM_SWITCH_ON:
+ switch (_objectState) {
+ case OS_MOUSEOVER:
+ sprite = sprites[MENU_SS_SWITCH_ON_MOUSEOVER];
+ break;
+ case OS_PRESSED:
+ sprite = sprites[MENU_SS_SWITCH_ON_PRESSED];
+ break;
+ default:
+ sprite = sprites[MENU_SS_SWITCH_ON_NORMAL];
+ break;
+ }
+ break;
+
+ case OBJTYPE_OM_SWITCH_OFF:
+ switch (_objectState) {
+ case OS_MOUSEOVER:
+ sprite = sprites[MENU_SS_SWITCH_OFF_MOUSEOVER];
+ break;
+ case OS_PRESSED:
+ sprite = sprites[MENU_SS_SWITCH_OFF_PRESSED];
+ break;
+ default:
+ sprite = sprites[MENU_SS_SWITCH_OFF_NORMAL];
+ break;
+ }
+ break;
+
+ case OBJTYPE_OM_DONE:
+ sprite = sprites[OM_DONE_BTN_GREYED + _objectState];
+ break;
+
+ case OBJTYPE_OM_CANCEL:
+ sprite = (_objectState == OS_GREYED) ? sprites[OM_CANCEL_BTN_NORMAL] :
+ sprites[OM_CANCEL_BTN_NORMAL + _objectState - 1];
+ break;
+
+ case OBJTYPE_SL_SAVE:
+ sprite = sprites[SL_SAVE_BTN_GREYED + _objectState];
+ break;
+
+ case OBJTYPE_SL_LOAD:
+ sprite = sprites[SL_LOAD_BTN_GREYED + _objectState];
+ break;
+
+ case OBJTYPE_SL_CANCEL:
+ sprite = (_objectState == OS_GREYED) ? sprites[SL_CANCEL_BTN_NORMAL] :
+ sprites[SL_CANCEL_BTN_NORMAL + _objectState - 1];
+ break;
+
+ case OBJTYPE_SL_TEXT:
+ switch (_objectState) {
+ case OS_MOUSEOVER:
+ _vm->_font->setColors(TEXT_COLOR_MOUSEOVER_SHADOW, TEXT_COLOR_MOUSEOVER_FOREGROUND,
+ TEXT_COLOR_MOUSEOVER_HILIGHT);
+ sprite = sprites[SL_LINE_MOUSEOVER];
+ break;
+
+ case OS_PRESSED:
+ _vm->_font->setColors(TEXT_COLOR_PRESSED_SHADOW, TEXT_COLOR_PRESSED_FOREGROUND,
+ TEXT_COLOR_PRESSED_HILIGHT);
+ sprite = sprites[SL_LINE_PRESSED];
+ break;
+
+ case OS_GREYED:
+ _vm->_font->setColors(TEXT_COLOR_GREYED_SHADOW, TEXT_COLOR_GREYED_FOREGROUND,
+ TEXT_COLOR_GREYED_HILIGHT);
+ sprite = sprites[SL_LINE_NORMAL];
+ break;
+
+ default:
+ case OS_NORMAL:
+ _vm->_font->setColors(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND,
+ TEXT_COLOR_NORMAL_HILIGHT);
+ sprite = sprites[SL_LINE_NORMAL];
+ break;
+ }
+ break;
+
+ default:
+ error("Unknown object type");
+ break;
+ }
+
+ // If no sprite object was set, then exit without doing anything
+ if (!sprite)
+ return;
+
+ // Draw the button
+ if (_transparent) {
+ // Transparent button, so restore original background
+ if (!_background)
+ return;
+ else
+ _background->copyTo(parent(), _bounds.left, _bounds.top);
+ }
+
+ sprite->copyTo(parent(), _bounds.left, _bounds.top, 0);
+}
+
+//--------------------------------------------------------------------------
+// MenuHorizSlider class
+//
+// Defines a horizontal slider that allows selection of a percentage
+//--------------------------------------------------------------------------
+
+MenuHorizSlider::MenuHorizSlider(DialogView *owner, int objectId, int xs, int ys,
+ int width, int height, int initialPercentage, Callback callbackFn, bool transparent):
+ MenuObject(owner, objectId, xs, ys, width, height, false, transparent) {
+
+ _objectType = OBJTYPE_SLIDER;
+ _callback = callbackFn;
+
+ SpriteAsset &sprites = *owner->sprites();
+ _sliderState = HSLIDER_THUMB_NORMAL;
+ _thumbSize.x = sprites[OM_SLIDER_BTN_NORMAL]->width();
+ _thumbSize.y = sprites[OM_SLIDER_BTN_NORMAL]->height();
+ _maxThumbX = width - _thumbSize.x;
+ _percent = MAX(MIN(initialPercentage, 100), 0);
+ _thumbX = initialPercentage * _maxThumbX / 100;
+}
+
+void MenuHorizSlider::onRefresh() {
+ // If the slider is transparent, first copy in the background
+ if (_transparent) {
+ // Transparent button
+ if (!_background)
+ return;
+
+ _background->copyTo(parent(), _bounds.left, _bounds.top, 0);
+ }
+
+ // Get the thumb sprite for the slider
+ SpriteAsset &sprites = *parent()->sprites();
+ M4Sprite *sprite = sprites[OM_SLIDER_BTN_NORMAL + _sliderState];
+ assert(sprite);
+
+ // Fill in the area to the left of the thumbnail
+ if (_thumbX > 2) {
+ Common::Rect leftBounds(_bounds.left + 3, _bounds.top + 9, _bounds.left + _thumbX,
+ _bounds.top + _thumbSize.y - 9);
+ parent()->fillRect(leftBounds, SLIDER_BAR_COLOR);
+ }
+
+ // Draw the thumbnail
+ sprite->copyTo(parent(), _bounds.left + _thumbX, _bounds.top, 0);
+}
+
+bool MenuHorizSlider::onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem) {
+ static bool movingFlag = false;
+ static int movingX = 0;
+ bool redrawFlag = false, handledFlag = false, callbackFlag = false;
+
+ if (event == KEVENT_KEY)
+ return false;
+
+ switch (event) {
+ case MEVENT_LEFT_CLICK:
+ case MEVENT_DOUBLECLICK:
+ if (isInside(x, y) && (x - _bounds.left >= _thumbX) &&
+ (x - _bounds.left <= _thumbX + _thumbSize.x - 1)) {
+ // The thumbnail has been clicked
+ _sliderState = HSLIDER_THUMB_PRESSED;
+ movingFlag = true;
+ movingX = x;
+ currentItem = this;
+ redrawFlag = true;
+ } else {
+ currentItem = NULL;
+ _sliderState = HSLIDER_THUMB_NORMAL;
+ redrawFlag = true;
+ }
+ redrawFlag = true;
+ break;
+
+ case MEVENT_LEFT_DRAG:
+ case MEVENT_DOUBLECLICK_DRAG:
+ if (!currentItem)
+ return true;
+
+ if (movingFlag) {
+ if (x != movingX) {
+ if (x < movingX)
+ _thumbX -= MIN(_thumbX, movingX - x);
+ else
+ _thumbX += MIN(_maxThumbX - _thumbX, x - movingX);
+ _percent = _thumbX * 100 / _maxThumbX;
+ redrawFlag = callbackFlag = true;
+ }
+ movingX = CLIP(x, _bounds.left + _thumbX,
+ _bounds.left + _thumbX + _thumbSize.x - 1);
+ } else {
+ currentItem = NULL;
+ }
+ break;
+
+ case MEVENT_LEFT_RELEASE:
+ case MEVENT_DOUBLECLICK_RELEASE:
+ if (!currentItem)
+ return true;
+
+ movingFlag = false;
+ if (isInside(x, y) && (x - _bounds.left >= _thumbX) &&
+ (x - _bounds.left <= _thumbX + _thumbSize.x - 1)) {
+ _sliderState = HSLIDER_THUMB_MOUSEOVER;
+ currentItem = this;
+ } else {
+ _sliderState = HSLIDER_THUMB_NORMAL;
+ currentItem = NULL;
+ }
+
+ redrawFlag = true;
+ callbackFlag = true;
+ break;
+
+ case MEVENT_MOVE:
+ if (isInside(x, y) && (x - _bounds.left >= _thumbX) &&
+ (x - _bounds.left <= _thumbX + _thumbSize.x - 1)) {
+ if (_sliderState != HSLIDER_THUMB_MOUSEOVER) {
+ _sliderState = HSLIDER_THUMB_MOUSEOVER;
+ currentItem = this;
+ }
+ } else {
+ if (_sliderState != HSLIDER_THUMB_NORMAL) {
+ _sliderState = HSLIDER_THUMB_NORMAL;
+ currentItem = NULL;
+ handledFlag = false;
+ }
+ }
+ redrawFlag = true;
+ break;
+
+ default:
+ break;
+ }
+
+ if (redrawFlag)
+ onRefresh();
+
+ if (callbackFlag)
+ onExecute();
+
+ return handledFlag;
+}
+
+//--------------------------------------------------------------------------
+// MenuVertSlider class
+//
+// Defines a vertical slider that's used in the save/load dialog
+//--------------------------------------------------------------------------
+
+MenuVertSlider::MenuVertSlider(DialogView *owner, int objectId, int xs, int ys,
+ int width, int height, int initialPercentage, Callback callbackFn, bool transparent):
+ MenuObject(owner, objectId, xs, ys, width, height, false, transparent) {
+
+ _objectType = OBJTYPE_SLIDER;
+ _callback = callbackFn;
+
+ SpriteAsset &sprites = *owner->sprites();
+ _sliderState = VSLIDER_NONE;
+ _thumbSize.x = sprites[SL_SLIDER_BTN_NORMAL]->width();
+ _thumbSize.y = sprites[SL_SLIDER_BTN_NORMAL]->height();
+ _minThumbY = sprites[SL_UP_BTN_NORMAL]->height() + 1;
+ _maxThumbY = sprites[SL_UP_BTN_NORMAL]->height() + sprites[SL_SCROLLBAR]->height() -
+ sprites[SL_SLIDER_BTN_NORMAL]->height() - 1;
+
+ _percent = MAX(MIN(initialPercentage, 100), 0);
+ _thumbY = _minThumbY + ((_percent * (_maxThumbY - _minThumbY)) / 100);
+}
+
+MenuVertSliderState MenuVertSlider::getSliderArea(int y) {
+ if (y < _minThumbY)
+ return VSLIDER_UP;
+ else if (y < _thumbY)
+ return VSLIDER_PAGE_UP;
+ else if (y < _thumbY + _thumbSize.y)
+ return VSLIDER_THUMBNAIL;
+ else if (y < _maxThumbY + _thumbSize.y)
+ return VSLIDER_PAGE_DOWN;
+ else
+ return VSLIDER_DOWN;
+}
+
+void MenuVertSlider::onRefresh() {
+ // If the slider is transparent, first copy in the background
+ if (_transparent) {
+ // Transparent button
+ if (!_background)
+ return;
+
+ _background->copyTo(parent(), _bounds.left, _bounds.top, 0);
+ }
+
+ // Get the various needed sprites
+ SpriteAsset &sprites = *parent()->sprites();
+ M4Sprite *barSprite = sprites[SL_SCROLLBAR];
+ M4Sprite *thumbSprite = sprites[SL_SLIDER_BTN_NORMAL];
+ M4Sprite *upSprite = sprites[SL_UP_BTN_NORMAL];
+ M4Sprite *downSprite = sprites[SL_DOWN_BTN_NORMAL];
+
+ if (_objectState == OS_GREYED) {
+ upSprite = sprites[SL_UP_BTN_GREYED];
+ downSprite = sprites[SL_DOWN_BTN_GREYED];
+ thumbSprite = NULL;
+
+ } else if (_objectState == OS_MOUSEOVER) {
+ if (_sliderState == VSLIDER_UP)
+ upSprite = sprites[SL_UP_BTN_MOUSEOVER];
+ else if (_sliderState == VSLIDER_THUMBNAIL)
+ thumbSprite = sprites[SL_SLIDER_BTN_MOUSEOVER];
+ else if (_sliderState == VSLIDER_DOWN)
+ downSprite = sprites[SL_DOWN_BTN_MOUSEOVER];
+ }
+ else if (_objectState == OS_PRESSED) {
+ if (_sliderState == VSLIDER_UP)
+ upSprite = sprites[SL_UP_BTN_PRESSED];
+ else if (_sliderState == VSLIDER_THUMBNAIL)
+ thumbSprite = sprites[SL_SLIDER_BTN_PRESSED];
+ else if (_sliderState == VSLIDER_DOWN)
+ downSprite = sprites[SL_DOWN_BTN_PRESSED];
+ }
+
+ // Draw the sprites
+ upSprite->copyTo(parent(), _bounds.left, _bounds.top, 0);
+ barSprite->copyTo(parent(), _bounds.left, _bounds.top + upSprite->height(), 0);
+ downSprite->copyTo(parent(), _bounds.left, _bounds.top + upSprite->height() + barSprite->height(), 0);
+ if (thumbSprite)
+ thumbSprite->copyTo(parent(), _bounds.left, _bounds.top + _thumbY, 0);
+}
+
+bool MenuVertSlider::onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem) {
+ static bool movingFlag = false;
+ static int movingY = 0;
+ static uint32 callbackTime;
+ MenuVertSliderState tempState;
+ int delta;
+ uint32 currentTime = g_system->getMillis();
+ bool redrawFlag = false;
+ bool handledFlag = true;
+ bool callbackFlag = false;
+
+ if (event == KEVENT_KEY)
+ return false;
+
+ if (_objectState == OS_GREYED) {
+ currentItem = NULL;
+ return false;
+ }
+
+ switch (event) {
+ case MEVENT_LEFT_CLICK:
+ case MEVENT_DOUBLECLICK:
+ if (isInside(x, y)) {
+ currentItem = this;
+ tempState = getSliderArea(y - _bounds.top);
+ if (tempState == VSLIDER_THUMBNAIL) {
+ movingFlag = true;
+ movingY = y;
+ }
+ if ((tempState == VSLIDER_PAGE_UP) || (tempState == VSLIDER_PAGE_DOWN)) {
+ _sliderState = tempState;
+ setState(OS_NORMAL);
+ } else {
+ _sliderState = tempState;
+ setState(OS_PRESSED);
+ redrawFlag = true;
+ }
+ callbackFlag = true;
+ } else {
+ currentItem = NULL;
+ setState(OS_NORMAL);
+ redrawFlag = true;
+ }
+ break;
+
+ case MEVENT_LEFT_DRAG:
+ case MEVENT_DOUBLECLICK_DRAG:
+ if (!currentItem)
+ return true;
+
+ if (movingFlag) {
+ if (y < movingY) {
+ delta = MIN(_thumbY - _minThumbY, movingY - y);
+ if (delta > 0) {
+ _thumbY -= delta;
+ _percent = ((_thumbY - _minThumbY) * 100) / (_maxThumbY - _minThumbY);
+ redrawFlag = true;
+ callbackFlag = true;
+ }
+ }
+ else if (y > movingY) {
+ delta = MIN(_maxThumbY - _thumbY, y - movingY);
+ if (delta > 0) {
+ _thumbY += delta;
+ _percent = ((_thumbY - _minThumbY) * 100) / (_maxThumbY - _minThumbY);
+ redrawFlag = true;
+ callbackFlag = true;
+ }
+ }
+ movingY = y;
+
+ if (movingY < (_thumbY + _bounds.top))
+ movingY = _thumbY + _bounds.top;
+ else if (movingY > (_bounds.top + _thumbY + _thumbSize.y - 1))
+ movingY = _bounds.top + _thumbY + _thumbSize.y - 1;
+
+ } else {
+ if (isInside(x, y)) {
+ tempState = getSliderArea(y - _bounds.top);
+ if (_sliderState == tempState) {
+ if ((tempState != VSLIDER_PAGE_UP) && (tempState != VSLIDER_PAGE_DOWN) &&
+ (_objectState != OS_PRESSED)) {
+ _sliderState = tempState;
+ setState(OS_PRESSED);
+ redrawFlag = true;
+ }
+ if (currentTime - callbackTime > 100)
+ callbackFlag = true;
+
+ } else {
+ if (_objectState != OS_MOUSEOVER)
+ setState(OS_MOUSEOVER);
+ redrawFlag = true;
+ }
+ callbackFlag = true;
+
+ } else {
+ if (_objectState != OS_MOUSEOVER) {
+ setState(OS_MOUSEOVER);
+ redrawFlag = true;
+ }
+ }
+ }
+ break;
+
+ case MEVENT_LEFT_RELEASE:
+ case MEVENT_DOUBLECLICK_RELEASE:
+ movingFlag = false;
+ if (isInside(x, y)) {
+ tempState = getSliderArea(y - _bounds.top);
+ if ((tempState == VSLIDER_PAGE_UP) || (tempState == VSLIDER_PAGE_DOWN))
+ setState(OS_NORMAL);
+ else {
+ setState(OS_MOUSEOVER);
+ currentItem = this;
+ }
+ } else {
+ setState(OS_NORMAL);
+ currentItem = NULL;
+ }
+
+ redrawFlag = true;
+ if (parent()->getMenuType() == LOAD_MENU)
+ updateThumbnails();
+ break;
+
+ case MEVENT_MOVE:
+ if (isInside(x, y)) {
+ currentItem = this;
+ tempState = getSliderArea(y - _bounds.top);
+ if (_sliderState != tempState) {
+ if ((tempState == VSLIDER_PAGE_UP) || (tempState == VSLIDER_PAGE_DOWN))
+ _objectState = OS_NORMAL;
+ else {
+ _sliderState = tempState;
+ _objectState = OS_MOUSEOVER;
+ }
+ redrawFlag = true;
+ }
+ } else {
+ currentItem = NULL;
+
+ if (_objectState != OS_NORMAL) {
+ _objectState = OS_NORMAL;
+ redrawFlag = true;
+ handledFlag = false;
+ }
+ }
+ break;
+
+ case MEVENT_LEFT_HOLD:
+ case MEVENT_DOUBLECLICK_HOLD:
+ if (!currentItem)
+ return true;
+
+ if (isInside(x, y)) {
+ tempState = getSliderArea(y - _bounds.top);
+
+ if (_sliderState == tempState) {
+ if (currentTime - callbackTime > 100)
+ callbackFlag = true;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (redrawFlag)
+ onRefresh();
+
+ if (callbackFlag) {
+ callbackTime = currentTime;
+ onExecute();
+ }
+
+ return handledFlag;
+}
+
+void MenuVertSlider::setPercentage(int value) {
+ _percent = value;
+ _thumbY = _minThumbY + ((_percent * (_maxThumbY - _minThumbY)) / 100);
+ onRefresh();
+}
+
+//--------------------------------------------------------------------------
+// MenuMessage class
+//
+// Defines a message menu object
+//--------------------------------------------------------------------------
+
+MenuMessage::MenuMessage(DialogView *owner, int objectId, int xs, int ys, int width, int height,
+ bool transparent):
+ MenuObject(owner, objectId, xs, ys, width, height, false, transparent) {
+}
+
+void MenuMessage::onRefresh() {
+ SpriteAsset &sprites = *parent()->sprites();
+ M4Surface *sprite = NULL;
+
+ // Get the correct sprite to use
+ switch (_objectId) {
+ case SLTAG_SAVELOAD_LABEL:
+ sprite = (parent()->getMenuType() == SAVE_MENU) ? sprites[SL_SAVE_LABEL] :
+ sprites[SL_LOAD_LABEL];
+ break;
+ }
+ assert(sprite);
+
+ // Draw the sprite
+ if (_transparent) {
+ // Transparent button
+ if (!_background)
+ return;
+
+ // Restore original background and then do a transparent copy of the sprite
+ _background->copyTo(parent(), _bounds.left, _bounds.top);
+ }
+
+ sprite->copyTo(parent(), _bounds.left, _bounds.top, 0);
+}
+
+//--------------------------------------------------------------------------
+// MenuImage class
+//
+// Defines a menu item that displays a given surface
+//--------------------------------------------------------------------------
+
+MenuImage::MenuImage(DialogView *owner, int objectId, int xs, int ys, int width, int height,
+ M4Surface *image, bool transparent):
+ MenuObject(owner, objectId, xs, ys, width, height, false, transparent) {
+
+ _sprite = image;
+}
+
+void MenuImage::onRefresh() {
+ if (!_sprite)
+ return;
+
+ // Draw the sprite
+ if (_transparent) {
+ // Transparent button
+ if (!_background)
+ return;
+
+ // Restore original background and then do a transparent copy of the sprite
+ _background->copyTo(parent(), _bounds.left, _bounds.top);
+ }
+
+ _sprite->copyTo(parent(), _bounds.left + (_bounds.width() - _sprite->width()) / 2,
+ _bounds.top + (_bounds.height() - _sprite->height()) / 2, 0);
+}
+
+//--------------------------------------------------------------------------
+// MenuSaveLoadText class
+//
+// Defines a save/load dialog text entry
+//--------------------------------------------------------------------------
+
+MenuSaveLoadText::MenuSaveLoadText(DialogView *owner, int textId, int xs, int ys,
+ int width, int height, Callback callbackFn, bool greyed, bool transparent,
+ bool loadFlag, const char *displayText, int displayValue):
+ MenuButton(owner, textId, xs, ys, width, height, callbackFn, greyed, transparent, OBJTYPE_SL_TEXT) {
+
+ _loadFlag = loadFlag;
+ _displayText = displayText;
+ _displayValue = displayValue;
+ _index = textId - SLTAG_SLOTS_START;
+ _visible = true;
+}
+
+void MenuSaveLoadText::onRefresh() {
+ if (!_visible) return;
+ _vm->_font->setFont(FONT_MENU);
+ MenuButton::onRefresh();
+
+ if ((_objectType == OBJTYPE_SL_TEXT) && (_displayText != NULL)) {
+ int xp = _bounds.left + 4;
+ if (_displayValue != 0) {
+ char tempBuffer[5];
+ sprintf(tempBuffer, "%02d", _displayValue);
+ _vm->_font->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1);
+ xp = _bounds.left + 26;
+ }
+
+ _vm->_font->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1);
+ }
+}
+
+bool MenuSaveLoadText::onEvent(M4::M4EventType event, int param, int x, int y, M4::MenuObject *&currentItem) {
+ if (!_visible) return false;
+ bool handledFlag = MenuButton::onEvent(event, param, x, y, currentItem);
+
+ // Further handling will only be done when in load mode and a slot hasn't been selected
+ if (!_loadFlag || (parent()->_selectedSlot != -1))
+ return handledFlag;
+
+ // Only handling for certain event types
+ if ((event != MEVENT_MOVE) && (event != MEVENT_LEFT_DRAG) &&
+ (event != MEVENT_LEFT_RELEASE) && (event != MEVENT_DOUBLECLICK_DRAG) &&
+ (event != MEVENT_DOUBLECLICK_RELEASE))
+ return handledFlag;
+
+ MenuImage *thumbnail = (MenuImage *) parent()->getItem(SLTAG_THUMBNAIL);
+
+ // Check whether the cursor is over the button
+ if ((_objectState == OS_MOUSEOVER) || (_objectState == OS_PRESSED)) {
+ if (_index != parent()->_highlightedSlot) {
+ // Selected slot has changed
+ if (parent()->_savegameThumbnail != NULL)
+ delete parent()->_savegameThumbnail;
+
+ parent()->_highlightedSlot = _index;
+ parent()->_savegameThumbnail = _vm->_saveLoad->getThumbnail(_index + 1);
+ thumbnail->setSprite(parent()->_savegameThumbnail);
+ }
+
+ } else {
+ // If the mouse has moved outside the area of all the save slots, then the
+ // thumbnail needs to be removed
+
+ Common::Rect slotArea(50, 256, 288, 377);
+ if (isInside(x, y) || !slotArea.contains(x, y)) {
+ if (parent()->_savegameThumbnail) {
+ delete parent()->_savegameThumbnail;
+ parent()->_savegameThumbnail = NULL;
+ }
+
+ thumbnail->setSprite(parent()->sprites()->getFrame(SL_EMPTY_THUMBNAIL));
+ parent()->_highlightedSlot = -1;
+ }
+ }
+
+ return handledFlag;
+}
+
+void MenuSaveLoadText::setVisible(bool value) {
+ _visible = value;
+ parent()->refresh(_bounds);
+}
+
+//--------------------------------------------------------------------------
+// MenuTextField class
+//
+// Defines a text entry field
+//--------------------------------------------------------------------------
+
+MenuTextField::MenuTextField(DialogView *owner, int fieldId, int xs, int ys, int width,
+ int height, bool greyed, Callback callbackFn,
+ const char *displayText, int displayValue, bool transparent):
+ MenuObject(owner, fieldId, xs, ys, width, height, greyed, transparent) {
+
+ _displayValue = displayValue;
+ _callback = callbackFn;
+ _pixelWidth = width - 27;
+ if (displayText) {
+ strcpy(_displayText, displayText);
+ _promptEnd = &_displayText[strlen(_displayText)];
+ } else {
+ _displayText[0] = '\0';
+ _promptEnd = &_displayText[0];
+ }
+ _cursor = _promptEnd;
+}
+
+void MenuTextField::onRefresh() {
+ bool focused = _objectState != OS_GREYED;
+
+ // Get the thumb sprite for the slider
+ SpriteAsset &sprites = *parent()->sprites();
+ M4Sprite *sprite = sprites[SL_LINE_NORMAL + OS_MOUSEOVER - 1];
+
+ // If the slider is transparent, first copy in the background
+ if (_transparent) {
+ // Transparent button
+ if (!_background)
+ return;
+
+ _background->copyTo(parent(), _bounds.left, _bounds.top, 0);
+ }
+
+ // Draw the sprite
+ sprite->copyTo(parent(), _bounds.left, _bounds.top, 0);
+
+ // Draw the text
+
+ _vm->_font->setFont(FONT_MENU);
+ _vm->_font->setColors(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND,
+ TEXT_COLOR_NORMAL_HILIGHT);
+ int xp = _bounds.left + 4;
+
+ if (_displayValue != 0) {
+ char tempBuffer[5];
+ sprintf(tempBuffer, "%02d", _displayValue);
+ _vm->_font->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1);
+ xp = _bounds.left + 26;
+ }
+
+ _vm->_font->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1);
+
+ if (focused) {
+ // Draw in the cursor
+
+ if (_cursor) {
+ // Get the width of the string up to the cursor position
+ char tempCh = *_cursor;
+ *_cursor = '\0';
+ int stringWidth = _vm->_font->getWidth(_displayText);
+ *_cursor = tempCh;
+
+ parent()->setColor(TEXT_COLOR_MOUSEOVER_FOREGROUND);
+ parent()->vLine(xp + stringWidth + 1, _bounds.top + 1, _bounds.top + 12);
+ }
+ }
+}
+
+bool MenuTextField::onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem) {
+ char tempStr[MAX_SAVEGAME_NAME];
+ int tempLen;
+ char *tempP;
+ bool handledFlag = false, redrawFlag = false, callbackFlag = false;
+
+ if (_objectState == OS_GREYED)
+ return false;
+
+ switch (event) {
+ case MEVENT_LEFT_CLICK:
+ case MEVENT_DOUBLECLICK:
+ parent()->_deleteSaveDesc = false;
+ if (isInside(x, y))
+ currentItem = this;
+ break;
+
+ case MEVENT_LEFT_RELEASE:
+ case MEVENT_DOUBLECLICK_RELEASE:
+ if (!currentItem)
+ return true;
+ currentItem = NULL;
+
+ if (isInside(x, y)) {
+ if (_objectState == OS_MOUSEOVER) {
+ tempLen = strlen(_displayText);
+ if (tempLen > 0) {
+ strcpy(tempStr, _displayText);
+ tempP = &tempStr[tempLen];
+ _vm->_font->setFont(FONT_MENU);
+
+ tempLen = _vm->_font->getWidth(tempStr);
+ while ((tempP != &tempStr[0]) && (tempLen > x - _bounds.left - 26)) {
+ *--tempP = '\0';
+ tempLen = _vm->_font->getWidth(tempStr);
+ }
+
+ _cursor = &_displayText[tempP - &tempStr[0]];
+ redrawFlag = true;
+ }
+ } else if (event == MEVENT_DOUBLECLICK_RELEASE) {
+ callbackFlag = true;
+ }
+ }
+ break;
+
+ case KEVENT_KEY:
+ switch (param) {
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ parent()->_deleteSaveDesc = false;
+ callbackFlag = true;
+ break;
+ break;
+
+ case Common::KEYCODE_HOME:
+ parent()->_deleteSaveDesc = false;
+ _cursor = &_displayText[0];
+ redrawFlag = true;
+ break;
+
+ case Common::KEYCODE_END:
+ parent()->_deleteSaveDesc = false;
+ _cursor = _promptEnd;
+ redrawFlag = true;
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ parent()->_deleteSaveDesc = false;
+ if (_cursor > &_displayText[0]) {
+ --_cursor;
+ redrawFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ parent()->_deleteSaveDesc = false;
+ if (_cursor < _promptEnd) {
+ ++_cursor;
+ redrawFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_DELETE:
+ if (parent()->_deleteSaveDesc) {
+ _displayText[0] = '\0';
+ _promptEnd = &_displayText[0];
+ _cursor = _promptEnd;
+ redrawFlag = true;
+ } else if (_cursor < _promptEnd) {
+ strcpy(tempStr, _cursor + 1);
+ strcpy(_cursor, tempStr);
+ --_promptEnd;
+ redrawFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_BACKSPACE:
+ parent()->_deleteSaveDesc = false;
+ if (_cursor > &_displayText[0]) {
+ strcpy(tempStr, _cursor);
+ --_promptEnd;
+ --_cursor;
+ strcpy(_cursor, tempStr);
+ redrawFlag = true;
+ }
+ break;
+
+ default:
+ parent()->_deleteSaveDesc = false;
+ _vm->_font->setFont(FONT_MENU);
+
+ tempLen = _vm->_font->getWidth(_displayText);
+ if ((strlen(_displayText) < MAX_SAVEGAME_NAME - 1) &&
+ (tempLen < _pixelWidth - 12) && (param >= 32) && (param <= 127)) {
+
+ // Valid displayable character
+ if (_cursor < _promptEnd) {
+ strcpy(tempStr, _cursor);
+ sprintf(_cursor, "%c%s", (char)param, tempStr);
+ } else {
+ *_cursor = (char)param;
+ *(_cursor + 1) = '\0';
+ }
+ ++_cursor;
+ ++_promptEnd;
+
+ redrawFlag = true;
+ }
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (redrawFlag)
+ onRefresh();
+
+ if (callbackFlag)
+ onExecute();
+
+ return handledFlag;
+}
+
+//--------------------------------------------------------------------------
+
+
+GUITextField::GUITextField(View *owner, const Common::Rect &bounds): GUIRect(owner, bounds, 0) {
+}
+
+void GUITextField::onRefresh() {
+ _parent->fillRect(_bounds, _vm->_palette->BLACK);
+ _vm->_font->setColors(3, 3, 3);
+ _vm->_font->setFont(FONT_INTERFACE);
+ _vm->_font->writeString(_parent, _text.c_str(), _bounds.left, _bounds.top, 0, 1);
+}
+
+//--------------------------------------------------------------------------
+
+GUIButton::GUIButton(View *owner, const Common::Rect &bounds, int tag,
+ M4Surface *normalSprite, M4Surface *mouseOverSprite, M4Surface *pressedSprite):
+ GUIRect(owner, bounds, tag) {
+
+ _normalSprite = normalSprite;
+ _mouseOverSprite = mouseOverSprite;
+ _pressedSprite = pressedSprite;
+ _visible = true;
+ _tracking = false;
+}
+
+void GUIButton::onRefresh() {
+ _parent->fillRect(_bounds, _vm->_palette->BLACK);
+
+ if (_visible) {
+ switch (_buttonState) {
+ case BUTTON_MOUSEOVER:
+ _mouseOverSprite->copyTo(_parent, _bounds.left, _bounds.top, 0);
+ break;
+ case BUTTON_PRESSED:
+ _pressedSprite->copyTo(_parent, _bounds.left, _bounds.top, 0);
+ break;
+ default:
+ _normalSprite->copyTo(_parent, _bounds.left, _bounds.top, 0);
+ break;
+ }
+ }
+}
+
+bool GUIButton::onEvent(M4EventType eventType, int param, int x, int y, GUIObject *&currentItem) {
+ bool result = false;
+ bool isPressed = (eventType == MEVENT_LEFT_CLICK) || (eventType == MEVENT_LEFT_HOLD) ||
+ (eventType == MEVENT_LEFT_DRAG);
+ bool isOver = isInside(x, y);
+ GUIButtonState oldState = _buttonState;
+
+ if (isOver) {
+ if (isPressed) {
+ // Button is pressed
+ if (!_tracking) {
+ _tracking = true;
+ _buttonState = BUTTON_PRESSED;
+ }
+
+ _vm->_globals->invSuppressClickSound = false;
+ } else {
+ // Button isn't pressed
+ if (_tracking)
+ result = true;
+ else {
+ result = false;
+ _buttonState = BUTTON_MOUSEOVER;
+ _tracking = false;
+ }
+ }
+ } else {
+ _tracking = false;
+ _buttonState = BUTTON_NORMAL;
+ }
+
+ if (oldState != _buttonState)
+ onRefresh();
+
+ return result;
+}
+
+} // End of namespace M4
diff --git a/engines/m4/gui.h b/engines/m4/gui.h
new file mode 100644
index 0000000000..3f7e6f20c3
--- /dev/null
+++ b/engines/m4/gui.h
@@ -0,0 +1,446 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_GUI_H
+#define M4_GUI_H
+
+#include "common/list.h"
+#include "common/rect.h"
+
+#include "m4/viewmgr.h"
+#include "m4/events.h"
+#include "m4/globals.h"
+#include "m4/graphics.h"
+#include "m4/saveload.h"
+#include "m4/sprite.h"
+#include "m4/assets.h"
+
+namespace M4 {
+
+class GUIObject;
+class MenuObject;
+class GUIObjectButton;
+class SpriteAsset;
+class View;
+
+enum MenuType {GAME_MENU, OPTIONS_MENU, SAVE_MENU, LOAD_MENU, MAIN_MENU};
+
+enum {
+ BTNID_QUIT = 1,
+ BTNID_OPTIONS = 2,
+ BTNID_RESUME = 3,
+ BTNID_SAVE = 4,
+ BTNID_LOAD = 5,
+ BTNID_MAIN = 6,
+
+ OPTIONID_DONE = 1,
+ OPTIONID_CANCEL = 2,
+ OPTIONID_HSLIDER_DIGI = 3,
+ OPTIONID_HSLIDER_MIDI = 4,
+
+ SAVELOADID_SAVE = 1,
+ SAVELOADID_CANCELSAVE = 2,
+ SAVELOADID_LOAD = 3,
+ SAVELOADID_CANCELLOAD = 4,
+
+ SLTAG_SAVELOAD = 100,
+ SLTAG_SAVELOAD_LABEL = 101,
+ SLTAG_CANCEL = 102,
+ SLTAG_VSLIDER = 103,
+ SLTAG_THUMBNAIL = 104,
+
+ SLTAG_SLOTS_START = 1001,
+ SLTAG_TEXTFIELD = 2000
+};
+
+enum ObjectType {
+ OBJTYPE_BUTTON,
+
+ OBJTYPE_SLIDER,
+ OBJTYPE_OM_SWITCH_ON,
+ OBJTYPE_OM_SWITCH_OFF,
+ OBJTYPE_OM_DONE,
+ OBJTYPE_OM_CANCEL,
+
+ OBJTYPE_SL_SAVE,
+ OBJTYPE_SL_LOAD,
+ OBJTYPE_SL_CANCEL,
+ OBJTYPE_SL_TEXT
+};
+
+enum GameMenuSpriteType {
+ GM_DIALOG_BOX,
+
+ GM_BUTTON_GREYED,
+ GM_BUTTON_NORMAL,
+ GM_BUTTON_MOUSEOVER,
+ GM_BUTTON_PRESSED
+};
+
+enum OptionMenuSpriteType {
+ OM_DIALOG_BOX,
+
+ OM_SLIDER_BTN_NORMAL,
+ OM_SLIDER_BTN_MOUSEOVER,
+ OM_SLIDER_BTN_PRESSED,
+ OM_SLIDER_BAR,
+ OM_DONE_BTN_GREYED,
+ OM_DONE_BTN_NORMAL,
+ OM_DONE_BTN_MOUSEOVER,
+ OM_DONE_BTN_PRESSED,
+ OM_CANCEL_BTN_NORMAL,
+ OM_CANCEL_BTN_MOUSEOVER,
+ OM_CANCEL_BTN_PRESSED
+};
+
+
+enum SaveLoadSpriteType {
+ SL_DIALOG_BOX,
+ SL_EMPTY_THUMBNAIL,
+
+ SL_SAVE_BTN_GREYED,
+ SL_SAVE_BTN_NORMAL,
+ SL_SAVE_BTN_MOUSEOVER,
+ SL_SAVE_BTN_PRESSED,
+
+ SL_LOAD_BTN_GREYED,
+ SL_LOAD_BTN_NORMAL,
+ SL_LOAD_BTN_MOUSEOVER,
+ SL_LOAD_BTN_PRESSED,
+
+ SL_CANCEL_BTN_NORMAL,
+ SL_CANCEL_BTN_MOUSEOVER,
+ SL_CANCEL_BTN_PRESSED,
+
+ SL_UP_BTN_GREYED,
+ SL_UP_BTN_NORMAL,
+ SL_UP_BTN_MOUSEOVER,
+ SL_UP_BTN_PRESSED,
+
+ SL_DOWN_BTN_GREYED,
+ SL_DOWN_BTN_NORMAL,
+ SL_DOWN_BTN_MOUSEOVER,
+ SL_DOWN_BTN_PRESSED,
+
+ SL_SAVE_LABEL,
+ SL_LOAD_LABEL,
+
+ SL_SLIDER_BTN_NORMAL,
+ SL_SLIDER_BTN_MOUSEOVER,
+ SL_SLIDER_BTN_PRESSED,
+
+ SL_LINE_NORMAL,
+ SL_LINE_MOUSEOVER,
+ SL_LINE_PRESSED,
+
+ SL_SCROLLBAR
+};
+
+enum TextColors {
+ TEXT_COLOR_NORMAL = 1,
+ TEXT_COLOR_GREYED = 1,
+ TEXT_COLOR_MOUSEOVER = 2,
+ TEXT_COLOR_PRESSED = 2,
+
+ TEXT_COLOR_GREYED_HILIGHT = 236,
+ TEXT_COLOR_GREYED_FOREGROUND = 131,
+ TEXT_COLOR_GREYED_SHADOW = 186,
+
+ TEXT_COLOR_NORMAL_HILIGHT = 129,
+ TEXT_COLOR_NORMAL_FOREGROUND = 130,
+ TEXT_COLOR_NORMAL_SHADOW = 236,
+
+ TEXT_COLOR_MOUSEOVER_HILIGHT = 129,
+ TEXT_COLOR_MOUSEOVER_FOREGROUND = 130,
+ TEXT_COLOR_MOUSEOVER_SHADOW = 236,
+
+ TEXT_COLOR_PRESSED_HILIGHT = 236,
+ TEXT_COLOR_PRESSED_FOREGROUND = 130,
+ TEXT_COLOR_PRESSED_SHADOW = 129
+};
+
+#define MENU_SS_FIELD_NORMAL 5
+#define MENU_SS_FIELD_MOUSEOVER 6
+
+#define MENU_SS_H_SLIDER_NORMAL 5
+#define MENU_SS_H_SLIDER_MOUSE_OVER 6
+#define MENU_SS_H_SLIDER_PRESSED 7
+
+#define MENU_SS_SWITCH_ON_NORMAL 8
+#define MENU_SS_SWITCH_ON_MOUSEOVER 9
+#define MENU_SS_SWITCH_ON_PRESSED 13
+
+#define MENU_SS_SWITCH_OFF_PRESSED 10
+#define MENU_SS_SWITCH_OFF_NORMAL 11
+#define MENU_SS_SWITCH_OFF_MOUSEOVER 12
+
+#define MENU_GUI "gui menu"
+#define MENU_GAME "gamemenu"
+#define MENU_OPTIONS "opmenu"
+#define MENU_ERROR "errmenu"
+#define MENU_SAVELOAD "slmenu"
+#define MENU_BURGMAIN "903menu"
+#define MENU_BURGDEMOMAIN "901menu"
+
+#define SL_NUM_VISIBLE_SLOTS 8
+#define SL_THUMBNAIL_WIDTH 215
+#define SL_THUMBNAIL_HEIGHT 162
+
+enum MenuObjectState {OS_GREYED = 0, OS_NORMAL = 1, OS_MOUSEOVER = 2, OS_PRESSED = 3};
+
+class DialogView: public View {
+public:
+ DialogView(M4Engine *Vm, const Common::Rect &viewBounds, bool transparent = false):
+ View(Vm, viewBounds, transparent) {};
+ DialogView(M4Engine *Vm, int x = 0, int y = 0, bool transparent = false):
+ View(Vm, x, y, transparent) {};
+
+ M4Engine *vm() { return _vm; }
+ virtual SpriteAsset *sprites() = 0;
+ virtual MenuType getMenuType() = 0;
+ virtual MenuObject *getItem(int objectId) { return NULL; }
+ virtual void refresh(const Common::Rect &areaRect) {}
+ virtual void close();
+
+ int _topSaveSlotIndex, _selectedSlot;
+ int _highlightedSlot;
+ bool _deleteSaveDesc;
+ M4Surface *_savegameThumbnail;
+};
+
+class GUIObject {
+protected:
+ View *_parent;
+ Common::Rect _bounds;
+ M4Surface *_background;
+public:
+ GUIObject(View *owner, const Common::Rect &bounds);
+ virtual ~GUIObject() {}
+
+ bool isInside(int x, int y) { return _bounds.contains(x, y); }
+ Common::Rect getBounds() const { return _bounds; }
+
+ virtual void onRefresh() {}
+};
+
+class MenuObject: public GUIObject {
+public:
+ typedef void (*Callback)(DialogView *view, MenuObject *item);
+protected:
+ MenuObject::Callback _callback;
+ ObjectType _objectType;
+ MenuObjectState _objectState;
+ bool _transparent;
+ int _objectId;
+public:
+ MenuObject(DialogView *owner, int objectId, int xs, int ys, int width, int height,
+ bool greyed = false, bool transparent = false);
+ ~MenuObject();
+
+ DialogView *parent() { return (DialogView *)_parent; }
+ MenuObjectState getState() { return _objectState; }
+ virtual void setState(MenuObjectState state) {
+ _objectState = state;
+ onRefresh();
+ }
+ int getObjectId() { return _objectId; }
+
+ void onExecute();
+ virtual bool onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem) { return false; }
+};
+
+class MenuButton: public MenuObject {
+public:
+ MenuButton(DialogView *owner, int buttonId, int xs, int ys, int width, int height,
+ Callback callbackFn = NULL, bool greyed = false, bool transparent = false,
+ ObjectType buttonType = OBJTYPE_BUTTON);
+
+ void onRefresh();
+ bool onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem);
+ bool getToggled() { return _objectType == OBJTYPE_OM_SWITCH_ON; }
+};
+
+enum MenuHorizSliderState {HSLIDER_THUMB_NORMAL = 0, HSLIDER_THUMB_MOUSEOVER = 1, HSLIDER_THUMB_PRESSED = 2};
+#define SLIDER_BAR_COLOR 129
+
+class MenuHorizSlider: public MenuObject {
+protected:
+ MenuHorizSliderState _sliderState;
+ Common::Point _thumbSize;
+ int _maxThumbX;
+ int _percent;
+ int _thumbX;
+public:
+ MenuHorizSlider(DialogView *owner, int sliderId, int xs, int ys, int width, int height,
+ int initialPercentage, Callback callbackFn = NULL, bool transparent = false);
+
+ void onRefresh();
+ bool onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem);
+ int percent() { return _percent; }
+};
+
+enum MenuVertSliderState {
+ VSLIDER_NONE = 0x0000,
+ VSLIDER_UP = 0x0010,
+ VSLIDER_PAGE_UP = 0x0020,
+ VSLIDER_THUMBNAIL = 0x0030,
+ VSLIDER_PAGE_DOWN = 0x0040,
+ VSLIDER_DOWN = 0x0050
+};
+
+class MenuVertSlider: public MenuObject {
+protected:
+ MenuVertSliderState _sliderState;
+ Common::Point _thumbSize;
+ int _percent;
+ int _thumbY;
+ int _minThumbY;
+ int _maxThumbY;
+
+ MenuVertSliderState getSliderArea(int y);
+ void updateThumbnails() {}
+public:
+ MenuVertSlider(DialogView *owner, int sliderId, int xs, int ys, int width, int height,
+ int initialPercentage, Callback callbackFn = NULL, bool transparent = false);
+
+ void onRefresh();
+ bool onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem);
+ MenuVertSliderState sliderState() { return _sliderState; }
+ int percent() { return _percent; }
+ void setPercentage(int value);
+};
+
+class MenuMessage: public MenuObject {
+public:
+ MenuMessage(DialogView *owner, int objectId, int x, int y, int w, int h, bool transparent = false);
+
+ void onRefresh();
+};
+
+class MenuImage: public MenuObject {
+private:
+ M4Surface *_sprite;
+public:
+ MenuImage(DialogView *owner, int objectId, int xs, int ys, int width, int height,
+ M4Surface *image, bool transparent = false);
+
+ void onRefresh();
+ const M4Surface *sprite() { return _sprite; }
+ void setSprite(M4Surface *v) {
+ _sprite = v;
+ onRefresh();
+ }
+};
+
+class MenuSaveLoadText: public MenuButton {
+private:
+ bool _loadFlag;
+ const char *_displayText;
+ int _displayValue;
+ int _index;
+ bool _visible;
+public:
+ MenuSaveLoadText(DialogView *owner, int textId, int xs, int ys, int width, int height,
+ Callback callbackFn = NULL, bool greyed = false, bool transparent = false,
+ bool loadFlag = false, const char *displayText = NULL, int displayValue = 0);
+
+ void onRefresh();
+ bool onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem);
+ void setDisplay(int value, const char *text) { _displayValue = value; _displayText = text; }
+ int getIndex() { return _index; }
+ const char *getText() { return _displayText; }
+ bool getVisible() const { return _visible; }
+ void setVisible(bool value);
+};
+
+class MenuTextField: public MenuObject {
+private:
+ int _displayValue;
+ char _displayText[MAX_SAVEGAME_NAME];
+ int _pixelWidth;
+ char *_promptEnd;
+ char *_cursor;
+public:
+ MenuTextField(DialogView *owner, int fieldId, int xs, int ys, int width, int height,
+ bool greyed, Callback callbackFn = NULL, const char *displayText = NULL,
+ int displayValue = 0, bool transparent = true);
+
+ void onRefresh();
+ bool onEvent(M4EventType event, int param, int x, int y, MenuObject *&currentItem);
+
+ const char *getText() { return _displayText; }
+ int getDisplayValue() { return _displayValue; }
+
+};
+
+class GUIRect: public GUIObject {
+private:
+ int _tag;
+public:
+ GUIRect(View *owner, const Common::Rect &bounds, int tag): GUIObject(owner, bounds) { _tag = tag; };
+
+ virtual bool onEvent(M4EventType eventType, int param, int x, int y, GUIObject *&currentItem) { return false; }
+ int getTag() const { return _tag; }
+};
+
+enum GUIButtonState {BUTTON_NORMAL, BUTTON_MOUSEOVER, BUTTON_PRESSED};
+
+class GUIButton: public GUIRect {
+protected:
+ M4Surface *_normalSprite, *_mouseOverSprite, *_pressedSprite;
+ GUIButtonState _buttonState;
+ bool _visible;
+ bool _tracking;
+public:
+ GUIButton(View *owner, const Common::Rect &bounds, int tag,
+ M4Surface *normalSprite, M4Surface *mouseOverSprite, M4Surface *pressedSprite);
+
+ void onRefresh();
+ bool onEvent(M4EventType eventType, int param, int x, int y, GUIObject *&currentItem);
+ GUIButtonState getState() const { return _buttonState; }
+};
+
+class GUITextField: public GUIRect {
+private:
+ Common::String _text;
+public:
+ GUITextField(View *owner, const Common::Rect &bounds);
+ void setText(const char *value) {
+ _text = value;
+ onRefresh();
+ }
+
+ void onRefresh();
+};
+
+
+class Dialogs {
+public:
+ void keyMouseCollision() {}
+};
+
+}
+
+#endif
diff --git a/engines/m4/hotspot.cpp b/engines/m4/hotspot.cpp
new file mode 100644
index 0000000000..bf791e09fc
--- /dev/null
+++ b/engines/m4/hotspot.cpp
@@ -0,0 +1,293 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/m4.h"
+#include "m4/events.h"
+#include "m4/hotspot.h"
+#include "gui/debugger.h"
+
+namespace M4 {
+
+/*
+ HotSpot
+*/
+
+HotSpot::HotSpot(int x1, int y1, int x2, int y2) :
+ _vocab(NULL), _verb(NULL), _prep(NULL), _sprite(NULL) {
+
+ _rect.left = x1;
+ _rect.top = y1;
+ _rect.right = x2 + 1;
+ _rect.bottom = y2 + 1;
+ _active = true;
+
+ _syntax = 0;
+ _cursor = 0;
+ _facing = 5;
+ _feetX = 32767;
+ _feetY = 32767;
+}
+
+HotSpot::~HotSpot() {
+}
+
+void HotSpot::setRect(int x1, int y1, int x2, int y2) {
+ _rect.left = x1;
+ _rect.top = y1;
+ _rect.right = x2 + 1;
+ _rect.bottom = y2 + 1;
+}
+
+void HotSpot::setFeet(int x, int y) {
+ _feetX = x;
+ _feetY = y;
+}
+
+void HotSpot::setVocab(const char *value) {
+ free(_vocab);
+ _vocab = strdup(value);
+}
+
+void HotSpot::setVerb(const char *value) {
+ free(_verb);
+ _verb = strdup(value);
+}
+
+void HotSpot::setPrep(const char *value) {
+ free(_prep);
+ _prep = strdup(value);
+}
+
+void HotSpot::setSprite(const char *value) {
+ free(_sprite);
+ _sprite = strdup(value);
+}
+
+Common::Rect HotSpot::getRect() const {
+ Common::Rect tempRect;
+ tempRect.left = _rect.left;
+ tempRect.top = _rect.top;
+ tempRect.right = _rect.right - 1;
+ tempRect.bottom = _rect.bottom - 1;
+
+ return tempRect;
+}
+
+/*
+ HotSpotList
+*/
+
+HotSpotList::HotSpotList() {
+}
+
+HotSpotList::~HotSpotList() {
+ clear();
+}
+
+int HotSpotList::add(HotSpot *hotspot, bool head) {
+ if (head || _hotspots.size() == 0) {
+ _hotspots.insert_at(0, hotspot);
+ return 0;
+ } else {
+ int32 area = hotspot->area();
+ int index = _hotspots.size();
+ for (uint i = 0; i < _hotspots.size(); i++) {
+ if (area < _hotspots[i]->area()) {
+ index = i;
+ break;
+ }
+ }
+ _hotspots.insert_at(index, hotspot);
+ return index;
+ }
+}
+
+void HotSpotList::remove(HotSpot *hotspot) {
+ unlink(hotspot);
+ delete hotspot; //TODO: check this?
+}
+
+void HotSpotList::unlink(HotSpot *hotspot) {
+ uint index = 0;
+ while (index < _hotspots.size()) {
+ if (_hotspots[index] == hotspot) {
+ _hotspots.remove_at(index);
+ } else {
+ index++;
+ }
+ }
+}
+
+void HotSpotList::clear() {
+ for (uint i = 0; i < _hotspots.size(); i++)
+ delete _hotspots[i];
+ _hotspots.clear();
+}
+
+HotSpot *HotSpotList::findByXY(int x, int y) {
+ for (uint i = 0; i < _hotspots.size(); i++) {
+ if (_hotspots[i]->getActive() && _hotspots[i]->pointInside(x, y)) {
+ return _hotspots[i];
+ }
+ }
+ return NULL;
+}
+
+void HotSpotList::setActive(const char *name, bool active) {
+ for (uint i = 0; i < _hotspots.size(); i++) {
+ if (!scumm_stricmp(_hotspots[i]->_vocab, name)) {
+ _hotspots[i]->setActive(active);
+ }
+ }
+}
+
+void HotSpotList::setActiveXY(const char *name, int x, int y, bool active) {
+ for (uint i = 0; i < _hotspots.size(); i++) {
+ if (_hotspots[i]->pointInside(x, y) && !scumm_stricmp(_hotspots[i]->_vocab, name)) {
+ _hotspots[i]->setActive(active);
+ }
+ }
+}
+
+void HotSpotList::dump() {
+ _vm->_events->getConsole()->DebugPrintf("%d hotspots in the list\n", _hotspots.size());
+
+ for (uint index = 0; index < _hotspots.size(); index++) {
+ _vm->_events->getConsole()->DebugPrintf("(%d): %p x1 = %d; y1 = %d; x2 = %d; y2 = %d\n",
+ index, (void *)_hotspots[index],
+ _hotspots[index]->_rect.left, _hotspots[index]->_rect.top,
+ _hotspots[index]->_rect.right - 1, _hotspots[index]->_rect.bottom - 1);
+ }
+}
+
+void HotSpotList::loadHotSpotsM4(Common::SeekableReadStream* hotspotStream, int hotspotCount) {
+ uint32 x1, y1, x2, y2;
+ char buffer[256];
+ uint32 strLength = 0;
+ uint32 index = 0;
+ HotSpot *currentHotSpot;
+ uint32 feetX, feetY;
+
+ for (int i = 0; i < hotspotCount; i++) {
+ x1 = hotspotStream->readUint32LE();
+ y1 = hotspotStream->readUint32LE();
+ x2 = hotspotStream->readUint32LE();
+ y2 = hotspotStream->readUint32LE();
+ index = add(new HotSpot(x1, y1, x2, y2), i == 0);
+ currentHotSpot = get(index);
+ feetX = hotspotStream->readUint32LE();
+ feetY = hotspotStream->readUint32LE();
+ currentHotSpot->setFeet(feetX, feetY);
+ currentHotSpot->setFacing((uint8)hotspotStream->readByte());
+ currentHotSpot->setActive(hotspotStream->readByte() != 0);
+ currentHotSpot->setCursor((uint8)hotspotStream->readByte());
+ hotspotStream->readByte(); // syntax (unused)
+ hotspotStream->readUint32LE(); // vocabID
+ hotspotStream->readUint32LE(); // verbID
+ strLength = hotspotStream->readUint32LE(); // vocabLength
+ hotspotStream->read(buffer, strLength); // vocab (the hotspot's name)
+ // Capitalize the hotspot's name here
+ str_upper(buffer);
+ currentHotSpot->setVocab(buffer);
+ // Verbs are used internally by the game scripts in Orion Burger
+ strLength = hotspotStream->readUint32LE(); // verbLength
+ hotspotStream->read(buffer, strLength); // verb
+ // Capitalize the hotspot's verb here
+ str_upper(buffer);
+ currentHotSpot->setVerb(buffer);
+ strLength = hotspotStream->readUint32LE(); // prepLength
+ hotspotStream->read(buffer, strLength); // prep
+ str_upper(buffer);
+
+ /* Hotspot names for non-English versions are stored in prep.
+ Prep can be set two ways: For English versions, copy the
+ text from vocab. For non-English versions, use the prep text
+ from the room file.
+ */
+ if (strlen(buffer) > 0 && strcmp(buffer, "--") != 0 && strcmp(buffer, "ON") != 0)
+ currentHotSpot->setPrep(buffer);
+ else
+ currentHotSpot->setPrep(currentHotSpot->getVocab());
+
+ // The following values are not used at all by Orion Burger
+ strLength = hotspotStream->readUint32LE(); // spriteLength
+ hotspotStream->read(buffer, strLength); // sprite
+ hotspotStream->readUint16LE(); // sprite hash
+ }
+}
+
+void HotSpotList::loadHotSpotsMads(Common::SeekableReadStream* hotspotStream, int hotspotCount) {
+ uint16 x1, y1, x2, y2;
+ HotSpot *currentHotSpot;
+ uint16 feetX, feetY;
+ uint16 index = 0;
+
+ for (int i = 0; i < hotspotCount; i++) {
+ x1 = hotspotStream->readUint16LE();
+ y1 = hotspotStream->readUint16LE();
+ x2 = hotspotStream->readUint16LE();
+ y2 = hotspotStream->readUint16LE();
+ index = add(new HotSpot(x1, y1, x2, y2), i == 0);
+ currentHotSpot = get(index);
+ //printf("x1, y1, x2, y2: %i %i %i %i\n", x1, y1, x2, y2);
+ feetX = hotspotStream->readUint16LE();
+ feetY = hotspotStream->readUint16LE();
+ currentHotSpot->setFeet(feetX, feetY);
+ currentHotSpot->setFacing((uint8)hotspotStream->readByte());
+ index = hotspotStream->readByte(); // unknown (initial facing?)
+
+ hotspotStream->readByte(); // unused (always 255)
+
+ index = hotspotStream->readByte(); // cursor
+ if (index == 0)
+ currentHotSpot->setCursor(0);
+ else
+ currentHotSpot->setCursor(index - 1);
+
+ // Rex Nebular doesn't have this field
+ if (_vm->getGameType() != GType_RexNebular) {
+ // This looks to be some sort of bitmask. Perhaps it signifies
+ // the valid verbs for this hotspot
+ index = hotspotStream->readUint16LE(); // unknown
+ //printf("%i ", index);
+ }
+
+ index = hotspotStream->readUint16LE(); // noun index
+ currentHotSpot->setVocabID(index);
+ currentHotSpot->setVocab(_vm->_globals->getVocab(index - 1));
+ index = hotspotStream->readUint16LE(); // verb index (default left click verb)
+ currentHotSpot->setVerbID(index);
+ if (index != 0) {
+ currentHotSpot->setVerb(_vm->_globals->getVocab(index - 1));
+ } else {
+ currentHotSpot->setVerb("");
+ }
+ //printf("%s ", currentHotSpot->getVerb());
+ //printf("%s ", currentHotSpot->getVocab());
+ //printf("\n");
+ }
+}
+
+} // End of namespace M4
diff --git a/engines/m4/hotspot.h b/engines/m4/hotspot.h
new file mode 100644
index 0000000000..bcd2aba919
--- /dev/null
+++ b/engines/m4/hotspot.h
@@ -0,0 +1,120 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_HOTSPOT_H
+#define M4_HOTSPOT_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/util.h"
+
+/*
+ TODO:
+ - check if hotspot_duplicate is needed
+ NOTES:
+ - hotspot_add_dynamic unused in Orion Burger
+*/
+
+//???: should String be used instead of char* here?
+
+namespace M4 {
+
+class HotSpot {
+ friend class HotSpotList;//just for debugging, to be removed later
+public:
+
+ HotSpot(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0);
+ ~HotSpot();
+
+ void setVocab(const char *value);
+ void setVocabID(int32 value) { _vocabID = value; }
+ void setVerb(const char *value);
+ void setVerbID(int32 value) { _verbID = value; }
+ void setPrep(const char *value);
+ void setSprite(const char *value);
+ void setActive(bool value) { _active = value; }
+ void setCursor(uint8 value) { _cursor = value; }
+ void setRect(int x1, int y1, int x2, int y2);
+ void setFeet(int x, int y);
+ void setFacing(uint8 facing) { _facing = facing; }
+ char *getVocab() const { return _vocab; }
+ int32 getVocabID() { return _vocabID; }
+ char *getVerb() const { return _verb; }
+ int32 getVerbID() { return _verbID; }
+ char *getPrep() const { return _prep; }
+ char *getSprite() const { return _sprite; }
+ uint8 getCursor() const { return _cursor; }
+ bool getActive() const { return _active; }
+ uint8 getFacing() const { return _facing; }
+ int getFeetX() { return _feetX; }
+ int getFeetY() { return _feetY; }
+ Common::Rect getRect() const;
+
+ int32 area() const { return (_rect.width() - 1) * (_rect.height() - 1); }
+ bool pointInside(int x, int y) { return _rect.contains(x, y); }
+
+private:
+ char *_vocab, *_verb, *_prep, *_sprite;
+ Common::Rect _rect;
+ bool _active;
+ int _feetX, _feetY;
+ uint8 _facing, _cursor;
+
+ // Unused in Orion Burger, used in MADS games
+ uint8 _syntax;
+ int32 _vocabID, _verbID;
+ //TODO: check if this is actually needed by the game
+ int16 _hash;
+};
+
+class HotSpotList {
+public:
+ HotSpotList();
+ ~HotSpotList();
+
+ int add(HotSpot *hotspot, bool head = false);
+ HotSpot *get(int index) { return _hotspots[index]; }
+ void remove(HotSpot *hotspot);
+ void unlink(HotSpot *hotspot);
+ void clear();
+ HotSpot *findByXY(int x, int y);
+ void setActive(const char *name, bool active);
+ void setActiveXY(const char *name, int x, int y, bool active);
+
+ void dump();
+
+ void loadHotSpotsM4(Common::SeekableReadStream* hotspotStream, int hotspotCount);
+ void loadHotSpotsMads(Common::SeekableReadStream* hotspotStream, int hotspotCount);
+
+private:
+ typedef Common::Array<HotSpot*> HotSpotArray;
+ HotSpotArray _hotspots;
+};
+
+
+} // End of namespace M4
+
+
+#endif
diff --git a/engines/m4/m4.cpp b/engines/m4/m4.cpp
new file mode 100644
index 0000000000..6707639d1c
--- /dev/null
+++ b/engines/m4/m4.cpp
@@ -0,0 +1,555 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+//#define SCRIPT_TEST
+//#define INTRO_TEST
+
+#include "m4/globals.h"
+#include "m4/burger_data.h"
+#include "m4/m4.h"
+#include "m4/resource.h"
+#include "m4/sprite.h"
+#include "m4/hotspot.h"
+#include "m4/font.h"
+#include "m4/rails.h"
+#include "m4/midi.h"
+#include "m4/events.h"
+#include "m4/graphics.h"
+#include "m4/viewmgr.h"
+#include "m4/gui.h"
+#include "m4/woodscript.h"
+#include "m4/actor.h"
+#include "m4/sound.h"
+#include "m4/rails.h"
+#include "m4/script.h"
+#include "m4/compression.h"
+#include "m4/animation.h"
+#include "m4/m4_menus.h"
+#include "m4/m4_views.h"
+#include "m4/mads_anim.h"
+#include "m4/mads_menus.h"
+
+#include "common/file.h"
+#include "common/events.h"
+#include "common/endian.h"
+#include "common/system.h"
+#include "common/config-manager.h"
+#include "engines/engine.h"
+#include "graphics/surface.h"
+#include "sound/mididrv.h"
+
+namespace M4 {
+
+// FIXME: remove global
+M4Engine *_vm;
+
+void escapeHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
+ // For now, simply exit the game
+ vm->_events->quitFlag = true;
+}
+
+// Temporary hotkey handler for use in testing the TextviewView class
+
+void textviewHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
+ // Deactivate the scene if it's currently active
+ View *sceneView = vm->_viewManager->getView(VIEWID_SCENE);
+ if (sceneView != NULL)
+ vm->_viewManager->deleteView(sceneView);
+
+ // Activate the textview view
+ vm->_font->setFont(FONT_CONVERSATION_MADS);
+ TextviewView *textView = new TextviewView(vm);
+ vm->_viewManager->addView(textView);
+ textView->setScript("quotes", NULL);
+}
+
+void saveGameHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
+ // TODO: See CreateF2SaveMenu - save menu should only be activated when
+ // certain conditions are met, such as player_commands_allowed, and isInterfaceVisible
+ vm->loadMenu(SAVE_MENU, true);
+}
+
+void loadGameHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
+ // TODO: See CreateF3LoadMenu - save menu should only be activated when
+ // certain conditions are met, such as player_commands_allowed, and isInterfaceVisible
+ vm->loadMenu(LOAD_MENU, true);
+}
+
+void gameMenuHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
+ vm->loadMenu(GAME_MENU);
+}
+
+M4Engine::M4Engine(OSystem *syst, const M4GameDescription *gameDesc) :
+ Engine(syst), _gameDescription(gameDesc) {
+
+ // FIXME
+ _vm = this;
+
+ Common::File::addDefaultDirectory(_gameDataPath);
+ Common::File::addDefaultDirectory("goodstuf");
+ Common::File::addDefaultDirectory("resource");
+
+ Common::addSpecialDebugLevel(kDebugScript, "script", "Script debug level");
+ Common::addSpecialDebugLevel(kDebugConversations, "conversations", "Conversations debugging");
+}
+
+
+M4Engine::~M4Engine() {
+ delete _globals;
+ delete _midi;
+ delete _saveLoad;
+ delete _kernel;
+ delete _player;
+ delete _mouse;
+ delete _events;
+ delete _font;
+ delete _actor;
+// delete _scene; // deleted by the viewmanager
+ delete _dialogs;
+ delete _screen;
+ delete _inventory;
+ delete _viewManager;
+ delete _rails;
+ delete _converse;
+ delete _script;
+ delete _ws;
+ delete _random;
+ delete _animation;
+ delete _palette;
+ delete _resourceManager;
+}
+
+int M4Engine::init() {
+ // Initialize backend
+ _system->beginGFXTransaction();
+ initCommonGFX(isM4());
+ if (isM4())
+ _system->initSize(640, 480);
+ else
+ _system->initSize(320, 200);
+ _system->endGFXTransaction();
+
+ _screen = new M4Surface(true); // Special form for creating screen reference
+
+ int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
+ bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
+
+ MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+ if (native_mt32)
+ driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+
+ _midi = new MidiPlayer(this, driver);
+ _midi->setGM(true);
+ _midi->setNativeMT32(native_mt32);
+
+ _globals = new Globals(this);
+ if (isM4())
+ _resourceManager = new M4ResourceManager(this);
+ else
+ _resourceManager = new MADSResourceManager(this);
+ _saveLoad = new SaveLoad(this);
+ _palette = new Palette(this);
+ _mouse = new Mouse(this);
+ _events = new Events(this);
+ _kernel = new Kernel(this);
+ _player = new Player(this);
+ _font = new Font(this);
+ if (getGameType() == GType_Burger) {
+ _actor = new Actor(this);
+ _interfaceView = new GameInterfaceView(this);
+ _conversationView = new ConversationView(this);
+ } else {
+ _actor = NULL;
+ }
+ _rails = new Rails(); // needs to be initialized before _scene
+ _scene = new Scene(this);
+ _dialogs = new Dialogs();
+ _viewManager = new ViewManager(this);
+ _inventory = new Inventory(this);
+ _sound = new Sound(this, _mixer, 255);
+ _converse = new Converse(this);
+ _script = new ScriptInterpreter(this);
+ _ws = new WoodScript(this);
+ _animation = new Animation(this);
+ //_callbacks = new Callbacks(this);
+ _random = new Common::RandomSource();
+ g_system->getEventManager()->registerRandomSource(*_random, "m4");
+
+ return 0;
+}
+
+void M4Engine::eventHandler() {
+ M4EventType event;
+ uint32 keycode = 0;
+
+ if ((event = _events->handleEvents()) != MEVENT_NO_EVENT) {
+ if (_viewManager->viewCount() > 0)
+ _viewManager->handleMouseEvents(event);
+ }
+
+ if (_events->kbdCheck(keycode))
+ _viewManager->handleKeyboardEvents(keycode);
+}
+
+bool M4Engine::delay(int duration, bool keyAborts, bool clickAborts) {
+ uint32 endTime = g_system->getMillis() + duration;
+ M4EventType event;
+ uint32 keycode = 0;
+
+ while (!_events->quitFlag && (g_system->getMillis() < endTime)) {
+ event = _events->handleEvents();
+ if (clickAborts && (event == MEVENT_LEFT_RELEASE) || (event == MEVENT_RIGHT_RELEASE))
+ return true;
+
+ if (_events->kbdCheck(keycode)) {
+ if (keyAborts)
+ return true;
+ }
+
+ g_system->delayMillis(10);
+ }
+
+ return false;
+}
+
+void M4Engine::loadMenu(MenuType menuType, bool loadSaveFromHotkey, bool calledFromMainMenu) {
+ if (isM4() && (menuType != MAIN_MENU)) {
+ bool menuActive = _viewManager->getView(VIEWID_MENU) != NULL;
+
+ if (!menuActive)
+ _palette->fadeToGreen(M4_DIALOG_FADE_STEPS, M4_DIALOG_FADE_DELAY);
+ }
+
+ View *view;
+
+ switch (menuType) {
+ case MAIN_MENU:
+ if (getGameType() == GType_RexNebular)
+ view = new RexMainMenuView(this);
+ else if (getGameType() == GType_DragonSphere)
+ view = new DragonMainMenuView(this);
+ else
+ view = new MadsMainMenuView(this);
+ break;
+ case GAME_MENU:
+ view = new OrionMenuView(this, 200, 100, GAME_MENU, calledFromMainMenu, loadSaveFromHotkey);
+ break;
+ case OPTIONS_MENU:
+ view = new OrionMenuView(this, 172, 160, OPTIONS_MENU, calledFromMainMenu, loadSaveFromHotkey);
+ break;
+ case LOAD_MENU:
+ case SAVE_MENU:
+ view = new OrionMenuView(this, 145, 10, menuType, calledFromMainMenu, loadSaveFromHotkey);
+ break;
+ default:
+ error("Unknown menu type");
+ break;
+ }
+
+ _viewManager->addView(view);
+ _viewManager->moveToFront(view);
+}
+
+int M4Engine::go() {
+ if (isM4())
+ return goM4();
+ else
+ return goMADS();
+}
+
+int M4Engine::goMADS() {
+ _palette->setMadsSystemPalette();
+
+ _mouse->init("cursor.ss", NULL);
+ _mouse->setCursorNum(0);
+
+ // Load MADS data files
+ _globals->loadMadsVocab(); // vocab.dat
+ _globals->loadMadsQuotes(); // quotes.dat
+ _globals->loadMadsMessagesInfo(); // messages.dat
+ // TODO: objects.dat
+ // TODO: hoganus.dat (what is it used for?)
+
+ // Test code to dump all messages to the console
+ //for (int i = 0; i < _globals->getMessagesSize(); i++)
+ //printf("%s\n----------\n", _globals->loadMessage(i));
+
+ if ((getGameType() == GType_RexNebular) || (getGameType() == GType_DragonSphere)) {
+ loadMenu(MAIN_MENU);
+
+ } else {
+ if (getGameType() == GType_DragonSphere) {
+ _scene->loadScene(FIRST_SCENE);
+ } else if (getGameType() == GType_Phantom) {
+ //_scene->loadScene(FIRST_SCENE);
+ _scene->loadScene(106); // a more interesting scene
+ }
+
+ _viewManager->addView(_scene);
+
+ _font->setFont(FONT_MAIN_MADS);
+ _font->setColors(2, 1, 3);
+ _font->writeString(_scene->getBackgroundSurface(), "Testing the M4/MADS ScummVM engine", 5, 160, 310, 2);
+ _font->writeString(_scene->getBackgroundSurface(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 5, 180, 310, 2);
+
+ if (getGameType() == GType_DragonSphere) {
+ //_scene->showMADSV2TextBox("Test", 10, 10, NULL);
+ }
+
+ _mouse->cursorOn();
+ }
+
+ _viewManager->systemHotkeys().add(Common::KEYCODE_ESCAPE, &escapeHotkeyHandler);
+ _viewManager->systemHotkeys().add(Common::KEYCODE_KP_MULTIPLY, &textviewHotkeyHandler);
+
+ // Load the general game SFX/voices
+ if (getGameType() == GType_RexNebular) {
+ _sound->loadDSRFile("rex009.dsr");
+ } else if (getGameType() == GType_Phantom) {
+ _sound->loadDSRFile("phan009.dsr");
+ } else if (getGameType() == GType_DragonSphere) {
+ _sound->loadDSRFile("drag009.dsr");
+ }
+
+ uint32 nextFrame = g_system->getMillis();
+ while (!_events->quitFlag) {
+ eventHandler();
+
+ _animation->updateAnim();
+
+ // Call the updateState method of all views
+ _viewManager->updateState();
+
+ if (g_system->getMillis() >= nextFrame) {
+
+ _viewManager->refreshAll();
+ nextFrame = g_system->getMillis();// + GAME_FRAME_DELAY;
+ }
+
+ g_system->delayMillis(10);
+ }
+
+ return 0;
+}
+
+int M4Engine::goM4() {
+
+ _script->open("m4.dat");
+
+#ifdef SCRIPT_TEST
+
+#if 0
+ ScriptFunction *func = _script->loadFunction("room_parser_142");
+ _script->runFunction(func);
+#endif
+
+#if 1
+ ScriptFunction *func = _script->loadFunction("room_daemon_951");
+ for (int i = 1; i < 58; i++) {
+ _vm->_kernel->trigger = i;
+ _script->runFunction(func);
+ printf("=================================\n");
+ fflush(stdout);
+ }
+#endif
+
+ return 0;
+#endif
+
+ // Set up the inventory
+
+ // Set up the game interface view
+ //_interfaceView->inventoryAdd("Money", "", 55); // Sample item
+
+ if (getGameType() == GType_Burger) {
+ for (int i = 0; i < ARRAYSIZE(burger_inventory); i++) {
+ char* itemName = strdup(burger_inventory[i].name);
+ _inventory->registerObject(itemName, burger_inventory[i].scene,
+ burger_inventory[i].icon);
+ _inventory->addToBackpack(i); // debug: this adds ALL objects to the player's backpack
+ }
+
+ _viewManager->addView(_interfaceView);
+ }
+
+ // Show intro
+
+ if (getGameType() == GType_Burger)
+ _kernel->newRoom = TITLE_SCENE_BURGER;
+ else
+ _scene->getBackgroundSurface()->loadBackgroundRiddle("main menu");
+
+ _viewManager->addView(_scene);
+
+ // Setup game wide hotkeys. Note that Orion Burger used F2/F3 for Save/Restore,
+ // but for standardisation with most other games, F5/F7 are also mapped
+
+ _viewManager->systemHotkeys().add(Common::KEYCODE_ESCAPE, &escapeHotkeyHandler);
+ _viewManager->systemHotkeys().add(Common::KEYCODE_F2, &saveGameHotkeyHandler);
+ _viewManager->systemHotkeys().add(Common::KEYCODE_F3, &loadGameHotkeyHandler);
+ _viewManager->systemHotkeys().add(Common::KEYCODE_F5, &saveGameHotkeyHandler);
+ _viewManager->systemHotkeys().add(Common::KEYCODE_F7, &loadGameHotkeyHandler);
+ _viewManager->systemHotkeys().add(Common::KEYCODE_F9, &gameMenuHotkeyHandler);
+
+ // Start playing Orion Burger intro music
+ //_midi->playMusic("999intro", 255, false, -1, -1);
+
+ // TODO: start playing intro animations
+
+ // TODO: Master Lu
+
+ // Test for mouse
+ _mouse->init("cursor", NULL);
+ _mouse->setCursorNum(0);
+ _mouse->cursorOn();
+
+ _ws->assets()->loadAsset("SHOW SCRIPT", NULL);
+ _ws->assets()->loadAsset("STREAM SCRIPT", NULL);
+
+#ifdef INTRO_TEST
+ int curPart = 0;
+ Machine *mach = NULL;
+#endif
+
+ _ws->setSurfaceView(_scene);
+
+ uint32 nextFrame = g_system->getMillis();
+ while (!_events->quitFlag) {
+
+ // This should probably be moved to either Scene or Kernel
+ if (_kernel->currentRoom != _kernel->newRoom) {
+
+ _ws->clear();
+
+ _kernel->currentSection = _kernel->newRoom / 100;
+ _kernel->currentRoom = _kernel->newRoom;
+
+ _scene->loadScene(_kernel->currentRoom);
+
+ _ws->setBackgroundSurface(_scene->getBackgroundSurface());
+ _ws->setInverseColorTable(_scene->getInverseColorTable());
+
+ _kernel->loadSectionScriptFunctions();
+ _kernel->loadRoomScriptFunctions();
+
+ _kernel->roomInit();
+
+#ifdef INTRO_TEST
+ if (_kernel->currentRoom == 951) {
+ curPart = 0;
+ mach = _ws->streamSeries("PLANET X HILLTOP A", 1, 0x1000, 0);
+ }
+#endif
+
+ }
+
+ eventHandler();
+
+ // Call the updateState method of all views
+ _viewManager->updateState();
+
+ // Handle frame updates
+ if (g_system->getMillis() >= nextFrame) {
+#ifdef INTRO_TEST
+ // Orion Burger intro test (scene 951)
+ // This is ugly and bad, machine is not deleted so there's a huge memory
+ // leak too. But hey, we can see some of the intro!
+ if (mach && mach->getState() == -1) {
+ if (curPart == 0)
+ mach = _ws->streamSeries("Planet X Low Ground Shot", 1, 0x1000, 0);
+ else if (curPart == 1)
+ mach = _ws->streamSeries("Planet X Hilltop B", 1, 0x1000, 0);
+ else if (curPart == 2)
+ mach = _ws->streamSeries("Space Station Panorama A", 1, 0x1000, 0);
+ else if (curPart == 3)
+ mach = _ws->streamSeries("Cargo Transfer Area A", 1, 0x1000, 0);
+ else if (curPart == 4)
+ mach = _ws->streamSeries("VP's Office A", 1, 0x1000, 0);
+ else if (curPart == 5)
+ mach = _ws->streamSeries("Hologram", 1, 0x1000, 0);
+ else if (curPart == 6)
+ mach = _ws->streamSeries("VP's Office B", 1, 0x1000, 0);
+ else if (curPart == 7)
+ mach = _ws->streamSeries("Cargo Transfer Area B", 1, 0x1000, 0);
+ else if (curPart == 8)
+ mach = _ws->streamSeries("Cargo Transfer Controls", 1, 0x1000, 0);
+ else if (curPart == 9)
+ mach = _ws->streamSeries("Space Station Panorama B", 1, 0x1000, 0);
+ // This last scene is from the rolling demo
+ //else if (curPart == 10)
+ // mach = _ws->streamSeries("Call To Action", 1, 0x1000, 0);
+ curPart++;
+ }
+#endif
+ _ws->update();
+ _viewManager->refreshAll();
+ nextFrame = g_system->getMillis();// + GAME_FRAME_DELAY;
+
+ // TEST STUFF ONLY
+ if (_player->commandReady) {
+ _kernel->roomParser();
+ _player->commandReady = false;
+ }
+
+ }
+
+ g_system->delayMillis(10);
+ }
+
+ return 0;
+}
+
+void M4Engine::dumpFile(const char* filename, bool uncompress) {
+ Common::SeekableReadStream *fileS = res()->get(filename);
+ byte buffer[256];
+ FILE *destFile = fopen(filename, "wb");
+ int bytesRead = 0;
+ printf("Dumping %s, size: %i\n", filename, fileS->size());
+
+ if (!uncompress) {
+ while(!fileS->eos()) {
+ bytesRead = fileS->read(buffer, 256);
+ fwrite(buffer, bytesRead, 1, destFile);
+ }
+ } else {
+ MadsPack packData(fileS);
+ Common::MemoryReadStream *sourceUnc;
+ for (int i = 0; i < packData.getCount(); i++) {
+ sourceUnc = packData.getItemStream(i);
+ printf("Dumping compressed chunk %i of %i, size is %i\n", i + 1, packData.getCount(), sourceUnc->size());
+ while(!sourceUnc->eos()) {
+ bytesRead = sourceUnc->read(buffer, 256);
+ fwrite(buffer, bytesRead, 1, destFile);
+ }
+ delete sourceUnc;
+ }
+ }
+
+ fclose(destFile);
+ res()->toss(filename);
+ res()->purge();
+}
+
+} // End of namespace M4
diff --git a/engines/m4/m4.h b/engines/m4/m4.h
new file mode 100644
index 0000000000..e989bcc4e6
--- /dev/null
+++ b/engines/m4/m4.h
@@ -0,0 +1,184 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_H
+#define M4_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+
+#include "engines/engine.h"
+
+#include "m4/globals.h"
+#include "m4/graphics.h"
+#include "m4/resource.h"
+#include "m4/saveload.h"
+#include "m4/viewmgr.h"
+#include "m4/gui.h"
+#include "m4/events.h"
+#include "m4/font.h"
+#include "m4/scene.h"
+#include "m4/actor.h"
+#include "m4/sound.h"
+#include "m4/rails.h"
+#include "m4/converse.h"
+#include "m4/animation.h"
+
+//#define DUMP_SCRIPTS
+
+namespace M4 {
+
+class MidiPlayer;
+class FileSystem;
+class ResourceManager;
+class Mouse;
+class Events;
+class Scene;
+class ViewManager;
+class View;
+class Inventory;
+class GameInterfaceView;
+class ConversationView;
+class Actor;
+class Converse;
+class ScriptInterpreter;
+class WoodScript;
+class Animation;
+
+enum M4GameType {
+ GType_Riddle = 1,
+ GType_Burger,
+ GType_RexNebular,
+ GType_DragonSphere,
+ GType_Phantom
+};
+
+enum Features {
+ kFeaturesNone = 0,
+ kFeaturesCD = 1 << 0,
+ kFeaturesDemo = 1 << 1
+};
+
+enum {
+ kFileTypeHash,
+ kFileTypeHAG
+};
+
+enum {
+ kDebugScript = 1 << 0,
+ kDebugConversations = 2 << 0
+};
+
+#define MESSAGE_BASIC 1
+#define MESSAGE_INTERMEDIATE 2
+#define MESSAGE_DETAILED 3
+
+struct M4GameDescription;
+
+#define GAME_FRAME_DELAY 50
+
+FORCEINLINE void str_lower(char *s) { while (*s) { *s = tolower(*s); s++; } }
+FORCEINLINE void str_upper(char *s) { while (*s) { *s = toupper(*s); s++; } }
+
+FORCEINLINE long FixedMul(long a, long b) { return (long)(((float)a * (float)b) / 65536.0); }
+FORCEINLINE long FixedDiv(long a, long b) { return (long)(((float)a / (float)b) * 65536.0); }
+
+class M4Engine : public Engine {
+private:
+ int goMADS();
+ int goM4();
+
+protected:
+ int init();
+ int go();
+ void shutdown();
+
+ MidiPlayer *_midi;
+
+public:
+ M4Engine(OSystem *syst, const M4GameDescription *gameDesc);
+ virtual ~M4Engine();
+
+ int getGameType() const;
+ uint32 getFeatures() const;
+ Common::Language getLanguage() const;
+ Common::Platform getPlatform() const;
+ bool isM4() const { return (getGameType() == GType_Riddle) || (getGameType() == GType_Burger); }
+
+ const char *getGameFile(int fileType);
+ Common::EventManager *eventMan() { return _eventMan; }
+ OSystem *system() { return _system; }
+
+ const M4GameDescription *_gameDescription;
+
+ ResourceManager *res() const { return _resourceManager; }
+ MidiPlayer *midi() { return _midi; }
+ Common::SaveFileManager *saveManager() { return _saveFileMan; }
+ void dumpFile(const char* filename, bool uncompress = false);
+ void eventHandler();
+ bool delay(int duration, bool keyAborts = true, bool clickAborts = true);
+ void loadMenu(MenuType menuType, bool loadSaveFromHotkey = false,
+ bool calledFromMainMenu = false);
+
+ // TODO: eventually these have to be removed
+ int32 seed;
+ void imath_seed(int32 seednum) { seed = seednum; }
+ uint32 imath_random() { return(seed = (25173*seed + 13849) & 0xffff); }
+ int32 imath_ranged_rand(int32 a, int32 b) { return (a + (((1 + ABS<int32>(b-a))*imath_random())>>16)); }
+ long imath_ranged_rand16(long a, long b) { return ((a + FixedMul(1+ABS<int32>(b-a),imath_random()))); }
+ //
+
+ ResourceManager *_resourceManager;
+ SaveLoad *_saveLoad;
+ ViewManager *_viewManager;
+ Palette *_palette;
+ Kernel *_kernel;
+ Globals *_globals;
+ Player *_player;
+ Mouse *_mouse;
+ Events *_events;
+ Font *_font;
+ Actor *_actor;
+ Scene *_scene;
+ Dialogs *_dialogs;
+ M4Surface *_screen;
+ Inventory *_inventory;
+ GameInterfaceView *_interfaceView;
+ ConversationView *_conversationView;
+ Sound *_sound;
+ Rails *_rails;
+ Converse *_converse;
+ ScriptInterpreter *_script;
+ WoodScript *_ws;
+ Animation *_animation;
+ Common::RandomSource *_random;
+};
+
+// FIXME: remove global
+extern M4Engine *_vm;
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/m4_menus.cpp b/engines/m4/m4_menus.cpp
new file mode 100644
index 0000000000..09c0afe313
--- /dev/null
+++ b/engines/m4/m4_menus.cpp
@@ -0,0 +1,727 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/algorithm.h" // for find()
+#include "gui/dialog.h"
+#include "gui/message.h"
+
+#include "m4/m4_menus.h"
+#include "m4/m4_views.h"
+#include "m4/woodscript.h"
+#include "m4/midi.h"
+
+namespace M4 {
+
+const char *EmptySaveString = "<empty>";
+
+//--------------------------------------------------------------------------
+// Callback methods
+//
+// Following is a set of callback methods used to handle the execution
+// of buttons in the various dialogs
+//--------------------------------------------------------------------------
+
+// General function which simply closes the active menu
+
+void OrionCallbacks::closeMenuFn(DialogView *view, MenuObject *item) {
+ view->close();
+}
+
+void OrionCallbacks::closeMenuFn(OrionMenuView *view) {
+ closeMenuFn(view, NULL);
+}
+
+/* Game menu functions */
+
+void OrionCallbacks::gameOptionsMenuFn(DialogView *view, MenuObject *item) {
+ view->vm()->loadMenu(OPTIONS_MENU);
+ view->close();
+}
+
+void OrionCallbacks::gameSaveGameFn(DialogView *view, MenuObject *item) {
+ view->vm()->loadMenu(SAVE_MENU);
+ view->close();
+}
+
+void OrionCallbacks::gameLoadGameFn(DialogView *view, MenuObject *item) {
+ view->vm()->loadMenu(LOAD_MENU);
+ view->close();
+}
+
+void OrionCallbacks::gameExitFn(DialogView *view, MenuObject *item) {
+ view->vm()->_events->quitFlag = true;
+ view->close();
+}
+
+/* Options menu */
+
+void OrionCallbacks::optionsDigiSliderFn(DialogView *view, MenuObject *item) {
+ // Digi volume slider changed
+ int percent = ((MenuHorizSlider *) item)->percent();
+
+ view->vm()->_sound->setVolume(percent * 255 / 100);
+}
+
+void OrionCallbacks::optionsMidiSliderFn(DialogView *view, MenuObject *item) {
+ // Midi volume slider changed
+ int percent = ((MenuHorizSlider *) item)->percent();
+
+ view->vm()->midi()->setVolume(percent * 255 / 100);
+}
+
+void OrionCallbacks::optionsScrollingFn(DialogView *view, MenuObject *item) {
+ // TODO: Change current Digi volume settings here
+}
+
+void OrionCallbacks::optionsCancelFn(DialogView *view, MenuObject *item) {
+ // TODO: Reset original option settings here
+ OrionMenuView *vw = (OrionMenuView *) view;
+
+ vw->vm()->midi()->setVolume(vw->_originalMidiVolume);
+
+ vw->vm()->loadMenu(GAME_MENU);
+ vw->close();
+}
+
+void OrionCallbacks::optionsDoneFn(DialogView *view, MenuObject *item) {
+ view->vm()->loadMenu(GAME_MENU);
+ view->close();
+}
+
+void OrionCallbacks::optionsReturnFn(OrionMenuView *view) {
+ optionsDoneFn(view, NULL);
+}
+
+void OrionCallbacks::optionsEscapeFn(OrionMenuView *view) {
+ optionsCancelFn(view, NULL);
+}
+
+/* Save/Load dialog functions */
+
+// Save the current game
+
+void OrionCallbacks::saveLoadSaveFn(DialogView *view, MenuObject *item) {
+ if (view->_selectedSlot == -1)
+ return;
+
+ MenuTextField *textItem = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD);
+ if (!textItem)
+ return;
+
+ textItem->setState(OS_NORMAL);
+
+ // Save the game
+ bool succeeded = view->vm()->_saveLoad->save(view->_selectedSlot + 1, textItem->getText());
+
+ if (!succeeded) {
+ GUI::MessageDialog dialog("Save game failed!");
+ dialog.runModal();
+ }
+
+ // Close the menu
+ closeMenuFn(view, item);
+}
+
+void OrionCallbacks::saveLoadLoadFn(DialogView *view, MenuObject *item) {
+ // TODO: load the selected save game
+ closeMenuFn(view, item);
+}
+
+void OrionCallbacks::saveLoadSlotFn(DialogView *view, MenuObject *item) {
+ OrionMenuView *vw = (OrionMenuView *) view;
+ MenuSaveLoadText *button = (MenuSaveLoadText *) item;
+
+ view->_selectedSlot = button->getIndex();
+ view->_deleteSaveDesc = true;
+
+ // Disable all the slots except the selected one
+ for (int index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index) {
+ MenuSaveLoadText *currentItem = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + index);
+ if (currentItem->getIndex() != button->getIndex()) {
+ currentItem->setState(OS_GREYED);
+ }
+ }
+
+ // Get a copy of the slot bounds
+ Common::Rect slotBounds = button->getBounds();
+
+ if (view->getMenuType() == SAVE_MENU) {
+ // Add in a text field for entry of the savegame name
+ vw->items().push_back(new MenuTextField(view, SLTAG_TEXTFIELD,
+ slotBounds.left, slotBounds.top, slotBounds.width(), slotBounds.height(), false,
+ saveLoadSaveFn, (button->getText() == EmptySaveString) ? NULL : button->getText(),
+ button->getIndex() + 1));
+
+ } else {
+ vw->items().push_back(new MenuTextField(view, SLTAG_TEXTFIELD,
+ slotBounds.left, slotBounds.top, slotBounds.width(), slotBounds.height(), true,
+ saveLoadLoadFn, button->getText(), button->getIndex() + 1));
+ }
+
+ // Hide the existing slot
+ button->setVisible(false);
+
+ // Disable the slider
+
+ MenuVertSlider *slider = (MenuVertSlider *) view->getItem(SLTAG_VSLIDER);
+ slider->setState(OS_GREYED);
+
+ // Enable the save/load button
+ MenuButton *btn = (MenuButton *) view->getItem(SLTAG_SAVELOAD);
+ btn->setState(OS_NORMAL);
+}
+
+void OrionCallbacks::saveLoadCancelFn(DialogView *view, MenuObject *item) {
+ OrionMenuView *vw = (OrionMenuView *) view;
+
+ if (view->_selectedSlot != -1) {
+ // Pressed cancel with a save selected, so revert back to no selection
+
+ // Re-enable all the other slots
+
+ for (int index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index) {
+ if (index != view->_selectedSlot) {
+ MenuSaveLoadText *currentItem = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + index);
+ currentItem->setState(OS_NORMAL);
+ }
+ }
+
+ // Show the previously hidden slot again
+ MenuSaveLoadText *slot = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + view->_selectedSlot);
+ slot->setVisible(true);
+ slot->setState(OS_NORMAL);
+
+ // Remove the text selection
+ MenuTextField *textField = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD);
+ delete textField;
+ vw->items().remove(textField);
+
+ // Set button enablement
+ MenuButton *btn = (MenuButton *) view->getItem(SLTAG_SAVELOAD);
+ btn->setState(OS_GREYED);
+ btn = (MenuButton *) view->getItem(SLTAG_CANCEL);
+ btn->setState(OS_NORMAL);
+
+ // Re-enable the slider
+
+ MenuVertSlider *slider = (MenuVertSlider *) view->getItem(SLTAG_VSLIDER);
+ slider->setState(OS_NORMAL);
+
+ view->_selectedSlot = -1;
+
+ } else {
+ // Close the dialog
+ if (vw->_loadSaveFromHotkey)
+ // Since dialog was called from hotkey, return directly to the game
+ closeMenuFn(view, item);
+ else {
+ // Return to the game menu
+ view->vm()->loadMenu(GAME_MENU);
+ view->close();
+ }
+ }
+}
+
+void OrionCallbacks::saveLoadSliderFn(DialogView *view, MenuObject *item) {
+ OrionMenuView *vw = (OrionMenuView *) view;
+ MenuVertSlider *slider = (MenuVertSlider *) item;
+
+ if (slider->sliderState() == VSLIDER_THUMBNAIL) {
+ // Callback generated by slider thumb, so set top slot using slider percentage
+ vw->setTopSaveSlot(slider->percent() * 89 / 100);
+
+ } else {
+ int newIndex = view->_topSaveSlotIndex;
+
+ switch (slider->sliderState()) {
+ case VSLIDER_UP:
+ if (newIndex > 0)
+ --newIndex;
+ break;
+
+ case VSLIDER_PAGE_UP:
+ if (newIndex > 0)
+ newIndex = MAX(newIndex - 10, 0);
+ break;
+
+ case VSLIDER_PAGE_DOWN:
+ if (newIndex < 89)
+ newIndex = MIN(newIndex + 10, 89);
+ break;
+
+ case VSLIDER_DOWN:
+ if (newIndex < 89)
+ ++newIndex;
+ break;
+
+ default:
+ break;
+ }
+
+ if (newIndex != view->_topSaveSlotIndex) {
+ // Set the new top slot
+ vw->setTopSaveSlot(newIndex);
+
+ // Set the new slider position
+ slider->setPercentage(newIndex * 100 / 89);
+ }
+ }
+}
+
+void OrionCallbacks::saveLoadEscapeFn(OrionMenuView *view) {
+ saveLoadCancelFn(view, NULL);
+}
+
+void OrionCallbacks::saveLoadReturnFn(OrionMenuView *view) {
+ MenuTextField *textItem = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD);
+ if (textItem) {
+ if (view->getMenuType() == SAVE_MENU)
+ saveLoadSaveFn(view, NULL);
+ else
+ saveLoadLoadFn(view, NULL);
+ }
+}
+
+//--------------------------------------------------------------------------
+
+OrionMenuView::OrionMenuView(M4Engine *Vm, int x, int y, MenuType menuType, bool calledFromMainMenu,
+ bool loadSaveFromHotkey): DialogView(Vm, x, y, true) {
+ _menuType = menuType;
+ _screenType = VIEWID_MENU;
+ _screenFlags.layer = LAYER_MENU;
+ _screenFlags.get = SCREVENT_ALL;
+ _screenFlags.blocks = SCREVENT_ALL;
+ _screenFlags.immovable = true;
+ //_screenFlags.immovable = false; // uncomment to make menu movable
+ _coords.left = x;
+ _coords.top = y;
+ _currentItem = NULL;
+ _escapeHandler = &OrionCallbacks::closeMenuFn;
+ _returnHandler = NULL;
+ _saveNames = NULL;
+ _savegameThumbnail = NULL;
+ _deleteSaveDesc = false;
+ _closeFlag = false;
+
+ _calledFromMainMenu = calledFromMainMenu;
+ _loadSaveFromHotkey = loadSaveFromHotkey;
+
+ _interfaceWasVisible = _vm->_interfaceView->isVisible();
+ if (_interfaceWasVisible)
+ _vm->_interfaceView->hide();
+
+ _vm->_mouse->setCursorNum(CURSOR_ARROW);
+
+ switch (menuType) {
+ case GAME_MENU:
+ loadSprites(MENU_GAME);
+
+ // Add menu contents
+ _menuObjects.push_back(new MenuButton(this, BTNID_MAIN, 45, 53, 24, 24, &OrionCallbacks::closeMenuFn));
+ _menuObjects.push_back(new MenuButton(this, BTNID_OPTIONS, 45, 94, 24, 24, &OrionCallbacks::gameOptionsMenuFn));
+ _menuObjects.push_back(new MenuButton(this, BTNID_RESUME, 45, 135, 24, 24, &OrionCallbacks::closeMenuFn));
+ _menuObjects.push_back(new MenuButton(this, BTNID_QUIT, 141, 135, 24, 24, &OrionCallbacks::gameExitFn));
+ _menuObjects.push_back(new MenuButton(this, BTNID_SAVE, 141, 53, 24, 24, &OrionCallbacks::gameSaveGameFn, _calledFromMainMenu));
+ _menuObjects.push_back(new MenuButton(this, BTNID_LOAD, 141, 94, 24, 24, &OrionCallbacks::gameLoadGameFn,
+ !_vm->_saveLoad->hasSaves()));
+
+ _escapeHandler = &OrionCallbacks::closeMenuFn;
+ _returnHandler = &OrionCallbacks::closeMenuFn;
+ break;
+
+ case OPTIONS_MENU:
+ loadSprites(MENU_OPTIONS);
+
+ // Store the original settings in case user aborts dialog
+ _originalMidiVolume = _vm->midi()->getVolume();
+
+ // Add menu contents
+ // TODO: Currently the Digi slider isn't hooked up to anything
+ _menuObjects.push_back(new MenuButton(this, OPTIONID_CANCEL, 93, 141, 74, 43,
+ &OrionCallbacks::optionsCancelFn, false, false, OBJTYPE_OM_CANCEL));
+ _menuObjects.push_back(new MenuButton(this, OPTIONID_DONE, 168, 141, 74, 43,
+ &OrionCallbacks::optionsDoneFn, false, false, OBJTYPE_OM_DONE));
+ _menuObjects.push_back(new MenuHorizSlider(this, OPTIONID_HSLIDER_MIDI, 47, 64, 212, 24,
+ _originalMidiVolume * 100 / 255, &OrionCallbacks::optionsMidiSliderFn, true));
+ _menuObjects.push_back(new MenuHorizSlider(this, OPTIONID_HSLIDER_DIGI, 47, 104, 212, 24,
+ 0, &OrionCallbacks::optionsDigiSliderFn, true));
+
+ _escapeHandler = &OrionCallbacks::optionsEscapeFn;
+ _returnHandler = &OrionCallbacks::optionsReturnFn;
+ break;
+
+ case SAVE_MENU:
+ case LOAD_MENU:
+ loadSprites(MENU_SAVELOAD);
+
+ // Set up the defaults for the window
+ _topSaveSlotIndex = 0;
+ _selectedSlot = -1;
+ _highlightedSlot = -1;
+ _saveNames = _vm->_saveLoad->getSaves();
+
+ // Set up menu elements
+ _menuObjects.push_back(new MenuMessage(this, SLTAG_SAVELOAD_LABEL, 50, 241, 70, 16));
+ _menuObjects.push_back(new MenuButton(this, SLTAG_SAVELOAD, 214, 384, 72, 41,
+ (menuType == SAVE_MENU) ? &OrionCallbacks::saveLoadSaveFn : &OrionCallbacks::saveLoadLoadFn,
+ true, true, (menuType == SAVE_MENU) ? OBJTYPE_SL_SAVE : OBJTYPE_SL_LOAD));
+ _menuObjects.push_back(new MenuButton(this, SLTAG_CANCEL, 139, 384, 74, 43,
+ &OrionCallbacks::saveLoadCancelFn, false, false, OBJTYPE_SL_CANCEL));
+ _menuObjects.push_back(new MenuVertSlider(this, SLTAG_VSLIDER, 291, 255, 23, 127, 0,
+ &OrionCallbacks::saveLoadSliderFn));
+
+ if (_menuType == SAVE_MENU)
+ _savegameThumbnail = createThumbnail();
+
+ _menuObjects.push_back(new MenuImage(this, SLTAG_THUMBNAIL, 66, 28, 215, 162,
+ (_savegameThumbnail == NULL) ? _sprites->getFrame(SL_EMPTY_THUMBNAIL) : _savegameThumbnail));
+
+
+ {
+ SaveGameIterator slot = _saveNames->begin();
+ for (uint slotIndex = 0; slotIndex < SL_NUM_VISIBLE_SLOTS; ++slotIndex, ++slot) {
+ // Get save slot
+ bool isEmpty = (slotIndex >= _saveNames->size()) || (*slot).empty();
+
+ _menuObjects.push_back(new MenuSaveLoadText(this, SLTAG_SLOTS_START + slotIndex,
+ 50, 256 + slotIndex * 15, 238, 15, &OrionCallbacks::saveLoadSlotFn,
+ (menuType == LOAD_MENU) && isEmpty, true, (menuType == LOAD_MENU),
+ isEmpty ? EmptySaveString : slot->c_str(), slotIndex + 1));
+ }
+ }
+
+ _escapeHandler = &OrionCallbacks::saveLoadEscapeFn;
+ _returnHandler = &OrionCallbacks::saveLoadReturnFn;
+ break;
+
+ default:
+ error("Unknown menu type");
+ break;
+ }
+
+ // Draw all the items onto the background surface
+ for (MenuObjectsIterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i)
+ (*i)->onRefresh();
+}
+
+OrionMenuView::~OrionMenuView() {
+ delete _sprites;
+
+ for (MenuObjectList::iterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i)
+ delete *i;
+ _menuObjects.clear();
+
+ if (_saveNames)
+ delete _saveNames;
+ if (_savegameThumbnail)
+ delete _savegameThumbnail;
+}
+
+bool OrionMenuView::loadSprites(const char *seriesName) {
+ Common::SeekableReadStream *data = _vm->res()->get(seriesName);
+ RGB8 *palette;
+
+ _sprites = new SpriteAsset(_vm, data, data->size(), seriesName);
+ palette = _sprites->getPalette();
+ _vm->_palette->setPalette(palette, 0, _sprites->getColorCount());
+
+ _vm->res()->toss(seriesName);
+
+ // Update the palette
+ //_vm->setPalette((byte *) _menuPalette, 59, 197);
+
+ // The first sprite is the menu background
+
+ M4Sprite *bg = _sprites->getFrame(0);
+ this->setSize(bg->width(), bg->height());
+ _coords.setWidth(bg->width());
+ _coords.setHeight(bg->height());
+ bg->copyTo(this);
+
+ return true;
+}
+
+// Creates a thumbnail based on the current background screen
+
+M4Surface *OrionMenuView::createThumbnail() {
+ M4Surface srcSurface(_vm->_screen->width(), _vm->_screen->height());
+ M4Surface *result = new M4Surface(_vm->_screen->width() / 3, _vm->_screen->height() / 3);
+
+ // Translate the scene data
+
+ _vm->_scene->onRefresh(NULL, &srcSurface);
+ byte *srcP = (byte *)srcSurface.pixels;
+ byte *destP = (byte *)result->pixels;
+
+ for (int yCtr = 0; yCtr < _vm->_scene->height() / 3; ++yCtr, srcP += g_system->getWidth() * 3) {
+ byte *src0P = srcP;
+ byte *src1P = srcP + _vm->_screen->width();
+ byte *src2P = src1P + _vm->_screen->width();
+
+ for (int xCtr = 0; xCtr < result->width(); ++xCtr) {
+ *destP = (byte)((uint32)((
+ *src0P + *(src0P + 1) + *(src0P + 2) +
+ *src1P + *(src1P + 1) + *(src1P + 2) +
+ *src2P + *(src2P + 1) + *(src2P + 2)) / 9));
+ if (*destP == 0)
+ *destP = 21;
+
+ ++destP;
+ src0P += 3;
+ src1P += 3;
+ src2P += 3;
+ }
+ }
+
+ // Translate the game interface view - since it's using standard colors that can't be
+ // averaged, simply take the top left pixel of every 3x3 pixel block
+
+ _vm->_interfaceView->onRefresh(NULL, &srcSurface);
+ destP = (byte *)result->pixels + (_vm->_screen->width() / 3) * (_vm->_interfaceView->bounds().top / 3);
+
+ int yStart = _vm->_interfaceView->bounds().top;
+ int yEnd = MIN(_vm->_screen->height() - 1, (int) _vm->_interfaceView->bounds().bottom - 1);
+ for (int yCtr = yStart; yCtr <= yEnd; yCtr += 3) {
+ srcP = (byte *)srcSurface.pixels + (yCtr * _vm->_screen->width());
+
+ for (int xCtr = 0; xCtr < result->width(); ++xCtr, srcP += 3)
+ *destP++ = *srcP;
+ }
+
+ return result;
+}
+
+void OrionMenuView::destroyView() {
+ M4Engine *engine = _vm;
+ bool interfaceVisible = _interfaceWasVisible;
+ engine->_viewManager->deleteView(this);
+
+ // Fade the game back in if no menu views are active (such as if a button was pressed in one menu
+ // to activate another menu dialog)
+ bool fadeIn = engine->_viewManager->getView(VIEWID_MENU) == NULL;
+
+ if (fadeIn) {
+ bool fadeToBlack = engine->_events->quitFlag;
+ engine->_ws->update();
+ engine->_palette->fadeFromGreen(M4_DIALOG_FADE_STEPS, M4_DIALOG_FADE_DELAY, fadeToBlack);
+
+ if (interfaceVisible)
+ engine->_interfaceView->show();
+ }
+}
+
+bool OrionMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ static Common::Point movingPos(0, 0);
+ static bool movingFlag = false;
+
+ bool handledFlag = false;
+ int localX, localY;
+ MenuObjectsIterator i;
+
+ if (!_screenFlags.visible)
+ return false;
+
+ if (!movingFlag)
+ captureEvents = false;
+
+ // If the escape key is pressed, then pass onto the Escape handler
+
+ if (eventType == KEVENT_KEY) {
+ if ((param == Common::KEYCODE_ESCAPE) && (_escapeHandler != NULL)) {
+ // Execute the Escape handler function
+ _currentItem = NULL;
+ captureEvents = false;
+ _escapeHandler(this);
+ destroyView();
+ return true;
+ }
+
+ if (((param == Common::KEYCODE_RETURN) || (param == Common::KEYCODE_KP_ENTER)) &&
+ (_returnHandler != NULL)) {
+ // Execute the Return handler function
+ _currentItem = NULL;
+ captureEvents = false;
+ _returnHandler(this);
+ return true;
+ }
+
+ MenuTextField *textItem = (MenuTextField *) getItem(SLTAG_TEXTFIELD);
+ if (textItem && textItem->onEvent(KEVENT_KEY, param, x, y, _currentItem))
+ return true;
+ }
+
+ // Convert the screen position to a relative position within the menu surface
+ localX = x - _coords.left;
+ localY = y - _coords.top;
+
+ // If there is an active object handling events, pass it on until it releases control
+
+ if (_currentItem) {
+ handledFlag = _currentItem->onEvent(eventType, param, localX, localY, _currentItem);
+
+ if (_closeFlag) {
+ // Dialog has been flagged to be closed
+ captureEvents = false;
+ destroyView();
+ return true;
+ }
+
+ if (_currentItem) {
+ captureEvents =
+ (Common::find(_menuObjects.begin(), _menuObjects.end(), _currentItem) != _menuObjects.end());
+ if (!captureEvents)
+ // The menu object is no longer active, so reset current item
+ _currentItem = NULL;
+ } else {
+ captureEvents = false;
+ }
+
+ if (handledFlag)
+ return true;
+ }
+
+ if (eventType == KEVENT_KEY) {
+ // Handle keypresses by looping through the item list to see if any of them want it
+
+ for (i = _menuObjects.begin(); (i != _menuObjects.end()) && !handledFlag; ++i) {
+ MenuObject *menuObj = *i;
+ MenuObject *dummyItem;
+ handledFlag = menuObj->onEvent(eventType, param, localX, localY, dummyItem);
+ }
+
+ return handledFlag;
+
+ } else {
+ // Handle mouse events by scanning the item list to see if the cursor is within any
+
+ for (i = _menuObjects.begin(); (i != _menuObjects.end()) && !handledFlag; ++i) {
+ MenuObject *menuObj = *i;
+
+ if (menuObj->isInside(localX, localY)) {
+ // Found an item, so pass it the event
+ menuObj->onEvent(eventType, param, localX, localY, _currentItem);
+
+ if (_closeFlag) {
+ // Dialog has been flagged to be closed
+ captureEvents = false;
+ destroyView();
+ return true;
+ }
+
+ if (_currentItem) {
+ captureEvents =
+ (Common::find(_menuObjects.begin(), _menuObjects.end(), _currentItem) != _menuObjects.end());
+ if (!captureEvents)
+ // The menu object is no longer active, so reset current item
+ _currentItem = NULL;
+ } else {
+ captureEvents = false;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ // None of the items have handled the event, so fall back on menu-wide event handling
+
+ switch (eventType) {
+ case MEVENT_LEFT_CLICK:
+ case MEVENT_DOUBLECLICK:
+ if (!_screenFlags.immovable) {
+ // Move the entire dialog
+ captureEvents = true;
+ movingFlag = true;
+ movingPos.x = x;
+ movingPos.y = y;
+ }
+ break;
+
+ case MEVENT_LEFT_DRAG:
+ case MEVENT_DOUBLECLICK_DRAG:
+ if (movingFlag) {
+ moveRelative(x - movingPos.x, y - movingPos.y);
+ movingPos.x = x;
+ movingPos.y = y;
+ }
+ break;
+
+ case MEVENT_LEFT_RELEASE:
+ case MEVENT_DOUBLECLICK_RELEASE:
+ captureEvents = false;
+ movingFlag = false;
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+MenuObject *OrionMenuView::getItem(int objectId) {
+ MenuObjectsIterator i;
+ for (i = _menuObjects.begin(); i != _menuObjects.end(); ++i) {
+ MenuObject *obj = *i;
+ if (obj->getObjectId() == objectId)
+ return obj;
+ }
+
+ return NULL;
+}
+
+void OrionMenuView::setTopSaveSlot(int slotNumber) {
+ _topSaveSlotIndex = MAX(MIN(slotNumber, 89), 0);
+
+ // Update the details of the load/save slots
+
+ // Get save slot
+ SaveGameIterator slot = _saveNames->begin();
+ for (int i = 0; i < _topSaveSlotIndex; ++i)
+ ++slot;
+
+ for (uint index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index, ++slot) {
+ MenuSaveLoadText *item = (MenuSaveLoadText *) getItem(SLTAG_SLOTS_START + index);
+ uint slotIndex = _topSaveSlotIndex + index;
+
+ bool isEmpty = (slotIndex >= _saveNames->size()) || slot->empty();
+ item->setDisplay(slotIndex + 1, isEmpty ? EmptySaveString : slot->c_str());
+
+ item->setState((_menuType == SAVE_MENU) || !isEmpty ? OS_NORMAL : OS_GREYED);
+ }
+}
+
+void OrionMenuView::refresh(const Common::Rect &areaRect) {
+ // Copy the selected portion of the background
+ _sprites->getFrame(0)->copyTo(this, areaRect, areaRect.left, areaRect.top);
+
+ for (MenuObjectsIterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i) {
+ MenuObject *obj = *i;
+ if (obj->getBounds().intersects(areaRect))
+ obj->onRefresh();
+ }
+}
+
+}
diff --git a/engines/m4/m4_menus.h b/engines/m4/m4_menus.h
new file mode 100644
index 0000000000..f0a0f07333
--- /dev/null
+++ b/engines/m4/m4_menus.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_M4_MENUS_H
+#define M4_M4_MENUS_H
+
+#include "common/list.h"
+#include "common/ptr.h"
+
+#include "m4/viewmgr.h"
+#include "m4/m4.h"
+#include "m4/gui.h"
+#include "m4/saveload.h"
+
+namespace M4 {
+
+#define M4_DIALOG_FADE_STEPS 5
+#define M4_DIALOG_FADE_DELAY 30
+
+typedef Common::List<MenuObject *> MenuObjectList;
+
+class OrionMenuView: public DialogView {
+ typedef MenuObjectList::iterator MenuObjectsIterator;
+private:
+ MenuType _menuType;
+ SpriteAsset *_sprites;
+ MenuObjectList _menuObjects;
+ MenuObject *_currentItem;
+ typedef void (*Callback)(OrionMenuView *view);
+ OrionMenuView::Callback _escapeHandler, _returnHandler;
+ bool _closeFlag;
+ bool _calledFromMainMenu;
+ bool _interfaceWasVisible;
+ int _firstSlotIndex;
+
+ bool loadSprites(const char *seriesName);
+ M4Surface *createThumbnail();
+ void destroyView();
+public:
+ OrionMenuView(M4Engine *vm, int x, int y, MenuType menuType, bool calledFromMainMenu,
+ bool loadSaveFromHotkey);
+ ~OrionMenuView();
+ MenuType getMenuType() { return _menuType; }
+ SpriteAsset *sprites() { return _sprites; }
+ MenuObjectList &items() { return _menuObjects; }
+ MenuObject *getItem(int objectId);
+ void setTopSaveSlot(int slotNumber);
+ void refresh(const Common::Rect &areaRect);
+ void close() { _closeFlag = true; }
+
+ bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents);
+
+ int _originalMidiVolume;
+ SaveGameList *_saveNames;
+ bool _loadSaveFromHotkey;
+};
+
+class OrionCallbacks {
+public:
+ static void closeMenuFn(DialogView *view, MenuObject *item);
+ static void closeMenuFn(OrionMenuView *view);
+ static void gameOptionsMenuFn(DialogView *view, MenuObject *item);
+ static void gameSaveGameFn(DialogView *view, MenuObject *item);
+ static void gameLoadGameFn(DialogView *view, MenuObject *item);
+ static void gameExitFn(DialogView *view, MenuObject *item);
+ static void optionsDigiSliderFn(DialogView *view, MenuObject *item);
+ static void optionsMidiSliderFn(DialogView *view, MenuObject *item);
+ static void optionsScrollingFn(DialogView *view, MenuObject *item);
+ static void optionsCancelFn(DialogView *view, MenuObject *item);
+ static void optionsDoneFn(DialogView *view, MenuObject *item);
+ static void optionsReturnFn(OrionMenuView *view);
+ static void optionsEscapeFn(OrionMenuView *view);
+ static void saveLoadSaveFn(DialogView *view, MenuObject *item);
+ static void saveLoadLoadFn(DialogView *view, MenuObject *item);
+ static void saveLoadSlotFn(DialogView *view, MenuObject *item);
+ static void saveLoadCancelFn(DialogView *view, MenuObject *item);
+ static void saveLoadSliderFn(DialogView *view, MenuObject *item);
+ static void saveLoadEscapeFn(OrionMenuView *view);
+ static void saveLoadReturnFn(OrionMenuView *view);
+};
+
+}
+
+#endif
diff --git a/engines/m4/m4_views.cpp b/engines/m4/m4_views.cpp
new file mode 100644
index 0000000000..9bf964ee96
--- /dev/null
+++ b/engines/m4/m4_views.cpp
@@ -0,0 +1,345 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/m4_views.h"
+#include "m4/events.h"
+#include "m4/font.h"
+#include "m4/globals.h"
+#include "m4/m4.h"
+
+namespace M4 {
+
+GUIInventory::GUIInventory(View *owner, M4Engine *vm, const Common::Rect &bounds, int horizCells,
+ int vertCells, int cellWidth, int cellHeight, int tag): GUIRect(owner, bounds, tag) {
+
+ _vm = vm;
+ _cellCount.x = horizCells;
+ _cellCount.y = vertCells;
+ _cellSize.x = cellWidth;
+ _cellSize.y = cellHeight;
+
+ // Validate the cell settings
+ if ((_cellCount.x * _cellSize.x > _bounds.width()) ||
+ (_cellCount.y * _cellSize.y > _bounds.height()))
+ error("Cell settings for inventory display exceeded control size");
+
+ _visible = true;
+ _scrollPosition = 0;
+ _scrollable = false;
+ _highlightedIndex = -1;
+ _selectedIndex = -1;
+}
+
+void GUIInventory::onRefresh() {
+ _parent->fillRect(_bounds, _vm->_palette->BLACK);
+ //_parent->frameRect(_bounds, _vm->_palette->LIGHT_GRAY);
+
+ if (_visible) {
+ //kernel_trigger_dispatch(kernel_trigger_create(TRIG_INV_CLICK));
+
+ _scrollable = false;
+
+ // Get to the starting inventory position for display
+ ItemsIterator i = _inventoryItems.begin();
+ int index = _scrollPosition;
+ while (index-- > 0) ++i;
+
+ // Loop through displaying entries
+ for (index = 0; (i != _inventoryItems.end()) && (index < _cellCount.x * _cellCount.y); ++index, ++i) {
+ GUIInventoryItem *item = (*i).get();
+ const Common::Point cellPos = getCellPosition(index);
+/* Common::Rect cellBounds(_bounds.left + cellPos.x + xOffset,
+ _bounds.top + cellPos.y + yOffset,
+ _bounds.left + cellPos.x + xOffset + _cellSize.x,
+ _bounds.top + cellPos.y + _cellSize.y);*/
+ Common::Rect cellBounds(_bounds.left + cellPos.x, _bounds.top + cellPos.y,
+ _bounds.left + cellPos.x + _cellSize.x, _bounds.top + cellPos.y + _cellSize.y);
+
+ Common::Point iconPt(
+ cellBounds.left + (cellBounds.width() - item->icon->width()) / 2,
+ cellBounds.top + (cellBounds.height() - item->icon->height()) / 2);
+
+ item->icon->copyTo(_parent, iconPt.x, iconPt.y, 0);
+
+ if (_highlightedIndex == index)
+ _parent->frameRect(Common::Rect(iconPt.x - 2, iconPt.y - 2,
+ iconPt.x + item->icon->width() + 2, iconPt.y + item->icon->height() + 2),
+ _vm->_palette->LIGHT_GRAY);
+ }
+ }
+}
+
+bool GUIInventory::onEvent(M4EventType eventType, int param, int x, int y, GUIObject *&currentItem) {
+ bool result = false;
+ int overIndex = getInsideIndex(x, y);
+ bool isPressed = (eventType == MEVENT_LEFT_CLICK) || (eventType == MEVENT_LEFT_HOLD) ||
+ (eventType == MEVENT_LEFT_DRAG);
+ ItemsIterator curItem = _inventoryItems.begin();
+
+ if (isPressed) {
+ if (_selectedIndex == -1 && overIndex != -1) {
+ setHighlight(overIndex);
+ _selectedIndex = overIndex;
+ for (int i = 0; i < _scrollPosition + _selectedIndex; i++)
+ ++curItem;
+ if (_scrollPosition + _selectedIndex < (int)_inventoryItems.size())
+ _vm->_mouse->setCursorNum(curItem->get()->iconIndex);
+ result = true;
+ } else {
+ // We are over something being tracked
+ if (_selectedIndex == overIndex) {
+ setHighlight(overIndex);
+ result = true;
+ } else {
+ // Otherwise reset highlighting
+ setHighlight(-1);
+ result = false;
+ }
+ }
+ } else {
+ // No button pressed
+ if (_selectedIndex == overIndex) {
+ result = (_selectedIndex != -1);
+ } else {
+ result = (overIndex + _scrollPosition < (int)_inventoryItems.size());
+ if (result) {
+ for (int i = 0; i < overIndex + _scrollPosition; i++)
+ ++curItem;
+ _vm->_interfaceView->setStatusText(curItem->get()->name);
+ }
+ }
+
+ // Stop tracking anything
+ setHighlight(overIndex);
+ }
+
+ return result;
+}
+
+void GUIInventory::add(const char *name, const char *verb, M4Surface *icon, int iconIndex) {
+ // First scan through the list to prevent duplicate objects
+ for (ItemsIterator i = _inventoryItems.begin(); i != _inventoryItems.end(); ++i) {
+ if (!strcmp(name, ((*i).get())->name))
+ return;
+ }
+
+ _inventoryItems.push_back(InventoryList::value_type(new GUIInventoryItem(name, verb, icon, iconIndex)));
+}
+
+bool GUIInventory::remove(const char *name) {
+ for (ItemsIterator i = _inventoryItems.begin(); i != _inventoryItems.end(); ++i) {
+ if (!strcmp(name, ((*i).get())->name)) {
+ _inventoryItems.erase(i);
+ _scrollPosition = 0;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int GUIInventory::getInsideIndex(int x, int y) {
+ if (!_bounds.contains(x, y))
+ return -1;
+
+ int localX = x - _bounds.left;
+ int localY = y - _bounds.top;
+ return (localX / _cellSize.x) * _cellCount.y + (localY / _cellSize.y);
+}
+
+const char *GUIInventory::getSelectedObjectName() {
+ if (_selectedIndex != -1) {
+ ItemsIterator curItem = _inventoryItems.begin();
+ for (int i = 0; i < _selectedIndex; i++)
+ ++curItem;
+ return curItem->get()->name;
+ } else {
+ return NULL;
+ }
+}
+
+const Common::Point &GUIInventory::getCellPosition(int index) {
+ static Common::Point result;
+
+ if (_cellCount.x > _cellCount.y) {
+ // Horizontal orientation
+ result.x = (index / _cellCount.y) * _cellSize.x;
+ result.y = (index % _cellCount.y) * _cellSize.x;
+ } else {
+ // Vertical orientation
+ result.x = (index / _cellCount.x) * _cellSize.y;
+ result.y = (index % _cellCount.x) * _cellSize.y;
+ }
+
+ return result;
+}
+
+void GUIInventory::setHighlight(int index) {
+ if (_highlightedIndex == index)
+ return;
+
+ _highlightedIndex = index;
+}
+
+void GUIInventory::setScrollPosition(int value) {
+ if (value < 0)
+ return;
+ else if (value >= (int)_inventoryItems.size() - (_cellCount.x * _cellCount.y))
+ return;
+
+ _scrollPosition = value;
+}
+
+//--------------------------------------------------------------------------
+
+const char *INTERFACE_SERIES = "999intr";
+
+#define SPR(x) _sprites->getFrame(x)
+
+GameInterfaceView::GameInterfaceView(M4Engine *vm):
+ View(vm, Common::Rect(0, vm->_screen->height() - INTERFACE_HEIGHT,
+ vm->_screen->width(), vm->_screen->height())),
+ _statusText(GUITextField(this, Common::Rect(200, 1, 450, 21))),
+ _inventory(GUIInventory(this, vm, Common::Rect(188, 22, 539, 97), 9, 1, 39, 75, 3)) {
+
+ _screenType = VIEWID_INTERFACE;
+ _screenFlags.layer = LAYER_INTERFACE;
+ _screenFlags.visible = false;
+ _screenFlags.get = SCREVENT_MOUSE;
+ _highlightedIndex = -1;
+ _selected = false;
+
+ Common::SeekableReadStream *data = _vm->res()->get(INTERFACE_SERIES);
+ RGB8 *palette;
+
+ _sprites = new SpriteAsset(_vm, data, data->size(), INTERFACE_SERIES);
+ palette = _sprites->getPalette();
+
+ //Palette.setPalette(palette, 0, _sprites->getColorCount());
+
+ _vm->res()->toss(INTERFACE_SERIES);
+
+ // Setup the interface buttons
+
+ _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(15, 35, 47, 66), 0, SPR(0), SPR(1), SPR(2)))); // look
+ _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(60, 35, 92, 66), 1, SPR(3), SPR(4), SPR(5)))); // take
+ _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(105, 35, 137, 66), 2, SPR(6), SPR(7), SPR(8)))); // manipulate
+
+ _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(580, 10, 620, 69), 3, SPR(69), SPR(70), SPR(71)))); // abduction
+ _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(582, 70, 619, 105), 4, SPR(76), SPR(77), SPR(78)))); // menu
+
+ _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(168, 22, 188, 97), 5, SPR(60), SPR(61), SPR(62)))); // Scroll left
+ _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(539, 22, 559, 97), 6, SPR(64), SPR(65), SPR(66)))); // Scroll right
+}
+
+#undef SPR
+
+GameInterfaceView::~GameInterfaceView() {
+ delete _sprites;
+}
+
+void GameInterfaceView::setHighlightedButton(int index) {
+ if (index == _highlightedIndex)
+ return;
+
+ _selected = (index == -1);
+ _highlightedIndex = index;
+}
+
+bool GameInterfaceView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ static bool selectionFlag = false;
+ if (eventType == MEVENT_LEFT_RELEASE)
+ selectionFlag = false;
+
+ captureEvents = isInside(x, y);
+ if (!captureEvents)
+ return false;
+
+ int localX = x - _coords.left;
+ int localY = y - _coords.top;
+ GUIObject *currentItem;
+
+ _statusText.onEvent(eventType, param, localX, localY, currentItem);
+ _inventory.onEvent(eventType, param, localX, localY, currentItem);
+
+ if (_vm->_mouse->getCursorNum() != CURSOR_LOOK &&
+ _vm->_mouse->getCursorNum() != CURSOR_TAKE &&
+ _vm->_mouse->getCursorNum() != CURSOR_USE &&
+ _vm->_interfaceView->_inventory.getSelectedIndex() == -1) {
+ if (_vm->_mouse->getCursorNum() != 0)
+ _vm->_mouse->setCursorNum(0);
+ }
+
+ for (ButtonsIterator i = _buttons.begin(); i != _buttons.end(); ++i) {
+ GUIButton *btn = (*i).get();
+ btn->onEvent(eventType, param, localX, localY, currentItem);
+ if ((btn->getState() == BUTTON_PRESSED) && !selectionFlag) {
+ selectionFlag = true;
+ _highlightedIndex = btn->getTag();
+
+ switch (_highlightedIndex) {
+ case 0:
+ _vm->_mouse->setCursorNum(CURSOR_LOOK);
+ break;
+ case 1:
+ _vm->_mouse->setCursorNum(CURSOR_TAKE);
+ break;
+ case 2:
+ _vm->_mouse->setCursorNum(CURSOR_USE);
+ break;
+ case 3:
+ // TODO: Jump to abduction
+ break;
+ case 4:
+ _vm->loadMenu(GAME_MENU);
+ break;
+ case 5:
+ _inventory.scrollLeft();
+ break;
+ case 6:
+ _inventory.scrollRight();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+void GameInterfaceView::onRefresh(RectList *rects, M4Surface *destSurface) {
+ empty();
+
+ _statusText.onRefresh();
+ _inventory.onRefresh();
+ for (ButtonsIterator i = _buttons.begin(); i != _buttons.end(); ++i)
+ ((*i).get())->onRefresh();
+
+ View::onRefresh(rects, destSurface);
+}
+
+
+} // End of namespace M4
diff --git a/engines/m4/m4_views.h b/engines/m4/m4_views.h
new file mode 100644
index 0000000000..2f2d450332
--- /dev/null
+++ b/engines/m4/m4_views.h
@@ -0,0 +1,118 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_M4_VIEWS_H
+#define M4_M4_VIEWS_H
+
+#include "m4/gui.h"
+#include "m4/viewmgr.h"
+#include "common/rect.h"
+#include "common/list.h"
+#include "common/ptr.h"
+
+namespace M4 {
+
+class GUIInventoryItem {
+public:
+ const char *name;
+ const char *verb;
+ M4Surface *icon;
+ int iconIndex;
+
+ GUIInventoryItem(const char *_name, const char *_verb, M4Surface *_icon, int _iconIndex) {
+ name = _name; _verb = verb; icon = _icon; iconIndex = _iconIndex;
+ }
+};
+
+class GUIInventory: public GUIRect {
+ typedef Common::List<Common::SharedPtr<GUIInventoryItem> > InventoryList;
+ typedef InventoryList::iterator ItemsIterator;
+private:
+ InventoryList _inventoryItems;
+ Common::Point _cellSize;
+ Common::Point _cellCount;
+ bool _visible;
+ bool _scrollable;
+ int _scrollPosition;
+ int _highlightedIndex;
+ int _selectedIndex;
+ M4Engine *_vm;
+public:
+ GUIInventory(View *owner, M4Engine *vm, const Common::Rect &bounds,
+ int horizCells, int vertCells, int cellWidth, int cellHeight, int tag);
+
+ void onRefresh();
+ bool onEvent(M4EventType eventType, int param, int x, int y, GUIObject *&currentItem);
+
+ void add(const char *name, const char *verb, M4Surface *icon, int iconIndex);
+ bool remove(const char *name);
+ int getInsideIndex(int x, int y);
+ int getSelectedIndex() { return _selectedIndex; }
+ const char *getSelectedObjectName();
+ void clearSelected() {
+ _selectedIndex = -1;
+ setHighlight(-1);
+ }
+ const Common::Point &getCellPosition(int index);
+ void setHighlight(int index);
+ bool needLeftButton() { return _scrollPosition != 0; }
+ bool needRightButton() {
+ return (uint)(_inventoryItems.size() - _scrollPosition) > (uint)(_cellCount.x * _cellCount.y);
+ }
+ void setScrollPosition(int value);
+ void scrollLeft() { setScrollPosition(_scrollPosition - 1); }
+ void scrollRight() { setScrollPosition(_scrollPosition + 1); }
+ void setVisible(bool value) { _visible = value; }
+};
+
+class GameInterfaceView: public View {
+ typedef Common::List<Common::SharedPtr<GUIButton> > ButtonList;
+ typedef ButtonList::iterator ButtonsIterator;
+public:
+ SpriteAsset *_sprites;
+ ButtonList _buttons;
+ GUITextField _statusText;
+ GUIInventory _inventory;
+ int _highlightedIndex;
+ bool _selected;
+private:
+ void setHighlightedButton(int index);
+public:
+ GameInterfaceView(M4Engine *vm);
+ ~GameInterfaceView();
+
+ void onRefresh(RectList *rects, M4Surface *destSurface);
+ bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents);
+ void setStatusText(const char *text) { _statusText.setText(text); }
+ void cancelSentence() { setStatusText(NULL); }
+ void inventoryAdd(const char *name, const char *verb, int iconIndex) {
+ _inventory.add(name, verb, _sprites->getFrame(iconIndex), iconIndex);
+ }
+ bool inventoryRemove(const char *name) { return _inventory.remove(name); }
+};
+
+}
+
+#endif
diff --git a/engines/m4/mads_anim.cpp b/engines/m4/mads_anim.cpp
new file mode 100644
index 0000000000..4d481f9397
--- /dev/null
+++ b/engines/m4/mads_anim.cpp
@@ -0,0 +1,705 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/mads_anim.h"
+#include "m4/m4.h"
+
+namespace M4 {
+
+#define TEXTVIEW_LINE_SPACING 2
+#define TEXT_ANIMATION_DELAY 100
+#define TV_NUM_FADE_STEPS 40
+#define TV_FADE_DELAY_MILLI 50
+
+TextviewView::TextviewView(M4Engine *vm):
+ View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())),
+ _bgSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT),
+ _textSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT + vm->_font->getHeight() +
+ TEXTVIEW_LINE_SPACING) {
+
+ _screenType = VIEWID_TEXTVIEW;
+ _screenFlags.layer = LAYER_BACKGROUND;
+ _screenFlags.visible = true;
+ _screenFlags.get = SCREVENT_ALL;
+ _callback = NULL;
+ _script = NULL;
+ _spareScreen = NULL;
+ _bgCurrent = NULL;
+ _bgSpare = NULL;
+ reset();
+
+ // Set up system palette colors and the two colors for text display
+ _vm->_palette->setMadsSystemPalette();
+ RGB8 palData[3];
+ palData[0].r = palData[0].g = palData[0].b = 0;
+ palData[1].r = 0; palData[1].g = palData[1].b = 252;
+ palData[2].r = 0; palData[2].g = palData[2].b = 180;
+ _vm->_palette->setPalette(&palData[0], 4, 3);
+ _vm->_palette->blockRange(4, 3);
+
+ _vm->_font->setColors(5, 6, 4);
+
+ empty();
+ _bgSurface.empty();
+ _textSurface.empty();
+
+ int y = (height() - MADS_SURFACE_HEIGHT) / 2;
+ setColor(2);
+ hLine(0, width() - 1, y - 2);
+ hLine(0, width() - 1, height() - y + 1);
+}
+
+TextviewView::~TextviewView() {
+ if (_script)
+ _vm->res()->toss(_resourceName);
+ if (_spareScreen)
+ delete _spareScreen;
+ if (_bgCurrent)
+ delete _bgCurrent;
+ if (_bgSpare)
+ delete _bgSpare;
+}
+
+void TextviewView::reset() {
+ _bgSurface.empty();
+ _textSurface.empty();
+ _animating = false;
+ _panX = 0;
+ _panY = 0;
+ _panSpeed = 0;
+ _soundDriverLoaded = false;
+ Common::set_to(&_spareScreens[0], &_spareScreens[10], 0);
+ _scrollCount = 0;
+ _lineY = -1;
+ _scrollTimeout = 0;
+ _panCountdown = 0;
+ _processEvents = true;
+}
+
+void TextviewView::setScript(const char *resourceName, TextviewCallback callback) {
+ _callback = callback;
+ if (_script)
+ _vm->res()->toss(_resourceName);
+ if (_spareScreen) {
+ delete _spareScreen;
+ _spareScreen = NULL;
+ }
+
+ reset();
+
+ strncpy(_resourceName, resourceName, 15);
+ _resourceName[15] = '\0';
+ if (!strchr(_resourceName, '.'))
+ strcat(_resourceName, ".txr");
+
+ _script = _vm->res()->get(_resourceName);
+
+ processLines();
+}
+
+bool TextviewView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ if (!_processEvents)
+ return false;
+
+ // Wait for the Escape key or a mouse press
+ if (((eventType == KEVENT_KEY) && (param == Common::KEYCODE_ESCAPE)) ||
+ (eventType == MEVENT_LEFT_RELEASE) || (eventType == MEVENT_RIGHT_RELEASE)) {
+ scriptDone();
+ captureEvents = false;
+ return true;
+ }
+
+ return false;
+}
+
+void TextviewView::updateState() {
+ if (!_animating)
+ return;
+
+ // Only update state if wait period has expired
+ uint32 currTime = g_system->getMillis();
+
+ // If a screen transition is in progress and it's time for another column, handle it
+ if (_spareScreen) {
+ byte *srcP = _spareScreen->getBasePtr(_translationX, 0);
+ byte *destP = _bgSurface.getBasePtr(_translationX, 0);
+
+ for (int y = 0; y < _bgSurface.height(); ++y, srcP += _spareScreen->width(),
+ destP += _bgSurface.width()) {
+ *destP = *srcP;
+ }
+
+ if (++_translationX >= _bgSurface.width()) {
+ // Surface transition is complete
+ delete _spareScreen;
+ _spareScreen = NULL;
+
+ _vm->_palette->deleteRange(_bgCurrent);
+ delete _bgCurrent;
+ _bgCurrent = _bgSpare;
+ _bgSpare = NULL;
+ }
+ }
+
+ // Make sure it's time for an update
+ if (currTime < _scrollTimeout)
+ return;
+ _scrollTimeout = g_system->getMillis() + TEXT_ANIMATION_DELAY;
+
+ // If any panning values are set, pan the background surface
+ if ((_panX != 0) || (_panY != 0)) {
+ if (_panCountdown > 0) {
+ --_panCountdown;
+ return;
+ }
+
+ // Handle horizontal panning
+ if (_panX != 0) {
+ byte *lineTemp = new byte[_panX];
+ for (int y = 0; y < _bgSurface.height(); ++y) {
+ byte *pixelsP = _bgSurface.getBasePtr(0, y);
+
+ // Copy the first X pixels into temp buffer, move the rest of the line
+ // to the start of the line, and then move temp buffer pixels to end of line
+ Common::copy(pixelsP, pixelsP + _panX, lineTemp);
+ Common::copy(pixelsP + _panX, pixelsP + _bgSurface.width(), pixelsP);
+ Common::copy(lineTemp, lineTemp + _panX, pixelsP + _bgSurface.width() - _panX);
+ }
+
+ delete[] lineTemp;
+ }
+
+ // Handle vertical panning
+ if (_panY != 0) {
+ // Store the bottom Y lines into a temp buffer, move the rest of the lines down,
+ // and then copy the stored lines back to the top of the screen
+ byte *linesTemp = new byte[_panY * _bgSurface.width()];
+ byte *pixelsP = _bgSurface.getBasePtr(0, _bgSurface.height() - _panY);
+ Common::copy(pixelsP, pixelsP + _bgSurface.width() * _panY, linesTemp);
+
+ for (int y = _bgSurface.height() - 1; y >= _panY; --y) {
+ byte *destP = _bgSurface.getBasePtr(0, y);
+ byte *srcP = _bgSurface.getBasePtr(0, y - _panY);
+ Common::copy(srcP, srcP + _bgSurface.width(), destP);
+ }
+
+ Common::copy(linesTemp, linesTemp + _panY * _bgSurface.width(), (byte *)_bgSurface.pixels);
+ delete[] linesTemp;
+ }
+ }
+
+ // Scroll the text surface up by one row
+ byte *pixelsP = (byte *)_textSurface.pixels;
+ Common::copy(pixelsP + width(), pixelsP + _textSurface.width() * _textSurface.height(), pixelsP);
+ pixelsP = _textSurface.getBasePtr(0, _textSurface.height() - 1);
+ Common::set_to(pixelsP, pixelsP + _textSurface.width(), _vm->_palette->BLACK);
+
+ if (_scrollCount > 0) {
+ // Handling final scrolling of text off of screen
+ if (--_scrollCount == 0) {
+ scriptDone();
+ return;
+ }
+ } else {
+ // Handling a text row
+ if (++_lineY == (_vm->_font->getHeight() + TEXTVIEW_LINE_SPACING))
+ processLines();
+ }
+
+ // Refresh the view
+ int yp = (height() - _bgSurface.height()) / 2;
+ _bgSurface.copyTo(this, 0, yp);
+ _textSurface.copyTo(this, Common::Rect(0, 0, _textSurface.width(), _bgSurface.height()),
+ 0, yp, _vm->_palette->BLACK);
+}
+
+void TextviewView::scriptDone() {
+ TextviewCallback fn = _callback;
+ M4Engine *vm = _vm;
+
+ // Remove this view from manager and destroy it
+ _vm->_viewManager->deleteView(this);
+
+ if (fn)
+ fn(vm);
+}
+
+void TextviewView::processLines() {
+ if (_script->eos())
+ error("Attempted to read past end of response file");
+
+ while (!_script->eos()) {
+ _script->readLine(_currentLine, 79);
+
+ // Commented out line, so go loop for another
+ if (_currentLine[0] == '#')
+ continue;
+
+ // Process the line
+ char *cStart = strchr(_currentLine, '[');
+ if (cStart) {
+ while (cStart) {
+ // Loop for possible multiple commands on one line
+ char *cEnd = strchr(_currentLine, ']');
+ if (!cEnd)
+ error("Unterminated command '%s' in response file", _currentLine);
+
+ *cEnd = '\0';
+ processCommand();
+
+ // Copy rest of line (if any) to start of buffer
+ strcpy(_currentLine, cEnd + 1);
+
+ cStart = strchr(_currentLine, '[');
+ }
+
+ if (_currentLine[0]) {
+ processText();
+ break;
+ }
+
+ } else {
+ processText();
+ break;
+ }
+ }
+}
+
+void TextviewView::processCommand() {
+ char commandStr[80];
+ char *paramP;
+ strcpy(commandStr, _currentLine + 1);
+ str_upper(commandStr);
+
+ if (!strncmp(commandStr, "BACKGROUND", 10)) {
+ // Set the background
+ paramP = commandStr + 10;
+ int screenId = getParameter(&paramP);
+ _bgSurface.loadBackground(screenId, &_bgCurrent);
+ _vm->_palette->addRange(_bgCurrent);
+ _bgSurface.translate(_bgCurrent);
+
+ } else if (!strncmp(commandStr, "GO", 2)) {
+ _animating = true;
+
+ // Grab what the final palete will be
+ RGB8 destPalette[256];
+ _vm->_palette->grabPalette(destPalette, 0, 256);
+
+ // Copy the loaded background, if any, to the view surface
+ int yp = (height() - _bgSurface.height()) / 2;
+ _bgSurface.copyTo(this, 0, yp);
+
+ // Handle fade-in
+ _processEvents = false; // stop processing events during fade-in
+ _vm->_palette->fadeIn(TV_NUM_FADE_STEPS, TV_FADE_DELAY_MILLI, destPalette, 256);
+ _processEvents = true;
+
+ } else if (!strncmp(commandStr, "PAN", 3)) {
+ // Set panning values
+ paramP = commandStr + 3;
+ int panX = getParameter(&paramP);
+ int panY = getParameter(&paramP);
+ int panSpeed = getParameter(&paramP);
+
+ if ((panX != 0) || (panY != 0)) {
+ _panX = panX;
+ _panY = panY;
+ _panSpeed = panSpeed;
+ }
+
+ } else if (!strncmp(commandStr, "DRIVER", 6)) {
+ // Set the driver to use
+ // TODO: Handling of the sound drivers
+
+ } else if (!strncmp(commandStr, "SOUND", 5)) {
+ // Set sound number
+ paramP = commandStr + 5;
+ //int soundId = getParameter(&paramP);
+
+ //TODO: Proper handling of the sound drivers/sounds
+ //if (!_soundDriverLoaded)
+ // error("Attempted to set sound without loading any driver\n");
+
+ } else if (!strncmp(commandStr, "COLOR", 5) && ((commandStr[5] == '0') || (commandStr[5] == '1'))) {
+ // Set the text colors
+ int index = commandStr[5] - '0';
+ paramP = commandStr + 6;
+
+ RGB8 palEntry;
+ palEntry.r = getParameter(&paramP) << 2;
+ palEntry.g = getParameter(&paramP) << 2;
+ palEntry.b = getParameter(&paramP) << 2;
+ _vm->_palette->setPalette(&palEntry, 5 + index, 1);
+
+ } else if (!strncmp(commandStr, "SPARE", 5)) {
+ // Sets a secondary background number that can be later switched in with a PAGE command
+ paramP = commandStr + 6;
+ int spareIndex = commandStr[5] - '0';
+ if ((spareIndex >= 0) && (spareIndex <= 9)) {
+ int screenId = getParameter(&paramP);
+
+ _spareScreens[spareIndex] = screenId;
+ }
+
+ } else if (!strncmp(commandStr, "PAGE", 4)) {
+ // Signals to change to a previous specified secondary background
+ paramP = commandStr + 4;
+ int spareIndex = getParameter(&paramP);
+
+ // Only allow background switches if one isn't currently in progress
+ if (!_spareScreen && (_spareScreens[spareIndex] != 0)) {
+ _spareScreen = new M4Surface(width(), MADS_SURFACE_HEIGHT);
+ _spareScreen->loadBackground(_spareScreens[spareIndex], &_bgSpare);
+ _vm->_palette->addRange(_bgSpare);
+ _spareScreen->translate(_bgSpare);
+
+ _translationX = 0;
+ }
+
+ } else {
+ error("Unknown response command: '%s'", commandStr);
+ }
+}
+
+int TextviewView::getParameter(char **paramP) {
+ if ((**paramP != '=') && (**paramP != ','))
+ return 0;
+
+ int result = 0;
+ ++*paramP;
+ while ((**paramP >= '0') && (**paramP <= '9')) {
+ result = result * 10 + (**paramP - '0');
+ ++*paramP;
+ }
+
+ return result;
+}
+
+void TextviewView::processText() {
+ int lineWidth, xStart;
+
+ if (!strcmp(_currentLine, "***")) {
+ // Special signifier for end of script
+ _scrollCount = _vm->_font->getHeight() * 13;
+ _lineY = -1;
+ return;
+ }
+
+ _lineY = 0;
+
+ // Lines are always centered, except if line contains a '@', in which case the
+ // '@' marks the position that must be horizontally centered
+ char *centerP = strchr(_currentLine, '@');
+ if (centerP) {
+ *centerP = '\0';
+ xStart = (width() / 2) - _vm->_font->getWidth(_currentLine);
+
+ // Delete the @ character and shift back the remainder of the string
+ char *p = centerP + 1;
+ if (*p == ' ') ++p;
+ strcpy(centerP, p);
+
+ } else {
+ lineWidth = _vm->_font->getWidth(_currentLine);
+ xStart = (width() - lineWidth) / 2;
+ }
+
+ // Copy the text line onto the bottom of the textSurface surface, which will allow it
+ // to gradually scroll onto the screen
+ int yp = _textSurface.height() - _vm->_font->getHeight() - TEXTVIEW_LINE_SPACING;
+ _textSurface.fillRect(Common::Rect(0, yp, _textSurface.width(), _textSurface.height()),
+ _vm->_palette->BLACK);
+ _vm->_font->writeString(&_textSurface, _currentLine, xStart, yp);
+}
+
+
+//--------------------------------------------------------------------------
+
+AnimviewView::AnimviewView(M4Engine *vm):
+ View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())),
+ _bgSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT) {
+
+ _screenType = VIEWID_ANIMVIEW;
+ _screenFlags.layer = LAYER_BACKGROUND;
+ _screenFlags.visible = true;
+ _screenFlags.get = SCREVENT_ALL;
+ _callback = NULL;
+ _script = NULL;
+ _palData = NULL;
+ _previousUpdate = 0;
+ _transition = kTransitionNone;
+ reset();
+
+ // Set up system palette colors
+ _vm->_palette->setMadsSystemPalette();
+
+ empty();
+ _bgSurface.empty();
+
+ int y = (height() - MADS_SURFACE_HEIGHT) / 2;
+ setColor(2);
+ hLine(0, width() - 1, y - 2);
+ hLine(0, width() - 1, height() - y + 1);
+}
+
+AnimviewView::~AnimviewView() {
+ if (_script)
+ _vm->res()->toss(_resourceName);
+}
+
+void AnimviewView::reset() {
+ _bgSurface.empty();
+ _soundDriverLoaded = false;
+}
+
+void AnimviewView::setScript(const char *resourceName, AnimviewCallback callback) {
+ _callback = callback;
+ if (_script)
+ _vm->res()->toss(_resourceName);
+
+ reset();
+
+ strncpy(_resourceName, resourceName, 15);
+ _resourceName[15] = '\0';
+ if (!strchr(_resourceName, '.'))
+ strcat(_resourceName, ".res");
+
+ _script = _vm->res()->get(_resourceName);
+
+ processLines();
+}
+
+bool AnimviewView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ // Wait for the Escape key or a mouse press
+ if (((eventType == KEVENT_KEY) && (param == Common::KEYCODE_ESCAPE)) ||
+ (eventType == MEVENT_LEFT_RELEASE) || (eventType == MEVENT_RIGHT_RELEASE)) {
+ scriptDone();
+ captureEvents = false;
+ return true;
+ }
+
+ return false;
+}
+
+void AnimviewView::updateState() {
+ char bgFile[10];
+ int bgNumber = 0;
+
+ // Only update state if wait period has expired
+ if (_previousUpdate > 0) {
+ if (g_system->getMillis() - _previousUpdate < 3000) {
+ return;
+ } else {
+ // time for an update
+ _previousUpdate = g_system->getMillis();
+ }
+ } else {
+ _previousUpdate = g_system->getMillis();
+ return;
+ }
+ uint32 currTime = g_system->getMillis();
+
+ strncpy(bgFile, _currentFile, 5);
+ bgFile[0] = bgFile[2];
+ bgFile[1] = bgFile[3];
+ bgFile[2] = bgFile[4];
+ bgFile[3] = '\0';
+ bgNumber = atoi(bgFile);
+ sprintf(bgFile, "rm%i.art", bgNumber);
+
+ // Not all scenes have a background. If there is one, refresh it
+ if (_vm->_resourceManager->resourceExists(bgFile)) {
+ if (_palData) {
+ _vm->_palette->deleteRange(_palData);
+ delete _palData;
+ }
+ _bgSurface.loadBackground(bgNumber, &_palData);
+ _vm->_palette->addRange(_palData);
+ _bgSurface.translate(_palData);
+ }
+
+ // Grab what the final palete will be
+ RGB8 destPalette[256];
+ _vm->_palette->grabPalette(destPalette, 0, 256);
+
+ // Handle scene transition
+ switch (_transition) {
+ case kTransitionNone:
+ // nothing to do
+ break;
+ case kTransitionFadeIn:
+ case kTransitionFadeIn2:
+ _vm->_palette->fadeIn(TV_NUM_FADE_STEPS, TV_FADE_DELAY_MILLI, destPalette, 256);
+ break;
+ case kTransitionBoxInBottomLeft:
+ case kTransitionBoxInBottomRight:
+ case kTransitionBoxInTopLeft:
+ case kTransitionBoxInTopRight:
+ // unused
+ warning("Unsupported box in scene effect");
+ break;
+ case kTransitionPanLeftToRight:
+ // TODO
+ break;
+ case kTransitionPanRightToLeft:
+ // TODO
+ break;
+ case kTransitionCircleIn:
+ // TODO
+ break;
+ default:
+ // nothing to do
+ break;
+ }
+
+ // Refresh the view
+ int yp = (height() - _bgSurface.height()) / 2;
+ _bgSurface.copyTo(this, 0, yp);
+
+ // Read next line
+ processLines();
+}
+
+void AnimviewView::scriptDone() {
+ AnimviewCallback fn = _callback;
+ M4Engine *vm = _vm;
+
+ // Remove this view from manager and destroy it
+ _vm->_viewManager->deleteView(this);
+
+ if (fn)
+ fn(vm);
+}
+
+void AnimviewView::processLines() {
+ if (_script->eos()) {
+ // end of script, end animation
+ scriptDone();
+ return;
+ }
+
+ while (!_script->eos()) {
+ _script->readLine(_currentLine, 79);
+
+ // Process the line
+ char *cStart = strchr(_currentLine, '-');
+ if (cStart) {
+ while (cStart) {
+ // Loop for possible multiple commands on one line
+ char *cEnd = strchr(_currentLine, ' ');
+ if (!cEnd)
+ error("Unterminated command '%s' in response file", _currentLine);
+
+ *cEnd = '\0';
+ processCommand();
+
+ // Copy rest of line (if any) to start of buffer
+ // Don't use strcpy() here, because if the
+ // rest of the line is the longer of the two
+ // strings, the memory areas will overlap.
+ memmove(_currentLine, cEnd + 1, strlen(cEnd + 1) + 1);
+
+ cStart = strchr(_currentLine, '-');
+ }
+
+ if (_currentLine[0]) {
+ sprintf(_currentFile, "%s", _currentLine);
+ //printf("File: %s\n", _currentLine);
+ break;
+ }
+
+ } else {
+ sprintf(_currentFile, "%s", _currentLine);
+ //printf("File: %s\n", _currentLine);
+ break;
+ }
+ }
+}
+
+/*
+Switches are: (taken from the help of the original executable)
+ -b Toggle background load status off/on.
+ -c:char Specify sound card id letter.
+ -g Stay in graphics mode on exit.
+ -h[:ex] Disable EMS/XMS high memory support.
+ -i Switch sound interrupts mode off/on.
+ -j Wait for music to finish at end.
+ -k Keystroke jumps to end instead of abort.
+ -m Operate in non-MADS mode.
+ -o:xxx Specify opening special effect.
+ -p Switch MADS path mode to CONCAT.
+ -r[:abn] Resynch timer (always, beginning, never).
+ -s:file Specify sound file.
+ -u[:...] Use DMA speech [optional: addr,type,irq,drq].
+ -w Toggle white bars off/on.
+ -x Exit immediately after last frame.
+ -y Do not clear screen initially
+ -z Display statistics after run.
+
+ Opening special effects are:
+ 0: no effect
+ 1: fade in
+ 2: fade in (looks to be the same as 1)
+ 3: box in from bottom left (unused)
+ 4: box in from bottom right (unused)
+ 5: box in from top left (unused)
+ 6: box in from top right (unused)
+ 7: pan in from left to right
+ 8: pan in from right to left
+ 9: circle in (new scene appears in a circle that expands)
+
+ Animview is ran like this from the original games:
+ animview.exe @resfilename -c:P,220,20 -u:220,20,07,01 -p -a:mainmenu -p
+
+ Note that the first -p is necessary to watch the animation, otherwise
+ the program just exits
+
+ To watch an animation within the *.res file, just run animview like this:
+ animview.exe -x -r:b -o:2 animfilename -p
+*/
+void AnimviewView::processCommand() {
+ char commandStr[80];
+ strcpy(commandStr, _currentLine + 1);
+ str_upper(commandStr);
+ char *param = commandStr;
+
+ if (!strncmp(commandStr, "X", 1)) {
+ //printf("X ");
+ } else if (!strncmp(commandStr, "W", 1)) {
+ //printf("W ");
+ } else if (!strncmp(commandStr, "R", 1)) {
+ param = param + 2;
+ //printf("R:%s ", param);
+ } else if (!strncmp(commandStr, "O", 1)) {
+ param = param + 2;
+ //printf("O:%i ", atoi(param));
+ _transition = atoi(param);
+ } else {
+ error("Unknown response command: '%s'", commandStr);
+ }
+}
+
+}
diff --git a/engines/m4/mads_anim.h b/engines/m4/mads_anim.h
new file mode 100644
index 0000000000..713e3bbfd5
--- /dev/null
+++ b/engines/m4/mads_anim.h
@@ -0,0 +1,118 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_MADS_ANIM_H
+#define M4_MADS_ANIM_H
+
+#include "m4/viewmgr.h"
+
+namespace M4 {
+
+enum SceneTransition {
+ kTransitionNone = 0,
+ kTransitionFadeIn = 1,
+ kTransitionFadeIn2 = 2,
+ kTransitionBoxInBottomLeft = 3,
+ kTransitionBoxInBottomRight = 4,
+ kTransitionBoxInTopLeft = 5,
+ kTransitionBoxInTopRight = 6,
+ kTransitionPanLeftToRight = 7,
+ kTransitionPanRightToLeft = 8,
+ kTransitionCircleIn = 9
+};
+
+typedef void (*TextviewCallback)(M4Engine *vm);
+
+class TextviewView: public View {
+private:
+ bool _animating;
+
+ char _resourceName[80];
+ Common::SeekableReadStream *_script;
+ uint16 _spareScreens[10];
+ M4Surface *_spareScreen;
+ RGBList *_bgCurrent, *_bgSpare;
+ int _translationX;
+ int _panX, _panY, _panSpeed;
+ int _panCountdown;
+ char _currentLine[80];
+ uint32 _scrollTimeout;
+ int _scrollCount;
+ int _lineY;
+ M4Surface _bgSurface;
+ M4Surface _textSurface;
+ TextviewCallback _callback;
+ bool _soundDriverLoaded;
+ bool _processEvents;
+
+ void reset();
+ void processLines();
+ void processCommand();
+ void processText();
+ int getParameter(char **paramP);
+public:
+ TextviewView(M4Engine *vm);
+ ~TextviewView();
+
+ void setScript(const char *resourceName, TextviewCallback callback);
+ bool isAnimating() { return _animating; }
+ void scriptDone();
+
+ bool onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents);
+ void updateState();
+};
+
+typedef void (*AnimviewCallback)(M4Engine *vm);
+
+class AnimviewView: public View {
+private:
+ char _resourceName[80];
+ Common::SeekableReadStream *_script;
+ uint32 _previousUpdate;
+ char _currentLine[80];
+ char _currentFile[10];
+ M4Surface _bgSurface;
+ AnimviewCallback _callback;
+ bool _soundDriverLoaded;
+ RGBList *_palData;
+ int _transition;
+
+ void reset();
+ void processLines();
+ void processCommand();
+public:
+ AnimviewView(M4Engine *vm);
+ ~AnimviewView();
+
+ void setScript(const char *resourceName, AnimviewCallback callback);
+ void scriptDone();
+
+ bool onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents);
+ void updateState();
+};
+
+}
+
+#endif
diff --git a/engines/m4/mads_menus.cpp b/engines/m4/mads_menus.cpp
new file mode 100644
index 0000000000..2857fc8080
--- /dev/null
+++ b/engines/m4/mads_menus.cpp
@@ -0,0 +1,586 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/mads_menus.h"
+#include "m4/m4.h"
+
+namespace M4 {
+
+#define REX_MENUSCREEN 990
+#define PHANTOM_MENUSCREEN 920
+#define DRAGON_MENUSCREEN 922
+
+static Common::Point rexMenuItemPosList[6] = {
+ Common::Point(12, 68), Common::Point(12, 87), Common::Point(12, 107),
+ Common::Point(184, 75), Common::Point(245, 75), Common::Point(184, 99)
+};
+
+static Common::Point dragonMenuItemPosList[6] = {
+ Common::Point(46, 187), Common::Point(92, 187), Common::Point(138, 187),
+ Common::Point(184, 187), Common::Point(230, 187), Common::Point(276, 187)
+};
+
+#define DRAGON_MENU_BUTTON_W = 45
+#define DRAGON_MENU_BUTTON_H = 11
+
+RexMainMenuView::RexMainMenuView(M4Engine *vm):
+ View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) {
+
+ _screenType = VIEWID_MAINMENU;
+ _screenFlags.get = SCREVENT_ALL;
+
+ _delayTimeout = 0;
+ _menuItem = NULL;
+ _menuItemIndex = 0;
+ _frameIndex = 0;
+ _highlightedIndex = -1;
+ _skipFlag = false;
+
+ // Load the background for the Rex Nebular game
+ _bgSurface = new M4Surface(width(), MADS_SURFACE_HEIGHT);
+ _bgSurface->loadBackground(REX_MENUSCREEN, &_bgPalData);
+ _vm->_palette->addRange(_bgPalData);
+ _bgSurface->translate(_bgPalData);
+
+ int row = (height() - MADS_SURFACE_HEIGHT) / 2;
+ _bgSurface->copyTo(this, 0, row);
+
+ // Add in the bounding lines for the background
+ setColor(2);
+ hLine(0, width() - 1, row - 1);
+ hLine(0, width() - 1, height() - row + 1);
+}
+
+RexMainMenuView::~RexMainMenuView() {
+ if (_menuItem)
+ delete _menuItem;
+
+ _vm->_palette->deleteRange(_bgPalData);
+
+ delete _bgPalData;
+ delete _bgSurface;
+
+ for (uint i = 0; i < _itemPalData.size(); ++i) {
+ _vm->_palette->deleteRange(_itemPalData[i]);
+ delete _itemPalData[i];
+ }
+}
+
+bool RexMainMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ // Handle keypresses - these can be done at any time, even when the menu items are being drawn
+ if (eventType == KEVENT_KEY) {
+ switch (param) {
+ case Common::KEYCODE_ESCAPE:
+ case Common::KEYCODE_F6:
+ handleAction(EXIT);
+ break;
+
+ case Common::KEYCODE_F1:
+ handleAction(START_GAME);
+ break;
+
+ case Common::KEYCODE_F2:
+ handleAction(RESUME_GAME);
+ break;
+
+ case Common::KEYCODE_F3:
+ handleAction(SHOW_INTRO);
+ break;
+
+ case Common::KEYCODE_F4:
+ handleAction(CREDITS);
+ break;
+
+ case Common::KEYCODE_F5:
+ handleAction(QUOTES);
+ break;
+
+ case Common::KEYCODE_s:
+ // Goodness knows why, but Rex has a key to restart the menuitem animations
+
+ // Delete the current menu items
+ if (_menuItem)
+ delete _menuItem;
+
+ _vm->_palette->deleteRange(_bgPalData);
+ delete _bgPalData;
+ for (uint i = 0; i < _itemPalData.size(); ++i) {
+ _vm->_palette->deleteRange(_itemPalData[i]);
+ delete _itemPalData[i];
+ }
+ _itemPalData.clear();
+
+ // Reload the background surface, and restart the animation
+ _bgSurface->loadBackground(REX_MENUSCREEN, &_bgPalData);
+ _vm->_palette->addRange(_bgPalData);
+ _bgSurface->translate(_bgPalData);
+
+ _menuItemIndex = 0;
+ _skipFlag = false;
+ _menuItem = NULL;
+ _vm->_mouse->cursorOff();
+ break;
+
+ default:
+ // Any other key skips the menu animation
+ _skipFlag = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ int row = (height() - MADS_SURFACE_HEIGHT) / 2;
+ int menuIndex;
+
+ switch (eventType) {
+ case MEVENT_LEFT_CLICK:
+ case MEVENT_LEFT_DRAG:
+ if (_vm->_mouse->getCursorOn()) {
+ menuIndex = getHighlightedItem(x, y);
+ if (menuIndex != _highlightedIndex) {
+ _bgSurface->copyTo(this, 0, row);
+
+ _highlightedIndex = menuIndex;
+ if (_highlightedIndex != -1) {
+ M4Sprite *spr = _menuItem->getFrame(_highlightedIndex);
+ const Common::Point &pt = rexMenuItemPosList[_highlightedIndex];
+ spr->copyTo(this, pt.x, row + pt.y, 0);
+ }
+ }
+ } else {
+ // Skip the menu animation
+ _skipFlag = true;
+ }
+ return true;
+
+ case MEVENT_LEFT_RELEASE:
+ if (_highlightedIndex != -1)
+ handleAction((MadsGameAction) _highlightedIndex);
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void RexMainMenuView::updateState() {
+ char resName[20];
+ Common::SeekableReadStream *data;
+ int row = (height() - MADS_SURFACE_HEIGHT) / 2;
+ int itemSize;
+
+ uint32 currTime = g_system->getMillis();
+ if (currTime < _delayTimeout)
+ return;
+ _delayTimeout = currTime + MADS_MENU_ANIM_DELAY;
+
+ // Rex Nebular handling to cycle through the animated display of the menu items
+ if (_menuItemIndex == 7)
+ return;
+
+ // If the user has chosen to skip the menu animation, show the menu immediately
+ if (_skipFlag && !_vm->_mouse->getCursorOn()) {
+ // Clear any pending animation
+ _bgSurface->copyTo(this, 0, row);
+ // Quickly loop through all the menuitems to display each's final frame
+ while (_menuItemIndex < 7) {
+
+ if (_menuItem) {
+ // Draw the final frame of the menuitem
+ M4Sprite *spr = _menuItem->getFrame(0);
+ itemSize = _menuItem->getFrame(0)->height();
+ spr->copyTo(this, rexMenuItemPosList[_menuItemIndex - 1].x,
+ rexMenuItemPosList[_menuItemIndex - 1].y + row + (itemSize / 2) - (spr->height() / 2), 0);
+
+ delete _menuItem;
+ copyTo(_bgSurface, Common::Rect(0, row, width(), row + MADS_SURFACE_HEIGHT), 0, 0);
+ }
+
+ // Get the next sprite set
+ sprintf(resName, "RM%dA%d.SS", REX_MENUSCREEN, ++_menuItemIndex);
+ data = _vm->res()->get(resName);
+ _menuItem = new SpriteAsset(_vm, data, data->size(), resName);
+ _vm->res()->toss(resName);
+
+ // Slot it into available palette space
+ RGBList *palData = _menuItem->getRgbList();
+ _vm->_palette->addRange(palData);
+ _menuItem->translate(palData, true);
+ _itemPalData.push_back(palData);
+ }
+
+ _vm->_mouse->cursorOn();
+ return;
+ }
+
+ if ((_menuItemIndex == 0) || (_frameIndex == 0)) {
+ // Get the next menu item
+ if (_menuItem) {
+ delete _menuItem;
+
+ // Copy over the current display surface area to the background, so the final frame
+ // of the previous menuitem should be kept on the screen
+ copyTo(_bgSurface, Common::Rect(0, row, width(), row + MADS_SURFACE_HEIGHT), 0, 0);
+ }
+
+ // Get the next menuitem resource
+ sprintf(resName, "RM%dA%d.SS", REX_MENUSCREEN, ++_menuItemIndex);
+ data = _vm->res()->get(resName);
+ _menuItem = new SpriteAsset(_vm, data, data->size(), resName);
+ _vm->res()->toss(resName);
+
+ // Slot it into available palette space
+ RGBList *palData = _menuItem->getRgbList();
+ _vm->_palette->addRange(palData);
+ _menuItem->translate(palData, true);
+ _itemPalData.push_back(palData);
+
+ _frameIndex = _menuItem->getCount() - 1;
+
+ // If the final resource is now loaded, which contains the highlighted versions of
+ // each menuitem, then the startup animation is complete
+ if (_menuItemIndex == 7) {
+ _vm->_mouse->cursorOn();
+ return;
+ }
+ } else {
+ --_frameIndex;
+ }
+
+ // Move to the next menuitem frame
+
+ itemSize = _menuItem->getFrame(0)->height();
+
+ _bgSurface->copyTo(this, 0, row);
+ M4Sprite *spr = _menuItem->getFrame(_frameIndex);
+ spr->copyTo(this, rexMenuItemPosList[_menuItemIndex - 1].x, rexMenuItemPosList[_menuItemIndex - 1].y +
+ row + (itemSize / 2) - (spr->height() / 2), 0);
+}
+
+int RexMainMenuView::getHighlightedItem(int x, int y) {
+ y -= (height() - MADS_SURFACE_HEIGHT) / 2;
+
+ for (int index = 0; index < 6; ++index) {
+ const Common::Point &pt = rexMenuItemPosList[index];
+ M4Sprite *spr = _menuItem->getFrame(index);
+
+ if ((x >= pt.x) && (y >= pt.y) && (x < (pt.x + spr->width())) && (y < (pt.y + spr->height())))
+ return index;
+ }
+
+ return -1;
+}
+
+void RexMainMenuView::handleAction(MadsGameAction action) {
+ M4Engine *vm = _vm;
+ vm->_mouse->cursorOff();
+ vm->_viewManager->deleteView(this);
+
+ switch (action) {
+ case START_GAME:
+ case RESUME_GAME:
+ // Load a sample starting scene - note that, currently, calling loadScene automatically
+ // removes this menu screen from being displayed
+ vm->_mouse->cursorOn();
+ vm->_viewManager->addView(vm->_scene);
+ vm->_scene->loadScene(101);
+ return;
+
+ case SHOW_INTRO:
+ vm->_viewManager->showAnimView("@rexopen");
+ break;
+
+ case CREDITS:
+ vm->_viewManager->showTextView("credits");
+ return;
+
+ case QUOTES:
+ vm->_viewManager->showTextView("quotes");
+ return;
+
+ case EXIT:
+ {
+ // When the Exit action is done from the menu, show one of two possible advertisements
+
+ // Activate the scene display with the specified scene
+ bool altAdvert = vm->_random->getRandomNumber(1000) >= 500;
+ vm->_scene->loadScene(altAdvert ? 995 : 996);
+ vm->_viewManager->addView(vm->_scene);
+
+ vm->_viewManager->refreshAll();
+ vm->delay(10000);
+
+ vm->_events->quitFlag = true;
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+//--------------------------------------------------------------------------
+
+MadsMainMenuView::MadsMainMenuView(M4Engine *vm):
+ View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) {
+
+}
+
+bool MadsMainMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ return false;
+}
+
+void MadsMainMenuView::updateState() {
+ // TODO: Implement me
+}
+
+//--------------------------------------------------------------------------
+
+DragonMainMenuView::DragonMainMenuView(M4Engine *vm):
+ View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) {
+
+ _screenType = VIEWID_MAINMENU;
+ _screenFlags.get = SCREVENT_ALL;
+
+ _delayTimeout = 0;
+ _menuItem = NULL;
+ _menuItemIndex = 0;
+ _frameIndex = 0;
+ _highlightedIndex = -1;
+ _skipFlag = false;
+
+ // Load the background for the Dragonsphere game
+ this->loadBackground(942, &_bgPalData);
+ _vm->_palette->addRange(_bgPalData);
+ this->translate(_bgPalData);
+}
+
+DragonMainMenuView::~DragonMainMenuView() {
+ //if (_menuItem)
+ // delete _menuItem;
+
+ _vm->_palette->deleteRange(_bgPalData);
+
+ delete _bgPalData;
+
+ for (uint i = 0; i < _itemPalData.size(); ++i) {
+ _vm->_palette->deleteRange(_itemPalData[i]);
+ delete _itemPalData[i];
+ }
+}
+
+bool DragonMainMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
+ char resName[20];
+ Common::SeekableReadStream *data;
+
+ // Handle keypresses - these can be done at any time, even when the menu items are being drawn
+ if (eventType == KEVENT_KEY) {
+ switch (param) {
+ case Common::KEYCODE_ESCAPE:
+ case Common::KEYCODE_F6:
+ handleAction(EXIT);
+ break;
+
+ case Common::KEYCODE_F1:
+ handleAction(START_GAME);
+ break;
+
+ case Common::KEYCODE_F2:
+ handleAction(RESUME_GAME);
+ break;
+
+ case Common::KEYCODE_F3:
+ handleAction(SHOW_INTRO);
+ break;
+
+ case Common::KEYCODE_F4:
+ handleAction(CREDITS);
+ break;
+
+ default:
+ // Any other key skips the menu animation
+ _skipFlag = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ int menuIndex;
+
+ switch (eventType) {
+ case MEVENT_LEFT_CLICK:
+ case MEVENT_LEFT_DRAG:
+ if (_vm->_mouse->getCursorOn()) {
+ menuIndex = getHighlightedItem(x, y);
+ if (menuIndex != _highlightedIndex) {
+
+ _highlightedIndex = menuIndex;
+ if (_highlightedIndex != -1) {
+ sprintf(resName, "MAIN%d.SS", menuIndex);
+ data = _vm->res()->get(resName);
+ _menuItem = new SpriteAsset(_vm, data, data->size(), resName);
+ _vm->res()->toss(resName);
+
+ M4Sprite *spr = _menuItem->getFrame(1);
+ spr->copyTo(this, spr->xOffset - 25, spr->yOffset - spr->height());
+ }
+ }
+ } else {
+ // Skip the menu animation
+ _skipFlag = true;
+ }
+ return true;
+
+ case MEVENT_LEFT_RELEASE:
+ if (_highlightedIndex != -1)
+ handleAction((MadsGameAction) _highlightedIndex);
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void DragonMainMenuView::updateState() {
+ char resName[20];
+ Common::SeekableReadStream *data;
+ RGBList *palData;
+ M4Sprite *spr;
+
+ if (_menuItemIndex == 6)
+ return;
+
+ while (_menuItemIndex < 6) {
+ sprintf(resName, "MAIN%d.SS", _menuItemIndex);
+ data = _vm->res()->get(resName);
+ _menuItem = new SpriteAsset(_vm, data, data->size(), resName);
+ _vm->res()->toss(resName);
+
+ // Slot it into available palette space
+ palData = _menuItem->getRgbList();
+ _vm->_palette->addRange(palData);
+ _menuItem->translate(palData, true);
+ _itemPalData.push_back(palData);
+
+ spr = _menuItem->getFrame(0);
+ spr->copyTo(this, spr->xOffset - 25, spr->yOffset - spr->height());
+
+ if (_menuItemIndex != 5)
+ delete _menuItem;
+ _menuItemIndex++;
+ }
+
+ // Sphere
+ sprintf(resName, "RM920X0.SS");
+ data = _vm->res()->get(resName);
+ _menuItem = new SpriteAsset(_vm, data, data->size(), resName);
+ _vm->res()->toss(resName);
+
+ // Slot it into available palette space
+ palData = _menuItem->getRgbList();
+ _vm->_palette->addRange(palData);
+ _menuItem->translate(palData, true);
+ _itemPalData.push_back(palData);
+
+ spr = _menuItem->getFrame(0); // empty sphere
+ spr->copyTo(this, spr->xOffset - 75, spr->yOffset - spr->height());
+ spr = _menuItem->getFrame(1); // dragon inside sphere
+ spr->copyTo(this, spr->xOffset - 75, spr->yOffset - spr->height());
+
+ // Dragonsphere letters
+ sprintf(resName, "RM920X3.SS");
+ data = _vm->res()->get(resName);
+ _menuItem = new SpriteAsset(_vm, data, data->size(), resName);
+ _vm->res()->toss(resName);
+
+ // Slot it into available palette space
+ palData = _menuItem->getRgbList();
+ _vm->_palette->addRange(palData);
+ _menuItem->translate(palData, true);
+ _itemPalData.push_back(palData);
+
+ spr = _menuItem->getFrame(1);
+ // FIXME: We assume that the transparent color is the color of the top left pixel
+ byte *transparentColor = (byte *)spr->pixels;
+ spr->copyTo(this, spr->xOffset - 140, spr->yOffset - spr->height(), (int)*transparentColor);
+
+ _vm->_mouse->cursorOn();
+}
+
+int DragonMainMenuView::getHighlightedItem(int x, int y) {
+ y -= (height() - MADS_SURFACE_HEIGHT) / 2;
+
+ for (int index = 0; index < 6; ++index) {
+ const Common::Point &pt = dragonMenuItemPosList[index];
+ M4Sprite *spr = _menuItem->getFrame(0);
+
+ if ((x >= pt.x - 25) && (y >= pt.y - spr->height()) && (x < (pt.x - 25 + spr->width())) && (y < (pt.y))) {
+ printf("x = %d, y = %d, index = %d\n", x, y, index);
+ return index;
+ }
+ }
+
+ return -1;
+}
+
+void DragonMainMenuView::handleAction(MadsGameAction action) {
+ M4Engine *vm = _vm;
+ vm->_mouse->cursorOff();
+ vm->_viewManager->deleteView(this);
+
+ switch (action) {
+ case START_GAME:
+ case RESUME_GAME:
+ // Load a sample starting scene - note that, currently, calling loadScene automatically
+ // removes this menu screen from being displayed
+ vm->_mouse->cursorOn();
+ vm->_viewManager->addView(vm->_scene);
+ vm->_scene->loadScene(101);
+ return;
+
+ case SHOW_INTRO:
+ vm->_viewManager->showAnimView("@dragon");
+ break;
+
+ case CREDITS:
+ vm->_viewManager->showTextView("credits");
+ return;
+
+ case EXIT:
+ vm->_events->quitFlag = true;
+ return;
+ break;
+ default:
+ break;
+ }
+}
+
+}
diff --git a/engines/m4/mads_menus.h b/engines/m4/mads_menus.h
new file mode 100644
index 0000000000..36d1e1e640
--- /dev/null
+++ b/engines/m4/mads_menus.h
@@ -0,0 +1,91 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_MADS_MENUS_H
+#define M4_MADS_MENUS_H
+
+#include "m4/viewmgr.h"
+
+namespace M4 {
+
+#define MADS_MENU_ANIM_DELAY 70
+
+enum MadsGameAction {START_GAME, RESUME_GAME, SHOW_INTRO, CREDITS, QUOTES, EXIT};
+
+class RexMainMenuView: public View {
+private:
+ M4Surface *_bgSurface;
+ RGBList *_bgPalData;
+ int _menuItemIndex;
+ int _frameIndex;
+ bool _skipFlag;
+ SpriteAsset *_menuItem;
+ Common::Array<RGBList *> _itemPalData;
+ uint32 _delayTimeout;
+ int _highlightedIndex;
+
+ int getHighlightedItem(int x, int y);
+ void handleAction(MadsGameAction action);
+public:
+ RexMainMenuView(M4Engine *vm);
+ ~RexMainMenuView();
+
+ bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents);
+ void updateState();
+};
+
+class DragonMainMenuView: public View {
+private:
+ //M4Surface *_bgSurface;
+ RGBList *_bgPalData;
+ int _menuItemIndex;
+ int _frameIndex;
+ bool _skipFlag;
+ SpriteAsset *_menuItem;
+ Common::Array<RGBList *> _itemPalData;
+ uint32 _delayTimeout;
+ int _highlightedIndex;
+
+ int getHighlightedItem(int x, int y);
+ void handleAction(MadsGameAction action);
+public:
+ DragonMainMenuView(M4Engine *vm);
+ ~DragonMainMenuView();
+
+ bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents);
+ void updateState();
+};
+
+class MadsMainMenuView: public View {
+public:
+ MadsMainMenuView(M4Engine *vm);
+
+ bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents);
+ void updateState();
+};
+
+}
+
+#endif
diff --git a/engines/m4/midi.cpp b/engines/m4/midi.cpp
new file mode 100644
index 0000000000..51dd8654ae
--- /dev/null
+++ b/engines/m4/midi.cpp
@@ -0,0 +1,359 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// FIXME: This is cribbed together from the SAGA music player. It needs cleanup
+// and testing.
+
+#include "m4/m4.h"
+#include "m4/midi.h"
+#include "common/stream.h"
+
+namespace M4 {
+
+MidiPlayer::MidiPlayer(M4Engine *vm, MidiDriver *driver) : _vm(vm), _midiData(NULL), _driver(driver), _isPlaying(false), _passThrough(false), _isGM(false) {
+ memset(_channel, 0, sizeof(_channel));
+ _masterVolume = 0;
+ _parser = MidiParser::createParser_SMF();
+ _parser->setMidiDriver(this);
+ _parser->setTimerRate(getBaseTempo());
+ open();
+}
+
+MidiPlayer::~MidiPlayer() {
+ _driver->setTimerCallback(NULL, NULL);
+ _parser->setMidiDriver(NULL);
+ stopMusic();
+ close();
+ delete _parser;
+ delete _midiData;
+}
+
+void MidiPlayer::setVolume(int volume) {
+ Common::StackLock lock(_mutex);
+
+ if (volume < 0)
+ volume = 0;
+ else if (volume > 255)
+ volume = 255;
+
+ if (_masterVolume == volume)
+ return;
+
+ _masterVolume = volume;
+
+ for (int i = 0; i < 16; ++i) {
+ if (_channel[i]) {
+ _channel[i]->volume(_channelVolume[i] * _masterVolume / 255);
+ }
+ }
+}
+
+int MidiPlayer::open() {
+ // Don't ever call open without first setting the output driver!
+ if (!_driver)
+ return 255;
+
+ int ret = _driver->open();
+ if (ret)
+ return ret;
+
+ _driver->setTimerCallback(this, &onTimer);
+ return 0;
+}
+
+void MidiPlayer::close() {
+ stopMusic();
+ if (_driver)
+ _driver->close();
+ _driver = 0;
+}
+
+void MidiPlayer::send(uint32 b) {
+ if (_passThrough) {
+ _driver->send(b);
+ return;
+ }
+
+ byte channel = (byte)(b & 0x0F);
+ if ((b & 0xFFF0) == 0x07B0) {
+ // Adjust volume changes by master volume
+ byte volume = (byte)((b >> 16) & 0x7F);
+ _channelVolume[channel] = volume;
+ volume = volume * _masterVolume / 255;
+ b = (b & 0xFF00FFFF) | (volume << 16);
+ } else if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) {
+ b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
+ }
+ else if ((b & 0xFFF0) == 0x007BB0) {
+ //Only respond to All Notes Off if this channel
+ //has currently been allocated
+ if (_channel[b & 0x0F])
+ return;
+ }
+
+ if (!_channel[channel])
+ _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
+
+ if (_channel[channel])
+ _channel[channel]->send(b);
+}
+
+void MidiPlayer::metaEvent(byte type, byte *data, uint16 length) {
+ switch (type) {
+ case 0x2F:
+ // End of track. (Not called when auto-looping.)
+ stopMusic();
+ break;
+ case 0x51:
+ // Set tempo. Handled by the standard MIDI parser already.
+ break;
+ default:
+ warning("Unhandled meta event: %02x", type);
+ break;
+ }
+}
+
+void MidiPlayer::onTimer(void *refCon) {
+ MidiPlayer *midi = (MidiPlayer *)refCon;
+ Common::StackLock lock(midi->_mutex);
+
+ if (midi->_isPlaying)
+ midi->_parser->onTimer();
+}
+
+void MidiPlayer::playMusic(const char *name, int32 vol, bool loop, int32 trigger, int32 scene) {
+ stopMusic();
+
+ char fullname[144];
+ _vm->res()->changeExtension(fullname, name, "HMP");
+
+ Common::SeekableReadStream *midiFile = _vm->res()->get(fullname);
+ byte *hmpData = new byte[midiFile->size()];
+ uint32 smfSize;
+
+ midiFile->read(hmpData, midiFile->size());
+ _midiData = convertHMPtoSMF(hmpData, midiFile->size(), smfSize);
+ delete[] hmpData;
+ _vm->res()->toss(fullname);
+ _vm->res()->purge();
+
+ if (_midiData) {
+ /*
+ FILE *out = fopen("music.mid", "wb");
+ fwrite(_midiData, smfSize, 1, out);
+ fclose(out);
+ */
+ _parser->loadMusic(_midiData, smfSize);
+ _parser->property(MidiParser::mpAutoLoop, loop);
+ }
+
+ setVolume(255);
+
+ _isPlaying = true;
+}
+
+void MidiPlayer::stopMusic() {
+ Common::StackLock lock(_mutex);
+
+ _isPlaying = false;
+ if (_parser) {
+ _parser->unloadMusic();
+ }
+ delete[] _midiData;
+ _midiData = NULL;
+}
+
+// This function will convert HMP music into type 1 SMF, which our SMF parser
+// will be able to handle. It is based on Hans de Goede's HMP 2 MIDI file
+// converter, which in turn is "based on the conversion algorithms found in
+// d1x, d2x-xl and jjffe". Hans's original code is licensed under the LGPL.
+//
+// TODO: It would probably be nicer to write a MIDI parser class to deal with
+// HMP data directly. Though the multi-track nature of HMP makes that tricky.
+
+byte *MidiPlayer::convertHMPtoSMF(byte *data, uint32 inSize, uint32 &outSize) {
+ Common::MemoryReadStream readS(data, inSize);
+ Common::MemoryWriteStreamDynamic writeS;
+
+ byte buf[8];
+
+ readS.read(buf, sizeof(buf));
+ if (memcmp(buf, "HMIMIDIP", 8) != 0) {
+ warning("convertHMPtoSMF: Invalid HMP header");
+ return NULL;
+ }
+
+ // Read the number of tracks. Note that all the tracks are still part
+ // of the same song, just like in type 1 SMF files.
+
+ readS.seek(0x30);
+
+ uint32 numTracks = readS.readUint32LE();
+
+ // The first track starts on offset 0x300. It's currently unknown what
+ // the skipped data is for.
+
+ readS.seek(0x300);
+
+ // For some reason, we skip the first track entirely.
+
+ byte a = readS.readByte();
+ byte b = readS.readByte();
+ byte c = readS.readByte();
+
+ while (a != 0xFF || b != 0x2F || c != 0x00) {
+ a = b;
+ b = c;
+ c = readS.readByte();
+ }
+
+ // The beginning of the MIDI header
+ static const byte midiHeader1[] = { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1 };
+ // The last 2 bytes of the midi header and track 0
+ static const byte midiHeader2[] = { 0, 0xC0, 'M', 'T', 'r', 'k', 0, 0, 0, 0x0B, 0, 0xFF, 0x51, 0x03, 0x18, 0x80, 0, 0, 0xFF, 0x2F, 0 };
+
+
+ // Write the MIDI header
+ writeS.write(midiHeader1, sizeof(midiHeader1));
+
+ // Write the number of tracks
+ writeS.writeUint16BE(numTracks);
+
+ // Write the rest of the MIDI header and track 0.
+ writeS.write(midiHeader2, sizeof(midiHeader2));
+
+ // Read and convert all the tracks
+ for (uint i = 1; i < numTracks; i++) {
+ if (readS.readUint32LE() != i) {
+ warning("convertHMPtoSMF: Invalid HMP track number");
+ delete[] writeS.getData();
+ return NULL;
+ }
+
+ uint32 trackLength = readS.readUint32LE() - 12;
+ readS.readUint32LE(); // Unused?
+
+ // Write the track header
+ writeS.write("MTrk", 4);
+
+ // This is where we will write the length of the track.
+ uint32 trackLengthPos = writeS.pos();
+ writeS.writeUint32LE(0);
+
+ // In the original, this is cleared once at the beginning of
+ // the function, but surely the last command does not carry
+ // over to the next track?
+
+ byte lastCmd = 0;
+
+ // Now we can finally convert the track
+ uint32 endPos = readS.pos() + trackLength;
+ while (readS.pos() < endPos) {
+ // Convert the VLQ
+ byte vlq[4];
+ int j = -1;
+
+ do {
+ j++;
+ vlq[j] = readS.readByte();
+ } while (!(vlq[j] & 0x80));
+
+ for (int k = 0; k <= j; k++) {
+ a = vlq[j - k] & 0x7F;
+ if (k != j)
+ a |= 0x80;
+ writeS.writeByte(a);
+ }
+
+ a = readS.readByte();
+
+ if (a == 0xFF) {
+ // META event
+ b = readS.readByte();
+ c = readS.readByte();
+
+ writeS.writeByte(a);
+ writeS.writeByte(b);
+ writeS.writeByte(c);
+
+ if (c > 0) {
+ byte *metaBuf = new byte[c];
+ readS.read(metaBuf, c);
+ writeS.write(metaBuf, c);
+ delete[] metaBuf;
+ }
+
+ if (b == 0x2F) {
+ if (c != 0x00) {
+ warning("convertHMPtoSMF: End of track with non-zero size");
+ delete[] writeS.getData();
+ return NULL;
+ }
+ break;
+ }
+ } else {
+ if (a != lastCmd)
+ writeS.writeByte(a);
+
+ switch (a & 0xF0) {
+ case 0x80:
+ case 0x90:
+ case 0xA0:
+ case 0xB0:
+ case 0xE0:
+ b = readS.readByte();
+ c = readS.readByte();
+ writeS.writeByte(b);
+ writeS.writeByte(c);
+ break;
+ case 0xC0:
+ case 0xD0:
+ b = readS.readByte();
+ writeS.writeByte(b);
+ break;
+ default:
+ warning("convertHMPtoSMF: Invalid HMP command %02X", a);
+ delete[] writeS.getData();
+ return NULL;
+ }
+
+ lastCmd = a;
+ }
+ }
+
+ if (readS.pos() != endPos) {
+ warning("convertHMPtoSMF: Invalid track length");
+ delete[] writeS.getData();
+ return NULL;
+ }
+
+ WRITE_BE_UINT32(writeS.getData() + trackLengthPos, writeS.pos() - trackLengthPos - 4);
+ }
+
+ outSize = writeS.size();
+ return writeS.getData();
+}
+
+} // End of namespace M4
diff --git a/engines/m4/midi.h b/engines/m4/midi.h
new file mode 100644
index 0000000000..78dacdf77e
--- /dev/null
+++ b/engines/m4/midi.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Music class
+
+#ifndef M4_MIDI_H
+#define M4_MIDI_H
+
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "common/mutex.h"
+
+namespace M4 {
+
+class MidiPlayer : public MidiDriver {
+public:
+ MidiPlayer(M4Engine *vm, MidiDriver *driver);
+ ~MidiPlayer();
+
+ bool isPlaying() { return _isPlaying; }
+
+ void setVolume(int volume);
+ int getVolume() { return _masterVolume; }
+
+ void setNativeMT32(bool b) { _nativeMT32 = b; }
+ bool hasNativeMT32() { return _nativeMT32; }
+ void playMusic(const char *name, int32 vol, bool loop, int32 trigger, int32 scene);
+ void stopMusic();
+ void setPassThrough(bool b) { _passThrough = b; }
+
+ void setGM(bool isGM) { _isGM = isGM; }
+
+ //MidiDriver interface implementation
+ int open();
+ void close();
+ void send(uint32 b);
+
+ void metaEvent(byte type, byte *data, uint16 length);
+
+ void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { }
+ uint32 getBaseTempo(void) { return _driver ? _driver->getBaseTempo() : 0; }
+
+ //Channel allocation functions
+ MidiChannel *allocateChannel() { return 0; }
+ MidiChannel *getPercussionChannel() { return 0; }
+
+protected:
+ static void onTimer(void *data);
+
+ M4Engine *_vm;
+ byte *_midiData;
+
+ MidiChannel *_channel[16];
+ MidiDriver *_driver;
+ MidiParser *_parser;
+ byte _channelVolume[16];
+ bool _nativeMT32;
+ bool _isGM;
+ bool _passThrough;
+
+ bool _isPlaying;
+ bool _randomLoop;
+ byte _masterVolume;
+
+ byte *_musicData;
+ uint16 *_buf;
+ size_t _musicDataSize;
+
+ Common::Mutex _mutex;
+
+ byte *convertHMPtoSMF(byte *data, uint32 inSize, uint32 &outSize);
+};
+
+} // End of namespace M4
+
+#endif
+
diff --git a/engines/m4/module.mk b/engines/m4/module.mk
new file mode 100644
index 0000000000..9791f66a58
--- /dev/null
+++ b/engines/m4/module.mk
@@ -0,0 +1,42 @@
+MODULE := engines/m4
+
+MODULE_OBJS = \
+ actor.o \
+ animation.o \
+ assets.o \
+ compression.o \
+ console.o \
+ converse.o \
+ detection.o \
+ events.o \
+ font.o \
+ globals.o \
+ graphics.o \
+ gui.o \
+ hotspot.o \
+ m4.o \
+ m4_menus.o \
+ m4_views.o \
+ mads_anim.o \
+ mads_menus.o \
+ midi.o \
+ rails.o \
+ resource.o \
+ saveload.o \
+ scene.o \
+ script.o \
+ sound.o \
+ sprite.o \
+ viewmgr.o \
+ woodscript.o \
+ ws_machine.o \
+ ws_sequence.o
+
+
+# This module can be built as a plugin
+ifdef BUILD_PLUGINS
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/m4/rails.cpp b/engines/m4/rails.cpp
new file mode 100644
index 0000000000..d5139cc3c4
--- /dev/null
+++ b/engines/m4/rails.cpp
@@ -0,0 +1,358 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/*
+ TODO:
+ - rewrite functions (GetShortestPath etc.)
+*/
+
+#include "graphics/primitives.h"
+#include "common/list.h"
+#include "common/rect.h"
+#include "common/util.h"
+
+#include "m4/rails.h"
+#include "m4/m4.h"
+
+namespace M4 {
+
+#define TOP_EDGE 1 << 0
+#define LEFT_EDGE 1 << 1
+#define BOTTOM_EDGE 1 << 2
+#define RIGHT_EDGE 1 << 3
+
+
+Rails::Rails() {
+}
+
+
+Rails::~Rails() {
+ clearRails();
+}
+
+
+void Rails::clearRails() {
+ uint32 i;
+ Common::List<NoWalkRect *>::iterator j;
+ RailNode *tempNode;
+
+ for (i = 0; i < _nodes.size(); i++) {
+ tempNode = _nodes[i];
+ _nodes.remove_at(i);
+ delete tempNode;
+ }
+
+ for (i = 0; i < _edges.size(); i++) {
+ _edges.remove_at(i);
+ }
+
+ for (j = _noWalkRects.begin(); j != _noWalkRects.end(); ++j)
+ delete (*j);
+ _noWalkRects.clear();
+}
+
+static void checkPoint(int x, int y, int color, void *data) {
+ IsWalkableData *isWalkableData = (IsWalkableData*)data;
+ if (!isWalkableData->result)
+ return;
+ else {
+ M4Surface *codes = isWalkableData->codes;
+ if (x >= 0 && x < codes->w && y >= 0 && y < codes->h) {
+ isWalkableData->result = !((*((uint8*)codes->getBasePtr(x, y))) & 0x10);
+ } else {
+ isWalkableData->result = false;
+ }
+ }
+}
+
+bool Rails::isLineWalkable(int x0, int y0, int x1, int y1) {
+ IsWalkableData isWalkableData;
+ isWalkableData.codes = _walkCodes;
+ isWalkableData.result = true;
+ Graphics::drawLine(x0, y0, x1, y1, 0, &checkPoint, &isWalkableData);
+ return isWalkableData.result;
+}
+
+// helper function
+uint8 getEndCode(int32 x, int32 y, Common::Rect rect) {
+ uint8 endCode = 0;
+ endCode = (x < rect.left) ? LEFT_EDGE : endCode;
+ endCode = (x > rect.right) ? RIGHT_EDGE : endCode;
+ endCode = (y < rect.top) ? endCode | TOP_EDGE : endCode;
+ endCode = (y > rect.bottom) ? endCode | BOTTOM_EDGE : endCode;
+ return endCode;
+}
+
+bool Rails::lineCrossesRect(int32 x1, int32 y1, int32 x2, int32 y2, Common::Rect rect) {
+ int32 mX, mY;
+ int32 pX1 = x1, pX2 = x2, pY1 = y1, pY2 = y2;
+ uint8 endCode1, endCode2, midCode;
+
+ if (rect.left > rect.right || rect.top > rect.bottom)
+ return false;
+
+ // Cohen-Sutherland line clipping algorithm
+
+ endCode1 = getEndCode(pX1, pY1, rect);
+ endCode2 = getEndCode(pX2, pY2, rect);
+
+ if (!endCode1 || !endCode2) // if both endcodes are zero
+ return true; // point is inside the rectangle, therefore the line intersects
+
+ while (true) {
+ if (endCode1 & endCode2) // if both endcodes have a common bitset
+ return false; // line is completely off one edge
+
+ // calculate midpoint
+ mX = (pX1 + pX2)>>1;
+ mY = (pY1 + pY2)>>1;
+
+ // avoid round-off error: make sure that the midpoint isn't the same as one of the
+ // two endpoints
+ if (((mX == pX1) && (mY == pY1)) || ((mX == pX2) && (mY == pY2)))
+ return false;
+
+ midCode = getEndCode(mX, mY, rect);
+
+ if (!midCode) {
+ return true;
+ } else if (midCode & endCode1) {
+ // the midCode and an end point form a line segment completely off one edge, so
+ // remove that half of the line segment
+ pX1 = mX;
+ pY1 = mY;
+ endCode1 = midCode;
+ } else {
+ pX2 = mX;
+ pY2 = mY;
+ endCode2 = midCode;
+ }
+ }
+}
+
+
+bool Rails::linePassesThroughRect(int32 x1, int32 y1, int32 x2, int32 y2) {
+ if (_noWalkRects.empty())
+ return false;
+
+ bool intersected = false;
+ Common::List<NoWalkRect *>::iterator i;
+
+ for (i = _noWalkRects.begin(); i != _noWalkRects.end(); ++i) {
+ intersected = lineCrossesRect(x1, y1, x2, y2, Common::Rect((*i)->x1, (*i)->y1, (*i)->x2, (*i)->y2));
+ if (intersected)
+ break;
+ }
+
+ return intersected;
+}
+
+long SqrtF16(long n) {
+ uint32 r = 0, s;
+ uint32 v = (uint32)n;
+
+ for (int i = 15; i <= 0; i--) {
+ s = r + (1L << i * 2);
+ r >>= 1;
+ if (s <= v) {
+ v -= s;
+ r |= (1L << i * 2);
+ }
+ }
+
+ return (long)r;
+}
+
+void Rails::createEdge(int32 node1, int32 node2) {
+ uint32 index;
+ int32 x1, y1, x2, y2;
+ bool valid, finished;
+ long deltaX, deltaY, distance;
+ uint8 *walkCodePtr;
+
+ if ((node1 < 0) || (node1 >= MAXRAILNODES) || (node2 < 0) || (node2 >= MAXRAILNODES))
+ return;
+
+ if (node1 == node2)
+ return;
+
+ if (node2 < node1)
+ SWAP(node1, node2); // ensure node1 < node2
+
+ // Find the table entry i.e. tableWidth * node1 + node2 and then subtract
+ // n(n+1)/2, since only the upper triangle of the table is stored
+ index = (MAXRAILNODES-1) * node1 + node2 - 1 - (node1*(node1+1)>>1);
+ if (index > _edges.size() - 1)
+ _edges.resize(index + 1);
+ _edges.insert_at(index, 0);
+ valid = true;
+ walkCodePtr = NULL;
+ finished = false;
+
+ if (_nodes.size() <= (uint32)node1 || _nodes.size() <= (uint32)node2)
+ return;
+
+ x1 = _nodes[node1]->x;
+ y1 = _nodes[node1]->y;
+ x2 = _nodes[node2]->x;
+ y2 = _nodes[node2]->y;
+
+ // Make sure that the algorithm is symmetric
+ if (x2 < x1) {
+ SWAP(x1, x2);
+ SWAP(y1, y2);
+ }
+
+ valid = isLineWalkable(_nodes[node1]->x, _nodes[node1]->y,
+ _nodes[node2]->x, _nodes[node2]->y);
+ printf("test code says: %d\n", valid);
+
+ // Check if the line passes through a forbidden rectangle
+ if (valid) {
+ if (linePassesThroughRect(x1, y1, x2, y2)) {
+ valid = false;
+ }
+ }
+
+ if (valid) {
+ deltaX = ABS(((long)(x2 - x1)) << 16);
+ deltaY = ABS(((long)(y2 - y1)) << 16);
+ if ((deltaX >= 0x800000) || (deltaY >= 0x800000)) {
+ deltaX >>= 16;
+ deltaY >>= 16;
+ distance = (long)(SqrtF16(deltaX * deltaX + deltaY * deltaY) << 16);
+ } else {
+ distance = SqrtF16(FixedMul(deltaX, deltaX) + FixedMul(deltaY, deltaY)) << 8;
+ }
+ _edges.insert_at(index, (int16*)(distance >> 16));
+ }
+
+ printf("node1 = %d, node2 = %d, valid = %d\n", node1, node2, valid);
+
+}
+
+
+void Rails::restoreNodeEdges(int32 nodeID) {
+ for (int32 i = 0; i < MAXRAILNODES; i++) {
+ createEdge(i, nodeID);
+ }
+}
+
+void Rails::restoreEdgeList() {
+ int32 j;
+ for (int32 i = 0; i < MAXRAILNODES; i++) {
+ for (j = i + 1; j < MAXRAILNODES; j++) {
+ createEdge(i, j);
+ }
+ }
+}
+
+int32 Rails::addRailNode(int32 x, int32 y, bool restoreEdges) {
+ uint32 i = _nodes.size();
+ if (i >= MAXRAILNODES)
+ return -1;
+
+ RailNode *newNode = new RailNode();
+ newNode->nodeID = i;
+ newNode->x = x;
+ newNode->y = y;
+ _nodes.insert_at(i, newNode);
+ if (restoreEdges) {
+ for (uint32 j=0; j<_nodes.size(); j++)
+ createEdge(i, j);
+ }
+ return i;
+}
+
+bool Rails::removeRailNode(int32 nodeID, bool restoreEdges) {
+ if (nodeID < 0 || nodeID >= MAXRAILNODES)
+ return false;
+
+ if (_nodes.empty() || _edges.empty())
+ return false;
+
+ RailNode *tempNode = _nodes[nodeID];
+ _nodes.remove_at(nodeID);
+ delete tempNode;
+
+ if (restoreEdges) {
+ restoreNodeEdges(nodeID);
+ }
+ return true;
+}
+
+int16 Rails::getEdgeLength(int32 node1, int32 node2) {
+ int32 index;
+ if (_edges.empty() || node1 == node2)
+ return 0;
+ if (node2 < node1)
+ SWAP(node1, node2);
+ // Find the table entry i.e. tableWidth * node1 + node2 and then subtract
+ // n(n+1)/2, since only the upper triangle of the table is stored
+ index = (MAXRAILNODES-1)*node1 + node2 - 1 - (node1*(node1+1)>>1);
+ return *_edges[index];
+}
+
+void Rails::disposePath(RailNode *pathStart) {
+ RailNode *tempNode = pathStart;
+ while (tempNode) {
+ pathStart = pathStart->shortPath;
+ delete tempNode;
+ tempNode = pathStart;
+ }
+}
+
+static RailNode* duplicatePath(RailNode *pathStart) {
+ RailNode *newNode = NULL;
+ RailNode *firstNode = NULL;
+ RailNode *prevNode = NULL;
+ // A valid path is assumed
+ RailNode *pathNode = pathStart;
+
+ while (pathNode) {
+ newNode = new RailNode();
+ newNode->x = pathNode->x;
+ newNode->y = pathNode->y;
+ newNode->shortPath = NULL;
+
+ if (!firstNode)
+ firstNode = newNode;
+ else
+ prevNode->shortPath = newNode;
+
+ prevNode = newNode;
+ // Get the next node
+ pathNode = pathNode->shortPath;
+ }
+
+ return firstNode;
+}
+
+bool Rails::getShortestPath(int32 origID, int32 destID, RailNode **shortPath) {
+ // TODO
+ return true;
+}
+
+} // End of namespace M4
diff --git a/engines/m4/rails.h b/engines/m4/rails.h
new file mode 100644
index 0000000000..d7320eef4d
--- /dev/null
+++ b/engines/m4/rails.h
@@ -0,0 +1,97 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_RAILS_H
+#define M4_RAILS_H
+
+#include "m4/graphics.h"
+#include "common/list.h"
+#include "common/array.h"
+#include "common/rect.h"
+
+// TODO: This needs cleaning up
+
+namespace M4 {
+
+#define MAXRAILNODES 32
+#define PATH_END 0xffff
+
+struct RailNode {
+ uint8 nodeID;
+ int32 x, y;
+ RailNode *shortPath;
+ int32 pathWeight;
+};
+
+struct NoWalkRect {
+ int32 x1, y1, x2, y2;
+ int32 alternateWalkToNode;
+ int32 walkAroundNode1;
+ int32 walkAroundNode2;
+ int32 walkAroundNode3;
+ int32 walkAroundNode4;
+};
+
+struct PathNode {
+ PathNode *next;
+ int8 nodeID;
+};
+
+struct IsWalkableData {
+ M4Surface *codes;
+ bool result;
+};
+
+class Rails {
+public:
+ Rails();
+ ~Rails();
+
+ void setCodeSurface(M4Surface *surface) { _walkCodes = surface; }
+ void clearRails();
+ int32 addRailNode(int32 x, int32 y, bool restoreEdges);
+
+private:
+ Common::Array<RailNode *> _nodes;
+ Common::Array<int16 *> _edges;
+ Common::List<NoWalkRect *> _noWalkRects;
+ M4Surface *_walkCodes;
+
+ bool lineCrossesRect(int32 x1, int32 y1, int32 x2, int32 y2, Common::Rect rect);
+ bool linePassesThroughRect(int32 x1, int32 y1, int32 x2, int32 y2);
+ void createEdge(int32 node1, int32 node2);
+ bool removeRailNode(int32 nodeID, bool restoreEdges);
+ int16 getEdgeLength(int32 node1, int32 node2);
+
+ void restoreNodeEdges(int32 nodeID);
+ void restoreEdgeList();
+ void disposePath(RailNode *pathStart);
+ bool getShortestPath(int32 origID, int32 destID, RailNode **shortPath);
+ bool isLineWalkable(int x0, int y0, int x1, int y1);
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/resource.cpp b/engines/m4/resource.cpp
new file mode 100644
index 0000000000..57816b6600
--- /dev/null
+++ b/engines/m4/resource.cpp
@@ -0,0 +1,436 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/m4.h"
+#include "m4/resource.h"
+#include "m4/events.h"
+
+namespace M4 {
+
+FileSystem::FileSystem(const char *hashFilename) {
+
+ for (int i = 0; i < 10; i++) {
+ _hagEntries[i].filename[0] = '\0';
+ _hagEntries[i].fileIndex = 0; // Was -1
+ _hagEntries[i].hagFile = 0;
+ }
+
+ Common::File hashFile;
+ uint32 hashSize;
+
+ hashFile.open(hashFilename);
+
+ if (!hashFile.isOpen()) {
+ printf("FileSystem::FileSystem: error opening hash %s\n", hashFilename);
+ }
+
+ hashSize = hashFile.readUint32LE();
+
+ //printf("FileSystem::FileSystem: hashSize = %d\n", hashSize);
+
+ /* load file records and add them to the hash list */
+ for (uint i = 0; i < hashSize; i++) {
+ HashFileEntry entry;
+ hashFile.read(entry.filename, kM4MaxFilenameSize);
+ str_lower(entry.filename);
+ entry.hagfile = hashFile.readByte();
+ hashFile.readByte();
+ entry.offset = hashFile.readUint32LE();
+ entry.size = hashFile.readUint32LE();
+ hashFile.readUint32LE();
+
+ if (entry.filename[0]) {
+ /*
+ printf(" filename: %s\n", entry.filename);
+ printf(" hagfile: %d\n", entry.hagfile);
+ printf(" disks: %d\n", entry.disks);
+ printf(" offset: %08X\n", entry.offset);
+ printf(" size: %d\n", entry.size);
+ printf(" next: %08X\n", entry.next);
+ */
+ _fileEntries[entry.filename] = entry;
+ }
+
+ }
+
+ /* load hagfile records and update the list */
+ while (!hashFile.eof()) {
+ HashHagEntry entry;
+ hashFile.read(entry.filename, kM4MaxFilenameSize);
+ entry.fileIndex = hashFile.readByte();
+ if (hashFile.eof())
+ break;
+
+ changeExtension(_hagEntries[entry.fileIndex].filename, entry.filename, "HAG");
+ _hagEntries[entry.fileIndex].fileIndex = entry.fileIndex;
+
+ _hagEntries[entry.fileIndex].hagFile = new Common::File();
+ _hagEntries[entry.fileIndex].hagFile->open(_hagEntries[entry.fileIndex].filename);
+
+ if (!_hagEntries[entry.fileIndex].hagFile->isOpen()) {
+ printf("FileSystem::FileSystem: error opening hag %s\n", _hagEntries[entry.fileIndex].filename);
+ }
+
+ }
+
+ hashFile.close();
+
+}
+
+FileSystem::~FileSystem() {
+
+ for (int i = 0; i < 10; i++) {
+ if (_hagEntries[i].hagFile)
+ delete _hagEntries[i].hagFile;
+ }
+
+}
+
+Common::SeekableReadStream *FileSystem::loadFile(const char *resourceName, bool preloadFlag) {
+ const HashFileEntry *hfe = getHashFileEntry(resourceName);
+ Common::SeekableReadStream *result = NULL;
+
+ if (hfe) {
+ //printf("FileSystem::loadFile() success opening %s\n", filename);
+ HashHagEntry *hagEntry = &_hagEntries[hfe->hagfile];
+
+ if (preloadFlag) {
+ // Creates a MemoryReadStream object that contains all of the resource in memory
+ hagEntry->hagFile->seek(hfe->offset);
+ result = _hagEntries[hfe->hagfile].hagFile->readStream(hfe->size);
+ }
+ else
+ // Creates a SeekableSubReadStream, which will read the data in from disk as the
+ // caller reads in data
+ result = new Common::SeekableSubReadStream(hagEntry->hagFile, hfe->offset,
+ hfe->offset + hfe->size);
+
+ } else {
+ printf("FileSystem::loadFile() error opening %s\n", resourceName);
+ }
+
+ return result;
+}
+
+const FileSystem::HashFileEntry *FileSystem::getHashFileEntry(const char *filename) {
+ char resourceName[20];
+ strcpy(resourceName, filename);
+ str_lower(resourceName);
+
+ FileHashMap::const_iterator entry = _fileEntries.find(filename);
+ if (entry != _fileEntries.end())
+ return &(entry->_value);
+ else
+ return NULL;
+}
+
+void FileSystem::changeExtension(char *destName, const char *sourceName, const char *extension) {
+ if (sourceName != destName)
+ strcpy(destName, sourceName);
+ char *dot = strrchr(destName, '.');
+ if (dot != NULL)
+ *dot = 0;
+ sprintf(destName, "%s.%s", destName, extension);
+
+ str_upper(destName);
+}
+
+//--------------------------------------------------------------------------
+
+ResourceManager::~ResourceManager() {
+ ResourceIterator i;
+ for (i = _resources.begin(); i != _resources.end(); ++i) {
+ Resource *r = (*i).get();
+ delete r->stream;
+ }
+}
+
+Common::SeekableReadStream *ResourceManager::get(const char *resourceName, bool preloadFlag) {
+ char lowerName[kM4MaxFilenameSize];
+
+ strcpy(lowerName, resourceName);
+ str_lower(lowerName);
+
+ // Check whether the resource is already loaded
+ ResourceIterator i;
+ for (i = _resources.begin(); i != _resources.end(); ++i) {
+ Resource *r = (*i).get();
+ if (!strcmp(r->name, resourceName)) {
+ // Just in case resource was marked to be purged, reactive it again
+ r->flags &= ~kResourcePurge;
+
+ // Return the existing copy of the resource
+ r->stream->seek(0, SEEK_SET);
+ return r->stream;
+ }
+ }
+
+ // the resource wasn't found in the list, load it from disk
+ Resource *newRes = new Resource();
+ strncpy(newRes->name, resourceName, 63);
+ newRes->name[63] = '\0';
+ newRes->flags = 0;
+ newRes->stream = loadResource(resourceName, preloadFlag);
+
+ _resources.push_back(ResourceList::value_type(newRes));
+ return newRes->stream;
+}
+
+void ResourceManager::toss(const char *resourceName) {
+ ResourceIterator i;
+ for (i = _resources.begin(); i != _resources.end(); ++i) {
+ Resource *r = (*i).get();
+
+ if (!strcmp(r->name, resourceName)) {
+ r->flags |= kResourcePurge;
+ //printf("M4ResourceManager::toss: mark resource %s to be purged\n", resourceName);
+ }
+ }
+}
+
+void ResourceManager::purge() {
+ ResourceIterator i = _resources.begin();
+ while (i != _resources.end()) {
+ Resource *r = (*i).get();
+
+ if (r->flags & kResourcePurge) {
+ delete r->stream;
+ i = _resources.erase(i);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void ResourceManager::dump() {
+ _vm->_events->getConsole()->DebugPrintf("Scene resources:\n");
+
+ int index = 0;
+ ResourceIterator i;
+ for (i = _resources.begin(); i != _resources.end(); ++i) {
+ Resource *r = (*i).get();
+
+ if (!(r->flags & kResourcePurge)) {
+ _vm->_events->getConsole()->DebugPrintf(
+ "Resource #%i, name: %s, handle pointer: %p, size: %d, flags: %02X\n",
+ index++, r->name, r->buffer, r->stream->size(), r->flags);
+ }
+ }
+}
+
+//--------------------------------------------------------------------------
+
+const char *madsConcatString = "MADSCONCAT";
+
+ResourceType MADSResourceManager::getResourceType(const char *resourceName) {
+ if (!strncmp(resourceName, "RM", 2)) {
+ // Room resource
+ return RESTYPE_ROOM;
+ } else if (!strncmp(resourceName, "SC", 2)) {
+ // SC resource
+ return RESTYPE_SC;
+ } else if (strstr(resourceName, ".TXT")) {
+ // Text resource
+ return RESTYPE_TEXT;
+ } else if (strstr(resourceName, ".QUO")) {
+ // QUO resource
+ return RESTYPE_QUO;
+ } else if (*resourceName == 'I') {
+ // I resource
+ return RESTYPE_I;
+ } else if (!strncmp(resourceName, "OB", 2)) {
+ // OB resource
+ return RESTYPE_OB;
+ } else if (!strncmp(resourceName, "FONT", 4)) {
+ // FONT resource
+ return RESTYPE_FONT;
+ } else if (!strncmp(resourceName, "SOUND", 5)) {
+ // SOUND resource
+ return RESTYPE_SOUND;
+ } else if (!strncmp(resourceName, "SPCHC", 5)) {
+ // SPEECH resource
+ return RESTYPE_SPEECH;
+ }
+
+ // Check for a known extension
+ const char *extPos = strchr(resourceName, '.');
+ if (extPos) {
+ ++extPos;
+ if (!strcmp(extPos, "FL") || !strcmp(extPos, "LBM") || !strcmp(extPos, "ANM") ||
+ !strcmp(extPos, "AA") || !strcmp(extPos, "SS")) {
+ return RESTYPE_HAS_EXT;
+ }
+ }
+
+ return RESTYPE_NO_EXT;
+}
+
+const char *MADSResourceManager::getResourceFilename(const char *resourceName) {
+ static char outputFilename[64];
+
+ ResourceType resType = getResourceType(resourceName);
+
+ strcpy(outputFilename, "GLOBAL.HAG");
+
+ if ((resType == RESTYPE_ROOM) || (resType == RESTYPE_SC)) {
+ int value = atoi(resourceName + 2);
+ int hagFileNum = (resType == RESTYPE_ROOM) ? value / 100 : value;
+
+ if (hagFileNum > 0)
+ sprintf(outputFilename, "SECTION%d.HAG", hagFileNum);
+ }
+
+ if (resType == RESTYPE_SPEECH)
+ strcpy(outputFilename, "SPEECH.HAG");
+
+ return outputFilename;
+}
+
+Common::SeekableReadStream *MADSResourceManager::loadResource(const char *resourceName, bool loadFlag) {
+ Common::File hagFile;
+ uint32 offset, size;
+
+ // If the first character is a '@' then look for an external file
+
+ if (*resourceName == '@') {
+ ++resourceName;
+
+ hagFile.open(resourceName);
+ if (loadFlag)
+ return hagFile.readStream(hagFile.size());
+ else
+ return new Common::SeekableSubReadStream(&hagFile, 0, hagFile.size());
+ }
+
+ // If the first character is the wildcard (resource indicator), skip over it
+ if (*resourceName == '*')
+ ++resourceName;
+
+ char resName[20];
+ strcpy(resName, resourceName);
+ str_upper(resName);
+
+ hagFile.open(getResourceFilename(resName));
+
+ // Validate hag file header
+ char headerBuffer[16];
+ if ((hagFile.read(headerBuffer, 16) != 16) || (strncmp(headerBuffer, madsConcatString, 10) != 0))
+ error("Invalid HAG file opened");
+
+ int numEntries = hagFile.readUint16LE();
+
+ int resIndex = -1;
+ while (++resIndex < numEntries) {
+ // Read in the details of the next resource
+ char resourceBuffer[14];
+ offset = hagFile.readUint32LE();
+ size = hagFile.readUint32LE();
+ hagFile.read(resourceBuffer, 14);
+
+ if (!strcmp(resName, resourceBuffer))
+ break;
+ }
+
+ if (resIndex == numEntries)
+ error("Invalid resource '%s' specified", resourceName);
+
+ // Get the resource, either loading it in it's entirely or getting a stream reference
+
+ if (loadFlag) {
+ hagFile.seek(offset);
+ return hagFile.readStream(size);
+ } else {
+ return new Common::SeekableSubReadStream(&hagFile, offset, offset + size);
+ }
+}
+
+bool MADSResourceManager::resourceExists(const char *resourceName) {
+ Common::File hagFile;
+ uint32 offset, size;
+
+ // If the first character is the wildcard (resource indicator), skip over it
+ if (*resourceName == '*')
+ ++resourceName;
+
+ char resName[20];
+ strcpy(resName, resourceName);
+ str_upper(resName);
+
+ hagFile.open(getResourceFilename(resName));
+
+ // Validate hag file header
+ char headerBuffer[16];
+ if ((hagFile.read(headerBuffer, 16) != 16) || (strncmp(headerBuffer, madsConcatString, 10) != 0))
+ error("Invalid HAG file opened");
+
+ int numEntries = hagFile.readUint16LE();
+
+ int resIndex = -1;
+ while (++resIndex < numEntries) {
+ // Read in the details of the next resource
+ char resourceBuffer[14];
+ offset = hagFile.readUint32LE();
+ size = hagFile.readUint32LE();
+ hagFile.read(resourceBuffer, 14);
+
+ if (!strcmp(resName, resourceBuffer))
+ break;
+ }
+
+ if (resIndex == numEntries)
+ return false;
+ else
+ return true;
+}
+
+//--------------------------------------------------------------------------
+
+M4ResourceManager::M4ResourceManager(M4Engine *vm): ResourceManager(vm) {
+ _hfs = new FileSystem(_vm->getGameFile(kFileTypeHash));
+}
+
+M4ResourceManager::~M4ResourceManager() {
+}
+
+Common::SeekableReadStream *M4ResourceManager::loadResource(const char *resourceName, bool preloadFlag) {
+ //printf("M4ResourceManager::loadResource() loading resource %s\n", resourceName);
+ Common::SeekableReadStream* result = NULL;
+ if (_hfs) {
+ // actually load the resource
+ result = _hfs->loadFile(resourceName, preloadFlag);
+ if (!result) {
+ error("M4ResourceManager::loadResource() Resource %s not found", resourceName);
+ }
+ } else {
+ error("M4ResourceManager::loadResource() No FileSystem attached");
+ }
+ return result;
+}
+
+bool M4ResourceManager::resourceExists(const char *resourceName) {
+ return (_hfs->getHashFileEntry(resourceName) != NULL);
+}
+
+} // End of namespace M4
diff --git a/engines/m4/resource.h b/engines/m4/resource.h
new file mode 100644
index 0000000000..dc099f2304
--- /dev/null
+++ b/engines/m4/resource.h
@@ -0,0 +1,141 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_RESOURCE_H
+#define M4_RESOURCE_H
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/endian.h"
+#include "common/file.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/list.h"
+#include "common/ptr.h"
+
+/*
+ TODO:
+ - change array to HashMap if it turns out to be slow
+*/
+
+namespace M4 {
+
+#define MAX_RESOURCES 128
+#define kM4MaxFilenameSize 33
+
+enum {
+ kResourcePurge = 1 << 1
+};
+
+
+class FileSystem {
+public:
+ struct HashHagEntry {
+ char filename[kM4MaxFilenameSize];
+ byte fileIndex;
+ Common::File *hagFile;
+ };
+
+ struct HashFileEntry {
+ char filename[kM4MaxFilenameSize];
+ byte hagfile;
+ uint32 offset, size;
+ };
+
+ FileSystem(const char *hashFilename);
+ ~FileSystem();
+
+ Common::SeekableReadStream *loadFile(const char *resourceName, bool preloadFlag);
+ static void changeExtension(char *destName, const char *sourceName, const char *extension);
+ const HashFileEntry *getHashFileEntry(const char *filename);
+
+private:
+ typedef Common::HashMap<Common::String,HashFileEntry,Common::IgnoreCase_Hash,Common::IgnoreCase_EqualTo> FileHashMap;
+
+ HashHagEntry _hagEntries[10]; // GLOBAL.HAG and SECTION1.HAG to SECTION9.HAG
+ FileHashMap _fileEntries;
+};
+
+struct Resource {
+ char name[64];
+ Common::SeekableReadStream *stream;
+ uint8 *buffer;
+ uint8 flags;
+};
+
+class ResourceManager {
+protected:
+ typedef Common::List<Common::SharedPtr<Resource> > ResourceList;
+ typedef ResourceList::iterator ResourceIterator;
+ ResourceList _resources;
+ M4Engine *_vm;
+
+ virtual Common::SeekableReadStream *loadResource(const char *resourceName, bool loadFlag) = 0;
+public:
+ ResourceManager(M4Engine *vm): _vm(vm) {};
+ virtual ~ResourceManager();
+
+ Common::SeekableReadStream *get(const char *resourceName, bool loadFlag = true);
+ void toss(const char *resourceName);
+ void purge();
+ void dump();
+ virtual bool resourceExists(const char *resourceName) = 0;
+
+ Common::SeekableReadStream *openFile(const char *resourceName) { return get(resourceName, false); }
+ void changeExtension(char *destName, const char *sourceName, const char *extension) {
+ FileSystem::changeExtension(destName, sourceName, extension);
+ }
+};
+
+enum ResourceType {RESTYPE_ROOM, RESTYPE_SC, RESTYPE_TEXT, RESTYPE_QUO, RESTYPE_I,
+ RESTYPE_OB, RESTYPE_FONT, RESTYPE_SOUND, RESTYPE_SPEECH, RESTYPE_HAS_EXT, RESTYPE_NO_EXT};
+
+class MADSResourceManager: public ResourceManager {
+private:
+ ResourceType getResourceType(const char *resourceName);
+ const char *getResourceFilename(const char *resourceName);
+protected:
+ Common::SeekableReadStream *loadResource(const char *resourceName, bool loadFlag);
+public:
+ MADSResourceManager(M4Engine *vm): ResourceManager(vm) {};
+ bool resourceExists(const char *resourceName);
+};
+
+class M4ResourceManager: public ResourceManager {
+protected:
+ Common::SeekableReadStream *loadResource(const char *resourceName, bool loadFlag);
+public:
+ M4ResourceManager(M4Engine *vm);
+ ~M4ResourceManager();
+ bool resourceExists(const char *resourceName);
+
+private:
+ FileSystem *_hfs;
+};
+
+} // End of namespace M4
+
+
+#endif
diff --git a/engines/m4/saveload.cpp b/engines/m4/saveload.cpp
new file mode 100644
index 0000000000..b46783ef4d
--- /dev/null
+++ b/engines/m4/saveload.cpp
@@ -0,0 +1,168 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/file.h"
+#include "common/savefile.h"
+
+#include "m4/m4.h"
+#include "m4/saveload.h"
+#include "m4/sprite.h"
+
+namespace M4 {
+
+const char *orionSavesList = "saves.dir";
+
+SaveLoad::SaveLoad(M4Engine *vm) : _vm(vm) {
+ // For Orion Burger, check the existance of a 'saves.dir' file to determine whether to
+ // act exactly like the original. Otherwise, we'll use the ScummVM standard, where we'll
+ // keep all the data for a savegame in a single file
+
+ Common::File file;
+ _emulateOriginal = file.exists(orionSavesList);
+}
+
+const char *SaveLoad::generateSaveName(int slotNumber) {
+ static char buffer[15];
+
+ sprintf(buffer, _emulateOriginal ? "burg%.3d.sav" : "burger.%.3d", slotNumber);
+ return buffer;
+}
+
+bool SaveLoad::hasSaves() {
+ // Return true if a savegame file exists for the first slot
+
+ if (_emulateOriginal) {
+ Common::File f;
+ return f.exists(generateSaveName(1));
+
+ } else {
+ Common::ReadStream *f = _vm->saveManager()->openForLoading(generateSaveName(1));
+ if (f == NULL)
+ return false;
+
+ delete f;
+ return true;
+ }
+}
+
+SaveGameList *SaveLoad::getSaves() {
+ SaveGameList *result = new SaveGameList();
+ char saveName[MAX_SAVEGAME_NAME];
+ Common::ReadStream *f = NULL;
+
+ if (_emulateOriginal) {
+ Common::File *saveFile = new Common::File();
+ saveFile->open(orionSavesList);
+ f = saveFile;
+ }
+
+ for (int slotNumber = 1; slotNumber <= 99; ++slotNumber) {
+ if (_emulateOriginal) {
+ // Read in savegame name from save directory
+ bool isPresent = (f->readByte() != 0);
+ f->read(&saveName[0], MAX_SAVEGAME_NAME);
+
+ if (isPresent)
+ result->push_back(Common::String(saveName));
+ else {
+ result->push_back(Common::String());
+ }
+
+ } else {
+ // Read in savegame name from savegame files directly
+ Common::ReadStream *saveFile = _vm->saveManager()->openForLoading(
+ generateSaveName(slotNumber));
+ if (!saveFile) {
+ // No savegame prsent at that slot
+ result->push_back(Common::String());
+ } else {
+ // Skip over byte offset
+ assert(saveFile->readUint32LE() < 0x100);
+
+ // Read in savegame name
+ saveFile->read(&saveName[0], MAX_SAVEGAME_NAME);
+ result->push_back(Common::String(saveName));
+
+ delete saveFile;
+ }
+ }
+ }
+
+ if (_emulateOriginal)
+ delete f;
+
+ return result;
+}
+
+M4Surface *SaveLoad::getThumbnail(int slotNumber) {
+ Common::SeekableReadStream *saveFile;
+ uint32 dataOffset;
+
+ if (_emulateOriginal) {
+ // Get savegame file from original game folder
+ Common::File *f = new Common::File();
+ if (!f->open(generateSaveName(slotNumber))) {
+ delete f;
+ return NULL;
+ }
+
+ saveFile = f;
+ } else {
+ // Open up savegame for access via savefile manager
+ saveFile = _vm->saveManager()->openForLoading(generateSaveName(slotNumber));
+ }
+ if (!saveFile)
+ return NULL;
+
+ dataOffset = saveFile->readUint32LE();
+ assert(dataOffset < 0x100);
+ saveFile->seek(dataOffset, SEEK_CUR);
+
+ // Read in the sprite data
+
+ saveFile->seek(16, SEEK_CUR);
+ int width = saveFile->readUint32LE();
+ int height = saveFile->readUint32LE();
+ saveFile->seek(21, SEEK_CUR);
+ saveFile->readUint32LE(); // sprite data size
+
+ M4Sprite *result = new M4Sprite(saveFile, 0, 0, width, height);
+ delete saveFile;
+
+ return result;
+}
+
+bool SaveLoad::load(int slotNumber) {
+ // TODO: Currently it's hardcoded to return a failure
+ return false;
+}
+
+bool SaveLoad::save(int slotNumber, Common::String saveName) {
+ // TODO: Currently it's hardcoded to return a failure
+ return false;
+}
+
+
+} // End of namespace M4
diff --git a/engines/m4/saveload.h b/engines/m4/saveload.h
new file mode 100644
index 0000000000..5218f7db84
--- /dev/null
+++ b/engines/m4/saveload.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_SAVELOAD_H
+#define M4_SAVELOAD_H
+
+#include "m4/graphics.h"
+#include "common/ptr.h"
+
+#define MAX_SAVEGAME_NAME 80
+
+namespace M4 {
+
+typedef Common::List<Common::String> SaveGameList;
+typedef SaveGameList::iterator SaveGameIterator;
+
+class SaveLoad {
+private:
+ M4Engine *_vm;
+ bool _emulateOriginal;
+
+ const char *generateSaveName(int slotNumber);
+public:
+ SaveLoad(M4Engine *vm);
+
+ bool hasSaves();
+ SaveGameList *getSaves();
+ M4Surface *getThumbnail(int slotNumber);
+ bool load(int slotNumber);
+ bool save(int slotNumber, Common::String saveName);
+};
+
+}
+
+#endif
diff --git a/engines/m4/scene.cpp b/engines/m4/scene.cpp
new file mode 100644
index 0000000000..787b8dd8eb
--- /dev/null
+++ b/engines/m4/scene.cpp
@@ -0,0 +1,669 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/system.h"
+
+#include "m4/globals.h"
+#include "m4/scene.h"
+#include "m4/events.h"
+#include "m4/graphics.h"
+#include "m4/rails.h"
+#include "m4/font.h"
+#include "m4/m4_views.h"
+#include "m4/compression.h"
+
+namespace M4 {
+
+Scene::Scene(M4Engine *vm): View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) {
+ _screenType = VIEWID_SCENE;
+
+ _sceneResources.hotspots = new HotSpotList();
+ _sceneResources.parallax = new HotSpotList();
+ _sceneResources.props = new HotSpotList();
+ _backgroundSurface = new M4Surface();
+ _codeSurface = new M4Surface();
+ _madsInterfaceSurface = new M4Surface();
+ _sceneSprites = NULL;
+ _palData = NULL;
+ _interfacePal = NULL;
+ _inverseColorTable = NULL;
+ strcpy(_statusText, "");
+ _vm->_rails->setCodeSurface(_codeSurface);
+}
+
+Scene::~Scene() {
+ _sceneResources.hotspots->clear();
+ _sceneResources.parallax->clear();
+ _sceneResources.props->clear();
+
+ delete _sceneResources.hotspots;
+ delete _sceneResources.parallax;
+ delete _sceneResources.props;
+
+ delete _backgroundSurface;
+ delete _codeSurface;
+ delete _madsInterfaceSurface;
+
+ if (_sceneSprites)
+ delete _sceneSprites;
+
+ _vm->_palette->deleteAllRanges();
+
+ if (_palData)
+ delete _palData;
+
+ if (_interfacePal)
+ delete _interfacePal;
+
+ if (_inverseColorTable)
+ delete[] _inverseColorTable;
+
+}
+
+void Scene::loadScene(int sceneNumber) {
+ _currentScene = sceneNumber;
+
+ // Close the menu if it's active
+ if (!_vm->isM4()) {
+ View *mainMenu = _vm->_viewManager->getView(VIEWID_MAINMENU);
+ if (mainMenu != NULL) {
+ _vm->_viewManager->deleteView(mainMenu);
+ }
+ }
+
+ // TODO: Check if we were loading a game
+
+
+ // Load scene background and set palette
+ if (_palData) {
+ _vm->_palette->deleteRange(_palData);
+ delete _palData;
+ }
+
+ if (_interfacePal) {
+ _vm->_palette->deleteRange(_interfacePal);
+ delete _interfacePal;
+ }
+
+ if (_vm->isM4()) {
+ _backgroundSurface->loadBackground(sceneNumber);
+ _palData = NULL;
+ } else {
+ _backgroundSurface->loadBackground(sceneNumber, &_palData);
+ _vm->_palette->addRange(_palData);
+ _backgroundSurface->translate(_palData);
+
+ if (sceneNumber < 900) {
+ /*_backgroundSurface->fillRect(Common::Rect(0, MADS_SURFACE_HEIGHT,
+ _backgroundSurface->width(), _backgroundSurface->height()),
+ _vm->_palette->BLACK);*/
+ // TODO: interface palette
+ _madsInterfaceSurface->madsloadInterface(0, &_interfacePal);
+ _vm->_palette->addRange(_interfacePal);
+ _madsInterfaceSurface->translate(_interfacePal);
+ _backgroundSurface->copyFrom(_madsInterfaceSurface, Common::Rect(0, 0, 320, 44), 0, 200 - 44);
+ }
+ }
+
+ if (_vm->getGameType() == GType_Burger &&
+ sceneNumber != TITLE_SCENE_BURGER && sceneNumber != MAINMENU_SCENE_BURGER)
+ _vm->_interfaceView->setStatusText("");
+
+ // Load scene def file (*.CHK)
+ if (_vm->isM4()) {
+ loadSceneResources(sceneNumber);
+ loadSceneInverseColorTable(sceneNumber);
+ } else {
+ // Don't load other screen resources for system screens
+ if (sceneNumber >= 900)
+ return;
+
+ loadSceneHotSpotsMads(sceneNumber);
+ }
+
+ // TODO: set walker scaling
+ // TODO: destroy woodscript buffer
+
+ // Load scene walk path file (*.COD/*.WW?)
+ loadSceneCodes(sceneNumber);
+
+ // Load inverse color table file (*.IPL)
+ loadSceneInverseColorTable(sceneNumber);
+
+ if (_vm->isM4()) {
+
+ if (_vm->getGameType() != GType_Burger) {
+ // Load scene sprites file (*.SSB)
+ loadSceneSprites(sceneNumber);
+
+ // Load scene sprite codes file (*.SSC)
+ loadSceneSpriteCodes(sceneNumber);
+ }
+
+
+ if (sceneNumber != TITLE_SCENE_BURGER && sceneNumber != MAINMENU_SCENE_BURGER) {
+ _vm->_interfaceView->show();
+ showSprites();
+ }
+ }
+
+ // Purge resources
+ _vm->res()->purge();
+}
+
+void Scene::loadSceneResources(int sceneNumber) {
+ char filename[kM4MaxFilenameSize];
+ int i = 0, x = 0, y = 0;
+ sprintf(filename, "%i.chk", sceneNumber);
+
+ Common::SeekableReadStream *sceneS = _vm->res()->get(filename);
+
+ if (sceneS != NULL) {
+ sceneS->read(_sceneResources.artBase, MAX_CHK_FILENAME_SIZE);
+ sceneS->read(_sceneResources.pictureBase, MAX_CHK_FILENAME_SIZE);
+ _sceneResources.hotspotCount = sceneS->readUint32LE();
+ _sceneResources.parallaxCount = sceneS->readUint32LE();
+ _sceneResources.propsCount = sceneS->readUint32LE();
+ _sceneResources.frontY = sceneS->readUint32LE();
+ _sceneResources.backY = sceneS->readUint32LE();
+ _sceneResources.frontScale = sceneS->readUint32LE();
+ _sceneResources.backScale = sceneS->readUint32LE();
+ for (i = 0; i < 16; i++)
+ _sceneResources.depthTable[i] = sceneS->readUint16LE();
+ _sceneResources.railNodeCount = sceneS->readUint32LE();
+
+ // Clear rails from previous scene
+ _vm->_rails->clearRails();
+
+ for (i = 0; i < _sceneResources.railNodeCount; i++) {
+ x = sceneS->readUint32LE();
+ y = sceneS->readUint32LE();
+ if (_vm->_rails->addRailNode(x, y, true) < 0) {
+ warning("Too many rail nodes defined for scene");
+ }
+ }
+
+ // Clear current hotspot lists
+ _sceneResources.hotspots->clear();
+ _sceneResources.parallax->clear();
+ _sceneResources.props->clear();
+
+ _sceneResources.hotspots->loadHotSpotsM4(sceneS, _sceneResources.hotspotCount);
+ _sceneResources.parallax->loadHotSpotsM4(sceneS, _sceneResources.parallaxCount);
+ _sceneResources.props->loadHotSpotsM4(sceneS, _sceneResources.propsCount);
+
+ // Note that toss() deletes the MemoryReadStream
+ _vm->res()->toss(filename);
+ }
+}
+
+void Scene::loadSceneHotSpotsMads(int sceneNumber) {
+ char filename[kM4MaxFilenameSize];
+ sprintf(filename, "rm%i.hh", sceneNumber);
+ MadsPack hotSpotData(filename, _vm);
+ Common::SeekableReadStream *hotspotStream = hotSpotData.getItemStream(0);
+
+ int hotspotCount = hotspotStream->readUint16LE();
+ delete hotspotStream;
+
+ HotSpotList *hotspotList = _sceneResources.hotspots;
+ _sceneResources.hotspotCount = hotspotCount;
+
+ hotspotStream = hotSpotData.getItemStream(1);
+
+ // Clear current hotspot lists
+ _sceneResources.hotspots->clear();
+
+ _sceneResources.hotspots->loadHotSpotsMads(hotspotStream, _sceneResources.hotspotCount);
+
+ delete hotspotStream;
+}
+
+void Scene::loadSceneCodes(int sceneNumber, int index) {
+ char filename[kM4MaxFilenameSize];
+ Common::SeekableReadStream *sceneS;
+
+ if (_vm->isM4()) {
+ sprintf(filename, "%i.cod", sceneNumber);
+ sceneS = _vm->res()->openFile(filename);
+ _codeSurface->loadCodesM4(sceneS);
+ _vm->res()->toss(filename);
+ } else if (_vm->getGameType() == GType_Phantom || _vm->getGameType() == GType_DragonSphere) {
+ sprintf(filename, "rm%i.ww%i", sceneNumber, index);
+ MadsPack walkData(filename, _vm);
+ sceneS = walkData.getItemStream(0);
+ _codeSurface->loadCodesMads(sceneS);
+ _vm->res()->toss(filename);
+ } else if (_vm->getGameType() == GType_RexNebular) {
+ // TODO
+ return;
+ }
+}
+
+void Scene::loadSceneInverseColorTable(int sceneNumber) {
+ char filename[kM4MaxFilenameSize];
+ Common::SeekableReadStream *iplS;
+
+ if (_vm->isM4()) {
+ sprintf(filename, "%i.ipl", sceneNumber);
+ iplS = _vm->res()->openFile(filename);
+ if (_inverseColorTable)
+ delete[] _inverseColorTable;
+ _inverseColorTable = new byte[iplS->size()];
+ iplS->read(_inverseColorTable, iplS->size());
+ _vm->res()->toss(filename);
+ } else {
+ // TODO?
+ return;
+ }
+
+}
+
+
+void Scene::loadSceneSprites(int sceneNumber) {
+ char filename[kM4MaxFilenameSize];
+ sprintf(filename, "%i.ssb", sceneNumber);
+
+ Common::SeekableReadStream *sceneS = _vm->res()->get(filename);
+ _sceneSprites = new SpriteAsset(_vm, sceneS, sceneS->size(), filename);
+ _vm->res()->toss(filename);
+
+ printf("Scene has %d sprites, each one having %d colors\n", _sceneSprites->getCount(), _sceneSprites->getColorCount());
+}
+
+void Scene::loadSceneSpriteCodes(int sceneNumber) {
+ char filename[kM4MaxFilenameSize];
+ sprintf(filename, "%i.ssc", sceneNumber);
+
+ Common::SeekableReadStream *sceneS = _vm->res()->get(filename);
+
+ // TODO
+
+ if (sceneS != NULL) {
+ SpriteAsset* _sceneSpriteCodes = new SpriteAsset(_vm, sceneS, sceneS->size(), filename);
+ int colorCount = _sceneSpriteCodes->getColorCount();
+// RGB8* spritePalette = _sceneSpriteCodes->getPalette();
+ //_vm->_palette->setPalette(spritePalette, 0, colorCount);
+
+ printf("Scene has %d sprite codes, each one having %d colors\n", _sceneSpriteCodes->getCount(), colorCount);
+
+ // Note that toss() deletes the MemoryReadStream
+ _vm->res()->toss(filename);
+ }
+}
+
+void Scene::showSprites() {
+ // TODO: This is all experimental code, it needs heavy restructuring
+ // and cleanup
+
+ // taken from set_walker_scaling() in adv_walk.cpp. A proper implementation will need
+ // to store these in global variables
+ int minScaling = FixedDiv(_sceneResources.backScale << 16, 100 << 16);
+ int maxScaling = FixedDiv(_sceneResources.frontScale << 16, 100 << 16);
+ int scaler;
+
+ _vm->_actor->setWalkerDirection(kFacingSouthEast);
+ //_vm->_actor->setWalkerPalette();
+
+ // taken from set_walker_scaling() in adv_walk.cpp
+ if (_sceneResources.frontY == _sceneResources.backY)
+ scaler = 0;
+ else
+ scaler = FixedDiv(maxScaling - minScaling,
+ (_sceneResources.frontY << 16) - (_sceneResources.backY << 16));
+
+ // FIXME: For now, we (incorrectly) scale the walker to 50% of the scene's max scaling
+ _vm->_actor->setWalkerScaling(scaler / 2);
+ // Test code to display the protagonist
+ _vm->_actor->placeWalkerSpriteAt(0, 320, 200);
+
+ // Test code to display scene sprites
+ // TODO
+}
+
+void Scene::checkHotspotAtMousePos(int x, int y) {
+ if (_vm->getGameType() == GType_Riddle)
+ return;
+
+ // TODO: loads of things to do here, only the mouse cursor and the status
+ // text is changed for now
+
+ // Only scene hotspots are checked for now, not parallax/props, as the
+ // latter ones are not used by Orion Burger
+ HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
+ if (currentHotSpot != NULL && currentHotSpot->getActive()) {
+ if (_vm->_mouse->getCursorNum() != CURSOR_LOOK &&
+ _vm->_mouse->getCursorNum() != CURSOR_TAKE &&
+ _vm->_mouse->getCursorNum() != CURSOR_USE &&
+ _vm->_interfaceView->_inventory.getSelectedIndex() == -1) {
+ _vm->_mouse->setCursorNum(currentHotSpot->getCursor());
+ }
+ _vm->_interfaceView->setStatusText(currentHotSpot->getPrep());
+ } else {
+ if (_vm->_mouse->getCursorNum() != CURSOR_LOOK &&
+ _vm->_mouse->getCursorNum() != CURSOR_TAKE &&
+ _vm->_mouse->getCursorNum() != CURSOR_USE &&
+ _vm->_interfaceView->_inventory.getSelectedIndex() == -1) {
+ _vm->_mouse->setCursorNum(0);
+ } else {
+
+ }
+ }
+}
+
+void Scene::checkHotspotAtMousePosMads(int x, int y) {
+ HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
+ if (currentHotSpot != NULL) {
+ _vm->_mouse->setCursorNum(currentHotSpot->getCursor());
+
+ // This is the "easy" interface, which updates the status text when the mouse is moved
+ // TODO: toggle this code for easy/normal interface mode
+ char statusText[50];
+ if (currentHotSpot->getVerbID() != 0) {
+ sprintf(statusText, "%s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab());
+ } else {
+ sprintf(statusText, "%s %s\n", _vm->_globals->getVocab(kVerbWalkTo), currentHotSpot->getVocab());
+ }
+
+ statusText[0] = toupper(statusText[0]); // capitalize first letter
+ setMADSStatusText(statusText);
+ } else {
+ _vm->_mouse->setCursorNum(0);
+ setMADSStatusText("");
+ }
+}
+
+// Test function, shows all scene hotspots
+void Scene::showHotSpots() {
+ int i = 0;
+ HotSpot *currentHotSpot;
+ // hotspots (green)
+ for (i = 0; i < _sceneResources.hotspotCount; i++) {
+ currentHotSpot = _sceneResources.hotspots->get(i);
+ _backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->GREEN);
+ }
+ if (_vm->isM4()) {
+ // parallax (yellow)
+ for (i = 0; i < _sceneResources.parallaxCount; i++) {
+ currentHotSpot = _sceneResources.parallax->get(i);
+ _backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->YELLOW);
+ }
+ // props (red)
+ for (i = 0; i < _sceneResources.propsCount; i++) {
+ currentHotSpot = _sceneResources.props->get(i);
+ _backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->RED);
+ }
+ }
+}
+
+// Test function, shows all scene codes
+void Scene::showCodes() {
+ uint8 *pixelData = (uint8*)_codeSurface->pixels;
+ for (int i = 0; i < _codeSurface->w * _codeSurface->h; i++)
+ if (pixelData[i] & 0x10)
+ pixelData[i] = 0xFF;
+ else
+ pixelData[i] = 0;
+
+ byte colors[256 * 4];
+ memset(colors, 0, sizeof(colors));
+ colors[255 * 4 + 0] = 255;
+ colors[255 * 4 + 1] = 255;
+ colors[255 * 4 + 2] = 255;
+ _vm->_palette->setPalette(colors, 0, 256);
+
+ _backgroundSurface->copyFrom(_codeSurface, Common::Rect(0, 0, 640, 480), 0, 0);
+ //_system->copyRectToScreen((byte *)codes->pixels, codes->w, 0, 0, codes->w, codes->h);
+}
+
+void Scene::playIntro() {
+
+}
+
+void Scene::update() {
+ // TODO: Needs a proper implementation
+ // NOTE: Don't copy the background when in M4 mode or WoodScript anims won't be shown
+ if (!_vm->isM4()) {
+ _backgroundSurface->copyTo(this);
+
+ if (_statusText[0]) {
+ // Text colors are inverted in Dragonsphere
+ if (_vm->getGameType() == GType_DragonSphere)
+ _vm->_font->setColors(_vm->_palette->BLACK, _vm->_palette->WHITE, _vm->_palette->BLACK);
+ else
+ _vm->_font->setColors(_vm->_palette->WHITE, _vm->_palette->BLACK, _vm->_palette->BLACK);
+
+ _vm->_font->setFont(FONT_MAIN_MADS);
+ _vm->_font->writeString(this, _statusText, (width() - _vm->_font->getWidth(_statusText)) / 2, 142, 0);
+ }
+ }
+}
+
+void Scene::onRefresh(RectList *rects, M4Surface *destSurface) {
+ update();
+ View::onRefresh(rects, destSurface);
+}
+
+bool Scene::onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents) {
+ //if (_vm->getGameType() != GType_Burger)
+ // return false;
+
+ // If the game is currently paused, don't do any scene processing
+ if (_vm->_kernel->paused)
+ return false;
+
+ switch (eventType) {
+ case MEVENT_LEFT_CLICK:
+ {
+ if (_vm->getGameType() == GType_Burger) {
+ // Place a Wilbur sprite with the correct facing
+ HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
+ if (currentHotSpot != NULL && currentHotSpot->getActive()) {
+ update();
+ _vm->_actor->setWalkerDirection(currentHotSpot->getFacing());
+ int posX = currentHotSpot->getFeetX();
+ int posY = currentHotSpot->getFeetY() -
+ scaleValue(_vm->_actor->getWalkerHeight(), _vm->_actor->getWalkerScaling(), 0);
+ //_vm->_actor->placeWalkerSpriteAt(0, posX, posY);
+
+ // Player said.... (for scene scripts)
+ printf("Player said: %s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab());
+
+ // FIXME: This should be moved somewhere else, and is incomplete
+ if (_vm->_interfaceView->_inventory.getSelectedIndex() == -1) {
+ if (_vm->_mouse->getVerb() == NULL) {
+ strcpy(_vm->_player->verb, currentHotSpot->getVerb());
+ } else {
+ strcpy(_vm->_player->verb, _vm->_mouse->getVerb());
+ }
+ } else {
+ strcpy(_vm->_player->verb, _vm->_interfaceView->_inventory.getSelectedObjectName());
+ }
+ strcpy(_vm->_player->noun, currentHotSpot->getVocab());
+ strcpy(_vm->_player->object, "");
+ _vm->_player->commandReady = true;
+
+ printf("## Player said: %s %s\n", _vm->_player->verb, _vm->_player->noun);
+
+ }
+ }
+
+ if (!_vm->isM4()) {
+ HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
+ if (currentHotSpot != NULL) {
+ char statusText[50];
+ if (currentHotSpot->getVerbID() != 0) {
+ sprintf(statusText, "%s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab());
+ } else {
+ sprintf(statusText, "%s %s\n", _vm->_globals->getVocab(kVerbWalkTo), currentHotSpot->getVocab());
+ }
+
+ statusText[0] = toupper(statusText[0]); // capitalize first letter
+ setMADSStatusText(statusText);
+ }
+ }
+ }
+ break;
+ case MEVENT_RIGHT_CLICK:
+ if (_vm->getGameType() == GType_Burger) {
+ nextCommonCursor();
+ _vm->_interfaceView->_inventory.clearSelected();
+ }
+ break;
+ case MEVENT_MOVE:
+ if (_vm->isM4())
+ checkHotspotAtMousePos(x, y);
+ else
+ checkHotspotAtMousePosMads(x, y);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+void Scene::nextCommonCursor() {
+ int cursorIndex = _vm->_mouse->getCursorNum();
+
+ switch (cursorIndex) {
+ case CURSOR_ARROW:
+ cursorIndex = CURSOR_LOOK;
+ break;
+ case CURSOR_LOOK:
+ cursorIndex = CURSOR_TAKE;
+ break;
+ case CURSOR_TAKE:
+ cursorIndex = CURSOR_USE;
+ break;
+ case CURSOR_USE:
+ cursorIndex = CURSOR_ARROW;
+ break;
+ default:
+ cursorIndex = CURSOR_ARROW;
+ }
+
+ _vm->_mouse->setCursorNum(cursorIndex);
+}
+
+enum boxSprites {
+ topLeft = 0,
+ topRight = 1,
+ bottomLeft = 2,
+ bottomRight = 3,
+ left = 4,
+ right = 5,
+ top = 6,
+ bottom = 7,
+ topMiddle = 8,
+ filler1 = 9,
+ filler2 = 10
+ // TODO: finish this
+};
+
+// TODO: calculate width and height, show text, show face if it exists
+// TODO: this has been tested with Dragonsphere only, there are some differences
+// in the sprites used in Phantom
+void Scene::showMADSV2TextBox(char *text, int x, int y, char *faceName) {
+ int repeatX = 40; // FIXME: this is hardcoded
+ int repeatY = 30; // FIXME: this is hardcoded
+ int curX = x, curY = y;
+ int topRightX = x; // TODO: this is probably not needed
+ Common::SeekableReadStream *data = _vm->res()->get("box.ss");
+ SpriteAsset *boxSprites = new SpriteAsset(_vm, data, data->size(), "box.ss");
+ _vm->res()->toss("box.ss");
+
+ RGBList *palData = new RGBList(boxSprites->getColorCount(), boxSprites->getPalette(), true);
+ _vm->_palette->addRange(palData);
+
+ for (int i = 0; i < boxSprites->getCount(); i++)
+ boxSprites->getFrame(i)->translate(palData); // sprite pixel translation
+
+ // Top left corner
+ boxSprites->getFrame(topLeft)->copyTo(_backgroundSurface, x, curY);
+ curX += boxSprites->getFrame(topLeft)->width();
+
+ // Top line
+ for (int i = 0; i < repeatX; i++) {
+ boxSprites->getFrame(top)->copyTo(_backgroundSurface, curX, curY + 3);
+ curX += boxSprites->getFrame(top)->width();
+ }
+
+ // Top right corner
+ boxSprites->getFrame(topRight)->copyTo(_backgroundSurface, curX, curY);
+ topRightX = curX;
+
+ // Top middle
+ // FIXME: the transparent color for this is also the black border color
+ boxSprites->getFrame(topMiddle)->copyTo(_backgroundSurface,
+ x + (curX - x) / 2 - boxSprites->getFrame(topMiddle)->width() / 2,
+ curY - 5, 167);
+ curX = x;
+ curY += boxSprites->getFrame(topLeft)->height();
+
+ // -----------------------------------------------------------------------------------------------
+
+ // Draw contents
+ for (int i = 0; i < repeatY; i++) {
+ for (int j = 0; j < repeatX; j++) {
+ if (j == 0) {
+ boxSprites->getFrame(left)->copyTo(_backgroundSurface, curX + 3, curY);
+ curX += boxSprites->getFrame(left)->width();
+ } else if (j == repeatX - 1) {
+ curX = topRightX - 2;
+ boxSprites->getFrame(right)->copyTo(_backgroundSurface, curX + 3, curY + 1);
+ } else {
+ // TODO: the background of the contents follows a pattern which is not understood yet
+ if (j % 2 == 0) {
+ boxSprites->getFrame(filler1)->copyTo(_backgroundSurface, curX + 3, curY);
+ curX += boxSprites->getFrame(filler1)->width();
+ } else {
+ boxSprites->getFrame(filler2)->copyTo(_backgroundSurface, curX + 3, curY);
+ curX += boxSprites->getFrame(filler2)->width();
+ }
+ }
+ } // for j
+ curX = x;
+ curY += boxSprites->getFrame(left)->height();
+ } // for i
+
+ // -----------------------------------------------------------------------------------------------
+ curX = x;
+
+ // Bottom left corner
+ boxSprites->getFrame(bottomLeft)->copyTo(_backgroundSurface, curX, curY);
+ curX += boxSprites->getFrame(bottomLeft)->width();
+
+ // Bottom line
+ for (int i = 0; i < repeatX; i++) {
+ boxSprites->getFrame(bottom)->copyTo(_backgroundSurface, curX, curY + 1);
+ curX += boxSprites->getFrame(bottom)->width();
+ }
+
+ // Bottom right corner
+ boxSprites->getFrame(bottomRight)->copyTo(_backgroundSurface, curX, curY + 1);
+}
+
+} // End of namespace M4
diff --git a/engines/m4/scene.h b/engines/m4/scene.h
new file mode 100644
index 0000000000..77578f5a02
--- /dev/null
+++ b/engines/m4/scene.h
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_SCENE_H
+#define M4_SCENE_H
+
+class View;
+
+#include "m4/assets.h"
+#include "m4/hotspot.h"
+#include "m4/graphics.h"
+#include "m4/viewmgr.h"
+
+namespace M4 {
+
+#define TITLE_SCENE_BURGER 951 // 951 = intro, 901 = demo menu, 971 = first scene
+#define MAINMENU_SCENE_BURGER 903
+#define FIRST_SCENE 101
+#define MAX_CHK_FILENAME_SIZE 144
+
+#define INTERFACE_HEIGHT 106
+#define MADS_SURFACE_HEIGHT 156
+
+enum MADSVerbs {
+ kVerbLook = 2,
+ kVerbTake = 3,
+ kVerbPush = 4,
+ kVerbOpen = 5,
+ kVerbPut = 6,
+ kVerbTalkTo = 7,
+ kVerbGive = 8,
+ kVerbPull = 9,
+ kVerbClose = 10,
+ kVerbThrow = 11,
+ kVerbWalkTo = 12
+};
+
+struct SceneResources {
+ char artBase[MAX_CHK_FILENAME_SIZE];
+ char pictureBase[MAX_CHK_FILENAME_SIZE];
+ int32 hotspotCount;
+ HotSpotList *hotspots;
+ int32 parallaxCount;
+ HotSpotList *parallax;
+ int32 propsCount;
+ HotSpotList *props;
+ int32 frontY, backY;
+ int32 frontScale, backScale;
+ int16 depthTable[16];
+ int32 railNodeCount; // # of rails
+};
+
+class Scene: public View {
+public:
+ Scene(M4Engine *vm);
+ ~Scene();
+
+ // TODO: perhaps move playIntro() someplace else?
+ void playIntro();
+ void loadScene(int sceneNumber);
+ void loadSceneResources(int sceneNumber);
+ void loadSceneHotSpotsMads(int sceneNumber);
+ void loadSceneCodes(int sceneNumber, int index = 0);
+ void loadSceneInverseColorTable(int sceneNumber);
+ void loadSceneSprites(int sceneNumber);
+ void loadSceneSpriteCodes(int sceneNumber);
+ void showSprites();
+ void checkHotspotAtMousePos(int x, int y);
+ void checkHotspotAtMousePosMads(int x, int y);
+ void showHotSpots();
+ void showCodes();
+ int getCurrentScene() { return _currentScene; }
+ SceneResources getSceneResources() { return _sceneResources; }
+ M4Surface *getBackgroundSurface() const { return _backgroundSurface; }
+ byte *getInverseColorTable() const { return _inverseColorTable; }
+ void update();
+ void setMADSStatusText(const char *text) { strcpy(_statusText, text); }
+ void showMADSV2TextBox(char *text, int x, int y, char *faceName);
+
+ void onRefresh(RectList *rects, M4Surface *destSurface);
+ bool onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents);
+
+private:
+ int _currentScene;
+ M4Surface *_backgroundSurface;
+ M4Surface *_codeSurface;
+ M4Surface *_madsInterfaceSurface;
+ byte *_inverseColorTable;
+ RGBList *_palData;
+ RGBList *_interfacePal;
+ SceneResources _sceneResources;
+ HotSpotList _sceneHotspots;
+ SpriteAsset *_sceneSprites;
+ SpriteAsset *_walkerSprite;
+ char _statusText[100];
+
+ void nextCommonCursor();
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/script.cpp b/engines/m4/script.cpp
new file mode 100644
index 0000000000..e7026a381d
--- /dev/null
+++ b/engines/m4/script.cpp
@@ -0,0 +1,1406 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/system.h"
+
+#include "m4/m4.h"
+#include "m4/script.h"
+#include "m4/resource.h"
+
+namespace M4 {
+
+enum OpcodeType {
+ opRet = 0,
+ opCall,
+ opCallKernel,
+ opPush,
+ opPush0,
+ opPush1,
+ opPushNeg1,
+ opPop,
+ opMov,
+ opAdd,
+ opSub,
+ opInc,
+ opDec,
+ opCmp,
+ opJmp,
+ opJmpByTable,
+ opJz,
+ opJnz,
+ opJe,
+ opJne,
+ opJl,
+ opJle,
+ opJg,
+ opJge,
+ opXor,
+ opShl,
+ opShr,
+
+ opDebug,
+
+ opInvalid
+};
+
+const char *opcodeNames[] = {
+ "opRet",
+ "opCall",
+ "opCallKernel",
+ "opPush",
+ "opPush0",
+ "opPush1",
+ "opPushNeg1",
+ "opPop",
+ "opMov",
+ "opAdd",
+ "opSub",
+ "opInc",
+ "opDec",
+ "opCmp",
+ "opJmp",
+ "opJmpByTable",
+ "opJz",
+ "opJnz",
+ "opJe",
+ "opJne",
+ "opJl",
+ "opJle",
+ "opJg",
+ "opJge",
+ "opXor",
+ "opShl",
+ "opShr",
+ "opDebug",
+ "opInvalid"
+};
+
+StringTable::StringTable() : _stringsData(NULL) {
+}
+
+StringTable::~StringTable() {
+ if (_stringsData)
+ delete[] _stringsData;
+}
+
+void StringTable::load(Common::File *fd) {
+ int stringSize = fd->readUint32LE();
+ int stringCount = fd->readUint32LE();
+ _stringsData = new char[stringSize];
+ fd->read(_stringsData, stringSize);
+ char *stringPtr = _stringsData;
+ for (int i = 0; i < stringCount; i++) {
+ _strings.push_back((const char*)stringPtr);
+ stringPtr += strlen(stringPtr) + 1;
+ }
+}
+
+SeriesStreamBreakList::~SeriesStreamBreakList() {
+}
+
+void SeriesStreamBreakList::load(Common::File *fd) {
+ uint32 count = fd->readUint32LE();
+ printf("SeriesStreamBreakList::load() count = %d\n", count);
+ for (uint32 i = 0; i < count; i++) {
+ SeriesStreamBreakItem *item = new SeriesStreamBreakItem();
+ item->frameNum = fd->readUint32LE();
+ item->digiName = _inter->loadGlobalString(fd);
+ item->digiChannel = fd->readUint32LE();
+ item->digiVolume = fd->readUint32LE();
+ item->trigger = fd->readUint32LE();
+ item->flags = fd->readUint32LE();
+ item->variable.type = kGameVar;
+ item->variable.value = fd->readUint32LE();
+ item->value = fd->readUint32LE();
+ _items.push_back(item);
+
+ printf("%02d: frameNum = %d; digiName = %s; digiChannel = %d; digiVolume = %d; trigger = %d; flags = %d; variable = %d; value = %d\n",
+ i, item->frameNum, item->digiName, item->digiChannel, item->digiVolume, item->trigger, item->flags, item->variable.value, item->value);
+
+ }
+}
+
+SaidArray::~SaidArray() {
+}
+
+void SaidArray::load(Common::File *fd) {
+ uint32 count = fd->readUint32LE();
+ printf("SaidArray::load() count = %d\n", count);
+ for (uint32 i = 0; i < count; i++) {
+ SaidArrayItem *item = new SaidArrayItem();
+ item->itemName = _inter->loadGlobalString(fd);
+ item->digiNameLook = _inter->loadGlobalString(fd);
+ item->digiNameTake = _inter->loadGlobalString(fd);
+ item->digiNameGear = _inter->loadGlobalString(fd);
+ _items.push_back(item);
+
+ printf("itemName = %s; digiNameLook = %s; digiNameTake = %s; digiNameGear = %s\n",
+ item->itemName, item->digiNameLook, item->digiNameTake, item->digiNameGear);
+
+ }
+}
+
+ParserArray::~ParserArray() {
+}
+
+void ParserArray::load(Common::File *fd) {
+ uint32 count = fd->readUint32LE();
+ printf("ParserArray::load() count = %d\n", count);
+ for (uint32 i = 0; i < count; i++) {
+ ParserArrayItem *item = new ParserArrayItem();
+ item->w0 = _inter->loadGlobalString(fd);
+ item->w1 = _inter->loadGlobalString(fd);
+ item->trigger = fd->readUint32LE();
+ item->testVariable.type = kGameVar;
+ item->testVariable.value = fd->readUint32LE();
+ item->testValue = fd->readUint32LE();
+ item->variable.type = kGameVar;
+ item->variable.value = fd->readUint32LE();
+ item->value = fd->readUint32LE();
+ _items.push_back(item);
+
+ printf("w0 = %s; w1 = %s; trigger = %d; testVariable = %d; testValue = %d; variable = %d; value = %d\n",
+ item->w0, item->w1, item->trigger, item->testVariable.value, item->testValue, item->variable.value, item->value);
+
+ }
+}
+
+ScriptFunction::ScriptFunction(ScriptInterpreter *inter) : _inter(inter) {
+}
+
+ScriptFunction::~ScriptFunction() {
+ if (_code)
+ delete _code;
+}
+
+void ScriptFunction::load(Common::File *fd) {
+ printf("ScriptFunction::load()\n");
+ uint32 size = fd->readUint32LE();
+ printf("ScriptFunction::load() size = %d\n", size);
+ _code = fd->readStream(size);
+}
+
+void ScriptFunction::jumpAbsolute(uint32 ofs) {
+ _code->seek(ofs);
+}
+
+void ScriptFunction::jumpRelative(int32 ofs) {
+ _code->seek(ofs, SEEK_CUR);
+}
+
+byte ScriptFunction::readByte() {
+ return _code->readByte();
+}
+
+uint32 ScriptFunction::readUint32() {
+ return _code->readUint32LE();
+}
+
+
+ScriptInterpreter::ScriptInterpreter(M4Engine *vm) : _scriptFile(NULL), _vm(vm) {
+ initScriptKernel();
+ _dataCache = new ScriptDataCache(this);
+ _runningFunction = NULL;
+}
+
+ScriptInterpreter::~ScriptInterpreter() {
+ close();
+ delete _dataCache;
+}
+
+void ScriptInterpreter::open(const char *filename) {
+ if (_scriptFile)
+ close();
+ _scriptFile = new Common::File();
+ _scriptFile->open(filename);
+ if (!_scriptFile->isOpen())
+ error("ScriptInterpreter::open() Error opening %s.", filename);
+
+ _scriptFile->readUint32LE(); // skip magic for now
+ uint32 version = _scriptFile->readUint32LE();
+ if (version != kScriptFileVersion) {
+ error("ScriptInterpreter::open() DAT file version mismatch; requested %li, got %i\n", kScriptFileVersion, version);
+ }
+
+ int functionCount = _scriptFile->readUint32LE();
+ printf("functionCount = %d\n", functionCount);
+ for (int i = 0; i < functionCount; i++) {
+ uint32 offset = _scriptFile->readUint32LE();
+ printf("func(%d) offset = %08X\n", i, offset);
+ uint32 len = _scriptFile->readUint32LE();
+ if (len > 0) {
+ char *funcName = new char[len + 1];
+ _scriptFile->read(funcName, len);
+ funcName[len] = '\0';
+ printf("func(%d) name = %s\n", i, funcName);
+ _functionNames[Common::String(funcName)] = _functions.size();
+ // DEBUG
+ _scriptFunctionNames.push_back(Common::String(funcName));
+ delete[] funcName;
+ }
+ _functions.push_back(new ScriptFunctionEntry(offset));
+ }
+
+ int dataCount = _scriptFile->readUint32LE();
+ printf("dataCount = %d\n", dataCount);
+ for (int i = 0; i < dataCount; i++) {
+ uint32 offset = _scriptFile->readUint32LE();
+ ScriptDataType type = (ScriptDataType)_scriptFile->readUint32LE();
+ printf("data(%d) offset = %08X; type = %d\n", i, offset, type);
+ _data.push_back(new ScriptDataEntry(offset, type));
+ }
+
+ _globalVarCount = _scriptFile->readUint32LE();
+ printf("_globalVarCount = %d\n", _globalVarCount);
+
+ uint32 stringOfs = _scriptFile->readUint32LE();
+ _scriptFile->seek(stringOfs);
+ _constStrings.load(_scriptFile);
+
+ for (int i = 0; i < ARRAYSIZE(_globalVars); i++) {
+ _globalVars[i].type = kInteger;
+ _globalVars[i].value = 0;
+ }
+
+ memset(_logicGlobals, 0, sizeof(_logicGlobals));
+
+ memset(_registers, 0, sizeof(_registers));
+ memset(_stack, 0, sizeof(_stack));
+ _stackPtr = 0;
+
+}
+
+void ScriptInterpreter::close() {
+ if (_scriptFile) {
+ delete _scriptFile;
+ }
+}
+
+void ScriptInterpreter::initScriptKernel() {
+
+#include "m4/scripttab.h"
+
+ _kernelFunctions = kernelFunctions;
+ _kernelFunctionsMax = ARRAYSIZE(kernelFunctions) + 1;
+
+ _kernelVars = kernelVars;
+ _kernelVarsMax = ARRAYSIZE(kernelVars) + 1;
+
+}
+
+
+ScriptFunction *ScriptInterpreter::loadFunction(uint32 index) {
+ //GONE WHILE DEBUGGING assert(index < _functions.size());
+ if (index >= _functions.size()) return NULL;
+ ScriptFunction *scriptFunction;
+ scriptFunction = _functions[index]->func;
+ if (!scriptFunction) {
+ scriptFunction = new ScriptFunction(this);
+ _scriptFile->seek(_functions[index]->offset);
+ scriptFunction->load(_scriptFile);
+ _functions[index]->func = scriptFunction;
+ }
+ return scriptFunction;
+}
+
+ScriptFunction *ScriptInterpreter::loadFunction(const Common::String &name) {
+ FunctionNameMap::iterator iter = _functionNames.find(name);
+ if (iter == _functionNames.end()) {
+ printf("ScriptInterpreter::loadFunction() Function '%s' not found!\n", name.c_str());
+ return NULL;
+ }
+ uint32 funcIndex = (*iter)._value;
+ printf("ScriptInterpreter::loadFunction() index('%s') = %d\n", name.c_str(), funcIndex);
+ return loadFunction(funcIndex);
+}
+
+void ScriptInterpreter::unloadFunctions() {
+ for (uint32 i = 0; i < _functions.size(); i++) {
+ if (_functions[i]->func) {
+ delete _functions[i]->func;
+ _functions[i]->func = NULL;
+ }
+ }
+}
+
+int ScriptInterpreter::runFunction(ScriptFunction *scriptFunction) {
+ bool done = false;
+
+ int oldLocalStackPtr = _localStackPtr;
+ ScriptFunction *oldRunningFunction = _runningFunction;
+
+ // TODO: Also initialize _localStackPtr
+
+ _runningFunction = scriptFunction;
+ _runningFunction->jumpAbsolute(0);
+ while (!done) {
+ byte opcode = _runningFunction->readByte();
+ done = !execOpcode(opcode);
+ fflush(stdout);
+ }
+
+ _localStackPtr = oldLocalStackPtr;
+ _runningFunction = oldRunningFunction;
+
+ return 0;
+}
+
+void ScriptInterpreter::push(const ScriptValue &value) {
+ if (_stackPtr == ARRAYSIZE(_stack))
+ error("ScriptInterpreter::push() Stack overflow!\n");
+ _stack[_stackPtr++] = value;
+}
+
+void ScriptInterpreter::pop(ScriptValue &value) {
+ if (_stackPtr == 0)
+ error("ScriptInterpreter::pop() Stack underflow!\n");
+ value = _stack[_stackPtr--];
+}
+
+void ScriptInterpreter::dumpStack() {
+ printf("ScriptInterpreter::dumpStack()\n");
+ for (int i = 0; i < _stackPtr; i++) {
+ printf("%03d. type = %02d; value = %d\n", i, _stack[i].type, _stack[i].value);
+ }
+}
+
+void ScriptInterpreter::dumpRegisters() {
+ printf("ScriptInterpreter::dumpRegisters()\n");
+ for (int i = 0; i < ARRAYSIZE(_registers); i++) {
+ printf("%03d. type = %02d; value = %d\n", i, _registers[i].type, _registers[i].value);
+ }
+}
+
+void ScriptInterpreter::dumpGlobalVars() {
+ printf("ScriptInterpreter::dumpGlobalVars()\n");
+ for (int i = 0; i < ARRAYSIZE(_globalVars); i++) {
+ if (_globalVars[i].type != -1)
+ printf("%03d. type = %02d; value = %d\n", i, _globalVars[i].type, _globalVars[i].value);
+ }
+}
+
+int ScriptInterpreter::toInteger(const ScriptValue &value) {
+
+ switch (value.type) {
+
+ case kInteger:
+ return value.value;
+
+ default:
+ printf("ScriptInterpreter::toInteger() Invalid type %d!\n", value.type);
+ return 0;
+
+ }
+
+}
+
+const char *ScriptInterpreter::toString(const ScriptValue &value) {
+
+ switch (value.type) {
+
+ case kInteger:
+ return NULL;
+
+ case kConstString:
+ return _constStrings[value.value];
+
+ default:
+ printf("ScriptInterpreter::toString() Invalid type %d!\n", value.type);
+ return NULL;
+
+ }
+
+}
+
+const char *ScriptInterpreter::loadGlobalString(Common::File *fd) {
+ uint32 index = fd->readUint32LE();
+ if (index != 0xFFFFFFFF)
+ return getGlobalString(index);
+ else
+ return NULL;
+}
+
+void ScriptInterpreter::test() {
+}
+
+void ScriptInterpreter::loadValue(ScriptValue &value) {
+
+ value.type = (ScriptValueType)_runningFunction->readByte();
+
+ switch (value.type) {
+
+ case kGameVar:
+ case kInteger:
+ case kConstString:
+ case kDataRef:
+ case kLogicVar:
+ case kLogicVarRef:
+ case kKernelVar:
+ value.value = _runningFunction->readUint32();
+ break;
+
+ case kRegister:
+ value.value = _runningFunction->readByte();
+ break;
+
+ default:
+ printf("ScriptInterpreter::loadValue() Invalid value type %d!\n", value.type);
+
+ }
+
+}
+
+void ScriptInterpreter::copyValue(ScriptValue &destValue, ScriptValue &sourceValue) {
+
+ if (sourceValue.type == -1) {
+ printf("ScriptInterpreter::copyValue() Trying to read uninitialized value!\n");
+ }
+
+ switch (destValue.type) {
+
+ case kGameVar:
+ _globalVars[destValue.value] = sourceValue;
+ break;
+
+ case kRegister:
+ _registers[destValue.value] = sourceValue;
+ break;
+
+ case kLogicVar:
+ // TODO: Move to own method
+ if (sourceValue.type == kInteger) {
+ _logicGlobals[destValue.value] = sourceValue.value;
+ } else {
+ printf("ScriptInterpreter::copyValue() Invalid source value type %d!\n", sourceValue.type);
+ }
+ break;
+
+ case kKernelVar:
+ setKernelVar(destValue.value, sourceValue);
+ break;
+
+ default:
+ printf("ScriptInterpreter::copyValue() Invalid dest value type %d!\n", destValue.type);
+
+ }
+
+}
+
+void ScriptInterpreter::derefValue(ScriptValue &value) {
+
+ switch (value.type) {
+
+ case kGameVar:
+ value = _globalVars[value.value];
+ break;
+
+ case kInteger:
+ case kConstString:
+ case kDataRef:
+ case kLogicVarRef:
+ // These need no dereferencing
+ break;
+
+ case kRegister:
+ value = _registers[value.value];
+ break;
+
+ case kLogicVar:
+ // TODO: Move to own method
+ value = _logicGlobals[value.value];
+ break;
+
+ case kKernelVar:
+ getKernelVar(value.value, value);
+ break;
+
+ default:
+ printf("ScriptInterpreter::derefValue() Invalid value type %d!\n", value.type);
+
+ }
+
+}
+
+void ScriptInterpreter::callKernelFunction(uint32 index) {
+
+ printf("ScriptInterpreter::callKernelFunction() index = %d\n", index);
+
+ if (index > _kernelFunctionsMax) {
+ printf("ScriptInterpreter::callKernelFunction() Invalid kernel functionindex (%d)\n", index);
+ return;
+ }
+
+ printf("ScriptInterpreter::callKernelFunction() name = %s\n", _kernelFunctions[index].desc);
+
+ int args = (this->*(_kernelFunctions[index].proc))();
+ // Now remove values from the stack if the function used any
+ if (args > 4)
+ _stackPtr -= args - 4;
+
+ printf("-------------\n");
+
+}
+
+ScriptValue ScriptInterpreter::getArg(uint32 index) {
+ if (index < 4) {
+ return _registers[index];
+ } else {
+ index -= 4;
+ return _stack[_stackPtr - index - 1];
+ }
+}
+
+void ScriptInterpreter::dumpArgs(uint32 count) {
+ printf("ScriptInterpreter::dumpArgs() ");
+ for (uint32 i = 0; i < count; i++) {
+ ScriptValue argValue = getArg(i);
+ if (argValue.type == kConstString) {
+ printf("'%s'", toString(argValue));
+ } else {
+ printf("%d", argValue.value);
+ }
+ if (i + 1 < count)
+ printf(", ");
+ }
+ printf("\n");
+}
+
+void ScriptInterpreter::callFunction(uint32 index) {
+ // NOTE: This is a temporary hack for script functions not yet in the m4.dat
+ if (index == 0xFFFFFFFF)
+ return;
+ printf("ScriptInterpreter::callFunction() index = %d [%s]\n", index, _scriptFunctionNames[index].c_str());
+ fflush(stdout);
+ ScriptFunction *subFunction = loadFunction(index);
+ if (!subFunction) {
+ // This *should* never happen since the linker checks this
+ printf("ScriptInterpreter::callFunction() Function %d could not be loaded!\n", index);
+ return;
+ }
+ runFunction(subFunction);
+}
+
+bool ScriptInterpreter::execOpcode(byte opcode) {
+
+ printf("opcode = %d (%s)\n", opcode, opcodeNames[opcode]);
+
+ ScriptValue value1, value2, value3;
+ uint32 temp;
+
+ /* TODO: Put all opcodes into separate functions and into an array
+ (but only after all needed opcodes are known and frozen)
+ */
+
+ switch (opcode) {
+
+ case opRet:
+ return false;
+
+ case opPush:
+ loadValue(value1);
+ derefValue(value1);
+ push(value1);
+ return true;
+
+ case opPush0:
+ push(ScriptValue(0));
+ return true;
+
+ case opPush1:
+ push(ScriptValue(1));
+ return true;
+
+ case opPushNeg1:
+ push(ScriptValue(-1));
+ return true;
+
+ case opPop:
+ loadValue(value1);
+ pop(value2);
+ copyValue(value1, value2);
+ return true;
+
+ case opMov:
+ loadValue(value1);
+ loadValue(value2);
+ derefValue(value2);
+ copyValue(value1, value2);
+ return true;
+
+ // Possibly join all jump variants into one opcode
+
+ case opJmp:
+ temp = _runningFunction->readUint32();
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ return true;
+
+ case opJl:
+ temp = _runningFunction->readUint32();
+ if (_cmpFlags < 0) {
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ }
+ return true;
+
+ case opJle:
+ temp = _runningFunction->readUint32();
+ if (_cmpFlags <= 0) {
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ }
+ return true;
+
+ case opJg:
+ temp = _runningFunction->readUint32();
+ if (_cmpFlags > 0) {
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ }
+ return true;
+
+ case opJge:
+ temp = _runningFunction->readUint32();
+ if (_cmpFlags >= 0) {
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ }
+ return true;
+
+ case opJz:
+ temp = _runningFunction->readUint32();
+ if (_cmpFlags == 0) {
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ }
+ return true;
+
+ case opJnz:
+ temp = _runningFunction->readUint32();
+ if (_cmpFlags != 0) {
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ }
+ return true;
+
+ case opJmpByTable:
+ temp = _runningFunction->readUint32();
+ printf("-> index = %d\n", _registers[0].value);
+ _runningFunction->jumpRelative(_registers[0].value * 4);
+ temp = _runningFunction->readUint32();
+ printf("-> ofs = %08X\n", temp);
+ _runningFunction->jumpAbsolute(temp);
+ return true;
+
+ case opCmp:
+ loadValue(value1);
+ loadValue(value2);
+ derefValue(value1);
+ derefValue(value2);
+ if (value1.type != kInteger || value2.type != kInteger)
+ warning("ScriptInterpreter::execOpcode() Trying to compare non-integer values (%d, %d, line %d)!\n", value1.type, value2.type, _lineNum);
+ _cmpFlags = value1.value - value2.value;
+ printf("-> cmp %d, %d\n", value1.value, value2.value);
+ printf("-> _cmpFlags = %d\n", _cmpFlags);
+ return true;
+
+ case opCall:
+ temp = _runningFunction->readUint32();
+ callFunction(temp);
+ return true;
+
+ case opCallKernel:
+ temp = _runningFunction->readUint32();
+ callKernelFunction(temp);
+ return true;
+
+ case opInc:
+ loadValue(value1);
+ value2 = value1;
+ derefValue(value2);
+ value2.value++;
+ copyValue(value1, value2);
+ return true;
+
+ case opDec:
+ loadValue(value1);
+ value2 = value1;
+ derefValue(value2);
+ value2.value--;
+ copyValue(value1, value2);
+ return true;
+
+ case opAdd:
+ loadValue(value1);
+ value3 = value1;
+ loadValue(value2);
+ derefValue(value3);
+ derefValue(value2);
+ value3.value += value2.value;
+ copyValue(value1, value3);
+ return true;
+
+ case opSub:
+ loadValue(value1);
+ value3 = value1;
+ loadValue(value2);
+ derefValue(value3);
+ derefValue(value2);
+ value3.value -= value2.value;
+ copyValue(value1, value3);
+ return true;
+
+ case opDebug:
+ _lineNum = (int)_runningFunction->readUint32();
+ return true;
+
+ default:
+ printf("Invalid opcode %d!\n", opcode);
+ return false;
+
+ }
+
+ return false;
+
+}
+
+// Kernel functions
+
+#define STRING(arg) (toString(getArg(arg)))
+#define INTEGER(arg) (toInteger(getArg(arg)))
+#define DATA(arg, T) (toData<T>(getArg(arg)))
+#define RETURN(value) (_registers[0] = (value))
+
+int ScriptInterpreter::o1_handleStreamBreak() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_handlePlayBreak() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_dispatchTriggerOnSoundState() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_getTicks() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_setSoundVolume() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_getSoundStatus() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_getSoundDuration() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_setSeriesFrameRate() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_terminateMachine() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_sendWoodScriptMessage() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_runConversation() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_exportConversationValue() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_exportConversationPointer() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_playBreakSeries() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_hideWalker() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_showWalker() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_walk() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_overrideCrunchTime() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_addBlockingRect() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_setPlayerCommandsAllowed() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_getPlayerCommandsAllowed() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_setPlayerFacingAngle() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_disablePlayerFadeToBlack() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_enablePlayer() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_disablePlayer() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_freshenSentence() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_playerGiveItem() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_moveObject() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_setStopSoundsBetweenRooms() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_backupPalette() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_unloadWilburWalker() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_wilburTalk() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_wilburFinishedTalking() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_preloadSound() {
+ const char *name = STRING(0);
+ int room = INTEGER(1);
+ printf("name = %s; room = %d\n", name, room);
+ return 2;
+}
+
+int ScriptInterpreter::o1_unloadSound() {
+ const char *name = STRING(0);
+ int room = INTEGER(1);
+ printf("name = %s; room = %d\n", name, room);
+ return 2;
+}
+
+int ScriptInterpreter::o1_playSound() {
+ const char *name = STRING(0);
+ int channel = INTEGER(1);
+ int volume = INTEGER(2);
+ int trigger = INTEGER(3);
+ int room = INTEGER(4);
+ printf("name = %s; channel = %d; volume = %d; trigger = %d; room = %d\n",
+ name, channel, volume, trigger, room);
+
+ Common::String soundName = Common::String(name) + ".raw";
+ _vm->_sound->playVoice(soundName.c_str(), 100);
+
+ // HACK until fixed
+ _vm->_kernel->sendTrigger(trigger);
+
+ return 5;
+}
+
+int ScriptInterpreter::o1_playLoopingSound() {
+ const char *name = STRING(0);
+ int channel = INTEGER(1);
+ int volume = INTEGER(2);
+ int trigger = INTEGER(3);
+ int room = INTEGER(4);
+ printf("name = %s; channel = %d; volume = %d; trigger = %d; room = %d\n",
+ name, channel, volume, trigger, room);
+
+ // HACK until fixed
+ _vm->_kernel->sendTrigger(trigger);
+
+ return 5;
+}
+
+int ScriptInterpreter::o1_stopSound() {
+ int channel = INTEGER(0);
+ printf("channel = %d\n", channel);
+ return 1;
+}
+
+int ScriptInterpreter::o1_fadeSetStart() {
+ // skip arg 0: palette ptr
+ int percent = INTEGER(1);
+ printf("percent = %d\n", percent);
+ return 2;
+}
+
+int ScriptInterpreter::o1_fadeInit() {
+ // skip arg 0: palette ptr
+ int first = INTEGER(1);
+ int last = INTEGER(2);
+ int percent = INTEGER(3);
+ int ticks = INTEGER(4);
+ int trigger = INTEGER(5);
+ printf("first = %d; last = %d; percent = %d; ticks = %d; trigger = %d\n",
+ first, last, percent, ticks, trigger);
+
+ // HACK until palette fading is implemented
+ _vm->_kernel->sendTrigger(trigger);
+
+ return 6;
+}
+
+int ScriptInterpreter::o1_fadeToBlack() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_initPaletteCycle() {
+ int first = INTEGER(0);
+ int last = INTEGER(1);
+ int delay = INTEGER(2);
+ int ticks = INTEGER(3);
+ int trigger = INTEGER(4);
+ printf("first = %d; last = %d; delay = %d; ticks = %d; trigger = %d\n",
+ first, last, delay, ticks, trigger);
+
+ // HACK until palette cycling is implemented
+ _vm->_kernel->sendTrigger(trigger);
+
+ return 5;
+}
+
+int ScriptInterpreter::o1_stopPaletteCycle() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_hasPlayerSaid() {
+ const char *words[3];
+ for (int i = 0; i < 3; i++)
+ words[i] = STRING(i);
+ printf("'%s', '%s', '%s'\n", words[0], words[1], words[2]);
+
+ int result = _vm->_player->said(words[0], words[1], words[2]);
+
+ printf(" -> '%d'\n", result);
+ fflush(stdout);
+
+ RETURN(result);
+ return 3;
+}
+
+int ScriptInterpreter::o1_hasPlayerSaidAny() {
+ const char *words[10];
+ for (int i = 0; i < 10; i++)
+ words[i] = STRING(i);
+
+ printf("'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'\n",
+ words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]);
+
+ int result = _vm->_player->saidAny(words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]);
+ printf(" -> '%d'\n", result);
+ fflush(stdout);
+
+ RETURN(result);
+ return 10;
+}
+
+int ScriptInterpreter::o1_updatePlayerInfo() {
+ // skip arg 0: player info struct
+ return 1;
+}
+
+int ScriptInterpreter::o1_playerHotspotWalkOverride() {
+ int x1 = INTEGER(0);
+ int y1 = INTEGER(1);
+ int x2 = INTEGER(2);
+ int y2 = INTEGER(3);
+ printf("(%d, %d); (%d, %d)\n", x1, y1, x2, y2);
+ return 4;
+}
+
+int ScriptInterpreter::o1_playerHasItem() {
+ const char *name = STRING(0);
+ printf("item = '%s'\n", name);
+ // TODO
+ RETURN(0);
+ return 1;
+}
+
+int ScriptInterpreter::o1_setWalkerLocation() {
+ // skip arg 0: walker
+ int x = INTEGER(1);
+ int y = INTEGER(2);
+ printf("x = %d; y = %d\n", x, y);
+ return 3;
+}
+
+int ScriptInterpreter::o1_setWalkerFacing() {
+ // skip arg 0: walker
+ int facing = INTEGER(1);
+ printf("facing = %d\n", facing);
+ return 2;
+}
+
+int ScriptInterpreter::o1_setHotspot() {
+ // skip arg 0: hotspot list
+ const char *name = STRING(1);
+ int value = INTEGER(2);
+ printf("name = '%s' -> %d\n", name, value);
+
+ _vm->_scene->getSceneResources().hotspots->setActive(name, (value != 0));
+
+ return 2;
+}
+
+int ScriptInterpreter::o1_loadConversation() {
+ const char *name = STRING(0);
+ int trigger = INTEGER(1);
+ int flag = INTEGER(2);
+
+ // TODO; just to show something
+ _vm->_converse->startConversation(name);
+
+ return 3;
+}
+
+int ScriptInterpreter::o1_playSeries() {
+ const char *name = STRING(0);
+ int layer = INTEGER(1);
+ int flags = INTEGER(2);
+ int trigger = INTEGER(3);
+ int frameRate = INTEGER(4);
+ int loopCount = INTEGER(5);
+ int scale = INTEGER(6);
+ int x = INTEGER(7);
+ int y = INTEGER(8);
+ int firstFrame = INTEGER(9);
+ int lastFrame = INTEGER(10);
+
+ printf("name = %s; layer = %04X; flags = %08X; trigger = %d; frameRate = %d; loopCount = %d; scale = %d; x = %d; y = %d: firstFrame = %d; lastFrame = %d\n",
+ name, layer, flags, trigger, frameRate, loopCount, scale, x, y, firstFrame, lastFrame);
+ fflush(stdout);
+
+ // TODO: Return the machine to the script
+ _vm->_ws->playSeries(name, layer, flags, trigger, frameRate, loopCount, scale, x, y, firstFrame, lastFrame);
+
+ return 11;
+}
+
+int ScriptInterpreter::o1_showSeries() {
+ const char *name = STRING(0);
+ int layer = INTEGER(1);
+ int flags = INTEGER(2);
+ int trigger = INTEGER(3);
+ int duration = INTEGER(4);
+ int frameIndex = INTEGER(5);
+ int scale = INTEGER(6);
+ int x = INTEGER(7);
+ int y = INTEGER(8);
+
+ printf("name = %s; layer = %04X; flags = %08X; trigger = %d; duration = %d; frameIndex = %d; scale = %d; x = %d; y = %d\n",
+ name, layer, flags, trigger, duration, frameIndex, scale, x, y);
+ fflush(stdout);
+
+ // TODO: Return the machine to the script
+ _vm->_ws->showSeries(name, layer, flags, trigger, duration, frameIndex, scale, x, y);
+
+ return 9;
+}
+
+int ScriptInterpreter::o1_loadSeries() {
+ const char *name = STRING(0);
+ int hash = INTEGER(1);
+ // skip arg 3: palette ptr
+
+ printf("name = %s; hash = %d\n", name, hash);
+ fflush(stdout);
+
+ int result = _vm->_ws->loadSeries(name, hash, NULL);
+
+ RETURN(result);
+ return 3;
+}
+
+int ScriptInterpreter::o1_unloadSeries() {
+ return 0;
+}
+
+int ScriptInterpreter::o1_preloadBreakSeries() {
+ const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList);
+ return 1;
+}
+
+int ScriptInterpreter::o1_unloadBreakSeries() {
+ const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList);
+ return 1;
+}
+
+int ScriptInterpreter::o1_startBreakSeries() {
+ const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList);
+ return 1;
+}
+
+int ScriptInterpreter::o1_globalTriggerProc() {
+ int value1 = INTEGER(0);
+ int value2 = INTEGER(1);
+ int value3 = INTEGER(2);
+ printf("%d; %d; %d\n", value1, value2, value3);
+ return 3;
+}
+
+int ScriptInterpreter::o1_triggerTimerProc() {
+ int value1 = INTEGER(0);
+ int value2 = INTEGER(1);
+ int value3 = INTEGER(2);
+ printf("%d; %d; %d\n", value1, value2, value3);
+ return 3;
+}
+
+int ScriptInterpreter::o1_dispatchTrigger() {
+ int trigger = INTEGER(0);
+ printf("trigger = %d\n", trigger);
+
+ _vm->_kernel->sendTrigger(trigger);
+ //g_system->delayMillis(5000);
+
+ return 1;
+}
+
+int ScriptInterpreter::o1_getRangedRandomValue() {
+ int minValue = INTEGER(0);
+ int maxValue = INTEGER(1);
+ RETURN(_vm->imath_ranged_rand(minValue, maxValue));
+ return 2;
+}
+
+int ScriptInterpreter::o1_wilburSaid() {
+ const SaidArray& saidArray = DATA(0, SaidArray);
+
+ int result = 0;
+
+ // NOTE: The "Common::String soundName" stuff is just temporary until playVoice is fixed.
+
+ for (int i = 0; i < saidArray.size(); i++) {
+ SaidArrayItem *item = saidArray[i];
+
+ if (_vm->_player->said("LOOK AT", item->itemName) && item->digiNameLook) {
+ printf(" -> LOOK AT: '%s'\n", item->digiNameLook);
+ Common::String soundName = Common::String(item->digiNameLook) + ".raw";
+ _vm->_sound->playVoice(soundName.c_str(), 100);
+ result = 1;
+ break;
+ }
+
+ if (_vm->_player->said("TAKE", item->itemName) && item->digiNameTake) {
+ printf(" -> TAKE: '%s'\n", item->digiNameTake);
+ Common::String soundName = Common::String(item->digiNameTake) + ".raw";
+ _vm->_sound->playVoice(soundName.c_str(), 100);
+ result = 1;
+ break;
+ }
+
+ if (_vm->_player->said("GEAR", item->itemName) && item->digiNameGear) {
+ printf(" -> GEAR: '%s'\n", item->digiNameGear);
+ Common::String soundName = Common::String(item->digiNameGear) + ".raw";
+ _vm->_sound->playVoice(soundName.c_str(), 100);
+ result = 1;
+ break;
+ }
+
+ /*
+ printf("##### itemName = '%s'; digiNameLook = %s; digiNameTake = %s; digiNameGear = %s\n",
+ item->itemName, item->digiNameLook, item->digiNameTake, item->digiNameGear);
+ */
+ }
+ printf(" -> '%d'\n", result);
+ fflush(stdout);
+
+ RETURN(result);
+ return 1;
+}
+
+int ScriptInterpreter::o1_wilburParse() {
+ const ParserArray& parserArray = DATA(0, ParserArray);
+ RETURN(0);
+ return 1;
+}
+
+int ScriptInterpreter::o1_wilburSpeech() {
+ const char *name = STRING(0);
+ int trigger = INTEGER(1);
+ int room = INTEGER(2);
+ int flag = INTEGER(3);
+ int volume = INTEGER(4);
+ int slot = INTEGER(5);
+
+ printf("%s; %d; %d; %d; %d; %d\n", name, trigger, room, flag, volume, slot);
+ fflush(stdout);
+ //g_system->delayMillis(5000);
+
+ KernelTriggerType oldTriggerMode = _vm->_kernel->triggerMode;
+
+ // TODO
+ Common::String soundName = Common::String(name) + ".raw";
+ _vm->_sound->playVoice(soundName.c_str(), 100);
+
+ _vm->_kernel->triggerMode = oldTriggerMode;
+
+ return 6;
+}
+
+// Kernel vars
+
+void ScriptInterpreter::getKernelVar(int index, ScriptValue &value) {
+
+ printf("ScriptInterpreter::getKernelVar() index = %d\n", index);
+
+ if (index > _kernelVarsMax) {
+ printf("ScriptInterpreter::getKernelVar() Invalid kernel var index %d!\n", index);
+ return;
+ }
+
+ printf("ScriptInterpreter::getKernelVar() name = %s\n", _kernelVars[index].desc);
+
+ ScriptKernelVariable var = _kernelVars[index].var;
+
+ switch (var) {
+
+ case kKernelTrigger:
+ value = _vm->_kernel->trigger;
+ break;
+
+ case kKernelTriggerMode:
+ value = (int)_vm->_kernel->triggerMode;
+ break;
+
+ case kKernelContinueHandlingTrigger:
+ value = (int)_vm->_kernel->daemonTriggerAvailable;
+ break;
+
+ case kGameVersion:
+ // TODO
+ value = 0;
+ break;
+
+ case kGameLanguage:
+ // TODO
+ value = 0;
+ break;
+
+ case kGameNewRoom:
+ // TODO
+ value = 0;
+ break;
+
+ case kPlayerCommandReady:
+ value = (int)_vm->_player->commandReady;
+ break;
+
+ default:
+ printf("ScriptInterpreter::getKernelVar() Invalid kernel var %d!\n", var);
+ //g_system->delayMillis(2000);
+
+ }
+
+}
+
+void ScriptInterpreter::setKernelVar(int index, const ScriptValue &value) {
+
+ printf("ScriptInterpreter::setKernelVar() index = %d\n", index);
+
+ if (index > _kernelVarsMax) {
+ printf("ScriptInterpreter::setKernelVar() Invalid kernel var index %d!\n", index);
+ return;
+ }
+
+ printf("ScriptInterpreter::setKernelVar() name = %s\n", _kernelVars[index].desc);
+
+ ScriptKernelVariable var = _kernelVars[index].var;
+
+ switch (var) {
+
+ case kKernelTrigger:
+ _vm->_kernel->trigger = toInteger(value);
+ printf("kKernelTrigger -> %d\n", toInteger(value));
+ break;
+
+ case kKernelTriggerMode:
+ _vm->_kernel->triggerMode = (KernelTriggerType)toInteger(value);
+ printf("kKernelTrigger -> %d\n", toInteger(value));
+ break;
+
+ case kKernelContinueHandlingTrigger:
+ _vm->_kernel->daemonTriggerAvailable = (toInteger(value) != 0);
+ printf("kKernelContinueHandlingTrigger -> %d\n", toInteger(value));
+ break;
+
+ case kGameNewRoom:
+ _vm->_kernel->newRoom = toInteger(value);
+ printf("kGameNewRoom -> %d\n", toInteger(value));
+ break;
+
+ case kPlayerCommandReady:
+ // TODO
+ printf("kPlayerCommandReady -> %d\n", toInteger(value));
+ break;
+
+ default:
+ printf("ScriptInterpreter::setKernelVar() Invalid kernel var %d!\n", var);
+ //g_system->delayMillis(2000);
+
+ }
+
+}
+
+} // End of namespace M4
diff --git a/engines/m4/script.h b/engines/m4/script.h
new file mode 100644
index 0000000000..a59bf46e86
--- /dev/null
+++ b/engines/m4/script.h
@@ -0,0 +1,457 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_SCRIPT_H
+#define M4_SCRIPT_H
+
+#include "common/file.h"
+#include "common/stream.h"
+#include "common/hashmap.h"
+#include "common/str.h"
+#include "common/stack.h"
+
+#include "m4/woodscript.h"
+
+namespace M4 {
+
+const unsigned long kScriptFileMagic = 0x5845344D;
+const unsigned long kScriptFileVersion = 1;
+
+enum ScriptValueType {
+ kInteger,
+ kConstString,
+ kLogicVar,
+ kLogicVarRef,
+ kGameVar,
+ kKernelVar,
+ kDataRef,
+ kRegister,
+ kStackVar
+};
+
+enum ScriptDataType {
+ kStreamBreakSeries,
+ kStreamPlaySeries,
+ kSaidArray,
+ kParserArray,
+ kSpeechArray,
+ kCreditsArray,
+ kInvObj,
+ kMineRoom,
+ kButtonItem
+};
+
+class ScriptInterpreter;
+
+class StringTable {
+public:
+ StringTable();
+ ~StringTable();
+ void load(Common::File *fd);
+ int size() { return _strings.size(); }
+ const char *operator[](uint32 index) const {
+ assert(index < _strings.size() );
+ return _strings[index];
+ }
+protected:
+ Common::Array<const char*> _strings;
+ char *_stringsData;
+};
+
+struct ScriptValue {
+
+ ScriptValueType type;
+
+ union {
+ int value;
+ };
+
+ ScriptValue() : type(kInteger), value(0) {};
+ ScriptValue(ScriptValueType itype, int ivalue) : type(itype), value(ivalue) {};
+
+ ScriptValue(const int intValue) : type(kInteger), value(intValue) {};
+
+ ScriptValue& operator=(const int intValue) {
+ type = kInteger;
+ value = intValue;
+ return *this;
+ }
+
+};
+
+class ScriptDataItem {
+public:
+ ScriptDataItem() : _inter(NULL) {}
+ ScriptDataItem(ScriptInterpreter *inter) : _inter(inter) {}
+ virtual ~ScriptDataItem() {}
+ virtual void load(Common::File *fd) = 0;
+ static int type() { return -1; }
+protected:
+ ScriptInterpreter *_inter;
+};
+
+class ScriptDataCache {
+public:
+ ScriptDataCache(ScriptInterpreter *inter) : _inter(inter) {
+ }
+ ~ScriptDataCache() {
+ clear();
+ }
+ template<class T>
+ T *load(Common::File *fd, uint32 ofs) {
+ T *item;
+ if (_cache.contains(ofs)) {
+ item = (T*)(_cache[ofs]);
+ } else {
+ item = new T(_inter);
+ fd->seek(ofs + 4); // "+4" skips the data size
+ item->load(fd);
+ _cache[ofs] = item;
+ }
+ return item;
+ }
+ void clear() {
+ // TODO: Free all cached items
+ }
+protected:
+ typedef Common::HashMap<uint32, ScriptDataItem*> CacheMap;
+ CacheMap _cache;
+ ScriptInterpreter *_inter;
+};
+
+struct SeriesStreamBreakItem {
+ int frameNum;
+ const char *digiName;
+ int digiChannel;
+ int digiVolume;
+ int trigger;
+ int flags;
+ ScriptValue variable;
+ int value;
+};
+
+class SeriesStreamBreakList : public ScriptDataItem {
+public:
+ SeriesStreamBreakList(ScriptInterpreter *inter) : ScriptDataItem(inter) {}
+ ~SeriesStreamBreakList();
+ void load(Common::File *fd);
+ int size() const { return _items.size(); }
+ SeriesStreamBreakItem *operator[](int index) const { return _items[index]; }
+ static int type() { return 0; }
+protected:
+ Common::Array<SeriesStreamBreakItem*> _items;
+};
+
+struct SaidArrayItem {
+ const char *itemName;
+ const char *digiNameLook;
+ const char *digiNameTake;
+ const char *digiNameGear;
+};
+
+class SaidArray : public ScriptDataItem {
+public:
+ SaidArray(ScriptInterpreter *inter) : ScriptDataItem(inter) {}
+ ~SaidArray();
+ void load(Common::File *fd);
+ int size() const { return _items.size(); }
+ SaidArrayItem *operator[](int index) const { return _items[index]; }
+ static int type() { return 2; }
+protected:
+ Common::Array<SaidArrayItem*> _items;
+};
+
+struct ParserArrayItem {
+ const char *w0;
+ const char *w1;
+ int trigger;
+ ScriptValue testVariable;
+ int testValue;
+ ScriptValue variable;
+ int value;
+};
+
+class ParserArray : public ScriptDataItem {
+public:
+ ParserArray(ScriptInterpreter *inter) : ScriptDataItem(inter) {}
+ ~ParserArray();
+ void load(Common::File *fd);
+ int size() const { return _items.size(); }
+ ParserArrayItem *operator[](int index) const { return _items[index]; }
+ static int type() { return 3; }
+protected:
+ Common::Array<ParserArrayItem*> _items;
+};
+
+class ScriptFunction {
+public:
+ ScriptFunction(ScriptInterpreter *inter);
+ ~ScriptFunction();
+ void load(Common::File *fd);
+ void jumpAbsolute(uint32 ofs);
+ void jumpRelative(int32 ofs);
+ byte readByte();
+ uint32 readUint32();
+protected:
+ ScriptInterpreter *_inter;
+ Common::MemoryReadStream *_code;
+};
+
+struct ScriptFunctionEntry {
+ uint32 offset;
+ ScriptFunction *func;
+ ScriptFunctionEntry(uint32 funcOffset) : offset(funcOffset), func(NULL) {
+ }
+};
+
+struct ScriptDataEntry {
+ uint32 offset;
+ ScriptDataType type;
+ ScriptDataEntry(uint32 dataOffset, ScriptDataType dataType) : offset(dataOffset), type(dataType) {
+ }
+};
+
+enum ScriptKernelVariable {
+ kGameLanguage,
+ kGameVersion,
+ kGameCurrentRoom,
+ kGameNewRoom,
+ kGamePreviousRoom,
+ kGameNewSection,
+ kKernelTrigger,
+ kKernelTriggerMode,
+ kKernelFirstFade,
+ kKernelSuppressFadeUp,
+ kKernelContinueHandlingTrigger,
+ kKernelUseDebugMonitor,
+ kPlayerPosX,
+ kPlayerPosY,
+ kPlayerFacing,
+ kPlayerScale,
+ kPlayerDepth,
+ kPlayerWalkX,
+ kPlayerWalkY,
+ kPlayerReadyToWalk,
+ kPlayerNeedToWalk,
+ kPlayerCommandReady,
+ kPlayerWalkerInThisScene,
+ kPlayerVerb,
+ kWalkerInitialized,
+ kCallDaemonEveryLoop,
+ kConvCurrentTalker,
+ kConvCurrentNode,
+ kConvCurrentEntry,
+ kConvSoundToPlay,
+ kInterfaceVisible
+};
+
+class ScriptInterpreter {
+public:
+ ScriptInterpreter(M4Engine *vm);
+ ~ScriptInterpreter();
+ /* Opens a M4 program file */
+ void open(const char *filename);
+ void close();
+ /* Loads a function via the index. Creates the function object if it's not already loaded. */
+ ScriptFunction *loadFunction(uint32 index);
+ /* Loads a function via the exported name. */
+ ScriptFunction *loadFunction(const Common::String &name);
+ /* Unload all loaded functions.
+ This should be called before entering a new room to free unused functions. */
+ void unloadFunctions();
+ //TODO void unloadData();
+ /* Executes a function. */
+ int runFunction(ScriptFunction *scriptFunction);
+
+ void push(const ScriptValue &value);
+ void pop(ScriptValue &value);
+ void dumpStack();
+ void dumpRegisters();
+ void dumpGlobalVars();
+
+ int toInteger(const ScriptValue &value);
+
+ const char *toString(const ScriptValue &value);
+
+ // Is this ok?
+ template<class T>
+ const T& toData(const ScriptValue &value) {
+ printf("ScriptInterpreter::toData() index = %d; type = %d; max = %d\n", value.value, _data[value.value]->type, _data.size());
+ assert((uint32)value.value < _data.size());
+ return *(_dataCache->load<T>(_scriptFile, _data[value.value]->offset));
+ }
+
+ const char *getGlobalString(int index) const {
+ return _constStrings[index];
+ }
+
+ const char *loadGlobalString(Common::File *fd);
+
+ void test();
+
+protected:
+
+ M4Engine *_vm;
+
+ typedef Common::HashMap<Common::String, uint32, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FunctionNameMap;
+ Common::File *_scriptFile;
+ /* An array of offset/ScriptFunction* pairs for each script function */
+ Common::Array<ScriptFunctionEntry*> _functions;
+
+ // DEBUG only
+ Common::Array<Common::String> _scriptFunctionNames;
+
+ Common::Array<ScriptDataEntry*> _data;
+ /* Maps function name -> index of function in _functions array */
+ FunctionNameMap _functionNames;
+ StringTable _constStrings;
+ /* The currently running function */
+ ScriptFunction *_runningFunction;
+ int _localStackPtr;
+
+ ScriptValue _registers[8];
+
+ ScriptValue _stack[512];
+ int _stackPtr;
+
+ int _globalVarCount;
+ ScriptValue _globalVars[1024];
+
+ int _logicGlobals[512];
+
+ int _cmpFlags;
+
+ ScriptDataCache *_dataCache;
+
+ int _lineNum;
+
+ typedef int (ScriptInterpreter::*KernelFunction)();
+ struct KernelFunctionEntry {
+ KernelFunction proc;
+ const char *desc;
+ };
+ const KernelFunctionEntry *_kernelFunctions;
+ uint16 _kernelFunctionsMax;
+
+ struct KernelVariableEntry {
+ ScriptKernelVariable var;
+ const char *desc;
+ };
+ const KernelVariableEntry *_kernelVars;
+ int16 _kernelVarsMax;
+
+ void initScriptKernel();
+
+ void loadValue(ScriptValue &value);
+ void writeValue(ScriptValue &value);
+ void copyValue(ScriptValue &destValue, ScriptValue &sourceValue);
+ void derefValue(ScriptValue &value);
+
+ void callKernelFunction(uint32 index);
+ ScriptValue getArg(uint32 index);
+ void dumpArgs(uint32 count);
+
+ void callFunction(uint32 index);
+
+ bool execOpcode(byte opcode);
+
+ // Kernel functions
+ int o1_handleStreamBreak();
+ int o1_handlePlayBreak();
+ int o1_dispatchTriggerOnSoundState();
+ int o1_getRangedRandomValue();
+ int o1_getTicks();
+ int o1_preloadSound();
+ int o1_unloadSound();
+ int o1_stopSound();
+ int o1_playSound();
+ int o1_playLoopingSound();
+ int o1_setSoundVolume();
+ int o1_getSoundStatus();
+ int o1_getSoundDuration();
+ int o1_loadSeries();
+ int o1_unloadSeries();
+ int o1_showSeries();
+ int o1_playSeries();
+ int o1_setSeriesFrameRate();
+ int o1_playBreakSeries();
+ int o1_preloadBreakSeries();
+ int o1_unloadBreakSeries();
+ int o1_startBreakSeries();
+ int o1_dispatchTrigger();
+ int o1_terminateMachine();
+ int o1_sendWoodScriptMessage();
+ int o1_runConversation();
+ int o1_loadConversation();
+ int o1_exportConversationValue();
+ int o1_exportConversationPointer();
+ int o1_fadeInit();
+ int o1_fadeSetStart();
+ int o1_fadeToBlack();
+ int o1_initPaletteCycle();
+ int o1_stopPaletteCycle();
+ int o1_setHotspot();
+ int o1_hideWalker();
+ int o1_showWalker();
+ int o1_setWalkerLocation();
+ int o1_setWalkerFacing();
+ int o1_walk();
+ int o1_overrideCrunchTime();
+ int o1_addBlockingRect();
+ int o1_triggerTimerProc();
+ int o1_setPlayerCommandsAllowed();
+ int o1_getPlayerCommandsAllowed();
+ int o1_updatePlayerInfo();
+ int o1_hasPlayerSaid();
+ int o1_hasPlayerSaidAny();
+ int o1_playerHotspotWalkOverride();
+ int o1_setPlayerFacingAngle();
+ int o1_disablePlayerFadeToBlack();
+ int o1_enablePlayer();
+ int o1_disablePlayer();
+ int o1_freshenSentence();
+ int o1_playerHasItem();
+ int o1_playerGiveItem();
+ int o1_moveObject();
+ int o1_setStopSoundsBetweenRooms();
+ int o1_backupPalette();
+ int o1_unloadWilburWalker();
+ int o1_globalTriggerProc();
+ int o1_wilburSpeech();
+ int o1_wilburSaid();
+ int o1_wilburParse();
+ int o1_wilburTalk();
+ int o1_wilburFinishedTalking();
+ //int ();
+
+ // Kernel vars
+ void getKernelVar(int index, ScriptValue &value);
+ void setKernelVar(int index, const ScriptValue &value);
+
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/scripttab.h b/engines/m4/scripttab.h
new file mode 100644
index 0000000000..b6156f0b82
--- /dev/null
+++ b/engines/m4/scripttab.h
@@ -0,0 +1,136 @@
+/* This file has been autogenerated by the linker.
+ Do not edit it or merge it with script.cpp! */
+#define FUNCTION(x) { &ScriptInterpreter::x, #x }
+ static KernelFunctionEntry kernelFunctions[] = {
+ /* 000 */
+ FUNCTION(o1_handleStreamBreak),
+ FUNCTION(o1_handlePlayBreak),
+ FUNCTION(o1_dispatchTriggerOnSoundState),
+ FUNCTION(o1_getRangedRandomValue),
+ /* 004 */
+ FUNCTION(o1_getTicks),
+ FUNCTION(o1_preloadSound),
+ FUNCTION(o1_unloadSound),
+ FUNCTION(o1_stopSound),
+ /* 008 */
+ FUNCTION(o1_playSound),
+ FUNCTION(o1_playLoopingSound),
+ FUNCTION(o1_setSoundVolume),
+ FUNCTION(o1_getSoundStatus),
+ /* 012 */
+ FUNCTION(o1_getSoundDuration),
+ FUNCTION(o1_loadSeries),
+ FUNCTION(o1_unloadSeries),
+ FUNCTION(o1_showSeries),
+ /* 016 */
+ FUNCTION(o1_playSeries),
+ FUNCTION(o1_setSeriesFrameRate),
+ FUNCTION(o1_playBreakSeries),
+ FUNCTION(o1_preloadBreakSeries),
+ /* 020 */
+ FUNCTION(o1_unloadBreakSeries),
+ FUNCTION(o1_startBreakSeries),
+ FUNCTION(o1_dispatchTrigger),
+ FUNCTION(o1_terminateMachine),
+ /* 024 */
+ FUNCTION(o1_sendWoodScriptMessage),
+ FUNCTION(o1_runConversation),
+ FUNCTION(o1_runConversation),
+ FUNCTION(o1_loadConversation),
+ /* 028 */
+ FUNCTION(o1_exportConversationValue),
+ FUNCTION(o1_exportConversationPointer),
+ FUNCTION(o1_runConversation),
+ FUNCTION(o1_fadeInit),
+ /* 032 */
+ FUNCTION(o1_fadeSetStart),
+ FUNCTION(o1_fadeToBlack),
+ FUNCTION(o1_initPaletteCycle),
+ FUNCTION(o1_stopPaletteCycle),
+ /* 036 */
+ FUNCTION(o1_setHotspot),
+ FUNCTION(o1_hideWalker),
+ FUNCTION(o1_showWalker),
+ FUNCTION(o1_setWalkerLocation),
+ /* 040 */
+ FUNCTION(o1_setWalkerFacing),
+ FUNCTION(o1_walk),
+ FUNCTION(o1_overrideCrunchTime),
+ FUNCTION(o1_addBlockingRect),
+ /* 044 */
+ FUNCTION(o1_triggerTimerProc),
+ FUNCTION(o1_setPlayerCommandsAllowed),
+ FUNCTION(o1_getPlayerCommandsAllowed),
+ FUNCTION(o1_updatePlayerInfo),
+ /* 048 */
+ FUNCTION(o1_hasPlayerSaid),
+ FUNCTION(o1_hasPlayerSaidAny),
+ FUNCTION(o1_playerHotspotWalkOverride),
+ FUNCTION(o1_setPlayerFacingAngle),
+ /* 052 */
+ FUNCTION(o1_disablePlayerFadeToBlack),
+ FUNCTION(o1_enablePlayer),
+ FUNCTION(o1_disablePlayer),
+ FUNCTION(o1_freshenSentence),
+ /* 056 */
+ FUNCTION(o1_playerHasItem),
+ FUNCTION(o1_playerGiveItem),
+ FUNCTION(o1_moveObject),
+ FUNCTION(o1_setStopSoundsBetweenRooms),
+ /* 060 */
+ FUNCTION(o1_backupPalette),
+ FUNCTION(o1_unloadWilburWalker),
+ FUNCTION(o1_globalTriggerProc),
+ FUNCTION(o1_wilburSpeech),
+ /* 064 */
+ FUNCTION(o1_wilburParse),
+ FUNCTION(o1_wilburSaid),
+ FUNCTION(o1_wilburTalk),
+ FUNCTION(o1_wilburFinishedTalking)
+ };
+#undef FUNCTION
+
+#define VARIABLE(x) { x, #x }
+ static KernelVariableEntry kernelVars[] = {
+ /* 000 */
+ VARIABLE(kGameLanguage),
+ VARIABLE(kGameVersion),
+ VARIABLE(kGameCurrentRoom),
+ VARIABLE(kGameNewRoom),
+ /* 004 */
+ VARIABLE(kGamePreviousRoom),
+ VARIABLE(kGameNewSection),
+ VARIABLE(kKernelTrigger),
+ VARIABLE(kKernelTriggerMode),
+ /* 008 */
+ VARIABLE(kKernelFirstFade),
+ VARIABLE(kKernelSuppressFadeUp),
+ VARIABLE(kKernelContinueHandlingTrigger),
+ VARIABLE(kKernelUseDebugMonitor),
+ /* 012 */
+ VARIABLE(kPlayerPosX),
+ VARIABLE(kPlayerPosY),
+ VARIABLE(kPlayerFacing),
+ VARIABLE(kPlayerScale),
+ /* 016 */
+ VARIABLE(kPlayerDepth),
+ VARIABLE(kPlayerWalkX),
+ VARIABLE(kPlayerWalkY),
+ VARIABLE(kPlayerReadyToWalk),
+ /* 020 */
+ VARIABLE(kPlayerNeedToWalk),
+ VARIABLE(kPlayerCommandReady),
+ VARIABLE(kPlayerWalkerInThisScene),
+ VARIABLE(kPlayerVerb),
+ /* 024 */
+ VARIABLE(kWalkerInitialized),
+ VARIABLE(kCallDaemonEveryLoop),
+ VARIABLE(kConvCurrentTalker),
+ VARIABLE(kConvCurrentNode),
+ /* 028 */
+ VARIABLE(kConvCurrentEntry),
+ VARIABLE(kConvSoundToPlay),
+ VARIABLE(kInterfaceVisible)
+ };
+#undef VARIABLE
+
diff --git a/engines/m4/sound.cpp b/engines/m4/sound.cpp
new file mode 100644
index 0000000000..3091592313
--- /dev/null
+++ b/engines/m4/sound.cpp
@@ -0,0 +1,283 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/m4.h"
+#include "m4/sound.h"
+#include "m4/compression.h"
+
+#include "sound/audiostream.h"
+#include "sound/mixer.h"
+#include "common/stream.h"
+
+namespace M4 {
+
+Sound::Sound(M4Engine *vm, Audio::Mixer *mixer, int volume) :
+ _vm(vm), _mixer(mixer) {
+
+ for (int i = 0; i < SOUND_HANDLES; i++)
+ _handles[i].type = kFreeHandle;
+
+ _dsrFileLoaded = false;
+
+ setVolume(volume);
+}
+
+Sound::~Sound() {
+ unloadDSRFile();
+}
+
+SndHandle *Sound::getHandle() {
+ for (int i = 0; i < SOUND_HANDLES; i++) {
+ if (_handles[i].type == kFreeHandle)
+ return &_handles[i];
+
+ if (!_mixer->isSoundHandleActive(_handles[i].handle)) {
+ _handles[i].type = kFreeHandle;
+ return &_handles[i];
+ }
+ }
+
+ error("Sound::getHandle(): Too many sound handles");
+
+ return NULL;
+}
+
+bool Sound::isHandleActive(SndHandle *handle) {
+ return (_mixer->isSoundHandleActive(handle->handle));
+}
+
+void Sound::playSound(const char *soundName, int volume, bool loop, int channel) {
+ byte flags;
+ Common::SeekableReadStream *soundStream = _vm->res()->get(soundName);
+ SndHandle *handle;
+ if (channel < 0) {
+ handle = getHandle();
+ } else {
+ if (_handles[channel].type == kFreeHandle) {
+ handle = &_handles[channel];
+ } else {
+ warning("Attempted to play a sound on a channel that isn't free");
+ return;
+ }
+ }
+
+ int bufferSize = soundStream->size();
+ byte *buffer = new byte[bufferSize];
+ soundStream->read(buffer, bufferSize);
+ _vm->res()->toss(soundName);
+
+ handle->type = kEffectHandle;
+ flags = Audio::Mixer::FLAG_AUTOFREE;
+ flags |= Audio::Mixer::FLAG_UNSIGNED;
+
+ if (loop)
+ flags |= Audio::Mixer::FLAG_LOOP;
+
+ _vm->res()->toss(soundName);
+
+ // Sound format is 8bit mono, unsigned, 11025kHz
+ _mixer->playRaw(Audio::Mixer::kSFXSoundType, &handle->handle, buffer, bufferSize, 11025, flags, -1, volume);
+}
+
+void Sound::pauseSound() {
+ for (int i = 0; i < SOUND_HANDLES; i++) {
+ if (_handles[i].type == kEffectHandle)
+ _mixer->pauseHandle(_handles[i].handle, true);
+ }
+}
+
+void Sound::resumeSound() {
+ for (int i = 0; i < SOUND_HANDLES; i++) {
+ if (_handles[i].type == kEffectHandle)
+ _mixer->pauseHandle(_handles[i].handle, false);
+ }
+}
+
+void Sound::stopSound(int channel) {
+ if (channel >= 0) {
+ if (_handles[channel].type == kEffectHandle) {
+ _mixer->stopHandle(_handles[channel].handle);
+ _handles[channel].type = kFreeHandle;
+ return;
+ } else {
+ warning("Attempted to stop a sound on a channel that is already free");
+ return;
+ }
+ }
+
+ for (int i = 0; i < SOUND_HANDLES; i++) {
+ if (_handles[i].type == kEffectHandle) {
+ _mixer->stopHandle(_handles[i].handle);
+ _handles[i].type = kFreeHandle;
+ }
+ }
+}
+
+void Sound::playVoice(const char *soundName, int volume) {
+ byte flags;
+ Common::SeekableReadStream *soundStream = _vm->res()->get(soundName);
+ SndHandle *handle = getHandle();
+ byte *buffer;
+
+ buffer = new byte[soundStream->size()];
+ soundStream->read(buffer, soundStream->size());
+
+ handle->type = kEffectHandle;
+ flags = Audio::Mixer::FLAG_AUTOFREE;
+ flags |= Audio::Mixer::FLAG_UNSIGNED;
+
+ _vm->res()->toss(soundName);
+
+ // Voice format is 8bit mono, unsigned, 11025kHz
+ _mixer->playRaw(Audio::Mixer::kSFXSoundType, &handle->handle, buffer, soundStream->size(), 11025, flags, -1, volume);
+}
+
+void Sound::pauseVoice() {
+ for (int i = 0; i < SOUND_HANDLES; i++)
+ if (_handles[i].type == kVoiceHandle)
+ _mixer->pauseHandle(_handles[i].handle, true);
+}
+
+void Sound::resumeVoice() {
+ for (int i = 0; i < SOUND_HANDLES; i++)
+ if (_handles[i].type == kVoiceHandle)
+ _mixer->pauseHandle(_handles[i].handle, false);
+}
+
+void Sound::stopVoice() {
+ for (int i = 0; i < SOUND_HANDLES; i++)
+ if (_handles[i].type == kVoiceHandle) {
+ _mixer->stopHandle(_handles[i].handle);
+ _handles[i].type = kFreeHandle;
+ }
+}
+
+void Sound::stopAll() {
+ stopVoice();
+ stopSound();
+}
+
+void Sound::setVolume(int volume) {
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume);
+}
+
+void Sound::loadDSRFile(const char *fileName) {
+ if (_dsrFileLoaded)
+ unloadDSRFile();
+
+ Common::SeekableReadStream *fileStream = _vm->res()->get(fileName);
+
+ sprintf(_dsrFile.fileName, "%s", fileName);
+
+ // Read header
+ _dsrFile.entryCount = fileStream->readUint16LE();
+ //printf("DSR has %i entries\n", _dsrFile.entryCount);
+
+ for (int i = 0; i < _dsrFile.entryCount; i++) {
+ DSREntry* newEntry = new DSREntry();
+ newEntry->frequency = fileStream->readUint16LE();
+ newEntry->channels = fileStream->readUint32LE();
+ newEntry->compSize = fileStream->readUint32LE();
+ newEntry->uncompSize = fileStream->readUint32LE();
+ newEntry->offset = fileStream->readUint32LE();
+ _dsrFile.dsrEntries.push_back(newEntry);
+
+ /*
+ printf("%i: ", i);
+ printf("frequency: %i ", newEntry->frequency);
+ printf("channels: %i ", newEntry->channels);
+ printf("comp: %i ", newEntry->compSize);
+ printf("uncomp: %i ", newEntry->uncompSize);
+ printf("offset: %i ", newEntry->offset);
+ printf("\n");
+ */
+ }
+
+ _vm->res()->toss(fileName);
+
+ _dsrFileLoaded = true;
+}
+
+void Sound::unloadDSRFile() {
+ if (!_dsrFileLoaded)
+ return;
+
+ for (int i = 0; i < _dsrFile.entryCount; i++) {
+ _dsrFile.dsrEntries.remove_at(0);
+ }
+
+ _dsrFile.entryCount = 0;
+ strcpy(_dsrFile.fileName, "");
+ _dsrFileLoaded = false;
+}
+
+void Sound::playDSRSound(int soundIndex, int volume, bool loop) {
+ if (!_dsrFileLoaded) {
+ warning("DSR file not loaded, not playing sound");
+ return;
+ }
+
+ if (soundIndex < 0 || soundIndex > _dsrFile.entryCount - 1) {
+ warning("Invalid sound index: %i, not playing sound", soundIndex);
+ return;
+ }
+
+ byte flags;
+ SndHandle *handle = getHandle();
+
+ handle->type = kEffectHandle;
+ flags = Audio::Mixer::FLAG_AUTOFREE;
+ flags |= Audio::Mixer::FLAG_UNSIGNED;
+
+ if (loop)
+ flags |= Audio::Mixer::FLAG_LOOP;
+
+ // Get sound data
+ FabDecompressor fab;
+ byte *compData = new byte[_dsrFile.dsrEntries[soundIndex]->compSize];
+ byte *buffer = new byte[_dsrFile.dsrEntries[soundIndex]->uncompSize];
+ Common::SeekableReadStream *fileStream = _vm->res()->get(_dsrFile.fileName);
+ fileStream->seek(_dsrFile.dsrEntries[soundIndex]->offset, SEEK_SET);
+ fileStream->read(compData, _dsrFile.dsrEntries[soundIndex]->compSize);
+ _vm->res()->toss(_dsrFile.fileName);
+
+ fab.decompress(compData, _dsrFile.dsrEntries[soundIndex]->compSize,
+ buffer, _dsrFile.dsrEntries[soundIndex]->uncompSize);
+
+ // Play sound
+ _mixer->playRaw(Audio::Mixer::kSFXSoundType, &handle->handle, buffer,
+ _dsrFile.dsrEntries[soundIndex]->uncompSize,
+ _dsrFile.dsrEntries[soundIndex]->frequency, flags, -1, volume);
+
+ /*
+ // Dump the sound file
+ FILE *destFile = fopen("sound.raw", "wb");
+ fwrite(_dsrFile.dsrEntries[soundIndex]->data, _dsrFile.dsrEntries[soundIndex]->uncompSize, 1, destFile);
+ fclose(destFile);
+ */
+}
+
+} // End of namespace M4
diff --git a/engines/m4/sound.h b/engines/m4/sound.h
new file mode 100644
index 0000000000..2dae502551
--- /dev/null
+++ b/engines/m4/sound.h
@@ -0,0 +1,112 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Sound class
+
+#ifndef M4_SOUND_H
+#define M4_SOUND_H
+
+#include "common/file.h"
+#include "common/array.h"
+#include "sound/mixer.h"
+#include "sound/mp3.h"
+#include "sound/vorbis.h"
+#include "sound/flac.h"
+
+namespace M4 {
+
+#define SOUND_HANDLES 10
+
+enum SOUND_FLAGS {
+ SOUND_LOOP = 1
+};
+
+enum sndHandleType {
+ kFreeHandle,
+ kEffectHandle,
+ kVoiceHandle
+};
+
+struct SndHandle {
+ Audio::SoundHandle handle;
+ sndHandleType type;
+};
+
+struct DSREntry {
+ int16 frequency;
+ int channels;
+ int32 compSize;
+ int32 uncompSize;
+ int32 offset;
+};
+
+struct DSRFile {
+ char fileName[20];
+ int entryCount;
+ Common::Array<DSREntry *> dsrEntries;
+};
+
+class M4Engine;
+
+class Sound {
+public:
+
+ Sound(M4Engine *vm, Audio::Mixer *mixer, int volume);
+ ~Sound();
+
+ void playSound(const char *soundName, int volume, bool loop, int channel = -1);
+ void pauseSound();
+ void resumeSound();
+ void stopSound(int channel = -1);
+
+ void playVoice(const char *soundName, int volume);
+ void pauseVoice();
+ void resumeVoice();
+ void stopVoice();
+
+ void stopAll();
+
+ void setVolume(int volume);
+
+ bool isHandleActive(SndHandle *handle);
+ SndHandle *getHandle();
+
+ void loadDSRFile(const char *fileName);
+ void unloadDSRFile();
+ void playDSRSound(int soundIndex, int volume, bool loop);
+
+ private:
+
+ M4Engine *_vm;
+ Audio::Mixer *_mixer;
+ SndHandle _handles[SOUND_HANDLES];
+
+ DSRFile _dsrFile;
+ bool _dsrFileLoaded;
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/sprite.cpp b/engines/m4/sprite.cpp
new file mode 100644
index 0000000000..3862435216
--- /dev/null
+++ b/engines/m4/sprite.cpp
@@ -0,0 +1,174 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/rect.h"
+
+#include "m4/globals.h"
+#include "m4/graphics.h"
+#include "m4/m4.h"
+#include "m4/resource.h"
+#include "m4/sprite.h"
+
+namespace M4 {
+
+enum {
+ kEndOfLine = 0,
+ kEndOfSprite = 1,
+ kMarker = 2
+};
+
+M4Sprite::M4Sprite(Common::SeekableReadStream* source, int xOfs, int yOfs, int widthVal, int heightVal, bool decodeRle, uint8 encodingVal)
+ : M4Surface(widthVal, heightVal), encoding(encodingVal) {
+
+ if (_vm->isM4()) {
+ if (decodeRle) {
+ loadRle(source);
+ } else {
+ // Raw sprite data, load directly
+ byte *dst = getData();
+ source->read(dst, widthVal * heightVal);
+ }
+ } else {
+ loadMadsSprite(source);
+ }
+
+ xOffset = xOfs;
+ yOffset = yOfs;
+
+}
+
+void M4Sprite::loadRle(Common::SeekableReadStream* rleData) {
+ byte *dst = getData();
+ while (1) {
+ byte len = rleData->readByte();
+ if (len == 0) {
+ len = rleData->readByte();
+ if (len <= kMarker) {
+ if (len == kEndOfSprite)
+ break;
+ } else {
+ while (len--) {
+ *dst++ = rleData->readByte();
+ }
+ }
+ } else {
+ byte value = rleData->readByte();
+ while (len--)
+ *dst++ = value;
+ }
+ }
+}
+
+void M4Sprite::loadDeltaRle(Common::SeekableReadStream* rleData, int destX, int destY) {
+ int lineNum = 0;
+ byte *dst = getBasePtr(destX, destY);
+ while (1) {
+ byte len = rleData->readByte();
+ if (len == 0) {
+ len = rleData->readByte();
+ if (len <= kMarker) {
+ if (len == kEndOfLine) {
+ dst = getBasePtr(destX, destY + lineNum);
+ lineNum++;
+ } else if (len == kEndOfSprite)
+ break;
+ } else {
+ while (len--) {
+ byte pixel = rleData->readByte();
+ if (pixel == 0)
+ dst++;
+ else
+ *dst++ = pixel;
+ /* NOTE: The change below behaved differently than the old code,
+ so I put the old code back in again above.
+ If the pixel value is 0, nothing should be written to the
+ output buffer, since 0 means transparent. */
+ //*dst++ = (pixel == 0xFD) ? 0 : pixel;
+ }
+ }
+ } else {
+ byte value = rleData->readByte();
+ if (value == 0)
+ dst += len;
+ else
+ while (len--)
+ *dst++ = value;
+ }
+ }
+}
+
+// TODO: The sprite outlines (pixel value 0xFD) are not shown
+void M4Sprite::loadMadsSprite(Common::SeekableReadStream* source) {
+ byte *outp, *lineStart;
+ bool newLine = false;
+
+ outp = getData();
+ lineStart = getData();
+
+ while (1) {
+ byte cmd1, cmd2, count, pixel;
+
+ if (newLine) {
+ outp = lineStart + w;
+ lineStart = outp;
+ newLine = false;
+ }
+
+ cmd1 = source->readByte();
+
+ if (cmd1 == 0xFC)
+ break;
+ else if (cmd1 == 0xFF)
+ newLine = true;
+ else if (cmd1 == 0xFD) {
+ while (!newLine) {
+ count = source->readByte();
+ if (count == 0xFF) {
+ newLine = true;
+ } else {
+ pixel = source->readByte();
+ while (count--)
+ *outp++ = (pixel == 0xFD) ? 0 : pixel;
+ }
+ }
+ } else {
+ while (!newLine) {
+ cmd2 = source->readByte();
+ if (cmd2 == 0xFF) {
+ newLine = true;
+ } else if (cmd2 == 0xFE) {
+ count = source->readByte();
+ pixel = source->readByte();
+ while (count--)
+ *outp++ = (pixel == 0xFD) ? 0 : pixel;
+ } else {
+ *outp++ = (cmd2 == 0xFD) ? 0 : cmd2;
+ }
+ }
+ }
+ }
+}
+
+} // End of namespace M4
diff --git a/engines/m4/sprite.h b/engines/m4/sprite.h
new file mode 100644
index 0000000000..5caeaa8664
--- /dev/null
+++ b/engines/m4/sprite.h
@@ -0,0 +1,122 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_SPRITE_H
+#define M4_SPRITE_H
+
+#include "common/util.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "m4/graphics.h"
+
+/*
+ TODO:
+ - change DrawRequestX and RendCell
+*/
+
+namespace M4 {
+
+typedef struct
+{
+ int32 x; // x position relative to GrBuff(0, 0)
+ int32 y; // y position relative to GrBuff(0, 0)
+ int32 scale_x; // x scale factor (can be negative for reverse draw)
+ int32 scale_y; // y scale factor (can't be negative)
+ uint8* depth_map; // depth code array for destination (doesn't care if srcDepth is 0)
+ BGR8 *Pal; // palette for shadow draw (doesn't care if SHADOW bit is not set in Src.encoding)
+ uint8* ICT; // Inverse Color Table (doesn't care if SHADOW bit is not set in Src.encoding)
+ uint8 depth; // depth code for source (0 if no depth processing)
+} DrawRequestX;
+
+typedef struct
+{
+ uint32 Pack;
+ uint32 Stream;
+ long hot_x;
+ long hot_y;
+ uint32 Width;
+ uint32 Height;
+ uint32 Comp;
+ uint32 Reserved[8];
+ uint8* data;
+} RendCell;
+
+#define SS_HEADER_NUM_FIELDS 14
+struct SpriteSeriesHeader {
+ uint32 header;
+ uint32 size;
+ uint32 packing;
+ uint32 frameRate;
+ uint32 pixSpeed;
+ uint32 maxWidth;
+ uint32 maxHeight;
+ uint32 reserved3;
+ uint32 reserved4;
+ uint32 reserved5;
+ uint32 reserved6;
+ uint32 reserved7;
+ uint32 reserved8;
+ uint32 count;
+};
+
+#define SF_HEADER_NUM_FIELDS 15
+struct SpriteFrameHeader {
+ uint32 pack;
+ uint32 stream;
+ uint32 x;
+ uint32 y;
+ uint32 width;
+ uint32 height;
+ uint32 comp;
+ uint32 reserved1;
+ uint32 reserved2;
+ uint32 reserved3;
+ uint32 reserved4;
+ uint32 reserved5;
+ uint32 reserved6;
+ uint32 reserved7;
+ uint32 reserved8;
+};
+
+class M4Sprite: public M4Surface {
+public:
+ int x, y;
+ int xOffset, yOffset;
+ uint8 encoding;
+
+ M4Sprite(M4Engine *vm): M4Surface() {}
+ M4Sprite(M4Engine *vm, int widthVal, int heightVal): M4Surface(widthVal, heightVal), xOffset(0), yOffset(0) {}
+ // Loads a sprite from the given stream, and optionally decompresses the RLE-encoded data
+ M4Sprite(Common::SeekableReadStream* source, int xOfs, int yOfs, int widthVal, int heightVal, bool decodeRle = true, uint8 encodingVal = 0);
+ // Loads an RLE compressed sprite; the surface must have been created before
+ void loadRle(Common::SeekableReadStream* rleData);
+ void loadDeltaRle(Common::SeekableReadStream* rleData, int destX, int destY);
+ void loadMadsSprite(Common::SeekableReadStream* source);
+protected:
+};
+
+} // End of namespace M4
+
+#endif
diff --git a/engines/m4/viewmgr.cpp b/engines/m4/viewmgr.cpp
new file mode 100644
index 0000000000..3a8b5d24a8
--- /dev/null
+++ b/engines/m4/viewmgr.cpp
@@ -0,0 +1,436 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// TODO: Views have a _coords rect, so I'm not sure if x/y is needed in the onRefresh
+
+#include "m4/m4.h"
+#include "m4/viewmgr.h"
+#include "m4/mads_anim.h"
+
+namespace M4 {
+
+void returnToMainMenuFn(M4Engine *vm) {
+ vm->_palette->resetColorCounts();
+ vm->_palette->setMadsSystemPalette();
+
+ vm->loadMenu(MAIN_MENU);
+}
+
+RectList::RectList() {
+}
+
+RectList::~RectList() {
+}
+
+void RectList::addRect(int x1, int y1, int x2, int y2) {
+ addRect(Common::Rect(x1, y1, x2, y2));
+}
+
+void RectList::addRect(const Common::Rect &rect) {
+ /* TODO:
+ Implement the following:
+ - Don't add the Rect if it's contained in any Rect in the list
+ - Split up the Rect if it intersects any Rect in the list
+ and add the resulting partial Rects instead
+ */
+ push_back(rect);
+}
+
+//--------------------------------------------------------------------------
+
+HotkeyList::HotkeyList(View *owner) {
+ _view = owner;
+}
+
+HotkeyList::~HotkeyList() {
+ for (uint32 i = 0; i < _hotkeys.size(); i++)
+ delete _hotkeys[i];
+}
+
+void HotkeyList::add(uint32 key, Hotkey::Callback callback) {
+ _hotkeys.push_back(new Hotkey(key, callback));
+}
+
+void HotkeyList::remove(uint32 key) {
+ for (uint32 i = 0; i < _hotkeys.size(); i++) {
+ if (_hotkeys[i]->key == key) {
+ delete _hotkeys[i];
+ _hotkeys.remove_at(i);
+ break;
+ }
+ }
+}
+
+bool HotkeyList::call(uint32 key) {
+ for (uint32 i = 0; i < _hotkeys.size(); i++) {
+ if (_hotkeys[i]->key == key) {
+ if (_hotkeys[i]->callback)
+ (_hotkeys[i]->callback)(_vm, _view, key);
+ return true;
+ }
+ }
+ return false;
+}
+
+//--------------------------------------------------------------------------
+
+// View constructor
+
+View::View(M4Engine *vm, const Common::Rect &viewBounds, bool transparent):
+ _hotkeys(HotkeyList(this)), M4Surface(viewBounds.width(), viewBounds.height()), _vm(vm) {
+ SCREEN_FLAGS_DEFAULT;
+ _coords = viewBounds;
+ _transparent = transparent;
+}
+
+View::View(M4Engine *vm, int x, int y, bool transparent): _hotkeys(HotkeyList(this)), M4Surface(), _vm(vm) {
+ SCREEN_FLAGS_DEFAULT;
+ _coords.left = x;
+ _coords.top = y;
+ _coords.right = _vm->_screen->width();
+ _coords.bottom = _vm->_screen->height();
+ _transparent = transparent;
+}
+
+void View::getCoordinates(Common::Rect &rect) {
+ rect = _coords;
+}
+
+void View::extract(int *status) {
+}
+
+void View::show() {
+ _screenFlags.visible = true;
+ _vm->_viewManager->moveToFront(this);
+ _vm->_viewManager->restore(_coords);
+}
+
+void View::hide() {
+ _screenFlags.visible = false;
+ _vm->_viewManager->restore(_coords);
+}
+
+void View::moveToBack() {
+ _vm->_viewManager->moveToBack(this);
+}
+
+void View::moveAbsolute(int x, int y) {
+ // TODO: Handle clipping and offscreen
+ Common::Rect oldCoords = _coords;
+ _coords.moveTo(x, y);
+ _vm->_viewManager->restore(oldCoords);
+ _vm->_viewManager->restore(_coords);
+}
+
+void View::moveRelative(int x, int y) {
+ // TODO: Handle clipping and offscreen
+ Common::Rect oldCoords = _coords;
+ _coords.translate(x, y);
+ _vm->_viewManager->restore(oldCoords);
+ _vm->_viewManager->restore(_coords);
+}
+
+void View::resize(int newWidth, int newHeight) {
+ Common::Rect oldCoords = _coords;
+ if (newWidth >= 0)
+ _coords.setWidth(newWidth);
+ if (newHeight >= 0)
+ _coords.setHeight(newHeight);
+ _vm->_viewManager->restore(oldCoords);
+ _vm->_viewManager->restore(_coords);
+}
+
+void View::restore(int x1, int y1, int x2, int y2) {
+ _vm->_viewManager->restore(_coords.left + x1, _coords.top + y1, _coords.left + x2, _coords.top + y2);
+}
+
+void View::onRefresh(RectList *rects, M4Surface *destSurface) {
+ assert(destSurface);
+
+ if (rects == NULL)
+ // No rect list specified, so copy entire surface
+ copyTo(destSurface, _coords.left, _coords.top, _transparent ? 0 : -1);
+ else {
+ // Loop through the set of specified rectangles
+ RectList::iterator i;
+ for (i = rects->begin(); i != rects->end(); ++i) {
+ Common::Rect &destRect = *i;
+ Common::Rect srcBounds(destRect.left - _coords.left, destRect.top - _coords.top,
+ destRect.right - _coords.left, destRect.bottom - _coords.top);
+ copyTo(destSurface, srcBounds, destRect.left, destRect.top, _transparent ? 0 : -1);
+ }
+ }
+}
+
+//--------------------------------------------------------------------------
+
+ViewManager::ViewManager(M4Engine *vm): _systemHotkeys(HotkeyList(NULL)), _vm(vm) {
+ _captureScreen = NULL;
+ _captureEvents = false;
+}
+
+ViewManager::~ViewManager() {
+ // Delete any remaining active views
+ ListIterator i;
+ for (i = _views.begin(); i != _views.end(); ++i)
+ delete (*i);
+}
+
+void ViewManager::addView(View *view) {
+ _views.push_back(view);
+ moveToFront(view);
+}
+
+// Warning: After calling this method, the passed view object will no longer be valid
+
+void ViewManager::deleteView(View *view) {
+ _views.remove(view);
+ delete view;
+}
+
+void ViewManager::handleEvents(const Common::Event &event) {
+}
+
+void ViewManager::handleKeyboardEvents(uint32 keycode) {
+ Common::Point mousePos = _vm->_mouse->currentPos();
+ View *view;
+ bool blockedFlag;
+ bool foundFlag;
+ bool handledFlag;
+
+ // Scan view list for one which accepts or blocks keyboard events. If one is found,
+ // then the event is passed to it
+
+ view = NULL;
+ handledFlag = false;
+ foundFlag = false;
+ blockedFlag = false;
+
+ // Loop from the front to back view
+ ListIterator i;
+ for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag; --i) {
+ view = *i;
+ if (!view->isVisible()) continue;
+
+ if (view->screenFlags().blocks & SCREVENT_KEY)
+ blockedFlag = true;
+ if (view->screenFlags().get & SCREVENT_KEY) {
+ foundFlag = true;
+ handledFlag = (view->onEvent)(KEVENT_KEY, keycode, mousePos.x, mousePos.y, _captureEvents);
+ }
+ }
+
+ // Scan view list for one with a hotkey list, aborting if a view is found that either
+ // blocks keyboard events, or has a hotkey list that includes the keycode
+
+ blockedFlag = false;
+ for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag && !blockedFlag; --i) {
+ view = *i;
+ if (!view->isVisible()) continue;
+
+ if (view->screenFlags().blocks & SCREVENT_KEY)
+ blockedFlag = true;
+ if (view->screenFlags().get & SCREVENT_KEY) {
+ if (view->hotkeys().call(keycode)) {
+ handledFlag = true;
+ _captureEvents = false;
+ //_vm->_dialogs->keyMouseCollision(); // TODO
+ }
+ }
+ }
+
+ // Final check: if no view handled or blocked the key, check against the system hotkey list
+
+ if (!handledFlag && !blockedFlag) {
+ handledFlag = _systemHotkeys.call(keycode);
+ if (handledFlag) {
+ _captureEvents = false;
+ //_vm->_dialogs->keyMouseCollision(); // TODO
+ }
+ }
+}
+
+void ViewManager::handleMouseEvents(M4EventType event) {
+ Common::Point mousePos = _vm->_mouse->currentPos();
+ ListIterator i;
+ View *view;
+ bool blockedFlag;
+ bool foundFlag;
+
+ // If a window sets the _captureEvents flag to true, it will receive all events until
+ // it sets it to false, even if it's not the top window
+ if (_captureEvents) {
+ if (_captureScreen->screenFlags().get & SCREVENT_MOUSE)
+ (_captureScreen->onEvent)(event, 0, mousePos.x, mousePos.y, _captureEvents);
+
+ } else {
+ blockedFlag = false;
+ foundFlag = false;
+ view = NULL;
+
+ // Loop from the front to back view
+ for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag && !blockedFlag; --i) {
+ view = *i;
+ if (!view->isVisible()) continue;
+
+ if (view->screenFlags().blocks & SCREVENT_MOUSE)
+ blockedFlag = true;
+ if ((view->screenFlags().get & SCREVENT_MOUSE) && view->isInside(mousePos.x, mousePos.y))
+ foundFlag = true;
+ }
+
+ if (foundFlag)
+ view->onEvent(event, 0, mousePos.x, mousePos.y, _captureEvents);
+ else
+ _captureEvents = false;
+ if (_captureEvents)
+ _captureScreen = view;
+ }
+}
+
+void ViewManager::restore(int x1, int y1, int x2, int y2) {
+ RectList *rl = new RectList();
+ Common::Rect redrawBounds(x1, y1, x2, y2);
+ rl->addRect(x1, y1, x2, y2);
+
+ for (ListIterator i = _views.begin(); i != _views.end(); ++i) {
+ View *v = *i;
+
+ if (v->isVisible() && v->bounds().intersects(redrawBounds))
+ v->onRefresh(rl, _vm->_screen);
+ }
+
+ _vm->_screen->update();
+
+}
+
+void ViewManager::restore(const Common::Rect &rect) {
+ restore(rect.left, rect.top, rect.right, rect.bottom);
+}
+
+void ViewManager::moveToFront(View *view) {
+ if (_views.size() < 2)
+ return;
+
+ _views.remove(view);
+
+ ListIterator i = _views.begin();
+ while ((i != _views.end()) && ((*i)->layer() <= view->layer()))
+ ++i;
+
+ _views.insert(i, view);
+}
+
+void ViewManager::moveToBack(View *view) {
+ if (_views.size() < 2)
+ return;
+
+ _views.remove(view);
+
+ ListIterator i = _views.begin();
+ while ((i != _views.end()) && ((*i)->layer() < view->layer()))
+ ++i;
+
+ _views.insert(i, view);
+}
+
+View *ViewManager::getView(int screenType) {
+ ListIterator i = _views.begin();
+ while (i != _views.end()) {
+ if ((*i)->screenType() == screenType)
+ return *i;
+ ++i;
+ }
+
+ return NULL;
+}
+
+void ViewManager::updateState() {
+ Common::List<View *> viewList = _views;
+
+ for (ListIterator i = viewList.begin(); i != viewList.end(); ++i) {
+ if (_vm->_events->quitFlag)
+ return;
+
+ View *v = *i;
+ v->updateState();
+ }
+}
+
+void ViewManager::refreshAll() {
+ _vm->_screen->empty();
+
+ for (ListIterator i = _views.begin(); i != _views.end(); ++i) {
+ View *v = *i;
+
+ if (v->isVisible())
+ v->onRefresh(NULL, _vm->_screen);
+ }
+
+ _vm->_screen->update();
+}
+
+void ViewManager::showTextView(const char *textViewName, bool returnToMainMenu) {
+ // Deactivate the scene if it's currently active
+ View *view = _vm->_viewManager->getView(VIEWID_SCENE);
+ if (view != NULL)
+ _vm->_viewManager->deleteView(view);
+
+ // Deactivate the main menu if it's currently active
+ view = _vm->_viewManager->getView(VIEWID_MAINMENU);
+ if (view != NULL)
+ _vm->_viewManager->deleteView(view);
+
+ // Activate the textview view
+ _vm->_font->setFont(FONT_CONVERSATION_MADS);
+ TextviewView *textView = new TextviewView(_vm);
+ _vm->_viewManager->addView(textView);
+ if (returnToMainMenu)
+ textView->setScript(textViewName, returnToMainMenuFn);
+ else
+ textView->setScript(textViewName, NULL);
+}
+
+void ViewManager::showAnimView(const char *animViewName, bool returnToMainMenu) {
+ // Deactivate the scene if it's currently active
+ View *view = _vm->_viewManager->getView(VIEWID_SCENE);
+ if (view != NULL)
+ _vm->_viewManager->deleteView(view);
+
+ // Deactivate the main menu if it's currently active
+ view = _vm->_viewManager->getView(VIEWID_MAINMENU);
+ if (view != NULL)
+ _vm->_viewManager->deleteView(view);
+
+ // Activate the animview view
+ AnimviewView *animView = new AnimviewView(_vm);
+ _vm->_viewManager->addView(animView);
+ if (returnToMainMenu)
+ animView->setScript(animViewName, returnToMainMenuFn);
+ else
+ animView->setScript(animViewName, NULL);
+}
+
+} // End of namespace M4
diff --git a/engines/m4/viewmgr.h b/engines/m4/viewmgr.h
new file mode 100644
index 0000000000..148fb565da
--- /dev/null
+++ b/engines/m4/viewmgr.h
@@ -0,0 +1,191 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_VIEWMGR_H
+#define M4_VIEWMGR_H
+
+#include "common/array.h"
+#include "common/list.h"
+#include "common/events.h"
+#include "common/rect.h"
+
+#include "m4/globals.h"
+#include "m4/events.h"
+#include "m4/graphics.h"
+
+namespace M4 {
+
+class View;
+class ViewManager;
+
+enum {SCREEN_DIALOG, SCREEN_BUFFER, SCREEN_TEXT, SCREEN_TRANSPARENT};
+enum ScreenEventType {SCREVENT_NONE = 0, SCREVENT_KEY = 1, SCREVENT_MOUSE = 2, SCREVENT_ALL = 3};
+enum ScreenLayers {
+ LAYER_BACKGROUND = 0, LAYER_DRIFTER = 1, LAYER_INTERFACE = 1, LAYER_FLOATER = 2,
+ LAYER_SURFACE = 3, LAYER_MENU = 9, LAYER_MOUSE = 15
+};
+
+enum ViewIds {
+ VIEWID_MAINMENU = 1,
+ VIEWID_SCENE = 2,
+ VIEWID_TEXTVIEW = 3,
+ VIEWID_ANIMVIEW = 4,
+ VIEWID_MENU = 69,
+ VIEWID_CONVERSATION = 48,
+ VIEWID_INTERFACE = 49
+};
+
+struct ScreenFlags {
+ bool visible:1;
+ bool transparent:1;
+ bool immovable:1;
+
+ enum ScreenEventType blocks:2;
+ enum ScreenEventType get:2;
+
+ uint layer:4;
+};
+
+#define SCREEN_FLAGS_DEFAULT _screenFlags.layer = LAYER_DRIFTER; \
+ _screenFlags.get = SCREVENT_ALL; _screenFlags.blocks = SCREVENT_NONE; \
+ _screenFlags.visible = true;
+#define SCREEN_FLAGS_ALERT _screenFlags.layer = LAYER_FLOATER \
+ _screenFlags.get = SCREVENT_ALL; _screenFlags.blocks = SCREVENT_ALL; \
+ _screenFlags.visible = true;
+
+class RectList: public Common::Array<Common::Rect> {
+public:
+ RectList();
+ ~RectList();
+ void addRect(int x1, int y1, int x2, int y2);
+ void addRect(const Common::Rect &rect);
+
+// Common::Rect& operator [](int idx) { return _rects[idx]; }
+};
+
+struct Hotkey {
+public:
+ typedef void (*Callback)(M4Engine *vm, View *view, uint32 key);
+ Hotkey(uint32 keyVal, Hotkey::Callback callbackFn) : key(keyVal), callback(callbackFn) {};
+ uint32 key;
+ Hotkey::Callback callback;
+};
+
+class HotkeyList {
+public:
+ HotkeyList(View *owner);
+ ~HotkeyList();
+ void add(uint32 key, Hotkey::Callback callback);
+ void remove(uint32 key);
+ bool call(uint32 key);
+private:
+ Common::Array<Hotkey*> _hotkeys;
+ View *_view;
+};
+
+class View: public M4Surface {
+public:
+ View(M4Engine *vm, const Common::Rect &viewBounds, bool transparent = false);
+ View(M4Engine *vm, int x = 0, int y = 0, bool transparent = false);
+ virtual ~View() {}
+
+ void getCoordinates(Common::Rect &rect);
+ void extract(int *status);
+ virtual void show();
+ virtual void hide();
+ void moveToFront() {}
+ void moveToBack();
+ void moveAbsolute(int x, int y);
+ void moveRelative(int x, int y);
+ void resize(int newWidth, int newHeight);
+ void restore(int x1, int y1, int x2, int y2);
+
+ Common::Rect bounds() const { return _coords; }
+ bool isInside(int x, int y) const { return _coords.contains(x, y); }
+ ScreenFlags screenFlags() const { return _screenFlags; }
+ int screenType() const { return _screenType; }
+ bool isOffscreen() const { return !_screenFlags.visible; }
+ bool isTransparent() const { return _screenFlags.transparent; }
+ bool isVisible() const { return _screenFlags.visible; }
+ uint layer() const { return _screenFlags.layer; }
+ HotkeyList &hotkeys() { return _hotkeys; }
+
+ virtual void onRefresh(RectList *rects, M4Surface *destSurface);
+ virtual bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { return false; }
+ virtual void updateState() {};
+
+protected:
+ M4Engine *_vm;
+ Common::Rect _coords;
+ HotkeyList _hotkeys;
+ int _screenType;
+ ScreenFlags _screenFlags;
+ bool _transparent;
+};
+
+class ViewManager {
+private:
+ M4Engine *_vm;
+ HotkeyList _systemHotkeys;
+ Common::List<View *> _views;
+ View *_captureScreen;
+ bool _captureEvents;
+public:
+ typedef Common::List<View *>::iterator ListIterator;
+
+ ViewManager(M4Engine *vm);
+ ~ViewManager();
+
+ void addView(View *view);
+ void deleteView(View *view);
+
+ void handleEvents(const Common::Event &event);
+ void handleKeyboardEvents(uint32 keycode);
+ void handleMouseEvents(M4EventType event);
+ void restore(int x1, int y1, int x2, int y2);
+ void restore(const Common::Rect &rect);
+
+ void moveToFront(View *view);
+ void moveToBack(View *view);
+
+ Common::List<View *> views() const { return _views; }
+ bool contains(View *key) const {
+ return find(_views.begin(), _views.end(), key) != _views.end();
+ }
+ bool contains(int screenType) { return getView(screenType) != NULL; }
+ View *getView(int screenType);
+ int viewCount() { return _views.size(); }
+
+ void showTextView(const char *textViewName, bool returnToMainMenu = true);
+ void showAnimView(const char *animViewName, bool returnToMainMenu = true);
+
+ void updateState();
+ void refreshAll();
+ HotkeyList &systemHotkeys() { return _systemHotkeys; }
+};
+
+}
+
+#endif
diff --git a/engines/m4/woodscript.cpp b/engines/m4/woodscript.cpp
new file mode 100644
index 0000000000..15d86a407e
--- /dev/null
+++ b/engines/m4/woodscript.cpp
@@ -0,0 +1,398 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/woodscript.h"
+
+namespace M4 {
+
+// FIXME: Put in Engine/WoodScript class
+RGB8 _mainPalette[256];
+
+//Woodscript Assembler/Compiler
+
+int32 Bytecode::_dataFormats[] = {0, 5, 8, 12, 16};
+
+Bytecode::Bytecode(WoodScript *ws, byte *code, int32 codeSize, Sequence *seq) {
+ _ws = ws;
+ _code = new Common::MemoryReadStream(code, codeSize);
+ _sequence = seq;
+}
+
+Bytecode::~Bytecode() {
+ delete _code;
+}
+
+int Bytecode::loadInstruction(Instruction &instruction) {
+
+ //printf("Bytecode::loadInstruction() ip = %08X\n", _code->pos());
+
+ int32 format, data;
+ uint32 code, code2;
+
+ code = _code->readUint32LE();
+
+ instruction.instr = (code >> 25) & 0xFF;
+ instruction.argp[0] = NULL;
+ instruction.argp[1] = NULL;
+ instruction.argp[2] = NULL;
+ instruction.argc = 0;
+
+ // Maybe make this a for-loop?
+
+ format = (code >> 22) & 7;
+ if (format) {
+ /* Load argument 1 */
+ data = code & 0xFFFF;
+ decodeArgument(format, data, instruction.argp[0], instruction.argv[0]);
+ instruction.argc++;
+ /* Load argument 2 */
+ format = (code >> 19) & 7;
+ if (format) {
+ code2 = _code->readUint32LE();
+ data = (code2 >> 16) & 0xFFFF;
+ decodeArgument(format, data, instruction.argp[1], instruction.argv[1]);
+ instruction.argc++;
+ /* Load argument 3 */
+ format = (code >> 16) & 7;
+ if (format) {
+ data = code2 & 0xFFFF;
+ decodeArgument(format, data, instruction.argp[2], instruction.argv[2]);
+ instruction.argc++;
+ }
+ }
+ }
+
+ return 0; //FIXME check if instruction size is needed by caller
+
+}
+
+void Bytecode::jumpAbsolute(int32 ofs) {
+ _code->seek(ofs * 4);
+ //printf("Bytecode::jumpAbsolute() ofs = %08X\n", _code->pos());
+}
+
+void Bytecode::jumpRelative(int32 ofs) {
+ _code->seek(ofs * 4, SEEK_CUR);
+}
+
+void Bytecode::setSequence(Sequence *seq) {
+ _sequence = seq;
+}
+
+void Bytecode::setCode(byte *code, int32 codeSize) {
+ delete _code;
+ _code = new Common::MemoryReadStream(code, codeSize);
+}
+
+Sequence *Bytecode::sequence() const {
+ assert(_sequence);
+ return _sequence;
+}
+
+bool Bytecode::decodeArgument(int32 format, int32 data, long *&arg, long &value) {
+
+ int32 index;
+
+ if (format == 1) {
+ if (data & 0x8000)
+ index = _sequence->indexReg();
+ else
+ index = data & 0x0FFF;
+ switch (data & 0x7000) {
+ case 0x0000:
+ arg = sequence()->getParentVarPtr(index);
+ value = *arg;
+ break;
+ case 0x1000:
+ arg = sequence()->getVarPtr(index);
+ value = *arg;
+ break;
+ case 0x2000:
+ arg = sequence()->getDataPtr(index);
+ value = *arg;
+ break;
+ }
+ } else if (format == 2) {
+ if (data & 0x8000)
+ index = _sequence->indexReg();
+ else
+ index = data & 0x0FFF;
+ arg = _ws->getGlobalPtr(index);
+ value = *arg;
+ } else {
+ if (data & 0x8000) {
+ value = -(data & 0x7FFF) << (_dataFormats[format - 3]);
+ } else {
+ value = (data & 0x7FFF) << (_dataFormats[format - 3]);
+ }
+ arg = &value;
+ }
+
+ return true;
+}
+
+WoodScript::WoodScript(M4Engine *vm) {
+ _vm = vm;
+ _machineId = 0;
+ _assets = new AssetManager(vm);
+ _globals = new long[256]; //FIXME Find out how many globals there should be
+ memset(_globals, 0, sizeof(long));
+
+ _backgroundSurface = NULL;
+
+ Common::Rect viewBounds = Common::Rect(0, 0, 640, 480);
+ //_surfaceView = new View(viewBounds);
+}
+
+WoodScript::~WoodScript() {
+ delete _assets;
+ delete[] _globals;
+}
+
+Sequence *WoodScript::createSequence(Machine *machine, int32 sequenceHash) {
+ Sequence *sequence = new Sequence(this, machine, sequenceHash);
+ _sequences.push_back(sequence);
+ _layers.push_back(sequence);
+ return sequence;
+}
+
+void WoodScript::runSequencePrograms() {
+ // A lot TODO
+ for (Common::Array<Sequence*>::iterator it = _sequences.begin(); it != _sequences.end(); it++) {
+ Sequence *sequence = *it;
+ if (sequence->isActive()) {
+ sequence->runProgram();
+ if (sequence->isTerminated() && sequence->hasEndOfSequenceRequestPending()) {
+ _endOfSequenceRequestList.push_back(sequence);
+ }
+ }
+ }
+}
+
+void WoodScript::runEndOfSequenceRequests() {
+}
+
+void WoodScript::runTimerSequenceRequests() {
+}
+
+Machine *WoodScript::createMachine(int32 machineHash, Sequence *parentSeq,
+ int32 dataHash, int32 dataRowIndex, int callbackHandler, const char *machineName) {
+
+ //printf("WoodScript::createMachine(%d)\n", machineHash); fflush(stdout);
+
+ Machine *machine = new Machine(this, machineHash, parentSeq, dataHash, dataRowIndex, callbackHandler, machineName, _machineId);
+ _machineId++;
+
+ _machines.push_back(machine);
+
+ // goto first state for initialization
+ machine->enterState();
+
+ return machine;
+}
+
+int32 WoodScript::loadSeries(const char* seriesName, int32 hash, RGB8* palette) {
+ return _assets->addSpriteAsset(seriesName, hash, palette);
+}
+
+void WoodScript::unloadSeries(int32 hash) {
+ _assets->clearAssets(kAssetTypeCELS, hash, hash);
+}
+
+void WoodScript::setSeriesFramerate(Machine *machine, int32 frameRate) {
+}
+
+Machine *WoodScript::playSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum,
+ int32 frameRate, int32 loopCount, int32 s, int32 x, int32 y,
+ int32 firstFrame, int32 lastFrame) {
+
+ //printf("WoodScript::playSeries(%s)\n", seriesName);
+
+ RGB8 *palette = NULL;
+ if (flags & SERIES_LOAD_PALETTE)
+ palette = &_mainPalette[0];
+
+ int32 spriteHash = _assets->addSpriteAsset(seriesName, -1, palette);
+
+ _globals[kGlobTemp1] = (long)spriteHash << 24;
+ _globals[kGlobTemp2] = layer << 16;
+ _globals[kGlobTemp3] = _vm->_kernel->createTrigger(triggerNum);
+ _globals[kGlobTemp4] = frameRate << 16;
+ _globals[kGlobTemp5] = loopCount << 16;
+ _globals[kGlobTemp6] = (s << 16) / 100;
+ _globals[kGlobTemp7] = x << 16;
+ _globals[kGlobTemp8] = y << 16;
+ _globals[kGlobTemp9] = firstFrame << 16;
+ _globals[kGlobTemp10] = lastFrame << 16;
+ _globals[kGlobTemp11] = (flags & SERIES_PINGPONG) ? 0x10000 : 0;
+ _globals[kGlobTemp12] = (flags & SERIES_BACKWARD) ? 0x10000 : 0;
+ _globals[kGlobTemp13] = (flags & SERIES_RANDOM) ? 0x10000 : 0;
+ _globals[kGlobTemp14] = (flags & SERIES_STICK) ? 0x10000 : 0;
+ _globals[kGlobTemp15] = (flags & SERIES_LOOP_TRIGGER) ? 0x10000 : 0;
+ _globals[kGlobTemp16] = (flags & SERIES_HORZ_FLIP) ? 0x10000 : 0;
+
+ return createMachine(0, NULL, -1, -1, kCallbackTriggerDispatch, seriesName);
+
+}
+
+Machine *WoodScript::showSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum,
+ int32 duration, int32 index, int32 s, int32 x, int32 y) {
+
+ RGB8 *palette = NULL;
+ if (flags & SERIES_LOAD_PALETTE)
+ palette = &_mainPalette[0];
+
+ int32 spriteHash = _assets->addSpriteAsset(seriesName, -1, palette);
+
+ _globals[kGlobTemp1] = spriteHash << 24;
+ _globals[kGlobTemp2] = layer << 16;
+ _globals[kGlobTemp3] = _vm->_kernel->createTrigger(triggerNum);
+ _globals[kGlobTemp4] = duration << 16;
+ _globals[kGlobTemp5] = index << 16;
+ _globals[kGlobTemp6] = (s << 16) / 100;
+ _globals[kGlobTemp7] = x << 16;
+ _globals[kGlobTemp8] = y << 16;
+ _globals[kGlobTemp14] = (flags & SERIES_STICK) ? 0x10000 : 0;
+ _globals[kGlobTemp16] = (flags & SERIES_HORZ_FLIP) ? 0x10000 : 0;
+
+ return createMachine(1, NULL, -1, -1, kCallbackTriggerDispatch, seriesName);
+
+}
+
+Machine *WoodScript::streamSeries(const char *seriesName, int32 frameRate, long layer, int32 triggerNum) {
+ //printf("WoodScript::streamSeries(%s)\n", seriesName);
+ _globals[kGlobTemp1] = frameRate << 16;
+ /* FIXME: Single frames from a stream series will be decompressed on-the-fly, contrary to
+ "normal" sprite series, to save some memory, and since no random access to single
+ frames is needed, this is ok.
+ */
+ _globals[kGlobTemp4] = 0; // The actual stream is opened in the Sequence
+ _globals[kGlobTemp5] = 0;//TODO: kernel_trigger_create(triggerNum); // trigger
+ _globals[kGlobTemp6] = layer << 16; // layer
+ return createMachine(6, NULL, -1, -1, kCallbackTriggerDispatch, seriesName);
+}
+
+void WoodScript::update() {
+ // TODO: Don't show hidden sequences etc.
+
+ // TODO: For now, prevent any engine action if a menu is being displayed - eventually this should be
+ // changed to a proper check of the engine paused variable, which the menus should set while active
+ if (_vm->_viewManager->getView(VIEWID_MENU) != NULL)
+ return;
+
+ //TODO: Include _pauseTime
+ uint32 clockTime = g_system->getMillis() / 60; // FIXME: g_system
+ _globals[kGlobTimeDelta] = clockTime - _globals[kGlobTime];
+ _globals[kGlobTime] += _globals[kGlobTimeDelta];
+
+ runSequencePrograms();
+
+ if (_backgroundSurface) {
+ // FIXME: For now, copy the whole surface. Later, copy only the rectangles that need updating.
+ _backgroundSurface->copyTo(_surfaceView);
+ } else {
+ // "This should never happen."
+ _surfaceView->fillRect(Common::Rect(0, 0, 640, 480), 0);
+ }
+
+ {
+ // FIXME: This should be done when a new palette is set
+ byte palette[1024];
+ g_system->grabPalette(palette, 0, 256);
+ for (int i = 0; i < 256; i++) {
+ _mainPalette[i].r = palette[i * 4 + 0];
+ _mainPalette[i].g = palette[i * 4 + 1];
+ _mainPalette[i].b = palette[i * 4 + 2];
+ }
+ }
+
+ for (Common::Array<Sequence*>::iterator it = _layers.begin(); it != _layers.end(); it++) {
+ Sequence *sequence = *it;
+
+ // TODO: Use correct clipRect etc.
+ Common::Rect clipRect = Common::Rect(0, 0, 640, 480);
+ Common::Rect updateRect;
+
+ sequence->draw(_surfaceView, clipRect, updateRect);
+
+ }
+
+ // Handle end-of-sequence requests
+ if (_endOfSequenceRequestList.size() > 0) {
+ for (Common::Array<Sequence*>::iterator it = _endOfSequenceRequestList.begin(); it != _endOfSequenceRequestList.end(); it++) {
+ Sequence *sequence = *it;
+
+ EndOfSequenceRequestItem endOfSequenceRequestItem = sequence->getEndOfSequenceRequestItem();
+ sequence->getMachine()->execBlock(endOfSequenceRequestItem.codeOffset, endOfSequenceRequestItem.count);
+ }
+ _endOfSequenceRequestList.clear();
+ }
+
+}
+
+void WoodScript::clear() {
+
+ for (Common::Array<Sequence*>::iterator it = _sequences.begin(); it != _sequences.end(); it++)
+ delete *it;
+ _sequences.clear();
+
+ for (Common::Array<Machine*>::iterator it = _machines.begin(); it != _machines.end(); it++)
+ delete *it;
+ _machines.clear();
+
+ _layers.clear();
+ _endOfSequenceRequestList.clear();
+
+}
+
+void WoodScript::setDepthTable(int16 *depthTable) {
+ _depthTable = depthTable;
+}
+
+long *WoodScript::getGlobalPtr(int index) {
+ return &_globals[index];
+}
+
+long WoodScript::getGlobal(int index) {
+ return _globals[index];
+}
+
+void WoodScript::setGlobal(int index, long value) {
+ _globals[index] = value;
+}
+
+void WoodScript::setBackgroundSurface(M4Surface *backgroundSurface) {
+ _backgroundSurface = backgroundSurface;
+}
+
+void WoodScript::setSurfaceView(View *view) {
+ _surfaceView = view;
+}
+
+RGB8 *WoodScript::getMainPalette() const {
+ return _mainPalette;
+}
+
+}
diff --git a/engines/m4/woodscript.h b/engines/m4/woodscript.h
new file mode 100644
index 0000000000..7e968742b8
--- /dev/null
+++ b/engines/m4/woodscript.h
@@ -0,0 +1,351 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef M4_WOODSCRIPT_H
+#define M4_WOODSCRIPT_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/str.h"
+#include "common/array.h"
+#include "common/stream.h"
+#include "graphics/surface.h"
+
+#include "m4/globals.h"
+#include "m4/assets.h"
+#include "m4/resource.h"
+#include "m4/sprite.h"
+#include "m4/m4.h"
+#include "m4/graphics.h"
+#include "m4/viewmgr.h"
+
+namespace M4 {
+
+class M4Engine;
+class WoodScript;
+class Machine;
+class Sequence;
+class AssetManager;
+class View;
+
+struct Instruction {
+ int32 instr;
+ long *argp[3];
+ long argv[3];
+ int argc;
+ // Helper method; many opcode functions can get either a defined value or a random value
+ long getValue() {
+ if (argc == 3)
+ return _vm->imath_ranged_rand16(argv[1], argv[2]);
+ else
+ return argv[1];
+ }
+};
+
+class Bytecode {
+public:
+ Bytecode(WoodScript *ws, byte *code, int32 codeSize, Sequence *seq);
+ ~Bytecode();
+ int loadInstruction(Instruction &instruction);
+ void jumpAbsolute(int32 ofs);
+ void jumpRelative(int32 ofs);
+ void setSequence(Sequence *sequence);
+ void setCode(byte *code, int32 codeSize);
+ Sequence *sequence() const;
+ uint32 pos() const { return _code->pos() / 4; }
+protected:
+ WoodScript *_ws;
+ Common::MemoryReadStream *_code;
+ Sequence *_sequence;
+ static int32 _dataFormats[];
+ bool decodeArgument(int32 format, int32 data, long *&arg, long &value);
+};
+
+struct EndOfSequenceRequestItem {
+ int32 codeOffset, count;
+ EndOfSequenceRequestItem() : codeOffset(-1) {}
+ bool isValid() const { return codeOffset >= 0; }
+};
+
+typedef Common::Array<EndOfSequenceRequestItem> EndOfSequenceRequestList;
+
+class Sequence {
+public:
+ Sequence(WoodScript *ws, Machine *machine, int32 sequenceHash);
+ ~Sequence();
+
+ void pause();
+ void resume();
+ void issueEndOfSequenceRequest(int32 codeOffset, int32 count);
+ void cancelEndOfSequenceRequest();
+
+ bool runProgram();
+
+ bool changeProgram(int32 sequenceHash);
+
+ void clearVars();
+
+ long *getVarPtr(int index);
+ long *getParentVarPtr(int index);
+ long *getDataPtr(int index);
+
+ void setActive(bool active) { _active = active; }
+ bool isActive() const { return _active; }
+
+ bool isTerminated() const { return _terminated; }
+
+ void draw(M4Surface *surface, const Common::Rect &clipRect, Common::Rect &updateRect);
+
+ bool s1_end(Instruction &instruction);
+ bool s1_clearVars(Instruction &instruction);
+ bool s1_set(Instruction &instruction);
+ bool s1_compare(Instruction &instruction);
+ bool s1_add(Instruction &instruction);
+ bool s1_sub(Instruction &instruction);
+ bool s1_mul(Instruction &instruction);
+ bool s1_div(Instruction &instruction);
+ bool s1_and(Instruction &instruction);
+ bool s1_or(Instruction &instruction);
+ bool s1_not(Instruction &instruction);
+ bool s1_sin(Instruction &instruction);
+ bool s1_cos(Instruction &instruction);
+ bool s1_abs(Instruction &instruction);
+ bool s1_min(Instruction &instruction);
+ bool s1_max(Instruction &instruction);
+ bool s1_mod(Instruction &instruction);
+ bool s1_floor(Instruction &instruction);
+ bool s1_round(Instruction &instruction);
+ bool s1_ceil(Instruction &instruction);
+ bool s1_point(Instruction &instruction);
+ bool s1_dist2d(Instruction &instruction);
+ bool s1_crunch(Instruction &instruction);
+ bool s1_branch(Instruction &instruction);
+ bool s1_setFrame(Instruction &instruction);
+ bool s1_sendMessage(Instruction &instruction);
+ bool s1_push(Instruction &instruction);
+ bool s1_pop(Instruction &instruction);
+ bool s1_jumpSub(Instruction &instruction);
+ bool s1_return(Instruction &instruction);
+ bool s1_getFrameCount(Instruction &instruction);
+ bool s1_getFrameRate(Instruction &instruction);
+ bool s1_getCelsPixSpeed(Instruction &instruction);
+ bool s1_setIndex(Instruction &instruction);
+ bool s1_setLayer(Instruction &instruction);
+ bool s1_setDepth(Instruction &instruction);
+ bool s1_setData(Instruction &instruction);
+ bool s1_openStream(Instruction &instruction);
+ bool s1_streamNextFrame(Instruction &instruction);
+ bool s1_closeStream(Instruction &instruction);
+
+ int32 indexReg() const { return _indexReg; }
+
+ EndOfSequenceRequestItem getEndOfSequenceRequestItem() const { return _endOfSequenceRequest; }
+ bool hasEndOfSequenceRequestPending() const { return _endOfSequenceRequest.isValid(); }
+ void resetEndOfSequenceRequest() { _endOfSequenceRequest.codeOffset = -1; }
+
+ Machine *getMachine() const { return _machine; }
+
+
+protected:
+ WoodScript *_ws;
+ Bytecode *_code;
+
+ long *_vars;
+ bool _active, _terminated;
+ Machine *_machine;
+ Sequence *_parentSequence;
+ int32 _layer;
+ int32 _startTime, _switchTime;
+ long *_dataRow;
+ int32 _localVarCount;
+ int32 _cmpFlags;
+
+ EndOfSequenceRequestItem _endOfSequenceRequest;
+
+ int32 _indexReg;
+
+ M4Sprite *_curFrame;
+
+ int32 _sequenceHash;
+
+ int32 _returnHashes[8]; //FIXME: Use constant instead of 8
+ uint32 _returnOffsets[8];
+ int32 _returnStackIndex;
+
+ Common::SeekableReadStream *_stream;
+ SpriteAsset *_streamSpriteAsset;
+
+ bool streamOpen();
+ bool streamNextFrame();
+ void streamClose();
+
+};
+
+class Machine {
+public:
+ Machine(WoodScript *ws, int32 machineHash, Sequence *parentSeq, int32 dataHash,
+ int32 dataRowIndex, int callbackHandler, Common::String machineName, int32 id);
+ ~Machine();
+
+ void clearMessages();
+ void clearPersistentMessages();
+ void restorePersistentMessages();
+ void sendMessage(uint32 messageHash, long messageValue, Machine *sender);
+ void resetSwitchTime();
+ bool changeSequenceProgram(int32 sequenceHash);
+
+ bool searchMessages(uint32 messageHash, uint32 messageValue, Machine *sender);
+ bool searchPersistentMessages(uint32 messageHash, uint32 messageValue, Machine *sender);
+
+ void enterState();
+ int32 execInstruction();
+ void execBlock(int32 offset, int32 count);
+ int32 getState() { return _currentState; }
+
+ int32 getId() const { return _id; }
+
+ bool m1_gotoState(Instruction &instruction);
+ bool m1_jump(Instruction &instruction);
+ bool m1_terminate(Instruction &instruction);
+ bool m1_startSequence(Instruction &instruction);
+ bool m1_pauseSequence(Instruction &instruction);
+ bool m1_resumeSequence(Instruction &instruction);
+ bool m1_storeValue(Instruction &instruction);
+ bool m1_sendMessage(Instruction &instruction);
+ bool m1_broadcastMessage(Instruction &instruction);
+ bool m1_replyMessage(Instruction &instruction);
+ bool m1_sendSystemMessage(Instruction &instruction);
+ bool m1_createMachine(Instruction &instruction);
+ bool m1_createMachineEx(Instruction &instruction);
+ bool m1_clearVars(Instruction &instruction);
+
+ void m1_onEndSequence(Instruction &instruction);
+ void m1_onMessage(Instruction &instruction);
+ void m1_switchLt(Instruction &instruction);
+ void m1_switchLe(Instruction &instruction);
+ void m1_switchEq(Instruction &instruction);
+ void m1_switchNe(Instruction &instruction);
+ void m1_switchGe(Instruction &instruction);
+ void m1_switchGt(Instruction &instruction);
+
+ long *dataRow() const { return _dataRow; }
+ Sequence *parentSequence() const { return _parentSequence; }
+ Common::String name() const { return _name; }
+
+protected:
+ WoodScript *_ws;
+ Bytecode *_code;
+
+ Common::String _name;
+ Sequence *_sequence, *_parentSequence;
+ byte *_mach;
+ int32 _machHash, _machineCodeOffset;
+ int32 _stateCount, _stateTableOffset;
+ long *_dataRow;
+ int32 _id, _recursionLevel, _currentState, _targetCount;
+ /* TODO:
+ m->msgReplyXM = NULL;
+ m->CintrMsg = CintrMsg;
+ _walkPath
+ _messages
+ _persistentMessages
+ _usedPersistentMessages
+ */
+};
+
+class WoodScript {
+public:
+
+ WoodScript(M4Engine *vm);
+ ~WoodScript();
+
+ Machine *createMachine(int32 machineHash, Sequence *parentSeq, int32 dataHash, int32 dataRowIndex, int callbackHandler, const char *machineName);
+ Sequence *createSequence(Machine *machine, int32 sequenceHash);
+
+ void runSequencePrograms();
+ void runEndOfSequenceRequests();
+ void runTimerSequenceRequests();
+
+ /* Series */
+ // Move to own class, e.g. SeriesPlayer
+ int32 loadSeries(const char* seriesName, int32 hash, RGB8* palette);
+ void unloadSeries(int32 hash);
+ void setSeriesFramerate(Machine *machine, int32 frameRate);
+ Machine *playSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum,
+ int32 frameRate, int32 loopCount, int32 s, int32 x, int32 y,
+ int32 firstFrame, int32 lastFrame);
+ Machine *showSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum,
+ int32 duration, int32 index, int32 s, int32 x, int32 y);
+ Machine *streamSeries(const char *seriesName, int32 frameRate, long layer, int32 triggerNum);
+
+ void update();
+ void clear();
+
+ /* Misc */
+ void setDepthTable(int16 *depthTable);
+
+ long *getGlobalPtr(int index);
+ long getGlobal(int index);
+ void setGlobal(int index, long value);
+
+ AssetManager *assets() const { return _assets; }
+
+ // Sets the untouched, clean surface which contains the room background
+ void setBackgroundSurface(M4Surface *backgroundSurface);
+ // Sets the view which is used for drawing
+ void setSurfaceView(View *view);
+
+ RGB8 *getMainPalette() const;
+
+ void setInverseColorTable(byte *inverseColorTable) { _inverseColorTable = inverseColorTable; }
+ byte *getInverseColorTable() const { return _inverseColorTable; }
+
+protected:
+ M4Engine *_vm;
+ AssetManager *_assets;
+
+ Common::Array<Sequence*> _sequences, _layers;
+ Common::Array<Machine*> _machines;
+ int32 _machineId;
+
+ long *_globals;
+
+ Common::Array<Sequence*> _endOfSequenceRequestList;
+
+ int32 _indexReg;
+
+ /* Misc */
+ int16 *_depthTable;
+ byte *_inverseColorTable;
+ M4Surface *_backgroundSurface;
+ View *_surfaceView;
+
+};
+
+
+} // End of namespace M4
+
+
+#endif
diff --git a/engines/m4/ws_machine.cpp b/engines/m4/ws_machine.cpp
new file mode 100644
index 0000000000..ece76fe5ab
--- /dev/null
+++ b/engines/m4/ws_machine.cpp
@@ -0,0 +1,422 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/woodscript.h"
+
+namespace M4 {
+
+bool (Machine::*machineCommandsTable[])(Instruction &instruction) = {
+ NULL,
+ NULL,//TODO: nop
+ &Machine::m1_gotoState,
+ &Machine::m1_jump,
+ &Machine::m1_terminate,
+ &Machine::m1_startSequence,
+ &Machine::m1_pauseSequence,
+ &Machine::m1_resumeSequence,
+ &Machine::m1_storeValue,
+ &Machine::m1_sendMessage,
+ &Machine::m1_broadcastMessage,
+ &Machine::m1_replyMessage,
+ &Machine::m1_sendSystemMessage,
+ &Machine::m1_createMachine,
+ &Machine::m1_createMachineEx,
+ &Machine::m1_clearVars
+};
+
+void (Machine::*machineConditionalsTable[])(Instruction &instruction) = {
+ NULL,//TODO: after
+ &Machine::m1_onEndSequence,
+ &Machine::m1_onMessage,
+ NULL,//TODO: on_p_msg
+ &Machine::m1_switchLt,
+ &Machine::m1_switchLe,
+ &Machine::m1_switchEq,
+ &Machine::m1_switchNe,
+ &Machine::m1_switchGe,
+ &Machine::m1_switchGt,
+};
+
+Machine::Machine(WoodScript *ws, int32 machineHash, Sequence *parentSeq, int32 dataHash,
+ int32 dataRowIndex, int callbackHandler, Common::String machineName, int32 id) {
+
+ _ws = ws;
+
+ _machHash = machineHash;
+ _name = machineName;
+ _id = id;
+
+ // initialize the machine's bytecode
+ MachineAsset *machineAsset = _ws->assets()->getMachine(_machHash);
+ byte *code;
+ uint32 codeSize;
+ machineAsset->getCode(code, codeSize);
+ _code = new Bytecode(_ws, code, codeSize, NULL);
+
+ // initialize the machine's data
+ if (dataHash >= 0) {
+ DataAsset *dataAsset = _ws->assets()->getData(dataHash);
+ _dataRow = dataAsset->getRow(dataRowIndex);
+ } else {
+ _dataRow = NULL;
+ }
+
+ _recursionLevel = 0;
+ _currentState = 0;
+ _sequence = NULL;
+ _parentSequence = parentSeq;
+ _targetCount = 0;
+
+}
+
+Machine::~Machine() {
+ delete _code;
+}
+
+void Machine::clearMessages() {
+}
+
+void Machine::clearPersistentMessages() {
+}
+
+void Machine::restorePersistentMessages() {
+}
+
+void Machine::sendMessage(uint32 messageHash, long messageValue, Machine *sender) {
+}
+
+void Machine::resetSwitchTime() {
+}
+
+bool Machine::changeSequenceProgram(int32 sequenceHash) {
+ return _sequence->changeProgram(sequenceHash);
+}
+
+bool Machine::searchMessages(uint32 messageHash, uint32 messageValue, Machine *sender) {
+ return false;
+}
+
+bool Machine::searchPersistentMessages(uint32 messageHash, uint32 messageValue, Machine *sender) {
+ return false;
+}
+
+void Machine::enterState() {
+
+ MachineAsset *machineAsset = _ws->assets()->getMachine(_machHash);
+
+ _code->jumpAbsolute(machineAsset->getStateOffset(_currentState));
+
+ int32 instruction = -1;
+
+ _recursionLevel++;
+
+ int32 oldId = _id;
+ int32 oldRecursionLevel = _recursionLevel;
+
+ while (instruction && instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel) {
+ instruction = execInstruction();
+ }
+
+ if (instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel) {
+ _recursionLevel--;
+ }
+
+}
+
+int32 Machine::execInstruction() {
+
+ //printf("Machine::execInstruction()\n"); fflush(stdout);
+
+ bool done = false;
+ Instruction instruction;
+ //Sequence *sequence;
+ int32 machID = _id;
+
+ _code->loadInstruction(instruction);
+
+ if (instruction.instr >= 64) {
+ if (machineConditionalsTable[instruction.instr - 64] != NULL)
+ (this->*machineConditionalsTable[instruction.instr - 64])(instruction);
+ /* The next line is to yield on unimplemented opcodes */
+ else { fflush(stdout); g_system->delayMillis(5000); }
+ } else if (instruction.instr > 0) {
+ if (machineCommandsTable[instruction.instr] != NULL)
+ done = !(this->*machineCommandsTable[instruction.instr])(instruction);
+ /* The next line is to yield on unimplemented opcodes */
+ else { fflush(stdout); g_system->delayMillis(5000); }
+ if (done) {
+ if (_id == machID) {
+ //TODO: Cancel all requests
+ if (_currentState == -1) {
+ // TODO: Set terminated flag and delete machine in WoodScript update
+ }
+ else {
+ // initialize new state
+ enterState();
+ }
+ }
+ }
+ }
+
+ return instruction.instr;
+
+}
+
+void Machine::execBlock(int32 offset, int32 count) {
+
+ // MachineAsset *machineAsset = _ws->assets()->getMachine(_machHash);
+
+ int32 startOffset = offset, endOffset = offset + count;
+
+ _recursionLevel++;
+
+ int32 oldId = _id;
+ int32 oldRecursionLevel = _recursionLevel;
+
+ _code->jumpAbsolute(offset);
+
+ int32 instruction = -1;
+
+ //printf("---------------------------------------\n"); fflush(stdout);
+
+ while (instruction && instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel &&
+ _code->pos() >= (uint32)startOffset && _code->pos() < (uint32)endOffset) {
+
+ instruction = execInstruction();
+ //g_system->delayMillis(500);
+ }
+
+ //printf("---------------------------------------\n"); fflush(stdout);
+
+ if (instruction == 3) {
+ execInstruction();
+ }
+
+ if (instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel) {
+ _recursionLevel--;
+ }
+
+}
+
+bool Machine::m1_gotoState(Instruction &instruction) {
+ //printf("Machine::m1_gotoState() state = %d\n", (int32)instruction.argv[0] >> 16);
+
+ _currentState = (int32)instruction.argv[0] >> 16;
+ _recursionLevel = 0;
+ return false;
+}
+
+bool Machine::m1_jump(Instruction &instruction) {
+ //printf("Machine::m1_jump() ofs = %08X\n", (int32)instruction.argv[0] >> 16);
+
+ _code->jumpRelative((int32)instruction.argv[0] >> 16);
+ return true;
+}
+
+bool Machine::m1_terminate(Instruction &instruction) {
+ //printf("Machine::m1_terminate()\n"); fflush(stdout);
+
+ _currentState = -1;
+ _recursionLevel = 0;
+ return false;
+}
+
+bool Machine::m1_startSequence(Instruction &instruction) {
+ //printf("Machine::m1_startSequence() sequence hash = %d\n", (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ int32 sequenceHash = instruction.argv[0] >> 16;
+ if (_sequence == NULL) {
+ //printf("Machine::m1_startSequence() creating new sequence\n");
+ _sequence = _ws->createSequence(this, sequenceHash);
+ _code->setSequence(_sequence);
+ } else {
+ //printf("Machine::m1_startSequence() using existing sequence\n");
+ _sequence->changeProgram(sequenceHash);
+ //_code->setSequence(_sequence);
+ }
+ return true;
+}
+
+bool Machine::m1_pauseSequence(Instruction &instruction) {
+ //printf("Machine::m1_pauseSequence()\n"); fflush(stdout);
+
+ _sequence->pause();
+ return true;
+}
+
+bool Machine::m1_resumeSequence(Instruction &instruction) {
+ //printf("Machine::m1_resumeSequence()\n"); fflush(stdout);
+
+ _sequence->resume();
+ return true;
+}
+
+bool Machine::m1_storeValue(Instruction &instruction) {
+ //printf("Machine::m1_storeValue() %p = %d (%08X)\n", (void*)instruction.argp[0], (uint32)instruction.argv[1], (uint32)instruction.argv[1]);
+
+ *instruction.argp[0] = instruction.getValue();
+ return true;
+}
+
+bool Machine::m1_sendMessage(Instruction &instruction) {
+ //printf("Machine::m1_sendMessage() %p = %d (%08X)\n", (void*)instruction.argp[0], (uint32)instruction.argv[1], (uint32)instruction.argv[1]);
+
+#if 0
+//TODO
+ long messageValue;
+
+ if (instruction.argc == 3) {
+ messageValue = instruction.argv[2];
+ } else {
+ messageValue = 0;
+ }
+ //_ws->sendMessage((uint32)instruction.argv[1], messageValue, (uint32)instruction.argv[0] >> 16);
+ //void SendWSMessage(uint32 msgHash, long msgValue, machine *recvM, uint32 machHash, machine *sendM, int32 msgCount) {
+#endif
+ return true;
+
+}
+
+bool Machine::m1_broadcastMessage(Instruction &instruction) {
+ //printf("Machine::m1_broadcastMessage() %p = %d (%08X)\n", (void*)instruction.argp[0], (uint32)instruction.argv[1], (uint32)instruction.argv[1]);
+
+#if 0
+//TODO
+ long messageValue;
+
+ if (instruction.argc == 3) {
+ messageValue = instruction.argv[2];
+ } else {
+ messageValue = 0;
+ }
+ //_ws->sendMessage((uint32)instruction.argv[1], messageValue, (uint32)instruction.argv[0] >> 16);
+#endif
+ return true;
+}
+
+bool Machine::m1_replyMessage(Instruction &instruction) {
+ //printf("Machine::m1_replyMessage() messageHash = %d; messageValue = %d\n", (uint32)instruction.argv[0], (uint32)instruction.argv[1]);
+#if 0
+ if (myArg2) {
+ msgValue = *myArg2;
+ }
+ else {
+ msgValue = 0;
+ }
+ SendWSMessage(*myArg1, msgValue, m->msgReplyXM, 0, m, 1);
+#endif
+ return true;
+}
+
+bool Machine::m1_sendSystemMessage(Instruction &instruction) {
+ //printf("Machine::m1_sendSystemMessage() messageValue = %d\n", (uint32)instruction.argv[0]);
+#if 0
+#endif
+ return true;
+}
+
+bool Machine::m1_createMachine(Instruction &instruction) {
+ //printf("Machine::m1_createMachine()\n");
+#if 0
+#endif
+ return true;
+}
+
+bool Machine::m1_createMachineEx(Instruction &instruction) {
+ //printf("Machine::m1_createMachineEx()\n");
+#if 0
+#endif
+ return true;
+}
+
+bool Machine::m1_clearVars(Instruction &instruction) {
+ //printf("Machine::m1_clearVars()\n"); fflush(stdout);
+
+ _sequence->clearVars();
+ return true;
+}
+
+
+void Machine::m1_onEndSequence(Instruction &instruction) {
+ //printf("Machine::m1_onEndSequence() count = %08X\n", (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ int32 count = instruction.argv[0] >> 16;
+ _sequence->issueEndOfSequenceRequest(_code->pos(), count);
+ _code->jumpRelative(count);
+}
+
+void Machine::m1_onMessage(Instruction &instruction) {
+ //printf("Machine::m1_onEndSequence() count = %08X\n", (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ // TODO: Add message to list
+
+ int32 count = instruction.argv[0] >> 16;
+ _code->jumpRelative(count);
+
+}
+
+void Machine::m1_switchLt(Instruction &instruction) {
+ //printf("Machine::m1_switchLt() %d < %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ if (instruction.argv[1] >= instruction.argv[2])
+ _code->jumpRelative(instruction.argv[0] >> 16);
+}
+
+void Machine::m1_switchLe(Instruction &instruction) {
+ //printf("Machine::m1_switchLe() %d <= %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ if (instruction.argv[1] > instruction.argv[2])
+ _code->jumpRelative(instruction.argv[0] >> 16);
+}
+
+void Machine::m1_switchEq(Instruction &instruction) {
+ //printf("Machine::m1_switchEq() %d == %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ if (instruction.argv[1] != instruction.argv[2])
+ _code->jumpRelative(instruction.argv[0] >> 16);
+}
+
+void Machine::m1_switchNe(Instruction &instruction) {
+ //printf("Machine::m1_switchNe() %d != %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ if (instruction.argv[1] == instruction.argv[2])
+ _code->jumpRelative(instruction.argv[0] >> 16);
+}
+
+void Machine::m1_switchGe(Instruction &instruction) {
+ //printf("Machine::m1_switchGe() %d >= %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ if (instruction.argv[1] < instruction.argv[2])
+ _code->jumpRelative(instruction.argv[0] >> 16);
+}
+
+void Machine::m1_switchGt(Instruction &instruction) {
+ //printf("Machine::m1_switchGt() %d > %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout);
+
+ if (instruction.argv[1] <= instruction.argv[2])
+ _code->jumpRelative(instruction.argv[0] >> 16);
+}
+
+}
diff --git a/engines/m4/ws_sequence.cpp b/engines/m4/ws_sequence.cpp
new file mode 100644
index 0000000000..918979ad17
--- /dev/null
+++ b/engines/m4/ws_sequence.cpp
@@ -0,0 +1,764 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "m4/woodscript.h"
+
+namespace M4 {
+
+long sinCosTable[320] = {
+ 0, 1608, 3215, 4821, 6423, 8022, 9616, 11204,
+ 12785, 14359, 15923, 17479, 19024, 20557, 22078, 23586,
+ 25079, 26557, 28020, 29465, 30893, 32302, 33692, 35061,
+ 36409, 37736, 39039, 40319, 41575, 42806, 44011, 45189,
+ 46340, 47464, 48558, 49624, 50660, 51665, 52639, 53581,
+ 54491, 55368, 56212, 57022, 57797, 58538, 59243, 59913,
+ 60547, 61144, 61705, 62228, 62714, 63162, 63571, 63943,
+ 64276, 64571, 64826, 65043, 65220, 65358, 65457, 65516,
+ 65536, 65516, 65457, 65358, 65220, 65043, 64826, 64571,
+ 64276, 63943, 63571, 63162, 62714, 62228, 61705, 61144,
+ 60547, 59913, 59243, 58538, 57797, 57022, 56212, 55368,
+ 54491, 53581, 52639, 51665, 50660, 49624, 48558, 47464,
+ 46340, 45189, 44011, 42806, 41575, 40319, 39039, 37736,
+ 36409, 35061, 33692, 32302, 30893, 29465, 28020, 26557,
+ 25079, 23586, 22078, 20557, 19024, 17479, 15923, 14359,
+ 12785, 11204, 9616, 8022, 6423, 4821, 3215, 1608,
+ 0, -1608, -3215, -4821, -6423, -8022, -9616, -11204,
+ -12785, -14359, -15923, -17479, -19024, -20557, -22078, -23586,
+ -25079, -26557, -28020, -29465, -30893, -32302, -33692, -35061,
+ -36409, -37736, -39039, -40319, -41575, -42806, -44011, -45189,
+ -46340, -47464, -48558, -49624, -50660, -51665, -52639, -53581,
+ -54491, -55368, -56212, -57022, -57797, -58538, -59243, -59913,
+ -60547, -61144, -61705, -62228, -62714, -63162, -63571, -63943,
+ -64276, -64571, -64826, -65043, -65220, -65358, -65457, -65516,
+ -65536, -65516, -65457, -65358, -65220, -65043, -64826, -64571,
+ -64276, -63943, -63571, -63162, -62714, -62228, -61705, -61144,
+ -60547, -59913, -59243, -58538, -57797, -57022, -56212, -55368,
+ -54491, -53581, -52639, -51665, -50660, -49624, -48558, -47464,
+ -46340, -45189, -44011, -42806, -41575, -40319, -39039, -37736,
+ -36409, -35061, -33692, -32302, -30893, -29465, -28020, -26557,
+ -25079, -23586, -22078, -20557, -19024, -17479, -15923, -14359,
+ -12785, -11204, -9616, -8022, -6423, -4821, -3215, -1608,
+ 0, 1608, 3215, 4821, 6423, 8022, 9616, 11204,
+ 12785, 14359, 15923, 17479, 19024, 20557, 22078, 23586,
+ 25079, 26557, 28020, 29465, 30893, 32302, 33692, 35061,
+ 36409, 37736, 39039, 40319, 41575, 42806, 44011, 45189,
+ 46340, 47464, 48558, 49624, 50660, 51665, 52639, 53581,
+ 54491, 55368, 56212, 57022, 57797, 58538, 59243, 59913,
+ 60547, 61144, 61705, 62228, 62714, 63162, 63571, 63943,
+ 64276, 64571, 64826, 65043, 65220, 65358, 65457, 65516
+};
+
+long *sinTable = &(sinCosTable[0]);
+long *cosTable = &(sinCosTable[64]);
+
+// FIXME: Tables
+
+const int sequenceVariableCount = 33;
+
+enum SequenceVariables {
+ kSeqVarTimer = 0,
+ kSeqVarTag = 1,
+ kSeqVarLayer = 2,
+ kSeqVarWidth = 3,
+ kSeqVarHeight = 4,
+ kSeqVarX = 5,
+ kSeqVarY = 6,
+ kSeqVarScale = 7,
+ kSeqVarR = 8,
+ kSeqVarSpriteHash = 9,
+ kSeqVarSpriteFrameNumber = 10,
+ kSeqVarSpriteFrameCount = 11,
+ kSeqVarSpriteFrameRate = 12,
+ kSeqVarSpriteFramePixelSpeed = 13,
+ kSeqVarTargetS = 14,
+ kSeqVarTargetR = 15,
+ kSeqVarTargetX = 16,
+ kSeqVarTargetY = 17,
+ kSeqVarDeltaS = 18,
+ kSeqVarDeltaR = 19,
+ kSeqVarDeltaX = 20,
+ kSeqVarDeltaY = 21,
+ kSeqVarVelocity = 22,
+ kSeqVarTheta = 23,
+ kSeqVarTemp1 = 24,
+ kSeqVarTemp2 = 25,
+ kSeqVarTemp3 = 26,
+ kSeqVarTemp4 = 27,
+ kSeqVarTemp5 = 28,
+ kSeqVarTemp6 = 29,
+ kSeqVarTemp7 = 30,
+ kSeqVarTemp8 = 31,
+ kSeqVarMachineID = 32
+};
+
+bool (Sequence::*sequenceCommandsTable[])(Instruction &instruction) = {
+ &Sequence::s1_end,
+ &Sequence::s1_clearVars,
+ &Sequence::s1_set,
+ &Sequence::s1_compare,
+ &Sequence::s1_add,
+ &Sequence::s1_sub,
+ &Sequence::s1_mul,
+ &Sequence::s1_div,
+ &Sequence::s1_and,
+ &Sequence::s1_or,
+ &Sequence::s1_not,
+ &Sequence::s1_sin,
+ &Sequence::s1_cos,
+ &Sequence::s1_abs,
+ &Sequence::s1_min,
+ &Sequence::s1_max,
+ &Sequence::s1_mod,
+ &Sequence::s1_floor,
+ &Sequence::s1_round,
+ &Sequence::s1_ceil,
+ &Sequence::s1_point,
+ &Sequence::s1_dist2d,
+ &Sequence::s1_crunch,
+ &Sequence::s1_branch,
+ &Sequence::s1_setFrame,
+ &Sequence::s1_sendMessage,
+ &Sequence::s1_push,
+ &Sequence::s1_pop,
+ &Sequence::s1_jumpSub,
+ &Sequence::s1_return,
+ &Sequence::s1_getFrameCount,
+ &Sequence::s1_getFrameRate,
+ &Sequence::s1_getCelsPixSpeed,
+ &Sequence::s1_setIndex,
+ &Sequence::s1_setLayer,
+ &Sequence::s1_setDepth,
+ &Sequence::s1_setData,
+ &Sequence::s1_openStream,
+ &Sequence::s1_streamNextFrame,
+ &Sequence::s1_closeStream
+};
+
+Sequence::Sequence(WoodScript *ws, Machine *machine, int32 sequenceHash) {
+
+ _ws = ws;
+
+ SequenceAsset *sequenceAsset = _ws->assets()->getSequence(sequenceHash);
+
+ // initialize the sequence's bytecode
+ byte *code;
+ uint32 codeSize;
+ sequenceAsset->getCode(code, codeSize);
+ _code = new Bytecode(_ws, code, codeSize, this);
+
+ _active = true;
+ _sequenceHash = sequenceHash;
+ _machine = machine;
+ _parentSequence = _machine->parentSequence();
+ _dataRow = _machine->dataRow();
+ _startTime = 0;
+ _switchTime = 0;
+ //TODO _flags = 0;
+ _localVarCount = sequenceAsset->localVarCount();
+ _vars = new long[sequenceVariableCount + _localVarCount];
+ _returnStackIndex = 0;
+ _layer = 0;
+ _terminated = false;
+
+ clearVars();
+ _vars[kSeqVarMachineID] = _machine->getId();
+
+}
+
+Sequence::~Sequence() {
+ delete _code;
+}
+
+void Sequence::pause() {
+ _active = false;
+}
+
+void Sequence::resume() {
+ _active = true;
+}
+
+void Sequence::issueEndOfSequenceRequest(int32 codeOffset, int32 count) {
+
+ //printf("Sequence::issueEndOfSequenceRequest(%04X, %04X)\n", codeOffset, count); fflush(stdout);
+ //g_system->delayMillis(5000);
+
+ _endOfSequenceRequest.codeOffset = codeOffset;
+ _endOfSequenceRequest.count = count;
+}
+
+void Sequence::cancelEndOfSequenceRequest() {
+ _endOfSequenceRequest.codeOffset = -1;
+}
+
+bool Sequence::runProgram() {
+
+ bool done = true;
+
+ //printf("_ws->getGlobal(kGlobTime) = %ld, _switchTime = %d\n", _ws->getGlobal(kGlobTime), _switchTime);
+
+ if (_switchTime >= 0 && _ws->getGlobal(kGlobTime) >= _switchTime)
+ done = false;
+
+ _vars[kSeqVarTimer] -= _ws->getGlobal(kGlobTimeDelta) << 16;
+
+ while (!done) {
+ Instruction instruction;
+ _code->loadInstruction(instruction);
+ if (sequenceCommandsTable[instruction.instr] != NULL)
+ done = !(this->*sequenceCommandsTable[instruction.instr])(instruction);
+ else { fflush(stdout); /*g_system->delayMillis(1000);*/ }
+ }
+
+ return _terminated;
+}
+
+void Sequence::clearVars() {
+ for (int i = 0; i < sequenceVariableCount + _localVarCount; i++)
+ _vars[i] = 0;
+ // set default scaling to 100%
+ _vars[kSeqVarScale] = 0x10000;
+}
+
+bool Sequence::changeProgram(int32 sequenceHash) {
+
+ SequenceAsset *sequenceAsset = _ws->assets()->getSequence(sequenceHash);
+
+ if (sequenceAsset->localVarCount() > _localVarCount) {
+ //printf("Sequence::changeProgram(%d) sequenceAsset->localVarCount() > _localVarCount\n", sequenceHash);
+ return false;
+ }
+
+ // Initialize the sequence's bytecode
+ byte *code;
+ uint32 codeSize;
+ sequenceAsset->getCode(code, codeSize);
+ _code->setCode(code, codeSize);
+
+ // Reset status variables
+ _switchTime = 0;
+ _active = true;
+ _terminated = false;
+ _endOfSequenceRequest.codeOffset = -1;
+
+ _sequenceHash = sequenceHash;
+ _returnStackIndex = 0;
+
+ return true;
+
+}
+
+long *Sequence::getVarPtr(int index) {
+ return &_vars[index];
+}
+
+long *Sequence::getParentVarPtr(int index) {
+ return _parentSequence->getVarPtr(index);
+}
+
+long *Sequence::getDataPtr(int index) {
+ return &_dataRow[index];
+}
+
+void Sequence::draw(M4Surface *surface, const Common::Rect &clipRect, Common::Rect &updateRect) {
+
+ SpriteInfo info;
+
+ info.sprite = _curFrame;
+ info.hotX = _curFrame->xOffset;
+ info.hotY = _curFrame->yOffset;
+ info.encoding = _curFrame->encoding;
+ info.inverseColorTable = _vm->_scene->getInverseColorTable();
+ info.palette = _ws->getMainPalette();
+ info.width = _curFrame->w;
+ info.height = _curFrame->h;
+ int32 scaler = FixedMul(_vars[kSeqVarScale], 100 << 16) >> 16;
+ info.scaleX = _vars[kSeqVarWidth] < 0 ? -scaler : scaler;
+ info.scaleY = scaler;
+ surface->drawSprite(_vars[kSeqVarX] >> 16, _vars[kSeqVarY] >> 16, info, clipRect);
+
+}
+
+bool Sequence::s1_end(Instruction &instruction) {
+ //printf("Sequence::s1_end()\n");
+
+ _terminated = true;
+ return false;
+}
+
+bool Sequence::s1_clearVars(Instruction &instruction) {
+ //printf("Sequence::s1_clearVars()\n");
+
+ clearVars();
+ _vars[kSeqVarMachineID] = _machine->getId();
+ return true;
+}
+
+bool Sequence::s1_set(Instruction &instruction) {
+ //printf("Sequence::s1_set()\n");
+
+ *instruction.argp[0] = instruction.getValue();
+ return true;
+}
+
+bool Sequence::s1_compare(Instruction &instruction) {
+ //printf("Sequence::s1_compare()\n");
+
+ long value = instruction.getValue();
+ if (instruction.argv[0] < value)
+ _cmpFlags = -1;
+ else if (instruction.argv[0] > value)
+ _cmpFlags = 1;
+ else
+ _cmpFlags = 0;
+ return true;
+}
+
+bool Sequence::s1_add(Instruction &instruction) {
+ //printf("Sequence::s1_add()\n");
+
+ *instruction.argp[0] += instruction.getValue();
+ return true;
+}
+
+bool Sequence::s1_sub(Instruction &instruction) {
+ //printf("Sequence::s1_sub()\n");
+
+ *instruction.argp[0] -= instruction.getValue();
+ return true;
+}
+
+bool Sequence::s1_mul(Instruction &instruction) {
+ //printf("Sequence::s1_mul()\n");
+
+ *instruction.argp[0] = FixedMul(instruction.argv[0], instruction.getValue());
+ return true;
+}
+
+bool Sequence::s1_div(Instruction &instruction) {
+ //printf("Sequence::s1_div()\n");
+
+ // TODO: Catch divisor = 0 in FixedDiv
+ *instruction.argp[0] = FixedDiv(instruction.argv[0], instruction.getValue());
+ return true;
+}
+
+bool Sequence::s1_and(Instruction &instruction) {
+ //printf("Sequence::s1_and()\n");
+
+ *instruction.argp[0] = instruction.argv[0] & instruction.getValue();
+ if (*instruction.argp[0])
+ _cmpFlags = 0;
+ else
+ _cmpFlags = 1;
+ return true;
+}
+
+bool Sequence::s1_or(Instruction &instruction) {
+ //printf("Sequence::s1_or()\n");
+
+ *instruction.argp[0] = instruction.argv[0] | instruction.getValue();
+ if (*instruction.argp[0])
+ _cmpFlags = 0;
+ else
+ _cmpFlags = 1;
+ return true;
+}
+
+bool Sequence::s1_not(Instruction &instruction) {
+ //printf("Sequence::s1_not()\n");
+
+ if (instruction.argv[0] == 0) {
+ *instruction.argp[0] = 0x10000;
+ _cmpFlags = 1;
+ } else {
+ *instruction.argp[0] = 0;
+ _cmpFlags = 0;
+ }
+ return true;
+}
+
+bool Sequence::s1_sin(Instruction &instruction) {
+ //printf("Sequence::s1_sin()\n");
+
+ int32 tempAngle = *instruction.argp[1] >> 16;
+ if (tempAngle < 0)
+ tempAngle = 0x0100 - ((-tempAngle) & 0xff);
+ else
+ tempAngle &= 0xff;
+
+ *instruction.argp[0] = -cosTable[tempAngle];
+
+ return true;
+}
+
+bool Sequence::s1_cos(Instruction &instruction) {
+ //printf("Sequence::s1_cos()\n");
+
+ int32 tempAngle = *instruction.argp[1] >> 16;
+ if (tempAngle < 0)
+ tempAngle = 0x0100 - ((-tempAngle) & 0xff);
+ else
+ tempAngle &= 0xff;
+
+ *instruction.argp[0] = sinTable[tempAngle];
+
+ return true;
+}
+
+bool Sequence::s1_abs(Instruction &instruction) {
+ //printf("Sequence::s1_abs()\n");
+
+ *instruction.argp[0] = ABS(instruction.argv[1]);
+ return true;
+}
+
+bool Sequence::s1_min(Instruction &instruction) {
+ //printf("Sequence::s1_min()\n");
+
+ *instruction.argp[0] = MIN(instruction.argv[1], instruction.argv[2]);
+ return true;
+}
+
+bool Sequence::s1_max(Instruction &instruction) {
+ //printf("Sequence::s1_max()\n");
+
+ *instruction.argp[0] = MAX(instruction.argv[1], instruction.argv[2]);
+ return true;
+}
+
+bool Sequence::s1_mod(Instruction &instruction) {
+ //printf("Sequence::s1_mod()\n");
+
+ *instruction.argp[0] = instruction.argv[0] % instruction.getValue();
+ return true;
+}
+
+bool Sequence::s1_floor(Instruction &instruction) {
+ //printf("Sequence::s1_floor()\n");
+
+ *instruction.argp[0] = instruction.getValue() & 0xffff0000;
+ return true;
+}
+
+bool Sequence::s1_round(Instruction &instruction) {
+ //printf("Sequence::s1_round()\n");
+
+ if ((*instruction.argp[1] & 0xffff) >= 0x8000)
+ *instruction.argp[0] = (*instruction.argp[1] + 0x10000) & 0xffff0000;
+ else
+ *instruction.argp[0] = *instruction.argp[1] & 0xffff0000;
+ return true;
+}
+
+bool Sequence::s1_ceil(Instruction &instruction) {
+ //printf("Sequence::s1_ceil()\n");
+
+ if ((*instruction.argp[1] & 0xffff) >= 0)
+ *instruction.argp[0] = (*instruction.argp[1] + 0x10000) & 0xffff0000;
+ else
+ *instruction.argp[0] = *instruction.argp[1] & 0xffff0000;
+ return true;
+}
+
+bool Sequence::s1_point(Instruction &instruction) {
+ printf("Sequence::s1_point()\n");
+ // TODO
+ return true;
+}
+
+bool Sequence::s1_dist2d(Instruction &instruction) {
+ printf("Sequence::s1_dist2d()\n");
+ // TODO
+ return true;
+}
+
+bool Sequence::s1_crunch(Instruction &instruction) {
+ //printf("Sequence::s1_crunch()\n");
+
+ long deltaTime;
+
+ if (instruction.argc == 2) {
+ deltaTime = _vm->imath_ranged_rand16(instruction.argv[0], instruction.argv[1]);
+ } else if (instruction.argc == 1) {
+ deltaTime = instruction.argv[0];
+ } else {
+ deltaTime = 0;
+ }
+
+ _startTime = _ws->getGlobal(kGlobTime);
+
+ //printf("deltaTime = %ld\n", deltaTime >> 16); fflush(stdout);
+ //g_system->delayMillis(5000);
+
+ if (deltaTime >= 0) {
+ _switchTime = _ws->getGlobal(kGlobTime) + (deltaTime >> 16);
+ //printf("_ws->getGlobal(kGlobTime) = %ld\n", _ws->getGlobal(kGlobTime)); fflush(stdout);
+ //g_system->delayMillis(5000);
+ } else {
+ _switchTime = -1;
+ }
+
+ // TODO: Update if walking etc.
+
+ return false;
+}
+
+bool Sequence::s1_branch(Instruction &instruction) {
+ //printf("Sequence::s1_branch()\n");
+
+ uint32 ofs = instruction.argv[1] >> 16;
+ switch (instruction.argv[0] >> 16) {
+ case 0: // jmp
+ _code->jumpRelative(ofs);
+ break;
+ case 1: // <
+ if (_cmpFlags < 0)
+ _code->jumpRelative(ofs);
+ break;
+ case 2: // <=
+ if (_cmpFlags <= 0)
+ _code->jumpRelative(ofs);
+ break;
+ case 3: // ==
+ if (_cmpFlags == 0)
+ _code->jumpRelative(ofs);
+ break;
+ case 4: // !=
+ if (_cmpFlags != 0)
+ _code->jumpRelative(ofs);
+ break;
+ case 5: // >=
+ if (_cmpFlags >= 0)
+ _code->jumpRelative(ofs);
+ break;
+ case 6: // >
+ if (_cmpFlags > 0)
+ _code->jumpRelative(ofs);
+ break;
+ }
+
+ return true;
+}
+
+bool Sequence::s1_setFrame(Instruction &instruction) {
+ //printf("Sequence::s1_setFrame()\n");
+
+ int32 frameIndex;
+ if (instruction.argc == 3) {
+ frameIndex = _vm->imath_ranged_rand(instruction.argv[1] >> 16, instruction.argv[2] >> 16);
+ } else if (instruction.argc == 2) {
+ frameIndex = instruction.argv[1] >> 16;
+ } else {
+ frameIndex = (instruction.argv[0] & 0xFF0000) >> 16;
+ }
+
+ //printf("Sequence::s1_setFrame() spriteHash = %d\n", (uint32)instruction.argv[0] >> 24);
+ //printf("Sequence::s1_setFrame() frameIndex = %d\n", frameIndex);
+
+ SpriteAsset *spriteAsset = _ws->assets()->getSprite((uint32)instruction.argv[0] >> 24);
+ _curFrame = spriteAsset->getFrame(frameIndex);
+
+ return true;
+}
+
+bool Sequence::s1_sendMessage(Instruction &instruction) {
+ printf("Sequence::s1_sendMessage()\n");
+ // TODO
+ return true;
+}
+
+bool Sequence::s1_push(Instruction &instruction) {
+ printf("Sequence::s1_push()\n");
+ // TODO
+ return true;
+}
+
+bool Sequence::s1_pop(Instruction &instruction) {
+ printf("Sequence::s1_pop()\n");
+ // TODO
+ return true;
+}
+
+bool Sequence::s1_jumpSub(Instruction &instruction) {
+ //printf("Sequence::s1_jumpSub()\n");
+
+ _returnHashes[_returnStackIndex] = _sequenceHash;
+ _returnOffsets[_returnStackIndex] = _code->pos();
+ _returnStackIndex++;
+
+ _sequenceHash = instruction.argv[0] >> 16;
+
+ SequenceAsset *sequenceAsset = _ws->assets()->getSequence(_sequenceHash);
+
+ // initialize the sequence's bytecode
+ byte *code;
+ uint32 codeSize;
+ sequenceAsset->getCode(code, codeSize);
+ _code->setCode(code, codeSize);
+
+ return true;
+}
+
+bool Sequence::s1_return(Instruction &instruction) {
+ //printf("Sequence::s1_return()\n");
+
+ if (_returnStackIndex <= 0)
+ return s1_end(instruction);
+
+ _returnStackIndex--;
+
+ _sequenceHash = _returnHashes[_returnStackIndex];
+ uint32 ofs = _returnOffsets[_returnStackIndex];
+
+ SequenceAsset *sequenceAsset = _ws->assets()->getSequence(_sequenceHash);
+
+ // initialize the sequence's bytecode
+ byte *code;
+ uint32 codeSize;
+ sequenceAsset->getCode(code, codeSize);
+ _code->setCode(code, codeSize);
+ _code->jumpAbsolute(ofs);
+
+
+ return true;
+}
+
+bool Sequence::s1_getFrameCount(Instruction &instruction) {
+ //printf("Sequence::s1_getFrameCount()\n");
+
+ SpriteAsset *spriteAsset = _ws->assets()->getSprite(instruction.argv[1] >> 24);
+ *instruction.argp[0] = spriteAsset->getCount() << 16;
+ return true;
+}
+
+bool Sequence::s1_getFrameRate(Instruction &instruction) {
+ //printf("Sequence::s1_getFrameRate()\n");
+
+ SpriteAsset *spriteAsset = _ws->assets()->getSprite(instruction.argv[1] >> 24);
+ *instruction.argp[0] = spriteAsset->getFrameRate();
+ return true;
+}
+
+bool Sequence::s1_getCelsPixSpeed(Instruction &instruction) {
+ printf("Sequence::s1_getCelsPixSpeed()\n");
+ // TODO
+ return true;
+}
+
+bool Sequence::s1_setIndex(Instruction &instruction) {
+ printf("Sequence::s1_setIndex()\n");
+ // TODO
+ return true;
+}
+
+bool Sequence::s1_setLayer(Instruction &instruction) {
+ printf("Sequence::s1_setLayer()\n");
+ //TODO
+ return true;
+}
+
+bool Sequence::s1_setDepth(Instruction &instruction) {
+ printf("Sequence::s1_setDepth()\n");
+ //TODO
+ return true;
+}
+
+bool Sequence::s1_setData(Instruction &instruction) {
+ printf("Sequence::s1_setData()\n");
+ //TODO
+ return true;
+}
+
+bool Sequence::s1_openStream(Instruction &instruction) {
+ //printf("Sequence::s1_openStream()\n");
+
+ _stream = _vm->res()->openFile(_machine->name().c_str());
+ streamOpen();
+ return true;
+}
+
+bool Sequence::s1_streamNextFrame(Instruction &instruction) {
+ //printf("Sequence::s1_streamNextFrame()\n");
+
+ streamNextFrame();
+ return true;
+}
+
+bool Sequence::s1_closeStream(Instruction &instruction) {
+ printf("Sequence::s1_closeStream()\n");
+ //TODO
+ return true;
+}
+
+bool Sequence::streamOpen() {
+
+ _streamSpriteAsset = new SpriteAsset(_vm, _stream, _stream->size(), "stream", true);
+
+ _vars[kSeqVarSpriteFrameNumber] = -0x10000;
+ _vars[kSeqVarSpriteFrameCount] = _streamSpriteAsset->getCount() << 16;
+ _vars[kSeqVarSpriteFrameRate] = _streamSpriteAsset->getFrameRate() << 16;
+
+ //printf("Sequence::streamOpen() frames = %d; max = %d x %d\n", _streamSpriteAsset->getCount(), _streamSpriteAsset->getMaxFrameWidth(), _streamSpriteAsset->getMaxFrameHeight());
+ //fflush(stdout);
+
+ _curFrame = new M4Sprite(_vm, _streamSpriteAsset->getMaxFrameWidth(), _streamSpriteAsset->getMaxFrameHeight());
+ streamNextFrame();
+
+ // TODO: Just a hack to see the series with the correct palette.
+ _vm->_palette->setPalette(_streamSpriteAsset->getPalette(), 0, 256);
+
+ return true;
+}
+
+bool Sequence::streamNextFrame() {
+
+ _vars[kSeqVarSpriteFrameNumber] += 0x10000;
+
+ int32 frameNum = _vars[kSeqVarSpriteFrameNumber] >> 16;
+ if (frameNum >= _streamSpriteAsset->getCount()) {
+ // End reached
+ return false;
+ }
+
+ _streamSpriteAsset->loadStreamingFrame(_curFrame, frameNum, _vars[kSeqVarX], _vars[kSeqVarY]);
+
+ _vars[kSeqVarWidth] = _curFrame->w << 16;
+ _vars[kSeqVarHeight] = _curFrame->h << 16;
+
+ return true;
+}
+
+void Sequence::streamClose() {
+ _stream = NULL;
+ _vm->res()->toss(_machine->name().c_str());
+ //_vm->res()->purge();
+ delete _streamSpriteAsset;
+ delete _curFrame;
+ _stream = NULL;
+ _streamSpriteAsset = NULL;
+ _curFrame = NULL;
+}
+
+}