aboutsummaryrefslogtreecommitdiff
path: root/engines/saga
diff options
context:
space:
mode:
authorMax Horn2006-02-11 22:45:04 +0000
committerMax Horn2006-02-11 22:45:04 +0000
commit26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch)
tree26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/saga
parent2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff)
downloadscummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/saga')
-rw-r--r--engines/saga/actor.cpp3092
-rw-r--r--engines/saga/actor.h778
-rw-r--r--engines/saga/animation.cpp720
-rw-r--r--engines/saga/animation.h198
-rw-r--r--engines/saga/console.cpp158
-rw-r--r--engines/saga/console.h62
-rw-r--r--engines/saga/events.cpp590
-rw-r--r--engines/saga/events.h179
-rw-r--r--engines/saga/font.cpp681
-rw-r--r--engines/saga/font.h205
-rw-r--r--engines/saga/font_map.cpp293
-rw-r--r--engines/saga/game.cpp1790
-rw-r--r--engines/saga/gfx.cpp485
-rw-r--r--engines/saga/gfx.h164
-rw-r--r--engines/saga/ihnm_introproc.cpp331
-rw-r--r--engines/saga/image.cpp439
-rw-r--r--engines/saga/input.cpp163
-rw-r--r--engines/saga/interface.cpp2453
-rw-r--r--engines/saga/interface.h466
-rw-r--r--engines/saga/isomap.cpp1694
-rw-r--r--engines/saga/isomap.h294
-rw-r--r--engines/saga/ite_introproc.cpp1028
-rw-r--r--engines/saga/itedata.cpp484
-rw-r--r--engines/saga/itedata.h113
-rw-r--r--engines/saga/list.h156
-rw-r--r--engines/saga/module.mk44
-rw-r--r--engines/saga/music.cpp524
-rw-r--r--engines/saga/music.h148
-rw-r--r--engines/saga/objectmap.cpp267
-rw-r--r--engines/saga/objectmap.h128
-rw-r--r--engines/saga/palanim.cpp209
-rw-r--r--engines/saga/palanim.h63
-rw-r--r--engines/saga/puzzle.cpp560
-rw-r--r--engines/saga/puzzle.h120
-rw-r--r--engines/saga/render.cpp189
-rw-r--r--engines/saga/render.h93
-rw-r--r--engines/saga/resnames.h253
-rw-r--r--engines/saga/rscfile.cpp615
-rw-r--r--engines/saga/rscfile.h173
-rw-r--r--engines/saga/saga.cpp505
-rw-r--r--engines/saga/saga.h741
-rw-r--r--engines/saga/saveload.cpp299
-rw-r--r--engines/saga/scene.cpp1283
-rw-r--r--engines/saga/scene.h370
-rw-r--r--engines/saga/script.cpp806
-rw-r--r--engines/saga/script.h637
-rw-r--r--engines/saga/sfuncs.cpp2038
-rw-r--r--engines/saga/sndres.cpp297
-rw-r--r--engines/saga/sndres.h68
-rw-r--r--engines/saga/sound.cpp149
-rw-r--r--engines/saga/sound.h97
-rw-r--r--engines/saga/sprite.cpp444
-rw-r--r--engines/saga/sprite.h102
-rw-r--r--engines/saga/sthread.cpp746
-rw-r--r--engines/saga/stream.h57
-rw-r--r--engines/saga/xref.txt139
56 files changed, 29180 insertions, 0 deletions
diff --git a/engines/saga/actor.cpp b/engines/saga/actor.cpp
new file mode 100644
index 0000000000..cc89f0ef2a
--- /dev/null
+++ b/engines/saga/actor.cpp
@@ -0,0 +1,3092 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 "saga/saga.h"
+
+#include "saga/actor.h"
+#include "saga/animation.h"
+#include "saga/console.h"
+#include "saga/events.h"
+#include "saga/gfx.h"
+#include "saga/interface.h"
+#include "saga/isomap.h"
+#include "saga/itedata.h"
+#include "saga/objectmap.h"
+#include "saga/resnames.h"
+#include "saga/rscfile.h"
+#include "saga/script.h"
+#include "saga/sndres.h"
+#include "saga/sprite.h"
+#include "saga/stream.h"
+#include "saga/font.h"
+#include "saga/sound.h"
+#include "saga/scene.h"
+
+#include "common/config-manager.h"
+
+namespace Saga {
+
+enum ActorFrameIds {
+//ITE
+ kFrameITEStand = 0,
+ kFrameITEWalk = 1,
+ kFrameITESpeak = 2,
+ kFrameITEGive = 3,
+ kFrameITEGesture = 4,
+ kFrameITEWait = 5,
+ kFrameITEPickUp = 6,
+ kFrameITELook = 7,
+//IHNM
+ kFrameIHNMStand = 0,
+ kFrameIHNMSpeak = 1,
+ kFrameIHNMWait = 2,
+ kFrameIHNMGesture = 3,
+ kFrameIHNMWalk = 4
+};
+
+static int commonObjectCompare(const CommonObjectDataPointer& obj1, const CommonObjectDataPointer& obj2) {
+ int p1 = obj1->_location.y - obj1->_location.z;
+ int p2 = obj2->_location.y - obj2->_location.z;
+ if (p1 == p2)
+ return 0;
+ if (p1 < p2)
+ return -1;
+ return 1;
+}
+
+static int tileCommonObjectCompare(const CommonObjectDataPointer& obj1, const CommonObjectDataPointer& obj2) {
+ int p1 = -obj1->_location.u() - obj1->_location.v() - obj1->_location.z;
+ int p2 = -obj2->_location.u() - obj2->_location.v() - obj2->_location.z;
+ //TODO: for kObjNotFlat obj Height*3 of sprite should be added to p1 and p2
+ //if (validObjId(obj1->id)) {
+
+ if (p1 == p2)
+ return 0;
+ if (p1 < p2)
+ return -1;
+ return 1;
+}
+
+// Lookup table to convert 8 cardinal directions to 4
+static const int actorDirectectionsLUT[8] = {
+ ACTOR_DIRECTION_BACK, // kDirUp
+ ACTOR_DIRECTION_RIGHT, // kDirUpRight
+ ACTOR_DIRECTION_RIGHT, // kDirRight
+ ACTOR_DIRECTION_RIGHT, // kDirDownRight
+ ACTOR_DIRECTION_FORWARD,// kDirDown
+ ACTOR_DIRECTION_LEFT, // kDirDownLeft
+ ACTOR_DIRECTION_LEFT, // kDirLeft
+ ACTOR_DIRECTION_LEFT, // kDirUpLeft
+};
+
+static const PathDirectionData pathDirectionLUT[8][3] = {
+ { { 0, 0, -1 }, { 7, -1, -1 }, { 4, 1, -1 } },
+ { { 1, 1, 0 }, { 4, 1, -1 }, { 5, 1, 1 } },
+ { { 2, 0, 1 }, { 5, 1, 1 }, { 6, -1, 1 } },
+ { { 3, -1, 0 }, { 6, -1, 1 }, { 7, -1, -1 } },
+ { { 0, 0, -1 }, { 1, 1, 0 }, { 4, 1, -1 } },
+ { { 1, 1, 0 }, { 2, 0, 1 }, { 5, 1, 1 } },
+ { { 2, 0, 1 }, { 3, -1, 0 }, { 6, -1, 1 } },
+ { { 3, -1, 0 }, { 0, 0, -1 }, { 7, -1, -1 } }
+};
+
+static const int pathDirectionLUT2[8][2] = {
+ { 0, -1 },
+ { 1, 0 },
+ { 0, 1 },
+ { -1, 0 },
+ { 1, -1 },
+ { 1, 1 },
+ { -1, 1 },
+ { -1, -1 }
+};
+
+static const int angleLUT[16][2] = {
+ { 0, -256 },
+ { 98, -237 },
+ { 181, -181 },
+ { 237, -98 },
+ { 256, 0 },
+ { 237, 98 },
+ { 181, 181 },
+ { 98, 237 },
+ { 0, 256 },
+ { -98, 237 },
+ { -181, 181 },
+ { -237, 98 },
+ { -256, 0 },
+ { -237, -98 },
+ { -181, -181 },
+ { -98, -237 }
+};
+
+static const int directionLUT[8][2] = {
+ { 0 * 2, -2 * 2 },
+ { 2 * 2, -1 * 2 },
+ { 3 * 2, 0 * 2 },
+ { 2 * 2, 1 * 2 },
+ { 0 * 2, 2 * 2 },
+ { -2 * 2, 1 * 2 },
+ { -4 * 2, 0 * 2 },
+ { -2 * 2, -1 * 2 }
+};
+
+static const int tileDirectionLUT[8][2] = {
+ { 1, 1 },
+ { 2, 0 },
+ { 1, -1 },
+ { 0, -2 },
+ { -1, -1 },
+ { -2, 0 },
+ { -1, 1 },
+ { 0, 2 }
+};
+
+struct DragonMove {
+ uint16 baseFrame;
+ int16 offset[4][2];
+};
+
+static const DragonMove dragonMoveTable[12] = {
+ { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+ { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+ { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+ { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+ { 28, { { -0, 0 }, { -1, 6 }, { -5, 11 }, { -10, 15 } } },
+ { 56, { { 0, 0 }, { 1, 6 }, { 5, 11 }, { 10, 15 } } },
+ { 40, { { 0, 0 }, { 6, 1 }, { 11, 5 }, { 15, 10 } } },
+ { 44, { { 0, 0 }, { 6, -1 }, { 11, -5 }, { 15, -10 } } },
+ { 32, { { -0, -0 }, { -6, -1 }, { -11, -5 }, { -15, -10 } } },
+ { 52, { { -0, 0 }, { -6, 1 }, { -11, 5 }, { -15, 10 } } },
+ { 36, { { 0, -0 }, { 1, -6 }, { 5, -11 }, { 10, -15 } } },
+ { 48, { { -0, -0 }, { -1, -6 }, { -5, -11 }, { -10, -15 } } }
+};
+
+Actor::Actor(SagaEngine *vm) : _vm(vm) {
+ int i;
+ byte *stringsPointer;
+ size_t stringsLength;
+ ActorData *actor;
+ ObjectData *obj;
+ debug(9, "Actor::Actor()");
+ _handleActionDiv = 15;
+
+ _actors = NULL;
+ _actorsCount = 0;
+
+ _objs = NULL;
+ _objsCount = 0;
+
+#ifdef ACTOR_DEBUG
+ _debugPoints = NULL;
+ _debugPointsAlloced = _debugPointsCount = 0;
+#endif
+
+ _protagStates = 0;
+ _protagStatesCount = 0;
+
+ _pathNodeList = _newPathNodeList = NULL;
+ _pathList = NULL;
+ _pathDirectionList = NULL;
+ _pathListAlloced = _pathNodeListAlloced = _newPathNodeListAlloced = 0;
+ _pathListIndex = _pathNodeListIndex = _newPathNodeListIndex = -1;
+ _pathDirectionListCount = 0;
+ _pathDirectionListAlloced = 0;
+
+ _centerActor = _protagonist = NULL;
+ _protagState = 0;
+ _lastTickMsec = 0;
+
+ _yCellCount = _vm->_scene->getHeight();
+ _xCellCount = _vm->getDisplayWidth();
+
+ _pathCell = (int8 *)malloc(_yCellCount * _xCellCount * sizeof(*_pathCell));
+
+ _pathRect.left = 0;
+ _pathRect.right = _vm->getDisplayWidth();
+ _pathRect.top = _vm->getDisplayInfo().pathStartY;
+ _pathRect.bottom = _vm->_scene->getHeight();
+
+ // Get actor resource file context
+ _actorContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (_actorContext == NULL) {
+ error("Actor::Actor() resource context not found");
+ }
+
+ // Load ITE actor strings. (IHNM actor strings are loaded by
+ // loadGlobalResources() instead.)
+
+ if (_vm->getGameType() == GType_ITE) {
+
+ _vm->_resource->loadResource(_actorContext, _vm->getResourceDescription()->actorsStringsResourceId, stringsPointer, stringsLength);
+
+ _vm->loadStrings(_actorsStrings, stringsPointer, stringsLength);
+ free(stringsPointer);
+ }
+
+ if (_vm->getGameType() == GType_ITE) {
+ _actorsCount = ITE_ACTORCOUNT;
+ _actors = (ActorData **)malloc(_actorsCount * sizeof(*_actors));
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i] = new ActorData();
+ actor->_id = actorIndexToId(i);
+ actor->_index = i;
+ debug(9, "init actor id=%d index=%d", actor->_id, actor->_index);
+ actor->_nameIndex = ITE_ActorTable[i].nameIndex;
+ actor->_scriptEntrypointNumber = ITE_ActorTable[i].scriptEntrypointNumber;
+ actor->_spriteListResourceId = ITE_ActorTable[i].spriteListResourceId;
+ actor->_frameListResourceId = ITE_ActorTable[i].frameListResourceId;
+ actor->_speechColor = ITE_ActorTable[i].speechColor;
+ actor->_sceneNumber = ITE_ActorTable[i].sceneIndex;
+ actor->_flags = ITE_ActorTable[i].flags;
+ actor->_currentAction = ITE_ActorTable[i].currentAction;
+ actor->_facingDirection = ITE_ActorTable[i].facingDirection;
+ actor->_actionDirection = ITE_ActorTable[i].actionDirection;
+
+ actor->_location.x = ITE_ActorTable[i].x;
+ actor->_location.y = ITE_ActorTable[i].y;
+ actor->_location.z = ITE_ActorTable[i].z;
+
+ actor->_disabled = !loadActorResources(actor);
+ if (actor->_disabled) {
+ warning("Disabling actor Id=%d index=%d", actor->_id, actor->_index);
+ }
+ }
+ _objsCount = ITE_OBJECTCOUNT;
+ _objs = (ObjectData **)malloc(_objsCount * sizeof(*_objs));
+ for (i = 0; i < _objsCount; i++) {
+ obj = _objs[i] = new ObjectData();
+ obj->_id = objIndexToId(i);
+ obj->_index = i;
+ debug(9, "init obj id=%d index=%d", obj->_id, obj->_index);
+ obj->_nameIndex = ITE_ObjectTable[i].nameIndex;
+ obj->_scriptEntrypointNumber = ITE_ObjectTable[i].scriptEntrypointNumber;
+ obj->_spriteListResourceId = ITE_ObjectTable[i].spriteListResourceId;
+ obj->_sceneNumber = ITE_ObjectTable[i].sceneIndex;
+ obj->_interactBits = ITE_ObjectTable[i].interactBits;
+
+ obj->_location.x = ITE_ObjectTable[i].x;
+ obj->_location.y = ITE_ObjectTable[i].y;
+ obj->_location.z = ITE_ObjectTable[i].z;
+ }
+ } else {
+ // TODO. This is causing problems for SYMBIAN os as it doesn't like a static class here
+ ActorData dummyActor;
+
+ dummyActor._frames = NULL;
+ dummyActor._walkStepsPoints = NULL;
+
+ _protagonist = &dummyActor;
+ }
+
+ _dragonHunt = true;
+}
+
+Actor::~Actor() {
+ debug(9, "Actor::~Actor()");
+
+#ifdef ACTOR_DEBUG
+ free(_debugPoints);
+#endif
+ free(_pathDirectionList);
+ free(_pathNodeList);
+ free(_newPathNodeList);
+ free(_pathList);
+ free(_pathCell);
+ _actorsStrings.freeMem();
+ //release resources
+ freeActorList();
+ freeObjList();
+}
+
+bool Actor::loadActorResources(ActorData *actor) {
+ byte *resourcePointer;
+ size_t resourceLength;
+ int framesCount;
+ ActorFrameSequence *framesPointer;
+ bool gotSomething = false;
+
+ if (actor->_frameListResourceId) {
+ debug(9, "Loading frame resource id %d", actor->_frameListResourceId);
+ _vm->_resource->loadResource(_actorContext, actor->_frameListResourceId, resourcePointer, resourceLength);
+
+ framesCount = resourceLength / 16;
+ debug(9, "Frame resource contains %d frames (res length is %d)", framesCount, resourceLength);
+
+ framesPointer = (ActorFrameSequence *)malloc(sizeof(ActorFrameSequence) * framesCount);
+ if (framesPointer == NULL && framesCount != 0) {
+ memoryError("Actor::loadActorResources");
+ }
+
+ MemoryReadStreamEndian readS(resourcePointer, resourceLength, _actorContext->isBigEndian);
+
+ for (int i = 0; i < framesCount; i++) {
+ debug(9, "frameType %d", i);
+ for (int orient = 0; orient < ACTOR_DIRECTIONS_COUNT; orient++) {
+ // Load all four orientations
+ framesPointer[i].directions[orient].frameIndex = readS.readUint16();
+ if (_vm->getGameType() == GType_ITE) {
+ framesPointer[i].directions[orient].frameCount = readS.readSint16();
+ } else {
+ framesPointer[i].directions[orient].frameCount = readS.readByte();
+ readS.readByte();
+ }
+ if (framesPointer[i].directions[orient].frameCount < 0)
+ warning("frameCount < 0 (%d)", framesPointer[i].directions[orient].frameCount);
+ debug(9, "frameIndex %d frameCount %d", framesPointer[i].directions[orient].frameIndex, framesPointer[i].directions[orient].frameCount);
+ }
+ }
+
+ free(resourcePointer);
+
+ actor->_frames = framesPointer;
+ actor->_framesCount = framesCount;
+
+ gotSomething = true;
+ } else {
+ warning("Frame List ID = 0 for actor index %d", actor->_index);
+
+ //if (_vm->getGameType() == GType_ITE)
+ return true;
+ }
+
+ if (actor->_spriteListResourceId) {
+ gotSomething = true;
+ } else {
+ warning("Sprite List ID = 0 for actor index %d", actor->_index);
+ }
+
+ return gotSomething;
+}
+
+void Actor::freeActorList() {
+ int i;
+ ActorData *actor;
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i];
+ delete actor;
+ }
+ free(_actors);
+ _actors = NULL;
+ _actorsCount = 0;
+}
+
+void Actor::loadActorSpriteList(ActorData *actor) {
+ int lastFrame = 0;
+ int resourceId = actor->_spriteListResourceId;
+
+ for (int i = 0; i < actor->_framesCount; i++) {
+ for (int orient = 0; orient < ACTOR_DIRECTIONS_COUNT; orient++) {
+ if (actor->_frames[i].directions[orient].frameIndex > lastFrame) {
+ lastFrame = actor->_frames[i].directions[orient].frameIndex;
+ }
+ }
+ }
+
+ debug(9, "Loading actor sprite resource id %d", resourceId);
+
+ _vm->_sprite->loadList(resourceId, actor->_spriteList);
+
+ if (_vm->getGameType() == GType_ITE) {
+ if (actor->_flags & kExtended) {
+ while ((lastFrame >= actor->_spriteList.spriteCount)) {
+ resourceId++;
+ debug(9, "Appending to actor sprite list %d", resourceId);
+ _vm->_sprite->loadList(resourceId, actor->_spriteList);
+ }
+ }
+ }
+}
+
+void Actor::loadActorList(int protagonistIdx, int actorCount, int actorsResourceID, int protagStatesCount, int protagStatesResourceID) {
+ int i, j;
+ ActorData *actor;
+ byte* actorListData;
+ size_t actorListLength;
+ byte walk[128];
+ byte acv[6];
+ int movementSpeed;
+ int walkStepIndex;
+ int walkStepCount;
+
+ freeActorList();
+
+ _vm->_resource->loadResource(_actorContext, actorsResourceID, actorListData, actorListLength);
+
+ _actorsCount = actorCount;
+
+ if (actorListLength != (uint)_actorsCount * ACTOR_INHM_SIZE) {
+ error("Actor::loadActorList wrong actorlist length");
+ }
+
+ MemoryReadStream actorS(actorListData, actorListLength);
+
+ _actors = (ActorData **)malloc(_actorsCount * sizeof(*_actors));
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i] = new ActorData();
+ actor->_id = objectIndexToId(kGameObjectActor, i); //actorIndexToId(i);
+ actor->_index = i;
+ debug(4, "init actor id=0x%x index=%d", actor->_id, actor->_index);
+ actorS.readUint32LE(); //next displayed
+ actorS.readByte(); //type
+ actor->_flags = actorS.readByte();
+ actor->_nameIndex = actorS.readUint16LE();
+ actor->_sceneNumber = actorS.readUint32LE();
+ actor->_location.fromStream(actorS);
+ actor->_screenPosition.x = actorS.readUint16LE();
+ actor->_screenPosition.y = actorS.readUint16LE();
+ actor->_screenScale = actorS.readUint16LE();
+ actor->_screenDepth = actorS.readUint16LE();
+ actor->_spriteListResourceId = actorS.readUint32LE();
+ actor->_frameListResourceId = actorS.readUint32LE();
+ debug(4, "%d: %d, %d [%d]", i, actor->_spriteListResourceId, actor->_frameListResourceId, actor->_nameIndex);
+ actor->_scriptEntrypointNumber = actorS.readUint32LE();
+ actorS.readUint32LE(); // xSprite *dSpr;
+ actorS.readUint16LE(); //LEFT
+ actorS.readUint16LE(); //RIGHT
+ actorS.readUint16LE(); //TOP
+ actorS.readUint16LE(); //BOTTOM
+ actor->_speechColor = actorS.readByte();
+ actor->_currentAction = actorS.readByte();
+ actor->_facingDirection = actorS.readByte();
+ actor->_actionDirection = actorS.readByte();
+ actor->_actionCycle = actorS.readUint16LE();
+ actor->_frameNumber = actorS.readUint16LE();
+ actor->_finalTarget.fromStream(actorS);
+ actor->_partialTarget.fromStream(actorS);
+ movementSpeed = actorS.readUint16LE(); //movement speed
+ if (movementSpeed) {
+ error("Actor::loadActorList movementSpeed != 0");
+ }
+ actorS.read(walk, 128);
+ for (j = 0; j < 128; j++) {
+ if (walk[j]) {
+ error("Actor::loadActorList walk[128] != 0");
+ }
+ }
+ //actorS.seek(128, SEEK_CUR);
+ walkStepCount = actorS.readByte();//walkStepCount
+ if (walkStepCount) {
+ error("Actor::loadActorList walkStepCount != 0");
+ }
+ walkStepIndex = actorS.readByte();//walkStepIndex
+ if (walkStepIndex) {
+ error("Actor::loadActorList walkStepIndex != 0");
+ }
+ //no need to check pointers
+ actorS.readUint32LE(); //sprites
+ actorS.readUint32LE(); //frames
+ actorS.readUint32LE(); //last zone
+ actor->_targetObject = actorS.readUint16LE();
+ actor->_actorFlags = actorS.readUint16LE();
+ //no need to check pointers
+ actorS.readUint32LE(); //next in scene
+ actorS.read(acv, 6);
+ for (j = 0; j < 6; j++) {
+ if (acv[j]) {
+ error("Actor::loadActorList acv[%d] != 0", j);
+ }
+ }
+// actorS.seek(6, SEEK_CUR); //action vars
+ }
+ free(actorListData);
+
+ _actors[protagonistIdx]->_flags |= kProtagonist | kExtended;
+
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i];
+ //if (actor->_flags & kProtagonist) {
+ loadActorResources(actor);
+ //break;
+ //}
+ }
+
+ _centerActor = _protagonist = _actors[protagonistIdx];
+ _protagState = 0;
+
+ if (protagStatesResourceID) {
+ free(_protagStates);
+
+ _protagStates = (ActorFrameSequence *)malloc(sizeof(ActorFrameSequence) * protagStatesCount);
+
+ byte *resourcePointer;
+ size_t resourceLength;
+
+ _vm->_resource->loadResource(_actorContext, protagStatesResourceID,
+ resourcePointer, resourceLength);
+
+
+ MemoryReadStream statesS(resourcePointer, resourceLength);
+
+ for (i = 0; i < protagStatesCount; i++) {
+ for (j = 0; j < ACTOR_DIRECTIONS_COUNT; j++) {
+ _protagStates[i].directions[j].frameIndex = statesS.readUint16LE();
+ _protagStates[i].directions[j].frameCount = statesS.readUint16LE();
+ }
+ }
+ free(resourcePointer);
+
+ _protagonist->_frames = &_protagStates[_protagState];
+ }
+
+ _protagStatesCount = protagStatesCount;
+}
+
+void Actor::freeObjList() {
+ int i;
+ ObjectData *object;
+ for (i = 0; i < _objsCount; i++) {
+ object = _objs[i];
+ delete object;
+ }
+ free(_objs);
+ _objs = NULL;
+ _objsCount = 0;
+}
+
+void Actor::loadObjList(int objectCount, int objectsResourceID) {
+ int i;
+ int frameListResourceId;
+ ObjectData *object;
+ byte* objectListData;
+ size_t objectListLength;
+ freeObjList();
+
+ _vm->_resource->loadResource(_actorContext, objectsResourceID, objectListData, objectListLength);
+
+ _objsCount = objectCount;
+
+ MemoryReadStream objectS(objectListData, objectListLength);
+
+ _objs = (ObjectData **)malloc(_objsCount * sizeof(*_objs));
+ for (i = 0; i < _objsCount; i++) {
+ object = _objs[i] = new ObjectData();
+ object->_id = objectIndexToId(kGameObjectObject, i);
+ object->_index = i;
+ debug(9, "init object id=%d index=%d", object->_id, object->_index);
+ objectS.readUint32LE(); //next displayed
+ objectS.readByte(); //type
+ object->_flags = objectS.readByte();
+ object->_nameIndex = objectS.readUint16LE();
+ object->_sceneNumber = objectS.readUint32LE();
+ object->_location.fromStream(objectS);
+ object->_screenPosition.x = objectS.readUint16LE();
+ object->_screenPosition.y = objectS.readUint16LE();
+ object->_screenScale = objectS.readUint16LE();
+ object->_screenDepth = objectS.readUint16LE();
+ object->_spriteListResourceId = objectS.readUint32LE();
+ frameListResourceId = objectS.readUint32LE(); // object->_frameListResourceId
+ if (frameListResourceId) {
+ error("Actor::loadObjList frameListResourceId != 0");
+ }
+ object->_scriptEntrypointNumber = objectS.readUint32LE();
+ objectS.readUint32LE(); // xSprite *dSpr;
+ objectS.readUint16LE(); //LEFT
+ objectS.readUint16LE(); //RIGHT
+ objectS.readUint16LE(); //TOP
+ objectS.readUint16LE(); //BOTTOM
+ object->_interactBits = objectS.readUint16LE();
+ }
+ free(objectListData);
+}
+
+void Actor::takeExit(uint16 actorId, const HitZone *hitZone) {
+ ActorData *actor;
+ actor = getActor(actorId);
+ actor->_lastZone = NULL;
+
+ _vm->_scene->changeScene(hitZone->getSceneNumber(), hitZone->getActorsEntrance(), kTransitionNoFade);
+ if (_vm->_interface->getMode() != kPanelSceneSubstitute) {
+ _vm->_script->setNoPendingVerb();
+ }
+}
+
+void Actor::stepZoneAction(ActorData *actor, const HitZone *hitZone, bool exit, bool stopped) {
+ Event event;
+
+ if (actor != _protagonist) {
+ return;
+ }
+ if (((hitZone->getFlags() & kHitZoneTerminus) && !stopped) || (!(hitZone->getFlags() & kHitZoneTerminus) && stopped)) {
+ return;
+ }
+
+ if (!exit) {
+ if (hitZone->getFlags() & kHitZoneAutoWalk) {
+ actor->_currentAction = kActionWalkDir;
+ actor->_actionDirection = actor->_facingDirection = hitZone->getDirection();
+ actor->_walkFrameSequence = getFrameType(kFrameWalk);
+ return;
+ }
+ } else if (!(hitZone->getFlags() & kHitZoneAutoWalk)) {
+ return;
+ }
+ if (hitZone->getFlags() & kHitZoneExit) {
+ takeExit(actor->_id, hitZone);
+ } else if (hitZone->getScriptNumber() > 0) {
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventExecNonBlocking;
+ event.time = 0;
+ event.param = _vm->_scene->getScriptModuleNumber(); // module number
+ event.param2 = hitZone->getScriptNumber(); // script entry point number
+ event.param3 = _vm->_script->getVerbType(kVerbEnter); // Action
+ event.param4 = ID_NOTHING; // Object
+ event.param5 = ID_NOTHING; // With Object
+ event.param6 = ID_PROTAG; // Actor
+
+ _vm->_events->queue(&event);
+ }
+}
+
+void Actor::realLocation(Location &location, uint16 objectId, uint16 walkFlags) {
+ int angle;
+ int distance;
+ ActorData *actor;
+ ObjectData *obj;
+ debug (8, "Actor::realLocation objectId=%i", objectId);
+ if (walkFlags & kWalkUseAngle) {
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ angle = (location.x + 2) & 15;
+ distance = location.y;
+
+ location.u() = (angleLUT[angle][0] * distance) >> 8;
+ location.v() = -(angleLUT[angle][1] * distance) >> 8;
+ } else {
+ angle = location.x & 15;
+ distance = location.y;
+
+ location.x = (angleLUT[angle][0] * distance) >> 6;
+ location.y = (angleLUT[angle][1] * distance) >> 6;
+ }
+ }
+
+ if (objectId != ID_NOTHING) {
+ if (validActorId(objectId)) {
+ actor = getActor(objectId);
+ location.addXY(actor->_location);
+ } else if (validObjId(objectId)) {
+ obj = getObj(objectId);
+ location.addXY(obj->_location);
+ }
+ }
+}
+
+void Actor::actorFaceTowardsPoint(uint16 actorId, const Location &toLocation) {
+ ActorData *actor;
+ Location delta;
+ //debug (8, "Actor::actorFaceTowardsPoint actorId=%i", actorId);
+ actor = getActor(actorId);
+
+ toLocation.delta(actor->_location, delta);
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ if (delta.u() > 0) {
+ actor->_facingDirection = (delta.v() > 0) ? kDirUp : kDirRight;
+ } else {
+ actor->_facingDirection = (delta.v() > 0) ? kDirLeft : kDirDown;
+ }
+ } else {
+ if (ABS(delta.y) > ABS(delta.x * 2)) {
+ actor->_facingDirection = (delta.y > 0) ? kDirDown : kDirUp;
+ } else {
+ actor->_facingDirection = (delta.x > 0) ? kDirRight : kDirLeft;
+ }
+ }
+}
+
+void Actor::actorFaceTowardsObject(uint16 actorId, uint16 objectId) {
+ ActorData *actor;
+ ObjectData *obj;
+
+ if (validActorId(objectId)) {
+ actor = getActor(objectId);
+ actorFaceTowardsPoint(actorId, actor->_location);
+ } else if (validObjId(objectId)) {
+ obj = getObj(objectId);
+ actorFaceTowardsPoint(actorId, obj->_location);
+ }
+}
+
+
+ObjectData *Actor::getObj(uint16 objId) {
+ ObjectData *obj;
+
+ if (!validObjId(objId))
+ error("Actor::getObj Wrong objId 0x%X", objId);
+
+ obj = _objs[objIdToIndex(objId)];
+
+ if (obj->_disabled)
+ error("Actor::getObj disabled objId 0x%X", objId);
+
+ return obj;
+}
+
+ActorData *Actor::getActor(uint16 actorId) {
+ ActorData *actor;
+
+ if (!validActorId(actorId)) {
+ warning("Actor::getActor Wrong actorId 0x%X", actorId);
+ assert(0);
+ }
+
+ if (actorId == ID_PROTAG) {
+ if (_protagonist == NULL) {
+ error("_protagonist == NULL");
+ }
+ return _protagonist;
+ }
+
+ actor = _actors[actorIdToIndex(actorId)];
+
+ if (actor->_disabled)
+ error("Actor::getActor disabled actorId 0x%X", actorId);
+
+ return actor;
+}
+
+bool Actor::validFollowerLocation(const Location &location) {
+ Point point;
+ location.toScreenPointXY(point);
+
+ if ((point.x < 5) || (point.x >= _vm->getDisplayWidth() - 5) ||
+ (point.y < 0) || (point.y > _vm->_scene->getHeight())) {
+ return false;
+ }
+
+ return (_vm->_scene->canWalk(point));
+}
+
+void Actor::setProtagState(int state) {
+ _protagState = state;
+
+ if (_vm->getGameType() == GType_IHNM)
+ _protagonist->_frames = &_protagStates[state];
+}
+
+void Actor::updateActorsScene(int actorsEntrance) {
+ int i, j;
+ int followerDirection;
+ ActorData *actor;
+ Location tempLocation;
+ Location possibleLocation;
+ Point delta;
+ const SceneEntry *sceneEntry;
+
+ if (_vm->_scene->currentSceneNumber() == 0) {
+ error("Actor::updateActorsScene _vm->_scene->currentSceneNumber() == 0");
+ }
+
+ _vm->_sound->stopVoice();
+ _activeSpeech.stringsCount = 0;
+ _activeSpeech.playing = false;
+ _protagonist = NULL;
+
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i];
+ actor->_inScene = false;
+ actor->_spriteList.freeMem();
+ if (actor->_disabled) {
+ continue;
+ }
+ if ((actor->_flags & (kProtagonist | kFollower)) || (i == 0)) {
+ if (actor->_flags & kProtagonist) {
+ actor->_finalTarget = actor->_location;
+ _centerActor = _protagonist = actor;
+ } else if (_vm->getGameType() == GType_ITE &&
+ _vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) {
+ continue;
+ }
+
+ actor->_sceneNumber = _vm->_scene->currentSceneNumber();
+ }
+ if (actor->_sceneNumber == _vm->_scene->currentSceneNumber()) {
+ actor->_inScene = true;
+ actor->_actionCycle = (_vm->_rnd.getRandomNumber(7) & 0x7) * 4; // 1/8th chance
+ }
+ }
+
+ assert(_protagonist);
+
+ if ((actorsEntrance >= 0) && (_vm->_scene->_entryList.entryListCount > 0)) {
+ if (_vm->_scene->_entryList.entryListCount <= actorsEntrance) {
+ actorsEntrance = 0; //OCEAN bug
+ }
+
+ sceneEntry = _vm->_scene->_entryList.getEntry(actorsEntrance);
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _protagonist->_location = sceneEntry->location;
+ } else {
+ _protagonist->_location.x = sceneEntry->location.x * ACTOR_LMULT;
+ _protagonist->_location.y = sceneEntry->location.y * ACTOR_LMULT;
+ _protagonist->_location.z = sceneEntry->location.z * ACTOR_LMULT;
+ }
+ // Workaround for bug #1328045:
+ // "When entering any of the houses at the start of the
+ // game if you click on anything inside the building you
+ // start walking through the door, turn around and leave."
+ //
+ // After steping of action zone - Rif trying to exit.
+ // This piece of code shift Rif's entry position to non action zone area.
+ if (_vm->getGameType() == GType_ITE) {
+ if ((_vm->_scene->currentSceneNumber() >= 53) && (_vm->_scene->currentSceneNumber() <= 66))
+ _protagonist->_location.y += 10;
+ }
+
+ _protagonist->_facingDirection = _protagonist->_actionDirection = sceneEntry->facing;
+ }
+
+ _protagonist->_currentAction = kActionWait;
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ //nothing?
+ } else {
+ _vm->_scene->initDoorsState(); //TODO: move to _scene
+ }
+
+ followerDirection = _protagonist->_facingDirection + 3;
+ calcScreenPosition(_protagonist);
+
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i];
+ if (actor->_flags & (kFollower)) {
+ actor->_facingDirection = actor->_actionDirection = _protagonist->_facingDirection;
+ actor->_currentAction = kActionWait;
+ actor->_walkStepsCount = actor->_walkStepIndex = 0;
+ actor->_location.z = _protagonist->_location.z;
+
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->placeOnTileMap(_protagonist->_location, actor->_location, 3, followerDirection & 0x07);
+ } else {
+ followerDirection &= 0x07;
+
+ possibleLocation = _protagonist->_location;
+
+
+ delta.x = directionLUT[followerDirection][0];
+ delta.y = directionLUT[followerDirection][1];
+
+
+ for (j = 0; j < 30; j++) {
+ tempLocation = possibleLocation;
+ tempLocation.x += delta.x;
+ tempLocation.y += delta.y;
+
+ if (validFollowerLocation(tempLocation)) {
+ possibleLocation = tempLocation;
+ } else {
+ tempLocation = possibleLocation;
+ tempLocation.x += delta.x;
+ if (validFollowerLocation(tempLocation)) {
+ possibleLocation = tempLocation;
+ } else {
+ tempLocation = possibleLocation;
+ tempLocation.y += delta.y;
+ if (validFollowerLocation(tempLocation)) {
+ possibleLocation = tempLocation;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ actor->_location = possibleLocation;
+ }
+ followerDirection += 2;
+ }
+
+ }
+
+ handleActions(0, true);
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->adjustScroll(true);
+ }
+}
+
+int Actor::getFrameType(ActorFrameTypes frameType) {
+
+ if (_vm->getGameType() == GType_ITE) {
+ switch (frameType) {
+ case kFrameStand:
+ return kFrameITEStand;
+ case kFrameWalk:
+ return kFrameITEWalk;
+ case kFrameSpeak:
+ return kFrameITESpeak;
+ case kFrameGive:
+ return kFrameITEGive;
+ case kFrameGesture:
+ return kFrameITEGesture;
+ case kFrameWait:
+ return kFrameITEWait;
+ case kFramePickUp:
+ return kFrameITEPickUp;
+ case kFrameLook:
+ return kFrameITELook;
+ }
+ }
+ else {
+ switch (frameType) {
+ case kFrameStand:
+ return kFrameIHNMStand;
+ case kFrameWalk:
+ return kFrameIHNMWalk;
+ case kFrameSpeak:
+ return kFrameIHNMSpeak;
+ case kFrameGesture:
+ return kFrameIHNMGesture;
+ case kFrameWait:
+ return kFrameIHNMWait;
+ case kFrameGive:
+ case kFramePickUp:
+ case kFrameLook:
+ error("Actor::getFrameType() unknown frame type %d", frameType);
+ return kFrameIHNMStand;
+ }
+ }
+ error("Actor::getFrameType() unknown frame type %d", frameType);
+}
+
+ActorFrameRange *Actor::getActorFrameRange(uint16 actorId, int frameType) {
+ ActorData *actor;
+ int fourDirection;
+ static ActorFrameRange def = {0, 0};
+
+ actor = getActor(actorId);
+ if (actor->_disabled)
+ error("Actor::getActorFrameRange Wrong actorId 0x%X", actorId);
+
+ if ((actor->_facingDirection < kDirUp) || (actor->_facingDirection > kDirUpLeft))
+ error("Actor::getActorFrameRange Wrong direction 0x%X actorId 0x%X", actor->_facingDirection, actorId);
+
+ //if (_vm->getGameType() == GType_ITE) {
+ if (frameType >= actor->_framesCount) {
+ warning("Actor::getActorFrameRange Wrong frameType 0x%X (%d) actorId 0x%X", frameType, actor->_framesCount, actorId);
+ return &def;
+ }
+
+
+ fourDirection = actorDirectectionsLUT[actor->_facingDirection];
+ return &actor->_frames[frameType].directions[fourDirection];
+/*
+ } else {
+ if (0 == actor->_framesCount) {
+ return &def;
+ }
+
+ //TEST
+ if (actor->_id == 0x2000) {
+ if (actor->_framesCount <= _currentFrameIndex) {
+ _currentFrameIndex = 0;
+ }
+ fr = actor->_frames[_currentFrameIndex].directions;
+ return fr;
+ }
+ //TEST
+ if (frameType >= actor->_framesCount) {
+ frameType = actor->_framesCount - 1;
+ }
+ if (frameType < 0) {
+ frameType = 0;
+ }
+
+ if (frameType == kFrameIHNMWalk ) {
+ switch (actor->_facingDirection) {
+ case kDirUpRight:
+ if (frameType > 0)
+ fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_RIGHT];
+ else
+ fr = &def;
+ if (!fr->frameCount)
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_RIGHT];
+ break;
+ case kDirDownRight:
+ if (frameType > 0)
+ fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_FORWARD];
+ else
+ fr = &def;
+ if (!fr->frameCount)
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_RIGHT];
+ break;
+ case kDirUpLeft:
+ if (frameType > 0)
+ fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_LEFT];
+ else
+ fr = &def;
+ if (!fr->frameCount)
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_LEFT];
+ break;
+ case kDirDownLeft:
+ if (frameType > 0)
+ fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_BACK];
+ else
+ fr = &def;
+ if (!fr->frameCount)
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_LEFT];
+ break;
+ case kDirRight:
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_RIGHT];
+ break;
+ case kDirLeft:
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_LEFT];
+ break;
+ case kDirUp:
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_BACK];
+ break;
+ case kDirDown:
+ fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_FORWARD];
+ break;
+ }
+ return fr;
+ }
+ else {
+ if (frameType >= actor->_framesCount) {
+ error("Actor::getActorFrameRange Wrong frameType 0x%X (%d) actorId 0x%X", frameType, actor->_framesCount, actorId);
+ }
+ fourDirection = actorDirectectionsLUT[actor->_facingDirection];
+ return &actor->_frames[frameType].directions[fourDirection];
+ }
+ }*/
+}
+
+void Actor::handleSpeech(int msec) {
+ int stringLength;
+ int sampleLength;
+ bool removeFirst;
+ int i;
+ ActorData *actor;
+ int width, height, height2;
+ Point posPoint;
+
+ if (_activeSpeech.playing) {
+ _activeSpeech.playingTime -= msec;
+ stringLength = strlen(_activeSpeech.strings[0]);
+
+ removeFirst = false;
+ if (_activeSpeech.playingTime <= 0) {
+ if (_activeSpeech.speechFlags & kSpeakSlow) {
+ _activeSpeech.slowModeCharIndex++;
+ if (_activeSpeech.slowModeCharIndex >= stringLength)
+ removeFirst = true;
+ } else {
+ removeFirst = true;
+ }
+ _activeSpeech.playing = false;
+ if (_activeSpeech.actorIds[0] != 0) {
+ actor = getActor(_activeSpeech.actorIds[0]);
+ if (!(_activeSpeech.speechFlags & kSpeakNoAnimate)) {
+ actor->_currentAction = kActionWait;
+ }
+ }
+ }
+
+ if (removeFirst) {
+ for (i = 1; i < _activeSpeech.stringsCount; i++) {
+ _activeSpeech.strings[i - 1] = _activeSpeech.strings[i];
+ }
+ _activeSpeech.stringsCount--;
+ }
+
+ if (_vm->_script->_skipSpeeches) {
+ _activeSpeech.stringsCount = 0;
+ _vm->_script->wakeUpThreads(kWaitTypeSpeech);
+ return;
+ }
+
+ if (_activeSpeech.stringsCount == 0) {
+ _vm->_script->wakeUpThreadsDelayed(kWaitTypeSpeech, ticksToMSec(kScriptTimeTicksPerSecond / 3));
+ }
+
+ return;
+ }
+
+ if (_vm->_script->_skipSpeeches) {
+ _activeSpeech.stringsCount = 0;
+ _vm->_script->wakeUpThreads(kWaitTypeSpeech);
+ }
+
+ if (_activeSpeech.stringsCount == 0) {
+ return;
+ }
+
+ stringLength = strlen(_activeSpeech.strings[0]);
+
+ if (_activeSpeech.speechFlags & kSpeakSlow) {
+ if (_activeSpeech.slowModeCharIndex >= stringLength)
+ error("Wrong string index");
+
+ warning("Slow string encountered!");
+ _activeSpeech.playingTime = stringLength * 1000 / 4;
+
+ } else {
+ sampleLength = _vm->_sndRes->getVoiceLength(_activeSpeech.sampleResourceId);
+
+ if (sampleLength < 0) {
+ _activeSpeech.playingTime = stringLength * 1000 / 22;
+ switch (_vm->_readingSpeed) {
+ case 1:
+ _activeSpeech.playingTime *= 2;
+ break;
+ case 2:
+ _activeSpeech.playingTime *= 4;
+ break;
+ case 3:
+ _activeSpeech.playingTime = 0x7fffff;
+ break;
+ }
+ } else {
+ _activeSpeech.playingTime = sampleLength;
+ }
+ }
+
+ if (_activeSpeech.sampleResourceId != -1) {
+ _vm->_sndRes->playVoice(_activeSpeech.sampleResourceId);
+ _activeSpeech.sampleResourceId++;
+ }
+
+ if (_activeSpeech.actorIds[0] != 0) {
+ actor = getActor(_activeSpeech.actorIds[0]);
+ if (!(_activeSpeech.speechFlags & kSpeakNoAnimate)) {
+ actor->_currentAction = kActionSpeak;
+ actor->_actionCycle = _vm->_rnd.getRandomNumber(63);
+ }
+ }
+
+ if (_activeSpeech.actorsCount == 1) {
+ if (_speechBoxScript.width() > 0) {
+ _activeSpeech.drawRect.left = _speechBoxScript.left;
+ _activeSpeech.drawRect.right = _speechBoxScript.right;
+ _activeSpeech.drawRect.top = _speechBoxScript.top;
+ _activeSpeech.drawRect.bottom = _speechBoxScript.bottom;
+ } else {
+ width = _activeSpeech.speechBox.width();
+ height = _vm->_font->getHeight(kKnownFontScript, _activeSpeech.strings[0], width - 2, _activeSpeech.getFontFlags(0)) + 1;
+
+ if (height > 40 && width < _vm->getDisplayWidth() - 100) {
+ width = _vm->getDisplayWidth() - 100;
+ height = _vm->_font->getHeight(kKnownFontScript, _activeSpeech.strings[0], width - 2, _activeSpeech.getFontFlags(0)) + 1;
+ }
+
+ _activeSpeech.speechBox.setWidth(width);
+
+ if (_activeSpeech.actorIds[0] != 0) {
+ actor = getActor(_activeSpeech.actorIds[0]);
+ _activeSpeech.speechBox.setHeight(height);
+
+ if (_activeSpeech.speechBox.right > _vm->getDisplayWidth() - 10) {
+ _activeSpeech.drawRect.left = _vm->getDisplayWidth() - 10 - width;
+ } else {
+ _activeSpeech.drawRect.left = _activeSpeech.speechBox.left;
+ }
+
+ height2 = actor->_screenPosition.y - 50;
+ _activeSpeech.speechBox.top = _activeSpeech.drawRect.top = MAX(10, (height2 - height) / 2);
+ } else {
+ _activeSpeech.drawRect.left = _activeSpeech.speechBox.left;
+ _activeSpeech.drawRect.top = _activeSpeech.speechBox.top + (_activeSpeech.speechBox.height() - height) / 2;
+ }
+ _activeSpeech.drawRect.setWidth(width);
+ _activeSpeech.drawRect.setHeight(height);
+ }
+ }
+
+ _activeSpeech.playing = true;
+}
+
+void Actor::handleActions(int msec, bool setup) {
+ int i;
+ ActorData *actor;
+ ActorFrameRange *frameRange;
+ int state;
+ int speed;
+ int32 framesLeft;
+ Location delta;
+ Location addDelta;
+ int hitZoneIndex;
+ const HitZone *hitZone;
+ Point hitPoint;
+ Location pickLocation;
+
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i];
+ if (!actor->_inScene)
+ continue;
+
+ if ((_vm->getGameType() == GType_ITE) && (i == ACTOR_DRAGON_INDEX)) {
+ moveDragon(actor);
+ continue;
+ }
+
+ switch (actor->_currentAction) {
+ case kActionWait:
+ if (!setup && (actor->_flags & kFollower)) {
+ followProtagonist(actor);
+ if (actor->_currentAction != kActionWait)
+ break;
+ }
+
+ if (actor->_targetObject != ID_NOTHING) {
+ actorFaceTowardsObject(actor->_id, actor->_targetObject);
+ }
+
+ if (actor->_flags & kCycle) {
+ frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameStand));
+ if (frameRange->frameCount > 0) {
+ actor->_actionCycle++;
+ actor->_actionCycle = (actor->_actionCycle) % frameRange->frameCount;
+ } else {
+ actor->_actionCycle = 0;
+ }
+ actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle;
+ break;
+ }
+
+ if ((actor->_actionCycle & 3) == 0) {
+ actor->cycleWrap(100);
+
+ frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameWait));
+ if ((frameRange->frameCount < 1 || actor->_actionCycle > 33))
+ frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameStand));
+
+ if (frameRange->frameCount) {
+ actor->_frameNumber = frameRange->frameIndex + (uint16)_vm->_rnd.getRandomNumber(frameRange->frameCount - 1);
+ } else {
+ actor->_frameNumber = frameRange->frameIndex;
+ }
+ }
+ actor->_actionCycle++;
+ break;
+
+ case kActionWalkToPoint:
+ case kActionWalkToLink:
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ actor->_partialTarget.delta(actor->_location, delta);
+
+ while ((delta.u() == 0) && (delta.v() == 0)) {
+
+ if ((actor == _protagonist) && (_vm->mouseButtonPressed())) {
+ _vm->_isoMap->screenPointToTileCoords(_vm->mousePos(), pickLocation);
+
+ if (!actorWalkTo(_protagonist->_id, pickLocation)) {
+ break;
+ }
+ } else if (!_vm->_isoMap->nextTileTarget(actor) && !actorEndWalk(actor->_id, true)) {
+ break;
+ }
+
+ actor->_partialTarget.delta(actor->_location, delta);
+ actor->_partialTarget.z = 0;
+ }
+
+ if (actor->_flags & kFastest) {
+ speed = 8;
+ } else if (actor->_flags & kFaster) {
+ speed = 6;
+ } else {
+ speed = 4;
+ }
+
+ if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) {
+ speed = 2;
+ }
+
+ if ((actor->_actionDirection == 2) || (actor->_actionDirection == 6)) {
+ speed = speed / 2;
+ }
+
+ if (ABS(delta.v()) > ABS(delta.u())) {
+ addDelta.v() = clamp(-speed, delta.v(), speed);
+ if (addDelta.v() == delta.v()) {
+ addDelta.u() = delta.u();
+ } else {
+ addDelta.u() = delta.u() * addDelta.v();
+ addDelta.u() += (addDelta.u() > 0) ? (delta.v() / 2) : (-delta.v() / 2);
+ addDelta.u() /= delta.v();
+ }
+ } else {
+ addDelta.u() = clamp(-speed, delta.u(), speed);
+ if (addDelta.u() == delta.u()) {
+ addDelta.v() = delta.v();
+ } else {
+ addDelta.v() = delta.v() * addDelta.u();
+ addDelta.v() += (addDelta.v() > 0) ? (delta.u() / 2) : (-delta.u() / 2);
+ addDelta.v() /= delta.u();
+ }
+ }
+
+ actor->_location.add(addDelta);
+ } else {
+ actor->_partialTarget.delta(actor->_location, delta);
+
+ while ((delta.x == 0) && (delta.y == 0)) {
+
+ if (actor->_walkStepIndex >= actor->_walkStepsCount) {
+ actorEndWalk(actor->_id, true);
+ break;
+ }
+
+ actor->_partialTarget.fromScreenPoint(actor->_walkStepsPoints[actor->_walkStepIndex++]);
+ if (_vm->getGameType() == GType_ITE) {
+ if (actor->_partialTarget.x > 224 * 2 * ACTOR_LMULT) {
+ actor->_partialTarget.x -= 256 * 2 * ACTOR_LMULT;
+ }
+ } else {
+ if (actor->_partialTarget.x > 224 * 4 * ACTOR_LMULT) {
+ actor->_partialTarget.x -= 256 * 4 * ACTOR_LMULT;
+ }
+ }
+
+ actor->_partialTarget.delta(actor->_location, delta);
+
+ if (ABS(delta.y) > ABS(delta.x)) {
+ actor->_actionDirection = delta.y > 0 ? kDirDown : kDirUp;
+ } else {
+ actor->_actionDirection = delta.x > 0 ? kDirRight : kDirLeft;
+ }
+ }
+
+ speed = (ACTOR_LMULT * 2 * actor->_screenScale + 63) / 256;
+ if (speed < 1) {
+ speed = 1;
+ }
+
+ if ((actor->_actionDirection == kDirUp) || (actor->_actionDirection == kDirDown)) {
+ addDelta.y = clamp(-speed, delta.y, speed);
+ if (addDelta.y == delta.y) {
+ addDelta.x = delta.x;
+ } else {
+ addDelta.x = delta.x * addDelta.y;
+ addDelta.x += (addDelta.x > 0) ? (delta.y / 2) : (-delta.y / 2);
+ addDelta.x /= delta.y;
+ actor->_facingDirection = actor->_actionDirection;
+ }
+ } else {
+ addDelta.x = clamp(-2 * speed, delta.x, 2 * speed);
+ if (addDelta.x == delta.x) {
+ addDelta.y = delta.y;
+ } else {
+ addDelta.y = delta.y * addDelta.x;
+ addDelta.y += (addDelta.y > 0) ? (delta.x / 2) : (-delta.x / 2);
+ addDelta.y /= delta.x;
+ actor->_facingDirection = actor->_actionDirection;
+ }
+ }
+
+ actor->_location.add(addDelta);
+ }
+
+ if (actor->_actorFlags & kActorBackwards) {
+ actor->_facingDirection = (actor->_actionDirection + 4) & 7;
+ actor->_actionCycle--;
+ } else {
+ actor->_actionCycle++;
+ }
+
+ frameRange = getActorFrameRange(actor->_id, actor->_walkFrameSequence);
+
+ if (actor->_actionCycle < 0) {
+ actor->_actionCycle = frameRange->frameCount - 1;
+ } else if (actor->_actionCycle >= frameRange->frameCount) {
+ actor->_actionCycle = 0;
+ }
+
+ actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle;
+ break;
+
+ case kActionWalkDir:
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ actor->_location.u() += tileDirectionLUT[actor->_actionDirection][0];
+ actor->_location.v() += tileDirectionLUT[actor->_actionDirection][1];
+
+ frameRange = getActorFrameRange(actor->_id, actor->_walkFrameSequence);
+
+ actor->_actionCycle++;
+ actor->cycleWrap(frameRange->frameCount);
+ actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle;
+ } else {
+ actor->_location.x += directionLUT[actor->_actionDirection][0] * 2;
+ actor->_location.y += directionLUT[actor->_actionDirection][1] * 2;
+
+ frameRange = getActorFrameRange(actor->_id, actor->_walkFrameSequence);
+ actor->_actionCycle++;
+ actor->cycleWrap(frameRange->frameCount);
+ actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle;
+ }
+ break;
+
+ case kActionSpeak:
+ actor->_actionCycle++;
+ actor->cycleWrap(64);
+
+ frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameGesture));
+ if (actor->_actionCycle >= frameRange->frameCount) {
+ if (actor->_actionCycle & 1)
+ break;
+ frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameSpeak));
+
+ state = (uint16)_vm->_rnd.getRandomNumber(frameRange->frameCount);
+
+ if (state == 0) {
+ frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameStand));
+ } else {
+ state--;
+ }
+ } else {
+ state = actor->_actionCycle;
+ }
+
+ actor->_frameNumber = frameRange->frameIndex + state;
+ break;
+
+ case kActionAccept:
+ case kActionStoop:
+ break;
+
+ case kActionCycleFrames:
+ case kActionPongFrames:
+ if (actor->_cycleTimeCount > 0) {
+ actor->_cycleTimeCount--;
+ break;
+ }
+
+ actor->_cycleTimeCount = actor->_cycleDelay;
+ actor->_actionCycle++;
+
+ frameRange = getActorFrameRange(actor->_id, actor->_cycleFrameSequence);
+
+ if (actor->_currentAction == kActionPongFrames) {
+ if (actor->_actionCycle >= frameRange->frameCount * 2 - 2) {
+ if (actor->_actorFlags & kActorContinuous) {
+ actor->_actionCycle = 0;
+ } else {
+ actor->_currentAction = kActionFreeze;
+ break;
+ }
+ }
+
+ state = actor->_actionCycle;
+ if (state >= frameRange->frameCount) {
+ state = frameRange->frameCount * 2 - 2 - state;
+ }
+ } else {
+ if (actor->_actionCycle >= frameRange->frameCount) {
+ if (actor->_actorFlags & kActorContinuous) {
+ actor->_actionCycle = 0;
+ } else {
+ actor->_currentAction = kActionFreeze;
+ break;
+ }
+ }
+ state = actor->_actionCycle;
+ }
+
+ if (frameRange->frameCount && (actor->_actorFlags & kActorRandom)) {
+ state = _vm->_rnd.getRandomNumber(frameRange->frameCount - 1);
+ }
+
+ if (actor->_actorFlags & kActorBackwards) {
+ actor->_frameNumber = frameRange->frameIndex + frameRange->frameCount - 1 - state;
+ } else {
+ actor->_frameNumber = frameRange->frameIndex + state;
+ }
+ break;
+
+ case kActionFall:
+ if (actor->_actionCycle > 0) {
+ framesLeft = actor->_actionCycle--;
+ actor->_finalTarget.delta(actor->_location, delta);
+ delta.x /= framesLeft;
+ delta.y /= framesLeft;
+ actor->_location.addXY(delta);
+ actor->_fallVelocity += actor->_fallAcceleration;
+ actor->_fallPosition += actor->_fallVelocity;
+ actor->_location.z = actor->_fallPosition >> 4;
+ } else {
+ actor->_location = actor->_finalTarget;
+ actor->_currentAction = kActionFreeze;
+ _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor);
+ }
+ break;
+
+ case kActionClimb:
+ actor->_cycleDelay++;
+ if (actor->_cycleDelay & 3) {
+ break;
+ }
+
+ if (actor->_location.z >= actor->_finalTarget.z + ACTOR_CLIMB_SPEED) {
+ actor->_location.z -= ACTOR_CLIMB_SPEED;
+ actor->_actionCycle--;
+ } else if (actor->_location.z <= actor->_finalTarget.z - ACTOR_CLIMB_SPEED) {
+ actor->_location.z += ACTOR_CLIMB_SPEED;
+ actor->_actionCycle++;
+ } else {
+ actor->_location.z = actor->_finalTarget.z;
+ actor->_currentAction = kActionFreeze;
+ _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor);
+ }
+
+ frameRange = getActorFrameRange(actor->_id, actor->_cycleFrameSequence);
+
+ if (actor->_actionCycle < 0) {
+ actor->_actionCycle = frameRange->frameCount - 1;
+ }
+ actor->cycleWrap(frameRange->frameCount);
+ actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle;
+ break;
+ }
+
+ if ((actor->_currentAction >= kActionWalkToPoint) && (actor->_currentAction <= kActionWalkDir)) {
+ hitZone = NULL;
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ actor->_location.toScreenPointUV(hitPoint);
+ } else {
+ actor->_location.toScreenPointXY(hitPoint);
+ }
+ hitZoneIndex = _vm->_scene->_actionMap->hitTest(hitPoint);
+ if (hitZoneIndex != -1) {
+ hitZone = _vm->_scene->_actionMap->getHitZone(hitZoneIndex);
+ }
+
+ if (hitZone != actor->_lastZone) {
+ if (actor->_lastZone)
+ stepZoneAction(actor, actor->_lastZone, true, false);
+ actor->_lastZone = hitZone;
+ if (hitZone)
+ stepZoneAction(actor, hitZone, false, false);
+ }
+ }
+ }
+}
+
+void Actor::direct(int msec) {
+
+ if (_vm->_scene->_entryList.entryListCount == 0) {
+ return;
+ }
+
+ if (_vm->_interface->_statusTextInput) {
+ return;
+ }
+
+ // FIXME: HACK. This should be turned into cycle event.
+ _lastTickMsec += msec;
+
+ if (_lastTickMsec > 1000 / _handleActionDiv) {
+ _lastTickMsec = 0;
+ //process actions
+ handleActions(msec, false);
+ }
+
+//process speech
+ handleSpeech(msec);
+}
+
+
+bool Actor::calcScreenPosition(CommonObjectData *commonObjectData) {
+ int beginSlope, endSlope, middle;
+ bool result;
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->tileCoordsToScreenPoint(commonObjectData->_location, commonObjectData->_screenPosition);
+ commonObjectData->_screenScale = 256;
+ } else {
+ middle = _vm->_scene->getHeight() - commonObjectData->_location.y / ACTOR_LMULT;
+
+ _vm->_scene->getSlopes(beginSlope, endSlope);
+
+ commonObjectData->_screenDepth = (14 * middle) / endSlope + 1;
+
+ if (middle <= beginSlope) {
+ commonObjectData->_screenScale = 256;
+ } else if (middle >= endSlope) {
+ commonObjectData->_screenScale = 1;
+ } else {
+ middle -= beginSlope;
+ endSlope -= beginSlope;
+ commonObjectData->_screenScale = 256 - (middle * 256) / endSlope;
+ }
+
+ commonObjectData->_location.toScreenPointXYZ(commonObjectData->_screenPosition);
+ }
+
+ result = commonObjectData->_screenPosition.x > -64 &&
+ commonObjectData->_screenPosition.x < _vm->getDisplayWidth() + 64 &&
+ commonObjectData->_screenPosition.y > -64 &&
+ commonObjectData->_screenPosition.y < _vm->_scene->getHeight() + 64;
+
+ return result;
+}
+
+uint16 Actor::hitTest(const Point &testPoint, bool skipProtagonist) {
+ // We can only interact with objects or actors that are inside the
+ // scene area. While this is usually the entire upper part of the
+ // screen, it could also be an inset. Note that other kinds of hit
+ // areas may be outside the inset, and that those are still perfectly
+ // fine to interact with. For example, the door entrance at the glass
+ // makers's house in ITE's ferret village.
+
+ if (!_vm->_scene->getSceneClip().contains(testPoint))
+ return ID_NOTHING;
+
+ CommonObjectOrderList::iterator drawOrderIterator;
+ CommonObjectDataPointer drawObject;
+ int frameNumber;
+ SpriteList *spriteList;
+
+ createDrawOrderList();
+
+ for (drawOrderIterator = _drawOrderList.begin(); drawOrderIterator != _drawOrderList.end(); ++drawOrderIterator) {
+ drawObject = drawOrderIterator.operator*();
+ if (skipProtagonist && (drawObject == _protagonist)) {
+ continue;
+ }
+ if (!getSpriteParams(drawObject, frameNumber, spriteList)) {
+ continue;
+ }
+ if (_vm->_sprite->hitTest(*spriteList, frameNumber, drawObject->_screenPosition, drawObject->_screenScale, testPoint)) {
+ return drawObject->_id;
+ }
+ }
+ return ID_NOTHING;
+}
+
+void Actor::createDrawOrderList() {
+ int i;
+ ActorData *actor;
+ ObjectData *obj;
+ CommonObjectOrderList::CompareFunction compareFunction;
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ compareFunction = &tileCommonObjectCompare;
+ } else {
+ compareFunction = &commonObjectCompare;
+ }
+
+ _drawOrderList.clear();
+ for (i = 0; i < _actorsCount; i++) {
+ actor = _actors[i];
+
+ if (!actor->_inScene)
+ continue;
+
+ if (calcScreenPosition(actor)) {
+ _drawOrderList.pushBack(actor, compareFunction);
+ }
+ }
+
+ for (i = 0; i < _objsCount; i++) {
+ obj = _objs[i];
+ if (obj->_disabled)
+ continue;
+
+ if (obj->_sceneNumber != _vm->_scene->currentSceneNumber())
+ continue;
+
+ if (calcScreenPosition(obj)) {
+ _drawOrderList.pushBack(obj, compareFunction);
+ }
+ }
+}
+
+bool Actor::getSpriteParams(CommonObjectData *commonObjectData, int &frameNumber, SpriteList *&spriteList) {
+ if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) {
+ if (!(commonObjectData->_flags & kProtagonist)){
+// warning("not protagonist");
+ return false;
+ }
+ frameNumber = 8;
+ spriteList = &_vm->_sprite->_mainSprites;
+ } else if (validActorId(commonObjectData->_id)) {
+ ActorData *actor = (ActorData *)commonObjectData;
+ spriteList = &(actor->_spriteList);
+ frameNumber = actor->_frameNumber;
+ if (spriteList->infoList == NULL)
+ loadActorSpriteList(actor);
+
+ } else if (validObjId(commonObjectData->_id)) {
+ spriteList = &_vm->_sprite->_mainSprites;
+ frameNumber = commonObjectData->_spriteListResourceId;
+ }
+
+ if ((frameNumber < 0) || (spriteList->spriteCount <= frameNumber)) {
+ debug(1, "Actor::getSpriteParams frameNumber invalid for %s id 0x%X (%d)",
+ validObjId(commonObjectData->_id) ? "object" : "actor",
+ commonObjectData->_id, frameNumber);
+ return false;
+ }
+ return true;
+}
+
+void Actor::drawActors() {
+ if (_vm->_anim->hasCutaway()) {
+ drawSpeech();
+ return;
+ }
+
+ if (_vm->_scene->currentSceneNumber() <= 0) {
+ return;
+ }
+
+ if (_vm->_scene->_entryList.entryListCount == 0) {
+ return;
+ }
+
+ CommonObjectOrderList::iterator drawOrderIterator;
+ CommonObjectDataPointer drawObject;
+ int frameNumber;
+ SpriteList *spriteList;
+
+ Surface *backBuffer;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ createDrawOrderList();
+
+ for (drawOrderIterator = _drawOrderList.begin(); drawOrderIterator != _drawOrderList.end(); ++drawOrderIterator) {
+ drawObject = drawOrderIterator.operator*();
+
+ if (!getSpriteParams(drawObject, frameNumber, spriteList)) {
+ continue;
+ }
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->drawSprite(backBuffer, *spriteList, frameNumber, drawObject->_location, drawObject->_screenPosition, drawObject->_screenScale);
+ } else {
+ _vm->_sprite->drawOccluded(backBuffer, _vm->_scene->getSceneClip(),*spriteList, frameNumber, drawObject->_screenPosition, drawObject->_screenScale, drawObject->_screenDepth);
+ }
+ }
+
+ drawSpeech();
+}
+
+void Actor::drawSpeech(void) {
+ if (!isSpeaking() || !_activeSpeech.playing || _vm->_script->_skipSpeeches
+ || (!_vm->_subtitlesEnabled && (_vm->getFeatures() & GF_CD_FX)))
+ return;
+
+ int i;
+ Point textPoint;
+ ActorData *actor;
+ int width, height;
+ char oneChar[2];
+ oneChar[1] = 0;
+ const char *outputString;
+ Surface *backBuffer;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ if (_activeSpeech.speechFlags & kSpeakSlow) {
+ outputString = oneChar;
+ oneChar[0] = _activeSpeech.strings[0][_activeSpeech.slowModeCharIndex];
+ } else {
+ outputString = _activeSpeech.strings[0];
+ }
+
+ if (_activeSpeech.actorsCount > 1) {
+ height = _vm->_font->getHeight(kKnownFontScript);
+ width = _vm->_font->getStringWidth(kKnownFontScript, _activeSpeech.strings[0], 0, kFontNormal);
+
+ for (i = 0; i < _activeSpeech.actorsCount; i++) {
+ actor = getActor(_activeSpeech.actorIds[i]);
+ calcScreenPosition(actor);
+
+ textPoint.x = clamp(10, actor->_screenPosition.x - width / 2, _vm->getDisplayWidth() - 10 - width);
+ textPoint.y = clamp(10, actor->_screenPosition.y - 58, _vm->_scene->getHeight() - 10 - height);
+
+ _vm->_font->textDraw(kKnownFontScript, backBuffer, _activeSpeech.strings[0], textPoint,
+ _activeSpeech.speechColor[i], _activeSpeech.outlineColor[i], _activeSpeech.getFontFlags(i));
+ }
+ } else {
+ _vm->_font->textDrawRect(kKnownFontScript, backBuffer, _activeSpeech.strings[0], _activeSpeech.drawRect, _activeSpeech.speechColor[0],
+ _activeSpeech.outlineColor[0], _activeSpeech.getFontFlags(0));
+ }
+}
+
+bool Actor::followProtagonist(ActorData *actor) {
+ Location protagonistLocation;
+ Location newLocation;
+ Location delta;
+ int protagonistBGMaskType;
+ Point prefer1;
+ Point prefer2;
+ Point prefer3;
+ int16 prefU;
+ int16 prefV;
+ int16 newU;
+ int16 newV;
+
+ assert(_protagonist);
+
+ actor->_flags &= ~(kFaster | kFastest);
+ protagonistLocation = _protagonist->_location;
+ calcScreenPosition(_protagonist);
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ prefU = 60;
+ prefV = 60;
+
+
+ actor->_location.delta(protagonistLocation, delta);
+
+ if (actor->_id == actorIndexToId(2)) {
+ prefU = prefV = 48;
+ }
+
+ if ((delta.u() > prefU) || (delta.u() < -prefU) || (delta.v() > prefV) || (delta.v() < -prefV)) {
+
+ if ((delta.u() > prefU * 2) || (delta.u() < -prefU * 2) || (delta.v() > prefV * 2) || (delta.v() < -prefV * 2)) {
+ actor->_flags |= kFaster;
+
+ if ((delta.u() > prefU * 3) || (delta.u() < -prefU*3) || (delta.v() > prefV * 3) || (delta.v() < -prefV * 3)) {
+ actor->_flags |= kFastest;
+ }
+ }
+
+ prefU /= 2;
+ prefV /= 2;
+
+ newU = clamp(-prefU, delta.u(), prefU) + protagonistLocation.u();
+ newV = clamp(-prefV, delta.v(), prefV) + protagonistLocation.v();
+
+ newLocation.u() = newU + _vm->_rnd.getRandomNumber(prefU - 1) - prefU / 2;
+ newLocation.v() = newV + _vm->_rnd.getRandomNumber(prefV - 1) - prefV / 2;
+ newLocation.z = 0;
+
+ return actorWalkTo(actor->_id, newLocation);
+ }
+
+ } else {
+ prefer1.x = (100 * _protagonist->_screenScale) >> 8;
+ prefer1.y = (50 * _protagonist->_screenScale) >> 8;
+
+ if (_protagonist->_currentAction == kActionWalkDir) {
+ prefer1.x /= 2;
+ }
+
+ if (prefer1.x < 8) {
+ prefer1.x = 8;
+ }
+
+ if (prefer1.y < 8) {
+ prefer1.y = 8;
+ }
+
+ prefer2.x = prefer1.x * 2;
+ prefer2.y = prefer1.y * 2;
+ prefer3.x = prefer1.x + prefer1.x / 2;
+ prefer3.y = prefer1.y + prefer1.y / 2;
+
+ actor->_location.delta(protagonistLocation, delta);
+
+ protagonistBGMaskType = 0;
+ if (_vm->_scene->isBGMaskPresent() && _vm->_scene->validBGMaskPoint(_protagonist->_screenPosition)) {
+ protagonistBGMaskType = _vm->_scene->getBGMaskType(_protagonist->_screenPosition);
+ }
+
+ if ((_vm->_rnd.getRandomNumber(7) & 0x7) == 0) // 1/8th chance
+ actor->_actorFlags &= ~kActorNoFollow;
+
+ if (actor->_actorFlags & kActorNoFollow) {
+ return false;
+ }
+
+ if ((delta.x > prefer2.x) || (delta.x < -prefer2.x) ||
+ (delta.y > prefer2.y) || (delta.y < -prefer2.y) ||
+ ((_protagonist->_currentAction == kActionWait) &&
+ (delta.x * 2 < prefer1.x) && (delta.x * 2 > -prefer1.x) &&
+ (delta.y < prefer1.y) && (delta.y > -prefer1.y))) {
+
+ if (ABS(delta.x) > ABS(delta.y)) {
+
+ delta.x = (delta.x > 0) ? prefer3.x : -prefer3.x;
+
+ newLocation.x = delta.x + protagonistLocation.x;
+ newLocation.y = clamp(-prefer2.y, delta.y, prefer2.y) + protagonistLocation.y;
+ } else {
+ delta.y = (delta.y > 0) ? prefer3.y : -prefer3.y;
+
+ newLocation.x = clamp(-prefer2.x, delta.x, prefer2.x) + protagonistLocation.x;
+ newLocation.y = delta.y + protagonistLocation.y;
+ }
+ newLocation.z = 0;
+
+ if (protagonistBGMaskType != 3) {
+ newLocation.x += _vm->_rnd.getRandomNumber(prefer1.x - 1) - prefer1.x / 2;
+ newLocation.y += _vm->_rnd.getRandomNumber(prefer1.y - 1) - prefer1.y / 2;
+ }
+
+ newLocation.x = clamp(-31*4, newLocation.x, (_vm->getDisplayWidth() + 31) * 4); //fixme
+
+ return actorWalkTo(actor->_id, newLocation);
+ }
+ }
+ return false;
+}
+
+bool Actor::actorEndWalk(uint16 actorId, bool recurse) {
+ bool walkMore = false;
+ ActorData *actor;
+ const HitZone *hitZone;
+ int hitZoneIndex;
+ Point testPoint;
+
+ actor = getActor(actorId);
+ actor->_actorFlags &= ~kActorBackwards;
+
+ if (actor->_location.distance(actor->_finalTarget) > 8 && (actor->_flags & kProtagonist) && recurse && !(actor->_actorFlags & kActorNoCollide)) {
+ actor->_actorFlags |= kActorNoCollide;
+ return actorWalkTo(actorId, actor->_finalTarget);
+ }
+
+ actor->_currentAction = kActionWait;
+ if (actor->_actorFlags & kActorFinalFace) {
+ actor->_facingDirection = actor->_actionDirection = (actor->_actorFlags >> 6) & 0x07; //?
+ }
+
+ actor->_actorFlags &= ~(kActorNoCollide | kActorCollided | kActorFinalFace | kActorFacingMask);
+ actor->_flags &= ~(kFaster | kFastest);
+
+ if (actor == _protagonist) {
+ _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor);
+ if (_vm->_script->_pendingVerb == _vm->_script->getVerbType(kVerbWalkTo)) {
+ if (_vm->getGameType() == GType_ITE)
+ actor->_location.toScreenPointUV(testPoint); // it's wrong calculation, but it is used in ITE
+ else
+ actor->_location.toScreenPointXY(testPoint);
+
+ hitZoneIndex = _vm->_scene->_actionMap->hitTest(testPoint);
+ if (hitZoneIndex != -1) {
+ hitZone = _vm->_scene->_actionMap->getHitZone(hitZoneIndex);
+ stepZoneAction(actor, hitZone, false, true);
+ } else {
+ _vm->_script->setNoPendingVerb();
+ }
+ } else if (_vm->_script->_pendingVerb != _vm->_script->getVerbType(kVerbNone)) {
+ _vm->_script->doVerb();
+ }
+ } else {
+ if (recurse && (actor->_flags & kFollower))
+ walkMore = followProtagonist(actor);
+
+ _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor);
+ }
+ return walkMore;
+}
+
+bool Actor::actorWalkTo(uint16 actorId, const Location &toLocation) {
+ ActorData *actor;
+ ActorData *anotherActor;
+ int i;
+
+ Rect testBox;
+ Rect testBox2;
+ Point anotherActorScreenPosition;
+ Point collision;
+ Point pointFrom, pointTo, pointBest, pointAdd;
+ Point delta, bestDelta;
+ Point tempPoint;
+ bool extraStartNode;
+ bool extraEndNode;
+
+ actor = getActor(actorId);
+
+ if (actor == _protagonist) {
+ _vm->_scene->setDoorState(2, 0xff);
+ _vm->_scene->setDoorState(3, 0);
+ } else {
+ _vm->_scene->setDoorState(2, 0);
+ _vm->_scene->setDoorState(3, 0xff);
+ }
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+
+ if ((_vm->getGameType() == GType_ITE) && (actor->_index == ACTOR_DRAGON_INDEX)) {
+ return false;
+ }
+
+ actor->_finalTarget = toLocation;
+ actor->_walkStepsCount = 0;
+ _vm->_isoMap->findTilePath(actor, actor->_location, toLocation);
+
+
+ if ((actor->_walkStepsCount == 0) && (actor->_flags & kProtagonist)) {
+ actor->_actorFlags |= kActorNoCollide;
+ _vm->_isoMap->findTilePath(actor, actor->_location, toLocation);
+ }
+
+ actor->_walkStepIndex = 0;
+ if (_vm->_isoMap->nextTileTarget(actor)) {
+ actor->_currentAction = kActionWalkToPoint;
+ actor->_walkFrameSequence = getFrameType(kFrameWalk);
+ } else {
+ actorEndWalk(actorId, false);
+ return false;
+ }
+ } else {
+
+ actor->_location.toScreenPointXY(pointFrom);
+ pointFrom.x &= ~1;
+
+ extraStartNode = _vm->_scene->offscreenPath(pointFrom);
+
+ toLocation.toScreenPointXY(pointTo);
+ pointTo.x &= ~1;
+
+ extraEndNode = _vm->_scene->offscreenPath(pointTo);
+
+ if (_vm->_scene->isBGMaskPresent()) {
+
+ if ((((actor->_currentAction >= kActionWalkToPoint) &&
+ (actor->_currentAction <= kActionWalkDir)) || (actor == _protagonist)) &&
+ !_vm->_scene->canWalk(pointFrom)) {
+ for (i = 1; i < 8; i++) {
+ pointAdd = pointFrom;
+ pointAdd.y += i;
+ if (_vm->_scene->canWalk(pointAdd)) {
+ pointFrom = pointAdd;
+ break;
+ }
+ pointAdd = pointFrom;
+ pointAdd.y -= i;
+ if (_vm->_scene->canWalk(pointAdd)) {
+ pointFrom = pointAdd;
+ break;
+ }
+ pointAdd = pointFrom;
+ pointAdd.x += i;
+ if (_vm->_scene->canWalk(pointAdd)) {
+ pointFrom = pointAdd;
+ break;
+ }
+ pointAdd = pointFrom;
+ pointAdd.x -= i;
+ if (_vm->_scene->canWalk(pointAdd)) {
+ pointFrom = pointAdd;
+ break;
+ }
+ }
+ }
+
+ _barrierCount = 0;
+ if (!(actor->_actorFlags & kActorNoCollide)) {
+ collision.x = ACTOR_COLLISION_WIDTH * actor->_screenScale / (256 * 2);
+ collision.y = ACTOR_COLLISION_HEIGHT * actor->_screenScale / (256 * 2);
+
+
+ for (i = 0; (i < _actorsCount) && (_barrierCount < ACTOR_BARRIERS_MAX); i++) {
+ anotherActor = _actors[i];
+ if (!anotherActor->_inScene)
+ continue;
+ if (anotherActor == actor)
+ continue;
+
+ anotherActorScreenPosition = anotherActor->_screenPosition;
+ testBox.left = (anotherActorScreenPosition.x - collision.x) & ~1;
+ testBox.right = (anotherActorScreenPosition.x + collision.x) & ~1 + 1;
+ testBox.top = anotherActorScreenPosition.y - collision.y;
+ testBox.bottom = anotherActorScreenPosition.y + collision.y + 1;
+ testBox2 = testBox;
+ testBox2.right += 2;
+ testBox2.left -= 2;
+ testBox2.top -= 1;
+ testBox2.bottom += 1;
+
+ if (testBox2.contains(pointFrom)) {
+ if (pointFrom.x > anotherActorScreenPosition.x + 4) {
+ testBox.right = pointFrom.x - 1;
+ } else if (pointFrom.x < anotherActorScreenPosition.x - 4) {
+ testBox.left = pointFrom.x + 2;
+ } else if (pointFrom.y > anotherActorScreenPosition.y) {
+ testBox.bottom = pointFrom.y;
+ } else {
+ testBox.top = pointFrom.y + 1 ;
+ }
+ }
+
+ if ((testBox.width() > 0) && (testBox.height() > 0)) {
+ _barrierList[_barrierCount++] = testBox;
+ }
+ }
+ }
+
+
+ pointBest = pointTo;
+ actor->_walkStepsCount = 0;
+ findActorPath(actor, pointFrom, pointTo);
+
+ if (actor->_walkStepsCount == 0) {
+ error("actor->_walkStepsCount == 0");
+ }
+
+ if (extraStartNode) {
+ actor->_walkStepIndex = 0;
+ } else {
+ actor->_walkStepIndex = 1;
+ }
+
+ if (extraEndNode) {
+ toLocation.toScreenPointXY(tempPoint);
+ actor->_walkStepsCount--;
+ actor->addWalkStepPoint(tempPoint);
+ }
+
+
+ pointBest = actor->_walkStepsPoints[actor->_walkStepsCount - 1];
+
+ pointBest.x &= ~1;
+ delta.x = ABS(pointFrom.x - pointTo.x);
+ delta.y = ABS(pointFrom.y - pointTo.y);
+
+ bestDelta.x = ABS(pointBest.x - pointTo.x);
+ bestDelta.y = ABS(pointBest.y - pointTo.y);
+
+ if ((delta.x + delta.y <= bestDelta.x + bestDelta.y) && (actor->_flags & kFollower)) {
+ actor->_actorFlags |= kActorNoFollow;
+ }
+
+ if (pointBest == pointFrom) {
+ actor->_walkStepsCount = 0;
+ }
+ } else {
+ actor->_walkStepsCount = 0;
+ actor->addWalkStepPoint(pointTo);
+ actor->_walkStepIndex = 0;
+ }
+
+ actor->_partialTarget = actor->_location;
+ actor->_finalTarget = toLocation;
+ if (actor->_walkStepsCount == 0) {
+ actorEndWalk(actorId, false);
+ return false;
+ } else {
+ if (actor->_flags & kProtagonist) {
+ _actors[1]->_actorFlags &= ~kActorNoFollow; // TODO: mark all actors with kFollower flag, not only 1 and 2
+ _actors[2]->_actorFlags &= ~kActorNoFollow;
+ }
+ actor->_currentAction = (actor->_walkStepsCount >= ACTOR_MAX_STEPS_COUNT) ? kActionWalkToLink : kActionWalkToPoint;
+ actor->_walkFrameSequence = getFrameType(kFrameWalk);
+ }
+ }
+ return true;
+}
+
+void Actor::actorSpeech(uint16 actorId, const char **strings, int stringsCount, int sampleResourceId, int speechFlags) {
+ ActorData *actor;
+ int i;
+ int16 dist;
+
+ actor = getActor(actorId);
+ calcScreenPosition(actor);
+ for (i = 0; i < stringsCount; i++) {
+ _activeSpeech.strings[i] = strings[i];
+ }
+
+ _activeSpeech.stringsCount = stringsCount;
+ _activeSpeech.speechFlags = speechFlags;
+ _activeSpeech.actorsCount = 1;
+ _activeSpeech.actorIds[0] = actorId;
+ _activeSpeech.speechColor[0] = actor->_speechColor;
+ _activeSpeech.outlineColor[0] = (_vm->getGameType() == GType_ITE ? kITEColorBlack : kIHNMColorBlack);
+ _activeSpeech.sampleResourceId = sampleResourceId;
+ _activeSpeech.playing = false;
+ _activeSpeech.slowModeCharIndex = 0;
+
+ dist = MIN(actor->_screenPosition.x - 10, _vm->getDisplayWidth() - 10 - actor->_screenPosition.x);
+ dist = clamp(60, dist, 150);
+
+ _activeSpeech.speechBox.left = actor->_screenPosition.x - dist;
+ _activeSpeech.speechBox.right = actor->_screenPosition.x + dist;
+
+ if (_activeSpeech.speechBox.left < 10) {
+ _activeSpeech.speechBox.right += 10 - _activeSpeech.speechBox.left;
+ _activeSpeech.speechBox.left = 10;
+ }
+ if (_activeSpeech.speechBox.right > _vm->getDisplayWidth() - 10) {
+ _activeSpeech.speechBox.left -= _activeSpeech.speechBox.right - _vm->getDisplayWidth() - 10;
+ _activeSpeech.speechBox.right = _vm->getDisplayWidth() - 10;
+ }
+}
+
+void Actor::nonActorSpeech(const Common::Rect &box, const char **strings, int stringsCount, int sampleResourceId, int speechFlags) {
+ int i;
+
+ _vm->_script->wakeUpThreads(kWaitTypeSpeech);
+
+ for (i = 0; i < stringsCount; i++) {
+ _activeSpeech.strings[i] = strings[i];
+ }
+ _activeSpeech.stringsCount = stringsCount;
+ _activeSpeech.speechFlags = speechFlags;
+ _activeSpeech.actorsCount = 1;
+ _activeSpeech.actorIds[0] = 0;
+ if (!(_vm->getFeatures() & GF_CD_FX))
+ _activeSpeech.sampleResourceId = -1;
+ else
+ _activeSpeech.sampleResourceId = sampleResourceId;
+ _activeSpeech.playing = false;
+ _activeSpeech.slowModeCharIndex = 0;
+ _activeSpeech.speechBox = box;
+}
+
+void Actor::simulSpeech(const char *string, uint16 *actorIds, int actorIdsCount, int speechFlags, int sampleResourceId) {
+ int i;
+
+ for (i = 0; i < actorIdsCount; i++) {
+ ActorData *actor;
+
+ actor = getActor(actorIds[i]);
+ _activeSpeech.actorIds[i] = actorIds[i];
+ _activeSpeech.speechColor[i] = actor->_speechColor;
+ _activeSpeech.outlineColor[i] = 0; // disable outline
+ }
+ _activeSpeech.actorsCount = actorIdsCount;
+ _activeSpeech.strings[0] = string;
+ _activeSpeech.stringsCount = 1;
+ _activeSpeech.speechFlags = speechFlags;
+ _activeSpeech.sampleResourceId = sampleResourceId;
+ _activeSpeech.playing = false;
+ _activeSpeech.slowModeCharIndex = 0;
+
+ // caller should call thread->wait(kWaitTypeSpeech) by itself
+}
+
+void Actor::abortAllSpeeches() {
+ abortSpeech();
+
+ if (_vm->_script->_abortEnabled)
+ _vm->_script->_skipSpeeches = true;
+
+ for (int i = 0; i < 10; i++)
+ _vm->_script->executeThreads(0);
+}
+
+void Actor::abortSpeech() {
+ _vm->_sound->stopVoice();
+ _activeSpeech.playingTime = 0;
+}
+
+void Actor::moveDragon(ActorData *actor) {
+ int16 dir0, dir1, dir2, dir3;
+ int16 moveType;
+ Event event;
+ const DragonMove *dragonMove;
+
+ if ((actor->_actionCycle < 0) ||
+ ((actor->_actionCycle == 0) && (actor->_dragonMoveType >= ACTOR_DRAGON_TURN_MOVES))) {
+
+ moveType = kDragonMoveInvalid;
+ if (actor->_location.distance(_protagonist->_location) < 24) {
+ if (_dragonHunt && (_protagonist->_currentAction != kActionFall)) {
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventExecNonBlocking;
+ event.time = 0;
+ event.param = _vm->_scene->getScriptModuleNumber(); // module number
+ event.param2 = ACTOR_EXP_KNOCK_RIF; // script entry point number
+ event.param3 = -1; // Action
+ event.param4 = -1; // Object
+ event.param5 = -1; // With Object
+ event.param6 = -1; // Actor
+
+ _vm->_events->queue(&event);
+ _dragonHunt = false;
+ }
+ } else {
+ _dragonHunt = true;
+ }
+
+ if (actor->_walkStepIndex + 2 > actor->_walkStepsCount) {
+
+ _vm->_isoMap->findDragonTilePath(actor, actor->_location, _protagonist->_location, actor->_actionDirection);
+
+ if (actor->_walkStepsCount == 0) {
+ _vm->_isoMap->findDragonTilePath(actor, actor->_location, _protagonist->_location, 0);
+ }
+
+ if (actor->_walkStepsCount < 2) {
+ return;
+ }
+
+ actor->_partialTarget = actor->_location;
+ actor->_finalTarget = _protagonist->_location;
+ actor->_walkStepIndex = 0;
+ }
+
+ dir0 = actor->_actionDirection;
+ dir1 = actor->_tileDirections[actor->_walkStepIndex++];
+ dir2 = actor->_tileDirections[actor->_walkStepIndex];
+ dir3 = actor->_tileDirections[actor->_walkStepIndex + 1];
+
+ if (dir0 != dir1){
+ actor->_actionDirection = dir0 = dir1;
+ }
+
+ actor->_location = actor->_partialTarget;
+
+ if ((dir1 != dir2) && (dir1 == dir3)) {
+ switch (dir1) {
+ case kDirUpLeft:
+ actor->_partialTarget.v() += 16;
+ moveType = kDragonMoveUpLeft;
+ break;
+ case kDirDownLeft:
+ actor->_partialTarget.u() -= 16;
+ moveType = kDragonMoveDownLeft;
+ break;
+ case kDirDownRight:
+ actor->_partialTarget.v() -= 16;
+ moveType = kDragonMoveDownRight;
+ break;
+ case kDirUpRight:
+ actor->_partialTarget.u() += 16;
+ moveType = kDragonMoveUpRight;
+ break;
+ }
+
+ switch (dir2) {
+ case kDirUpLeft:
+ actor->_partialTarget.v() += 16;
+ break;
+ case kDirDownLeft:
+ actor->_partialTarget.u() -= 16;
+ break;
+ case kDirDownRight:
+ actor->_partialTarget.v() -= 16;
+ break;
+ case kDirUpRight:
+ actor->_partialTarget.u() += 16;
+ break;
+ }
+
+ actor->_walkStepIndex++;
+ } else {
+ switch (dir1) {
+ case kDirUpLeft:
+ actor->_partialTarget.v() += 16;
+ switch (dir2) {
+ case kDirDownLeft:
+ moveType = kDragonMoveUpLeft_Left;
+ actor->_partialTarget.u() -= 16;
+ break;
+ case kDirUpLeft:
+ moveType = kDragonMoveUpLeft;
+ break;
+ case kDirUpRight:
+ actor->_partialTarget.u() += 16;
+ moveType = kDragonMoveUpLeft_Right;
+ break;
+ default:
+ actor->_actionDirection = dir1;
+ actor->_walkStepsCount = 0;
+ break;
+ }
+ break;
+ case kDirDownLeft:
+ actor->_partialTarget.u() -= 16;
+ switch (dir2) {
+ case kDirDownRight:
+ moveType = kDragonMoveDownLeft_Left;
+ actor->_partialTarget.v() -= 16;
+ break;
+ case kDirDownLeft:
+ moveType = kDragonMoveDownLeft;
+ break;
+ case kDirUpLeft:
+ moveType = kDragonMoveDownLeft_Right;
+ actor->_partialTarget.v() += 16;
+ break;
+ default:
+ actor->_actionDirection = dir1;
+ actor->_walkStepsCount = 0;
+ break;
+ }
+ break;
+ case kDirDownRight:
+ actor->_partialTarget.v() -= 16;
+ switch (dir2) {
+ case kDirUpRight:
+ moveType = kDragonMoveDownRight_Left;
+ actor->_partialTarget.u() += 16;
+ break;
+ case kDirDownRight:
+ moveType = kDragonMoveDownRight;
+ break;
+ case kDirDownLeft:
+ moveType = kDragonMoveDownRight_Right;
+ actor->_partialTarget.u() -= 16;
+ break;
+ default:
+ actor->_actionDirection = dir1;
+ actor->_walkStepsCount = 0;
+ break;
+ }
+ break;
+ case kDirUpRight:
+ actor->_partialTarget.u() += 16;
+ switch (dir2) {
+ case kDirUpLeft:
+ moveType = kDragonMoveUpRight_Left;
+ actor->_partialTarget.v() += 16;
+ break;
+ case kDirUpRight:
+ moveType = kDragonMoveUpRight;
+ break;
+ case kDirDownRight:
+ moveType = kDragonMoveUpRight_Right;
+ actor->_partialTarget.v() -= 16;
+ break;
+ default:
+ actor->_actionDirection = dir1;
+ actor->_walkStepsCount = 0;
+ break;
+ }
+ break;
+
+ default:
+ actor->_actionDirection = dir1;
+ actor->_walkStepsCount = 0;
+ break;
+ }
+ }
+
+ actor->_dragonMoveType = moveType;
+
+ if (moveType >= ACTOR_DRAGON_TURN_MOVES) {
+ actor->_dragonStepCycle = 0;
+ actor->_actionCycle = 4;
+ actor->_walkStepIndex++;
+ } else {
+ actor->_actionCycle = 4;
+ }
+ }
+
+ actor->_actionCycle--;
+
+ if ((actor->_walkStepsCount < 1) || (actor->_actionCycle < 0)) {
+ return;
+ }
+
+ if (actor->_dragonMoveType < ACTOR_DRAGON_TURN_MOVES) {
+
+ actor->_dragonStepCycle++;
+ if (actor->_dragonStepCycle >= 7) {
+ actor->_dragonStepCycle = 0;
+ }
+
+ actor->_dragonBaseFrame = actor->_dragonMoveType * 7;
+
+ if (actor->_location.u() > actor->_partialTarget.u() + 3) {
+ actor->_location.u() -= 4;
+ } else if (actor->_location.u() < actor->_partialTarget.u() - 3) {
+ actor->_location.u() += 4;
+ } else {
+ actor->_location.u() = actor->_partialTarget.u();
+ }
+
+ if (actor->_location.v() > actor->_partialTarget.v() + 3) {
+ actor->_location.v() -= 4;
+ } else if (actor->_location.v() < actor->_partialTarget.v() - 3) {
+ actor->_location.v() += 4;
+ } else {
+ actor->_location.v() = actor->_partialTarget.v();
+ }
+ } else {
+ dragonMove = &dragonMoveTable[actor->_dragonMoveType];
+ actor->_dragonBaseFrame = dragonMove->baseFrame;
+
+
+ actor->_location.u() = actor->_partialTarget.u() - dragonMove->offset[actor->_actionCycle][0];
+ actor->_location.v() = actor->_partialTarget.v() - dragonMove->offset[actor->_actionCycle][1];
+
+ actor->_dragonStepCycle++;
+ if (actor->_dragonStepCycle >= 3) {
+ actor->_dragonStepCycle = 3;
+ }
+ }
+
+ actor->_frameNumber = actor->_dragonBaseFrame + actor->_dragonStepCycle;
+}
+
+void Actor::findActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint) {
+ Point iteratorPoint;
+ Point bestPoint;
+ int maskType;
+ int i;
+ Rect intersect;
+
+#ifdef ACTOR_DEBUG
+ _debugPointsCount = 0;
+#endif
+
+ actor->_walkStepsCount = 0;
+ if (fromPoint == toPoint) {
+ actor->addWalkStepPoint(toPoint);
+ return;
+ }
+
+ for (iteratorPoint.y = 0; iteratorPoint.y < _yCellCount; iteratorPoint.y++) {
+ for (iteratorPoint.x = 0; iteratorPoint.x < _xCellCount; iteratorPoint.x++) {
+ if (_vm->_scene->validBGMaskPoint(iteratorPoint)) {
+ maskType = _vm->_scene->getBGMaskType(iteratorPoint);
+ setPathCell(iteratorPoint, _vm->_scene->getDoorState(maskType) ? kPathCellBarrier : kPathCellEmpty);
+ } else {
+ setPathCell(iteratorPoint, kPathCellBarrier);
+ }
+ }
+ }
+
+ for (i = 0; i < _barrierCount; i++) {
+ intersect.left = MAX(_pathRect.left, _barrierList[i].left);
+ intersect.top = MAX(_pathRect.top, _barrierList[i].top);
+ intersect.right = MIN(_pathRect.right, _barrierList[i].right);
+ intersect.bottom = MIN(_pathRect.bottom, _barrierList[i].bottom);
+
+ for (iteratorPoint.y = intersect.top; iteratorPoint.y < intersect.bottom; iteratorPoint.y++) {
+ for (iteratorPoint.x = intersect.left; iteratorPoint.x < intersect.right; iteratorPoint.x++) {
+ setPathCell(iteratorPoint, kPathCellBarrier);
+ }
+ }
+ }
+
+#ifdef ACTOR_DEBUG
+ for (iteratorPoint.y = 0; iteratorPoint.y < _yCellCount; iteratorPoint.y++) {
+ for (iteratorPoint.x = 0; iteratorPoint.x < _xCellCount; iteratorPoint.x++) {
+ if (getPathCell(iteratorPoint) == kPathCellBarrier) {
+ addDebugPoint(iteratorPoint, 24);
+ }
+ }
+ }
+#endif
+
+ if (scanPathLine(fromPoint, toPoint)) {
+ actor->addWalkStepPoint(fromPoint);
+ actor->addWalkStepPoint(toPoint);
+ return;
+ }
+
+ i = fillPathArray(fromPoint, toPoint, bestPoint);
+
+ if (fromPoint == bestPoint) {
+ actor->addWalkStepPoint(bestPoint);
+ return;
+ }
+
+ if (i == 0) {
+ error("fillPathArray returns zero");
+ }
+
+ setActorPath(actor, fromPoint, bestPoint);
+}
+
+bool Actor::scanPathLine(const Point &point1, const Point &point2) {
+ Point point;
+ Point delta;
+ bool interchange = false;
+ Point fDelta;
+ int errterm;
+ int s1;
+ int s2;
+ int i;
+
+ point = point1;
+ delta.x = ABS(point1.x - point2.x);
+ delta.y = ABS(point1.y - point2.y);
+ s1 = integerCompare(point2.x, point1.x);
+ s2 = integerCompare(point2.y, point1.y);
+
+ if (delta.y > delta.x) {
+ SWAP(delta.y, delta.x);
+ interchange = true;
+ }
+
+ fDelta.x = delta.x * 2;
+ fDelta.y = delta.y * 2;
+
+ errterm = fDelta.y - delta.x;
+
+ for (i = 0; i < delta.x; i++) {
+ while (errterm >= 0) {
+ if (interchange) {
+ point.x += s1;
+ } else {
+ point.y += s2;
+ }
+ errterm -= fDelta.x;
+ }
+
+ if (interchange)
+ point.y += s2;
+ else
+ point.x += s1;
+
+ errterm += fDelta.y;
+
+ if (!validPathCellPoint(point)) {
+ return false;
+ }
+ if (getPathCell(point) == kPathCellBarrier) {
+ return false;
+ }
+ }
+ return true;
+}
+
+int Actor::fillPathArray(const Point &fromPoint, const Point &toPoint, Point &bestPoint) {
+ int bestRating;
+ int currentRating;
+ int i;
+ Point bestPath;
+ int pointCounter;
+ int startDirection;
+ PathDirectionData *pathDirection;
+ PathDirectionData *newPathDirection;
+ const PathDirectionData *samplePathDirection;
+ Point nextPoint;
+ int directionCount;
+
+ _pathDirectionListCount = 0;
+ pointCounter = 0;
+ bestRating = quickDistance(fromPoint, toPoint);
+ bestPath = fromPoint;
+
+ for (startDirection = 0; startDirection < 4; startDirection++) {
+ newPathDirection = addPathDirectionListData();
+ newPathDirection->x = fromPoint.x;
+ newPathDirection->y = fromPoint.y;
+ newPathDirection->direction = startDirection;
+ }
+
+ if (validPathCellPoint(fromPoint)) {
+ setPathCell(fromPoint, kDirUp);
+
+#ifdef ACTOR_DEBUG
+ addDebugPoint(fromPoint, 24+36);
+#endif
+ }
+
+ i = 0;
+
+ do {
+ pathDirection = &_pathDirectionList[i];
+ for (directionCount = 0; directionCount < 3; directionCount++) {
+ samplePathDirection = &pathDirectionLUT[pathDirection->direction][directionCount];
+ nextPoint.x = samplePathDirection->x + pathDirection->x;
+ nextPoint.y = samplePathDirection->y + pathDirection->y;
+
+ if (!validPathCellPoint(nextPoint)) {
+ continue;
+ }
+
+ if (getPathCell(nextPoint) != kPathCellEmpty) {
+ continue;
+ }
+
+ setPathCell(nextPoint, samplePathDirection->direction);
+
+#ifdef ACTOR_DEBUG
+ addDebugPoint(nextPoint, samplePathDirection->direction + 96);
+#endif
+ newPathDirection = addPathDirectionListData();
+ newPathDirection->x = nextPoint.x;
+ newPathDirection->y = nextPoint.y;
+ newPathDirection->direction = samplePathDirection->direction;
+ ++pointCounter;
+ if (nextPoint == toPoint) {
+ bestPoint = toPoint;
+ return pointCounter;
+ }
+ currentRating = quickDistance(nextPoint, toPoint);
+ if (currentRating < bestRating) {
+ bestRating = currentRating;
+ bestPath = nextPoint;
+ }
+ pathDirection = &_pathDirectionList[i];
+ }
+ ++i;
+ } while (i < _pathDirectionListCount);
+
+ bestPoint = bestPath;
+ return pointCounter;
+}
+
+void Actor::setActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint) {
+ Point nextPoint;
+ int8 direction;
+ int i;
+
+ _pathListIndex = -1;
+ addPathListPoint(toPoint);
+ nextPoint = toPoint;
+
+ while (!(nextPoint == fromPoint)) {
+ direction = getPathCell(nextPoint);
+ if ((direction < 0) || (direction >= 8)) {
+ error("Actor::setActorPath error direction 0x%X", direction);
+ }
+ nextPoint.x -= pathDirectionLUT2[direction][0];
+ nextPoint.y -= pathDirectionLUT2[direction][1];
+ addPathListPoint(nextPoint);
+
+#ifdef ACTOR_DEBUG
+ addDebugPoint(nextPoint, 0x8a);
+#endif
+ }
+
+ pathToNode();
+ removeNodes();
+ nodeToPath();
+ removePathPoints();
+
+ for (i = 0; i <= _pathNodeListIndex; i++) {
+ actor->addWalkStepPoint(_pathNodeList[i].point);
+ }
+}
+
+void Actor::pathToNode() {
+ Point point1, point2, delta;
+ int direction;
+ int i;
+ Point *point;
+
+ point= &_pathList[_pathListIndex];
+ direction = 0;
+
+ _pathNodeListIndex = -1;
+ addPathNodeListPoint(*point);
+
+ for (i = _pathListIndex; i > 0; i--) {
+ point1 = *point;
+ --point;
+ point2 = *point;
+ if (direction == 0) {
+ delta.x = integerCompare(point2.x, point1.x);
+ delta.y = integerCompare(point2.y, point1.y);
+ direction++;
+ }
+ if ((point1.x + delta.x != point2.x) || (point1.y + delta.y != point2.y)) {
+ addPathNodeListPoint(point1);
+ direction--;
+ i++;
+ point++;
+ }
+ }
+ addPathNodeListPoint(*_pathList);
+}
+
+int pathLine(Point *pointList, const Point &point1, const Point &point2) {
+ Point point;
+ Point delta;
+ Point tempPoint;
+ int s1;
+ int s2;
+ bool interchange = false;
+ int errterm;
+ int i;
+
+ delta.x = abs(point2.x - point1.x);
+ delta.y = abs(point2.y - point1.y);
+ point = point1;
+ s1 = integerCompare(point2.x, point1.x);
+ s2 = integerCompare(point2.y, point1.y);
+
+ if (delta.y > delta.x) {
+ SWAP(delta.y, delta.x);
+ interchange = true;
+ }
+
+ tempPoint.x = delta.x * 2;
+ tempPoint.y = delta.y * 2;
+
+ errterm = tempPoint.y - delta.x;
+
+ for (i = 0; i < delta.x; i++) {
+ while (errterm >= 0) {
+ if (interchange) {
+ point.x += s1;
+ } else {
+ point.y += s2;
+ }
+ errterm -= tempPoint.x;
+ }
+ if (interchange) {
+ point.y += s2;
+ } else {
+ point.x += s1;
+ }
+ errterm += tempPoint.y;
+
+ pointList[i] = point;
+ }
+ return delta.x;
+}
+
+void Actor::nodeToPath() {
+ int i;
+ Point point1, point2;
+ PathNode *node;
+ Point *point;
+
+ for (i = 0, point = _pathList; i < _pathListAlloced; i++, point++) {
+ point->x = point->y = PATH_NODE_EMPTY;
+ }
+
+ _pathListIndex = 1;
+ _pathList[0] = _pathNodeList[0].point;
+ _pathNodeList[0].link = 0;
+ for (i = 0, node = _pathNodeList; i < _pathNodeListIndex; i++) {
+ point1 = node->point;
+ node++;
+ point2 = node->point;
+ _pathListIndex += pathLine(&_pathList[_pathListIndex], point1, point2);
+ node->link = _pathListIndex - 1;
+ }
+ _pathListIndex--;
+ _pathNodeList[_pathNodeListIndex].link = _pathListIndex;
+
+}
+
+void Actor::removeNodes() {
+ int i, j, k;
+ PathNode *iNode, *jNode, *kNode, *fNode;
+ fNode = &_pathNodeList[_pathNodeListIndex];
+
+ if (scanPathLine(_pathNodeList[0].point, fNode->point)) {
+ _pathNodeList[1] = *fNode;
+ _pathNodeListIndex = 1;
+ }
+
+ if (_pathNodeListIndex < 4) {
+ return;
+ }
+
+ for (i = _pathNodeListIndex - 1, iNode = fNode-1; i > 1 ; i--, iNode--) {
+ if (iNode->point.x == PATH_NODE_EMPTY) {
+ continue;
+ }
+
+ if (scanPathLine(_pathNodeList[0].point, iNode->point)) {
+ for (j = 1, jNode = _pathNodeList + 1; j < i; j++, jNode++) {
+ jNode->point.x = PATH_NODE_EMPTY;
+ }
+ }
+ }
+
+ for (i = 1, iNode = _pathNodeList + 1; i < _pathNodeListIndex - 1; i++, iNode++) {
+ if (iNode->point.x == PATH_NODE_EMPTY) {
+ continue;
+ }
+
+ if (scanPathLine(fNode->point, iNode->point)) {
+ for (j = i + 1, jNode = iNode + 1; j < _pathNodeListIndex; j++, jNode++) {
+ jNode->point.x = PATH_NODE_EMPTY;
+ }
+ }
+ }
+ condenseNodeList();
+
+ for (i = 1, iNode = _pathNodeList + 1; i < _pathNodeListIndex - 1; i++, iNode++) {
+ if (iNode->point.x == PATH_NODE_EMPTY) {
+ continue;
+ }
+ for (j = i + 2, jNode = iNode + 2; j < _pathNodeListIndex; j++, jNode++) {
+ if (jNode->point.x == PATH_NODE_EMPTY) {
+ continue;
+ }
+
+ if (scanPathLine(iNode->point, jNode->point)) {
+ for (k = i + 1,kNode = iNode + 1; k < j; k++, kNode++) {
+ kNode->point.x = PATH_NODE_EMPTY;
+ }
+ }
+ }
+ }
+ condenseNodeList();
+}
+
+void Actor::condenseNodeList() {
+ int i, j, count;
+ PathNode *iNode, *jNode;
+
+ count = _pathNodeListIndex;
+
+ for (i = 1, iNode = _pathNodeList + 1; i < _pathNodeListIndex; i++, iNode++) {
+ if (iNode->point.x == PATH_NODE_EMPTY) {
+ j = i + 1;
+ jNode = iNode + 1;
+ while (jNode->point.x == PATH_NODE_EMPTY) {
+ j++;
+ jNode++;
+ }
+ *iNode = *jNode;
+ count = i;
+ jNode->point.x = PATH_NODE_EMPTY;
+ if (j == _pathNodeListIndex) {
+ break;
+ }
+ }
+ }
+ _pathNodeListIndex = count;
+}
+
+void Actor::removePathPoints() {
+ int i, j, k, l;
+ PathNode *node;
+ int start;
+ int end;
+ Point point1, point2;
+
+ if (_pathNodeListIndex < 2)
+ return;
+
+ _newPathNodeListIndex = -1;
+ addNewPathNodeListPoint(_pathNodeList[0]);
+
+ for (i = 1, node = _pathNodeList + 1; i < _pathNodeListIndex; i++, node++) {
+ addNewPathNodeListPoint(*node);
+
+ for (j = 5; j > 0; j--) {
+ start = node->link - j;
+ end = node->link + j;
+
+ if (start < 0 || end > _pathListIndex) {
+ continue;
+ }
+
+ point1 = _pathList[start];
+ point2 = _pathList[end];
+ if ((point1.x == PATH_NODE_EMPTY) || (point2.x == PATH_NODE_EMPTY)) {
+ continue;
+ }
+
+ if (scanPathLine(point1, point2)) {
+ for (l = 1; l <= _newPathNodeListIndex; l++) {
+ if (start <= _newPathNodeList[l].link) {
+ _newPathNodeListIndex = l;
+ _newPathNodeList[_newPathNodeListIndex].point = point1;
+ _newPathNodeList[_newPathNodeListIndex].link = start;
+ incrementNewPathNodeListIndex();
+ break;
+ }
+ }
+ _newPathNodeList[_newPathNodeListIndex].point = point2;
+ _newPathNodeList[_newPathNodeListIndex].link = end;
+
+ for (k = start + 1; k < end; k++) {
+ _pathList[k].x = PATH_NODE_EMPTY;
+ }
+ break;
+ }
+ }
+ }
+
+ addNewPathNodeListPoint(_pathNodeList[_pathNodeListIndex]);
+
+ for (i = 0, j = 0; i <= _newPathNodeListIndex; i++) {
+ if (_newPathNodeListIndex == i || (_newPathNodeList[i].point != _newPathNodeList[i+1].point)) {
+ _pathNodeList[j++] = _newPathNodeList[i];
+ }
+ }
+ _pathNodeListIndex = j - 1;
+}
+
+void Actor::drawPathTest() {
+#ifdef ACTOR_DEBUG
+ int i;
+ Surface *surface;
+ surface = _vm->_gfx->getBackBuffer();
+ if (_debugPoints == NULL) {
+ return;
+ }
+
+ for (i = 0; i < _debugPointsCount; i++) {
+ *((byte *)surface->pixels + (_debugPoints[i].point.y * surface->pitch) + _debugPoints[i].point.x) = _debugPoints[i].color;
+ }
+#endif
+}
+
+void Actor::saveState(Common::OutSaveFile *out) {
+ uint16 i;
+
+ out->writeSint16LE(getProtagState());
+
+ for (i = 0; i < _actorsCount; i++) {
+ ActorData *a = _actors[i];
+ a->saveState(out);
+ }
+
+ for (i = 0; i < _objsCount; i++) {
+ ObjectData *o = _objs[i];
+ o->saveState(out);
+ }
+}
+
+void Actor::loadState(Common::InSaveFile *in) {
+ int32 i;
+
+ setProtagState(in->readSint16LE());
+
+ for (i = 0; i < _actorsCount; i++) {
+ ActorData *a = _actors[i];
+ a->loadState(_vm->getCurrentLoadVersion(), in);
+
+ // Fix bug #1258633 "ITE: Second Rif appears in wall of dog castle prison"
+ // For some reason in some cases actor position is all wrong, so Rif
+ // crawls to his original poition
+ if (i == 122 && _vm->getGameType() == GType_ITE) {
+ a->_location.x = 130;
+ a->_location.y = 55;
+ }
+ }
+
+ for (i = 0; i < _objsCount; i++) {
+ ObjectData *o = _objs[i];
+ o->loadState(in);
+ }
+}
+
+// Console wrappers - must be safe to run
+
+void Actor::cmdActorWalkTo(int argc, const char **argv) {
+ uint16 actorId = (uint16) atoi(argv[1]);
+ Location location;
+ Point movePoint;
+
+ movePoint.x = atoi(argv[2]);
+ movePoint.y = atoi(argv[3]);
+
+ location.fromScreenPoint(movePoint);
+
+ if (!validActorId(actorId)) {
+ _vm->_console->DebugPrintf("Actor::cmActorWalkTo Invalid actorId 0x%X.\n", actorId);
+ return;
+ }
+
+ actorWalkTo(actorId, location);
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/actor.h b/engines/saga/actor.h
new file mode 100644
index 0000000000..74f1abd689
--- /dev/null
+++ b/engines/saga/actor.h
@@ -0,0 +1,778 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Actor management module header file
+
+#ifndef SAGA_ACTOR_H__
+#define SAGA_ACTOR_H__
+
+#include "common/savefile.h"
+
+#include "saga/sprite.h"
+#include "saga/itedata.h"
+#include "saga/list.h"
+#include "saga/saga.h"
+#include "saga/font.h"
+
+namespace Saga {
+
+class HitZone;
+
+
+//#define ACTOR_DEBUG //only for actor pathfinding debug!
+
+#define ACTOR_BARRIERS_MAX 16
+
+#define ACTOR_MAX_STEPS_COUNT 32
+
+#define ACTOR_DIALOGUE_HEIGHT 100
+
+#define ACTOR_LMULT 4
+
+#define ACTOR_CLIMB_SPEED 8
+
+#define ACTOR_COLLISION_WIDTH 32
+#define ACTOR_COLLISION_HEIGHT 8
+
+#define ACTOR_DIRECTIONS_COUNT 4 // for ActorFrameSequence
+#define ACTOR_DIRECTION_RIGHT 0
+#define ACTOR_DIRECTION_LEFT 1
+#define ACTOR_DIRECTION_BACK 2
+#define ACTOR_DIRECTION_FORWARD 3
+
+#define ACTOR_SPEECH_STRING_MAX 16 // speech const
+#define ACTOR_SPEECH_ACTORS_MAX 8
+
+#define ACTOR_DRAGON_TURN_MOVES 4
+#define ACTOR_DRAGON_INDEX 133
+
+#define ACTOR_NO_ENTRANCE -1
+
+#define ACTOR_EXP_KNOCK_RIF 24
+
+#define PATH_NODE_EMPTY -1
+
+#define ACTOR_INHM_SIZE 228
+
+enum ActorActions {
+ kActionWait = 0,
+ kActionWalkToPoint = 1,
+ kActionWalkToLink = 2,
+ kActionWalkDir = 3,
+ kActionSpeak = 4,
+ kActionAccept = 5,
+ kActionStoop = 6,
+ kActionLook = 7,
+ kActionCycleFrames = 8,
+ kActionPongFrames = 9,
+ kActionFreeze = 10,
+ kActionFall = 11,
+ kActionClimb = 12
+};
+
+enum SpeechFlags {
+ kSpeakNoAnimate = 1,
+ kSpeakAsync = 2,
+ kSpeakSlow = 4
+};
+
+enum ActorFrameTypes {
+ kFrameStand,
+ kFrameWalk,
+ kFrameSpeak,
+ kFrameGive,
+ kFrameGesture,
+ kFrameWait,
+ kFramePickUp,
+ kFrameLook
+};
+
+enum ActorFlagsEx {
+ kActorNoCollide = (1 << 0),
+ kActorNoFollow = (1 << 1),
+ kActorCollided = (1 << 2),
+ kActorBackwards = (1 << 3),
+ kActorContinuous = (1 << 4),
+ kActorFinalFace = (1 << 5),
+ kActorFinishLeft = ((1 << 5) | (kDirLeft << 6)),
+ kActorFinishRight = ((1 << 5) | (kDirRight << 6)),
+ kActorFinishUp = ((1 << 5) | (kDirUp << 6)),
+ kActorFinishDown = ((1 << 5) | (kDirDown << 6)),
+ kActorFacingMask = (0xf << 5),
+ kActorRandom = (1 << 10)
+};
+
+enum PathCellType {
+ kPathCellEmpty = -1,
+ //kDirUp = 0 .... kDirUpLeft = 7
+ kPathCellBarrier = 0x57
+};
+
+enum DragonMoveTypes {
+ kDragonMoveUpLeft = 0,
+ kDragonMoveUpRight = 1,
+ kDragonMoveDownLeft = 2,
+ kDragonMoveDownRight = 3,
+ kDragonMoveUpLeft_Left = 4,
+ kDragonMoveUpLeft_Right = 5,
+ kDragonMoveUpRight_Left = 6,
+ kDragonMoveUpRight_Right = 7,
+ kDragonMoveDownLeft_Left = 8,
+ kDragonMoveDownLeft_Right = 9,
+ kDragonMoveDownRight_Left = 10,
+ kDragonMoveDownRight_Right = 11,
+ kDragonMoveInvalid = 12
+};
+
+struct PathDirectionData {
+ int8 direction;
+ int16 x;
+ int16 y;
+};
+
+struct ActorFrameRange {
+ int frameIndex;
+ int frameCount;
+};
+
+struct ActorFrameSequence {
+ ActorFrameRange directions[ACTOR_DIRECTIONS_COUNT];
+};
+
+int pathLine(Point *pointList, const Point &point1, const Point &point2);
+
+struct Location {
+ int32 x; // logical coordinates
+ int32 y; //
+ int32 z; //
+ Location() {
+ x = y = z = 0;
+ }
+ void saveState(Common::OutSaveFile *out) {
+ out->writeSint32LE(x);
+ out->writeSint32LE(y);
+ out->writeSint32LE(z);
+ }
+ void loadState(Common::InSaveFile *in) {
+ x = in->readSint32LE();
+ y = in->readSint32LE();
+ z = in->readSint32LE();
+ }
+
+ int distance(const Location &location) const {
+ return MAX(ABS(x - location.x), ABS(y - location.y));
+ }
+ int32 &u() {
+ return x;
+ }
+ int32 &v() {
+ return y;
+ }
+ int32 u() const {
+ return x;
+ }
+ int32 v() const {
+ return y;
+ }
+ int32 uv() const {
+ return u() + v();
+ }
+ void delta(const Location &location, Location &result) const {
+ result.x = x - location.x;
+ result.y = y - location.y;
+ result.z = z - location.z;
+ }
+ void addXY(const Location &location) {
+ x += location.x;
+ y += location.y;
+ }
+ void add(const Location &location) {
+ x += location.x;
+ y += location.y;
+ z += location.z;
+ }
+ void fromScreenPoint(const Point &screenPoint) {
+ x = (screenPoint.x * ACTOR_LMULT);
+ y = (screenPoint.y * ACTOR_LMULT);
+ z = 0;
+ }
+ void toScreenPointXY(Point &screenPoint) const {
+ screenPoint.x = x / ACTOR_LMULT;
+ screenPoint.y = y / ACTOR_LMULT;
+ }
+ void toScreenPointUV(Point &screenPoint) const {
+ screenPoint.x = u();
+ screenPoint.y = v();
+ }
+ void toScreenPointXYZ(Point &screenPoint) const {
+ screenPoint.x = x / ACTOR_LMULT;
+ screenPoint.y = y / ACTOR_LMULT - z;
+ }
+ void fromStream(Common::MemoryReadStream &stream) {
+ x = stream.readUint16LE();
+ y = stream.readUint16LE();
+ z = stream.readUint16LE();
+ }
+
+ void debugPrint(int debuglevel = 0, const char *loc = "Loc:") const {
+ debug(debuglevel, "%s %d, %d, %d", loc, x, y, z);
+ }
+};
+
+class CommonObjectData {
+public:
+//constant
+ bool _disabled; // disabled in init section
+ int32 _index; // index in local array
+ uint16 _id; // object id
+ int32 _scriptEntrypointNumber; // script entrypoint number
+
+//variables
+ uint16 _flags; // initial flags
+ int32 _nameIndex; // index in name string list
+ int32 _sceneNumber; // scene
+ int32 _spriteListResourceId; // sprite list resource id
+
+ Location _location; // logical coordinates
+ Point _screenPosition; // screen coordinates
+ int32 _screenDepth; //
+ int32 _screenScale; //
+
+ void saveState(Common::OutSaveFile *out) {
+ out->writeUint16LE(_flags);
+ out->writeSint32LE(_nameIndex);
+ out->writeSint32LE(_sceneNumber);
+ out->writeSint32LE(_spriteListResourceId);
+ _location.saveState(out);
+ out->writeSint16LE(_screenPosition.x);
+ out->writeSint16LE(_screenPosition.y);
+ out->writeSint32LE(_screenDepth);
+ out->writeSint32LE(_screenScale);
+ }
+ void loadState(Common::InSaveFile *in) {
+ _flags = in->readUint16LE();
+ _nameIndex = in->readSint32LE();
+ _sceneNumber = in->readSint32LE();
+ _spriteListResourceId = in->readSint32LE();
+ _location.loadState(in);
+ _screenPosition.x = in->readSint16LE();
+ _screenPosition.y = in->readSint16LE();
+ _screenDepth = in->readSint32LE();
+ _screenScale = in->readSint32LE();
+ }
+};
+
+typedef CommonObjectData *CommonObjectDataPointer;
+
+typedef SortedList<CommonObjectDataPointer> CommonObjectOrderList;
+
+class ObjectData: public CommonObjectData {
+public:
+ //constant
+ uint16 _interactBits;
+ ObjectData() {
+ memset(this, 0, sizeof(*this));
+ }
+};
+
+class ActorData: public CommonObjectData {
+public:
+ //constant
+ SpriteList _spriteList; // sprite list data
+
+ ActorFrameSequence *_frames; // Actor's frames
+ int _framesCount; // Actor's frames count
+ int _frameListResourceId; // Actor's frame list resource id
+
+ byte _speechColor; // Actor dialogue color
+ //
+ bool _inScene;
+
+ //variables
+ uint16 _actorFlags; // dynamic flags
+ int32 _currentAction; // ActorActions type
+ int32 _facingDirection; // orientation
+ int32 _actionDirection;
+ int32 _actionCycle;
+ uint16 _targetObject;
+ const HitZone *_lastZone;
+
+ int32 _cycleFrameSequence;
+ uint8 _cycleDelay;
+ uint8 _cycleTimeCount;
+ uint8 _cycleFlags;
+
+ int16 _fallVelocity;
+ int16 _fallAcceleration;
+ int16 _fallPosition;
+
+ uint8 _dragonBaseFrame;
+ uint8 _dragonStepCycle;
+ uint8 _dragonMoveType;
+
+ int32 _frameNumber; // current frame number
+
+ int32 _tileDirectionsAlloced;
+ byte *_tileDirections;
+
+ int32 _walkStepsAlloced;
+ Point *_walkStepsPoints;
+
+ int32 _walkStepsCount;
+ int32 _walkStepIndex;
+
+ Location _finalTarget;
+ Location _partialTarget;
+ int32 _walkFrameSequence;
+
+public:
+ void saveState(Common::OutSaveFile *out) {
+ int i = 0;
+ CommonObjectData::saveState(out);
+ out->writeUint16LE(_actorFlags);
+ out->writeSint32LE(_currentAction);
+ out->writeSint32LE(_facingDirection);
+ out->writeSint32LE(_actionDirection);
+ out->writeSint32LE(_actionCycle);
+ out->writeUint16LE(_targetObject);
+
+ out->writeSint32LE(_cycleFrameSequence);
+ out->writeByte(_cycleDelay);
+ out->writeByte(_cycleTimeCount);
+ out->writeByte(_cycleFlags);
+ out->writeSint16LE(_fallVelocity);
+ out->writeSint16LE(_fallAcceleration);
+ out->writeSint16LE(_fallPosition);
+ out->writeByte(_dragonBaseFrame);
+ out->writeByte(_dragonStepCycle);
+ out->writeByte(_dragonMoveType);
+ out->writeSint32LE(_frameNumber);
+
+ out->writeSint32LE(_tileDirectionsAlloced);
+ for (i = 0; i < _tileDirectionsAlloced; i++) {
+ out->writeByte(_tileDirections[i]);
+ }
+
+ out->writeSint32LE(_walkStepsAlloced);
+ for (i = 0; i < _walkStepsAlloced; i++) {
+ out->writeSint16LE(_walkStepsPoints[i].x);
+ out->writeSint16LE(_walkStepsPoints[i].y);
+ }
+
+ out->writeSint32LE(_walkStepsCount);
+ out->writeSint32LE(_walkStepIndex);
+ _finalTarget.saveState(out);
+ _partialTarget.saveState(out);
+ out->writeSint32LE(_walkFrameSequence);
+ }
+
+ void loadState(uint32 version, Common::InSaveFile *in) {
+ int i = 0;
+ CommonObjectData::loadState(in);
+ _actorFlags = in->readUint16LE();
+ _currentAction = in->readSint32LE();
+ _facingDirection = in->readSint32LE();
+ _actionDirection = in->readSint32LE();
+ _actionCycle = in->readSint32LE();
+ _targetObject = in->readUint16LE();
+
+ _lastZone = NULL;
+ _cycleFrameSequence = in->readSint32LE();
+ _cycleDelay = in->readByte();
+ _cycleTimeCount = in->readByte();
+ _cycleFlags = in->readByte();
+ if (version > 1) {
+ _fallVelocity = in->readSint16LE();
+ _fallAcceleration = in->readSint16LE();
+ _fallPosition = in->readSint16LE();
+ } else {
+ _fallVelocity = _fallAcceleration = _fallPosition = 0;
+ }
+ if (version > 2) {
+ _dragonBaseFrame = in->readByte();
+ _dragonStepCycle = in->readByte();
+ _dragonMoveType = in->readByte();
+ } else {
+ _dragonBaseFrame = _dragonStepCycle = _dragonMoveType = 0;
+ }
+
+ _frameNumber = in->readSint32LE();
+
+
+ setTileDirectionsSize(in->readSint32LE(), true);
+ for (i = 0; i < _tileDirectionsAlloced; i++) {
+ _tileDirections[i] = in->readByte();
+ }
+
+ setWalkStepsPointsSize(in->readSint32LE(), true);
+ for (i = 0; i < _walkStepsAlloced; i++) {
+ _walkStepsPoints[i].x = in->readSint16LE();
+ _walkStepsPoints[i].y = in->readSint16LE();
+ }
+
+ _walkStepsCount = in->readSint32LE();
+ _walkStepIndex = in->readSint32LE();
+ _finalTarget.loadState(in);
+ _partialTarget.loadState(in);
+ _walkFrameSequence = in->readSint32LE();
+ }
+
+ void setTileDirectionsSize(int size, bool forceRealloc) {
+ if ((size <= _tileDirectionsAlloced) && !forceRealloc) {
+ return;
+ }
+ _tileDirectionsAlloced = size;
+ _tileDirections = (byte*)realloc(_tileDirections, _tileDirectionsAlloced * sizeof(*_tileDirections));
+ }
+
+ void cycleWrap(int cycleLimit) {
+ if (_actionCycle >= cycleLimit)
+ _actionCycle = 0;
+ }
+
+ void setWalkStepsPointsSize(int size, bool forceRealloc) {
+ if ((size <= _walkStepsAlloced) && !forceRealloc) {
+ return;
+ }
+ _walkStepsAlloced = size;
+ _walkStepsPoints = (Point*)realloc(_walkStepsPoints, _walkStepsAlloced * sizeof(*_walkStepsPoints));
+ }
+
+ void addWalkStepPoint(const Point &point) {
+ setWalkStepsPointsSize(_walkStepsCount + 1, false);
+ _walkStepsPoints[_walkStepsCount++] = point;
+ }
+
+ void freeSpriteList() {
+ _spriteList.freeMem();
+ }
+
+ ActorData() {
+ memset(this, 0, sizeof(*this));
+ }
+ ~ActorData() {
+ free(_frames);
+ free(_tileDirections);
+ free(_walkStepsPoints);
+ freeSpriteList();
+ }
+};
+
+
+
+struct SpeechData {
+ int speechColor[ACTOR_SPEECH_ACTORS_MAX];
+ int outlineColor[ACTOR_SPEECH_ACTORS_MAX];
+ int speechFlags;
+ const char *strings[ACTOR_SPEECH_STRING_MAX];
+ Rect speechBox;
+ Rect drawRect;
+ int stringsCount;
+ int slowModeCharIndex;
+ uint16 actorIds[ACTOR_SPEECH_ACTORS_MAX];
+ int actorsCount;
+ int sampleResourceId;
+ bool playing;
+ int playingTime;
+
+ SpeechData() {
+ memset(this, 0, sizeof(*this));
+ }
+
+ FontEffectFlags getFontFlags(int i) {
+ if (outlineColor[i] != 0) {
+ return kFontOutline;
+ } else {
+ return kFontNormal;
+ }
+ }
+};
+
+
+
+class Actor {
+ friend class IsoMap;
+ friend class SagaEngine;
+ friend class Puzzle;
+public:
+
+ Actor(SagaEngine *vm);
+ ~Actor();
+
+ void cmdActorWalkTo(int argc, const char **argv);
+
+ bool validActorId(uint16 id) { return (id == ID_PROTAG) || ((id >= objectIndexToId(kGameObjectActor, 0)) && (id < objectIndexToId(kGameObjectActor, _actorsCount))); }
+ int actorIdToIndex(uint16 id) { return (id == ID_PROTAG ) ? 0 : objectIdToIndex(id); }
+ uint16 actorIndexToId(int index) { return (index == 0 ) ? ID_PROTAG : objectIndexToId(kGameObjectActor, index); }
+ ActorData *getActor(uint16 actorId);
+
+// clarification: Obj - means game object, such Hat, Spoon etc, Object - means Actor,Obj,HitZone,StepZone
+
+ bool validObjId(uint16 id) { return (id >= objectIndexToId(kGameObjectObject, 0)) && (id < objectIndexToId(kGameObjectObject, _objsCount)); }
+ int objIdToIndex(uint16 id) { return objectIdToIndex(id); }
+ uint16 objIndexToId(int index) { return objectIndexToId(kGameObjectObject, index); }
+ ObjectData *getObj(uint16 objId);
+
+ int getObjectScriptEntrypointNumber(uint16 id) {
+ int objectType;
+ objectType = objectTypeId(id);
+ if (!(objectType & (kGameObjectObject | kGameObjectActor))) {
+ error("Actor::getObjectScriptEntrypointNumber wrong id 0x%X", id);
+ }
+ return (objectType == kGameObjectObject) ? getObj(id)->_scriptEntrypointNumber : getActor(id)->_scriptEntrypointNumber;
+ }
+ int getObjectFlags(uint16 id) {
+ int objectType;
+ objectType = objectTypeId(id);
+ if (!(objectType & (kGameObjectObject | kGameObjectActor))) {
+ error("Actor::getObjectFlags wrong id 0x%X", id);
+ }
+ return (objectType == kGameObjectObject) ? getObj(id)->_flags : getActor(id)->_flags;
+ }
+
+ void direct(int msec);
+ void drawActors();
+ void updateActorsScene(int actorsEntrance); // calls from scene loading to update Actors info
+
+ void drawSpeech();
+
+ void drawPathTest();
+
+ uint16 hitTest(const Point &testPoint, bool skipProtagonist);
+ void takeExit(uint16 actorId, const HitZone *hitZone);
+ bool actorEndWalk(uint16 actorId, bool recurse);
+ bool actorWalkTo(uint16 actorId, const Location &toLocation);
+ int getFrameType(ActorFrameTypes frameType);
+ ActorFrameRange *getActorFrameRange(uint16 actorId, int frameType);
+ void actorFaceTowardsPoint(uint16 actorId, const Location &toLocation);
+ void actorFaceTowardsObject(uint16 actorId, uint16 objectId);
+
+ void realLocation(Location &location, uint16 objectId, uint16 walkFlags);
+
+// speech
+ void actorSpeech(uint16 actorId, const char **strings, int stringsCount, int sampleResourceId, int speechFlags);
+ void nonActorSpeech(const Common::Rect &box, const char **strings, int stringsCount, int sampleResourceId, int speechFlags);
+ void simulSpeech(const char *string, uint16 *actorIds, int actorIdsCount, int speechFlags, int sampleResourceId);
+ void setSpeechColor(int speechColor, int outlineColor) {
+ _activeSpeech.speechColor[0] = speechColor;
+ _activeSpeech.outlineColor[0] = outlineColor;
+ }
+ void abortAllSpeeches();
+ void abortSpeech();
+ bool isSpeaking() {
+ return _activeSpeech.stringsCount > 0;
+ }
+
+ void saveState(Common::OutSaveFile *out);
+ void loadState(Common::InSaveFile *in);
+
+ void setProtagState(int state);
+ int getProtagState() { return _protagState; }
+
+ void freeActorList();
+ void loadActorList(int protagonistIdx, int actorCount, int actorsResourceID,
+ int protagStatesCount, int protagStatesResourceID);
+ void freeObjList();
+ void loadObjList(int objectCount, int objectsResourceID);
+
+ /*
+ uint16 _currentFrameIndex;
+ void frameTest() {
+ _currentFrameIndex++;
+ }*/
+protected:
+ friend class Script;
+ bool loadActorResources(ActorData *actor);
+
+private:
+ void stepZoneAction(ActorData *actor, const HitZone *hitZone, bool exit, bool stopped);
+ void loadActorSpriteList(ActorData *actor);
+
+ void createDrawOrderList();
+ bool calcScreenPosition(CommonObjectData *commonObjectData);
+ bool getSpriteParams(CommonObjectData *commonObjectData, int &frameNumber, SpriteList *&spriteList);
+
+ bool followProtagonist(ActorData *actor);
+ void findActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint);
+ void handleSpeech(int msec);
+ void handleActions(int msec, bool setup);
+ bool validPathCellPoint(const Point &testPoint) {
+ return !((testPoint.x < 0) || (testPoint.x >= _xCellCount) ||
+ (testPoint.y < 0) || (testPoint.y >= _yCellCount));
+ }
+ void setPathCell(const Point &testPoint, int8 value) {
+ if (!validPathCellPoint(testPoint)) {
+ error("Actor::setPathCell wrong point");
+ }
+ _pathCell[testPoint.x + testPoint.y * _xCellCount] = value;
+ }
+ int8 getPathCell(const Point &testPoint) {
+ if (!validPathCellPoint(testPoint)) {
+ error("Actor::getPathCell wrong point");
+ }
+ return _pathCell[testPoint.x + testPoint.y * _xCellCount];
+ }
+ bool scanPathLine(const Point &point1, const Point &point2);
+ int fillPathArray(const Point &fromPoint, const Point &toPoint, Point &bestPoint);
+ void setActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint);
+ void pathToNode();
+ void condenseNodeList();
+ void removeNodes();
+ void nodeToPath();
+ void removePathPoints();
+ bool validFollowerLocation(const Location &location);
+ void moveDragon(ActorData *actor);
+
+
+protected:
+//constants
+ int _actorsCount;
+ ActorData **_actors;
+
+ int _objsCount;
+ ObjectData **_objs;
+
+ SagaEngine *_vm;
+ ResourceContext *_actorContext;
+
+ int _lastTickMsec;
+ CommonObjectOrderList _drawOrderList;
+
+//variables
+public:
+ ActorData *_centerActor;
+ ActorData *_protagonist;
+ int _handleActionDiv;
+
+ Rect _speechBoxScript;
+
+ StringsTable _objectsStrings;
+ StringsTable _actorsStrings;
+
+protected:
+ SpeechData _activeSpeech;
+ int _protagState;
+ bool _dragonHunt;
+
+private:
+ ActorFrameSequence *_protagStates;
+ int _protagStatesCount;
+
+//path stuff
+ struct PathNode {
+ Point point;
+ int link;
+ };
+
+ Rect _barrierList[ACTOR_BARRIERS_MAX];
+ int _barrierCount;
+ int8 *_pathCell;
+
+ int _xCellCount;
+ int _yCellCount;
+ Rect _pathRect;
+
+ PathDirectionData *_pathDirectionList;
+ int _pathDirectionListCount;
+ int _pathDirectionListAlloced;
+ PathDirectionData * addPathDirectionListData() {
+ if (_pathDirectionListCount + 1 >= _pathDirectionListAlloced) {
+ _pathDirectionListAlloced += 100;
+ _pathDirectionList = (PathDirectionData*) realloc(_pathDirectionList, _pathDirectionListAlloced * sizeof(*_pathDirectionList));
+ }
+ return &_pathDirectionList[_pathDirectionListCount++];
+ }
+
+ Point *_pathList;
+ int _pathListIndex;
+ int _pathListAlloced;
+ void addPathListPoint(const Point &point) {
+ ++_pathListIndex;
+ if (_pathListIndex >= _pathListAlloced) {
+ _pathListAlloced += 100;
+ _pathList = (Point*) realloc(_pathList, _pathListAlloced * sizeof(*_pathList));
+
+ }
+ _pathList[_pathListIndex] = point;
+ }
+
+ int _pathNodeListIndex;
+ int _pathNodeListAlloced;
+ PathNode *_pathNodeList;
+ void addPathNodeListPoint(const Point &point) {
+ ++_pathNodeListIndex;
+ if (_pathNodeListIndex >= _pathNodeListAlloced) {
+ _pathNodeListAlloced += 100;
+ _pathNodeList = (PathNode*) realloc(_pathNodeList, _pathNodeListAlloced * sizeof(*_pathNodeList));
+
+ }
+ _pathNodeList[_pathNodeListIndex].point = point;
+ }
+
+ int _newPathNodeListIndex;
+ int _newPathNodeListAlloced;
+ PathNode *_newPathNodeList;
+ void incrementNewPathNodeListIndex() {
+ ++_newPathNodeListIndex;
+ if (_newPathNodeListIndex >= _newPathNodeListAlloced) {
+ _newPathNodeListAlloced += 100;
+ _newPathNodeList = (PathNode*) realloc(_newPathNodeList, _newPathNodeListAlloced * sizeof(*_newPathNodeList));
+
+ }
+ }
+ void addNewPathNodeListPoint(const PathNode &pathNode) {
+ incrementNewPathNodeListIndex();
+ _newPathNodeList[_newPathNodeListIndex] = pathNode;
+ }
+
+public:
+#ifdef ACTOR_DEBUG
+//path debug - use with care
+ struct DebugPoint {
+ Point point;
+ byte color;
+ };
+ DebugPoint *_debugPoints;
+ int _debugPointsCount;
+ int _debugPointsAlloced;
+ void addDebugPoint(const Point &point, byte color) {
+ if (_debugPointsCount + 1 > _debugPointsAlloced) {
+ _debugPointsAlloced += 1000;
+ _debugPoints = (DebugPoint*) realloc(_debugPoints, _debugPointsAlloced * sizeof(*_debugPoints));
+ }
+ _debugPoints[_debugPointsCount].color = color;
+ _debugPoints[_debugPointsCount++].point = point;
+ }
+#endif
+};
+
+inline int16 quickDistance(const Point &point1, const Point &point2) {
+ Point delta;
+ delta.x = ABS(point1.x - point2.x) / 2;
+ delta.y = ABS(point1.y - point2.y);
+ return ((delta.x < delta.y) ? (delta.y + delta.x / 2) : (delta.x + delta.y / 2));
+}
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/animation.cpp b/engines/saga/animation.cpp
new file mode 100644
index 0000000000..016070dc50
--- /dev/null
+++ b/engines/saga/animation.cpp
@@ -0,0 +1,720 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Background animation management module
+#include "saga/saga.h"
+#include "saga/gfx.h"
+
+#include "saga/console.h"
+#include "saga/events.h"
+#include "saga/interface.h"
+#include "saga/render.h"
+#include "saga/rscfile.h"
+#include "saga/scene.h"
+
+#include "saga/animation.h"
+
+namespace Saga {
+
+Anim::Anim(SagaEngine *vm) : _vm(vm) {
+ uint16 i;
+
+ _cutawayList = NULL;
+ _cutawayListLength = 0;
+ _cutawayActive = false;
+
+ for (i = 0; i < MAX_ANIMATIONS; i++)
+ _animations[i] = NULL;
+
+ for (i = 0; i < ARRAYSIZE(_cutawayAnimations); i++)
+ _cutawayAnimations[i] = NULL;
+}
+
+Anim::~Anim(void) {
+ reset();
+}
+
+void Anim::loadCutawayList(const byte *resourcePointer, size_t resourceLength) {
+ free(_cutawayList);
+ _cutawayListLength = resourceLength / 8;
+ _cutawayList = (Cutaway *)malloc(_cutawayListLength * sizeof(Cutaway));
+
+ MemoryReadStream cutawayS(resourcePointer, resourceLength);
+
+ for (int i = 0; i < _cutawayListLength; i++) {
+ _cutawayList[i].backgroundResourceId = cutawayS.readUint16LE();
+ _cutawayList[i].animResourceId = cutawayS.readUint16LE();
+ _cutawayList[i].cycles = cutawayS.readSint16LE();
+ _cutawayList[i].frameRate = cutawayS.readSint16LE();
+ }
+}
+
+void Anim::freeCutawayList(void) {
+ free(_cutawayList);
+ _cutawayList = NULL;
+ _cutawayListLength = 0;
+}
+
+void Anim::playCutaway(int cut, bool fade) {
+ debug(0, "playCutaway(%d, %d)", cut, fade);
+
+ if (fade) {
+ // TODO: Fade down. Is this blocking or non-blocking?
+ }
+
+ if (!_cutawayActive) {
+ _vm->_gfx->showCursor(false);
+ _vm->_interface->setStatusText("");
+ _vm->_interface->setSaveReminderState(0);
+ _vm->_interface->rememberMode();
+ _vm->_interface->setMode(kPanelCutaway);
+ _cutawayActive = true;
+ }
+
+ // Set the initial background and palette for the cutaway
+
+ ResourceContext *context = _vm->_resource->getContext(GAME_RESOURCEFILE);
+
+ byte *resourceData;
+ size_t resourceDataLength;
+
+ _vm->_resource->loadResource(context, _cutawayList[cut].backgroundResourceId, resourceData, resourceDataLength);
+
+ byte *buf;
+ size_t buflen;
+ int width;
+ int height;
+
+ _vm->decodeBGImage(resourceData, resourceDataLength, &buf, &buflen, &width, &height);
+
+ const PalEntry *palette = (const PalEntry *)_vm->getImagePal(resourceData, resourceDataLength);
+
+ Surface *bgSurface = _vm->_render->getBackGroundSurface();
+ const Rect rect(width, height);
+
+ bgSurface->blit(rect, buf);
+ _vm->_gfx->setPalette(palette);
+
+ free(buf);
+ free(resourceData);
+
+ // Play the animation
+
+ int cutawaySlot = -1;
+
+ for (int i = 0; i < ARRAYSIZE(_cutawayAnimations); i++) {
+ if (!_cutawayAnimations[i]) {
+ cutawaySlot = i;
+ } else if (_cutawayAnimations[i]->state == ANIM_PAUSE) {
+ delete _cutawayAnimations[i];
+ _cutawayAnimations[i] = NULL;
+ cutawaySlot = i;
+ } else if (_cutawayAnimations[i]->state == ANIM_PLAYING) {
+ _cutawayAnimations[i]->state = ANIM_PAUSE;
+ }
+ }
+
+ if (cutawaySlot == -1) {
+ warning("Could not allocate cutaway animation slot");
+ return;
+ }
+
+ _vm->_resource->loadResource(context, _cutawayList[cut].animResourceId, resourceData, resourceDataLength);
+
+ load(MAX_ANIMATIONS + cutawaySlot, resourceData, resourceDataLength);
+
+ free(resourceData);
+
+ setCycles(MAX_ANIMATIONS + cutawaySlot, _cutawayList[cut].cycles);
+ setFrameTime(MAX_ANIMATIONS + cutawaySlot, 1000 / _cutawayList[cut].frameRate);
+ play(MAX_ANIMATIONS + cutawaySlot, 0);
+}
+
+void Anim::endCutaway(void) {
+ // I believe this is called by scripts after running one cutaway. At
+ // this time, nothing needs to be done here.
+
+ debug(0, "endCutaway()");
+}
+
+void Anim::returnFromCutaway(void) {
+ // I believe this is called by scripts after running a cutaway to
+ // ensure that we return to the scene as if nothing had happened. It's
+ // not called by the IHNM intro, presumably because there is no old
+ // scene to return to.
+
+ debug(0, "returnFromCutaway()");
+
+ if (_cutawayActive) {
+ // Note that clearCutaway() sets _cutawayActive to false.
+ clearCutaway();
+
+ // TODO: Handle fade up, if we previously faded down
+
+ // TODO: Restore the scene
+
+ // TODO: Restore the animations
+
+ for (int i = 0; i < MAX_ANIMATIONS; i++) {
+ if (_animations[i] && _animations[i]->state == ANIM_PLAYING) {
+ resume(i, 0);
+ }
+ }
+ }
+}
+
+void Anim::clearCutaway(void) {
+ debug(0, "clearCutaway()");
+
+ if (_cutawayActive) {
+ _cutawayActive = false;
+
+ for (int i = 0; i < ARRAYSIZE(_cutawayAnimations); i++) {
+ delete _cutawayAnimations[i];
+ _cutawayAnimations[i] = NULL;
+ }
+
+ _vm->_interface->restoreMode();
+ _vm->_gfx->showCursor(true);
+ }
+}
+
+void Anim::load(uint16 animId, const byte *animResourceData, size_t animResourceLength) {
+ AnimationData *anim;
+ uint16 temp;
+
+ if (animId >= MAX_ANIMATIONS) {
+ if (animId >= MAX_ANIMATIONS + ARRAYSIZE(_cutawayAnimations))
+ error("Anim::load could not find unused animation slot");
+ anim = _cutawayAnimations[animId - MAX_ANIMATIONS] = new AnimationData(animResourceData, animResourceLength);
+ } else
+ anim = _animations[animId] = new AnimationData(animResourceData, animResourceLength);
+
+ MemoryReadStreamEndian headerReadS(anim->resourceData, anim->resourceLength, _vm->isBigEndian());
+ anim->magic = headerReadS.readUint16LE(); // cause ALWAYS LE
+ anim->screenWidth = headerReadS.readUint16();
+ anim->screenHeight = headerReadS.readUint16();
+
+ anim->unknown06 = headerReadS.readByte();
+ anim->unknown07 = headerReadS.readByte();
+ anim->maxFrame = headerReadS.readByte() - 1;
+ anim->loopFrame = headerReadS.readByte() - 1;
+ temp = headerReadS.readUint16BE();
+ anim->start = headerReadS.pos();
+ if (temp == (uint16)(-1)) {
+ temp = 0;
+ }
+ anim->start += temp;
+
+ // Cache frame offsets
+ anim->frameOffsets = (size_t *)malloc((anim->maxFrame + 1) * sizeof(*anim->frameOffsets));
+ if (anim->frameOffsets == NULL) {
+ memoryError("Anim::load");
+ }
+
+ fillFrameOffsets(anim);
+
+ /* char s[200];
+ sprintf(s, "d:\\anim%i",animId);
+ long flen=anim->resourceLength;
+ char *buf=(char*)anim->resourceData;
+ FILE*f;
+ f=fopen(s,"wb");
+ for(long i=0;i<flen;i++)
+ fputc(buf[i],f);
+ fclose(f);*/
+
+ // Set animation data
+ anim->currentFrame = 0;
+ anim->completed = 0;
+ anim->cycles = anim->maxFrame;
+
+ anim->frameTime = DEFAULT_FRAME_TIME;
+ anim->flags = ANIM_FLAG_NONE;
+ anim->linkId = -1;
+ anim->state = ANIM_PAUSE;
+}
+
+void Anim::link(int16 animId1, int16 animId2) {
+ AnimationData *anim1;
+ AnimationData *anim2;
+
+ anim1 = getAnimation(animId1);
+
+ anim1->linkId = animId2;
+
+ if (animId2 == -1) {
+ return;
+ }
+
+ anim2 = getAnimation(animId2);
+ anim2->frameTime = anim1->frameTime;
+}
+
+void Anim::setCycles(uint16 animId, int cycles) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ anim->cycles = cycles;
+}
+
+void Anim::play(uint16 animId, int vectorTime, bool playing) {
+ Event event;
+ Surface *backGroundSurface;
+
+ byte *displayBuffer;
+
+ uint16 frame;
+ int frameTime;
+
+ AnimationData *anim;
+ AnimationData *linkAnim;
+
+ if (animId > MAX_ANIMATIONS && !_cutawayActive)
+ return;
+
+ if (animId < MAX_ANIMATIONS && _cutawayActive)
+ return;
+
+ anim = getAnimation(animId);
+
+ backGroundSurface = _vm->_render->getBackGroundSurface();
+ displayBuffer = (byte*)backGroundSurface->pixels;
+
+ if (playing) {
+ anim->state = ANIM_PLAYING;
+ }
+
+ if (anim->state == ANIM_PAUSE) {
+ return;
+ }
+
+ if (anim->completed < anim->cycles) {
+ frame = anim->currentFrame;
+ // FIXME: if start > 0, then this works incorrectly
+ decodeFrame(anim, anim->frameOffsets[frame], displayBuffer, _vm->getDisplayWidth() * _vm->getDisplayHeight());
+
+ anim->currentFrame++;
+ if (anim->completed != 65535) {
+ anim->completed++;
+ }
+
+ if (anim->currentFrame > anim->maxFrame) {
+ anim->currentFrame = anim->loopFrame;
+
+ if (anim->state == ANIM_STOPPING || anim->currentFrame == -1) {
+ anim->state = ANIM_PAUSE;
+ }
+ }
+ } else {
+ // Animation done playing
+ anim->state = ANIM_PAUSE;
+ if (anim->linkId == -1) {
+ if (anim->flags & ANIM_FLAG_ENDSCENE) {
+ // This animation ends the scene
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = anim->frameTime + vectorTime;
+ _vm->_events->queue(&event);
+ }
+ return;
+ } else {
+ anim->currentFrame = 0;
+ anim->completed = 0;
+ }
+ }
+
+ if (anim->state == ANIM_PAUSE && anim->linkId != -1) {
+ // If this animation has a link, follow it
+ linkAnim = getAnimation(anim->linkId);
+
+ debug(5, "Animation ended going to %d", anim->linkId);
+ linkAnim->state = ANIM_PLAYING;
+ animId = anim->linkId;
+ frameTime = 0;
+ } else {
+ frameTime = anim->frameTime + vectorTime;
+ }
+
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventFrame;
+ event.param = animId;
+ event.time = frameTime;
+
+ _vm->_events->queue(&event);
+}
+
+void Anim::stop(uint16 animId) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ anim->state = ANIM_PAUSE;
+}
+
+void Anim::finish(uint16 animId) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ anim->state = ANIM_STOPPING;
+}
+
+void Anim::resume(uint16 animId, int cycles) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ anim->cycles += cycles;
+ play(animId, 0, true);
+}
+
+void Anim::reset() {
+ uint16 i;
+
+ for (i = 0; i < MAX_ANIMATIONS; i++) {
+ if (_animations[i] != NULL) {
+ delete _animations[i];
+ _animations[i] = NULL;
+ }
+ }
+
+ for (i = 0; i < ARRAYSIZE(_cutawayAnimations); i++) {
+ if (_cutawayAnimations[i] != NULL) {
+ delete _cutawayAnimations[i];
+ _cutawayAnimations[i] = NULL;
+ }
+ }
+}
+
+void Anim::setFlag(uint16 animId, uint16 flag) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ anim->flags |= flag;
+}
+
+void Anim::clearFlag(uint16 animId, uint16 flag) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ anim->flags &= ~flag;
+}
+
+void Anim::setFrameTime(uint16 animId, int time) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ anim->frameTime = time;
+}
+
+int16 Anim::getCurrentFrame(uint16 animId) {
+ AnimationData *anim;
+
+ anim = getAnimation(animId);
+
+ return anim->currentFrame;
+}
+
+void Anim::decodeFrame(AnimationData *anim, size_t frameOffset, byte *buf, size_t bufLength) {
+ byte *writePointer = NULL;
+
+ uint16 xStart = 0;
+ uint16 yStart = 0;
+ uint32 screenWidth;
+ uint32 screenHeight;
+
+ int markByte;
+ byte dataByte;
+ int newRow;
+
+ uint16 controlChar;
+ uint16 paramChar;
+
+ uint16 runcount;
+ int xVector;
+
+ uint16 i;
+ bool longData = isLongData();
+
+ screenWidth = anim->screenWidth;
+ screenHeight = anim->screenHeight;
+
+ if ((screenWidth * screenHeight) > bufLength) {
+ // Buffer argument is too small to hold decoded frame, abort.
+ error("decodeFrame() Buffer size inadequate");
+ }
+
+ MemoryReadStream readS(anim->resourceData + frameOffset, anim->resourceLength - frameOffset);
+
+
+#if 1
+#define VALIDATE_WRITE_POINTER \
+ if ((writePointer < buf) || (writePointer >= (buf + screenWidth * screenHeight))) { \
+ error("VALIDATE_WRITE_POINTER: writePointer=%x buf=%x", writePointer, buf); \
+ }
+#else
+#define VALIDATE_WRITE_POINTER
+#endif
+
+
+ // Begin RLE decompression to output buffer
+ do {
+ markByte = readS.readByte();
+ switch (markByte) {
+ case SAGA_FRAME_START:
+ xStart = readS.readUint16BE();
+ if (longData)
+ yStart = readS.readUint16BE();
+ else
+ yStart = readS.readByte();
+ readS.readByte(); /* Skip pad byte */
+ /*xPos = */readS.readUint16BE();
+ /*yPos = */readS.readUint16BE();
+ /*width = */readS.readUint16BE();
+ /*height = */readS.readUint16BE();
+
+ // Setup write pointer to the draw origin
+ writePointer = (buf + (yStart * screenWidth) + xStart);
+ VALIDATE_WRITE_POINTER;
+ continue;
+ break;
+ case SAGA_FRAME_NOOP: // Does nothing
+ readS.readByte();
+ readS.readByte();
+ readS.readByte();
+ continue;
+ break;
+ case SAGA_FRAME_LONG_UNCOMPRESSED_RUN: // Long Unencoded Run
+ runcount = readS.readSint16BE();
+ for (i = 0; i < runcount; i++) {
+ dataByte = readS.readByte();
+ if (dataByte != 0) {
+ *writePointer = dataByte;
+ }
+ writePointer++;
+ VALIDATE_WRITE_POINTER;
+ }
+ continue;
+ break;
+ case SAGA_FRAME_LONG_COMPRESSED_RUN: // Long encoded run
+ runcount = readS.readSint16BE();
+ dataByte = readS.readByte();
+ for (i = 0; i < runcount; i++) {
+ *writePointer++ = dataByte;
+ VALIDATE_WRITE_POINTER;
+ }
+ continue;
+ break;
+ case SAGA_FRAME_ROW_END: // End of row
+ xVector = readS.readSint16BE();
+
+ if (longData)
+ newRow = readS.readSint16BE();
+ else
+ newRow = readS.readByte();
+
+ // Set write pointer to the new draw origin
+ writePointer = buf + ((yStart + newRow) * screenWidth) + xStart + xVector;
+ VALIDATE_WRITE_POINTER;
+ continue;
+ break;
+ case SAGA_FRAME_REPOSITION: // Reposition command
+ xVector = readS.readSint16BE();
+ writePointer += xVector;
+ VALIDATE_WRITE_POINTER;
+ continue;
+ break;
+ case SAGA_FRAME_END: // End of frame marker
+ return;
+ break;
+ default:
+ break;
+ }
+
+ // Mask all but two high order control bits
+ controlChar = markByte & 0xC0U;
+ paramChar = markByte & 0x3FU;
+ switch (controlChar) {
+ case SAGA_FRAME_EMPTY_RUN: // 1100 0000
+ // Run of empty pixels
+ runcount = paramChar + 1;
+ writePointer += runcount;
+ VALIDATE_WRITE_POINTER;
+ continue;
+ break;
+ case SAGA_FRAME_COMPRESSED_RUN: // 1000 0000
+ // Run of compressed data
+ runcount = paramChar + 1;
+ dataByte = readS.readByte();
+ for (i = 0; i < runcount; i++) {
+ *writePointer++ = dataByte;
+ VALIDATE_WRITE_POINTER;
+ }
+ continue;
+ break;
+ case SAGA_FRAME_UNCOMPRESSED_RUN: // 0100 0000
+ // Uncompressed run
+ runcount = paramChar + 1;
+ for (i = 0; i < runcount; i++) {
+ dataByte = readS.readByte();
+ if (dataByte != 0) {
+ *writePointer = dataByte;
+ }
+ writePointer++;
+ VALIDATE_WRITE_POINTER;
+ }
+ continue;
+ break;
+ default:
+ // Unknown marker found - abort
+ error("decodeFrame() Invalid RLE marker encountered");
+ break;
+ }
+ } while (1);
+}
+
+void Anim::fillFrameOffsets(AnimationData *anim) {
+ uint16 currentFrame;
+ byte markByte;
+ uint16 control;
+ uint16 runcount;
+ int i;
+ bool longData = isLongData();
+
+ MemoryReadStreamEndian readS(anim->resourceData, anim->resourceLength, _vm->isBigEndian());
+
+ readS.seek(12);
+
+ readS._bigEndian = !_vm->isBigEndian(); // RLE has inversion BE<>LE
+
+ for (currentFrame = 0; currentFrame <= anim->maxFrame; currentFrame++) {
+ anim->frameOffsets[currentFrame] = readS.pos();
+
+ // For some strange reason, the animation header is in little
+ // endian format, but the actual RLE encoded frame data,
+ // including the frame header, is in big endian format. */
+ do {
+ markByte = readS.readByte();
+// debug(7, "_pos=%x currentFrame=%i markByte=%x", readS.pos(), currentFrame, markByte);
+
+ switch (markByte) {
+ case SAGA_FRAME_START: // Start of frame
+ // skip header
+ if (longData) {
+ readS.seek(13, SEEK_CUR);
+ } else {
+ readS.seek(12, SEEK_CUR);
+ }
+ continue;
+ break;
+
+ case SAGA_FRAME_END: // End of frame marker
+ continue;
+ break;
+ case SAGA_FRAME_REPOSITION: // Reposition command
+ readS.readSint16BE();
+ continue;
+ break;
+ case SAGA_FRAME_ROW_END: // End of row marker
+ readS.readSint16BE();
+ if (longData)
+ readS.readSint16BE();
+ else
+ readS.readByte();
+ continue;
+ break;
+ case SAGA_FRAME_LONG_COMPRESSED_RUN: // Long compressed run marker
+ readS.readSint16BE();
+ readS.readByte();
+ continue;
+ break;
+ case SAGA_FRAME_LONG_UNCOMPRESSED_RUN: // (16) 0001 0000
+ // Long Uncompressed Run
+ runcount = readS.readSint16BE();
+ for (i = 0; i < runcount; i++)
+ readS.readByte();
+ continue;
+ break;
+ case SAGA_FRAME_NOOP: // Does nothing
+ readS.readByte();
+ readS.readByte();
+ readS.readByte();
+ continue;
+ break;
+ default:
+ break;
+ }
+
+ // Mask all but two high order (control) bits
+ control = markByte & 0xC0;
+ switch (control) {
+ case SAGA_FRAME_EMPTY_RUN:
+ // Run of empty pixels
+ continue;
+ break;
+ case SAGA_FRAME_COMPRESSED_RUN:
+ // Run of compressed data
+ readS.readByte(); // Skip data byte
+ continue;
+ break;
+ case SAGA_FRAME_UNCOMPRESSED_RUN:
+ // Uncompressed run
+ runcount = (markByte & 0x3f) + 1;
+ for (i = 0; i < runcount; i++)
+ readS.readByte();
+ continue;
+ break;
+ default:
+ error("Encountered unknown RLE marker %i", markByte);
+ break;
+ }
+ } while (markByte != SAGA_FRAME_END);
+ }
+}
+
+void Anim::animInfo() {
+ uint16 animCount;
+ uint16 i;
+
+ animCount = getAnimationCount();
+
+ _vm->_console->DebugPrintf("There are %d animations loaded:\n", animCount);
+
+ for (i = 0; i < MAX_ANIMATIONS; i++) {
+ if (_animations[i] == NULL) {
+ continue;
+ }
+
+ _vm->_console->DebugPrintf("%02d: Frames: %u Flags: %u\n", i, _animations[i]->maxFrame, _animations[i]->flags);
+ }
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/animation.h b/engines/saga/animation.h
new file mode 100644
index 0000000000..191166732a
--- /dev/null
+++ b/engines/saga/animation.h
@@ -0,0 +1,198 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Background animation management module private header
+
+#ifndef SAGA_ANIMATION_H_
+#define SAGA_ANIMATION_H_
+
+#include "saga/stream.h"
+
+namespace Saga {
+
+#define MAX_ANIMATIONS 10
+#define DEFAULT_FRAME_TIME 140
+
+#define SAGA_FRAME_START 0xF
+#define SAGA_FRAME_END 0x3F
+#define SAGA_FRAME_NOOP 0x1F
+#define SAGA_FRAME_REPOSITION 0x30
+#define SAGA_FRAME_ROW_END 0x2F
+#define SAGA_FRAME_LONG_COMPRESSED_RUN 0x20
+#define SAGA_FRAME_LONG_UNCOMPRESSED_RUN 0x10
+#define SAGA_FRAME_COMPRESSED_RUN 0x80
+#define SAGA_FRAME_UNCOMPRESSED_RUN 0x40
+#define SAGA_FRAME_EMPTY_RUN 0xC0
+
+enum AnimationState {
+ ANIM_PLAYING = 0x01,
+ ANIM_PAUSE = 0x02,
+ ANIM_STOPPING = 0x03
+};
+
+enum AnimationFlags {
+ ANIM_FLAG_NONE = 0x00,
+ ANIM_FLAG_ENDSCENE = 0x01 // When animation ends, dispatch scene end event
+};
+
+// Cutaway info array member. Cutaways are basically animations with a really
+// bad attitude.
+struct Cutaway {
+ uint16 backgroundResourceId;
+ uint16 animResourceId;
+ int16 cycles;
+ int16 frameRate;
+};
+
+// Animation info array member
+struct AnimationData {
+ byte *resourceData;
+ size_t resourceLength;
+
+ uint16 magic;
+
+ uint16 screenWidth;
+ uint16 screenHeight;
+
+ byte unknown06;
+ byte unknown07;
+
+ int16 maxFrame;
+ int16 loopFrame;
+
+ int16 start;
+
+ int16 currentFrame;
+ size_t *frameOffsets;
+
+ uint16 completed;
+ uint16 cycles;
+
+ int frameTime;
+
+ AnimationState state;
+ int16 linkId;
+ uint16 flags;
+
+ AnimationData(const byte *animResourceData, size_t animResourceLength) {
+ memset(this, 0, sizeof(*this));
+ resourceLength = animResourceLength;
+ resourceData = (byte*)malloc(animResourceLength);
+ memcpy(resourceData, animResourceData, animResourceLength);
+ }
+ ~AnimationData() {
+ free(frameOffsets);
+ free(resourceData);
+ }
+};
+
+class Anim {
+public:
+ Anim(SagaEngine *vm);
+ ~Anim(void);
+
+ void loadCutawayList(const byte *resourcePointer, size_t resourceLength);
+ void freeCutawayList(void);
+ void playCutaway(int cut, bool fade);
+ void endCutaway(void);
+ void returnFromCutaway(void);
+ void clearCutaway(void);
+
+ void load(uint16 animId, const byte *animResourceData, size_t animResourceLength);
+ void freeId(uint16 animId);
+ void play(uint16 animId, int vectorTime, bool playing = true);
+ void link(int16 animId1, int16 animId2);
+ void setFlag(uint16 animId, uint16 flag);
+ void clearFlag(uint16 animId, uint16 flag);
+ void setFrameTime(uint16 animId, int time);
+ void reset(void);
+ void animInfo(void);
+ void setCycles(uint16 animId, int cycles);
+ void stop(uint16 animId);
+ void finish(uint16 animId);
+ void resume(uint16 animId, int cycles);
+ int16 getCurrentFrame(uint16 animId);
+ bool hasCutaway(void) {
+ return _cutawayActive;
+ }
+ bool hasAnimation(uint16 animId) {
+ if (animId >= MAX_ANIMATIONS) {
+ if (animId < MAX_ANIMATIONS + ARRAYSIZE(_cutawayAnimations))
+ return (_cutawayAnimations[animId - MAX_ANIMATIONS] != NULL);
+ return false;
+ }
+ return (_animations[animId] != NULL);
+ }
+private:
+ void decodeFrame(AnimationData *anim, size_t frameOffset, byte *buf, size_t bufLength);
+ void fillFrameOffsets(AnimationData *anim);
+
+ void validateAnimationId(uint16 animId) {
+ if (animId >= MAX_ANIMATIONS) {
+ if (animId >= MAX_ANIMATIONS + ARRAYSIZE(_cutawayAnimations))
+ error("validateAnimationId: animId out of range");
+ if (_cutawayAnimations[animId - MAX_ANIMATIONS] == NULL) {
+ error("validateAnimationId: animId=%i unassigned", animId);
+ }
+ }
+ if (_animations[animId] == NULL) {
+ error("validateAnimationId: animId=%i unassigned", animId);
+ }
+ }
+
+ bool isLongData() const {
+ if ((_vm->getGameType() == GType_ITE) && (_vm->getPlatform() != Common::kPlatformMacintosh)) {
+ return false;
+ }
+ return true;
+ }
+
+ AnimationData* getAnimation(uint16 animId) {
+ validateAnimationId(animId);
+ if (animId > MAX_ANIMATIONS)
+ return _cutawayAnimations[animId - MAX_ANIMATIONS];
+ return _animations[animId];
+ }
+
+ uint16 getAnimationCount() const {
+ uint16 i = 0;
+ for (; i < MAX_ANIMATIONS; i++) {
+ if (_animations[i] == NULL) {
+ break;
+ }
+ }
+ return i;
+ }
+
+ SagaEngine *_vm;
+ AnimationData *_animations[MAX_ANIMATIONS];
+ AnimationData *_cutawayAnimations[2];
+ Cutaway *_cutawayList;
+ int _cutawayListLength;
+ bool _cutawayActive;
+};
+
+} // End of namespace Saga
+
+#endif /* ANIMATION_H_ */
diff --git a/engines/saga/console.cpp b/engines/saga/console.cpp
new file mode 100644
index 0000000000..3455c8c1c3
--- /dev/null
+++ b/engines/saga/console.cpp
@@ -0,0 +1,158 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Console module
+
+#include "saga/saga.h"
+#include "saga/actor.h"
+#include "saga/animation.h"
+#include "saga/scene.h"
+#include "saga/script.h"
+
+#include "saga/console.h"
+
+#include "common/debugger.cpp"
+
+namespace Saga {
+
+Console::Console(SagaEngine *vm) : Common::Debugger<Console>() {
+ _vm = vm;
+
+ DCmd_Register("continue", &Console::Cmd_Exit);
+ DCmd_Register("exit", &Console::Cmd_Exit);
+ DCmd_Register("quit", &Console::Cmd_Exit);
+ DCmd_Register("help", &Console::Cmd_Help);
+
+ // CVAR_Register_I(&_soundEnabled, "sound", NULL, CVAR_CFG, 0, 1);
+ // CVAR_Register_I(&_musicEnabled, "music", NULL, CVAR_CFG, 0, 1);
+
+ // Actor commands
+ DCmd_Register("actor_walk_to", &Console::cmdActorWalkTo);
+
+ // Animation commands
+ DCmd_Register("anim_info", &Console::Cmd_AnimInfo);
+
+ // Game stuff
+
+#if 0
+ // Register "g_language" cfg cvar
+ strncpy(GameModule.game_language, "us", MAXPATH);
+
+ CVAR_Register_S(GameModule.game_language, "g_language", NULL, CVAR_CFG, GAME_LANGSTR_LIMIT);
+
+ // Register "g_skipintro" cfg cvar
+ CVAR_Register_I(&GameModule.g_skipintro, "g_skipintro", NULL, CVAR_CFG, 0, 1);
+#endif
+
+ // Scene commands
+ DCmd_Register("scene_change", &Console::cmdSceneChange);
+ DCmd_Register("action_map_info", &Console::cmdActionMapInfo);
+ DCmd_Register("object_map_info", &Console::cmdObjectMapInfo);
+}
+
+Console::~Console() {
+}
+
+void Console::preEnter() {
+}
+
+void Console::postEnter() {
+}
+
+bool Console::Cmd_Exit(int argc, const char **argv) {
+ _detach_now = true;
+ return false;
+}
+
+bool Console::Cmd_Help(int argc, const char **argv) {
+ // console normally has 39 line width
+ // wrap around nicely
+ int width = 0, size, i;
+
+ DebugPrintf("Commands are:\n");
+ for (i = 0 ; i < _dcmd_count ; i++) {
+ size = strlen(_dcmds[i].name) + 1;
+
+ if ((width + size) >= 39) {
+ DebugPrintf("\n");
+ width = size;
+ } else
+ width += size;
+
+ DebugPrintf("%s ", _dcmds[i].name);
+ }
+
+ width = 0;
+
+ DebugPrintf("\n\nVariables are:\n");
+ for (i = 0 ; i < _dvar_count ; i++) {
+ size = strlen(_dvars[i].name) + 1;
+
+ if ((width + size) >= 39) {
+ DebugPrintf("\n");
+ width = size;
+ } else
+ width += size;
+
+ DebugPrintf("%s ", _dvars[i].name);
+ }
+
+ DebugPrintf("\n");
+
+ return true;
+}
+
+bool Console::cmdActorWalkTo(int argc, const char **argv) {
+ if (argc != 4)
+ DebugPrintf("Usage: %s <Actor id> <lx> <ly>\n", argv[0]);
+ else
+ _vm->_actor->cmdActorWalkTo(argc, argv);
+ return true;
+}
+
+
+bool Console::Cmd_AnimInfo(int argc, const char **argv) {
+ _vm->_anim->animInfo();
+ return true;
+}
+
+bool Console::cmdSceneChange(int argc, const char **argv) {
+ if (argc != 2)
+ DebugPrintf("Usage: %s <Scene number>\n", argv[0]);
+ else
+ _vm->_scene->cmdSceneChange(argc, argv);
+ return true;
+}
+
+bool Console::cmdActionMapInfo(int argc, const char **argv) {
+ _vm->_scene->cmdActionMapInfo();
+ return true;
+}
+
+bool Console::cmdObjectMapInfo(int argc, const char **argv) {
+ _vm->_scene->cmdObjectMapInfo();
+ return true;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/console.h b/engines/saga/console.h
new file mode 100644
index 0000000000..c93373960f
--- /dev/null
+++ b/engines/saga/console.h
@@ -0,0 +1,62 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+ // Console module header file
+
+#ifndef SAGA_CONSOLE_H_
+#define SAGA_CONSOLE_H_
+
+#include "common/debugger.h"
+
+namespace Saga {
+
+class Console : public Common::Debugger<Console> {
+public:
+ Console(SagaEngine *vm);
+ virtual ~Console(void);
+
+protected:
+ virtual void preEnter();
+ virtual void postEnter();
+
+private:
+ bool Cmd_Exit(int argc, const char **argv);
+ bool Cmd_Help(int argc, const char **argv);
+
+ bool cmdActorWalkTo(int argc, const char **argv);
+
+ bool Cmd_AnimInfo(int argc, const char **argv);
+
+ bool cmdSceneChange(int argc, const char **argv);
+ bool cmdActionMapInfo(int argc, const char **argv);
+ bool cmdObjectMapInfo(int argc, const char **argv);
+
+
+private:
+ SagaEngine *_vm;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/events.cpp b/engines/saga/events.cpp
new file mode 100644
index 0000000000..0d1da65efb
--- /dev/null
+++ b/engines/saga/events.cpp
@@ -0,0 +1,590 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Event management module
+
+#include "saga/saga.h"
+#include "saga/gfx.h"
+
+#include "saga/animation.h"
+#include "saga/console.h"
+#include "saga/scene.h"
+#include "saga/interface.h"
+#include "saga/palanim.h"
+#include "saga/render.h"
+#include "saga/sndres.h"
+#include "saga/music.h"
+#include "saga/actor.h"
+
+#include "saga/events.h"
+
+namespace Saga {
+
+Events::Events(SagaEngine *vm) : _vm(vm), _initialized(false) {
+ debug(8, "Initializing event subsystem...");
+ _initialized = true;
+}
+
+Events::~Events(void) {
+ debug(8, "Shutting down event subsystem...");
+ freeList();
+}
+
+// Function to process event list once per frame.
+// First advances event times, then processes each event with the appropriate
+// handler depending on the type of event.
+int Events::handleEvents(long msec) {
+ Event *event_p;
+
+ long delta_time;
+ int result;
+
+ // Advance event times
+ processEventTime(msec);
+
+ // Process each event in list
+ for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
+ event_p = (Event *)eventi.operator->();
+
+ // Call the appropriate event handler for the specific event type
+ switch (event_p->type) {
+
+ case kEvTOneshot:
+ result = handleOneShot(event_p);
+ break;
+
+ case kEvTContinuous:
+ result = handleContinuous(event_p);
+ break;
+
+ case kEvTInterval:
+ result = handleInterval(event_p);
+ break;
+
+ case kEvTImmediate:
+ result = handleImmediate(event_p);
+ break;
+
+ default:
+ result = kEvStInvalidCode;
+ warning("Invalid event code encountered");
+ break;
+ }
+
+ // Process the event appropriately based on result code from
+ // handler
+ if ((result == kEvStDelete) || (result == kEvStInvalidCode)) {
+ // If there is no event chain, delete the base event.
+ if (event_p->chain == NULL) {
+ eventi = _eventList.eraseAndPrev(eventi);
+ } else {
+ // If there is an event chain present, move the next event
+ // in the chain up, adjust it by the previous delta time,
+ // and reprocess the event
+ delta_time = event_p->time;
+ Event *from_chain = event_p->chain;
+ memcpy(event_p, from_chain, sizeof(*event_p));
+ free(from_chain);
+
+ event_p->time += delta_time;
+ --eventi;
+ }
+ } else if (result == kEvStBreak) {
+ break;
+ }
+ }
+
+ return SUCCESS;
+}
+
+int Events::handleContinuous(Event *event) {
+ double event_pc = 0.0; // Event completion percentage
+ int event_done = 0;
+
+ Surface *backGroundSurface;
+ BGInfo bgInfo;
+ Rect rect;
+ if(event->duration != 0) {
+ event_pc = ((double)event->duration - event->time) / event->duration;
+ } else {
+ event_pc = 1.0;
+ }
+
+ if (event_pc >= 1.0) {
+ // Cap percentage to 100
+ event_pc = 1.0;
+ event_done = 1;
+ }
+
+ if (event_pc < 0.0) {
+ // Event not signaled, skip it
+ return kEvStContinue;
+ } else if (!(event->code & kEvFSignaled)) {
+ // Signal event
+ event->code |= kEvFSignaled;
+ event_pc = 0.0;
+ }
+
+ switch (event->code & EVENT_MASK) {
+ case kPalEvent:
+ switch (event->op) {
+ case kEventBlackToPal:
+ _vm->_gfx->blackToPal((PalEntry *)event->data, event_pc);
+ break;
+
+ case kEventPalToBlack:
+ _vm->_gfx->palToBlack((PalEntry *)event->data, event_pc);
+ break;
+ default:
+ break;
+ }
+ break;
+ case kTransitionEvent:
+ switch (event->op) {
+ case kEventDissolve:
+ backGroundSurface = _vm->_render->getBackGroundSurface();
+ _vm->_scene->getBGInfo(bgInfo);
+ rect.left = rect.top = 0;
+ rect.right = bgInfo.bounds.width();
+ rect.bottom = bgInfo.bounds.height();
+ backGroundSurface->transitionDissolve(bgInfo.buffer, rect, 0, event_pc);
+ break;
+ case kEventDissolveBGMask:
+ // we dissolve it centered.
+ // set flag of Dissolve to 1. It is a hack to simulate zero masking.
+ int w, h;
+ byte *maskBuffer;
+ size_t len;
+
+ backGroundSurface = _vm->_render->getBackGroundSurface();
+ _vm->_scene->getBGMaskInfo(w, h, maskBuffer, len);
+ rect.left = (_vm->getDisplayWidth() - w) / 2;
+ rect.top = (_vm->getDisplayHeight() - h) / 2;
+ rect.setWidth(w);
+ rect.setHeight(h);
+
+ backGroundSurface->transitionDissolve( maskBuffer, rect, 1, event_pc);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+
+ }
+
+ if (event_done) {
+ return kEvStDelete;
+ }
+
+ return kEvStContinue;
+}
+
+int Events::handleImmediate(Event *event) {
+ double event_pc = 0.0; // Event completion percentage
+ bool event_done = false;
+
+ // Duration might be 0 so dont do division then
+ if(event->duration != 0) {
+ event_pc = ((double)event->duration - event->time) / event->duration;
+ } else {
+ // Just make sure that event_pc is 1.0 so event_done is true
+ event_pc = 1.0;
+ }
+
+ if (event_pc >= 1.0) {
+ // Cap percentage to 100
+ event_pc = 1.0;
+ event_done = true;
+ }
+
+ if (event_pc < 0.0) {
+ // Event not signaled, skip it
+ return kEvStBreak;
+ } else if (!(event->code & kEvFSignaled)) {
+ // Signal event
+ event->code |= kEvFSignaled;
+ event_pc = 0.0;
+ }
+
+ switch (event->code & EVENT_MASK) {
+ case kPalEvent:
+ switch (event->op) {
+ case kEventBlackToPal:
+ _vm->_gfx->blackToPal((PalEntry *)event->data, event_pc);
+ break;
+
+ case kEventPalToBlack:
+ _vm->_gfx->palToBlack((PalEntry *)event->data, event_pc);
+ break;
+ default:
+ break;
+ }
+ break;
+ case kScriptEvent:
+ case kBgEvent:
+ case kInterfaceEvent:
+ handleOneShot(event);
+ event_done = true;
+ break;
+ default:
+ break;
+
+ }
+
+ if (event_done) {
+ return kEvStDelete;
+ }
+
+ return kEvStBreak;
+}
+
+int Events::handleOneShot(Event *event) {
+ Surface *backBuffer;
+ ScriptThread *sthread;
+ Rect rect;
+
+
+ if (event->time > 0) {
+ return kEvStContinue;
+ }
+
+ // Event has been signaled
+
+ switch (event->code & EVENT_MASK) {
+ case kTextEvent:
+ switch (event->op) {
+ case kEventDisplay:
+ ((TextListEntry *)event->data)->display = true;
+ break;
+ case kEventRemove:
+ _vm->_scene->_textList.remove((TextListEntry *)event->data);
+ break;
+ default:
+ break;
+ }
+
+ break;
+ case kSoundEvent:
+ _vm->_sound->stopSound();
+ if (event->op == kEventPlay)
+ _vm->_sndRes->playSound(event->param, event->param2, event->param3 != 0);
+ break;
+ case kVoiceEvent:
+ _vm->_sndRes->playVoice(event->param);
+ break;
+ case kMusicEvent:
+ _vm->_music->stop();
+ if (event->op == kEventPlay)
+ _vm->_music->play(event->param, (MusicFlags)event->param2);
+ break;
+ case kBgEvent:
+ {
+ Surface *backGroundSurface;
+ BGInfo bgInfo;
+
+ if (!(_vm->_scene->getFlags() & kSceneFlagISO)) {
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+ backGroundSurface = _vm->_render->getBackGroundSurface();
+ _vm->_scene->getBGInfo(bgInfo);
+
+ backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer);
+
+ // If it is inset scene then draw black border
+ if (bgInfo.bounds.width() < _vm->getDisplayWidth() || bgInfo.bounds.height() < _vm->_scene->getHeight()) {
+ Common::Rect rect1(2, bgInfo.bounds.height() + 4);
+ Common::Rect rect2(bgInfo.bounds.width() + 4, 2);
+ Common::Rect rect3(2, bgInfo.bounds.height() + 4);
+ Common::Rect rect4(bgInfo.bounds.width() + 4, 2);
+ rect1.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.top - 2);
+ rect2.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.top - 2);
+ rect3.moveTo(bgInfo.bounds.right, bgInfo.bounds.top - 2);
+ rect4.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.bottom);
+
+ backGroundSurface->drawRect(rect1, kITEColorBlack);
+ backGroundSurface->drawRect(rect2, kITEColorBlack);
+ backGroundSurface->drawRect(rect3, kITEColorBlack);
+ backGroundSurface->drawRect(rect4, kITEColorBlack);
+ }
+
+ if (event->param == kEvPSetPalette) {
+ PalEntry *palPointer;
+ _vm->_scene->getBGPal(palPointer);
+ _vm->_gfx->setPalette(palPointer);
+ }
+ }
+ }
+ break;
+ case kAnimEvent:
+ switch (event->op) {
+ case kEventPlay:
+ _vm->_anim->play(event->param, event->time, true);
+ break;
+ case kEventStop:
+ _vm->_anim->stop(event->param);
+ break;
+ case kEventFrame:
+ _vm->_anim->play(event->param, event->time, false);
+ break;
+ case kEventSetFlag:
+ _vm->_anim->setFlag(event->param, event->param2);
+ break;
+ case kEventClearFlag:
+ _vm->_anim->clearFlag(event->param, event->param2);
+ break;
+ default:
+ break;
+ }
+ break;
+ case kSceneEvent:
+ switch (event->op) {
+ case kEventEnd:
+ _vm->_scene->nextScene();
+ return kEvStBreak;
+ break;
+ default:
+ break;
+ }
+ break;
+ case kPalAnimEvent:
+ switch (event->op) {
+ case kEventCycleStart:
+ _vm->_palanim->cycleStart();
+ break;
+ case kEventCycleStep:
+ _vm->_palanim->cycleStep(event->time);
+ break;
+ default:
+ break;
+ }
+ break;
+ case kInterfaceEvent:
+ switch (event->op) {
+ case kEventActivate:
+ _vm->_interface->activate();
+ break;
+ case kEventDeactivate:
+ _vm->_interface->deactivate();
+ break;
+ case kEventSetStatus:
+ _vm->_interface->setStatusText((const char*)event->data);
+ _vm->_interface->drawStatusBar();
+ break;
+ case kEventClearStatus:
+ _vm->_interface->setStatusText("");
+ _vm->_interface->drawStatusBar();
+ break;
+ case kEventSetFadeMode:
+ _vm->_interface->setFadeMode(event->param);
+ break;
+ default:
+ break;
+ }
+ break;
+ case kScriptEvent:
+ switch (event->op) {
+ case kEventExecBlocking:
+ case kEventExecNonBlocking:
+ debug(6, "Exec module number %d script entry number %d", event->param, event->param2);
+
+ sthread = _vm->_script->createThread(event->param, event->param2);
+ if (sthread == NULL) {
+ _vm->_console->DebugPrintf("Thread creation failed.\n");
+ break;
+ }
+
+ sthread->_threadVars[kThreadVarAction] = event->param3;
+ sthread->_threadVars[kThreadVarObject] = event->param4;
+ sthread->_threadVars[kThreadVarWithObject] = event->param5;
+ sthread->_threadVars[kThreadVarActor] = event->param6;
+
+ if (event->op == kEventExecBlocking)
+ _vm->_script->completeThread();
+
+ break;
+ case kEventThreadWake:
+ _vm->_script->wakeUpThreads(event->param);
+ break;
+ }
+ break;
+ case kCursorEvent:
+ switch (event->op) {
+ case kEventShow:
+ _vm->_gfx->showCursor(true);
+ break;
+ case kEventHide:
+ _vm->_gfx->showCursor(false);
+ break;
+ default:
+ break;
+ }
+ break;
+ case kGraphicsEvent:
+ switch (event->op) {
+ case kEventFillRect:
+ rect.top = event->param2;
+ rect.bottom = event->param3;
+ rect.left = event->param4;
+ rect.right = event->param5;
+ ((Surface *)event->data)->drawRect(rect, event->param);
+ break;
+ case kEventSetFlag:
+ _vm->_render->setFlag(event->param);
+ break;
+ case kEventClearFlag:
+ _vm->_render->clearFlag(event->param);
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return kEvStDelete;
+}
+
+int Events::handleInterval(Event *event) {
+ return kEvStDelete;
+}
+
+// Schedules an event in the event list; returns a pointer to the scheduled
+// event suitable for chaining if desired.
+Event *Events::queue(Event *event) {
+ Event *queuedEvent;
+
+ queuedEvent = _eventList.pushBack(*event).operator->();
+ initializeEvent(queuedEvent);
+
+ return queuedEvent;
+}
+
+// Places a 'add_event' on the end of an event chain given by 'head_event'
+// (head_event may be in any position in the event chain)
+Event *Events::chain(Event *headEvent, Event *addEvent) {
+ if (headEvent == NULL) {
+ return queue(addEvent);
+ }
+
+ Event *walkEvent;
+ for (walkEvent = headEvent; walkEvent->chain != NULL; walkEvent = walkEvent->chain) {
+ continue;
+ }
+
+ walkEvent->chain = (Event *)malloc(sizeof(*walkEvent->chain));
+ *walkEvent->chain = *addEvent;
+ initializeEvent(walkEvent->chain);
+
+ return walkEvent->chain;
+}
+
+int Events::initializeEvent(Event *event) {
+ event->chain = NULL;
+ switch (event->type) {
+ case kEvTOneshot:
+ break;
+ case kEvTContinuous:
+ case kEvTImmediate:
+ event->time += event->duration;
+ break;
+ case kEvTInterval:
+ break;
+ default:
+ return FAILURE;
+ break;
+ }
+
+ return SUCCESS;
+}
+
+int Events::clearList() {
+ Event *chain_walk;
+ Event *next_chain;
+ Event *event_p;
+
+ // Walk down event list
+ for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
+ event_p = (Event *)eventi.operator->();
+
+ // Only remove events not marked kEvFNoDestory (engine events)
+ if (!(event_p->code & kEvFNoDestory)) {
+ // Remove any events chained off this one */
+ for (chain_walk = event_p->chain; chain_walk != NULL; chain_walk = next_chain) {
+ next_chain = chain_walk->chain;
+ free(chain_walk);
+ }
+ eventi = _eventList.eraseAndPrev(eventi);
+ }
+ }
+
+ return SUCCESS;
+}
+
+// Removes all events from the list (even kEvFNoDestory)
+int Events::freeList() {
+ Event *chain_walk;
+ Event *next_chain;
+ Event *event_p;
+
+ // Walk down event list
+ EventList::iterator eventi = _eventList.begin();
+ while (eventi != _eventList.end()) {
+ event_p = (Event *)eventi.operator->();
+
+ // Remove any events chained off this one */
+ for (chain_walk = event_p->chain; chain_walk != NULL; chain_walk = next_chain) {
+ next_chain = chain_walk->chain;
+ free(chain_walk);
+ }
+ eventi=_eventList.erase(eventi);
+ }
+
+ return SUCCESS;
+}
+
+// Walks down the event list, updating event times by 'msec'.
+int Events::processEventTime(long msec) {
+ Event *event_p;
+ uint16 event_count = 0;
+
+ for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
+ event_p = (Event *)eventi.operator->();
+
+ event_p->time -= msec;
+ event_count++;
+
+ if (event_p->type == kEvTImmediate)
+ break;
+
+ if (event_count > EVENT_WARNINGCOUNT) {
+ warning("Event list exceeds %u", EVENT_WARNINGCOUNT);
+ }
+ }
+
+ return SUCCESS;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/events.h b/engines/saga/events.h
new file mode 100644
index 0000000000..d89b3d89f5
--- /dev/null
+++ b/engines/saga/events.h
@@ -0,0 +1,179 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Event management module header file
+
+#ifndef SAGA_EVENT_H
+#define SAGA_EVENT_H
+
+#include "saga/list.h"
+
+namespace Saga {
+
+enum EventTypes {
+ kEvTOneshot, // Event takes no time
+ kEvTContinuous, // Event takes time; next event starts immediately
+ kEvTInterval, // Not yet implemented
+ kEvTImmediate // Event takes time; next event starts when event is done
+};
+
+enum EventFlags {
+ kEvFSignaled = 0x8000,
+ kEvFNoDestory = 0x4000
+};
+
+enum EventCodes {
+ kBgEvent = 1,
+ kAnimEvent,
+ kMusicEvent,
+ kVoiceEvent,
+ kSoundEvent,
+ kSceneEvent,
+ kTextEvent,
+ kPalEvent,
+ kPalAnimEvent,
+ kTransitionEvent,
+ kInterfaceEvent,
+ kActorEvent,
+ kScriptEvent,
+ kCursorEvent,
+ kGraphicsEvent
+};
+
+enum EventOps {
+ // INSTANTANEOUS events
+ // BG events
+ kEventDisplay = 1,
+ // ANIM events
+ // kEventPlay = 1, // reused
+ // kEventStop = 2, // reused
+ kEventFrame = 3,
+ kEventSetFlag = 4,
+ kEventClearFlag = 5,
+ // MUISC & SOUND events
+ kEventPlay = 1,
+ kEventStop = 2,
+ // SCENE events
+ kEventEnd = 2,
+ // TEXT events
+ kEventHide = 2,
+ kEventRemove = 3,
+ // PALANIM events
+ kEventCycleStart = 1,
+ kEventCycleStep = 2,
+ // INTERFACE events
+ kEventActivate = 1,
+ kEventDeactivate = 2,
+ kEventSetStatus = 3,
+ kEventClearStatus = 4,
+ kEventSetFadeMode = 5,
+ // ACTOR events
+ kEventMove = 1,
+ // SCRIPT events
+ kEventExecBlocking = 1,
+ kEventExecNonBlocking = 2,
+ kEventThreadWake = 3,
+ // CURSOR events
+ kEventShow = 1,
+ // kEventHide = 2, // reused
+ // GRAPHICS events
+ kEventFillRect = 1,
+ // kEventSetFlag = 4, // reused
+ // kEventClearFlag = 5, // reused
+
+ // CONTINUOUS events
+ // PALETTE events
+ kEventPalToBlack = 1,
+ kEventBlackToPal = 2,
+ // TRANSITION events
+ kEventDissolve = 1,
+ kEventDissolveBGMask = 2
+};
+
+enum EventParams {
+ kEvPNoSetPalette,
+ kEvPSetPalette
+};
+
+struct Event {
+ unsigned int type;
+ unsigned int code; // Event operation category & flags
+ int op; // Event operation
+ long param; // Optional event parameter
+ long param2;
+ long param3;
+ long param4;
+ long param5;
+ long param6;
+ void *data; // Optional event data
+ long time; // Elapsed time until event
+ long duration; // Duration of event
+ long d_reserved;
+
+ Event *chain; // Event chain (For consecutive events)
+ Event() {
+ memset(this, 0, sizeof(*this));
+ }
+};
+
+typedef SortedList<Event> EventList;
+
+#define EVENT_WARNINGCOUNT 1000
+#define EVENT_MASK 0x00FF
+
+enum EventStatusCode {
+ kEvStInvalidCode = 0,
+ kEvStDelete,
+ kEvStContinue,
+ kEvStBreak
+};
+
+class Events {
+ public:
+ Events(SagaEngine *vm);
+ ~Events(void);
+ int handleEvents(long msec);
+ int clearList();
+ int freeList();
+ Event *queue(Event *event);
+ Event *chain(Event *headEvent, Event *addEvent);
+
+ private:
+ int handleContinuous(Event *event);
+ int handleOneShot(Event *event);
+ int handleInterval(Event *event);
+ int handleImmediate(Event *event);
+ int processEventTime(long msec);
+ int initializeEvent(Event *event);
+
+ private:
+ SagaEngine *_vm;
+ bool _initialized;
+
+ EventList _eventList;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp
new file mode 100644
index 0000000000..9a91bf4872
--- /dev/null
+++ b/engines/saga/font.cpp
@@ -0,0 +1,681 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Font management and font drawing module
+#include "saga/saga.h"
+#include "saga/gfx.h"
+#include "saga/rscfile.h"
+
+#include "saga/font.h"
+#include "saga/stream.h"
+
+namespace Saga {
+
+Font::Font(SagaEngine *vm) : _vm(vm), _initialized(false) {
+ int i;
+
+ // Load font module resource context
+
+ assert(_vm->getFontsCount() > 0);
+
+ _fonts = (FontData **)calloc(_vm->getFontsCount(), sizeof(*_fonts));
+ _loadedFonts = 0;
+
+ for (i = 0; i < _vm->getFontsCount(); i++) {
+ loadFont(_vm->getFontDescription(i)->fontResourceId);
+ }
+
+ _initialized = true;
+}
+
+Font::~Font(void) {
+ debug(8, "Font::~Font(): Freeing fonts.");
+ int i;
+
+ for (i = 0 ; i < _loadedFonts ; i++) {
+ if (_fonts[i] != NULL) {
+ free(_fonts[i]->normal.font);
+ free(_fonts[i]->outline.font);
+ }
+
+ free(_fonts[i]);
+ }
+}
+
+
+void Font::loadFont(uint32 fontResourceId) {
+ FontData *font;
+ byte *fontResourcePointer;
+ size_t fontResourceLength;
+ int numBits;
+ int c;
+ ResourceContext *fontContext;
+
+ debug(1, "Font::loadFont(): Reading fontResourceId %d...", fontResourceId);
+
+ fontContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (fontContext == NULL) {
+ error("Font::Font() resource context not found");
+ }
+
+ // Load font resource
+ _vm->_resource->loadResource(fontContext, fontResourceId, fontResourcePointer, fontResourceLength);
+
+ if (fontResourceLength < FONT_DESCSIZE) {
+ error("Font::loadFont() Invalid font length (%i < %i)", fontResourceLength, FONT_DESCSIZE);
+ }
+
+ MemoryReadStreamEndian readS(fontResourcePointer, fontResourceLength, fontContext->isBigEndian);
+
+ // Create new font structure
+ font = (FontData *)malloc(sizeof(*font));
+
+ // Read font header
+ font->normal.header.charHeight = readS.readUint16();
+ font->normal.header.charWidth = readS.readUint16();
+ font->normal.header.rowLength = readS.readUint16();
+
+
+ debug(2, "Character width: %d", font->normal.header.charWidth);
+ debug(2, "Character height: %d", font->normal.header.charHeight);
+ debug(2, "Row padding: %d", font->normal.header.rowLength);
+
+ for (c = 0; c < FONT_CHARCOUNT; c++) {
+ font->normal.fontCharEntry[c].index = readS.readUint16();
+ }
+
+ for (c = 0; c < FONT_CHARCOUNT; c++) {
+ numBits = font->normal.fontCharEntry[c].width = readS.readByte();
+ font->normal.fontCharEntry[c].byteWidth = getByteLen(numBits);
+ }
+
+ for (c = 0; c < FONT_CHARCOUNT; c++) {
+ font->normal.fontCharEntry[c].flag = readS.readByte();
+ }
+
+ for (c = 0; c < FONT_CHARCOUNT; c++) {
+ font->normal.fontCharEntry[c].tracking = readS.readByte();
+ }
+
+ if (readS.pos() != FONT_DESCSIZE) {
+ error("Invalid font resource size.");
+ }
+
+ font->normal.font = (byte*)malloc(fontResourceLength - FONT_DESCSIZE);
+ memcpy(font->normal.font, fontResourcePointer + FONT_DESCSIZE, fontResourceLength - FONT_DESCSIZE);
+
+ free(fontResourcePointer);
+
+
+ // Create outline font style
+ createOutline(font);
+
+ // Set font data
+ _fonts[_loadedFonts++] = font;
+}
+
+void Font::createOutline(FontData *font) {
+ int i;
+ int row;
+ int newByteWidth;
+ int oldByteWidth;
+ int newRowLength = 0;
+ size_t indexOffset = 0;
+ int index;
+ int currentByte;
+ unsigned char *basePointer;
+ unsigned char *srcPointer;
+ unsigned char *destPointer1;
+ unsigned char *destPointer2;
+ unsigned char *destPointer3;
+ unsigned char charRep;
+
+
+ // Populate new font style character data
+ for (i = 0; i < FONT_CHARCOUNT; i++) {
+ newByteWidth = 0;
+ oldByteWidth = 0;
+ index = font->normal.fontCharEntry[i].index;
+ if ((index > 0) || (i == FONT_FIRSTCHAR)) {
+ index += indexOffset;
+ }
+
+ font->outline.fontCharEntry[i].index = index;
+ font->outline.fontCharEntry[i].tracking = font->normal.fontCharEntry[i].tracking;
+ font->outline.fontCharEntry[i].flag = font->normal.fontCharEntry[i].flag;
+
+ if (font->normal.fontCharEntry[i].width != 0) {
+ newByteWidth = getByteLen(font->normal.fontCharEntry[i].width + 2);
+ oldByteWidth = getByteLen(font->normal.fontCharEntry[i].width);
+
+ if (newByteWidth > oldByteWidth) {
+ indexOffset++;
+ }
+ }
+
+ font->outline.fontCharEntry[i].width = font->normal.fontCharEntry[i].width + 2;
+ font->outline.fontCharEntry[i].byteWidth = newByteWidth;
+ newRowLength += newByteWidth;
+ }
+
+ debug(2, "New row length: %d", newRowLength);
+
+ font->outline.header = font->normal.header;
+ font->outline.header.charWidth += 2;
+ font->outline.header.charHeight += 2;
+ font->outline.header.rowLength = newRowLength;
+
+ // Allocate new font representation storage
+ font->outline.font = (unsigned char *)calloc(newRowLength, font->outline.header.charHeight);
+
+
+ // Generate outline font representation
+ for (i = 0; i < FONT_CHARCOUNT; i++) {
+ for (row = 0; row < font->normal.header.charHeight; row++) {
+ for (currentByte = 0; currentByte < font->outline.fontCharEntry[i].byteWidth; currentByte++) {
+ basePointer = font->outline.font + font->outline.fontCharEntry[i].index + currentByte;
+ destPointer1 = basePointer + newRowLength * row;
+ destPointer2 = basePointer + newRowLength * (row + 1);
+ destPointer3 = basePointer + newRowLength * (row + 2);
+ if (currentByte > 0) {
+ // Get last two columns from previous byte
+ srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + (currentByte - 1);
+ charRep = *srcPointer;
+ *destPointer1 |= ((charRep << 6) | (charRep << 7));
+ *destPointer2 |= ((charRep << 6) | (charRep << 7));
+ *destPointer3 |= ((charRep << 6) | (charRep << 7));
+ }
+
+ if (currentByte < font->normal.fontCharEntry[i].byteWidth) {
+ srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + currentByte;
+ charRep = *srcPointer;
+ *destPointer1 |= charRep | (charRep >> 1) | (charRep >> 2);
+ *destPointer2 |= charRep | (charRep >> 1) | (charRep >> 2);
+ *destPointer3 |= charRep | (charRep >> 1) | (charRep >> 2);
+ }
+ }
+ }
+
+ // "Hollow out" character to prevent overdraw
+ for (row = 0; row < font->normal.header.charHeight; row++) {
+ for (currentByte = 0; currentByte < font->outline.fontCharEntry[i].byteWidth; currentByte++) {
+ destPointer2 = font->outline.font + font->outline.header.rowLength * (row + 1) + font->outline.fontCharEntry[i].index + currentByte;
+ if (currentByte > 0) {
+ // Get last two columns from previous byte
+ srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + (currentByte - 1);
+ *destPointer2 &= ((*srcPointer << 7) ^ 0xFFU);
+ }
+
+ if (currentByte < font->normal.fontCharEntry[i].byteWidth) {
+ srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + currentByte;
+ *destPointer2 &= ((*srcPointer >> 1) ^ 0xFFU);
+ }
+ }
+ }
+ }
+}
+
+// Returns the horizontal length in pixels of the graphical representation
+// of at most 'count' characters of the string 'text', taking
+// into account any formatting options specified by 'flags'.
+// If 'count' is 0, all characters of 'test' are counted.
+int Font::getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) {
+ FontData *font;
+ size_t ct;
+ int width = 0;
+ int ch;
+ const byte *txt;
+
+
+ font = getFont(fontId);
+
+ txt = (const byte *) text;
+
+ for (ct = count; *txt && (!count || ct > 0); txt++, ct--) {
+ ch = *txt & 0xFFU;
+ // Translate character
+ ch = _charMap[ch];
+ assert(ch < FONT_CHARCOUNT);
+ width += font->normal.fontCharEntry[ch].tracking;
+ }
+
+ if ((flags & kFontBold) || (flags & kFontOutline)) {
+ width += 1;
+ }
+
+ return width;
+}
+
+
+void Font::draw(FontId fontId, Surface *ds, const char *text, size_t count, const Common::Point &point,
+ int color, int effectColor, FontEffectFlags flags) {
+ FontData *font;
+ Point offsetPoint(point);
+
+ font = getFont(fontId);
+
+ if (flags & kFontOutline) {
+ offsetPoint.x--;
+ offsetPoint.y--;
+ outFont(font->outline, ds, text, count, offsetPoint, effectColor, flags);
+ outFont(font->normal, ds, text, count, point, color, flags);
+ } else if (flags & kFontShadow) {
+ offsetPoint.x--;
+ offsetPoint.y++;
+ outFont(font->normal, ds, text, count, offsetPoint, effectColor, flags);
+ outFont(font->normal, ds, text, count, point, color, flags);
+ } else { // FONT_NORMAL
+ outFont(font->normal, ds, text, count, point, color, flags);
+ }
+}
+
+void Font::outFont(const FontStyle &drawFont, Surface *ds, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags) {
+ const byte *textPointer;
+ byte *c_dataPointer;
+ int c_code;
+ int charRow;
+ Point textPoint(point);
+
+ byte *outputPointer;
+ byte *outputPointer_min;
+ byte *outputPointer_max;
+
+ int row;
+ int rowLimit;
+
+ int c_byte_len;
+ int c_byte;
+ int c_bit;
+ int ct;
+
+ if ((point.x > ds->w) || (point.y > ds->h)) {
+ // Output string can't be visible
+ return;
+ }
+
+ textPointer = (const byte *)text;
+ ct = count;
+
+ // Draw string one character at a time, maximum of 'draw_str'_ct
+ // characters, or no limit if 'draw_str_ct' is 0
+ for (; *textPointer && (!count || ct); textPointer++, ct--) {
+ c_code = *textPointer & 0xFFU;
+
+ // Translate character
+ if (!(flags & kFontDontmap))
+ c_code = _charMap[c_code];
+ assert(c_code < FONT_CHARCOUNT);
+
+ // Check if character is defined
+ if ((drawFont.fontCharEntry[c_code].index == 0) && (c_code != FONT_FIRSTCHAR)) {
+#if FONT_SHOWUNDEFINED
+ if (c_code == FONT_CH_SPACE) {
+ textPoint.x += drawFont.fontCharEntry[c_code].tracking;
+ continue;
+ }
+ c_code = FONT_CH_QMARK;
+#else
+ // Character code is not defined, but advance tracking
+ // ( Not defined if offset is 0, except for 33 ('!') which
+ // is defined )
+ textPoint.x += drawFont.fontCharEntry[c_code].tracking;
+ continue;
+#endif
+ }
+
+ // Get length of character in bytes
+ c_byte_len = ((drawFont.fontCharEntry[c_code].width - 1) / 8) + 1;
+ rowLimit = (ds->h < (textPoint.y + drawFont.header.charHeight)) ? ds->h : textPoint.y + drawFont.header.charHeight;
+ charRow = 0;
+
+ for (row = textPoint.y; row < rowLimit; row++, charRow++) {
+ // Clip negative rows */
+ if (row < 0) {
+ continue;
+ }
+
+ outputPointer = (byte *)ds->pixels + (ds->pitch * row) + textPoint.x;
+ outputPointer_min = (byte *)ds->pixels + (ds->pitch * row) + (textPoint.x > 0 ? textPoint.x : 0);
+ outputPointer_max = outputPointer + (ds->pitch - textPoint.x);
+
+ // If character starts off the screen, jump to next character
+ if (outputPointer < outputPointer_min) {
+ break;
+ }
+
+ c_dataPointer = drawFont.font + charRow * drawFont.header.rowLength + drawFont.fontCharEntry[c_code].index;
+
+ for (c_byte = 0; c_byte < c_byte_len; c_byte++, c_dataPointer++) {
+ // Check each bit, draw pixel if bit is set
+ for (c_bit = 7; c_bit >= 0 && (outputPointer < outputPointer_max); c_bit--) {
+ if ((*c_dataPointer >> c_bit) & 0x01) {
+ *outputPointer = (byte)color;
+ }
+ outputPointer++;
+ } // end per-bit processing
+ } // end per-byte processing
+ } // end per-row processing
+
+ // Advance tracking position
+ textPoint.x += drawFont.fontCharEntry[c_code].tracking;
+ } // end per-character processing
+}
+
+
+void Font::textDraw(FontId fontId, Surface *ds, const char *text, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) {
+ int textWidth;
+ int textLength;
+ int fitWidth;
+ Common::Point textPoint(point);
+
+ textLength = strlen(text);
+
+ if (!(flags & kFontCentered)) {
+ // Text is not centered; No formatting required
+ draw(fontId, ds, text, textLength, point, color, effectColor, flags);
+ return;
+ }
+
+ // Text is centered... format output
+ // Enforce minimum and maximum center points for centered text
+ if (textPoint.x < TEXT_CENTERLIMIT) {
+ textPoint.x = TEXT_CENTERLIMIT;
+ }
+
+ if (textPoint.x > ds->w - TEXT_CENTERLIMIT) {
+ textPoint.x = ds->w - TEXT_CENTERLIMIT;
+ }
+
+ if (textPoint.x < (TEXT_MARGIN * 2)) {
+ // Text can't be centered if it's too close to the margin
+ return;
+ }
+
+ textWidth = getStringWidth(fontId, text, textLength, flags);
+
+ if (textPoint.x < (ds->w / 2)) {
+ // Fit to right side
+ fitWidth = (textPoint.x - TEXT_MARGIN) * 2;
+ } else {
+ // Fit to left side
+ fitWidth = ((ds->w - TEXT_MARGIN) - textPoint.x) * 2;
+ }
+
+ if (fitWidth < textWidth) {
+ warning("text too long to be displayed in one line");
+ textWidth = fitWidth;
+ }
+ // Entire string fits, draw it
+ textPoint.x = textPoint.x - (textWidth / 2);
+ draw(fontId, ds, text, textLength, textPoint, color, effectColor, flags);
+}
+
+int Font::getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) {
+ int textWidth;
+ int textLength;
+ int fitWidth;
+ const char *startPointer;
+ const char *searchPointer;
+ const char *measurePointer;
+ const char *foundPointer;
+ int len;
+ int w;
+ const char *endPointer;
+ int h;
+ int wc;
+ int w_total;
+ int len_total;
+ Common::Point textPoint;
+ Common::Point textPoint2;
+
+ textLength = strlen(text);
+
+ textWidth = getStringWidth(fontId, text, textLength, flags);
+ h = getHeight(fontId);
+ fitWidth = width;
+
+ textPoint.x = (fitWidth / 2);
+ textPoint.y = 0;
+
+ if (fitWidth >= textWidth) {
+ return h;
+ }
+
+ // String won't fit on one line
+ w_total = 0;
+ len_total = 0;
+ wc = 0;
+
+ startPointer = text;
+ measurePointer = text;
+ searchPointer = text;
+ endPointer = text + textLength;
+
+ for (;;) {
+ foundPointer = strchr(searchPointer, ' ');
+ if (foundPointer == NULL) {
+ // Ran to the end of the buffer
+ len = endPointer - measurePointer;
+ } else {
+ len = foundPointer - measurePointer;
+ }
+
+ w = getStringWidth(fontId, measurePointer, len, flags);
+ measurePointer = foundPointer;
+
+ if ((w_total + w) > fitWidth) {
+ // This word won't fit
+ if (wc == 0) {
+ // The first word in the line didn't fit. Still print it
+ searchPointer = measurePointer + 1;
+ }
+ // Wrap what we've got and restart
+ textPoint.y += h + TEXT_LINESPACING;
+ if (foundPointer == NULL) {
+ // Since word hit NULL but fit, we are done
+ return textPoint.y + h;
+ }
+ w_total = 0;
+ len_total = 0;
+ wc = 0;
+ measurePointer = searchPointer;
+ startPointer = searchPointer;
+ } else {
+ // Word will fit ok
+ w_total += w;
+ len_total += len;
+ wc++;
+ if (foundPointer == NULL) {
+ // Since word hit NULL but fit, we are done
+ return textPoint.y + h;
+ }
+ searchPointer = measurePointer + 1;
+ }
+ }
+}
+
+void Font::textDrawRect(FontId fontId, Surface *ds, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) {
+ int textWidth;
+ int textLength;
+ int fitWidth;
+ const char *startPointer;
+ const char *searchPointer;
+ const char *measurePointer;
+ const char *foundPointer;
+ int len;
+ int w;
+ const char *endPointer;
+ int h;
+ int wc;
+ int w_total;
+ int len_total;
+ Common::Point textPoint;
+ Common::Point textPoint2;
+
+ textLength = strlen(text);
+
+ textWidth = getStringWidth(fontId, text, textLength, flags);
+ fitWidth = rect.width();
+
+ textPoint.x = rect.left + (fitWidth / 2);
+ textPoint.y = rect.top;
+
+ if (fitWidth >= textWidth) {
+ // Entire string fits, draw it
+ textPoint.x -= (textWidth / 2);
+ draw(fontId, ds, text, textLength, textPoint, color, effectColor, flags);
+ return;
+ }
+
+ // String won't fit on one line
+ h = getHeight(fontId);
+ w_total = 0;
+ len_total = 0;
+ wc = 0;
+
+ startPointer = text;
+ measurePointer = text;
+ searchPointer = text;
+ endPointer = text + textLength;
+
+ for (;;) {
+ foundPointer = strchr(searchPointer, ' ');
+ if (foundPointer == NULL) {
+ // Ran to the end of the buffer
+ len = endPointer - measurePointer;
+ } else {
+ len = foundPointer - measurePointer;
+ }
+
+ w = getStringWidth(fontId, measurePointer, len, flags);
+ measurePointer = foundPointer;
+
+ if ((w_total + w) > fitWidth) {
+ // This word won't fit
+ if (wc == 0) {
+ w_total = fitWidth;
+ len_total = len;
+ }
+
+ // Wrap what we've got and restart
+ textPoint2.x = textPoint.x - (w_total / 2);
+ textPoint2.y = textPoint.y;
+ draw(fontId, ds, startPointer, len_total, textPoint2, color, effectColor, flags);
+ textPoint.y += h + TEXT_LINESPACING;
+ if (textPoint.y >= rect.bottom) {
+ return;
+ }
+ w_total = 0;
+ len_total = 0;
+ if (wc == 0) {
+ searchPointer = measurePointer + 1;
+ }
+ wc = 0;
+
+ // Advance the search pointer to the next non-space.
+ // Otherwise, the first "word" to be measured will be
+ // an empty string. Measuring or drawing a string of
+ // length 0 is interpreted as measure/draw the entire
+ // buffer, which certainly is not what we want here.
+ //
+ // This happes because a string may contain several
+ // spaces in a row, e.g. after a period.
+
+ while (*searchPointer == ' ')
+ searchPointer++;
+
+ measurePointer = searchPointer;
+ startPointer = searchPointer;
+ } else {
+ // Word will fit ok
+ w_total += w;
+ len_total += len;
+ wc++;
+ if (foundPointer == NULL) {
+ // Since word hit NULL but fit, we are done
+ textPoint2.x = textPoint.x - (w_total / 2);
+ textPoint2.y = textPoint.y;
+ draw(fontId, ds, startPointer, len_total, textPoint2, color,
+ effectColor, flags);
+ return;
+ }
+ searchPointer = measurePointer + 1;
+ }
+ }
+}
+
+Font::FontId Font::knownFont2FontIdx(KnownFont font) {
+ FontId fontId = kSmallFont;
+
+ if (_vm->getGameType() == GType_ITE) {
+ switch (font)
+ {
+ case (kKnownFontSmall):
+ fontId = kSmallFont;
+ break;
+ case (kKnownFontMedium):
+ fontId = kMediumFont;
+ break;
+ case (kKnownFontBig):
+ fontId = kBigFont;
+ break;
+
+ case (kKnownFontVerb):
+ fontId = kSmallFont;
+ break;
+ case (kKnownFontScript):
+ fontId = kMediumFont;
+ break;
+ case (kKnownFontPause):
+ fontId = _vm->_font->valid(kBigFont) ? kBigFont : kMediumFont;
+ break;
+ }
+ } else if (_vm->getGameType() == GType_IHNM) {
+ switch (font)
+ {
+ case (kKnownFontSmall):
+ fontId = kSmallFont;
+ break;
+ case (kKnownFontMedium):
+ fontId = kMediumFont;
+ break;
+ case (kKnownFontBig):
+ fontId = kBigFont;
+ break;
+
+ case (kKnownFontVerb):
+ fontId = kIHNMFont8;
+ break;
+ case (kKnownFontScript):
+ fontId = kIHNMMainFont;
+ break;
+ case (kKnownFontPause):
+ fontId = kMediumFont; // unchecked
+ break;
+ }
+ }
+ return fontId;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/font.h b/engines/saga/font.h
new file mode 100644
index 0000000000..f7f2113808
--- /dev/null
+++ b/engines/saga/font.h
@@ -0,0 +1,205 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Font management and font drawing header file
+
+#ifndef SAGA_FONT_H__
+#define SAGA_FONT_H__
+
+#include "saga/list.h"
+#include "saga/gfx.h"
+
+namespace Saga {
+
+#define FONT_SHOWUNDEFINED 1 // Define to draw undefined characters * as ?'s
+
+// The first defined character (!) is the only one that may
+// have a valid offset of '0'
+#define FONT_FIRSTCHAR 33
+
+#define FONT_CH_SPACE 32
+#define FONT_CH_QMARK 63
+
+// Minimum font header size without font data
+// (6 + 512 + 256 + 256 + 256 )
+#define FONT_DESCSIZE 1286
+
+#define FONT_CHARCOUNT 256
+#define FONT_CHARMASK 0xFFU
+
+#define SAGA_FONT_HEADER_LEN 6
+
+#define TEXT_CENTERLIMIT 50
+#define TEXT_MARGIN 10
+#define TEXT_LINESPACING 2
+
+enum FontEffectFlags {
+ kFontNormal = 0,
+ kFontOutline = 1 << 0,
+ kFontShadow = 1 << 1,
+ kFontBold = 1 << 2,
+ kFontCentered = 1 << 3,
+ kFontDontmap = 1 << 4
+};
+
+enum KnownFont {
+ kKnownFontSmall,
+ kKnownFontMedium,
+ kKnownFontBig,
+
+ kKnownFontPause,
+ kKnownFontScript,
+ kKnownFontVerb
+};
+
+struct TextListEntry {
+ bool display;
+ bool useRect;
+ Common::Point point;
+ Common::Rect rect;
+ KnownColor knownColor;
+ KnownColor effectKnownColor;
+ FontEffectFlags flags;
+ KnownFont font;
+ const char *text;
+ TextListEntry() {
+ memset(this, 0, sizeof(*this));
+ }
+};
+
+class TextList: public SortedList<TextListEntry> {
+public:
+
+ TextListEntry *addEntry(const TextListEntry &entry) {
+ return pushBack(entry).operator->();
+ }
+};
+
+struct FontHeader {
+ int charHeight;
+ int charWidth;
+ int rowLength;
+};
+
+struct FontCharEntry {
+ int index;
+ int byteWidth;
+ int width;
+ int flag;
+ int tracking;
+};
+
+struct FontStyle {
+ FontHeader header;
+ FontCharEntry fontCharEntry[256];
+ byte *font;
+};
+
+struct FontData {
+ FontStyle normal;
+ FontStyle outline;
+};
+
+class Font {
+ public:
+ Font(SagaEngine *vm);
+ ~Font(void);
+ int getStringWidth(KnownFont font, const char *text, size_t count, FontEffectFlags flags) {
+ return getStringWidth(knownFont2FontIdx(font), text, count, flags);
+ }
+ int getHeight(KnownFont font) {
+ return getHeight(knownFont2FontIdx(font));
+ }
+ int getHeight(KnownFont font, const char *text, int width, FontEffectFlags flags) {
+ return getHeight(knownFont2FontIdx(font), text, width, flags);
+ }
+ void textDraw(KnownFont font, Surface *ds, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) {
+ textDraw(knownFont2FontIdx(font), ds, string, point, color, effectColor, flags);
+ }
+ void textDrawRect(KnownFont font, Surface *ds, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) {
+ textDrawRect(knownFont2FontIdx(font), ds, text, rect, color, effectColor, flags);
+ }
+
+ private:
+ enum FontId {
+ kSmallFont,
+ kMediumFont,
+ kBigFont,
+ kIHNMUnknown,
+ kIHNMFont8,
+ kIHNMUnknown2,
+ kIHNMMainFont
+ };
+
+ Font::FontId knownFont2FontIdx(KnownFont font);
+
+ int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags);
+ int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags);
+ void textDrawRect(FontId fontId, Surface *ds, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags);
+ void textDraw(FontId fontId, Surface *ds, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags);
+
+ void loadFont(uint32 fontResourceId);
+ void createOutline(FontData *font);
+ void draw(FontId fontId, Surface *ds, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags);
+ void outFont(const FontStyle &drawFont, Surface *ds, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags);
+
+ FontData *getFont(FontId fontId) {
+ validate(fontId);
+ return _fonts[fontId];
+ }
+
+ int getHeight(FontId fontId) {
+ return getFont(fontId)->normal.header.charHeight;
+ }
+
+ void validate(FontId fontId) {
+ if (!valid(fontId)) {
+ error("Font::validate: Invalid font id.");
+ }
+ }
+ bool valid(FontId fontId) {
+ return ((fontId >= 0) && (fontId < _loadedFonts));
+ }
+ int getByteLen(int numBits) const {
+ int byteLength = numBits / 8;
+
+ if (numBits % 8) {
+ byteLength++;
+ }
+
+ return byteLength;
+ }
+
+ static const int _charMap[256];
+ SagaEngine *_vm;
+
+ bool _initialized;
+
+ int _loadedFonts;
+ FontData **_fonts;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/font_map.cpp b/engines/saga/font_map.cpp
new file mode 100644
index 0000000000..04e3400b2e
--- /dev/null
+++ b/engines/saga/font_map.cpp
@@ -0,0 +1,293 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Font module character mapping table ( MS CP-850 to ISO 8859-1 )
+
+// Translation table derived from http://www.kostis.net/charsets/
+
+#include "saga/saga.h"
+#include "saga/font.h"
+
+namespace Saga {
+
+const int Font::_charMap[256] = {
+ 0, // 0
+ 1, // 1
+ 2, // 2
+ 3, // 3
+ 4, // 4
+ 5, // 5
+ 6, // 6
+ 7, // 7
+ 8, // 8
+ 9, // 9
+ 10, // 10
+ 11, // 11
+ 12, // 12
+ 13, // 13
+ 14, // 14
+ 15, // 15
+ 16, // 16
+ 17, // 17
+ 18, // 18
+ 19, // 19
+ 20, // 20
+ 21, // 21
+ 22, // 22
+ 23, // 23
+ 24, // 24
+ 25, // 25
+ 26, // 26
+ 27, // 27
+ 28, // 28
+ 29, // 29
+ 30, // 30
+ 31, // 31
+ 32, // 32
+ 33, // 33
+ 34, // 34
+ 35, // 35
+ 36, // 36
+ 37, // 37
+ 38, // 38
+ 39, // 39
+ 40, // 40
+ 41, // 41
+ 42, // 42
+ 43, // 43
+ 44, // 44
+ 45, // 45
+ 46, // 46
+ 47, // 47
+ 48, // 48
+ 49, // 49
+ 50, // 50
+ 51, // 51
+ 52, // 52
+ 53, // 53
+ 54, // 54
+ 55, // 55
+ 56, // 56
+ 57, // 57
+ 58, // 58
+ 59, // 59
+ 60, // 60
+ 61, // 61
+ 62, // 62
+ 63, // 63
+ 64, // 64
+ 65, // 65
+ 66, // 66
+ 67, // 67
+ 68, // 68
+ 69, // 69
+ 70, // 70
+ 71, // 71
+ 72, // 72
+ 73, // 73
+ 74, // 74
+ 75, // 75
+ 76, // 76
+ 77, // 77
+ 78, // 78
+ 79, // 79
+ 80, // 80
+ 81, // 81
+ 82, // 82
+ 83, // 83
+ 84, // 84
+ 85, // 85
+ 86, // 86
+ 87, // 87
+ 88, // 88
+ 89, // 89
+ 90, // 90
+ 91, // 91
+ 92, // 92
+ 93, // 93
+ 94, // 94
+ 95, // 95
+ 96, // 96
+ 97, // 97
+ 98, // 98
+ 99, // 99
+ 100, // 100
+ 101, // 101
+ 102, // 102
+ 103, // 103
+ 104, // 104
+ 105, // 105
+ 106, // 106
+ 107, // 107
+ 108, // 108
+ 109, // 109
+ 110, // 110
+ 111, // 111
+ 112, // 112
+ 113, // 113
+ 114, // 114
+ 115, // 115
+ 116, // 116
+ 117, // 117
+ 118, // 118
+ 119, // 119
+ 120, // 120
+ 121, // 121
+ 122, // 122
+ 123, // 123
+ 124, // 124
+ 125, // 125
+ 126, // 126
+ 127, // 127
+ 199, // 128 LATIN CAPITAL LETTER C WITH CEDILLA
+ 252, // 129 LATIN SMALL LETTER U WITH DIAERESIS
+ 233, // 130 LATIN SMALL LETTER E WITH ACUTE
+ 226, // 131 LATIN SMALL LETTER A WITH CIRCUMFLEX
+ 228, // 132 LATIN SMALL LETTER A WITH DIAERESIS
+ 224, // 133 LATIN SMALL LETTER A WITH GRAVE
+ 229, // 134 LATIN SMALL LETTER A WITH RING ABOVE
+ 231, // 135 LATIN SMALL LETTER C WITH CEDILLA
+ 234, // 136 LATIN SMALL LETTER E WITH CIRCUMFLEX
+ 235, // 137 LATIN SMALL LETTER E WITH DIAERESIS
+ 232, // 138 LATIN SMALL LETTER E WITH GRAVE
+ 239, // 139 LATIN SMALL LETTER I WITH DIAERESIS
+ 238, // 140 LATIN SMALL LETTER I WITH CIRCUMFLEX
+ 236, // 141 LATIN SMALL LETTER I WITH GRAVE
+ 196, // 142 LATIN CAPITAL LETTER A WITH DIAERESIS
+ 197, // 143 LATIN CAPITAL LETTER A WITH RING ABOVE
+ 201, // 144 LATIN CAPITAL LETTER E WITH ACUTE
+ 230, // 145 LATIN SMALL LETTER AE
+ 198, // 146 LATIN CAPITAL LETTER AE
+ 244, // 147 LATIN SMALL LETTER O WITH CIRCUMFLEX
+ 246, // 148 LATIN SMALL LETTER O WITH DIAERESIS
+ 242, // 149 LATIN SMALL LETTER O WITH GRAVE
+ 251, // 150 LATIN SMALL LETTER U WITH CIRCUMFLEX
+ 249, // 151 LATIN SMALL LETTER U WITH GRAVE
+ 255, // 152 LATIN SMALL LETTER Y WITH DIAERESIS
+ 214, // 153 LATIN CAPITAL LETTER O WITH DIAERESIS
+ 220, // 154 LATIN CAPITAL LETTER U WITH DIAERESIS
+ 248, // 155 LATIN SMALL LETTER O WITH STROKE
+ 163, // 156 POUND SIGN
+ 216, // 157 LATIN CAPITAL LETTER O WITH STROKE
+ 215, // 158 MULTIPLICATION SIGN
+ 0, // 159 LATIN SMALL LETTER F WITH HOOK
+ 225, // 160 LATIN SMALL LETTER A WITH ACUTE
+ 237, // 161 LATIN SMALL LETTER I WITH ACUTE
+ 243, // 162 LATIN SMALL LETTER O WITH ACUTE
+ 250, // 163 LATIN SMALL LETTER U WITH ACUTE
+ 241, // 164 LATIN SMALL LETTER N WITH TILDE
+ 209, // 165 LATIN CAPITAL LETTER N WITH TILDE
+ 170, // 166 FEMININE ORDINAL INDICATOR
+ 186, // 167 MASCULINE ORDINAL INDICATOR
+ 191, // 168 INVERTED QUESTION MARK
+ 174, // 169 REGISTERED SIGN
+ 172, // 170 NOT SIGN
+ 189, // 171 VULGAR FRACTION ONE HALF
+ 188, // 172 VULGAR FRACTION ONE QUARTER
+ 161, // 173 INVERTED EXCLAMATION MARK
+ 171, // 174 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 187, // 175 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0, // 176 LIGHT SHADE
+ 0, // 177 MEDIUM SHADE
+ 0, // 178 DARK SHADE
+ 0, // 179 BOX DRAWINGS LIGHT VERTICAL
+ 0, // 180 BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 193, // 181 LATIN CAPITAL LETTER A WITH ACUTE
+ 194, // 182 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ 192, // 183 LATIN CAPITAL LETTER A WITH GRAVE
+ 169, // 184 COPYRIGHT SIGN
+ 0, // 185 BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ 0, // 186 BOX DRAWINGS DOUBLE VERTICAL
+ 0, // 187 BOX DRAWINGS DOUBLE DOWN AND LEFT
+ 0, // 188 BOX DRAWINGS DOUBLE UP AND LEFT
+ 162, // 189 CENT SIGN
+ 165, // 190 YEN SIGN
+ 0, // 191 BOX DRAWINGS LIGHT DOWN AND LEFT
+ 0, // 192 BOX DRAWINGS LIGHT UP AND RIGHT
+ 0, // 193 BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 0, // 194 BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 0, // 195 BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 0, // 196 BOX DRAWINGS LIGHT HORIZONTAL
+ 0, // 197 BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 227, // 198 LATIN SMALL LETTER A WITH TILDE
+ 195, // 199 LATIN CAPITAL LETTER A WITH TILDE
+ 0, // 200 BOX DRAWINGS DOUBLE UP AND RIGHT
+ 0, // 201 BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ 0, // 202 BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ 0, // 203 BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ 0, // 204 BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ 0, // 205 BOX DRAWINGS DOUBLE HORIZONTAL
+ 0, // 206 BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ 164, // 207 CURRENCY SIGN
+ 240, // 208 LATIN SMALL LETTER ETH
+ 208, // 209 LATIN CAPITAL LETTER ETH
+ 202, // 210 LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ 203, // 211 LATIN CAPITAL LETTER E WITH DIAERESIS
+ 200, // 212 LATIN CAPITAL LETTER E WITH GRAVE
+ 305, // 213 LATIN SMALL LETTER DOTLESS I
+ 205, // 214 LATIN CAPITAL LETTER I WITH ACUTE
+ 206, // 215 LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ 207, // 216 LATIN CAPITAL LETTER I WITH DIAERESIS
+ 0, // 217 BOX DRAWINGS LIGHT UP AND LEFT
+ 0, // 218 BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 0, // 219 FULL BLOCK
+ 0, // 220 LOWER HALF BLOCK
+ 166, // 221 BROKEN BAR
+ 204, // 222 LATIN CAPITAL LETTER I WITH GRAVE
+ 0, // 223 UPPER HALF BLOCK
+ 211, // 224 LATIN CAPITAL LETTER O WITH ACUTE
+ 223, // 225 LATIN SMALL LETTER SHARP S
+ 212, // 226 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ 210, // 227 LATIN CAPITAL LETTER O WITH GRAVE
+ 245, // 228 LATIN SMALL LETTER O WITH TILDE
+ 213, // 229 LATIN CAPITAL LETTER O WITH TILDE
+ 181, // 230 MICRO SIGN
+ 254, // 231 LATIN SMALL LETTER THORN
+ 222, // 232 LATIN CAPITAL LETTER THORN
+ 218, // 233 LATIN CAPITAL LETTER U WITH ACUTE
+ 219, // 234 LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ 217, // 235 LATIN CAPITAL LETTER U WITH GRAVE
+ 253, // 236 LATIN SMALL LETTER Y WITH ACUTE
+ 221, // 237 LATIN CAPITAL LETTER Y WITH ACUTE
+ 175, // 238 MACRON
+ 180, // 239 ACUTE ACCENT
+ 173, // 240 SOFT HYPHEN
+ 177, // 241 PLUS-MINUS SIGN
+ 0, // 242 DOUBLE LOW LINE
+ 190, // 243 VULGAR FRACTION THREE QUARTERS
+ 182, // 244 PILCROW SIGN
+ 167, // 245 SECTION SIGN
+ 247, // 246 DIVISION SIGN
+ 184, // 247 CEDILLA
+ 176, // 248 DEGREE SIGN
+ 168, // 249 DIAERESIS
+ 183, // 250 MIDDLE DOT
+ 185, // 251 SUPERSCRIPT ONE
+ 179, // 252 SUPERSCRIPT THREE
+ 178, // 253 SUPERSCRIPT TWO
+ 0, // 254 BLACK SQUARE
+ 160 // 255 NO-BREAK SPACE
+};
+
+} // End of namespace Saga
diff --git a/engines/saga/game.cpp b/engines/saga/game.cpp
new file mode 100644
index 0000000000..86526fe267
--- /dev/null
+++ b/engines/saga/game.cpp
@@ -0,0 +1,1790 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Game detection, general game parameters
+
+#include "saga/saga.h"
+
+#include "common/file.h"
+#include "common/md5.h"
+#include "common/map.h"
+#include "common/config-manager.h"
+#include "base/plugins.h"
+#include "base/gameDetector.h"
+#include "backends/fs/fs.h"
+
+#include "saga/rscfile.h"
+#include "saga/interface.h"
+#include "saga/scene.h"
+#include "saga/resnames.h"
+
+#define ITE_CONVERSE_MAX_TEXT_WIDTH (256 - 60)
+#define ITE_CONVERSE_TEXT_HEIGHT 10
+#define ITE_CONVERSE_TEXT_LINES 4
+
+//TODO: ihnm
+#define IHNM_CONVERSE_MAX_TEXT_WIDTH (256 - 60)
+#define IHNM_CONVERSE_TEXT_HEIGHT 10
+#define IHNM_CONVERSE_TEXT_LINES 10
+
+namespace Saga {
+
+static int detectGame(const FSList &fslist, bool mode = false, int start = -1);
+
+// ITE section
+static PanelButton ITE_MainPanelButtons[] = {
+ {kPanelButtonVerb, 52,4, 57,10, kVerbITEWalkTo,'w',0, 0,1,0},
+ {kPanelButtonVerb, 52,15, 57,10, kVerbITELookAt,'l',0, 2,3,0},
+ {kPanelButtonVerb, 52,26, 57,10, kVerbITEPickUp,'p',0, 4,5,0},
+ {kPanelButtonVerb, 52,37, 57,10, kVerbITETalkTo,'t',0, 0,1,0},
+ {kPanelButtonVerb, 110,4, 56,10, kVerbITEOpen,'o',0, 6,7,0},
+ {kPanelButtonVerb, 110,15, 56,10, kVerbITEClose,'c',0, 8,9,0},
+ {kPanelButtonVerb, 110,26, 56,10, kVerbITEUse,'u',0, 10,11,0},
+ {kPanelButtonVerb, 110,37, 56,10, kVerbITEGive,'g',0, 12,13,0},
+ {kPanelButtonArrow, 306,6, 8,5, -1,'U',0, 0,4,2},
+ {kPanelButtonArrow, 306,41, 8,5, 1,'D',0, 1,5,3},
+
+ {kPanelButtonInventory, 181 + 32*0,6, 27,18, 0,'-',0, 0,0,0},
+ {kPanelButtonInventory, 181 + 32*1,6, 27,18, 1,'-',0, 0,0,0},
+ {kPanelButtonInventory, 181 + 32*2,6, 27,18, 2,'-',0, 0,0,0},
+ {kPanelButtonInventory, 181 + 32*3,6, 27,18, 3,'-',0, 0,0,0},
+
+ {kPanelButtonInventory, 181 + 32*0,27, 27,18, 4,'-',0, 0,0,0},
+ {kPanelButtonInventory, 181 + 32*1,27, 27,18, 5,'-',0, 0,0,0},
+ {kPanelButtonInventory, 181 + 32*2,27, 27,18, 6,'-',0, 0,0,0},
+ {kPanelButtonInventory, 181 + 32*3,27, 27,18, 7,'-',0, 0,0,0}
+};
+
+static PanelButton ITE_ConversePanelButtons[] = {
+ {kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 0, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 0,'1',0, 0,0,0},
+ {kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 1, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 1,'2',0, 0,0,0},
+ {kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 2, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 2,'3',0, 0,0,0},
+ {kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 3, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 3,'4',0, 0,0,0},
+ {kPanelButtonArrow, 257,6, 9,6, -1,'u',0, 0,4,2},
+ {kPanelButtonArrow, 257,41, 9,6, 1,'d',0, 1,5,3},
+};
+
+static PanelButton ITE_OptionPanelButtons[] = {
+ {kPanelButtonOptionSlider, 284,19, 13,75, 0,'-',0, 0,0,0}, //slider-scroller
+ {kPanelButtonOption, 113,18, 45,17, kTextReadingSpeed,'r',0, 0,0,0}, //read speed
+ {kPanelButtonOption, 113,37, 45,17, kTextMusic,'m',0, 0,0,0}, //music
+ {kPanelButtonOption, 113,56, 45,17, kTextSound,'n',0, 0,0,0}, //sound-noise
+ {kPanelButtonOption, 13,79, 135,17, kTextQuitGame,'q',0, 0,0,0}, //quit
+ {kPanelButtonOption, 13,98, 135,17, kTextContinuePlaying,'c',0, 0,0,0}, //continue
+ {kPanelButtonOption, 164,98, 57,17, kTextLoad,'l',0, 0,0,0}, //load
+ {kPanelButtonOption, 241,98, 57,17, kTextSave,'s',0, 0,0,0}, //save
+ {kPanelButtonOptionSaveFiles, 166,20, 112,74, 0,'-',0, 0,0,0}, //savefiles
+
+ {kPanelButtonOptionText,106,4, 0,0, kTextGameOptions,'-',0, 0,0,0}, // text: game options
+ {kPanelButtonOptionText,11,22, 0,0, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed
+ {kPanelButtonOptionText,28,22, 0,0, kTextShowDialog,'-',0, 0,0,0}, // text: read speed
+ {kPanelButtonOptionText,73,41, 0,0, kTextMusic,'-',0, 0,0,0}, // text: music
+ {kPanelButtonOptionText,69,60, 0,0, kTextSound,'-',0, 0,0,0}, // text: noise
+};
+
+static PanelButton ITE_QuitPanelButtons[] = {
+ {kPanelButtonQuit, 11,17, 60,16, kTextQuit,'q',0, 0,0,0},
+ {kPanelButtonQuit, 121,17, 60,16, kTextCancel,'c',0, 0,0,0},
+ {kPanelButtonQuitText, -1,5, 0,0, kTextQuitTheGameQuestion,'-',0, 0,0,0},
+};
+
+static PanelButton ITE_LoadPanelButtons[] = {
+ {kPanelButtonLoad, 101,19, 60,16, kTextOK,'o',0, 0,0,0},
+ {kPanelButtonLoadText, -1,5, 0,0, kTextLoadSuccessful,'-',0, 0,0,0},
+};
+
+static PanelButton ITE_SavePanelButtons[] = {
+ {kPanelButtonSave, 11,37, 60,16, kTextSave,'s',0, 0,0,0},
+ {kPanelButtonSave, 101,37, 60,16, kTextCancel,'c',0, 0,0,0},
+ {kPanelButtonSaveEdit, 26,17, 119,17, 0,'-',0, 0,0,0},
+ {kPanelButtonSaveText, -1,5, 0,0, kTextEnterSaveGameName,'-',0, 0,0,0},
+};
+
+static PanelButton ITE_ProtectPanelButtons[] = {
+ {kPanelButtonProtectEdit, 26,17, 119,17, 0,'-',0, 0,0,0},
+ {kPanelButtonProtectText, -1,5, 0,0, kTextEnterProtectAnswer,'-',0, 0,0,0},
+};
+
+/*
+static PanelButton ITE_ProtectionPanelButtons[] = {
+ {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO
+};*/
+
+static GameDisplayInfo ITE_DisplayInfo = {
+ 320, 200, // logical width&height
+
+ 35, // scene path y offset
+ 137, // scene height
+
+ 0, // status x offset
+ 137, // status y offset
+ 320, // status width
+ 11, // status height
+ 2, // status text y offset
+ 186, // status text color
+ 15, // status BG color
+ 308,137, // save reminder pos
+ 12,12, // save reminder w & h
+ 6,7, // save reminder sprite numbers
+
+ 5, 4, // left portrait x, y offset
+ 274, 4, // right portrait x, y offset
+
+ 8, 9, // inventory Up & Down button indexies
+ 2, 4, // inventory rows, columns
+
+ 0, 148, // main panel offsets
+ ARRAYSIZE(ITE_MainPanelButtons),
+ ITE_MainPanelButtons,
+
+ ITE_CONVERSE_MAX_TEXT_WIDTH,
+ ITE_CONVERSE_TEXT_HEIGHT,
+ ITE_CONVERSE_TEXT_LINES,
+ 4, 5, // converse Up & Down button indexies
+ 0, 148, // converse panel offsets
+ ARRAYSIZE(ITE_ConversePanelButtons),
+ ITE_ConversePanelButtons,
+
+ 8, 0, // save file index
+ 8, // optionSaveFileVisible
+ 8, 8, // option panel offsets
+ ARRAYSIZE(ITE_OptionPanelButtons),
+ ITE_OptionPanelButtons,
+
+ 64,54, // quit panel offsets
+ 192,38, // quit panel width & height
+ ARRAYSIZE(ITE_QuitPanelButtons),
+ ITE_QuitPanelButtons,
+
+ 74, 53, // load panel offsets
+ 172, 40, // load panel width & height
+ ARRAYSIZE(ITE_LoadPanelButtons),
+ ITE_LoadPanelButtons,
+
+ 2, // save edit index
+ 74, 44, // save panel offsets
+ 172, 58, // save panel width & height
+ ARRAYSIZE(ITE_SavePanelButtons),
+ ITE_SavePanelButtons,
+
+ 0, // protect edit index
+ 74, 44, // protect panel offsets
+ 172, 58, // protect panel width & height
+ ARRAYSIZE(ITE_ProtectPanelButtons),
+ ITE_ProtectPanelButtons
+};
+
+static GameResourceDescription ITE_Resources = {
+ RID_ITE_SCENE_LUT, // Scene lookup table RN
+ RID_ITE_SCRIPT_LUT, // Script lookup table RN
+ RID_ITE_MAIN_PANEL,
+ RID_ITE_CONVERSE_PANEL,
+ RID_ITE_OPTION_PANEL,
+ RID_ITE_MAIN_SPRITES,
+ RID_ITE_MAIN_PANEL_SPRITES,
+ RID_ITE_DEFAULT_PORTRAITS,
+ RID_ITE_MAIN_STRINGS,
+ RID_ITE_ACTOR_NAMES
+};
+
+static GameResourceDescription ITEDemo_Resources = {
+ RID_ITEDEMO_SCENE_LUT, // Scene lookup table RN
+ RID_ITEDEMO_SCRIPT_LUT, // Script lookup table RN
+ RID_ITEDEMO_MAIN_PANEL,
+ RID_ITEDEMO_CONVERSE_PANEL,
+ RID_ITEDEMO_OPTION_PANEL,
+ RID_ITEDEMO_MAIN_SPRITES,
+ RID_ITEDEMO_MAIN_PANEL_SPRITES,
+ RID_ITEDEMO_DEFAULT_PORTRAITS,
+ RID_ITEDEMO_MAIN_STRINGS,
+ RID_ITEDEMO_ACTOR_NAMES
+};
+
+// Inherit the Earth - DOS Demo version
+static GameFileDescription ITEDEMO_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ //{"ite.dmo", GAME_DEMOFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"voices.rsc", GAME_SOUNDFILE | GAME_VOICEFILE}
+};
+
+static GameFontDescription ITEDEMO_GameFonts[] = {
+ {0},
+ {1}
+};
+
+static GameSoundInfo ITEDEMO_GameSound = {
+ kSoundVOC,
+ -1,
+ -1,
+ false,
+ false,
+ true
+};
+
+// Inherit the Earth - Wyrmkeep Win32 Demo version
+static GameFileDescription ITEWINDEMO_GameFiles[] = {
+ {"ited.rsc", GAME_RESOURCEFILE},
+ {"scriptsd.rsc", GAME_SCRIPTFILE},
+ {"soundsd.rsc", GAME_SOUNDFILE},
+ {"voicesd.rsc", GAME_VOICEFILE}
+};
+
+static GameFontDescription ITEWINDEMO_GameFonts[] = {
+ {2},
+ {0}
+};
+
+static GameSoundInfo ITEWINDEMO1_GameSound = {
+ kSoundPCM,
+ 22050,
+ 8,
+ false,
+ false,
+ false
+};
+
+static GameSoundInfo ITEWINDEMO2_GameVoice = {
+ kSoundVOX,
+ 22050,
+ 16,
+ false,
+ false,
+ true
+};
+
+static GameSoundInfo ITEWINDEMO2_GameSound = {
+ kSoundPCM,
+ 22050,
+ 16,
+ false,
+ false,
+ true
+};
+
+// Inherit the Earth - Wyrmkeep Mac Demo version
+static GameFileDescription ITEMACDEMO_GameFiles[] = {
+ {"ited.rsc", GAME_RESOURCEFILE},
+ {"scriptsd.rsc", GAME_SCRIPTFILE},
+ {"soundsd.rsc", GAME_SOUNDFILE},
+ {"voicesd.rsc", GAME_VOICEFILE},
+ {"musicd.rsc", GAME_MUSICFILE}
+};
+
+static GameSoundInfo ITEMACDEMO_GameVoice = {
+ kSoundVOX,
+ 22050,
+ 16,
+ false,
+ false,
+ true
+};
+
+static GameSoundInfo ITEMACDEMO_GameSound = {
+ kSoundPCM,
+ 22050,
+ 16,
+ false,
+ true,
+ true
+};
+
+static GameSoundInfo ITEMACDEMO_GameMusic = {
+ kSoundPCM,
+ 11025,
+ 16,
+ false,
+ false,
+ true
+};
+
+// Inherit the Earth - Wyrmkeep Linux Demo version
+static GameFileDescription ITELINDEMO_GameFiles[] = {
+ {"ited.rsc", GAME_RESOURCEFILE},
+ {"scriptsd.rsc", GAME_SCRIPTFILE},
+ {"soundsd.rsc", GAME_SOUNDFILE},
+ {"voicesd.rsc", GAME_VOICEFILE},
+ {"musicd.rsc", GAME_MUSICFILE}
+};
+
+static GameSoundInfo ITELINDEMO_GameMusic = {
+ kSoundPCM,
+ 11025,
+ 16,
+ true,
+ false,
+ true
+};
+
+// Inherit the Earth - Wyrmkeep Linux version
+static GameFileDescription ITELINCD_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"sounds.rsc", GAME_SOUNDFILE},
+ {"voices.rsc", GAME_VOICEFILE},
+ {"music.rsc", GAME_MUSICFILE}
+};
+
+// Inherit the Earth - Wyrmkeep combined Windows/Mac/Linux version. This
+// version is different from the other Wyrmkeep re-releases in that it does
+// not have any substitute files. Presumably the ite.rsc file has been
+// modified to include the Wyrmkeep changes. The resource files are little-
+// endian, except for the voice file which is big-endian.
+
+static GameFileDescription ITEMULTICD_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"sounds.rsc", GAME_SOUNDFILE},
+ {"Inherit the Earth Voices", GAME_VOICEFILE | GAME_SWAPENDIAN},
+ {"music.rsc", GAME_MUSICFILE}
+};
+
+static GameFileDescription ITEMACCD_G_GameFiles[] = {
+ {"ITE Resources.bin", GAME_RESOURCEFILE | GAME_MACBINARY},
+ {"ITE Scripts.bin", GAME_SCRIPTFILE | GAME_MACBINARY},
+ {"ITE Sounds.bin", GAME_SOUNDFILE | GAME_MACBINARY},
+ {"ITE Music.bin", GAME_MUSICFILE_GM | GAME_MACBINARY},
+ {"ITE Voices.bin", GAME_VOICEFILE | GAME_MACBINARY}
+};
+
+static GameSoundInfo ITEMACCD_G_GameSound = {
+ kSoundMacPCM,
+ 22050,
+ 8,
+ false,
+ false,
+ false
+};
+
+// Inherit the Earth - Mac Wyrmkeep version
+static GameFileDescription ITEMACCD_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"sounds.rsc", GAME_SOUNDFILE},
+ {"Inherit the Earth Voices", GAME_VOICEFILE},
+ {"music.rsc", GAME_MUSICFILE}
+};
+
+static GameSoundInfo ITEMACCD_GameSound = {
+ kSoundPCM,
+ 22050,
+ 16,
+ false,
+ true,
+ true
+};
+
+static GameSoundInfo ITEMACCD_GameMusic = {
+ kSoundPCM,
+ 11025,
+ 16,
+ true,
+ false,
+ true
+};
+
+// Inherit the Earth - Diskette version
+static GameFileDescription ITEDISK_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"voices.rsc", GAME_SOUNDFILE | GAME_VOICEFILE}
+};
+
+static GameFileDescription ITEDISK2_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"voices.rsc", GAME_SOUNDFILE | GAME_VOICEFILE},
+ {"music.rsc", GAME_MUSICFILE}
+};
+
+static GameFontDescription ITEDISK_GameFonts[] = {
+ {2},
+ {0},
+ {1}
+};
+
+static GameSoundInfo ITEDISK_GameSound = {
+ kSoundVOC,
+ -1,
+ -1,
+ false,
+ false,
+ true
+};
+
+// Inherit the Earth - CD Enhanced version
+static GameFileDescription ITECD_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"sounds.rsc", GAME_SOUNDFILE},
+ {"voices.rsc", GAME_VOICEFILE}
+};
+
+static GameFileDescription ITECD2_GameFiles[] = {
+ {"ite.rsc", GAME_RESOURCEFILE},
+ {"scripts.rsc", GAME_SCRIPTFILE},
+ {"sounds.rsc", GAME_SOUNDFILE},
+ {"voices.rsc", GAME_VOICEFILE},
+ {"music.rsc", GAME_MUSICFILE}
+};
+
+static GameFontDescription ITECD_GameFonts[] = {
+ {2},
+ {0},
+ {1}
+};
+
+static GameSoundInfo ITECD_GameSound = {
+ kSoundPCM,
+ 22050,
+ 16,
+ false,
+ false,
+ true
+};
+
+static GamePatchDescription ITEWinPatch1_Files[] = {
+ { "cave.mid", GAME_RESOURCEFILE, 9, NULL},
+ { "intro.mid", GAME_RESOURCEFILE, 10, NULL},
+ { "fvillage.mid", GAME_RESOURCEFILE, 11, NULL},
+ { "elkhall.mid", GAME_RESOURCEFILE, 12, NULL},
+ { "mouse.mid", GAME_RESOURCEFILE, 13, NULL},
+ { "darkclaw.mid", GAME_RESOURCEFILE, 14, NULL},
+ { "birdchrp.mid", GAME_RESOURCEFILE, 15, NULL},
+ { "orbtempl.mid", GAME_RESOURCEFILE, 16, NULL},
+ { "spooky.mid", GAME_RESOURCEFILE, 17, NULL},
+ { "catfest.mid", GAME_RESOURCEFILE, 18, NULL},
+ { "elkfanfare.mid", GAME_RESOURCEFILE, 19, NULL},
+ { "bcexpl.mid", GAME_RESOURCEFILE, 20, NULL},
+ { "boargtnt.mid", GAME_RESOURCEFILE, 21, NULL},
+ { "boarking.mid", GAME_RESOURCEFILE, 22, NULL},
+ { "explorea.mid", GAME_RESOURCEFILE, 23, NULL},
+ { "exploreb.mid", GAME_RESOURCEFILE, 24, NULL},
+ { "explorec.mid", GAME_RESOURCEFILE, 25, NULL},
+ { "sunstatm.mid", GAME_RESOURCEFILE, 26, NULL},
+ { "nitstrlm.mid", GAME_RESOURCEFILE, 27, NULL},
+ { "humruinm.mid", GAME_RESOURCEFILE, 28, NULL},
+ { "damexplm.mid", GAME_RESOURCEFILE, 29, NULL},
+ { "tychom.mid", GAME_RESOURCEFILE, 30, NULL},
+ { "kitten.mid", GAME_RESOURCEFILE, 31, NULL},
+ { "sweet.mid", GAME_RESOURCEFILE, 32, NULL},
+ { "brutalmt.mid", GAME_RESOURCEFILE, 33, NULL},
+ { "shiala.mid", GAME_RESOURCEFILE, 34, NULL},
+
+ { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL},
+ { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL},
+ { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL},
+ { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL},
+ { "wyrm4.dlt", GAME_RESOURCEFILE, 1533, NULL},
+ { "credit3n.dlt", GAME_RESOURCEFILE, 1796, NULL},
+ { "credit4n.dlt", GAME_RESOURCEFILE, 1797, NULL},
+ { "p2_a.voc", GAME_VOICEFILE, 4, NULL}
+};
+
+static GamePatchDescription ITEWinPatch2_Files[] = {
+ { "cave.mid", GAME_RESOURCEFILE, 9, NULL},
+ { "intro.mid", GAME_RESOURCEFILE, 10, NULL},
+ { "fvillage.mid", GAME_RESOURCEFILE, 11, NULL},
+ { "elkfanfare.mid", GAME_RESOURCEFILE, 19, NULL},
+ { "bcexpl.mid", GAME_RESOURCEFILE, 20, NULL},
+ { "boargtnt.mid", GAME_RESOURCEFILE, 21, NULL},
+ { "explorea.mid", GAME_RESOURCEFILE, 23, NULL},
+ { "sweet.mid", GAME_RESOURCEFILE, 32, NULL},
+
+ { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL},
+ { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL},
+ { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL},
+ { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL},
+ { "p2_a.iaf", GAME_VOICEFILE, 4, &ITECD_GameSound}
+/* boarhall.bbm
+ elkenter.bbm
+ ferrets.bbm
+ ratdoor.bbm
+ sanctuar.bbm
+ tycho.bbm*/
+};
+
+static GamePatchDescription ITEMacPatch_Files[] = {
+ { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL},
+ { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL},
+ { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL},
+ { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL},
+ { "wyrm4.dlt", GAME_RESOURCEFILE, 1533, NULL},
+ { "credit3m.dlt", GAME_RESOURCEFILE, 1796, NULL},
+ { "credit4m.dlt", GAME_RESOURCEFILE, 1797, NULL},
+ { "p2_a.iaf", GAME_VOICEFILE, 4, &ITEMACCD_GameSound}
+};
+
+static GamePatchDescription ITELinPatch_Files[] = {
+ { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL},
+ { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL},
+ { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL},
+ { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL},
+ { "credit3n.dlt", GAME_RESOURCEFILE, 1796, NULL},
+ { "credit4n.dlt", GAME_RESOURCEFILE, 1797, NULL},
+ { "P2_A.iaf", GAME_VOICEFILE, 4, &ITECD_GameSound}
+};
+
+// IHNM section
+
+static PanelButton IHNM_MainPanelButtons[] = {
+ {kPanelButtonVerb, 106,12, 114,30, kVerbIHNMWalk,'w',0, 0,1,0},
+ {kPanelButtonVerb, 106,44, 114,30, kVerbIHNMLookAt,'l',0, 2,3,0},
+ {kPanelButtonVerb, 106,76, 114,30, kVerbIHNMTake,'k',0, 4,5,0},
+ {kPanelButtonVerb, 106,108, 114,30, kVerbIHNMUse,'u',0, 6,7,0},
+ {kPanelButtonVerb, 223,12, 114,30, kVerbIHNMTalkTo,'t',0, 8,9,0},
+ {kPanelButtonVerb, 223,44, 114,30, kVerbIHNMSwallow,'s',0, 10,11,0},
+ {kPanelButtonVerb, 223,76, 114,30, kVerbIHNMGive,'g',0, 12,13,0},
+ {kPanelButtonVerb, 223,108, 114,30, kVerbIHNMPush,'p',0, 14,15,0},
+ {kPanelButtonArrow, 606,22, 20,25, -1,'[',0, 0,0,0}, //TODO: arrow Sprite Numbers
+ {kPanelButtonArrow, 606,108, 20,25, 1,']',0, 0,0,0},
+
+ {kPanelButtonInventory, 357 + 64*0,18, 54,54, 0,'-',0, 0,0,0},
+ {kPanelButtonInventory, 357 + 64*1,18, 54,54, 1,'-',0, 0,0,0},
+ {kPanelButtonInventory, 357 + 64*2,18, 54,54, 2,'-',0, 0,0,0},
+ {kPanelButtonInventory, 357 + 64*3,18, 54,54, 3,'-',0, 0,0,0},
+
+ {kPanelButtonInventory, 357 + 64*0,80, 54,54, 4,'-',0, 0,0,0},
+ {kPanelButtonInventory, 357 + 64*1,80, 54,54, 5,'-',0, 0,0,0},
+ {kPanelButtonInventory, 357 + 64*2,80, 54,54, 6,'-',0, 0,0,0},
+ {kPanelButtonInventory, 357 + 64*3,80, 54,54, 7,'-',0, 0,0,0}
+};
+
+static PanelButton IHNM_ConversePanelButtons[] = {
+ {kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 0, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 0,'1',0, 0,0,0},
+ {kPanelButtonConverseText, 52,18 + IHNM_CONVERSE_TEXT_HEIGHT * 1, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 1,'2',0, 0,0,0},
+ {kPanelButtonConverseText, 52,18 + IHNM_CONVERSE_TEXT_HEIGHT * 2, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 2,'3',0, 0,0,0},
+ {kPanelButtonConverseText, 52,18 + IHNM_CONVERSE_TEXT_HEIGHT * 3, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 3,'4',0, 0,0,0},
+ //.....
+ {kPanelButtonArrow, 606,22, 20,25, -1,'[',0, 0,0,0}, //TODO: arrow Sprite Numbers
+ {kPanelButtonArrow, 606,108, 20,25, 1,']',0, 0,0,0}
+};
+
+static PanelButton IHNM_OptionPanelButtons[] = {
+ {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO
+};
+
+static PanelButton IHNM_QuitPanelButtons[] = {
+ {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO
+};
+
+static PanelButton IHNM_LoadPanelButtons[] = {
+ {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO
+};
+
+static PanelButton IHNM_SavePanelButtons[] = {
+ {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO
+};
+
+
+static GameDisplayInfo IHNM_DisplayInfo = { //TODO: fill it all
+ 640, 480, // logical width&height
+
+ 0, // scene path y offset
+ 304, // scene height
+
+ 0, // status x offset
+ 304, // status y offset
+ 616, // status width
+ 24, // status height
+ 8, // status text y offset
+ 253, // status text color
+ 250, // status BG color
+ 616, 303, // save reminder pos
+ 24, 24, // save reminder w&h
+ 0,1, // save reminder sprite numbers
+
+ 11, 12, // left portrait x, y offset
+ -1, -1, // right portrait x, y offset
+
+ -1, -1, // inventory Up & Down button indexies
+ 2, 4, // inventory rows, columns
+
+ 0, 328, // main panel offsets
+ ARRAYSIZE(IHNM_MainPanelButtons),
+ IHNM_MainPanelButtons,
+
+ -1, -1, // converse Up & Down button indexies
+
+ IHNM_CONVERSE_MAX_TEXT_WIDTH,
+ IHNM_CONVERSE_TEXT_HEIGHT,
+ IHNM_CONVERSE_TEXT_LINES,
+ 0, 328, // converse panel offsets
+ ARRAYSIZE(IHNM_ConversePanelButtons),
+ IHNM_ConversePanelButtons,
+
+ -1, -1, // save file index
+ 0, // optionSaveFileVisible
+ 0, 0, // option panel offsets
+ ARRAYSIZE(IHNM_OptionPanelButtons),
+ IHNM_OptionPanelButtons,
+
+ 0,0, // quit panel offsets
+ 0,0, // quit panel width & height
+ ARRAYSIZE(IHNM_QuitPanelButtons),
+ IHNM_QuitPanelButtons,
+
+ 0, 0, // load panel offsets
+ 0, 0, // load panel width & height
+ ARRAYSIZE(IHNM_LoadPanelButtons),
+ IHNM_LoadPanelButtons,
+
+ -1, // save edit index
+ 0, 0, // save panel offsets
+ 0, 0, // save panel width & height
+ ARRAYSIZE(IHNM_SavePanelButtons),
+ IHNM_SavePanelButtons,
+
+ // No protection panel in IHNM
+ -1, // protect edit index
+ 0, 0, // protect panel offsets
+ 0, 0, // protect panel width & height
+ ARRAYSIZE(IHNM_SavePanelButtons),
+ IHNM_SavePanelButtons
+};
+
+static GameResourceDescription IHNM_Resources = {
+ RID_IHNM_SCENE_LUT, // Scene lookup table RN
+ RID_IHNM_SCRIPT_LUT, // Script lookup table RN
+ RID_IHNM_MAIN_PANEL,
+ RID_IHNM_CONVERSE_PANEL,
+ RID_IHNM_OPTION_PANEL,
+ RID_IHNM_MAIN_SPRITES,
+ RID_IHNM_MAIN_PANEL_SPRITES,
+ 0,
+ RID_IHNM_MAIN_STRINGS,
+ 0
+};
+
+// I Have No Mouth and I Must Scream - Demo version
+static GameFileDescription IHNMDEMO_GameFiles[] = {
+ {"scream.res", GAME_RESOURCEFILE},
+ {"scripts.res", GAME_SCRIPTFILE},
+ {"sfx.res", GAME_SOUNDFILE},
+ {"voicesd.res", GAME_VOICEFILE}
+};
+
+// I Have No Mouth and I Must Scream - Retail CD version
+static GameFileDescription IHNMCD_GameFiles[] = {
+ {"musicfm.res", GAME_MUSICFILE_FM},
+ {"musicgm.res", GAME_MUSICFILE_GM},
+ {"scream.res", GAME_RESOURCEFILE},
+ {"patch.re_", GAME_PATCHFILE | GAME_RESOURCEFILE},
+ {"scripts.res", GAME_SCRIPTFILE},
+ {"sfx.res", GAME_SOUNDFILE},
+ {"voicess.res", GAME_VOICEFILE}, //order of voice bank file is important
+ {"voices1.res", GAME_VOICEFILE},
+ {"voices2.res", GAME_VOICEFILE},
+ {"voices3.res", GAME_VOICEFILE},
+ {"voices4.res", GAME_VOICEFILE},
+ {"voices5.res", GAME_VOICEFILE},
+ {"voices6.res", GAME_VOICEFILE}
+};
+
+// I Have No Mouth and I Must Scream - Censored CD version (without Nimdok)
+static GameFileDescription IHNMCD_Censored_GameFiles[] = {
+ {"musicfm.res", GAME_MUSICFILE_FM},
+ {"musicgm.res", GAME_MUSICFILE_GM},
+ {"scream.res", GAME_RESOURCEFILE},
+ {"scripts.res", GAME_SCRIPTFILE},
+ {"patch.re_", GAME_PATCHFILE | GAME_RESOURCEFILE},
+ {"sfx.res", GAME_SOUNDFILE},
+ {"voicess.res", GAME_VOICEFILE}, //order of voice bank file is important
+ {"voices1.res", GAME_VOICEFILE},
+ {"voices2.res", GAME_VOICEFILE},
+ {"voices3.res", GAME_VOICEFILE},
+ {"voices5.res", GAME_VOICEFILE},
+ {"voices6.res", GAME_VOICEFILE}
+};
+
+static GameFontDescription IHNMDEMO_GameFonts[] = {
+ {2},
+ {3},
+ {4}
+};
+
+static GameFontDescription IHNMCD_GameFonts[] = {
+ {2},
+ {3},
+ {4},
+ {5},
+ {6}, // kIHNMFont8
+ {7},
+ {8} // kIHNMMainFont
+};
+
+static GameSoundInfo IHNM_GameSound = {
+ kSoundWAV,
+ -1,
+ -1,
+ false,
+ false,
+ true
+};
+
+struct GameMD5 {
+ GameIds id;
+ const char *md5;
+ const char *filename;
+ bool caseSensitive;
+};
+
+#define FILE_MD5_BYTES 5000
+
+static GameMD5 gameMD5[] = {
+ { GID_ITE_DISK_G, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false },
+ { GID_ITE_DISK_G, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false },
+ { GID_ITE_DISK_G, "c46e4392fcd2e89bc91e5567db33b62d", "voices.rsc", false },
+
+ { GID_ITE_DISK_G2, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false },
+ { GID_ITE_DISK_G2, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false },
+ { GID_ITE_DISK_G2, "c46e4392fcd2e89bc91e5567db33b62d", "voices.rsc", false },
+ { GID_ITE_DISK_G2, "d6454756517f042f01210458abe8edd4", "music.rsc", false },
+
+ { GID_ITE_CD_G, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false },
+ { GID_ITE_CD_G, "50a0d2d7003c926a3832d503c8534e90", "scripts.rsc", false },
+ { GID_ITE_CD_G, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_CD_G, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false },
+
+ { GID_ITE_CD_G2, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false },
+ { GID_ITE_CD_G2, "50a0d2d7003c926a3832d503c8534e90", "scripts.rsc", false },
+ { GID_ITE_CD_G2, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_CD_G2, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false },
+ { GID_ITE_CD_G2, "d6454756517f042f01210458abe8edd4", "music.rsc", false },
+
+ { GID_ITE_CD, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false },
+ { GID_ITE_CD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false },
+ { GID_ITE_CD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_CD, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false },
+
+ // reported by mld. Bestsellergamers cover disk
+ { GID_ITE_CD_DE, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false },
+ { GID_ITE_CD_DE, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false },
+ { GID_ITE_CD_DE, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_CD_DE, "2fbad5d10b9b60a3415dc4aebbb11718", "voices.rsc", false },
+
+ { GID_ITE_CD_DE2, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false },
+ { GID_ITE_CD_DE2, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false },
+ { GID_ITE_CD_DE2, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_CD_DE2, "2fbad5d10b9b60a3415dc4aebbb11718", "voices.rsc", false },
+ { GID_ITE_CD_DE2, "d6454756517f042f01210458abe8edd4", "music.rsc", false },
+
+ { GID_ITE_DEMO_G, "986c79c4d2939dbe555576529fd37932", "ite.rsc", false },
+ { GID_ITE_DEMO_G, "d5697dd3240a3ceaddaa986c47e1a2d7", "scripts.rsc", false },
+ { GID_ITE_DEMO_G, "c58e67c506af4ffa03fd0aac2079deb0", "voices.rsc", false },
+ { GID_ITE_DEMO_G, "0b9a70eb4e120b6f00579b46c8cae29e", "ite.dmo", false },
+
+ { GID_ITE_WINCD, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false },
+ { GID_ITE_WINCD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false },
+ { GID_ITE_WINCD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_WINCD, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false },
+
+ { GID_ITE_MACCD, "4f7fa11c5175980ed593392838523060", "ite.rsc", false },
+ { GID_ITE_MACCD, "adf1f46c1d0589083996a7060c798ad0", "scripts.rsc", false },
+ { GID_ITE_MACCD, "1a91cd60169f367ecb6c6e058d899b2f", "music.rsc", false },
+ { GID_ITE_MACCD, "95863b89a0916941f6c5e1789843ba14", "sounds.rsc", false },
+ { GID_ITE_MACCD, "c14c4c995e7a0d3828e3812a494301b7", "Inherit the Earth Voices", true },
+
+ { GID_ITE_MACCD_G, "0bd506aa887bfc7965f695c6bd28237d", "ITE Resources.bin", true },
+ { GID_ITE_MACCD_G, "af0d7a2588e09ad3ecbc5b474ea238bf", "ITE Scripts.bin", true },
+ { GID_ITE_MACCD_G, "c1d20324b7cdf1650e67061b8a93251c", "ITE Music.bin", true },
+ { GID_ITE_MACCD_G, "441426c6bb2a517f65c7e49b57f7a345", "ITE Sounds.bin", true },
+ { GID_ITE_MACCD_G, "dba92ae7d57e942250fe135609708369", "ITE Voices.bin", true },
+
+ { GID_ITE_LINCD, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false },
+ { GID_ITE_LINCD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false },
+ { GID_ITE_LINCD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_LINCD, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false },
+ { GID_ITE_LINCD, "d6454756517f042f01210458abe8edd4", "music.rsc", false },
+
+ { GID_ITE_MULTICD, "a6433e34b97b15e64fe8214651012db9", "ite.rsc", false },
+ { GID_ITE_MULTICD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false },
+ { GID_ITE_MULTICD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false },
+ { GID_ITE_MULTICD, "c14c4c995e7a0d3828e3812a494301b7", "Inherit the Earth Voices", true },
+ { GID_ITE_MULTICD, "d6454756517f042f01210458abe8edd4", "music.rsc", false },
+
+ { GID_ITE_DISK_DE, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false },
+ { GID_ITE_DISK_DE, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false },
+ { GID_ITE_DISK_DE, "0c9113e630f97ef0996b8c3114badb08", "voices.rsc", false },
+
+ { GID_ITE_DISK_DE2, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false },
+ { GID_ITE_DISK_DE2, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false },
+ { GID_ITE_DISK_DE2, "0c9113e630f97ef0996b8c3114badb08", "voices.rsc", false },
+ { GID_ITE_DISK_DE2, "d6454756517f042f01210458abe8edd4", "music.rsc", false },
+
+ { GID_ITE_WINDEMO2, "3a450852cbf3c80773984d565647e6ac", "ited.rsc", false },
+ { GID_ITE_WINDEMO2, "3f12b67fa93e56e1a6be39d2921d80bb", "scriptsd.rsc", false },
+ { GID_ITE_WINDEMO2, "95a6c148e22e99a8c243f2978223583c", "soundsd.rsc", false },
+ { GID_ITE_WINDEMO2, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false },
+
+ { GID_ITE_LINDEMO, "3a450852cbf3c80773984d565647e6ac", "ited.rsc", false },
+ { GID_ITE_LINDEMO, "3f12b67fa93e56e1a6be39d2921d80bb", "scriptsd.rsc", false },
+ { GID_ITE_LINDEMO, "d6454756517f042f01210458abe8edd4", "musicd.rsc", false },
+ { GID_ITE_LINDEMO, "95a6c148e22e99a8c243f2978223583c", "soundsd.rsc", false },
+ { GID_ITE_LINDEMO, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false },
+
+ { GID_ITE_MACDEMO2, "addfc9d82bc2fa1f4cab23743c652c08", "ited.rsc", false },
+ { GID_ITE_MACDEMO2, "fded5c59b8b7c5976229f960d21e6b0b", "scriptsd.rsc", false },
+ { GID_ITE_MACDEMO2, "495bdde51fd9f4bea2b9c911091b1ab2", "musicd.rsc", false },
+ { GID_ITE_MACDEMO2, "b3a831fbed337d1f1300fee1dd474f6c", "soundsd.rsc", false },
+ { GID_ITE_MACDEMO2, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false },
+
+ { GID_ITE_WINDEMO1, "3a450852cbf3c80773984d565647e6ac", "ited.rsc", false },
+ { GID_ITE_WINDEMO1, "3f12b67fa93e56e1a6be39d2921d80bb", "scriptsd.rsc", false },
+ { GID_ITE_WINDEMO1, "a741139dd7365a13f463cd896ff9969a", "soundsd.rsc", false },
+ { GID_ITE_WINDEMO1, "0759eaf5b64ae19fd429920a70151ad3", "voicesd.rsc", false },
+
+ { GID_ITE_MACDEMO1, "addfc9d82bc2fa1f4cab23743c652c08", "ited.rsc", false },
+ { GID_ITE_MACDEMO1, "fded5c59b8b7c5976229f960d21e6b0b", "scriptsd.rsc", false },
+ { GID_ITE_MACDEMO1, "1a91cd60169f367ecb6c6e058d899b2f", "musicd.rsc", false },
+ { GID_ITE_MACDEMO1, "b3a831fbed337d1f1300fee1dd474f6c", "soundsd.rsc", false },
+ { GID_ITE_MACDEMO1, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false },
+
+ { GID_IHNM_CD, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false },
+ { GID_IHNM_CD, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false },
+ { GID_IHNM_CD, "46bbdc65d164ba7e89836a0935eec8e6", "scream.res", false },
+ { GID_IHNM_CD, "be38bbc5a26be809dbf39f13befebd01", "scripts.res", false },
+ { GID_IHNM_CD, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false },
+ { GID_IHNM_CD, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false },
+ { GID_IHNM_CD, "fc6440b38025f4b2cc3ff55c3da5c3eb", "voices1.res", false },
+ { GID_IHNM_CD, "b37f10fd1696ade7d58704ccaaebceeb", "voices2.res", false },
+ { GID_IHNM_CD, "3bbc16a8f741dbb511da506c660a0b54", "voices3.res", false },
+ { GID_IHNM_CD, "ebfa160122d2247a676ca39920e5d481", "voices4.res", false },
+ { GID_IHNM_CD, "1f501ce4b72392bdd1d9ec38f6eec6da", "voices5.res", false },
+ { GID_IHNM_CD, "f580ed7568c7d6ef34e934ba20adf834", "voices6.res", false },
+ { GID_IHNM_CD, "54b1f2013a075338ceb0e258d97808bd", "voicess.res", false },
+
+ // Reported by mld. German Retail
+ { GID_IHNM_CD_DE, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false },
+ { GID_IHNM_CD_DE, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false },
+ { GID_IHNM_CD_DE, "c92370d400e6f2a3fc411c3729d09224", "scream.res", false },
+ { GID_IHNM_CD_DE, "32aa01a89937520fe0ea513950117292", "scripts.res", false },
+ { GID_IHNM_CD_DE, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false },
+ { GID_IHNM_CD_DE, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false },
+ { GID_IHNM_CD_DE, "424971e1e2373187c3f5734fe36071a2", "voices1.res", false },
+ { GID_IHNM_CD_DE, "c270e0980782af43641a86e4a14e2a32", "voices2.res", false },
+ { GID_IHNM_CD_DE, "49e42befea883fd101ec3d0f5d0647b9", "voices3.res", false },
+ { GID_IHNM_CD_DE, "c477443c52a0aa56e686ebd8d051e4ab", "voices5.res", false },
+ { GID_IHNM_CD_DE, "2b9aea838f74b4eecfb29a8f205a2bd4", "voices6.res", false },
+ { GID_IHNM_CD_DE, "8b09a196a52627cacb4eab13bfe0b2c3", "voicess.res", false },
+
+ { GID_IHNM_CD_ES, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false },
+ { GID_IHNM_CD_ES, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false },
+ { GID_IHNM_CD_ES, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false },
+ { GID_IHNM_CD_ES, "c92370d400e6f2a3fc411c3729d09224", "scream.res", false },
+ { GID_IHNM_CD_ES, "be38bbc5a26be809dbf39f13befebd01", "scripts.res", false },
+ { GID_IHNM_CD_ES, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false },
+ { GID_IHNM_CD_ES, "dc6a34e3d1668730ea46815a92c7847f", "voices1.res", false },
+ { GID_IHNM_CD_ES, "dc6a5fa7a4cdc2ca5a6fd924e969986c", "voices2.res", false },
+ { GID_IHNM_CD_ES, "dc6a5fa7a4cdc2ca5a6fd924e969986c", "voices3.res", false },
+ { GID_IHNM_CD_ES, "0f87400b804232a58dd22e404420cc45", "voices4.res", false },
+ { GID_IHNM_CD_ES, "172668cfc5d8c305cb5b1a9b4d995fc0", "voices5.res", false },
+ { GID_IHNM_CD_ES, "96c9bda9a5f41d6bc232ed7bf6d371d9", "voices6.res", false },
+ { GID_IHNM_CD_ES, "d869de9883c8faea7f687217a9ec7057", "voicess.res", false },
+
+ { GID_IHNM_CD_RU, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false },
+ { GID_IHNM_CD_RU, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false },
+ { GID_IHNM_CD_RU, "46bbdc65d164ba7e89836a0935eec8e6", "scream.res", false },
+ { GID_IHNM_CD_RU, "be38bbc5a26be809dbf39f13befebd01", "scripts.res", false },
+ { GID_IHNM_CD_RU, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false },
+ { GID_IHNM_CD_RU, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false },
+ { GID_IHNM_CD_RU, "d6100d2dc3b2b9f2e1ad247f613dce9b", "voices1.res", false },
+ { GID_IHNM_CD_RU, "84f6f48ecc2832841ea6417a9a379430", "voices2.res", false },
+ { GID_IHNM_CD_RU, "ebb9501283047f27a0f54e27b3c8ba1e", "voices3.res", false },
+ { GID_IHNM_CD_RU, "4c145da5fa6d1306162a7ca8ce5a4f2e", "voices4.res", false },
+ { GID_IHNM_CD_RU, "871a559644281917677eca4af1b05620", "voices5.res", false },
+ { GID_IHNM_CD_RU, "211be5c24f066d69a2f6cfa953acfba6", "voices6.res", false },
+ { GID_IHNM_CD_RU, "9df7cd3b18ddaa16b5291b3432567036", "voicess.res", false },
+
+ { GID_IHNM_CD_FR, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false },
+ { GID_IHNM_CD_FR, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false },
+ { GID_IHNM_CD_FR, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false },
+ { GID_IHNM_CD_FR, "c92370d400e6f2a3fc411c3729d09224", "scream.res", false },
+ { GID_IHNM_CD_FR, "32aa01a89937520fe0ea513950117292", "scripts.res", false },
+ { GID_IHNM_CD_FR, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false },
+ { GID_IHNM_CD_FR, "424971e1e2373187c3f5734fe36071a2", "voices1.res", false },
+ { GID_IHNM_CD_FR, "c2d93a35d2c2def9c3d6d242576c794b", "voices2.res", false },
+ { GID_IHNM_CD_FR, "49e42befea883fd101ec3d0f5d0647b9", "voices3.res", false },
+ { GID_IHNM_CD_FR, "f4c415de7c03de86b73f9a12b8bd632f", "voices5.res", false },
+ { GID_IHNM_CD_FR, "3fc5358a5d8eee43bdfab2740276572e", "voices6.res", false },
+ { GID_IHNM_CD_FR, "b8642e943bbebf89cef2f48b31cb4305", "voicess.res", false },
+
+ { GID_IHNM_DEMO, "46bbdc65d164ba7e89836a0935eec8e6", "scream.res", false },
+ { GID_IHNM_DEMO, "9626bda8978094ff9b29198bc1ed5f9a", "scripts.res", false },
+ { GID_IHNM_DEMO, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false },
+ { GID_IHNM_DEMO, "3bbc16a8f741dbb511da506c660a0b54", "voicesd.res", false },
+};
+
+static GameDescription gameDescriptions[] = {
+ // Inherit the earth - DOS Demo version
+ // sound unchecked
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_DEMO_G, // Game id
+ "Inherit the Earth: Quest for the Orb (DOS Demo)", // Game title
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE, // Starting scene number
+ &ITEDemo_Resources,
+ ARRAYSIZE(ITEDEMO_GameFiles), // Game datafiles
+ ITEDEMO_GameFiles,
+ ARRAYSIZE(ITEDEMO_GameFonts),
+ ITEDEMO_GameFonts,
+ &ITEDEMO_GameSound,
+ &ITEDEMO_GameSound,
+ NULL,
+ 0,
+ NULL,
+ 0, // features
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - MAC Demo version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_MACDEMO2,
+ "Inherit the Earth: Quest for the Orb (MAC Demo)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEMACDEMO_GameFiles),
+ ITEMACDEMO_GameFiles,
+ ARRAYSIZE(ITEWINDEMO_GameFonts),
+ ITEWINDEMO_GameFonts,
+ &ITEMACDEMO_GameVoice,
+ &ITEMACDEMO_GameSound,
+ &ITEMACDEMO_GameMusic,
+ ARRAYSIZE(ITEMacPatch_Files),
+ ITEMacPatch_Files,
+ GF_BIG_ENDIAN_DATA | GF_WYRMKEEP | GF_CD_FX | GF_SCENE_SUBSTITUTES,
+ Common::EN_USA,
+ Common::kPlatformMacintosh,
+ },
+
+ // Inherit the earth - early MAC Demo version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_MACDEMO1,
+ "Inherit the Earth: Quest for the Orb (early MAC Demo)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEMACDEMO_GameFiles),
+ ITEMACDEMO_GameFiles,
+ ARRAYSIZE(ITEWINDEMO_GameFonts),
+ ITEWINDEMO_GameFonts,
+ &ITEMACDEMO_GameVoice,
+ &ITEMACDEMO_GameSound,
+ &ITEMACCD_GameMusic,
+ ARRAYSIZE(ITEMacPatch_Files),
+ ITEMacPatch_Files,
+ GF_BIG_ENDIAN_DATA | GF_WYRMKEEP | GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformMacintosh,
+ },
+
+ // Inherit the earth - MAC CD Guild version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_MACCD_G,
+ "Inherit the Earth: Quest for the Orb (MAC CD)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEMACCD_G_GameFiles),
+ ITEMACCD_G_GameFiles,
+ ARRAYSIZE(ITEWINDEMO_GameFonts),
+ ITEWINDEMO_GameFonts,
+ &ITEMACCD_G_GameSound,
+ &ITEMACCD_G_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_BIG_ENDIAN_DATA | GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformMacintosh,
+ },
+
+ // Inherit the earth - MAC CD Wyrmkeep version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_MACCD,
+ "Inherit the Earth: Quest for the Orb (Wyrmkeep MAC CD)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEMACCD_GameFiles),
+ ITEMACCD_GameFiles,
+ ARRAYSIZE(ITEWINDEMO_GameFonts),
+ ITEWINDEMO_GameFonts,
+ &ITEMACCD_GameSound,
+ &ITEMACCD_GameSound,
+ &ITEMACCD_GameMusic,
+ ARRAYSIZE(ITEMacPatch_Files),
+ ITEMacPatch_Files,
+ GF_BIG_ENDIAN_DATA | GF_WYRMKEEP | GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformMacintosh,
+ },
+
+ // Inherit the earth - Linux Demo version
+ // Note: it should be before GID_ITE_WINDEMO2 version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_LINDEMO,
+ "Inherit the Earth: Quest for the Orb (Linux Demo)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITELINDEMO_GameFiles),
+ ITELINDEMO_GameFiles,
+ ARRAYSIZE(ITEWINDEMO_GameFonts),
+ ITEWINDEMO_GameFonts,
+ &ITEWINDEMO2_GameVoice,
+ &ITEWINDEMO2_GameSound,
+ &ITELINDEMO_GameMusic,
+ ARRAYSIZE(ITELinPatch_Files),
+ ITELinPatch_Files,
+ GF_WYRMKEEP | GF_CD_FX | GF_SCENE_SUBSTITUTES,
+ Common::EN_USA,
+ Common::kPlatformLinux,
+ },
+
+ // Inherit the earth - Win32 Demo version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_WINDEMO2,
+ "Inherit the Earth: Quest for the Orb (Win32 Demo)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEWINDEMO_GameFiles),
+ ITEWINDEMO_GameFiles,
+ ARRAYSIZE(ITEWINDEMO_GameFonts),
+ ITEWINDEMO_GameFonts,
+ &ITEWINDEMO2_GameVoice,
+ &ITEWINDEMO2_GameSound,
+ NULL,
+ ARRAYSIZE(ITEWinPatch2_Files),
+ ITEWinPatch2_Files,
+ GF_WYRMKEEP | GF_CD_FX | GF_SCENE_SUBSTITUTES,
+ Common::EN_USA,
+ Common::kPlatformWindows,
+ },
+
+ // Inherit the earth - early Win32 Demo version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_WINDEMO1,
+ "Inherit the Earth: Quest for the Orb (early Win32 Demo)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEWINDEMO_GameFiles),
+ ITEWINDEMO_GameFiles,
+ ARRAYSIZE(ITEWINDEMO_GameFonts),
+ ITEWINDEMO_GameFonts,
+ &ITEWINDEMO1_GameSound,
+ &ITEWINDEMO1_GameSound,
+ NULL,
+ ARRAYSIZE(ITEWinPatch1_Files),
+ ITEWinPatch1_Files,
+ GF_WYRMKEEP | GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformWindows,
+ },
+
+ // Inherit the earth - Wyrmkeep combined Windows/Mac/Linux CD
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_MULTICD,
+ "Inherit the Earth: Quest for the Orb (Multi-OS CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEMULTICD_GameFiles),
+ ITEMULTICD_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITEMACCD_GameSound,
+ &ITECD_GameSound,
+ &ITEMACCD_GameMusic,
+ 0,
+ NULL,
+ GF_WYRMKEEP | GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformUnknown,
+ },
+
+ // Inherit the earth - Wyrmkeep Linux CD version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_LINCD,
+ "Inherit the Earth: Quest for the Orb (Linux CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITELINCD_GameFiles),
+ ITELINCD_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITECD_GameSound,
+ &ITECD_GameSound,
+ &ITEMACCD_GameMusic,
+ ARRAYSIZE(ITELinPatch_Files),
+ ITELinPatch_Files,
+ GF_WYRMKEEP | GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformLinux,
+ },
+
+ // Inherit the earth - Wyrmkeep Windows CD version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_WINCD,
+ "Inherit the Earth: Quest for the Orb (Win32 CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITECD_GameFiles),
+ ITECD_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITECD_GameSound,
+ &ITECD_GameSound,
+ NULL,
+ ARRAYSIZE(ITEWinPatch1_Files),
+ ITEWinPatch1_Files,
+ GF_WYRMKEEP | GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformWindows,
+ },
+
+ // Inherit the earth - DOS CD version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_CD_G,
+ "Inherit the Earth: Quest for the Orb (DOS CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITECD_GameFiles),
+ ITECD_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITECD_GameSound,
+ &ITECD_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - DOS CD version with digital music
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_CD_G2,
+ "Inherit the Earth: Quest for the Orb (DOS CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITECD2_GameFiles),
+ ITECD2_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITECD_GameSound,
+ &ITECD_GameSound,
+ &ITEMACCD_GameMusic,
+ 0,
+ NULL,
+ GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - DOS CD German version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_CD_DE,
+ "Inherit the Earth: Quest for the Orb (De DOS CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITECD_GameFiles),
+ ITECD_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITECD_GameSound,
+ &ITECD_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_CD_FX,
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - DOS CD German version with digital music
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_CD_DE2,
+ "Inherit the Earth: Quest for the Orb (De DOS CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITECD2_GameFiles),
+ ITECD2_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITECD_GameSound,
+ &ITECD_GameSound,
+ &ITEMACCD_GameMusic,
+ 0,
+ NULL,
+ GF_CD_FX,
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - CD version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_CD,
+ "Inherit the Earth: Quest for the Orb (DOS CD Version)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITECD_GameFiles),
+ ITECD_GameFiles,
+ ARRAYSIZE(ITECD_GameFonts),
+ ITECD_GameFonts,
+ &ITECD_GameSound,
+ &ITECD_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_CD_FX,
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - German Floppy version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_DISK_DE,
+ "Inherit the Earth: Quest for the Orb (De DOS Floppy)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEDISK_GameFiles),
+ ITEDISK_GameFiles,
+ ARRAYSIZE(ITEDISK_GameFonts),
+ ITEDISK_GameFonts,
+ &ITEDISK_GameSound,
+ &ITEDISK_GameSound,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - German Floppy version with digital music
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_DISK_DE2,
+ "Inherit the Earth: Quest for the Orb (De DOS Floppy)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEDISK2_GameFiles),
+ ITEDISK2_GameFiles,
+ ARRAYSIZE(ITEDISK_GameFonts),
+ ITEDISK_GameFonts,
+ &ITEDISK_GameSound,
+ &ITEDISK_GameSound,
+ &ITEMACCD_GameMusic,
+ 0,
+ NULL,
+ 0,
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - Disk version
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_DISK_G,
+ "Inherit the Earth: Quest for the Orb (DOS Floppy)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEDISK_GameFiles),
+ ITEDISK_GameFiles,
+ ARRAYSIZE(ITEDISK_GameFonts),
+ ITEDISK_GameFonts,
+ &ITEDISK_GameSound,
+ &ITEDISK_GameSound,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // Inherit the earth - Disk version with digital music
+ {
+ "ite",
+ GType_ITE,
+ GID_ITE_DISK_G2,
+ "Inherit the Earth: Quest for the Orb (DOS Floppy)",
+ &ITE_DisplayInfo,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITEDISK2_GameFiles),
+ ITEDISK2_GameFiles,
+ ARRAYSIZE(ITEDISK_GameFonts),
+ ITEDISK_GameFonts,
+ &ITEDISK_GameSound,
+ &ITEDISK_GameSound,
+ &ITEMACCD_GameMusic,
+ 0,
+ NULL,
+ 0,
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // I Have No Mouth And I Must Scream - Demo version
+ {
+ "ihnm",
+ GType_IHNM,
+ GID_IHNM_DEMO,
+ "I Have No Mouth and I Must Scream (DOS Demo)",
+ &IHNM_DisplayInfo,
+ 0,
+ &IHNM_Resources,
+ ARRAYSIZE(IHNMDEMO_GameFiles),
+ IHNMDEMO_GameFiles,
+ ARRAYSIZE(IHNMDEMO_GameFonts),
+ IHNMDEMO_GameFonts,
+ &IHNM_GameSound,
+ &IHNM_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_DEFAULT_TO_1X_SCALER,
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // I Have No Mouth And I Must Scream - CD version
+ {
+ "ihnm",
+ GType_IHNM,
+ GID_IHNM_CD,
+ "I Have No Mouth and I Must Scream (DOS)",
+ &IHNM_DisplayInfo,
+ IHNM_DEFAULT_SCENE,
+ &IHNM_Resources,
+ ARRAYSIZE(IHNMCD_GameFiles),
+ IHNMCD_GameFiles,
+ ARRAYSIZE(IHNMCD_GameFonts),
+ IHNMCD_GameFonts,
+ &IHNM_GameSound,
+ &IHNM_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_DEFAULT_TO_1X_SCALER,
+ Common::EN_USA,
+ Common::kPlatformPC,
+ },
+
+ // I Have No Mouth And I Must Scream - De CD version
+ {
+ "ihnm",
+ GType_IHNM,
+ GID_IHNM_CD_DE,
+ "I Have No Mouth and I Must Scream (DE DOS)",
+ &IHNM_DisplayInfo,
+ IHNM_DEFAULT_SCENE,
+ &IHNM_Resources,
+ ARRAYSIZE(IHNMCD_Censored_GameFiles),
+ IHNMCD_Censored_GameFiles,
+ ARRAYSIZE(IHNMCD_GameFonts),
+ IHNMCD_GameFonts,
+ &IHNM_GameSound,
+ &IHNM_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_DEFAULT_TO_1X_SCALER,
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ },
+ // I Have No Mouth And I Must Scream - Sp CD version
+ {
+ "ihnm",
+ GType_IHNM,
+ GID_IHNM_CD_ES,
+ "I Have No Mouth and I Must Scream (Sp DOS)",
+ &IHNM_DisplayInfo,
+ IHNM_DEFAULT_SCENE,
+ &IHNM_Resources,
+ ARRAYSIZE(IHNMCD_GameFiles),
+ IHNMCD_GameFiles,
+ ARRAYSIZE(IHNMCD_GameFonts),
+ IHNMCD_GameFonts,
+ &IHNM_GameSound,
+ &IHNM_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_DEFAULT_TO_1X_SCALER,
+ Common::ES_ESP,
+ Common::kPlatformPC,
+ },
+ // I Have No Mouth And I Must Scream - Ru CD version
+ {
+ "ihnm",
+ GType_IHNM,
+ GID_IHNM_CD_RU,
+ "I Have No Mouth and I Must Scream (Ru DOS)",
+ &IHNM_DisplayInfo,
+ IHNM_DEFAULT_SCENE,
+ &IHNM_Resources,
+ ARRAYSIZE(IHNMCD_GameFiles),
+ IHNMCD_GameFiles,
+ ARRAYSIZE(IHNMCD_GameFonts),
+ IHNMCD_GameFonts,
+ &IHNM_GameSound,
+ &IHNM_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_DEFAULT_TO_1X_SCALER,
+ Common::RU_RUS,
+ Common::kPlatformPC,
+ },
+ // I Have No Mouth And I Must Scream - Fr CD version
+ {
+ "ihnm",
+ GType_IHNM,
+ GID_IHNM_CD_FR,
+ "I Have No Mouth and I Must Scream (Fr DOS)",
+ &IHNM_DisplayInfo,
+ IHNM_DEFAULT_SCENE,
+ &IHNM_Resources,
+ ARRAYSIZE(IHNMCD_Censored_GameFiles),
+ IHNMCD_Censored_GameFiles,
+ ARRAYSIZE(IHNMCD_GameFonts),
+ IHNMCD_GameFonts,
+ &IHNM_GameSound,
+ &IHNM_GameSound,
+ NULL,
+ 0,
+ NULL,
+ GF_DEFAULT_TO_1X_SCALER,
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ },
+};
+
+bool SagaEngine::initGame() {
+ uint16 gameCount = ARRAYSIZE(gameDescriptions);
+ int gameNumber = -1;
+ FSList dummy;
+ DetectedGameList detectedGames;
+ int *matches;
+ Common::Language language = Common::UNK_LANG;
+ Common::Platform platform = Common::kPlatformUnknown;
+
+ if (ConfMan.hasKey("language"))
+ language = Common::parseLanguage(ConfMan.get("language"));
+ if (ConfMan.hasKey("platform"))
+ platform = Common::parsePlatform(ConfMan.get("platform"));
+
+
+ detectedGames = GAME_ProbeGame(dummy, &matches);
+
+ if (detectedGames.size() == 0) {
+ warning("No valid games were found in the specified directory.");
+ return false;
+ }
+
+ // If we have more than one match then try to match by platform and
+ // language
+ int count = 0;
+ if (detectedGames.size() > 1) {
+ for (int i = 0; i < ARRAYSIZE(gameDescriptions); i++)
+ if (matches[i] != -1) {
+ if ((gameDescriptions[matches[i]].language != language &&
+ language != Common::UNK_LANG) ||
+ (gameDescriptions[matches[i]].platform != platform &&
+ platform != Common::kPlatformUnknown)) {
+ debug(2, "Purged (pass 2) %s", gameDescriptions[matches[i]].title);
+ matches[i] = -1;
+ }
+ else
+ count++;
+ }
+ } else
+ count = 1;
+
+ if (count != 1)
+ warning("Conflicting targets detected (%d)", count);
+
+ for (int i = 0; i < ARRAYSIZE(gameDescriptions); i++)
+ if (matches[i] != -1) {
+ gameNumber = matches[i];
+ break;
+ }
+
+ free(matches);
+
+ if (gameNumber >= gameCount || gameNumber == -1) {
+ error("SagaEngine::loadGame wrong gameNumber");
+ }
+
+ debug(2, "Running %s", gameDescriptions[gameNumber].title);
+
+ _gameNumber = gameNumber;
+ _gameDescription = &gameDescriptions[gameNumber];
+ _gameDisplayInfo = *_gameDescription->gameDisplayInfo;
+ _displayClip.right = _gameDisplayInfo.logicalWidth;
+ _displayClip.bottom = _gameDisplayInfo.logicalHeight;
+
+ if (!_resource->createContexts()) {
+ return false;
+ }
+ return true;
+}
+
+DetectedGameList GAME_ProbeGame(const FSList &fslist, int **retmatches) {
+ DetectedGameList detectedGames;
+ int game_n;
+ int index = 0, i, j;
+ int matches[ARRAYSIZE(gameDescriptions)];
+ bool mode = retmatches ? false : true;
+
+ game_n = -1;
+ for (i = 0; i < ARRAYSIZE(gameDescriptions); i++)
+ matches[i] = -1;
+
+ while (1) {
+ game_n = detectGame(fslist, mode, game_n);
+ if (game_n == -1)
+ break;
+ matches[index++] = game_n;
+ }
+
+ // We have some resource sets which are superpositions of other
+ // Particularly it is ite-demo-linux vs ite-demo-win
+ // Now remove lesser set if bigger matches too
+
+ if (index > 1) {
+ // Search max number
+ int maxcount = 0;
+ for (i = 0; i < index; i++) {
+ int count = 0;
+ for (j = 0; j < ARRAYSIZE(gameMD5); j++)
+ if (gameMD5[j].id == gameDescriptions[matches[i]].gameId)
+ count++;
+ maxcount = MAX(maxcount, count);
+ }
+
+ // Now purge targets with number of files lesser than max
+ for (i = 0; i < index; i++) {
+ int count = 0;
+ for (j = 0; j < ARRAYSIZE(gameMD5); j++)
+ if (gameMD5[j].id == gameDescriptions[matches[i]].gameId)
+ count++;
+ if (count < maxcount) {
+ debug(2, "Purged: %s", gameDescriptions[matches[i]].title);
+ matches[i] = -1;
+ }
+ }
+
+ }
+
+ // and now push them into list of detected games
+ for (i = 0; i < index; i++)
+ if (matches[i] != -1)
+ detectedGames.push_back(DetectedGame(gameDescriptions[matches[i]].toGameSettings(),
+ gameDescriptions[matches[i]].language,
+ gameDescriptions[matches[i]].platform));
+
+ if (retmatches) {
+ *retmatches = (int *)calloc(ARRAYSIZE(gameDescriptions), sizeof(int));
+ for (i = 0; i < ARRAYSIZE(gameDescriptions); i++)
+ (*retmatches)[i] = matches[i];
+ }
+
+ return detectedGames;
+}
+
+int detectGame(const FSList &fslist, bool mode, int start) {
+ int game_count = ARRAYSIZE(gameDescriptions);
+ int game_n = -1;
+ typedef Common::Map<Common::String, Common::String> StringMap;
+ StringMap filesMD5;
+
+ typedef Common::Map<Common::String, bool> StringSet;
+ StringSet filesList;
+
+ uint16 file_count;
+ uint16 file_n;
+ Common::File test_file;
+ bool file_missing;
+
+ Common::String tstr, tstr1;
+ char md5str[32+1];
+ uint8 md5sum[16];
+
+ // First we compose list of files which we need MD5s for
+ for (int i = 0; i < ARRAYSIZE(gameMD5); i++) {
+ tstr = Common::String(gameMD5[i].filename);
+ tstr.toLowercase();
+
+ if (gameMD5[i].caseSensitive && !mode)
+ filesList[Common::String(gameMD5[i].filename)] = true;
+ else
+ filesList[tstr] = true;
+ }
+
+ if (mode) {
+ // Now count MD5s for required files
+ for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+ if (!file->isDirectory()) {
+ tstr = file->displayName();
+ // FIXME: there is a bug in String class. tstr1 = tstr; tstr.toLowercase()
+ // makes tstr1 lowercase as well
+ tstr1 = Common::String(file->displayName().c_str());
+ tstr.toLowercase();
+
+ if (filesList.contains(tstr) || filesList.contains(tstr1)) {
+ if (Common::md5_file(file->path().c_str(), md5sum, NULL, FILE_MD5_BYTES)) {
+ for (int j = 0; j < 16; j++) {
+ sprintf(md5str + j*2, "%02x", (int)md5sum[j]);
+ }
+ filesMD5[tstr] = Common::String(md5str);
+ filesMD5[tstr1] = Common::String(md5str);
+ }
+ }
+ }
+ }
+ } else {
+ Common::File testFile;
+
+ for (StringSet::const_iterator file = filesList.begin(); file != filesList.end(); ++file) {
+ if (testFile.open(file->_key.c_str())) {
+ testFile.close();
+ if (Common::md5_file(file->_key.c_str(), md5sum, NULL, FILE_MD5_BYTES)) {
+ for (int j = 0; j < 16; j++) {
+ sprintf(md5str + j*2, "%02x", (int)md5sum[j]);
+ }
+ filesMD5[file->_key] = Common::String(md5str);
+ }
+ }
+ }
+ }
+
+ for (game_n = start + 1; game_n < game_count; game_n++) {
+ file_count = gameDescriptions[game_n].filesCount;
+ file_missing = false;
+
+ // Try to open all files for this game
+ for (file_n = 0; file_n < file_count; file_n++) {
+ tstr = gameDescriptions[game_n].filesDescriptions[file_n].fileName;
+
+ if (!filesMD5.contains(tstr)) {
+ file_missing = true;
+ break;
+ }
+ }
+
+ // Try the next game, couldn't find all files for the current
+ // game
+ if (file_missing) {
+ continue;
+ } else {
+ bool match = true;
+
+ debug(2, "Probing game: %s", gameDescriptions[game_n].title);
+
+ for (int i = 0; i < ARRAYSIZE(gameMD5); i++) {
+ if (gameMD5[i].id == gameDescriptions[game_n].gameId) {
+ tstr = gameMD5[i].filename;
+
+ if (strcmp(gameMD5[i].md5, filesMD5[tstr].c_str())) {
+ match = false;
+ break;
+ }
+ }
+ }
+ if (!match)
+ continue;
+
+ debug(2, "Found game: %s", gameDescriptions[game_n].title);
+
+ return game_n;
+ }
+ }
+
+ if (!filesMD5.isEmpty() && start == -1) {
+ printf("MD5s of your game version are unknown. Please, report following data to\n");
+ printf("ScummVM team along with your game name and version:\n");
+
+ for (StringMap::const_iterator file = filesMD5.begin(); file != filesMD5.end(); ++file)
+ printf("%s: %s\n", file->_key.c_str(), file->_value.c_str());
+ }
+
+ return -1;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/gfx.cpp b/engines/saga/gfx.cpp
new file mode 100644
index 0000000000..4de8c52de2
--- /dev/null
+++ b/engines/saga/gfx.cpp
@@ -0,0 +1,485 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Misc. graphics routines
+
+#include "saga/saga.h"
+#include "saga/gfx.h"
+#include "saga/interface.h"
+#include "saga/resnames.h"
+#include "saga/rscfile.h"
+#include "saga/scene.h"
+#include "saga/stream.h"
+
+#include "common/system.h"
+
+namespace Saga {
+
+Gfx::Gfx(SagaEngine *vm, OSystem *system, int width, int height, GameDetector &detector) : _vm(vm), _system(system) {
+ _system->beginGFXTransaction();
+ _vm->initCommonGFX(detector);
+ _system->initSize(width, height);
+ _system->endGFXTransaction();
+
+ debug(5, "Init screen %dx%d", width, height);
+ // Convert surface data to R surface data
+ _backBuffer.create(width, height, 1);
+
+ // Set module data
+ _init = 1;
+
+ // Start with the cursor shown. It will be hidden before the intro, if
+ // there is an intro. (With boot params, there may not be.)
+ setCursor(kCursorNormal);
+ showCursor(true);
+}
+
+Gfx::~Gfx() {
+ _backBuffer.free();
+}
+
+void Surface::drawPalette() {
+ int x;
+ int y;
+ int color = 0;
+ Rect palRect;
+
+ for (y = 0; y < 16; y++) {
+ palRect.top = (y * 8) + 4;
+ palRect.bottom = palRect.top + 8;
+
+ for (x = 0; x < 16; x++) {
+ palRect.left = (x * 8) + 4;
+ palRect.right = palRect.left + 8;
+
+ drawRect(palRect, color);
+ color++;
+ }
+ }
+}
+
+// * Copies a rectangle from a raw 8 bit pixel buffer to the specified surface.
+// - The surface must match the logical dimensions of the buffer exactly.
+void Surface::blit(const Common::Rect &destRect, const byte *sourceBuffer) {
+ const byte *readPointer;
+ byte *writePointer;
+ int row;
+ ClipData clipData;
+
+ clipData.sourceRect.left = 0;
+ clipData.sourceRect.top = 0;
+ clipData.sourceRect.right = destRect.width();
+ clipData.sourceRect.bottom = destRect.height();
+
+ clipData.destPoint.x = destRect.left;
+ clipData.destPoint.y = destRect.top;
+ clipData.destRect.left = 0;
+ clipData.destRect.right = w;
+ clipData.destRect.top = 0;
+ clipData.destRect.bottom = h;
+
+ if (!clipData.calcClip()) {
+ return;
+ }
+
+ // Transfer buffer data to surface
+ readPointer = (sourceBuffer + clipData.drawSource.x) +
+ (clipData.sourceRect.right * clipData.drawSource.y);
+
+ writePointer = ((byte *)pixels + clipData.drawDest.x) + (pitch * clipData.drawDest.y);
+
+ for (row = 0; row < clipData.drawHeight; row++) {
+ memcpy(writePointer, readPointer, clipData.drawWidth);
+
+ writePointer += pitch;
+ readPointer += clipData.sourceRect.right;
+ }
+}
+
+void Surface::drawPolyLine(const Point *points, int count, int color) {
+ int i;
+ if (count >= 3) {
+ for (i = 1; i < count; i++) {
+ drawLine(points[i].x, points[i].y, points[i - 1].x, points[i - 1].y, color);
+ }
+
+ drawLine(points[count - 1].x, points[count - 1].y, points->x, points->y, color);
+ }
+}
+
+/**
+* Dissolve one image with another.
+* If flags if set to 1, do zero masking.
+*/
+void Surface::transitionDissolve(const byte *sourceBuffer, const Common::Rect &sourceRect, int flags, double percent) {
+#define XOR_MASK 0xB400;
+ int pixelcount = w * h;
+ int seqlimit = (int)(65535 * percent);
+ int seq = 1;
+ int i, x1, y1;
+ byte color;
+
+ for (i = 0; i < seqlimit; i++) {
+ if (seq & 1) {
+ seq = (seq >> 1) ^ XOR_MASK;
+ } else {
+ seq = seq >> 1;
+ }
+
+ if (seq == 1) {
+ return;
+ }
+
+ if (seq >= pixelcount) {
+ continue;
+ } else {
+ x1 = seq % w;
+ y1 = seq / w;
+
+ if (sourceRect.contains(x1, y1)) {
+ color = sourceBuffer[(x1-sourceRect.left) + sourceRect.width()*(y1-sourceRect.top)];
+ if (flags == 0 || color)
+ ((byte*)pixels)[seq] = color;
+ }
+ }
+ }
+}
+
+void Gfx::initPalette() {
+ if(_vm->getGameType() != GType_IHNM)
+ return;
+
+ ResourceContext *resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (resourceContext == NULL) {
+ error("Resource::loadGlobalResources() resource context not found");
+ }
+
+ byte *resourcePointer;
+ size_t resourceLength;
+
+ _vm->_resource->loadResource(resourceContext, RID_IHNM_DEFAULT_PALETTE,
+ resourcePointer, resourceLength);
+
+ MemoryReadStream metaS(resourcePointer, resourceLength);
+
+ for(int i = 0; i < 256; i++) {
+ _globalPalette[i].red = metaS.readByte();
+ _globalPalette[i].green = metaS.readByte();
+ _globalPalette[i].blue = metaS.readByte();
+ }
+
+ free(resourcePointer);
+
+ setPalette(_globalPalette, true);
+}
+
+void Gfx::setPalette(const PalEntry *pal, bool full) {
+ int i;
+ byte *ppal;
+ int from, numcolors;
+
+ if (_vm->getGameType() != GType_IHNM || full) {
+ from = 0;
+ numcolors = PAL_ENTRIES;
+ } else {
+ from = 0;
+ numcolors = 248;
+ }
+
+ for (i = 0, ppal = &_currentPal[from * 4]; i < numcolors; i++, ppal += 4) {
+ ppal[0] = _globalPalette[i].red = pal[i].red;
+ ppal[1] = _globalPalette[i].green = pal[i].green;
+ ppal[2] = _globalPalette[i].blue = pal[i].blue;
+ ppal[3] = 0;
+ }
+
+ // Make 256th color black. See bug #1256368
+ if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro())
+ memset(&_currentPal[255 * 4], 0, 4);
+
+ _system->setPalette(_currentPal, 0, PAL_ENTRIES);
+}
+
+void Gfx::setPaletteColor(int n, int r, int g, int b) {
+ bool update = false;
+
+ // This function may get called a lot. To avoid forcing full-screen
+ // updates, only update the palette if the color actually changes.
+
+ if (_currentPal[4 * n + 0] != r) {
+ _currentPal[4 * n + 0] = _globalPalette[n].red = r;
+ update = true;
+ }
+ if (_currentPal[4 * n + 1] != g) {
+ _currentPal[4 * n + 1] = _globalPalette[n].green = g;
+ update = true;
+ }
+ if (_currentPal[4 * n + 2] != b) {
+ _currentPal[4 * n + 2] = _globalPalette[n].blue = b;
+ update = true;
+ }
+ if (_currentPal[4 * n + 3] != 0) {
+ _currentPal[4 * n + 3] = 0;
+ update = true;
+ }
+
+ if (update)
+ _system->setPalette(_currentPal, n, 1);
+}
+
+void Gfx::getCurrentPal(PalEntry *src_pal) {
+ int i;
+ byte *ppal;
+
+ for (i = 0, ppal = _currentPal; i < PAL_ENTRIES; i++, ppal += 4) {
+ src_pal[i].red = ppal[0];
+ src_pal[i].green = ppal[1];
+ src_pal[i].blue = ppal[2];
+ }
+}
+
+void Gfx::palToBlack(PalEntry *srcPal, double percent) {
+ int i;
+ //int fade_max = 255;
+ int new_entry;
+ byte *ppal;
+ PalEntry *palE;
+ int from, numcolors;
+
+ double fpercent;
+
+ if (_vm->getGameType() != GType_IHNM) {
+ from = 0;
+ numcolors = PAL_ENTRIES;
+ } else {
+ from = 0;
+ numcolors = 248;
+ }
+
+ if (percent > 1.0) {
+ percent = 1.0;
+ }
+
+ // Exponential fade
+ fpercent = percent * percent;
+
+ fpercent = 1.0 - fpercent;
+
+ // Use the correct percentage change per frame for each palette entry
+ for (i = 0, ppal = _currentPal; i < PAL_ENTRIES; i++, ppal += 4) {
+ if (i < from || i >= from + numcolors)
+ palE = &_globalPalette[i];
+ else
+ palE = &srcPal[i];
+
+ new_entry = (int)(palE->red * fpercent);
+
+ if (new_entry < 0) {
+ ppal[0] = 0;
+ } else {
+ ppal[0] = (byte) new_entry;
+ }
+
+ new_entry = (int)(palE->green * fpercent);
+
+ if (new_entry < 0) {
+ ppal[1] = 0;
+ } else {
+ ppal[1] = (byte) new_entry;
+ }
+
+ new_entry = (int)(palE->blue * fpercent);
+
+ if (new_entry < 0) {
+ ppal[2] = 0;
+ } else {
+ ppal[2] = (byte) new_entry;
+ }
+ ppal[3] = 0;
+ }
+
+ // Make 256th color black. See bug #1256368
+ if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro())
+ memset(&_currentPal[255 * 4], 0, 4);
+
+ _system->setPalette(_currentPal, 0, PAL_ENTRIES);
+}
+
+void Gfx::blackToPal(PalEntry *srcPal, double percent) {
+ int new_entry;
+ double fpercent;
+ byte *ppal;
+ int i;
+ PalEntry *palE;
+ int from, numcolors;
+
+ if (_vm->getGameType() != GType_IHNM) {
+ from = 0;
+ numcolors = PAL_ENTRIES;
+ } else {
+ from = 0;
+ numcolors = 248;
+ }
+
+ if (percent > 1.0) {
+ percent = 1.0;
+ }
+
+ // Exponential fade
+ fpercent = percent * percent;
+
+ fpercent = 1.0 - fpercent;
+
+ // Use the correct percentage change per frame for each palette entry
+ for (i = 0, ppal = _currentPal; i < PAL_ENTRIES; i++, ppal += 4) {
+ if (i < from || i >= from + numcolors)
+ palE = &_globalPalette[i];
+ else
+ palE = &srcPal[i];
+
+ new_entry = (int)(palE->red - palE->red * fpercent);
+
+ if (new_entry < 0) {
+ ppal[0] = 0;
+ } else {
+ ppal[0] = (byte)new_entry;
+ }
+
+ new_entry = (int)(palE->green - palE->green * fpercent);
+
+ if (new_entry < 0) {
+ ppal[1] = 0;
+ } else {
+ ppal[1] = (byte) new_entry;
+ }
+
+ new_entry = (int)(palE->blue - palE->blue * fpercent);
+
+ if (new_entry < 0) {
+ ppal[2] = 0;
+ } else {
+ ppal[2] = (byte) new_entry;
+ }
+ ppal[3] = 0;
+ }
+
+ // Make 256th color black. See bug #1256368
+ if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro())
+ memset(&_currentPal[255 * 4], 0, 4);
+
+ _system->setPalette(_currentPal, 0, PAL_ENTRIES);
+}
+
+void Gfx::showCursor(bool state) {
+ g_system->showMouse(state);
+}
+
+void Gfx::setCursor(CursorType cursorType) {
+ if (_vm->getGameType() == GType_ITE) {
+ // Set up the mouse cursor
+ const byte A = kITEColorLightGrey;
+ const byte B = kITEColorWhite;
+
+ const byte cursor_img[CURSOR_W * CURSOR_H] = {
+ 0, 0, 0, A, 0, 0, 0,
+ 0, 0, 0, A, 0, 0, 0,
+ 0, 0, 0, A, 0, 0, 0,
+ A, A, A, B, A, A, A,
+ 0, 0, 0, A, 0, 0, 0,
+ 0, 0, 0, A, 0, 0, 0,
+ 0, 0, 0, A, 0, 0, 0,
+ };
+
+ _system->setMouseCursor(cursor_img, CURSOR_W, CURSOR_H, 3, 3, 0);
+ } else {
+ uint32 resourceId;
+
+ switch (cursorType) {
+ case kCursorBusy:
+ resourceId = RID_IHNM_HOURGLASS_CURSOR;
+ break;
+ default:
+ resourceId = (uint32)-1;
+ break;
+ }
+
+ byte *resource;
+ size_t resourceLength;
+ byte *image;
+ size_t imageLength;
+ int width, height;
+
+ if (resourceId != (uint32)-1) {
+ ResourceContext *context = _vm->_resource->getContext(GAME_RESOURCEFILE);
+
+ _vm->_resource->loadResource(context, resourceId, resource, resourceLength);
+
+ _vm->decodeBGImage(resource, resourceLength, &image, &imageLength, &width, &height);
+ } else {
+ resource = NULL;
+ width = height = 31;
+ image = (byte *)calloc(width, height);
+
+ for (int i = 0; i < 14; i++) {
+ image[15 * 31 + i] = 1;
+ image[15 * 31 + 30 - i] = 1;
+ image[i * 31 + 15] = 1;
+ image[(30 - i) * 31 + 15] = 1;
+ }
+ }
+
+ // Note: Hard-coded hotspot
+ _system->setMouseCursor(image, width, height, 15, 15, 0);
+
+ free(image);
+ free(resource);
+ }
+}
+
+bool hitTestPoly(const Point *points, unsigned int npoints, const Point& test_point) {
+ int yflag0;
+ int yflag1;
+ bool inside_flag = false;
+ unsigned int pt;
+
+ const Point *vtx0 = &points[npoints - 1];
+ const Point *vtx1 = &points[0];
+
+ yflag0 = (vtx0->y >= test_point.y);
+ for (pt = 0; pt < npoints; pt++, vtx1++) {
+ yflag1 = (vtx1->y >= test_point.y);
+ if (yflag0 != yflag1) {
+ if (((vtx1->y - test_point.y) * (vtx0->x - vtx1->x) >=
+ (vtx1->x - test_point.x) * (vtx0->y - vtx1->y)) == yflag1) {
+ inside_flag = !inside_flag;
+ }
+ }
+ yflag0 = yflag1;
+ vtx0 = vtx1;
+ }
+
+ return inside_flag;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/gfx.h b/engines/saga/gfx.h
new file mode 100644
index 0000000000..ff96cbf081
--- /dev/null
+++ b/engines/saga/gfx.h
@@ -0,0 +1,164 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Graphics maniuplation routines - private header file
+
+#ifndef SAGA_GFX_H_
+#define SAGA_GFX_H_
+
+#include "graphics/surface.h"
+
+namespace Saga {
+
+using Common::Point;
+using Common::Rect;
+
+enum CursorType {
+ kCursorNormal,
+ kCursorBusy
+};
+
+struct ClipData {
+ // input members
+ Rect sourceRect;
+ Rect destRect;
+ Point destPoint;
+
+ // output members
+ Point drawSource;
+ Point drawDest;
+ int drawWidth;
+ int drawHeight;
+
+ bool calcClip() {
+ Common::Rect s;
+
+ // Adjust the rect to draw to its screen coordinates
+ s = sourceRect;
+ s.left += destPoint.x;
+ s.right += destPoint.x;
+ s.top += destPoint.y;
+ s.bottom += destPoint.y;
+
+ s.clip(destRect);
+
+ if ((s.width() <= 0) || (s.height() <= 0)) {
+ return false;
+ }
+
+ drawSource.x = s.left - sourceRect.left - destPoint.x;
+ drawSource.y = s.top - sourceRect.top - destPoint.y;
+ drawDest.x = s.left;
+ drawDest.y = s.top;
+ drawWidth = s.width();
+ drawHeight = s.height();
+
+ return true;
+ }
+};
+
+#pragma START_PACK_STRUCTS
+struct PalEntry {
+ byte red;
+ byte green;
+ byte blue;
+} GCC_PACK;
+
+#pragma END_PACK_STRUCTS
+
+struct Color {
+ int red;
+ int green;
+ int blue;
+ int alpha;
+};
+
+struct Surface : Graphics::Surface {
+
+ void transitionDissolve(const byte *sourceBuffer, const Common::Rect &sourceRect, int flags, double percent);
+ void drawPalette();
+ void drawPolyLine(const Point *points, int count, int color);
+ void blit(const Common::Rect &destRect, const byte *sourceBuffer);
+
+ void getRect(Common::Rect &rect) {
+ rect.left = rect.top = 0;
+ rect.right = w;
+ rect.bottom = h;
+ }
+ void drawFrame(const Common::Point &p1, const Common::Point &p2, int color) {
+ Common::Rect rect(MIN(p1.x, p2.x), MIN(p1.y, p2.y), MAX(p1.x, p2.x) + 1, MAX(p1.y, p2.y) + 1);
+ frameRect(rect, color);
+ }
+ void drawRect(const Common::Rect &destRect, int color) {
+ Common::Rect rect(w , h);
+ rect.clip(destRect);
+
+ if (rect.isValidRect()) {
+ fillRect(rect, color);
+ }
+ }
+};
+
+#define PAL_ENTRIES 256
+
+#define CURSOR_W 7
+#define CURSOR_H 7
+
+#define CURSOR_ORIGIN_X 4
+#define CURSOR_ORIGIN_Y 4
+
+bool hitTestPoly(const Point *points, unsigned int npoints, const Point& test_point);
+class SagaEngine;
+
+class Gfx {
+public:
+
+ Gfx(SagaEngine *vm, OSystem *system, int width, int height, GameDetector &detector);
+ ~Gfx();
+ Surface *getBackBuffer() {
+ return &_backBuffer;
+ }
+
+ void initPalette();
+ void setPalette(const PalEntry *pal, bool full = false);
+ void setPaletteColor(int n, int r, int g, int b);
+ void getCurrentPal(PalEntry *src_pal);
+ void palToBlack(PalEntry *src_pal, double percent);
+ void blackToPal(PalEntry *src_pal, double percent);
+ void showCursor(bool state);
+
+private:
+ void setCursor(CursorType cursorType = kCursorNormal);
+ int _init;
+ Surface _backBuffer;
+ byte _currentPal[PAL_ENTRIES * 4];
+ OSystem *_system;
+ SagaEngine *_vm;
+
+ PalEntry _globalPalette[PAL_ENTRIES];
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/ihnm_introproc.cpp b/engines/saga/ihnm_introproc.cpp
new file mode 100644
index 0000000000..881625c170
--- /dev/null
+++ b/engines/saga/ihnm_introproc.cpp
@@ -0,0 +1,331 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// "I Have No Mouth" Intro sequence scene procedures
+
+#include "saga/saga.h"
+#include "saga/gfx.h"
+
+#include "saga/animation.h"
+#include "saga/events.h"
+#include "saga/interface.h"
+#include "saga/sndres.h"
+#include "saga/music.h"
+
+#include "saga/scene.h"
+
+namespace Saga {
+
+SceneResourceData IHNM_IntroMovie1RL[] = {
+ {30, 2, 0, 0, false} ,
+ {31, 14, 0, 0, false}
+};
+
+SceneDescription IHNM_IntroMovie1Desc = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ IHNM_IntroMovie1RL,
+ ARRAYSIZE(IHNM_IntroMovie1RL)
+};
+
+SceneResourceData IHNM_IntroMovie2RL[] = {
+ {32, 2, 0, 0, false} ,
+ {33, 14, 0, 0, false}
+};
+
+SceneDescription IHNM_IntroMovie2Desc = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ IHNM_IntroMovie2RL,
+ ARRAYSIZE(IHNM_IntroMovie2RL)
+};
+
+SceneResourceData IHNM_IntroMovie3RL[] = {
+ {34, 2, 0, 0, false},
+ {35, 14, 0, 0, false}
+};
+
+SceneDescription IHNM_IntroMovie3Desc = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ IHNM_IntroMovie3RL,
+ ARRAYSIZE(IHNM_IntroMovie3RL)
+};
+
+SceneResourceData IHNM_IntroMovie4RL[] = {
+ {1227, 2, 0, 0, false},
+ {1226, 14, 0, 0, false}
+};
+
+SceneDescription IHNM_IntroMovie4Desc = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ IHNM_IntroMovie4RL,
+ ARRAYSIZE(IHNM_IntroMovie4RL)
+};
+
+LoadSceneParams IHNM_IntroList[] = {
+ {0, kLoadByDescription, &IHNM_IntroMovie1Desc, Scene::SC_IHNMIntroMovieProc1, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {0, kLoadByDescription, &IHNM_IntroMovie2Desc, Scene::SC_IHNMIntroMovieProc2, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {0, kLoadByDescription, &IHNM_IntroMovie3Desc, Scene::SC_IHNMIntroMovieProc3, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+};
+
+int Scene::IHNMStartProc() {
+ size_t n_introscenes;
+ size_t i;
+
+ LoadSceneParams firstScene;
+
+ // The original used the "play video" mechanism for the first part of
+ // the intro. We just use that panel mode.
+
+ _vm->_interface->setMode(kPanelVideo);
+
+ n_introscenes = ARRAYSIZE(IHNM_IntroList);
+
+ for (i = 0; i < n_introscenes; i++) {
+ _vm->_scene->queueScene(&IHNM_IntroList[i]);
+ }
+
+ firstScene.loadFlag = kLoadBySceneNumber;
+ firstScene.sceneDescriptor = -1;
+ firstScene.sceneDescription = NULL;
+ firstScene.sceneSkipTarget = true;
+ firstScene.sceneProc = NULL;
+ firstScene.transitionType = kTransitionFade;
+ firstScene.actorsEntrance = 0;
+ firstScene.chapter = -1;
+
+ _vm->_scene->queueScene(&firstScene);
+
+ return SUCCESS;
+}
+
+int Scene::SC_IHNMIntroMovieProc1(int param, void *refCon) {
+ return ((Scene *)refCon)->IHNMIntroMovieProc1(param);
+}
+
+int Scene::IHNMIntroMovieProc1(int param) {
+ Event event;
+ Event *q_event;
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Background for intro scene is the first frame of the
+ // intro animation; display it and set the palette
+ event.type = kEvTOneshot;
+ event.code = kBgEvent;
+ event.op = kEventDisplay;
+ event.param = kEvPSetPalette;
+ event.time = 0;
+
+ q_event = _vm->_events->queue(&event);
+
+ _vm->_anim->setFrameTime(0, IHNM_INTRO_FRAMETIME);
+ _vm->_anim->setFlag(0, ANIM_FLAG_ENDSCENE);
+
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.param = 0;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_IHNMIntroMovieProc2(int param, void *refCon) {
+ return ((Scene *)refCon)->IHNMIntroMovieProc2(param);
+}
+
+int Scene::IHNMIntroMovieProc2(int param) {
+ Event event;
+ Event *q_event;
+ PalEntry *pal;
+
+ static PalEntry current_pal[PAL_ENTRIES];
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Fade to black out of the intro CyberDreams logo anim
+ _vm->_gfx->getCurrentPal(current_pal);
+
+ event.type = kEvTContinuous;
+ event.code = kPalEvent;
+ event.op = kEventPalToBlack;
+ event.time = 0;
+ event.duration = IHNM_PALFADE_TIME;
+ event.data = current_pal;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Background for intro scene is the first frame of the
+ // intro animation; display it but don't set palette
+ event.type = kEvTOneshot;
+ event.code = kBgEvent;
+ event.op = kEventDisplay;
+ event.param = kEvPNoSetPalette;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ _vm->_anim->setCycles(0, -1);
+
+ // Unlike the original, we keep the logo spinning during the
+ // palette fades. We don't have to, but I think it looks better
+ // that way.
+
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.param = 0;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Fade in from black to the scene background palette
+ _vm->_scene->getBGPal(pal);
+
+ event.type = kEvTContinuous;
+ event.code = kPalEvent;
+ event.op = kEventBlackToPal;
+ event.time = 0;
+ event.duration = IHNM_PALFADE_TIME;
+ event.data = pal;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Fade to black after looping animation for a while
+ event.type = kEvTContinuous;
+ event.code = kPalEvent;
+ event.op = kEventPalToBlack;
+ event.time = IHNM_DGLOGO_TIME;
+ event.duration = IHNM_PALFADE_TIME;
+ event.data = pal;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue end of scene
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_IHNMIntroMovieProc3(int param, void *refCon) {
+ return ((Scene *)refCon)->IHNMIntroMovieProc3(param);
+}
+
+int Scene::IHNMIntroMovieProc3(int param) {
+ Event event;
+ Event *q_event;
+ PalEntry *pal;
+ static PalEntry current_pal[PAL_ENTRIES];
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Fade to black out of the intro DG logo anim
+ _vm->_gfx->getCurrentPal(current_pal);
+
+ event.type = kEvTContinuous;
+ event.code = kPalEvent;
+ event.op = kEventPalToBlack;
+ event.time = 0;
+ event.duration = IHNM_PALFADE_TIME;
+ event.data = current_pal;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Music, maestro
+
+ // In the GM file, this music also appears as tracks 7, 13, 19,
+ // 25 and 31, but only track 1 sounds right with the FM music.
+
+ event.type = kEvTOneshot;
+ event.code = kMusicEvent;
+ event.param = 1;
+ event.param2 = MUSIC_NORMAL;
+ event.op = kEventPlay;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Background for intro scene is the first frame of the intro
+ // animation; display it but don't set palette
+ event.type = kEvTOneshot;
+ event.code = kBgEvent;
+ event.op = kEventDisplay;
+ event.param = kEvPNoSetPalette;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Fade in from black to the scene background palette
+ _vm->_scene->getBGPal(pal);
+
+ event.type = kEvTContinuous;
+ event.code = kPalEvent;
+ event.op = kEventBlackToPal;
+ event.time = 0;
+ event.duration = IHNM_PALFADE_TIME;
+ event.data = pal;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.param = 0;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue end of scene after a while
+ // TODO: I've increased the delay so the speech won't start
+ // until the music has ended. Could someone verify if that's
+ // the correct behaviour?
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = _vm->_music->hasAdlib() ? IHNM_TITLE_TIME_FM : IHNM_TITLE_TIME_GM;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/image.cpp b/engines/saga/image.cpp
new file mode 100644
index 0000000000..b7ac53f179
--- /dev/null
+++ b/engines/saga/image.cpp
@@ -0,0 +1,439 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// SAGA Image resource management routines
+#include "saga/saga.h"
+
+#include "saga/stream.h"
+
+namespace Saga {
+
+static int granulate(int value, int granularity) {
+ int remainder;
+
+ if (value == 0)
+ return 0;
+
+ if (granularity == 0)
+ return 0;
+
+ remainder = value % granularity;
+
+ if (remainder == 0) {
+ return value;
+ } else {
+ return (granularity - remainder + value);
+ }
+}
+
+int SagaEngine::decodeBGImage(const byte *image_data, size_t image_size,
+ byte **output_buf, size_t *output_buf_len, int *w, int *h, bool flip) {
+ ImageHeader hdr;
+ int modex_height;
+ const byte *RLE_data_ptr;
+ size_t RLE_data_len;
+ byte *decode_buf;
+ size_t decode_buf_len;
+ byte *out_buf;
+ size_t out_buf_len;
+
+ if (image_size <= SAGA_IMAGE_DATA_OFFSET) {
+ error("decodeBGImage() Image size is way too small (%d)", image_size);
+ }
+
+ MemoryReadStreamEndian readS(image_data, image_size, isBigEndian());
+
+ hdr.width = readS.readUint16();
+ hdr.height = readS.readUint16();
+ // The next four bytes of the image header aren't used.
+ readS.readUint16();
+ readS.readUint16();
+
+ RLE_data_ptr = image_data + SAGA_IMAGE_DATA_OFFSET;
+ RLE_data_len = image_size - SAGA_IMAGE_DATA_OFFSET;
+
+ modex_height = granulate(hdr.height, 4);
+
+ decode_buf_len = hdr.width * modex_height;
+ decode_buf = (byte *)malloc(decode_buf_len);
+
+ out_buf_len = hdr.width * hdr.height;
+ out_buf = (byte *)malloc(out_buf_len);
+
+ if (decodeBGImageRLE(RLE_data_ptr,
+ RLE_data_len, decode_buf, decode_buf_len) != SUCCESS) {
+ free(decode_buf);
+ free(out_buf);
+ return FAILURE;
+ }
+
+ unbankBGImage(out_buf, decode_buf, hdr.width, hdr.height);
+
+ // For some reason bg images in IHNM are upside down
+ if (getGameType() == GType_IHNM && !flip) {
+ flipImage(out_buf, hdr.width, hdr.height);
+ }
+
+ free(decode_buf);
+
+ *output_buf_len = out_buf_len;
+ *output_buf = out_buf;
+
+ *w = hdr.width;
+ *h = hdr.height;
+
+ return SUCCESS;
+}
+
+int SagaEngine::decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, byte *outbuf, size_t outbuf_len) {
+ const byte *inbuf_ptr;
+ byte *outbuf_ptr;
+ uint32 inbuf_remain;
+
+ const byte *inbuf_end;
+ byte *outbuf_end;
+ uint32 outbuf_remain;
+
+ byte mark_byte;
+ int test_byte;
+
+ uint32 runcount;
+
+ byte bitfield;
+ byte bitfield_byte1;
+ byte bitfield_byte2;
+
+ byte *backtrack_ptr;
+ int backtrack_amount;
+
+ uint16 c, b;
+
+ int decode_err = 0;
+
+ inbuf_ptr = inbuf;
+ inbuf_remain = inbuf_len;
+
+ outbuf_ptr = outbuf;
+ outbuf_remain = outbuf_len;
+
+ inbuf_end = (inbuf + inbuf_len) - 1;
+ outbuf_end = (outbuf + outbuf_len) - 1;
+
+ memset(outbuf, 0, outbuf_len);
+
+ while ((inbuf_remain > 1) && (outbuf_remain > 0) && !decode_err) {
+
+ if ((inbuf_ptr > inbuf_end) || (outbuf_ptr > outbuf_end)) {
+ return FAILURE;
+ }
+
+ mark_byte = *inbuf_ptr++;
+ inbuf_remain--;
+
+ test_byte = mark_byte & 0xC0; // Mask all but two high order bits
+
+ switch (test_byte) {
+ case 0xC0: // 1100 0000
+ // Uncompressed run follows: Max runlength 63
+ runcount = mark_byte & 0x3f;
+ if ((inbuf_remain < runcount) || (outbuf_remain < runcount)) {
+ return FAILURE;
+ }
+
+ for (c = 0; c < runcount; c++) {
+ *outbuf_ptr++ = *inbuf_ptr++;
+ }
+
+ inbuf_remain -= runcount;
+ outbuf_remain -= runcount;
+ continue;
+ break;
+ case 0x80: // 1000 0000
+ // Compressed run follows: Max runlength 63
+ runcount = (mark_byte & 0x3f) + 3;
+ if (!inbuf_remain || (outbuf_remain < runcount)) {
+ return FAILURE;
+ }
+
+ for (c = 0; c < runcount; c++) {
+ *outbuf_ptr++ = *inbuf_ptr;
+ }
+
+ inbuf_ptr++;
+ inbuf_remain--;
+ outbuf_remain -= runcount;
+ continue;
+
+ break;
+
+ case 0x40: // 0100 0000
+ // Repeat decoded sequence from output stream:
+ // Max runlength 10
+
+ runcount = ((mark_byte >> 3) & 0x07U) + 3;
+ backtrack_amount = *inbuf_ptr;
+
+ if (!inbuf_remain || (backtrack_amount > (outbuf_ptr - outbuf)) || (runcount > outbuf_remain)) {
+ return FAILURE;
+ }
+
+ inbuf_ptr++;
+ inbuf_remain--;
+
+ backtrack_ptr = outbuf_ptr - backtrack_amount;
+
+ for (c = 0; c < runcount; c++) {
+ *outbuf_ptr++ = *backtrack_ptr++;
+ }
+
+ outbuf_remain -= runcount;
+ continue;
+ break;
+ default: // 0000 0000
+ break;
+ }
+
+ // Mask all but the third and fourth highest order bits
+ test_byte = mark_byte & 0x30;
+
+ switch (test_byte) {
+
+ case 0x30: // 0011 0000
+ // Bitfield compression
+ runcount = (mark_byte & 0x0F) + 1;
+
+ if ((inbuf_remain < (runcount + 2)) || (outbuf_remain < (runcount * 8))) {
+ return FAILURE;
+ }
+
+ bitfield_byte1 = *inbuf_ptr++;
+ bitfield_byte2 = *inbuf_ptr++;
+
+ for (c = 0; c < runcount; c++) {
+ bitfield = *inbuf_ptr;
+ for (b = 0; b < 8; b++) {
+ if (bitfield & 0x80) {
+ *outbuf_ptr = bitfield_byte2;
+ } else {
+ *outbuf_ptr = bitfield_byte1;
+ }
+ bitfield <<= 1;
+ outbuf_ptr++;
+ }
+ inbuf_ptr++;
+ }
+
+ inbuf_remain -= (runcount + 2);
+ outbuf_remain -= (runcount * 8);
+ continue;
+ break;
+ case 0x20: // 0010 0000
+ // Uncompressed run follows
+ runcount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr;
+ if ((inbuf_remain < (runcount + 1)) || (outbuf_remain < runcount)) {
+ return FAILURE;
+ }
+
+ inbuf_ptr++;
+
+ for (c = 0; c < runcount; c++) {
+ *outbuf_ptr++ = *inbuf_ptr++;
+ }
+
+ inbuf_remain -= (runcount + 1);
+ outbuf_remain -= runcount;
+ continue;
+
+ break;
+
+ case 0x10: // 0001 0000
+ // Repeat decoded sequence from output stream
+ backtrack_amount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr;
+ if (inbuf_remain < 2) {
+ return FAILURE;
+ }
+
+ inbuf_ptr++;
+ runcount = *inbuf_ptr++;
+
+ if ((backtrack_amount > (outbuf_ptr - outbuf)) || (outbuf_remain < runcount)) {
+ return FAILURE;
+ }
+
+ backtrack_ptr = outbuf_ptr - backtrack_amount;
+
+ for (c = 0; c < runcount; c++) {
+ *outbuf_ptr++ = *backtrack_ptr++;
+ }
+
+ inbuf_remain -= 2;
+ outbuf_remain -= runcount;
+ continue;
+ break;
+ default:
+ return FAILURE;
+ break;
+ }
+ }
+
+ return SUCCESS;
+}
+
+int SagaEngine::flipImage(byte *img_buf, int columns, int scanlines) {
+ int line;
+ byte *tmp_scan;
+
+ byte *flip_p1;
+ byte *flip_p2;
+
+ int flipcount = scanlines / 2;
+
+ tmp_scan = (byte *)malloc(columns);
+ if (tmp_scan == NULL) {
+ return FAILURE;
+ }
+
+ flip_p1 = img_buf;
+ flip_p2 = img_buf + (columns * (scanlines - 1));
+
+ for (line = 0; line < flipcount; line++) {
+ memcpy(tmp_scan, flip_p1, columns);
+ memcpy(flip_p1, flip_p2, columns);
+ memcpy(flip_p2, tmp_scan, columns);
+ flip_p1 += columns;
+ flip_p2 -= columns;
+ }
+
+ free(tmp_scan);
+
+ return SUCCESS;
+}
+
+int SagaEngine::unbankBGImage(byte *dst_buf, const byte *src_buf, int columns, int scanlines) {
+ int x, y;
+ int temp;
+ int quadruple_rows;
+ int remain_rows;
+ int rowjump_src;
+ int rowjump_dest;
+ const byte *src_p;
+ byte *dst_p;
+ const byte *srcptr1, *srcptr2, *srcptr3, *srcptr4;
+ byte *dstptr1, *dstptr2, *dstptr3, *dstptr4;
+
+ quadruple_rows = scanlines - (scanlines % 4);
+ remain_rows = scanlines - quadruple_rows;
+
+ assert(scanlines > 0);
+
+ src_p = src_buf;
+ dst_p = dst_buf + columns;
+
+ srcptr1 = src_p;
+ srcptr2 = src_p + 1;
+ srcptr3 = src_p + 2;
+ srcptr4 = src_p + 3;
+
+ dstptr1 = dst_buf;
+ dstptr2 = dst_buf + columns;
+ dstptr3 = dst_buf + columns * 2;
+ dstptr4 = dst_buf + columns * 3;
+
+ rowjump_src = columns * 4;
+ rowjump_dest = columns * 4;
+
+ // Unbank groups of 4 first
+ for (y = 0; y < quadruple_rows; y += 4) {
+ for (x = 0; x < columns; x++) {
+ temp = x * 4;
+ dstptr1[x] = srcptr1[temp];
+ dstptr2[x] = srcptr2[temp];
+ dstptr3[x] = srcptr3[temp];
+ dstptr4[x] = srcptr4[temp];
+ }
+
+ // This is to avoid generating invalid pointers -
+ // usually innocuous, but undefined
+ if (y < quadruple_rows - 4) {
+ dstptr1 += rowjump_dest;
+ dstptr2 += rowjump_dest;
+ dstptr3 += rowjump_dest;
+ dstptr4 += rowjump_dest;
+ srcptr1 += rowjump_src;
+ srcptr2 += rowjump_src;
+ srcptr3 += rowjump_src;
+ srcptr4 += rowjump_src;
+ }
+ }
+
+ // Unbank rows remaining
+ switch (remain_rows) {
+ case 1:
+ dstptr1 += rowjump_dest;
+ srcptr1 += rowjump_src;
+ for (x = 0; x < columns; x++) {
+ temp = x * 4;
+ dstptr1[x] = srcptr1[temp];
+ }
+ break;
+ case 2:
+ dstptr1 += rowjump_dest;
+ dstptr2 += rowjump_dest;
+ srcptr1 += rowjump_src;
+ srcptr2 += rowjump_src;
+ for (x = 0; x < columns; x++) {
+ temp = x * 4;
+ dstptr1[x] = srcptr1[temp];
+ dstptr2[x] = srcptr2[temp];
+ }
+ break;
+ case 3:
+ dstptr1 += rowjump_dest;
+ dstptr2 += rowjump_dest;
+ dstptr3 += rowjump_dest;
+ srcptr1 += rowjump_src;
+ srcptr2 += rowjump_src;
+ srcptr3 += rowjump_src;
+ for (x = 0; x < columns; x++) {
+ temp = x * 4;
+ dstptr1[x] = srcptr1[temp];
+ dstptr2[x] = srcptr2[temp];
+ dstptr3[x] = srcptr3[temp];
+ }
+ break;
+ default:
+ break;
+ }
+ return SUCCESS;
+}
+
+const byte *SagaEngine::getImagePal(const byte *image_data, size_t image_size) {
+ if (image_size <= SAGA_IMAGE_HEADER_LEN) {
+ return NULL;
+ }
+
+ return image_data + SAGA_IMAGE_HEADER_LEN;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/input.cpp b/engines/saga/input.cpp
new file mode 100644
index 0000000000..0abce03a31
--- /dev/null
+++ b/engines/saga/input.cpp
@@ -0,0 +1,163 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/actor.h"
+#include "saga/console.h"
+#include "saga/interface.h"
+#include "saga/render.h"
+#include "saga/scene.h"
+#include "saga/script.h"
+#include "saga/isomap.h"
+
+#include "common/system.h"
+
+namespace Saga {
+
+int SagaEngine::processInput() {
+ OSystem::Event event;
+
+// Point imousePt;
+
+ while (g_system->pollEvent(event)) {
+ switch (event.type) {
+ case OSystem::EVENT_KEYDOWN:
+ if (event.kbd.flags == OSystem::KBD_CTRL) {
+ if (event.kbd.keycode == 'd')
+ _console->attach();
+ }
+ if (_interface->_textInput || _interface->_statusTextInput) {
+ _interface->processAscii(event.kbd.ascii);
+ return SUCCESS;
+ }
+
+ switch (event.kbd.keycode) {
+ case '#':
+ case '`':
+ case '~':
+ _console->attach();
+ break;
+ case 'r':
+ _interface->draw();
+ break;
+
+#if 0
+ case 269:
+ case 270:
+ case 273:
+ case 274:
+ case 275:
+ case 276:
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->_viewDiff += (event.kbd.keycode == 270) - (event.kbd.keycode == 269);
+ _vm->_isoMap->_viewScroll.y += (_vm->_isoMap->_viewDiff * (event.kbd.keycode == 274) - _vm->_isoMap->_viewDiff * (event.kbd.keycode == 273));
+ _vm->_isoMap->_viewScroll.x += (_vm->_isoMap->_viewDiff * (event.kbd.keycode == 275) - _vm->_isoMap->_viewDiff * (event.kbd.keycode == 276));
+ }
+ break;
+#endif
+ case 282: // F1
+ _render->toggleFlag(RF_SHOW_FPS);
+ _actor->_handleActionDiv = (_actor->_handleActionDiv == 15) ? 50 : 15;
+ break;
+ case 283: // F2
+ _render->toggleFlag(RF_PALETTE_TEST);
+ break;
+ case 284: // F3
+ _render->toggleFlag(RF_TEXT_TEST);
+ break;
+ case 285: // F4
+ _render->toggleFlag(RF_OBJECTMAP_TEST);
+ break;
+ case 286: // F5
+ if (_interface->getSaveReminderState() > 0)
+ _interface->setMode(kPanelOption);
+ break;
+ case 287: // F6
+ _render->toggleFlag(RF_ACTOR_PATH_TEST);
+ break;
+ case 288: // F7
+ //_actor->frameTest();
+ break;
+ case 289: // F8
+ break;
+ case 290: // F9
+ _interface->keyBoss();
+ break;
+
+ // Actual game keys
+ case 32: // space
+ _actor->abortSpeech();
+ break;
+ case 19: // pause
+ case 'z':
+ _render->toggleFlag(RF_RENDERPAUSE);
+ break;
+ default:
+ _interface->processAscii(event.kbd.ascii);
+ break;
+ }
+ break;
+ case OSystem::EVENT_KEYUP:
+ _interface->processKeyUp(event.kbd.ascii);
+ break;
+ case OSystem::EVENT_LBUTTONUP:
+ _leftMouseButtonPressed = false;
+ break;
+ case OSystem::EVENT_RBUTTONUP:
+ _rightMouseButtonPressed = false;
+ break;
+ case OSystem::EVENT_LBUTTONDOWN:
+ _leftMouseButtonPressed = true;
+ _mousePos = event.mouse;
+ _interface->update(_mousePos, UPDATE_LEFTBUTTONCLICK);
+ break;
+ case OSystem::EVENT_RBUTTONDOWN:
+ _rightMouseButtonPressed = true;
+ _mousePos = event.mouse;
+ _interface->update(_mousePos, UPDATE_RIGHTBUTTONCLICK);
+ break;
+ case OSystem::EVENT_WHEELUP:
+ _interface->update(_mousePos, UPDATE_WHEELUP);
+ break;
+ case OSystem::EVENT_WHEELDOWN:
+ _interface->update(_mousePos, UPDATE_WHEELDOWN);
+ break;
+ case OSystem::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+ case OSystem::EVENT_QUIT:
+ shutDown();
+ break;
+ default:
+ break;
+ }
+ }
+
+ return SUCCESS;
+}
+
+
+} // End of namespace Saga
+
diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp
new file mode 100644
index 0000000000..1d3b110068
--- /dev/null
+++ b/engines/saga/interface.cpp
@@ -0,0 +1,2453 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Game interface module
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/actor.h"
+#include "saga/console.h"
+#include "saga/events.h"
+#include "saga/font.h"
+#include "saga/objectmap.h"
+#include "saga/isomap.h"
+#include "saga/itedata.h"
+#include "saga/music.h"
+#include "saga/puzzle.h"
+#include "saga/render.h"
+#include "saga/scene.h"
+#include "saga/script.h"
+#include "saga/sound.h"
+#include "saga/sprite.h"
+#include "saga/rscfile.h"
+#include "saga/resnames.h"
+
+#include "saga/interface.h"
+
+#include "common/config-manager.h"
+#include "common/system.h"
+#include "common/timer.h"
+
+namespace Saga {
+
+static int verbTypeToTextStringsIdLUT[2][kVerbTypeIdsMax] = {
+ {-1,
+ kTextPickUp,
+ kTextLookAt,
+ kTextWalkTo,
+ kTextTalkTo,
+ kTextOpen,
+ kTextClose,
+ kTextGive,
+ kTextUse,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1},
+ {-1,
+ 3, //TODO:check
+ 2,
+ 1,
+ 5,
+ 6, //TODO:check
+ 8, //TODO:check
+ 7,
+ 4}
+};
+
+Interface::Interface(SagaEngine *vm) : _vm(vm) {
+ byte *resource;
+ size_t resourceLength;
+ int i;
+
+ // Load interface module resource file context
+ _interfaceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (_interfaceContext == NULL) {
+ error("Interface::Interface() resource context not found");
+ }
+
+ _mainPanel.buttons = _vm->getDisplayInfo().mainPanelButtons;
+ _mainPanel.buttonsCount = _vm->getDisplayInfo().mainPanelButtonsCount;
+
+ for (i = 0; i < kVerbTypeIdsMax; i++) {
+ _verbTypeToPanelButton[i] = NULL;
+ }
+
+ for (i = 0; i < _mainPanel.buttonsCount; i++) {
+ if (_mainPanel.buttons[i].type == kPanelButtonVerb) {
+ _verbTypeToPanelButton[_mainPanel.buttons[i].id] = &_mainPanel.buttons[i];
+ }
+ }
+
+ _vm->_resource->loadResource(_interfaceContext, _vm->getResourceDescription()->mainPanelResourceId, resource, resourceLength);
+ _vm->decodeBGImage(resource, resourceLength, &_mainPanel.image,
+ &_mainPanel.imageLength, &_mainPanel.imageWidth, &_mainPanel.imageHeight);
+
+ free(resource);
+
+ _conversePanel.buttons = _vm->getDisplayInfo().conversePanelButtons;
+ _conversePanel.buttonsCount = _vm->getDisplayInfo().conversePanelButtonsCount;
+
+ _vm->_resource->loadResource(_interfaceContext, _vm->getResourceDescription()->conversePanelResourceId, resource, resourceLength);
+ _vm->decodeBGImage(resource, resourceLength, &_conversePanel.image,
+ &_conversePanel.imageLength, &_conversePanel.imageWidth, &_conversePanel.imageHeight);
+ free(resource);
+
+ _optionPanel.buttons = _vm->getDisplayInfo().optionPanelButtons;
+ _optionPanel.buttonsCount = _vm->getDisplayInfo().optionPanelButtonsCount;
+
+ _vm->_resource->loadResource(_interfaceContext, _vm->getResourceDescription()->optionPanelResourceId, resource, resourceLength);
+ _vm->decodeBGImage(resource, resourceLength, &_optionPanel.image,
+ &_optionPanel.imageLength, &_optionPanel.imageWidth, &_optionPanel.imageHeight);
+ free(resource);
+
+ _vm->_sprite->loadList(_vm->getResourceDescription()->mainPanelSpritesResourceId, _mainPanel.sprites);
+
+ if (_vm->getGameType() == GType_ITE) {
+ _vm->_sprite->loadList(_vm->getResourceDescription()->defaultPortraitsResourceId, _defPortraits);
+ }
+
+ setPortraitBgColor(0, 0, 0);
+
+ _mainPanel.x = _vm->getDisplayInfo().mainPanelXOffset;
+ _mainPanel.y = _vm->getDisplayInfo().mainPanelYOffset;
+ _mainPanel.currentButton = NULL;
+ _inventoryUpButton = _mainPanel.getButton(_vm->getDisplayInfo().inventoryUpButtonIndex);
+ _inventoryDownButton = _mainPanel.getButton(_vm->getDisplayInfo().inventoryDownButtonIndex);
+
+ _conversePanel.x = _vm->getDisplayInfo().conversePanelXOffset;
+ _conversePanel.y = _vm->getDisplayInfo().conversePanelYOffset;
+ _conversePanel.currentButton = NULL;
+ _converseUpButton = _conversePanel.getButton(_vm->getDisplayInfo().converseUpButtonIndex);
+ _converseDownButton = _conversePanel.getButton(_vm->getDisplayInfo().converseDownButtonIndex);
+
+ _leftPortrait = 0;
+ _rightPortrait = 0;
+
+ _optionPanel.x = _vm->getDisplayInfo().optionPanelXOffset;
+ _optionPanel.y = _vm->getDisplayInfo().optionPanelYOffset;
+ _optionPanel.currentButton = NULL;
+ _optionSaveFileSlider = _optionPanel.getButton(_vm->getDisplayInfo().optionSaveFileSliderIndex);
+ _optionSaveFilePanel = _optionPanel.getButton(_vm->getDisplayInfo().optionSaveFilePanelIndex);
+
+ _quitPanel.x = _vm->getDisplayInfo().quitPanelXOffset;
+ _quitPanel.y = _vm->getDisplayInfo().quitPanelYOffset;
+ _quitPanel.imageWidth = _vm->getDisplayInfo().quitPanelWidth;
+ _quitPanel.imageHeight = _vm->getDisplayInfo().quitPanelHeight;
+ _quitPanel.buttons = _vm->getDisplayInfo().quitPanelButtons;
+ _quitPanel.buttonsCount = _vm->getDisplayInfo().quitPanelButtonsCount;
+ _quitPanel.currentButton = NULL;
+
+ _loadPanel.x = _vm->getDisplayInfo().loadPanelXOffset;
+ _loadPanel.y = _vm->getDisplayInfo().loadPanelYOffset;
+ _loadPanel.imageWidth = _vm->getDisplayInfo().loadPanelWidth;
+ _loadPanel.imageHeight = _vm->getDisplayInfo().loadPanelHeight;
+ _loadPanel.buttons = _vm->getDisplayInfo().loadPanelButtons;
+ _loadPanel.buttonsCount = _vm->getDisplayInfo().loadPanelButtonsCount;
+ _loadPanel.currentButton = NULL;
+
+ _savePanel.x = _vm->getDisplayInfo().savePanelXOffset;
+ _savePanel.y = _vm->getDisplayInfo().savePanelYOffset;
+ _savePanel.imageWidth = _vm->getDisplayInfo().savePanelWidth;
+ _savePanel.imageHeight = _vm->getDisplayInfo().savePanelHeight;
+ _savePanel.buttons = _vm->getDisplayInfo().savePanelButtons;
+ _savePanel.buttonsCount = _vm->getDisplayInfo().savePanelButtonsCount;
+ _saveEdit = _savePanel.getButton(_vm->getDisplayInfo().saveEditIndex);
+ _savePanel.currentButton = NULL;
+
+ _protectPanel.x = _vm->getDisplayInfo().protectPanelXOffset;
+ _protectPanel.y = _vm->getDisplayInfo().protectPanelYOffset;
+ _protectPanel.imageWidth = _vm->getDisplayInfo().protectPanelWidth;
+ _protectPanel.imageHeight = _vm->getDisplayInfo().protectPanelHeight;
+ _protectPanel.buttons = _vm->getDisplayInfo().protectPanelButtons;
+ _protectPanel.buttonsCount = _vm->getDisplayInfo().protectPanelButtonsCount;
+ _protectEdit = _protectPanel.getButton(_vm->getDisplayInfo().protectEditIndex);
+ _protectPanel.currentButton = NULL;
+
+ _active = true;
+ _panelMode = _lockedMode = kPanelNull;
+ _savedMode = -1;
+ _bossMode = -1;
+ _fadeMode = kNoFade;
+ _inMainMode = false;
+ *_statusText = 0;
+ _statusOnceColor = -1;
+
+ _inventoryCount = 0;
+ _inventoryPos = 0;
+ _inventoryStart = 0;
+ _inventoryEnd = 0;
+ _inventoryBox = 0;
+ _inventorySize = ITE_INVENTORY_SIZE;
+ _saveReminderState = 0;
+
+ _optionSaveFileTop = 0;
+ _optionSaveFileTitleNumber = 0;
+
+ _inventory = (uint16 *)calloc(_inventorySize, sizeof(uint16));
+ if (_inventory == NULL) {
+ error("Interface::Interface(): not enough memory");
+ }
+
+ _textInputRepeatPhase = 0;
+ _textInput = false;
+ _statusTextInput = false;
+ _statusTextInputState = kStatusTextInputFirstRun;
+
+ _disableAbortSpeeches = false;
+}
+
+Interface::~Interface(void) {
+ free(_inventory);
+
+ _mainPanel.sprites.freeMem();
+ _defPortraits.freeMem();
+ _scenePortraits.freeMem();
+}
+
+int Interface::activate() {
+ if (!_active) {
+ _active = true;
+ _vm->_script->_skipSpeeches = false;
+ _vm->_actor->_protagonist->_targetObject = ID_NOTHING;
+ unlockMode();
+ if (_panelMode == kPanelMain){
+ _saveReminderState = 1;
+ }
+ draw();
+ }
+ _vm->_gfx->showCursor(true);
+
+ return SUCCESS;
+}
+
+int Interface::deactivate() {
+ if (_active) {
+ _active = false;
+ lockMode();
+ setMode(kPanelNull);
+ }
+ _vm->_gfx->showCursor(false);
+
+ return SUCCESS;
+}
+
+void Interface::rememberMode() {
+ assert (_savedMode == -1);
+
+ _savedMode = _panelMode;
+}
+
+void Interface::restoreMode() {
+ assert (_savedMode != -1);
+
+ _panelMode = _savedMode;
+ _savedMode = -1;
+
+ draw();
+}
+
+void Interface::setMode(int mode) {
+ debug(1, "Interface::setMode %i", mode);
+
+ if (mode == kPanelMain) {
+ _inMainMode = true;
+ _saveReminderState = 1; //TODO: blinking timeout
+ } else {
+ if (mode == kPanelConverse) {
+ _inMainMode = false;
+ }
+ _saveReminderState = 0;
+ }
+
+ _panelMode = mode;
+
+ switch (_panelMode) {
+ case kPanelMain:
+ if (_vm->getGameType() == GType_IHNM)
+ warning("FIXME: Implement IHNM differences from ExecuteInventoryPanel");
+
+ _mainPanel.currentButton = NULL;
+ break;
+ case kPanelConverse:
+ _conversePanel.currentButton = NULL;
+ converseDisplayText();
+ break;
+ case kPanelOption:
+ _optionPanel.currentButton = NULL;
+ _vm->fillSaveList();
+ calcOptionSaveSlider();
+ if (_optionSaveFileTitleNumber >= _vm->getDisplayInfo().optionSaveFileVisible) {
+ _optionSaveFileTitleNumber = _vm->getDisplayInfo().optionSaveFileVisible - 1;
+ }
+ break;
+ case kPanelLoad:
+ _loadPanel.currentButton = NULL;
+ break;
+ case kPanelQuit:
+ _quitPanel.currentButton = NULL;
+ break;
+ case kPanelSave:
+ _savePanel.currentButton = NULL;
+ _textInputMaxWidth = _saveEdit->width - 10;
+ _textInput = true;
+ _textInputStringLength = strlen(_textInputString);
+ _textInputPos = _textInputStringLength + 1;
+ _textInputRepeatPhase = 0;
+ break;
+ case kPanelMap:
+ mapPanelShow();
+ break;
+ case kPanelSceneSubstitute:
+ _vm->_render->setFlag(RF_DEMO_SUBST);
+ _vm->_gfx->getCurrentPal(_mapSavedPal);
+ break;
+ case kPanelChapterSelection:
+ break;
+ case kPanelBoss:
+ _vm->_render->setFlag(RF_DEMO_SUBST);
+ break;
+ case kPanelProtect:
+ _protectPanel.currentButton = NULL;
+ _textInputMaxWidth = _protectEdit->width - 10;
+ _textInput = true;
+ _textInputString[0] = 0;
+ _textInputStringLength = 0;
+ _textInputPos = _textInputStringLength + 1;
+ _textInputRepeatPhase = 0;
+ break;
+ }
+
+ draw();
+}
+
+bool Interface::processAscii(uint16 ascii, bool synthetic) {
+ // TODO: Checking for Esc and Enter below is a bit hackish, and
+ // and probably only works with the English version. Maybe we should
+ // add a flag to the button so it can indicate if it's the default or
+ // cancel button?
+
+ int i;
+ PanelButton *panelButton;
+ if (!synthetic)
+ _textInputRepeatPhase = 0;
+ if (_statusTextInput) {
+ processStatusTextInput(ascii);
+ return true;
+ }
+
+ switch (_panelMode) {
+ case kPanelNull:
+ if (ascii == 27) { // Esc
+ if (_vm->_scene->isInIntro()) {
+ _vm->_scene->skipScene();
+ } else {
+ if (!_disableAbortSpeeches)
+ _vm->_actor->abortAllSpeeches();
+ }
+ return true;
+ }
+ break;
+ case kPanelCutaway:
+ if (ascii == 27) { // Esc
+ if (!_disableAbortSpeeches)
+ _vm->_actor->abortAllSpeeches();
+ _vm->_scene->cutawaySkip();
+ return true;
+ }
+ break;
+ case kPanelVideo:
+ if (ascii == 27) { // Esc
+ if (_vm->_scene->isInIntro()) {
+ _vm->_scene->skipScene();
+ } else {
+ if (!_disableAbortSpeeches)
+ _vm->_actor->abortAllSpeeches();
+ }
+ _vm->_scene->cutawaySkip();
+ }
+ break;
+ case kPanelOption:
+ // TODO: check input dialog keys
+ if (ascii == 27 || ascii == 13) { // Esc or Enter
+ ascii = 'c'; //continue
+ }
+
+ for (i = 0; i < _optionPanel.buttonsCount; i++) {
+ panelButton = &_optionPanel.buttons[i];
+ if (panelButton->type == kPanelButtonOption) {
+ if (panelButton->ascii == ascii) {
+ setOption(panelButton);
+ return true;
+ }
+ }
+ }
+ break;
+ case kPanelSave:
+ if (_textInput && processTextInput(ascii)) {
+ return true;
+ }
+
+ if (ascii == 27) { // Esc
+ ascii = 'c'; // cancel
+ } else if (ascii == 13) { // Enter
+ ascii = 's'; // save
+ }
+
+ for (i = 0; i < _savePanel.buttonsCount; i++) {
+ panelButton = &_savePanel.buttons[i];
+ if (panelButton->type == kPanelButtonSave) {
+ if (panelButton->ascii == ascii) {
+ setSave(panelButton);
+ return true;
+ }
+ }
+ }
+ break;
+ case kPanelQuit:
+ if (ascii == 27) { // Esc
+ ascii = 'c'; // cancel
+ } else if (ascii == 13) { // Enter
+ ascii = 'q'; // quit
+ }
+
+ for (i = 0; i < _quitPanel.buttonsCount; i++) {
+ panelButton = &_quitPanel.buttons[i];
+ if (panelButton->type == kPanelButtonQuit) {
+ if (panelButton->ascii == ascii) {
+ setQuit(panelButton);
+ return true;
+ }
+ }
+ }
+ break;
+ case kPanelLoad:
+ for (i = 0; i < _loadPanel.buttonsCount; i++) {
+ panelButton = &_loadPanel.buttons[i];
+ if (panelButton->type == kPanelButtonLoad) {
+ if (panelButton->ascii == ascii) {
+ setLoad(panelButton);
+ return true;
+ }
+ }
+ }
+ break;
+ case kPanelMain:
+ for (i = 0; i < _mainPanel.buttonsCount; i++) {
+ panelButton = &_mainPanel.buttons[i];
+ if (panelButton->ascii == ascii) {
+ if (panelButton->type == kPanelButtonVerb) {
+ _vm->_script->setVerb(panelButton->id);
+ }
+ if (panelButton->type == kPanelButtonArrow) {
+ inventoryChangePos(panelButton->id);
+ }
+ return true;
+ }
+ }
+ if (ascii == 15) // ctrl-o
+ {
+ if (_saveReminderState > 0) {
+ setMode(kPanelOption);
+ return true;
+ }
+ }
+ break;
+ case kPanelConverse:
+ switch (ascii) {
+ case 'x':
+ setMode(kPanelMain);
+ if (_vm->_puzzle->isActive())
+ _vm->_puzzle->exitPuzzle();
+ break;
+
+ case 'u':
+ converseChangePos(-1);
+ break;
+
+ case 'd':
+ converseChangePos(1);
+ break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ converseSetPos(ascii);
+ break;
+ }
+ break;
+ case kPanelMap:
+ mapPanelClean();
+ break;
+ case kPanelSceneSubstitute:
+ if (ascii == 13) {
+ _vm->_render->clearFlag(RF_DEMO_SUBST);
+ _vm->_gfx->setPalette(_mapSavedPal);
+ setMode(kPanelMain);
+ _vm->_script->setNoPendingVerb();
+ } else if (ascii == 'q' || ascii == 'Q') {
+ _vm->shutDown();
+ }
+ break;
+ case kPanelBoss:
+ _vm->_render->clearFlag(RF_DEMO_SUBST);
+ keyBossExit();
+ break;
+ case kPanelProtect:
+ if (_textInput && processTextInput(ascii)) {
+ return true;
+ }
+
+ if (ascii == 27 || ascii == 13) { // Esc or Enter
+ _vm->_script->wakeUpThreads(kWaitTypeRequest);
+ _vm->_interface->setMode(kPanelMain);
+
+ _protectHash = 0;
+
+ for (char *p = _textInputString; *p; p++)
+ _protectHash = (_protectHash << 1) + toupper(*p);
+ }
+ break;
+ }
+ return false;
+}
+
+#define KEYBOARD_REPEAT_DELAY1 300000L
+#define KEYBOARD_REPEAT_DELAY2 50000L
+
+void Interface::textInputRepeatCallback(void *refCon) {
+ ((Interface *)refCon)->textInputRepeat();
+}
+
+void Interface::textInputStartRepeat(uint16 ascii) {
+ if (!_textInputRepeatPhase) {
+ _textInputRepeatPhase = 1;
+ Common::g_timer->removeTimerProc(&textInputRepeatCallback);
+ Common::g_timer->installTimerProc(&textInputRepeatCallback, KEYBOARD_REPEAT_DELAY1, this);
+ }
+
+ _textInputRepeatChar = ascii;
+}
+
+void Interface::textInputRepeat() {
+ if (_textInputRepeatPhase == 1) {
+ _textInputRepeatPhase = 2;
+ Common::g_timer->removeTimerProc(&textInputRepeatCallback);
+ Common::g_timer->installTimerProc(&textInputRepeatCallback, KEYBOARD_REPEAT_DELAY2, this);
+ } else if (_textInputRepeatPhase == 2) {
+ processAscii(_textInputRepeatChar, true);
+ }
+}
+
+void Interface::processKeyUp(uint16 ascii) {
+ if (_textInputRepeatPhase) {
+ Common::g_timer->removeTimerProc(&textInputRepeatCallback);
+ _textInputRepeatPhase = 0;
+ }
+}
+
+void Interface::setStatusText(const char *text, int statusColor) {
+ assert(text != NULL);
+ assert(strlen(text) < STATUS_TEXT_LEN);
+
+ if (_vm->_render->getFlags() & (RF_PLACARD | RF_MAP))
+ return;
+
+ strncpy(_statusText, text, STATUS_TEXT_LEN);
+ _statusOnceColor = statusColor;
+ drawStatusBar();
+}
+
+void Interface::loadScenePortraits(int resourceId) {
+ _scenePortraits.freeMem();
+
+ _vm->_sprite->loadList(resourceId, _scenePortraits);
+}
+
+void Interface::drawVerbPanel(Surface *backBuffer, PanelButton* panelButton) {
+ PanelButton * rightButtonVerbPanelButton;
+ PanelButton * currentVerbPanelButton;
+ KnownColor textColor;
+ int spriteNumber;
+ Point point;
+
+ rightButtonVerbPanelButton = getPanelButtonByVerbType(_vm->_script->getRightButtonVerb());
+ currentVerbPanelButton = getPanelButtonByVerbType(_vm->_script->getCurrentVerb());
+
+ if (panelButton->state) {
+ textColor = kKnownColorVerbTextActive;
+ } else if (panelButton == rightButtonVerbPanelButton) {
+ textColor = kKnownColorVerbTextActive;
+ } else {
+ textColor = kKnownColorVerbText;
+ }
+
+ if (panelButton == currentVerbPanelButton) {
+ spriteNumber = panelButton->downSpriteNumber;
+ } else {
+ spriteNumber = panelButton->upSpriteNumber;
+ }
+ point.x = _mainPanel.x + panelButton->xOffset;
+ point.y = _mainPanel.y + panelButton->yOffset;
+
+ _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _mainPanel.sprites, spriteNumber, point, 256);
+
+ drawVerbPanelText(backBuffer, panelButton, textColor, kKnownColorVerbTextShadow);
+}
+
+void Interface::draw() {
+ Surface *backBuffer;
+ int i;
+
+ Point leftPortraitPoint;
+ Point rightPortraitPoint;
+ Rect rect;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ if (_vm->_scene->isInIntro() || _fadeMode == kFadeOut)
+ return;
+
+ drawStatusBar();
+
+ if (_panelMode == kPanelMain || _panelMode == kPanelMap) {
+ _mainPanel.getRect(rect);
+ backBuffer->blit(rect, _mainPanel.image);
+
+ for (i = 0; i < kVerbTypeIdsMax; i++) {
+ if (_verbTypeToPanelButton[i] != NULL) {
+ drawVerbPanel(backBuffer, _verbTypeToPanelButton[i]);
+ }
+ }
+ } else if (_panelMode == kPanelConverse) {
+ _conversePanel.getRect(rect);
+ backBuffer->blit(rect, _conversePanel.image);
+ converseDisplayTextLines(backBuffer);
+ }
+
+ if (_vm->getGameType() == GType_IHNM) {
+ if (_vm->_spiritualBarometer > 255)
+ _vm->_gfx->setPaletteColor(kIHNMColorPortrait, 0xff, 0xff, 0xff);
+ else
+ _vm->_gfx->setPaletteColor(kIHNMColorPortrait,
+ _vm->_spiritualBarometer * _portraitBgColor.red / 256,
+ _vm->_spiritualBarometer * _portraitBgColor.green / 256,
+ _vm->_spiritualBarometer * _portraitBgColor.blue / 256);
+ }
+
+ if (_panelMode == kPanelMain || _panelMode == kPanelConverse ||
+ _lockedMode == kPanelMain || _lockedMode == kPanelConverse) {
+ leftPortraitPoint.x = _mainPanel.x + _vm->getDisplayInfo().leftPortraitXOffset;
+ leftPortraitPoint.y = _mainPanel.y + _vm->getDisplayInfo().leftPortraitYOffset;
+ _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _defPortraits, _leftPortrait, leftPortraitPoint, 256);
+ }
+
+ if (!_inMainMode && _vm->getDisplayInfo().rightPortraitXOffset >= 0) { //FIXME: should we change !_inMainMode to _panelMode == kPanelConverse ?
+ rightPortraitPoint.x = _mainPanel.x + _vm->getDisplayInfo().rightPortraitXOffset;
+ rightPortraitPoint.y = _mainPanel.y + _vm->getDisplayInfo().rightPortraitYOffset;
+
+ // This looks like hack - particularly since it's only done for
+ // the right-side portrait - and perhaps it is! But as far as I
+ // can tell this is what the original engine does. And it keeps
+ // ITE from crashing when entering the Elk King's court.
+
+ if (_rightPortrait >= _scenePortraits.spriteCount)
+ _rightPortrait = 0;
+
+ _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _scenePortraits, _rightPortrait, rightPortraitPoint, 256);
+ }
+
+ drawInventory(backBuffer);
+}
+
+void Interface::calcOptionSaveSlider() {
+ int totalFiles = _vm->getSaveFilesCount();
+ int visibleFiles = _vm->getDisplayInfo().optionSaveFileVisible;
+ if (_optionSaveFileSlider == NULL) return; //TODO:REMOVE
+ int height = _optionSaveFileSlider->height;
+ int sliderHeight;
+ int pos;
+
+ if (totalFiles < visibleFiles) {
+ totalFiles = visibleFiles;
+ }
+
+ sliderHeight = visibleFiles * height / totalFiles;
+ if (sliderHeight < 7) {
+ sliderHeight = 7;
+ }
+
+ if (totalFiles - visibleFiles <= 0) {
+ pos = 0;
+ } else {
+ pos = _optionSaveFileTop * (height - sliderHeight) / (totalFiles - visibleFiles);
+ }
+ _optionPanel.calcPanelButtonRect(_optionSaveFileSlider, _optionSaveRectTop);
+ _optionSaveRectBottom = _optionSaveRectSlider = _optionSaveRectTop;
+
+ _optionSaveRectTop.bottom = _optionSaveRectTop.top + pos;
+ _optionSaveRectTop.top++;
+ _optionSaveRectTop.right--;
+
+ _optionSaveRectSlider.top = _optionSaveRectTop.bottom;
+ _optionSaveRectSlider.bottom = _optionSaveRectSlider.top + sliderHeight;
+
+ _optionSaveRectBottom.top = _optionSaveRectSlider.bottom;
+ _optionSaveRectBottom.right--;
+}
+
+void Interface::drawPanelText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) {
+ const char *text;
+ int textWidth;
+ Rect rect;
+ Point textPoint;
+
+ // Button differs for CD version
+ if (panelButton->id == kTextReadingSpeed && _vm->getFeatures() & GF_CD_FX)
+ return;
+ if (panelButton->id == kTextShowDialog && !(_vm->getFeatures() & GF_CD_FX))
+ return;
+
+ text = _vm->getTextString(panelButton->id);
+ panel->calcPanelButtonRect(panelButton, rect);
+ if (panelButton->xOffset < 0) {
+ textWidth = _vm->_font->getStringWidth(kKnownFontMedium, text, 0, kFontNormal);
+ rect.left += 2 + (panel->imageWidth - 1 - textWidth) / 2;
+ }
+
+ textPoint.x = rect.left;
+ textPoint.y = rect.top + 1;
+
+ _vm->_font->textDraw(kKnownFontMedium, ds, text, textPoint, _vm->KnownColor2ColorId(kKnownColorVerbText), _vm->KnownColor2ColorId(kKnownColorVerbTextShadow), kFontShadow);
+}
+
+void Interface::drawOption() {
+ const char *text;
+ Surface *backBuffer;
+ int i;
+ int fontHeight;
+ uint j, idx;
+ int fgColor;
+ int bgColor;
+ Rect rect;
+ Rect rect2;
+ PanelButton *panelButton;
+ Point textPoint;
+ if (_optionSaveFileSlider == NULL) return;//TODO:REMOVE
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ _optionPanel.getRect(rect);
+ backBuffer->blit(rect, _optionPanel.image);
+
+ for (i = 0; i < _optionPanel.buttonsCount; i++) {
+ panelButton = &_optionPanel.buttons[i];
+ if (panelButton->type == kPanelButtonOption) {
+ drawPanelButtonText(backBuffer, &_optionPanel, panelButton);
+ }
+ if (panelButton->type == kPanelButtonOptionText) {
+ drawPanelText(backBuffer, &_optionPanel, panelButton);
+ }
+ }
+
+ if (_optionSaveRectTop.height() > 0) {
+ backBuffer->drawRect(_optionSaveRectTop, kITEColorDarkGrey);
+ }
+
+ drawButtonBox(backBuffer, _optionSaveRectSlider, kSlider, _optionSaveFileSlider->state > 0);
+
+ if (_optionSaveRectBottom.height() > 0) {
+ backBuffer->drawRect(_optionSaveRectBottom, kITEColorDarkGrey);
+ }
+
+ _optionPanel.calcPanelButtonRect(_optionSaveFilePanel, rect);
+ rect.top++;
+ rect2 = rect;
+ fontHeight = _vm->_font->getHeight(kKnownFontSmall);
+ for (j = 0; j < _vm->getDisplayInfo().optionSaveFileVisible; j++) {
+ bgColor = kITEColorDarkGrey0C;
+ fgColor = kITEColorBrightWhite;
+
+ idx = j + _optionSaveFileTop;
+ if (idx == _optionSaveFileTitleNumber) {
+ SWAP(bgColor, fgColor);
+ }
+ if (idx < _vm->getSaveFilesCount()) {
+ rect2.top = rect.top + j * (fontHeight + 1);
+ rect2.bottom = rect2.top + fontHeight;
+ backBuffer->fillRect(rect2, bgColor);
+ text = _vm->getSaveFile(idx)->name;
+ textPoint.x = rect.left + 1;
+ textPoint.y = rect2.top;
+ _vm->_font->textDraw(kKnownFontSmall, backBuffer, text, textPoint, fgColor, 0, kFontNormal);
+ }
+ }
+
+}
+
+void Interface::drawQuit() {
+ Surface *backBuffer;
+ Rect rect;
+ int i;
+ PanelButton *panelButton;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ _quitPanel.getRect(rect);
+ drawButtonBox(backBuffer, rect, kButton, false);
+ for (i = 0; i < _quitPanel.buttonsCount; i++) {
+ panelButton = &_quitPanel.buttons[i];
+ if (panelButton->type == kPanelButtonQuit) {
+ drawPanelButtonText(backBuffer, &_quitPanel, panelButton);
+ }
+ if (panelButton->type == kPanelButtonQuitText) {
+ drawPanelText(backBuffer, &_quitPanel, panelButton);
+ }
+ }
+}
+
+void Interface::handleQuitUpdate(const Point& mousePoint) {
+ bool releasedButton;
+
+ _quitPanel.currentButton = quitHitTest(mousePoint);
+ releasedButton = (_quitPanel.currentButton != NULL) && (_quitPanel.currentButton->state > 0) && (!_vm->mouseButtonPressed());
+
+ if (!_vm->mouseButtonPressed()) {
+ _quitPanel.zeroAllButtonState();
+ }
+
+ if (releasedButton) {
+ setQuit(_quitPanel.currentButton);
+ }
+}
+
+void Interface::handleQuitClick(const Point& mousePoint) {
+ _quitPanel.currentButton = quitHitTest(mousePoint);
+
+ _quitPanel.zeroAllButtonState();
+
+ if (_quitPanel.currentButton == NULL) {
+ return;
+ }
+
+ _quitPanel.currentButton->state = 1;
+}
+
+void Interface::setQuit(PanelButton *panelButton) {
+ _quitPanel.currentButton = NULL;
+ switch (panelButton->id) {
+ case kTextCancel:
+ setMode(kPanelOption);
+ break;
+ case kTextQuit:
+ _vm->shutDown();
+ break;
+ }
+}
+
+void Interface::drawLoad() {
+ Surface *backBuffer;
+ Rect rect;
+ int i;
+ PanelButton *panelButton;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ _loadPanel.getRect(rect);
+ drawButtonBox(backBuffer, rect, kButton, false);
+ for (i = 0; i < _loadPanel.buttonsCount; i++) {
+ panelButton = &_loadPanel.buttons[i];
+ if (panelButton->type == kPanelButtonLoad) {
+ drawPanelButtonText(backBuffer, &_loadPanel, panelButton);
+ }
+ if (panelButton->type == kPanelButtonLoadText) {
+ drawPanelText(backBuffer, &_loadPanel, panelButton);
+ }
+ }
+}
+
+void Interface::handleLoadUpdate(const Point& mousePoint) {
+ bool releasedButton;
+
+ _loadPanel.currentButton = loadHitTest(mousePoint);
+ releasedButton = (_loadPanel.currentButton != NULL) && (_loadPanel.currentButton->state > 0) && (!_vm->mouseButtonPressed());
+
+ if (!_vm->mouseButtonPressed()) {
+ _loadPanel.zeroAllButtonState();
+ }
+
+ if (releasedButton) {
+ setLoad(_loadPanel.currentButton);
+ }
+}
+
+void Interface::handleLoadClick(const Point& mousePoint) {
+ _loadPanel.currentButton = loadHitTest(mousePoint);
+
+ _loadPanel.zeroAllButtonState();
+
+ if (_loadPanel.currentButton == NULL) {
+ return;
+ }
+
+ _loadPanel.currentButton->state = 1;
+}
+
+void Interface::setLoad(PanelButton *panelButton) {
+ _loadPanel.currentButton = NULL;
+ switch (panelButton->id) {
+ case kTextOK:
+ setMode(kPanelMain);
+ break;
+ }
+}
+
+void Interface::processStatusTextInput(uint16 ascii) {
+
+ textInputStartRepeat(ascii);
+ switch (ascii) {
+ case 27: // esc
+ _statusTextInputState = kStatusTextInputAborted;
+ _statusTextInput = false;
+ _vm->_script->wakeUpThreads(kWaitTypeStatusTextInput);
+ break;
+ case 13: // return
+ _statusTextInputState = kStatusTextInputEntered;
+ _statusTextInput = false;
+ _vm->_script->wakeUpThreads(kWaitTypeStatusTextInput);
+ break;
+ case 8: // backspace
+ if (_statusTextInputPos == 0) {
+ break;
+ }
+ _statusTextInputPos--;
+ _statusTextInputString[_statusTextInputPos] = 0;
+ default:
+ if (_statusTextInputPos >= STATUS_TEXT_INPUT_MAX) {
+ break;
+ }
+ if (((ascii >= 'a') && (ascii <='z')) ||
+ ((ascii >= '0') && (ascii <='9')) ||
+ ((ascii >= 'A') && (ascii <='Z')) ||
+ (ascii == ' ')) {
+ _statusTextInputString[_statusTextInputPos++] = ascii;
+ _statusTextInputString[_statusTextInputPos] = 0;
+ }
+ }
+ setStatusText(_statusTextInputString);
+}
+
+bool Interface::processTextInput(uint16 ascii) {
+ char ch[2];
+ char tempString[SAVE_TITLE_SIZE];
+ uint tempWidth;
+ memset(tempString, 0, SAVE_TITLE_SIZE);
+ ch[1] = 0;
+
+ textInputStartRepeat(ascii);
+
+ switch (ascii) {
+ case 13:
+ return false;
+ case 27: // esc
+ _textInput = false;
+ break;
+ case 8: // backspace
+ if (_textInputPos <= 1) {
+ break;
+ }
+ _textInputPos--;
+ case 127: // del
+ if (_textInputPos <= _textInputStringLength) {
+ if (_textInputPos != 1) {
+ strncpy(tempString, _textInputString, _textInputPos - 1);
+ }
+ if (_textInputPos != _textInputStringLength) {
+ strncat(tempString, &_textInputString[_textInputPos], _textInputStringLength - _textInputPos);
+ }
+ strcpy(_textInputString, tempString);
+ _textInputStringLength = strlen(_textInputString);
+ }
+ break;
+ case 276: // left
+ if (_textInputPos > 1) {
+ _textInputPos--;
+ }
+ break;
+ case 275: // right
+ if (_textInputPos <= _textInputStringLength) {
+ _textInputPos++;
+ }
+ break;
+ default:
+ if (((ascii >= 'a') && (ascii <='z')) ||
+ ((ascii >= '0') && (ascii <='9')) ||
+ ((ascii >= 'A') && (ascii <='Z')) ||
+ (ascii == ' ')) {
+ if (_textInputStringLength < SAVE_TITLE_SIZE - 1) {
+ ch[0] = ascii;
+ tempWidth = _vm->_font->getStringWidth(kKnownFontSmall, ch, 0, kFontNormal);
+ tempWidth += _vm->_font->getStringWidth(kKnownFontSmall, _textInputString, 0, kFontNormal);
+ if (tempWidth > _textInputMaxWidth) {
+ break;
+ }
+ if (_textInputPos != 1) {
+ strncpy(tempString, _textInputString, _textInputPos - 1);
+ strcat(tempString, ch);
+ }
+ if ((_textInputStringLength == 0) || (_textInputPos == 1)) {
+ strcpy(tempString, ch);
+ }
+ if ((_textInputStringLength != 0) && (_textInputPos != _textInputStringLength)) {
+ strncat(tempString, &_textInputString[_textInputPos - 1], _textInputStringLength - _textInputPos + 1);
+ }
+
+ strcpy(_textInputString, tempString);
+ _textInputStringLength = strlen(_textInputString);
+ _textInputPos++;
+ }
+ }
+ break;
+ }
+ return true;
+}
+
+void Interface::drawTextInput(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) {
+ Point textPoint;
+ Rect rect;
+ char ch[2];
+ int fgColor;
+ uint i;
+
+ ch[1] = 0;
+ panel->calcPanelButtonRect(panelButton, rect);
+ drawButtonBox(ds, rect, kEdit, _textInput);
+ rect.left += 4;
+ rect.top += 4;
+ rect.setHeight(_vm->_font->getHeight(kKnownFontSmall));
+
+ i = 0;
+ while ((ch[0] = _textInputString[i++]) != 0) {
+ rect.setWidth(_vm->_font->getStringWidth(kKnownFontSmall, ch, 0, kFontNormal));
+ if ((i == _textInputPos) && _textInput) {
+ fgColor = kITEColorBlack;
+ ds->fillRect(rect, kITEColorWhite);
+ } else {
+ fgColor = kITEColorWhite;
+ }
+ textPoint.x = rect.left;
+ textPoint.y = rect.top + 1;
+
+ _vm->_font->textDraw(kKnownFontSmall, ds, ch, textPoint, fgColor, 0, kFontNormal);
+ rect.left += rect.width();
+ }
+ if (_textInput && (_textInputPos >= i)) {
+ ch[0] = ' ';
+ rect.setWidth(_vm->_font->getStringWidth(kKnownFontSmall, ch, 0, kFontNormal));
+ ds->fillRect(rect, kITEColorWhite);
+ }
+}
+
+void Interface::drawSave() {
+ Surface *backBuffer;
+ Rect rect;
+ int i;
+ PanelButton *panelButton;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ _savePanel.getRect(rect);
+ drawButtonBox(backBuffer, rect, kButton, false);
+ for (i = 0; i < _savePanel.buttonsCount; i++) {
+ panelButton = &_savePanel.buttons[i];
+ if (panelButton->type == kPanelButtonSave) {
+ drawPanelButtonText(backBuffer, &_savePanel, panelButton);
+ }
+ if (panelButton->type == kPanelButtonSaveText) {
+ drawPanelText(backBuffer, &_savePanel, panelButton);
+ }
+ }
+
+ drawTextInput(backBuffer, &_savePanel, _saveEdit);
+}
+
+void Interface::drawProtect() {
+ Surface *backBuffer;
+ Rect rect;
+ int i;
+ PanelButton *panelButton;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ _protectPanel.getRect(rect);
+ drawButtonBox(backBuffer, rect, kButton, false);
+
+ for (i = 0; i < _protectPanel.buttonsCount; i++) {
+ panelButton = &_protectPanel.buttons[i];
+ if (panelButton->type == kPanelButtonProtectText) {
+ drawPanelText(backBuffer, &_protectPanel, panelButton);
+ }
+ }
+ drawTextInput(backBuffer, &_protectPanel, _protectEdit);
+}
+
+void Interface::handleSaveUpdate(const Point& mousePoint) {
+ bool releasedButton;
+
+ _savePanel.currentButton = saveHitTest(mousePoint);
+
+ validateSaveButtons();
+
+ releasedButton = (_savePanel.currentButton != NULL) &&
+ (_savePanel.currentButton->state > 0) && (!_vm->mouseButtonPressed());
+
+ if (!_vm->mouseButtonPressed()) {
+ _savePanel.zeroAllButtonState();
+ }
+
+ if (releasedButton) {
+ setSave(_savePanel.currentButton);
+ }
+}
+
+void Interface::handleSaveClick(const Point& mousePoint) {
+ _savePanel.currentButton = saveHitTest(mousePoint);
+
+ validateSaveButtons();
+
+ _savePanel.zeroAllButtonState();
+
+ if (_savePanel.currentButton == NULL) {
+ _textInput = false;
+ return;
+ }
+
+ _savePanel.currentButton->state = 1;
+ if (_savePanel.currentButton == _saveEdit) {
+ _textInput = true;
+ }
+}
+
+void Interface::setSave(PanelButton *panelButton) {
+ _savePanel.currentButton = NULL;
+ uint titleNumber;
+ char *fileName;
+ switch (panelButton->id) {
+ case kTextSave:
+ if (_textInputStringLength == 0 ) {
+ break;
+ }
+ if (!_vm->isSaveListFull() && (_optionSaveFileTitleNumber == 0)) {
+ if (_vm->locateSaveFile(_textInputString, titleNumber)) {
+ fileName = _vm->calcSaveFileName(_vm->getSaveFile(titleNumber)->slotNumber);
+ _vm->save(fileName, _textInputString);
+ _optionSaveFileTitleNumber = titleNumber;
+ } else {
+ fileName = _vm->calcSaveFileName(_vm->getNewSaveSlotNumber());
+ _vm->save(fileName, _textInputString);
+ _vm->fillSaveList();
+ calcOptionSaveSlider();
+ }
+ } else {
+ fileName = _vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber);
+ _vm->save(fileName, _textInputString);
+ }
+ _textInput = false;
+ setMode(kPanelOption);
+ break;
+ case kTextCancel:
+ _textInput = false;
+ setMode(kPanelOption);
+ break;
+ }
+}
+
+void Interface::handleOptionUpdate(const Point& mousePoint) {
+ int16 mouseY;
+ Rect rect;
+ int totalFiles = _vm->getSaveFilesCount();
+ int visibleFiles = _vm->getDisplayInfo().optionSaveFileVisible;
+ bool releasedButton;
+
+ if (_vm->mouseButtonPressed()) {
+ if (_optionSaveFileSlider != NULL) //TODO:REMOVE
+ if (_optionSaveFileSlider->state > 0) {
+ _optionPanel.calcPanelButtonRect(_optionSaveFileSlider, rect);
+
+ mouseY = mousePoint.y - rect.top -_optionSaveFileMouseOff;
+
+ if (totalFiles - visibleFiles <= 0) {
+ _optionSaveFileTop = 0;
+ } else {
+ _optionSaveFileTop = mouseY * (totalFiles - visibleFiles) /
+ (_optionSaveFileSlider->height - _optionSaveRectSlider.height());
+ }
+
+ _optionSaveFileTop = clamp(0, _optionSaveFileTop, _vm->getSaveFilesCount() - _vm->getDisplayInfo().optionSaveFileVisible);
+ calcOptionSaveSlider();
+ }
+ }
+
+ _optionPanel.currentButton = optionHitTest(mousePoint);
+
+ validateOptionButtons();
+
+ releasedButton = (_optionPanel.currentButton != NULL) && (_optionPanel.currentButton->state > 0) && (!_vm->mouseButtonPressed());
+
+ if (!_vm->mouseButtonPressed()) {
+ _optionPanel.zeroAllButtonState();
+ }
+
+ if (releasedButton) {
+ setOption(_optionPanel.currentButton);
+ }
+}
+
+
+void Interface::handleOptionClick(const Point& mousePoint) {
+ Rect rect;
+ _optionPanel.currentButton = optionHitTest(mousePoint);
+
+ validateOptionButtons();
+
+ _optionPanel.zeroAllButtonState();
+
+ if (_optionPanel.currentButton == NULL) {
+ return;
+ }
+
+ if (_optionPanel.currentButton == _optionSaveFileSlider) {
+ if ((_optionSaveRectTop.height() > 0) && (mousePoint.y < _optionSaveRectTop.bottom)) {
+ _optionSaveFileTop -= _vm->getDisplayInfo().optionSaveFileVisible;
+ } else {
+ if ((_optionSaveRectBottom.height() > 0) && (mousePoint.y >= _optionSaveRectBottom.top)) {
+ _optionSaveFileTop += _vm->getDisplayInfo().optionSaveFileVisible;
+ } else {
+ if (_vm->getDisplayInfo().optionSaveFileVisible < _vm->getSaveFilesCount()) {
+ _optionSaveFileMouseOff = mousePoint.y - _optionSaveRectSlider.top;
+ _optionPanel.currentButton->state = 1;
+ }
+ }
+ }
+
+ _optionSaveFileTop = clamp(0, _optionSaveFileTop, _vm->getSaveFilesCount() - _vm->getDisplayInfo().optionSaveFileVisible);
+ calcOptionSaveSlider();
+ } else {
+ if (_optionPanel.currentButton == _optionSaveFilePanel) {
+ _optionPanel.calcPanelButtonRect(_optionSaveFilePanel, rect);
+ _optionSaveFileTitleNumber = (mousePoint.y - rect.top) / (_vm->_font->getHeight(kKnownFontSmall) + 1);
+
+ if (_optionSaveFileTitleNumber >= _vm->getDisplayInfo().optionSaveFileVisible) {
+ _optionSaveFileTitleNumber = _vm->getDisplayInfo().optionSaveFileVisible - 1;
+ }
+ _optionSaveFileTitleNumber += _optionSaveFileTop;
+ if (_optionSaveFileTitleNumber >= _vm->getSaveFilesCount()) {
+ _optionSaveFileTitleNumber = _vm->getSaveFilesCount() - 1;
+ }
+ } else {
+ _optionPanel.currentButton->state = 1;
+ }
+ }
+}
+
+void Interface::handleChapterSelectionUpdate(const Point& mousePoint) {
+ uint16 objectId;
+
+ // FIXME: Original handled more object types here.
+
+ objectId = _vm->_actor->hitTest(mousePoint, true);
+
+ if (objectId != _vm->_script->_pointerObject) {
+ _vm->_script->_pointerObject = objectId;
+ }
+}
+
+void Interface::handleChapterSelectionClick(const Point& mousePoint) {
+ int obj = _vm->_script->_pointerObject;
+
+ _vm->_actor->abortSpeech();
+
+ if (obj) {
+ int script = 0;
+ HitZone *hitZone;
+ ActorData *a;
+ ObjectData *o;
+ Event event;
+
+ switch (objectTypeId(obj)) {
+ case kGameObjectHitZone:
+ hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(obj));
+ if (hitZone->getFlags() & kHitZoneExit)
+ script = hitZone->getScriptNumber();
+ break;
+
+ case kGameObjectActor:
+ a = _vm->_actor->getActor(obj);
+ script = a->_scriptEntrypointNumber;
+ break;
+
+ case kGameObjectObject:
+ o = _vm->_actor->getObj(obj);
+ script = o->_scriptEntrypointNumber;
+ break;
+ }
+
+ if (script > 0) {
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventExecNonBlocking;
+ event.time = 0;
+ event.param = _vm->_scene->getScriptModuleNumber();
+ event.param2 = script;
+ event.param3 = _vm->_script->getVerbType(kVerbUse); // Action
+ event.param4 = obj; // Object
+ event.param5 = 0; // With Object
+ event.param6 = obj; // Actor
+
+ _vm->_events->queue(&event);
+ }
+ }
+}
+
+void Interface::setOption(PanelButton *panelButton) {
+ char * fileName;
+ _optionPanel.currentButton = NULL;
+ switch (panelButton->id) {
+ case kTextContinuePlaying:
+ ConfMan.flushToDisk();
+ setMode(kPanelMain);
+ break;
+ case kTextQuitGame:
+ setMode(kPanelQuit);
+ break;
+ case kTextLoad:
+ if (_vm->getSaveFilesCount() > 0) {
+ if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) {
+ fileName = _vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber);
+ setMode(kPanelMain);
+ _vm->load(fileName);
+ }
+ }
+ break;
+ case kTextSave:
+ if (!_vm->isSaveListFull() && (_optionSaveFileTitleNumber == 0)) {
+ _textInputString[0] = 0;
+ } else {
+ strcpy(_textInputString, _vm->getSaveFile(_optionSaveFileTitleNumber)->name);
+ }
+ setMode(kPanelSave);
+ break;
+ case kTextReadingSpeed:
+ if (_vm->getFeatures() & GF_CD_FX) {
+ _vm->_subtitlesEnabled = !_vm->_subtitlesEnabled;
+ ConfMan.set("subtitles", _vm->_subtitlesEnabled);
+ } else {
+ _vm->_readingSpeed = (_vm->_readingSpeed + 1) % 4;
+ ConfMan.set("talkspeed", _vm->_readingSpeed);
+ }
+ break;
+ case kTextMusic:
+ _vm->_musicVolume = (_vm->_musicVolume + 1) % 11;
+ _vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1);
+ ConfMan.set("music_volume", _vm->_musicVolume * 25);
+ break;
+ case kTextSound:
+ _vm->_soundVolume = (_vm->_soundVolume + 1) % 11;
+ _vm->_sound->setVolume(_vm->_soundVolume == 10 ? 255 : _vm->_soundVolume * 25);
+ ConfMan.set("sfx_volume", _vm->_soundVolume * 25);
+ break;
+ }
+}
+
+void Interface::update(const Point& mousePoint, int updateFlag) {
+
+ if (!_active && _panelMode == kPanelNull && (updateFlag & UPDATE_MOUSECLICK))
+ _vm->_actor->abortSpeech();
+
+ if (_vm->_scene->isInIntro() || _fadeMode == kFadeOut || !_active) {
+ return;
+ }
+
+ if (_statusTextInput) {
+ return;
+ }
+
+ switch (_panelMode) {
+ case kPanelMain:
+ if (updateFlag & UPDATE_MOUSEMOVE) {
+ bool lastWasPlayfield = _lastMousePoint.y < _vm->_scene->getHeight();
+ if (mousePoint.y < _vm->_scene->getHeight()) {
+ if (!lastWasPlayfield) {
+ handleMainUpdate(mousePoint);
+ }
+ _vm->_script->whichObject(mousePoint);
+ } else {
+ if (lastWasPlayfield) {
+ _vm->_script->setNonPlayfieldVerb();
+ }
+ handleMainUpdate(mousePoint);
+ }
+
+ } else {
+
+ if (updateFlag & UPDATE_MOUSECLICK) {
+ if (mousePoint.y < _vm->_scene->getHeight()) {
+ _vm->_script->playfieldClick(mousePoint, (updateFlag & UPDATE_LEFTBUTTONCLICK) != 0);
+ } else {
+ handleMainClick(mousePoint);
+ }
+ }
+ }
+ break;
+
+ case kPanelConverse:
+ if (updateFlag & UPDATE_MOUSEMOVE) {
+ handleConverseUpdate(mousePoint);
+ } else {
+ if (updateFlag & UPDATE_MOUSECLICK) {
+ handleConverseClick(mousePoint);
+ }
+ if (updateFlag & UPDATE_WHEELUP) {
+ converseChangePos(-1);
+ }
+ if (updateFlag & UPDATE_WHEELDOWN) {
+ converseChangePos(1);
+ }
+
+ if (_vm->_puzzle->isActive()) {
+ _vm->_puzzle->handleClick(mousePoint);
+ }
+ }
+ break;
+
+ case kPanelOption:
+ if (updateFlag & UPDATE_MOUSEMOVE) {
+ handleOptionUpdate(mousePoint);
+ } else {
+ if (updateFlag & UPDATE_MOUSECLICK) {
+ handleOptionClick(mousePoint);
+ }
+ if (updateFlag & UPDATE_WHEELUP) {
+ if (_optionSaveFileTop)
+ _optionSaveFileTop--;
+ calcOptionSaveSlider();
+ }
+ if (updateFlag & UPDATE_WHEELDOWN) {
+ if (_optionSaveFileTop < _vm->getSaveFilesCount() - _vm->getDisplayInfo().optionSaveFileVisible)
+ _optionSaveFileTop++;
+ calcOptionSaveSlider();
+ }
+ }
+ break;
+
+ case kPanelQuit:
+ if (updateFlag & UPDATE_MOUSEMOVE) {
+ handleQuitUpdate(mousePoint);
+ } else {
+ if (updateFlag & UPDATE_MOUSECLICK) {
+ handleQuitClick(mousePoint);
+ }
+ }
+ break;
+
+ case kPanelLoad:
+ if (updateFlag & UPDATE_MOUSEMOVE) {
+
+ handleLoadUpdate(mousePoint);
+
+ } else {
+ if (updateFlag & UPDATE_MOUSECLICK) {
+ handleLoadClick(mousePoint);
+ }
+ }
+ break;
+
+ case kPanelSave:
+ if (updateFlag & UPDATE_MOUSEMOVE) {
+
+ handleSaveUpdate(mousePoint);
+
+ } else {
+ if (updateFlag & UPDATE_MOUSECLICK) {
+ handleSaveClick(mousePoint);
+ }
+ }
+ break;
+
+ case kPanelMap:
+ if (updateFlag & UPDATE_MOUSECLICK)
+ mapPanelClean();
+ break;
+
+ case kPanelSceneSubstitute:
+ if (updateFlag & UPDATE_MOUSECLICK) {
+ _vm->_render->clearFlag(RF_DEMO_SUBST);
+ _vm->_gfx->setPalette(_mapSavedPal);
+ setMode(kPanelMain);
+ _vm->_script->setNoPendingVerb();
+ }
+ break;
+
+ case kPanelChapterSelection:
+ // TODO: panel has silent button
+ if (updateFlag & UPDATE_MOUSEMOVE) {
+ handleChapterSelectionUpdate(mousePoint);
+ } else {
+ if (updateFlag & UPDATE_MOUSECLICK)
+ handleChapterSelectionClick(mousePoint);
+ }
+ break;
+
+ case kPanelProtect:
+ // No mouse interaction
+ break;
+
+ }
+
+ _lastMousePoint = mousePoint;
+}
+
+void Interface::drawStatusBar() {
+ Surface *backBuffer;
+ Rect rect;
+ Point textPoint;
+ int stringWidth;
+ int color;
+
+ if (_panelMode == kPanelChapterSelection)
+ return;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ // Disable this for IHNM for now, since that game uses the full screen
+ // in some cases.
+
+ // Erase background of status bar
+ rect.left = _vm->getDisplayInfo().statusXOffset;
+ rect.top = _vm->getDisplayInfo().statusYOffset;
+ rect.right = rect.left + _vm->getDisplayWidth();
+ rect.bottom = rect.top + _vm->getDisplayInfo().statusHeight;
+
+ backBuffer->drawRect(rect, _vm->getDisplayInfo().statusBGColor);
+
+ stringWidth = _vm->_font->getStringWidth(kKnownFontSmall, _statusText, 0, kFontNormal);
+
+ if (_statusOnceColor == -1)
+ color = _vm->getDisplayInfo().statusTextColor;
+ else
+ color = _statusOnceColor;
+
+ textPoint.x = _vm->getDisplayInfo().statusXOffset + (_vm->getDisplayInfo().statusWidth - stringWidth) / 2;
+ textPoint.y = _vm->getDisplayInfo().statusYOffset + _vm->getDisplayInfo().statusTextY;
+ _vm->_font->textDraw(kKnownFontSmall, backBuffer, _statusText, textPoint, color, 0, kFontNormal);
+
+ if (_saveReminderState > 0) {
+ rect.left = _vm->getDisplayInfo().saveReminderXOffset;
+ rect.top = _vm->getDisplayInfo().saveReminderYOffset;
+
+ rect.right = rect.left + _vm->getDisplayInfo().saveReminderWidth;
+ rect.bottom = rect.top + _vm->getDisplayInfo().saveReminderHeight;
+ _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _vm->_sprite->_saveReminderSprites,
+ _saveReminderState == 1 ? _vm->getDisplayInfo().saveReminderFirstSpriteNumber : _vm->getDisplayInfo().saveReminderSecondSpriteNumber,
+ rect, 256);
+
+ }
+}
+
+void Interface::handleMainClick(const Point& mousePoint) {
+
+ PanelButton *panelButton;
+
+ panelButton = verbHitTest(mousePoint);
+ if (panelButton) {
+ _vm->_script->setVerb(panelButton->id);
+ return;
+ }
+
+ panelButton = _mainPanel.hitTest(mousePoint, kPanelAllButtons);
+
+ if (panelButton != NULL) {
+ if (panelButton->type == kPanelButtonArrow) {
+ panelButton->state = 1;
+ converseChangePos(panelButton->id);
+ }
+
+ if (panelButton->type == kPanelButtonInventory) {
+ if (_vm->_script->_pointerObject != ID_NOTHING) {
+ _vm->_script->hitObject(_vm->leftMouseButtonPressed());
+ }
+ if (_vm->_script->_pendingVerb) {
+ _vm->_actor->_protagonist->_currentAction = kActionWait;
+ _vm->_script->doVerb();
+ }
+ }
+ } else {
+ if (_saveReminderState > 0) {
+ Rect rect;
+ rect.left = _vm->getDisplayInfo().saveReminderXOffset;
+ rect.top = _vm->getDisplayInfo().saveReminderYOffset;
+
+ rect.right = rect.left + _vm->getDisplayInfo().saveReminderWidth;
+ rect.bottom = rect.top + _vm->getDisplayInfo().saveReminderHeight;
+ if (rect.contains(mousePoint)) {
+ setMode(kPanelOption);
+ }
+ }
+ }
+}
+
+void Interface::handleMainUpdate(const Point& mousePoint) {
+ PanelButton *panelButton;
+
+ panelButton = verbHitTest(mousePoint);
+ if (_mainPanel.currentButton != panelButton) {
+ if (_mainPanel.currentButton) {
+ if (_mainPanel.currentButton->type == kPanelButtonVerb) {
+ setVerbState(_mainPanel.currentButton->id, 0);
+ }
+ }
+ if (panelButton) {
+ setVerbState(panelButton->id, 1);
+ }
+ }
+
+ if (panelButton) {
+ _mainPanel.currentButton = panelButton;
+ return;
+ }
+
+
+ if (!_vm->mouseButtonPressed()) { // remove pressed flag
+ if (_inventoryUpButton) {
+ _inventoryUpButton->state = 0;
+ _inventoryDownButton->state = 0;
+ }
+ }
+
+ panelButton = _mainPanel.hitTest(mousePoint, kPanelAllButtons);
+
+ bool changed = false;
+
+ if ((panelButton != NULL) && (panelButton->type == kPanelButtonArrow)) {
+ if (panelButton->state == 1) {
+ //TODO: insert timeout catchup
+ inventoryChangePos(panelButton->id);
+ }
+ changed = true;
+ } else {
+ _vm->_script->whichObject(mousePoint);
+ }
+
+ changed = changed || (panelButton != _mainPanel.currentButton);
+ _mainPanel.currentButton = panelButton;
+ if (changed) {
+ draw();
+ }
+}
+
+//inventory stuff
+void Interface::inventoryChangePos(int chg) {
+ if ((chg < 0 && _inventoryStart + chg >= 0) ||
+ (chg > 0 && _inventoryStart < _inventoryEnd)) {
+ _inventoryStart += chg;
+ draw();
+ }
+}
+
+void Interface::inventorySetPos(int key) {
+ _inventoryBox = key - '1';
+ _inventoryPos = _inventoryStart + _inventoryBox;
+ if (_inventoryPos >= _inventoryCount)
+ _inventoryPos = -1;
+}
+
+void Interface::updateInventory(int pos) {
+ int cols = _vm->getDisplayInfo().inventoryColumns;
+ if (pos >= _inventoryCount) {
+ pos = _inventoryCount - 1;
+ }
+ if (pos < 0) {
+ pos = 0;
+ }
+ _inventoryStart = (pos - cols) / cols * cols;
+ if (_inventoryStart < 0) {
+ _inventoryStart = 0;
+ }
+
+ _inventoryEnd = (_inventoryCount - 1 - cols) / cols * cols;
+ if (_inventoryEnd < 0) {
+ _inventoryEnd = 0;
+ }
+}
+
+void Interface::addToInventory(int objectId) {
+ if (_inventoryCount >= _inventorySize) {
+ return;
+ }
+
+ for (int i = _inventoryCount; i > 0; i--) {
+ _inventory[i] = _inventory[i - 1];
+ }
+
+ _inventory[0] = objectId;
+ _inventoryCount++;
+
+ _inventoryPos = 0;
+ updateInventory(0);
+ draw();
+}
+
+void Interface::removeFromInventory(int objectId) {
+ int j = inventoryItemPosition(objectId);
+ if (j == -1) {
+ return;
+ }
+
+ int i;
+
+ for (i = j; i < _inventoryCount - 1; i++) {
+ _inventory[i] = _inventory[i + 1];
+ }
+
+ --_inventoryCount;
+ _inventory[_inventoryCount] = 0;
+ updateInventory(j);
+ draw();
+}
+
+void Interface::clearInventory() {
+ for (int i = 0; i < _inventoryCount; i++)
+ _inventory[i] = 0;
+
+ _inventoryCount = 0;
+ updateInventory(0);
+}
+
+int Interface::inventoryItemPosition(int objectId) {
+ for (int i = 0; i < _inventoryCount; i++)
+ if (_inventory[i] == objectId)
+ return i;
+
+ return -1;
+}
+
+void Interface::drawInventory(Surface *backBuffer) {
+ if (!isInMainMode())
+ return;
+
+ int i;
+ Rect rect;
+ int ci;
+ ObjectData *obj;
+ ci = _inventoryStart;
+ if (_inventoryStart != 0) {
+ drawPanelButtonArrow(backBuffer, &_mainPanel, _inventoryUpButton);
+ }
+ if (_inventoryStart != _inventoryEnd) {
+ drawPanelButtonArrow(backBuffer, &_mainPanel, _inventoryDownButton);
+ }
+
+ for (i = 0; i < _mainPanel.buttonsCount; i++) {
+ if (_mainPanel.buttons[i].type != kPanelButtonInventory) {
+ continue;
+ }
+ _mainPanel.calcPanelButtonRect(&_mainPanel.buttons[i], rect);
+
+ // TODO: Different colour for IHNM, probably.
+ backBuffer->drawRect(rect, kITEColorDarkGrey);
+
+ if (ci < _inventoryCount) {
+ obj = _vm->_actor->getObj(_inventory[ci]);
+ _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _vm->_sprite->_inventorySprites, obj->_spriteListResourceId, rect, 256);
+ }
+
+ ci++;
+ }
+}
+
+void Interface::setVerbState(int verb, int state) {
+ PanelButton * panelButton = getPanelButtonByVerbType(verb);
+ if (panelButton == NULL) return;
+ if (state == 2) {
+ state = (_mainPanel.currentButton == panelButton) ? 1 : 0;
+ }
+ panelButton->state = state;
+ draw();
+}
+
+void Interface::drawButtonBox(Surface *ds, const Rect& rect, ButtonKind kind, bool down) {
+ byte cornerColor;
+ byte frameColor;
+ byte fillColor;
+ byte solidColor;
+ byte odl, our, idl, iur;
+
+ switch (kind ) {
+ case kSlider:
+ cornerColor = 0x8b;
+ frameColor = kITEColorBlack;
+ fillColor = kITEColorLightBlue96;
+ odl = kITEColorDarkBlue8a;
+ our = kITEColorLightBlue92;
+ idl = 0x89;
+ iur = 0x94;
+ solidColor = down ? kITEColorLightBlue94 : kITEColorLightBlue96;
+ break;
+ case kEdit:
+ cornerColor = kITEColorLightBlue96;
+ frameColor = kITEColorLightBlue96;
+ fillColor = kITEColorLightBlue96;
+ our = kITEColorDarkBlue8a;
+ odl = kITEColorLightBlue94;
+ iur = 0x97;
+ idl = 0x95;
+ if (down) {
+ solidColor = kITEColorBlue;
+ } else {
+ solidColor = kITEColorDarkGrey0C;
+ }
+ break;
+ default:
+ cornerColor = 0x8b;
+ frameColor = kITEColorBlack;
+ solidColor = fillColor = kITEColorLightBlue96;
+ odl = kITEColorDarkBlue8a;
+ our = kITEColorLightBlue94;
+ idl = 0x97;
+ iur = 0x95;
+ if (down) {
+ SWAP(odl, our);
+ SWAP(idl, iur);
+ }
+ break;
+ }
+
+ int x = rect.left;
+ int y = rect.top;
+ int w = rect.width();
+ int h = rect.height();
+ int xe = rect.right - 1;
+ int ye = rect.bottom - 1;
+
+ ((byte *)ds->getBasePtr(x, y))[0] = cornerColor;
+ ((byte *)ds->getBasePtr(x, ye))[0] = cornerColor;
+ ((byte *)ds->getBasePtr(xe, y))[0] = cornerColor;
+ ((byte *)ds->getBasePtr(xe, ye))[0] = cornerColor;
+ ds->hLine(x + 1, y, x + 1 + w - 2, frameColor);
+ ds->hLine(x + 1, ye, x + 1 + w - 2, frameColor);
+ ds->vLine(x, y + 1, y + 1 + h - 2, frameColor);
+ ds->vLine(xe, y + 1, y + 1 + h - 2, frameColor);
+
+ x++;
+ y++;
+ xe--;
+ ye--;
+ w -= 2;
+ h -= 2;
+ ds->vLine(x, y, y + h - 1, odl);
+ ds->hLine(x, ye, x + w - 1, odl);
+ ds->vLine(xe, y, y + h - 1, our);
+ ds->hLine(x + 1, y, x + 1 + w - 2, our);
+
+ x++;
+ y++;
+ xe--;
+ ye--;
+ w -= 2;
+ h -= 2;
+ ((byte *)ds->getBasePtr(x, y))[0] = fillColor;
+ ((byte *)ds->getBasePtr(xe, ye))[0] = fillColor;
+ ds->vLine(x, y + 1, y + 1 + h - 2, idl);
+ ds->hLine(x + 1, ye, x + 1 + w - 2, idl);
+ ds->vLine(xe, y, y + h - 1, iur);
+ ds->hLine(x + 1, y, x + 1 + w - 2, iur);
+
+ x++; y++;
+ w -= 2; h -= 2;
+
+ Common::Rect fill(x, y, x + w, y + h);
+ ds->fillRect(fill, solidColor);
+}
+
+static const int readingSpeeds[] = { kTextFast, kTextMid, kTextSlow, kTextClick };
+
+void Interface::drawPanelButtonText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) {
+ const char *text;
+ int textId;
+ int textWidth;
+ int textHeight;
+ Point point;
+ KnownColor textColor;
+ Rect rect;
+
+ textId = panelButton->id;
+ switch(panelButton->id) {
+ case kTextReadingSpeed:
+ if (_vm->getFeatures() & GF_CD_FX) {
+ if (_vm->_subtitlesEnabled)
+ textId = kTextOn;
+ else
+ textId = kTextOff;
+ } else {
+ textId = readingSpeeds[_vm->_readingSpeed];
+ }
+ break;
+ case kTextMusic:
+ if (_vm->_musicVolume)
+ textId = kText10Percent + _vm->_musicVolume - 1;
+ else
+ textId = kTextOff;
+ break;
+ case kTextSound:
+ if (_vm->_soundVolume)
+ textId = kText10Percent + _vm->_soundVolume - 1;
+ else
+ textId = kTextOff;
+ break;
+ }
+ text = _vm->getTextString(textId);
+
+ textWidth = _vm->_font->getStringWidth(kKnownFontMedium, text, 0, kFontNormal);
+ textHeight = _vm->_font->getHeight(kKnownFontMedium);
+
+ point.x = panel->x + panelButton->xOffset + (panelButton->width / 2) - (textWidth / 2);
+ point.y = panel->y + panelButton->yOffset + (panelButton->height / 2) - (textHeight / 2);
+
+ if (panelButton == panel->currentButton) {
+ textColor = kKnownColorVerbTextActive;
+ } else {
+ textColor = kKnownColorVerbText;
+ }
+
+ panel->calcPanelButtonRect(panelButton, rect);
+ drawButtonBox(ds, rect, kButton, panelButton->state > 0);
+
+ _vm->_font->textDraw(kKnownFontMedium, ds, text, point,
+ _vm->KnownColor2ColorId(textColor), _vm->KnownColor2ColorId(kKnownColorVerbTextShadow), kFontShadow);
+}
+
+void Interface::drawPanelButtonArrow(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) {
+ Point point;
+ int spriteNumber;
+
+ if (panel->currentButton == panelButton) {
+ if (panelButton->state != 0) {
+ spriteNumber = panelButton->downSpriteNumber;
+ } else {
+ spriteNumber = panelButton->overSpriteNumber;
+ }
+ } else {
+ spriteNumber = panelButton->upSpriteNumber;
+ }
+
+ point.x = panel->x + panelButton->xOffset;
+ point.y = panel->y + panelButton->yOffset;
+
+ _vm->_sprite->draw(ds, _vm->getDisplayClip(), _vm->_sprite->_mainSprites, spriteNumber, point, 256);
+}
+
+void Interface::drawVerbPanelText(Surface *ds, PanelButton *panelButton, KnownColor textKnownColor, KnownColor textShadowKnownColor) {
+ const char *text;
+ int textWidth;
+ Point point;
+ int textId;
+
+ if (_vm->getGameType() == GType_ITE) {
+ textId = verbTypeToTextStringsIdLUT[0][panelButton->id];
+ text = _vm->getTextString(textId);
+ } else {
+ textId = verbTypeToTextStringsIdLUT[1][panelButton->id];
+ text = _vm->_script->_mainStrings.getString(textId + 1);
+ textShadowKnownColor = kKnownColorTransparent;
+ }
+
+ textWidth = _vm->_font->getStringWidth(kKnownFontVerb, text, 0, kFontNormal);
+
+ if (_vm->getGameType() == GType_ITE) {
+ point.x = _mainPanel.x + panelButton->xOffset + 1 + (panelButton->width - 1 - textWidth) / 2;
+ point.y = _mainPanel.y + panelButton->yOffset + 1;
+ } else {
+ point.x = _mainPanel.x + panelButton->xOffset + 1 + (panelButton->width - textWidth) / 2;
+ point.y = _mainPanel.y + panelButton->yOffset + 12;
+ }
+
+ _vm->_font->textDraw(kKnownFontVerb, ds, text, point, _vm->KnownColor2ColorId(textKnownColor),_vm->KnownColor2ColorId(textShadowKnownColor), (textShadowKnownColor != kKnownColorTransparent) ? kFontShadow : kFontNormal);
+}
+
+
+// Converse stuff
+void Interface::converseInit(void) {
+ for (int i = 0; i < CONVERSE_MAX_TEXTS; i++)
+ _converseText[i].text = NULL;
+ converseClear();
+}
+
+void Interface::converseClear(void) {
+ for (int i = 0; i < CONVERSE_MAX_TEXTS; i++) {
+ if (_converseText[i].text != NULL) {
+ free(_converseText[i].text);
+ _converseText[i].text = NULL;
+ }
+ _converseText[i].stringNum = -1;
+ _converseText[i].replyId = 0;
+ _converseText[i].replyFlags = 0;
+ _converseText[i].replyBit = 0;
+ }
+
+ _converseTextCount = 0;
+ _converseStrCount = 0;
+ _converseStartPos = 0;
+ _converseEndPos = 0;
+ _conversePos = -1;
+}
+
+bool Interface::converseAddText(const char *text, int replyId, byte replyFlags, int replyBit) {
+ int count = 0; // count how many pieces of text per string
+ int i;
+ int len;
+ byte c;
+
+ assert(strlen(text) < CONVERSE_MAX_WORK_STRING);
+
+ strncpy(_converseWorkString, text, CONVERSE_MAX_WORK_STRING);
+
+ while (1) {
+ len = strlen(_converseWorkString);
+
+ for (i = len; i >= 0; i--) {
+ c = _converseWorkString[i];
+
+ if ((c == ' ' || c == '\0') && (_vm->_font->getStringWidth(kKnownFontSmall, _converseWorkString, i, kFontNormal) <= _vm->getDisplayInfo().converseMaxTextWidth)) {
+ break;
+ }
+ }
+ if (i < 0) {
+ return true;
+ }
+
+ if (_converseTextCount == CONVERSE_MAX_TEXTS) {
+ return true;
+ }
+
+ _converseText[_converseTextCount].text = (char *)malloc(i + 1);
+ strncpy(_converseText[_converseTextCount].text, _converseWorkString, i);
+
+ _converseText[_converseTextCount].text[i] = 0;
+ _converseText[_converseTextCount].textNum = count;
+ _converseText[_converseTextCount].stringNum = _converseStrCount;
+ _converseText[_converseTextCount].replyId = replyId;
+ _converseText[_converseTextCount].replyFlags = replyFlags;
+ _converseText[_converseTextCount].replyBit = replyBit;
+
+ _converseTextCount++;
+ count++;
+
+ if (len == i)
+ break;
+
+ strncpy(_converseWorkString, &_converseWorkString[i + 1], len - i);
+ }
+
+ _converseStrCount++;
+
+ return false;
+}
+
+void Interface::converseDisplayText() {
+ int end;
+
+ _converseStartPos = 0;
+
+ end = _converseTextCount - _vm->getDisplayInfo().converseTextLines;
+
+ if (end < 0)
+ end = 0;
+
+ _converseEndPos = end;
+ draw();
+}
+
+
+void Interface::converseSetTextLines(int row) {
+ int pos = row + _converseStartPos;
+ if (pos >= _converseTextCount)
+ pos = -1;
+ if (pos != _conversePos) {
+ _conversePos = pos;
+ draw();
+ }
+}
+
+void Interface::converseDisplayTextLines(Surface *ds) {
+ int relPos;
+ byte foregnd;
+ byte backgnd;
+ byte bulletForegnd;
+ byte bulletBackgnd;
+ const char *str;
+ char bullet[2] = {
+ (char)0xb7, 0
+ };
+ Rect rect(8, _vm->getDisplayInfo().converseTextLines * _vm->getDisplayInfo().converseTextHeight);
+ Point textPoint;
+
+ assert(_conversePanel.buttonsCount >= 6);
+
+ bulletForegnd = kITEColorGreen;
+ bulletBackgnd = kITEColorBlack;
+
+ rect.moveTo(_conversePanel.x + _conversePanel.buttons[0].xOffset,
+ _conversePanel.y + _conversePanel.buttons[0].yOffset);
+
+ ds->drawRect(rect, kITEColorDarkGrey); //fill bullet place
+
+ for (int i = 0; i < _vm->getDisplayInfo().converseTextLines; i++) {
+ relPos = _converseStartPos + i;
+
+ if (_converseTextCount <= relPos) {
+ break;
+ }
+
+ if (_conversePos >= 0 && _converseText[_conversePos].stringNum == _converseText[relPos].stringNum) {
+ foregnd = kITEColorBrightWhite;
+ backgnd = (!_vm->leftMouseButtonPressed()) ? kITEColorDarkGrey : kITEColorGrey;
+ } else {
+ foregnd = kITEColorBlue;
+ backgnd = kITEColorDarkGrey;
+ }
+
+ _conversePanel.calcPanelButtonRect(&_conversePanel.buttons[i], rect);
+ rect.left += 8;
+ ds->drawRect(rect, backgnd);
+
+ str = _converseText[relPos].text;
+
+ if (_converseText[relPos].textNum == 0) { // first entry
+ textPoint.x = rect.left - 6;
+ textPoint.y = rect.top;
+
+ _vm->_font->textDraw(kKnownFontSmall, ds, bullet, textPoint, bulletForegnd, bulletBackgnd, (FontEffectFlags)(kFontShadow | kFontDontmap));
+ }
+ textPoint.x = rect.left + 1;
+ textPoint.y = rect.top;
+ _vm->_font->textDraw(kKnownFontSmall, ds, str, textPoint, foregnd, kITEColorBlack, kFontShadow);
+ }
+
+ if (_converseStartPos != 0) {
+ drawPanelButtonArrow(ds, &_conversePanel, _converseUpButton);
+ }
+
+ if (_converseStartPos != _converseEndPos) {
+ drawPanelButtonArrow(ds, &_conversePanel, _converseDownButton);
+ }
+}
+
+void Interface::converseChangePos(int chg) {
+ if ((chg < 0 && _converseStartPos + chg >= 0) ||
+ (chg > 0 && _converseStartPos < _converseEndPos)) {
+ _converseStartPos += chg;
+ draw();
+ }
+}
+
+void Interface::converseSetPos(int key) {
+ Converse *ct;
+ int selection = key - '1';
+
+ if (selection >= _converseTextCount)
+ return;
+
+ converseSetTextLines(selection);
+
+ ct = &_converseText[_conversePos];
+
+ _vm->_script->finishDialog(ct->replyId, ct->replyFlags, ct->replyBit);
+
+ if (_vm->_puzzle->isActive())
+ _vm->_puzzle->handleReply(ct->replyId);
+
+ _conversePos = -1;
+}
+
+
+void Interface::handleConverseUpdate(const Point& mousePoint) {
+ bool changed;
+
+ PanelButton *last = _conversePanel.currentButton;
+
+ if (!_vm->mouseButtonPressed()) { // remove pressed flag
+ if (_converseUpButton) {
+ _converseUpButton->state = 0;
+ _converseDownButton->state = 0;
+ }
+ }
+
+ _conversePanel.currentButton = converseHitTest(mousePoint);
+ changed = last != _conversePanel.currentButton;
+
+
+ if (_conversePanel.currentButton == NULL) {
+ _conversePos = -1;
+ if (changed) {
+ draw();
+ }
+ return;
+ }
+
+ if (_conversePanel.currentButton->type == kPanelButtonConverseText) {
+ converseSetTextLines(_conversePanel.currentButton->id);
+ }
+
+ if (_conversePanel.currentButton->type == kPanelButtonArrow) {
+ if (_conversePanel.currentButton->state == 1) {
+ //TODO: insert timeout catchup
+ converseChangePos(_conversePanel.currentButton->id);
+ }
+ draw();
+ }
+}
+
+
+void Interface::handleConverseClick(const Point& mousePoint) {
+ _conversePanel.currentButton = converseHitTest(mousePoint);
+
+ if (_conversePanel.currentButton == NULL) {
+ return;
+ }
+
+ if (_conversePanel.currentButton->type == kPanelButtonConverseText) {
+ converseSetPos(_conversePanel.currentButton->ascii);
+ }
+
+ if (_conversePanel.currentButton->type == kPanelButtonArrow) {
+ _conversePanel.currentButton->state = 1;
+ converseChangePos(_conversePanel.currentButton->id);
+ }
+
+}
+
+void Interface::saveState(Common::OutSaveFile *out) {
+ out->writeUint16LE(_inventoryCount);
+
+ for (int i = 0; i < _inventoryCount; i++) {
+ out->writeUint16LE(_inventory[i]);
+ }
+}
+
+void Interface::loadState(Common::InSaveFile *in) {
+ _inventoryCount = in->readUint16LE();
+
+ for (int i = 0; i < _inventoryCount; i++) {
+ _inventory[i] = in->readUint16LE();
+ }
+
+ updateInventory(0);
+}
+
+void Interface::mapPanelShow() {
+ int i;
+ byte *resource;
+ size_t resourceLength, imageLength;
+ Surface *backBuffer;
+ Rect rect;
+ byte *image;
+ int imageWidth, imageHeight;
+ const byte *pal;
+ PalEntry cPal[PAL_ENTRIES];
+
+ _vm->_gfx->showCursor(false);
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ rect.left = rect.top = 0;
+
+ _vm->_resource->loadResource(_interfaceContext,
+ _vm->_resource->convertResourceId(RID_ITE_TYCHO_MAP), resource, resourceLength);
+ if (resourceLength == 0) {
+ error("Interface::mapPanelShow() unable to load Tycho map resource");
+ }
+
+ _vm->_gfx->getCurrentPal(_mapSavedPal);
+
+ for (i = 0; i < 6 ; i++) {
+ _vm->_gfx->palToBlack(_mapSavedPal, 0.2 * i);
+ _vm->_render->drawScene();
+ _vm->_system->delayMillis(5);
+ }
+
+ _vm->_render->setFlag(RF_MAP);
+
+ _vm->decodeBGImage(resource, resourceLength, &image, &imageLength, &imageWidth, &imageHeight);
+ pal = _vm->getImagePal(resource, resourceLength);
+
+ for (i = 0; i < PAL_ENTRIES; i++) {
+ cPal[i].red = *pal++;
+ cPal[i].green = *pal++;
+ cPal[i].blue = *pal++;
+ }
+
+ rect.setWidth(imageWidth);
+ rect.setHeight(imageHeight);
+
+ backBuffer->blit(rect, image);
+
+ // Evil Evil
+ for (i = 0; i < 6 ; i++) {
+ _vm->_gfx->blackToPal(cPal, 0.2 * i);
+ _vm->_render->drawScene();
+ _vm->_system->delayMillis(5);
+ }
+
+ free(resource);
+ free(image);
+
+ setSaveReminderState(false);
+
+ _mapPanelCrossHairState = true;
+}
+
+void Interface::mapPanelClean() {
+ PalEntry pal[PAL_ENTRIES];
+ int i;
+
+ _vm->_gfx->getCurrentPal(pal);
+
+ for (i = 0; i < 6 ; i++) {
+ _vm->_gfx->palToBlack(pal, 0.2 * i);
+ _vm->_render->drawScene();
+ _vm->_system->delayMillis(5);
+ }
+
+ _vm->_render->clearFlag(RF_MAP);
+ setMode(kPanelMain);
+
+ _vm->_gfx->showCursor(true);
+ _vm->_render->drawScene();
+
+ for (i = 0; i < 6 ; i++) {
+ _vm->_gfx->blackToPal(_mapSavedPal, 0.2 * i);
+ _vm->_render->drawScene();
+ _vm->_system->delayMillis(5);
+ }
+}
+
+void Interface::mapPanelDrawCrossHair() {
+ Surface *backBuffer;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+ _mapPanelCrossHairState = !_mapPanelCrossHairState;
+
+ Point mapPosition = _vm->_isoMap->getMapPosition();
+ Rect screen(_vm->getDisplayWidth(), _vm->_scene->getHeight());
+
+ if (screen.contains(mapPosition)) {
+ _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _vm->_sprite->_mainSprites,
+ _mapPanelCrossHairState? RID_ITE_SPR_XHAIR1 : RID_ITE_SPR_XHAIR2,
+ mapPosition, 256);
+ }
+}
+
+void Interface::keyBoss() {
+ if (_vm->getGameType() != GType_IHNM)
+ return;
+
+ if (_bossMode != -1 || _fadeMode != kNoFade)
+ return;
+
+ _vm->_sound->pauseVoice();
+ _vm->_sound->pauseSound();
+ _vm->_music->pause();
+
+ int i;
+ byte *resource;
+ size_t resourceLength, imageLength;
+ Surface *backBuffer;
+ Rect rect;
+ byte *image;
+ int imageWidth, imageHeight;
+ const byte *pal;
+ PalEntry cPal[PAL_ENTRIES];
+
+ _vm->_gfx->showCursor(false);
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ rect.left = rect.top = 0;
+
+ _vm->_resource->loadResource(_interfaceContext, RID_IHNM_BOSS_SCREEN, resource, resourceLength);
+ if (resourceLength == 0) {
+ error("Interface::bossKey() unable to load Boss image resource");
+ }
+
+ _bossMode = _panelMode;
+ setMode(kPanelBoss);
+
+ _vm->decodeBGImage(resource, resourceLength, &image, &imageLength, &imageWidth, &imageHeight);
+ rect.setWidth(imageWidth);
+ rect.setHeight(imageHeight);
+
+ _vm->_gfx->getCurrentPal(_mapSavedPal);
+ pal = _vm->getImagePal(resource, resourceLength);
+
+ for (i = 0; i < PAL_ENTRIES; i++) {
+ cPal[i].red = *pal++;
+ cPal[i].green = *pal++;
+ cPal[i].blue = *pal++;
+ }
+
+ backBuffer->blit(rect, image);
+
+ _vm->_gfx->setPalette(cPal);
+
+ free(resource);
+ free(image);
+}
+
+
+void Interface::keyBossExit() {
+ PalEntry pal[PAL_ENTRIES];
+
+ _vm->_sound->resumeVoice();
+ _vm->_sound->resumeSound();
+ _vm->_music->resume();
+
+ _vm->_gfx->getCurrentPal(pal);
+
+ _vm->_gfx->palToBlack(pal, 1);
+ setMode(_bossMode);
+
+ _vm->_render->drawScene();
+
+ _vm->_gfx->blackToPal(_mapSavedPal, 1);
+
+ _vm->_gfx->showCursor(true);
+
+ _bossMode = -1;
+}
+
+
+} // End of namespace Saga
diff --git a/engines/saga/interface.h b/engines/saga/interface.h
new file mode 100644
index 0000000000..bee3fd4a2a
--- /dev/null
+++ b/engines/saga/interface.h
@@ -0,0 +1,466 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Game interface module private header file
+
+#ifndef SAGA_INTERFACE_H__
+#define SAGA_INTERFACE_H__
+
+#include "common/savefile.h"
+
+#include "saga/sprite.h"
+#include "saga/script.h"
+
+namespace Saga {
+
+enum InterfaceUpdateFlags {
+ UPDATE_MOUSEMOVE = 1,
+ UPDATE_LEFTBUTTONCLICK = 2,
+ UPDATE_RIGHTBUTTONCLICK = 4,
+ UPDATE_MOUSECLICK = UPDATE_LEFTBUTTONCLICK | UPDATE_RIGHTBUTTONCLICK,
+ UPDATE_WHEELUP = 8,
+ UPDATE_WHEELDOWN = 16
+};
+
+#define CONVERSE_MAX_TEXTS 64
+#define CONVERSE_MAX_WORK_STRING 128
+
+#define ITE_INVENTORY_SIZE 24
+
+#define VERB_STRLIMIT 32
+
+#define STATUS_TEXT_LEN 128
+#define STATUS_TEXT_INPUT_MAX 256
+
+// Converse-specific stuff
+
+enum PanelModes {
+ kPanelNull,
+ kPanelMain,
+ kPanelOption,
+ kPanelSave, //ex- kPanelTextBox,
+ kPanelQuit,
+ kPanelError,
+ kPanelLoad,
+ kPanelConverse,
+ kPanelProtect,
+ kPanelPlacard,
+ kPanelMap,
+ kPanelSceneSubstitute,
+ kPanelChapterSelection,
+ kPanelCutaway,
+ kPanelVideo,
+ kPanelBoss
+// kPanelInventory
+};
+
+enum FadeModes {
+ kNoFade = 0,
+ kFadeIn,
+ kFadeOut
+};
+
+struct InterfacePanel {
+ int x;
+ int y;
+ byte *image;
+ size_t imageLength;
+ int imageWidth;
+ int imageHeight;
+
+ PanelButton *currentButton;
+ int buttonsCount;
+ PanelButton *buttons;
+ SpriteList sprites;
+
+ InterfacePanel() {
+ x = y = 0;
+ image = NULL;
+ imageLength = 0;
+ imageWidth = imageHeight = 0;
+ currentButton = NULL;
+ buttonsCount = 0;
+ buttons = NULL;
+ }
+
+ PanelButton *getButton(int index) {
+ if ((index >= 0) && (index < buttonsCount)) {
+ return &buttons[index];
+ }
+ return NULL;
+ }
+
+ void getRect(Rect &rect) {
+ rect.left = x;
+ rect.top = y;
+ rect.setWidth(imageWidth);
+ rect.setHeight(imageHeight);
+ }
+
+ void calcPanelButtonRect(const PanelButton* panelButton, Rect &rect) {
+ rect.left = x + panelButton->xOffset;
+ rect.right = rect.left + panelButton->width;
+ rect.top = y + panelButton->yOffset;
+ rect.bottom = rect.top + panelButton->height;
+ }
+
+ PanelButton *hitTest(const Point& mousePoint, int buttonType) {
+ PanelButton *panelButton;
+ Rect rect;
+ int i;
+ for (i = 0; i < buttonsCount; i++) {
+ panelButton = &buttons[i];
+ if (panelButton != NULL) {
+ if ((panelButton->type & buttonType) > 0) {
+ calcPanelButtonRect(panelButton, rect);
+ if (rect.contains(mousePoint)) {
+ return panelButton;
+ }
+ }
+ }
+ }
+ return NULL;
+ }
+
+ void zeroAllButtonState() {
+ int i;
+ for (i = 0; i < buttonsCount; i++) {
+ buttons[i].state = 0;
+ }
+ }
+
+
+};
+
+struct Converse {
+ char *text;
+ int stringNum;
+ int textNum;
+ int replyId;
+ int replyFlags;
+ int replyBit;
+};
+
+
+enum StatusTextInputState {
+ kStatusTextInputFirstRun,
+ kStatusTextInputEntered,
+ kStatusTextInputAborted
+};
+
+class Interface {
+public:
+ Interface(SagaEngine *vm);
+ ~Interface(void);
+
+ int activate();
+ int deactivate();
+ void setSaveReminderState(int state) {
+ _saveReminderState = state;
+ draw();
+ }
+ int getSaveReminderState() {
+ return _saveReminderState;
+ }
+ bool isActive() { return _active; }
+ void setMode(int mode);
+ int getMode(void) const { return _panelMode; }
+ void setFadeMode(int fadeMode) {
+ _fadeMode = fadeMode;
+ draw();
+ }
+ int getFadeMode() const {
+ return _fadeMode;
+ }
+ void rememberMode();
+ void restoreMode();
+ bool isInMainMode() { return _inMainMode; }
+ void setStatusText(const char *text, int statusColor = -1);
+ void loadScenePortraits(int resourceId);
+ void setLeftPortrait(int portrait) {
+ _leftPortrait = portrait;
+ draw();
+ }
+ void setRightPortrait(int portrait) {
+ _rightPortrait = portrait;
+ draw();
+ }
+ void setPortraitBgColor(int red, int green, int blue) {
+ _portraitBgColor.red = red;
+ _portraitBgColor.green = green;
+ _portraitBgColor.blue = blue;
+ }
+
+ void draw();
+ void drawOption();
+ void drawQuit();
+ void drawLoad();
+ void drawSave();
+ void drawProtect();
+ void update(const Point& mousePoint, int updateFlag);
+ void drawStatusBar();
+ void setVerbState(int verb, int state);
+
+ bool processAscii(uint16 ascii, bool synthetic = false);
+ void processKeyUp(uint16 ascii);
+
+ void keyBoss();
+ void keyBossExit();
+
+ void disableAbortSpeeches(bool d) { _disableAbortSpeeches = d; }
+
+ bool _textInput;
+
+ bool _statusTextInput;
+ StatusTextInputState _statusTextInputState;
+ char _statusTextInputString[STATUS_TEXT_INPUT_MAX];
+ void enterStatusString() {
+ _statusTextInput = true;
+ _statusTextInputPos = 0;
+ _statusTextInputString[0] = 0;
+ setStatusText(_statusTextInputString);
+ }
+
+private:
+ static void textInputRepeatCallback(void *refCon);
+
+ void drawInventory(Surface *backBuffer);
+ void updateInventory(int pos);
+ void inventoryChangePos(int chg);
+ void inventorySetPos(int key);
+
+public:
+ void refreshInventory() {
+ updateInventory(_inventoryCount);
+ draw();
+ }
+ void addToInventory(int objectId);
+ void removeFromInventory(int objectId);
+ void clearInventory();
+ int inventoryItemPosition(int objectId);
+ int getInventoryContentByPanelButton(PanelButton * panelButton) {
+ int cell = _inventoryStart + panelButton->id;
+ if (cell >= _inventoryCount) {
+ return 0;
+ }
+ return _inventory[cell];
+ }
+
+ PanelButton *inventoryHitTest(const Point& mousePoint) {
+ return _mainPanel.hitTest(mousePoint, kPanelButtonInventory);
+ }
+ PanelButton *verbHitTest(const Point& mousePoint){
+ return _mainPanel.hitTest(mousePoint, kPanelButtonVerb);
+ }
+ void saveState(Common::OutSaveFile *out);
+ void loadState(Common::InSaveFile *in);
+
+ void mapPanelDrawCrossHair();
+
+ int32 getProtectHash() { return _protectHash; }
+
+private:
+ void handleMainUpdate(const Point& mousePoint); // main panel update
+ void handleMainClick(const Point& mousePoint); // main panel click
+
+ PanelButton *converseHitTest(const Point& mousePoint) {
+ return _conversePanel.hitTest(mousePoint, kPanelAllButtons);
+ }
+ void handleConverseUpdate(const Point& mousePoint); // converse panel update
+ void handleConverseClick(const Point& mousePoint); // converse panel click
+
+ PanelButton *optionHitTest(const Point& mousePoint) {
+ return _optionPanel.hitTest(mousePoint, kPanelButtonOptionSaveFiles | kPanelButtonOption | kPanelButtonOptionSlider);
+ }
+ void handleOptionUpdate(const Point& mousePoint); // option panel update
+ void handleOptionClick(const Point& mousePoint); // option panel click
+
+ PanelButton *quitHitTest(const Point& mousePoint) {
+ return _quitPanel.hitTest(mousePoint, kPanelAllButtons);
+ }
+ void handleQuitUpdate(const Point& mousePoint); // quit panel update
+ void handleQuitClick(const Point& mousePoint); // quit panel click
+
+ PanelButton *loadHitTest(const Point& mousePoint) {
+ return _loadPanel.hitTest(mousePoint, kPanelAllButtons);
+ }
+ void handleLoadUpdate(const Point& mousePoint); // load panel update
+ void handleLoadClick(const Point& mousePoint); // load panel click
+
+ PanelButton *saveHitTest(const Point& mousePoint) {
+ return _savePanel.hitTest(mousePoint, kPanelAllButtons);
+ }
+ void handleSaveUpdate(const Point& mousePoint); // save panel update
+ void handleSaveClick(const Point& mousePoint); // save panel click
+
+ void handleChapterSelectionUpdate(const Point& mousePoint);
+ void handleChapterSelectionClick(const Point& mousePoint);
+
+ void mapPanelShow();
+ void mapPanelClean();
+
+ void lockMode() { _lockedMode = _panelMode; }
+ void unlockMode() { _panelMode = _lockedMode; }
+
+ void setOption(PanelButton *panelButton);
+ void setQuit(PanelButton *panelButton);
+ void setLoad(PanelButton *panelButton);
+ void setSave(PanelButton *panelButton);
+
+ void drawTextInput(Surface *ds, InterfacePanel *panel, PanelButton *panelButton);
+ void drawPanelText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton);
+ void drawPanelButtonText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton);
+ enum ButtonKind {
+ kButton,
+ kSlider,
+ kEdit
+ };
+ void drawButtonBox(Surface *ds, const Rect &rect, ButtonKind kind, bool down);
+ void drawPanelButtonArrow(Surface *ds, InterfacePanel *panel, PanelButton *panelButton);
+ void drawVerbPanelText(Surface *ds, PanelButton *panelButton, KnownColor textKnownColor, KnownColor textShadowKnownColor);
+ void drawVerbPanel(Surface *backBuffer, PanelButton* panelButton);
+ void calcOptionSaveSlider();
+ bool processTextInput(uint16 ascii);
+ void processStatusTextInput(uint16 ascii);
+ void textInputStartRepeat(uint16 ascii);
+ void textInputRepeat(void);
+
+public:
+ void converseInit(void);
+ void converseClear(void);
+ bool converseAddText(const char *text, int replyId, byte replyFlags, int replyBit);
+ void converseDisplayText();
+ void converseSetTextLines(int row);
+ void converseChangePos(int chg);
+ void converseSetPos(int key);
+
+private:
+ void converseDisplayTextLines(Surface *ds);
+ PanelButton *getPanelButtonByVerbType(int verb) {
+ if ((verb < 0) || (verb >= kVerbTypeIdsMax)) {
+ error("Interface::getPanelButtonByVerbType wrong verb");
+ }
+ return _verbTypeToPanelButton[verb];
+ }
+
+ void validateOptionButtons() {
+ if (!_vm->isSaveListFull() && (_optionSaveFileTitleNumber == 0) && (_optionPanel.currentButton != NULL)) {
+ if (_optionPanel.currentButton->id == kTextLoad) {
+ _optionPanel.currentButton = NULL;
+ }
+ }
+ }
+ void validateSaveButtons() {
+ if ((_textInputStringLength == 0) && (_savePanel.currentButton != NULL)) {
+ if (_savePanel.currentButton->id == kTextSave) {
+ _savePanel.currentButton = NULL;
+ }
+ }
+ }
+
+public:
+ SpriteList _defPortraits;
+
+private:
+ SagaEngine *_vm;
+
+ ResourceContext *_interfaceContext;
+ InterfacePanel _mainPanel;
+ PanelButton *_inventoryUpButton;
+ PanelButton *_inventoryDownButton;
+ InterfacePanel _conversePanel;
+ PanelButton *_converseUpButton;
+ PanelButton *_converseDownButton;
+ SpriteList _scenePortraits;
+ PanelButton *_verbTypeToPanelButton[kVerbTypeIdsMax];
+ InterfacePanel _optionPanel;
+ PanelButton * _optionSaveFileSlider;
+ PanelButton * _optionSaveFilePanel;
+ InterfacePanel _quitPanel;
+ InterfacePanel _loadPanel;
+ InterfacePanel _savePanel;
+ PanelButton * _saveEdit;
+ InterfacePanel _protectPanel;
+ PanelButton * _protectEdit;
+
+ bool _disableAbortSpeeches;
+
+ int _saveReminderState;
+ bool _active;
+ int _fadeMode;
+ int _panelMode;
+ int _savedMode;
+ int _lockedMode;
+ int _bossMode;
+ bool _inMainMode;
+ char _statusText[STATUS_TEXT_LEN];
+ int _statusOnceColor;
+ int _leftPortrait;
+ int _rightPortrait;
+ PalEntry _portraitBgColor;
+
+ Point _lastMousePoint;
+
+ uint16 *_inventory;
+ int _inventorySize;
+ int _inventoryStart;
+ int _inventoryEnd;
+ int _inventoryPos;
+ int _inventoryBox;
+ int _inventoryCount;
+
+ char _converseWorkString[CONVERSE_MAX_WORK_STRING];
+ Converse _converseText[CONVERSE_MAX_TEXTS];
+ int _converseTextCount;
+ int _converseStrCount;
+ int _converseStartPos;
+ int _converseEndPos;
+ int _conversePos;
+
+ uint _optionSaveFileTop;
+ uint _optionSaveFileTitleNumber;
+ int16 _optionSaveFileMouseOff;
+ Rect _optionSaveRectTop;
+ Rect _optionSaveRectSlider;
+ Rect _optionSaveRectBottom;
+
+ char _textInputString[SAVE_TITLE_SIZE];
+ uint _textInputStringLength;
+ uint _textInputPos;
+ uint _textInputMaxWidth;
+
+ uint _statusTextInputPos;
+
+ int _textInputRepeatPhase;
+ uint16 _textInputRepeatChar;
+
+ PalEntry _mapSavedPal[PAL_ENTRIES];
+ bool _mapPanelCrossHairState;
+
+ int32 _protectHash;
+};
+
+} // End of namespace Saga
+
+#endif /* INTERFACE_H__ */
diff --git a/engines/saga/isomap.cpp b/engines/saga/isomap.cpp
new file mode 100644
index 0000000000..6acbf8ba70
--- /dev/null
+++ b/engines/saga/isomap.cpp
@@ -0,0 +1,1694 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Isometric level module
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+
+#include "saga/resnames.h"
+#include "saga/scene.h"
+#include "saga/isomap.h"
+#include "saga/stream.h"
+
+namespace Saga {
+
+enum MaskRules {
+ kMaskRuleNever = 0,
+ kMaskRuleAlways,
+ kMaskRuleUMIN,
+ kMaskRuleUMID,
+ kMaskRuleUMAX,
+ kMaskRuleVMIN,
+ kMaskRuleVMID,
+ kMaskRuleVMAX,
+ kMaskRuleYMIN,
+ kMaskRuleYMID,
+ kMaskRuleYMAX,
+ kMaskRuleUVMAX,
+ kMaskRuleUVMIN,
+ kMaskRuleUorV,
+ kMaskRuleUandV
+};
+
+
+static const IsoMap::TilePoint normalDirTable[8] = {
+ { 1, 1, 0, SAGA_DIAG_NORMAL_COST},
+ { 1, 0, 0, SAGA_STRAIGHT_NORMAL_COST},
+ { 1,-1, 0, SAGA_DIAG_NORMAL_COST},
+ { 0,-1, 0, SAGA_STRAIGHT_NORMAL_COST},
+ {-1,-1, 0, SAGA_DIAG_NORMAL_COST},
+ {-1, 0, 0, SAGA_STRAIGHT_NORMAL_COST},
+ {-1, 1, 0, SAGA_DIAG_NORMAL_COST},
+ { 0, 1, 0, SAGA_STRAIGHT_NORMAL_COST},
+};
+
+static const IsoMap::TilePoint easyDirTable[8] = {
+ { 1, 1, 0, SAGA_DIAG_EASY_COST},
+ { 1, 0, 0, SAGA_STRAIGHT_EASY_COST},
+ { 1,-1, 0, SAGA_DIAG_EASY_COST},
+ { 0,-1, 0, SAGA_STRAIGHT_EASY_COST},
+ {-1,-1, 0, SAGA_DIAG_EASY_COST},
+ {-1, 0, 0, SAGA_STRAIGHT_EASY_COST},
+ {-1, 1, 0, SAGA_DIAG_EASY_COST},
+ { 0, 1, 0, SAGA_STRAIGHT_EASY_COST},
+};
+
+static const IsoMap::TilePoint hardDirTable[8] = {
+ { 1, 1, 0, SAGA_DIAG_HARD_COST},
+ { 1, 0, 0, SAGA_STRAIGHT_HARD_COST},
+ { 1,-1, 0, SAGA_DIAG_HARD_COST},
+ { 0,-1, 0, SAGA_STRAIGHT_HARD_COST},
+ {-1,-1, 0, SAGA_DIAG_HARD_COST},
+ {-1, 0, 0, SAGA_STRAIGHT_HARD_COST},
+ {-1, 1, 0, SAGA_DIAG_HARD_COST},
+ { 0, 1, 0, SAGA_STRAIGHT_HARD_COST},
+};
+
+IsoMap::IsoMap(SagaEngine *vm) : _vm(vm) {
+ _tileData = NULL;
+ _tilesCount = 0;
+ _tilePlatformList = NULL;
+ _tilePlatformsCount = 0;
+ _metaTileList = NULL;
+ _metaTilesCount = 0;
+ _multiTable = NULL;
+ _multiCount = 0;
+ _multiTableData = NULL;
+ _multiDataCount = 0;
+ _viewScroll.x = (128 - 8) * 16;
+ _viewScroll.x = (128 - 8) * 16 - 64;
+ _viewDiff = 1;
+
+}
+
+void IsoMap::loadImages(const byte *resourcePointer, size_t resourceLength) {
+ IsoTileData *tileData;
+ uint16 i;
+
+ if (resourceLength == 0) {
+ error("IsoMap::loadImages wrong resourceLength");
+ }
+
+ _tileData = (byte*)malloc(resourceLength);
+ _tileDataLength = resourceLength;
+ memcpy(_tileData, resourcePointer, resourceLength);
+
+ MemoryReadStreamEndian readS(_tileData, _tileDataLength, _vm->isBigEndian());
+ readS.readUint16(); // skip
+ _tilesCount = readS.readUint16();
+ _tilesCount = _tilesCount / SAGA_ISOTILEDATA_LEN;
+
+ readS.seek(0);
+
+ _tilesTable = (IsoTileData *)malloc(_tilesCount * sizeof(*_tilesTable));
+ if (_tilesTable == NULL) {
+ memoryError("IsoMap::loadImages");
+ }
+
+ for (i = 0; i < _tilesCount; i++) {
+ tileData = &_tilesTable[i];
+ tileData->height = readS.readByte();
+ tileData->attributes = readS.readSByte();
+ tileData->offset = readS.readUint16();
+ tileData->terrainMask = readS.readUint16();
+ tileData->FGDBGDAttr = readS.readByte();
+ readS.readByte(); //skip
+ }
+
+}
+
+void IsoMap::loadPlatforms(const byte * resourcePointer, size_t resourceLength) {
+ TilePlatformData *tilePlatformData;
+ uint16 i, x, y;
+
+ if (resourceLength == 0) {
+ error("IsoMap::loadPlatforms wrong resourceLength");
+ }
+
+ MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian());
+
+ _tilePlatformsCount = resourceLength / SAGA_TILEPLATFORMDATA_LEN;
+ _tilePlatformList = (TilePlatformData *)malloc(_tilePlatformsCount * sizeof(*_tilePlatformList));
+ if (_tilePlatformList == NULL) {
+ memoryError("IsoMap::loadPlatforms");
+ }
+
+ for (i = 0; i < _tilePlatformsCount; i++) {
+ tilePlatformData = &_tilePlatformList[i];
+ tilePlatformData->metaTile = readS.readSint16();
+ tilePlatformData->height = readS.readSint16();
+ tilePlatformData->highestPixel = readS.readSint16();
+ tilePlatformData->vBits = readS.readByte();
+ tilePlatformData->uBits = readS.readByte();
+ for (x = 0; x < SAGA_PLATFORM_W; x++) {
+ for (y = 0; y < SAGA_PLATFORM_W; y++) {
+ tilePlatformData->tiles[x][y] = readS.readSint16();
+ }
+ }
+ }
+
+}
+
+void IsoMap::loadMap(const byte * resourcePointer, size_t resourceLength) {
+ uint16 x, y;
+
+ if (resourceLength != SAGA_TILEMAP_LEN) {
+ error("IsoMap::loadMap wrong resourceLength");
+ }
+
+ MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian());
+ _tileMap.edgeType = readS.readByte();
+ readS.readByte(); //skip
+
+ for (x = 0; x < SAGA_TILEMAP_W; x++) {
+ for (y = 0; y < SAGA_TILEMAP_H; y++) {
+ _tileMap.tilePlatforms[x][y] = readS.readSint16();
+ }
+ }
+
+}
+
+void IsoMap::loadMetaTiles(const byte * resourcePointer, size_t resourceLength) {
+ MetaTileData *metaTileData;
+ uint16 i, j;
+
+ if (resourceLength == 0) {
+ error("IsoMap::loadMetaTiles wrong resourceLength");
+ }
+
+ MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian());
+ _metaTilesCount = resourceLength / SAGA_METATILEDATA_LEN;
+
+ _metaTileList = (MetaTileData *)malloc(_metaTilesCount * sizeof(*_metaTileList));
+ if (_metaTileList == NULL) {
+ memoryError("IsoMap::loadMetaTiles");
+ }
+
+ for (i = 0; i < _metaTilesCount; i++) {
+ metaTileData = &_metaTileList[i];
+ metaTileData->highestPlatform = readS.readUint16();
+ metaTileData->highestPixel = readS.readUint16();
+ for (j = 0; j < SAGA_MAX_PLATFORM_H; j++) {
+ metaTileData->stack[j] = readS.readSint16();
+ }
+ }
+}
+
+void IsoMap::loadMulti(const byte * resourcePointer, size_t resourceLength) {
+ MultiTileEntryData *multiTileEntryData;
+ uint16 i;
+ int16 offsetDiff;
+
+ if (resourceLength < 2) {
+ error("IsoMap::loadMetaTiles wrong resourceLength");
+ }
+
+ MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian());
+ _multiCount = readS.readUint16();
+ _multiTable = (MultiTileEntryData *)malloc(_multiCount * sizeof(*_multiTable));
+ if (_multiTable == NULL) {
+ memoryError("IsoMap::loadMulti");
+ }
+
+ for (i = 0; i < _multiCount; i++) {
+ multiTileEntryData = &_multiTable[i];
+ readS.readUint32();//skip
+ multiTileEntryData->offset = readS.readSint16();
+ multiTileEntryData->u = readS.readByte();
+ multiTileEntryData->v = readS.readByte();
+ multiTileEntryData->h = readS.readByte();
+ multiTileEntryData->uSize = readS.readByte();
+ multiTileEntryData->vSize = readS.readByte();
+ multiTileEntryData->numStates = readS.readByte();
+ multiTileEntryData->currentState = readS.readByte();
+ readS.readByte();//skip
+ }
+
+ offsetDiff = (readS.pos() - 2);
+
+ for (i = 0; i < _multiCount; i++) {
+ _multiTable[i].offset -= offsetDiff;
+ }
+
+ _multiDataCount = (readS.size() - readS.pos()) / 2;
+
+ _multiTableData = (int16 *)malloc(_multiDataCount * sizeof(*_multiTableData));
+ for (i = 0; i < _multiDataCount; i++) {
+ _multiTableData[i] = readS.readSint16();
+ }
+}
+
+void IsoMap::freeMem() {
+ free(_tileData);
+ _tileData = NULL;
+ _tilesCount = 0;
+ free(_tilePlatformList);
+ _tilePlatformList = NULL;
+ _tilePlatformsCount = 0;
+ free(_metaTileList);
+ _metaTileList = NULL;
+ _metaTilesCount = 0;
+ free(_multiTable);
+ _multiTable = NULL;
+ _multiCount = 0;
+ free(_multiTableData);
+ _multiTableData = NULL;
+ _multiDataCount = 0;
+}
+
+void IsoMap::adjustScroll(bool jump) {
+ Point playerPoint;
+ Point minScrollPos;
+ Point maxScrollPos;
+
+
+ tileCoordsToScreenPoint(_vm->_actor->_centerActor->_location, playerPoint);
+
+ if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) {
+ _mapPosition.x = (playerPoint.x + _viewScroll.x) * 30 / 100 - (381);
+ _mapPosition.y = (playerPoint.y + _viewScroll.y) * 30 / 100 - (342);
+ }
+
+ if (_vm->_actor->_centerActor != _vm->_actor->_protagonist) {
+ playerPoint.y -= 24;
+ }
+ playerPoint.y -= 28;
+
+ playerPoint.x += _viewScroll.x - _vm->getDisplayWidth()/2;
+ playerPoint.y += _viewScroll.y - _vm->_scene->getHeight()/2;
+
+ minScrollPos.x = playerPoint.x - SAGA_SCROLL_LIMIT_X1;
+ minScrollPos.y = playerPoint.y - SAGA_SCROLL_LIMIT_Y1;
+
+ maxScrollPos.x = playerPoint.x + SAGA_SCROLL_LIMIT_X1;
+ maxScrollPos.y = playerPoint.y + SAGA_SCROLL_LIMIT_Y2;
+
+ if (jump) {
+ if (_viewScroll.y < minScrollPos.y) {
+ _viewScroll.y = minScrollPos.y;
+ }
+ if (_viewScroll.y > maxScrollPos.y) {
+ _viewScroll.y = maxScrollPos.y;
+ }
+ if (_viewScroll.x < minScrollPos.x) {
+ _viewScroll.x = minScrollPos.x;
+ }
+ if (_viewScroll.x > maxScrollPos.x) {
+ _viewScroll.x = maxScrollPos.x;
+ }
+ } else {
+ _viewScroll.y = smoothSlide( _viewScroll.y, minScrollPos.y, maxScrollPos.y );
+ _viewScroll.x = smoothSlide( _viewScroll.x, minScrollPos.x, maxScrollPos.x );
+ }
+
+ if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) {
+ ObjectData *obj;
+ uint16 objectId;
+ objectId = _vm->_actor->objIndexToId(ITE_OBJ_MAP);
+ obj = _vm->_actor->getObj(objectId);
+ if (obj->_sceneNumber != ITE_SCENE_INV) {
+ _viewScroll.x = 1552 + 8;
+ _viewScroll.y = 1456 + 8;
+ }
+ }
+}
+
+int16 IsoMap::findMulti(int16 tileIndex, int16 absU, int16 absV, int16 absH) {
+ MultiTileEntryData *multiTileEntryData;
+ int16 ru;
+ int16 rv;
+ int16 mu;
+ int16 mv;
+ int16 state;
+ uint16 i, offset;
+ int16 *tiles;
+
+ ru = (tileIndex >> 13) & 0x03;
+ rv = (tileIndex >> 11) & 0x03;
+ mu = absU - ru;
+ mv = absV - rv;
+
+ tileIndex = 0;
+ for (i = 0; i < _multiCount; i++) {
+ multiTileEntryData = &_multiTable[i];
+
+ if ((multiTileEntryData->u == mu) &&
+ (multiTileEntryData->v == mv) &&
+ (multiTileEntryData->h == absH)) {
+ state = multiTileEntryData->currentState;
+
+ offset = (ru + state * multiTileEntryData->uSize) * multiTileEntryData->vSize + rv;
+ offset *= sizeof(*_multiTableData);
+ offset += multiTileEntryData->offset;
+ if (offset + sizeof(*_multiTableData) - 1 >= _multiDataCount * sizeof(*_multiTableData)) {
+ error("wrong multiTileEntryData->offset");
+ }
+ tiles = (int16*)((byte*)_multiTableData + offset);
+ tileIndex = *tiles;
+ if (tileIndex >= 256) {
+ warning("something terrible happened");
+ return 1;
+ }
+ return tileIndex;
+ }
+ }
+
+ return 1;
+}
+
+void IsoMap::draw(Surface *ds) {
+
+ _tileClip = _vm->_scene->getSceneClip();
+ ds->drawRect(_tileClip, 0);
+ drawTiles(ds, NULL);
+}
+
+void IsoMap::setMapPosition(int x, int y) {
+ _mapPosition.x = x;
+ _mapPosition.y = y;
+}
+
+void IsoMap::drawSprite(Surface *ds, SpriteList &spriteList, int spriteNumber, const Location &location, const Point &screenPosition, int scale) {
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+ const byte *spriteBuffer;
+ Point spritePointer;
+ Rect clip(_vm->_scene->getSceneClip());
+
+ _vm->_sprite->getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
+
+ spritePointer.x = screenPosition.x + xAlign;
+ spritePointer.y = screenPosition.y + yAlign;
+
+ _tileClip.left = spritePointer.x;
+ _tileClip.top = spritePointer.y;
+ _tileClip.right = spritePointer.x + width;
+ _tileClip.bottom = spritePointer.y + height;
+
+ if (_tileClip.left < 0) {
+ _tileClip.left = 0;
+ }
+ if (_tileClip.right > _vm->getDisplayWidth()) {
+ _tileClip.right = _vm->getDisplayWidth();
+ }
+ if (_tileClip.top < 0) {
+ _tileClip.top = 0;
+ }
+ if (_tileClip.bottom > _vm->_scene->getHeight()) {
+ _tileClip.bottom = _vm->_scene->getHeight();
+ }
+
+ _vm->_sprite->drawClip(ds, clip, spritePointer, width, height, spriteBuffer);
+ drawTiles(ds, &location);
+}
+
+
+void IsoMap::drawTiles(Surface *ds, const Location *location) {
+ Point view1;
+ Point fineScroll;
+ Point tileScroll;
+ Point metaTileY;
+ Point metaTileX;
+ int16 u0, v0,
+ u1, v1,
+ u2, v2,
+ uc, vc;
+ uint16 metaTileIndex;
+ Location rLocation;
+ int16 workAreaWidth;
+ int16 workAreaHeight;
+
+ tileScroll.x = _viewScroll.x >> 4;
+ tileScroll.y = _viewScroll.y >> 4;
+
+ fineScroll.x = _viewScroll.x & 0xf;
+ fineScroll.y = _viewScroll.y & 0xf;
+
+ view1.x = tileScroll.x - (8 * SAGA_TILEMAP_W);
+ view1.y = (8 * SAGA_TILEMAP_W) - tileScroll.y;
+
+ u0 = ((view1.y + 64) * 2 + view1.x) >> 4;
+ v0 = ((view1.y + 64) * 2 - view1.x) >> 4;
+
+ metaTileY.x = (u0 - v0) * 128 - (view1.x * 16 + fineScroll.x);
+ metaTileY.y = (view1.y * 16 - fineScroll.y) - (u0 + v0) * 64;
+
+ workAreaWidth = _vm->getDisplayWidth() + 128;
+ workAreaHeight = _vm->_scene->getHeight() + 128 + 80;
+
+ for (u1 = u0, v1 = v0; metaTileY.y < workAreaHeight; u1--, v1-- ) {
+ metaTileX = metaTileY;
+
+ for (u2 = u1, v2 = v1; metaTileX.x < workAreaWidth; u2++, v2--, metaTileX.x += 256) {
+
+ uc = u2 & (SAGA_TILEMAP_W - 1);
+ vc = v2 & (SAGA_TILEMAP_W - 1);
+
+ if (uc != u2 || vc != v2) {
+ metaTileIndex = 0;
+ switch ( _tileMap.edgeType) {
+ case kEdgeTypeBlack:
+ continue;
+ case kEdgeTypeFill0:
+ break;
+ case kEdgeTypeFill1:
+ metaTileIndex = 1;
+ break;
+ case kEdgeTypeRpt:
+ uc = clamp( 0, u2, SAGA_TILEMAP_W - 1);
+ vc = clamp( 0, v2, SAGA_TILEMAP_W - 1);
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ break;
+ case kEdgeTypeWrap:
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ break;
+ }
+ } else {
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ }
+
+ if (location != NULL) {
+ rLocation.u() = location->u() - (u2 << 7);
+ rLocation.v() = location->v() - (v2 << 7);
+ rLocation.z = location->z;
+ drawSpriteMetaTile(ds, metaTileIndex, metaTileX, rLocation, u2 << 3, v2 << 3);
+ } else {
+ drawMetaTile(ds, metaTileIndex, metaTileX, u2 << 3, v2 << 3);
+ }
+ }
+
+ metaTileY.y += 64;
+
+ metaTileX = metaTileY;
+
+ metaTileX.x -= 128;
+
+ for (u2 = u1 - 1, v2 = v1; metaTileX.x < workAreaWidth; u2++, v2--, metaTileX.x += 256) {
+
+ uc = u2 & (SAGA_TILEMAP_W - 1);
+ vc = v2 & (SAGA_TILEMAP_W - 1);
+
+ if (uc != u2 || vc != v2) {
+ metaTileIndex = 0;
+ switch ( _tileMap.edgeType) {
+ case kEdgeTypeBlack:
+ continue;
+ case kEdgeTypeFill0:
+ break;
+ case kEdgeTypeFill1:
+ metaTileIndex = 1;
+ break;
+ case kEdgeTypeRpt:
+ uc = clamp( 0, u2, SAGA_TILEMAP_W - 1);
+ vc = clamp( 0, v2, SAGA_TILEMAP_W - 1);
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ break;
+ case kEdgeTypeWrap:
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ break;
+ }
+ } else {
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ }
+
+ if (location != NULL) {
+ rLocation.u() = location->u() - (u2 << 7);
+ rLocation.v() = location->v() - (v2 << 7);
+ rLocation.z = location->z;
+ drawSpriteMetaTile(ds, metaTileIndex, metaTileX, rLocation, u2 << 3, v2 << 3);
+ } else {
+ drawMetaTile(ds, metaTileIndex, metaTileX, u2 << 3, v2 << 3);
+ }
+ }
+ metaTileY.y += 64;
+ }
+
+}
+
+void IsoMap::drawSpriteMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, Location &location, int16 absU, int16 absV) {
+ MetaTileData * metaTile;
+ uint16 high;
+ int16 platformIndex;
+ Point platformPoint;
+ platformPoint = point;
+
+ if (_metaTilesCount <= metaTileIndex) {
+ error("IsoMap::drawMetaTile wrong metaTileIndex");
+ }
+
+ metaTile = &_metaTileList[metaTileIndex];
+
+ if (metaTile->highestPlatform > 18) {
+ metaTile->highestPlatform = 0;
+ }
+
+ for (high = 0; high <= metaTile->highestPlatform; high++, platformPoint.y -= 8, location.z -= 8) {
+ assert(SAGA_MAX_PLATFORM_H > high);
+ platformIndex = metaTile->stack[high];
+
+ if (platformIndex >= 0) {
+ drawSpritePlatform( ds, platformIndex, platformPoint, location, absU, absV, high );
+ }
+ }
+}
+
+void IsoMap::drawMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, int16 absU, int16 absV) {
+ MetaTileData * metaTile;
+ uint16 high;
+ int16 platformIndex;
+ Point platformPoint;
+ platformPoint = point;
+
+ if (_metaTilesCount <= metaTileIndex) {
+ error("IsoMap::drawMetaTile wrong metaTileIndex");
+ }
+
+ metaTile = &_metaTileList[metaTileIndex];
+
+ if (metaTile->highestPlatform > 18) {
+ metaTile->highestPlatform = 0;
+ }
+
+ for (high = 0; high <= metaTile->highestPlatform; high++, platformPoint.y -= 8) {
+ assert(SAGA_MAX_PLATFORM_H > high);
+ platformIndex = metaTile->stack[high];
+
+ if (platformIndex >= 0) {
+ drawPlatform( ds, platformIndex, platformPoint, absU, absV, high );
+ }
+ }
+}
+
+void IsoMap::drawSpritePlatform(Surface *ds, uint16 platformIndex, const Point &point, const Location &location, int16 absU, int16 absV, int16 absH) {
+ TilePlatformData *tilePlatform;
+ int16 u, v;
+ Point s;
+ Point s0;
+ uint16 tileIndex;
+ Location copyLocation(location);
+
+ if (_tilePlatformsCount <= platformIndex) {
+ error("IsoMap::drawPlatform wrong platformIndex");
+ }
+
+ tilePlatform = &_tilePlatformList[platformIndex];
+
+ if ((point.y <= _tileClip.top) || (point.y - SAGA_MAX_TILE_H - SAGA_PLATFORM_W * SAGA_TILE_NOMINAL_H >= _tileClip.bottom)) {
+ return;
+ }
+
+ s0 = point;
+ s0.y -= (((SAGA_PLATFORM_W - 1) + (SAGA_PLATFORM_W - 1)) * 8);
+
+ for (v = SAGA_PLATFORM_W - 1,
+ copyLocation.v() = location.v() - ((SAGA_PLATFORM_W - 1) << 4);
+ v >= 0 && s0.y - SAGA_MAX_TILE_H < _tileClip.bottom && s0.x - 128 < _tileClip.right;
+ v--, copyLocation.v() += 16, s0.x += 16, s0.y += 8) {
+
+ if ((tilePlatform->vBits & (1 << v)) == 0) {
+ continue;
+ }
+
+ if (s0.x + 128 + 32 < _tileClip.left) {
+ continue;
+ }
+
+ s = s0;
+
+ for (u = SAGA_PLATFORM_W - 1,
+ copyLocation.u() = location.u() - ((SAGA_PLATFORM_W - 1) << 4);
+ u >= 0 && s.x + 32 > _tileClip.left && s.y - SAGA_MAX_TILE_H < _tileClip.bottom;
+ u--, copyLocation.u() += 16, s.x -= 16, s.y += 8 ) {
+ if (s.x < _tileClip.right && s.y > _tileClip.top) {
+
+ tileIndex = tilePlatform->tiles[u][v];
+ if (tileIndex != 0) {
+ if (tileIndex & SAGA_MULTI_TILE) {
+ tileIndex = findMulti(tileIndex, absU + u, absV + v, absH);
+ }
+
+ drawTile(ds, tileIndex, s, &copyLocation);
+ }
+ }
+ }
+ }
+}
+
+void IsoMap::drawPlatform(Surface *ds, uint16 platformIndex, const Point &point, int16 absU, int16 absV, int16 absH) {
+ TilePlatformData *tilePlatform;
+ int16 u, v;
+ Point s;
+ Point s0;
+ uint16 tileIndex;
+
+ if (_tilePlatformsCount <= platformIndex) {
+ error("IsoMap::drawPlatform wrong platformIndex");
+ }
+
+ tilePlatform = &_tilePlatformList[platformIndex];
+
+ if ((point.y <= _tileClip.top) || (point.y - SAGA_MAX_TILE_H - SAGA_PLATFORM_W * SAGA_TILE_NOMINAL_H >= _tileClip.bottom)) {
+ return;
+ }
+
+ s0 = point;
+ s0.y -= (((SAGA_PLATFORM_W - 1) + (SAGA_PLATFORM_W - 1)) * 8);
+
+ for (v = SAGA_PLATFORM_W - 1;
+ v >= 0 && s0.y - SAGA_MAX_TILE_H < _tileClip.bottom && s0.x - 128 < _tileClip.right;
+ v--, s0.x += 16, s0.y += 8) {
+
+ if ((tilePlatform->vBits & (1 << v)) == 0) {
+ continue;
+ }
+
+ if (s0.x + 128 + 32 < _tileClip.left) {
+ continue;
+ }
+
+ s = s0;
+
+ for (u = SAGA_PLATFORM_W - 1;
+ u >= 0 && s.x + 32 > _tileClip.left && s.y - SAGA_MAX_TILE_H < _tileClip.bottom;
+ u--, s.x -= 16, s.y += 8 ) {
+ if (s.x < _tileClip.right && s.y > _tileClip.top) {
+
+ tileIndex = tilePlatform->tiles[u][v];
+ if (tileIndex > 1) {
+ if (tileIndex & SAGA_MULTI_TILE) {
+ tileIndex = findMulti(tileIndex, absU + u, absV + v, absH);
+ }
+
+ drawTile(ds, tileIndex, s, NULL);
+ }
+ }
+ }
+ }
+}
+
+#define THRESH0 0
+#define THRESH8 8
+#define THRESH16 16
+
+void IsoMap::drawTile(Surface *ds, uint16 tileIndex, const Point &point, const Location *location) {
+ const byte *tilePointer;
+ const byte *readPointer;
+ byte *drawPointer;
+ Point drawPoint;
+ int height;
+ int widthCount = 0;
+ int row, col, count, lowBound;
+ int bgRunCount;
+ int fgRunCount;
+
+
+ if (tileIndex >= _tilesCount) {
+ error("IsoMap::drawTile wrong tileIndex");
+ }
+
+
+ if (point.x + SAGA_ISOTILE_WIDTH < _tileClip.left) {
+ return;
+ }
+
+ if (point.x - SAGA_ISOTILE_WIDTH >= _tileClip.right) {
+ return;
+ }
+
+ tilePointer = _tileData + _tilesTable[tileIndex].offset;
+ height = _tilesTable[tileIndex].height;
+
+ if ((height <= 8) || (height > 64)) {
+ return;
+ }
+
+ drawPoint = point;
+ drawPoint.y -= height;
+
+ if (drawPoint.y >= _tileClip.bottom) {
+ return;
+ }
+
+ if (location != NULL) {
+ if (location->z <= -16) {
+ if (location->z <= -48) {
+ if (location->u() < -THRESH8 || location->v() < -THRESH8) {
+ return;
+ }
+ } else {
+ if (location->u() < THRESH0 || location->v() < THRESH0) {
+ return;
+ }
+ }
+ } else {
+ if (location->z >= 16) {
+ return;
+ } else {
+ switch (_tilesTable[tileIndex].GetMaskRule()) {
+ case kMaskRuleNever:
+ return;
+ case kMaskRuleAlways:
+ break;
+ case kMaskRuleUMIN:
+ if (location->u() < THRESH0) {
+ return;
+ }
+ break;
+ case kMaskRuleUMID:
+ if (location->u() < THRESH8) {
+ return;
+ }
+ break;
+ case kMaskRuleUMAX:
+ if (location->u() < THRESH16) {
+ return;
+ }
+ break;
+ case kMaskRuleVMIN:
+ if (location->v() < THRESH0) {
+ return;
+ }
+ break;
+ case kMaskRuleVMID:
+ if (location->v() < THRESH8) {
+ return;
+ }
+ break;
+ case kMaskRuleVMAX:
+ if (location->v() < THRESH16) {
+ return;
+ }
+ break;
+ case kMaskRuleYMIN:
+ if (location->uv() < THRESH0 * 2) {
+ return;
+ }
+ break;
+ case kMaskRuleYMID:
+ if (location->uv() < THRESH8 * 2) {
+ return;
+ }
+ break;
+ case kMaskRuleYMAX:
+ if (location->uv() < THRESH16 * 2) {
+ return;
+ }
+ break;
+ case kMaskRuleUVMAX:
+ if (location->u() < THRESH16 && location->v() < THRESH16) {
+ return;
+ }
+ break;
+ case kMaskRuleUVMIN:
+ if (location->u() < THRESH0 || location->v() < THRESH0) {
+ return;
+ }
+ break;
+ case kMaskRuleUorV:
+ if (location->u() < THRESH8 && location->v() < THRESH8) {
+ return;
+ }
+ break;
+ case kMaskRuleUandV:
+ if (location->u() < THRESH8 || location->v() < THRESH8) {
+ return;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ readPointer = tilePointer;
+ lowBound = MIN((int)(drawPoint.y + height), (int)_tileClip.bottom);
+ for (row = drawPoint.y; row < lowBound; row++) {
+ widthCount = 0;
+ if (row >= _tileClip.top) {
+ drawPointer = (byte *)ds->pixels + drawPoint.x + (row * ds->pitch);
+ col = drawPoint.x;
+ for (;;) {
+ bgRunCount = *readPointer++;
+ widthCount += bgRunCount;
+ if (widthCount >= SAGA_ISOTILE_WIDTH) {
+ break;
+ }
+
+ drawPointer += bgRunCount;
+ col += bgRunCount;
+ fgRunCount = *readPointer++;
+ widthCount += fgRunCount;
+
+ count = 0;
+ while ((col < _tileClip.left) && (count < fgRunCount)) {
+ count++;
+ col++;
+ }
+ while ((col < _tileClip.right) && (count < fgRunCount)) {
+ assert((byte *)ds->pixels <= (byte *)(drawPointer + count));
+ assert((byte *)((byte *)ds->pixels + (_vm->getDisplayWidth() *
+ _vm->getDisplayHeight())) > (byte *)(drawPointer + count));
+ drawPointer[count] = readPointer[count];
+ count++;
+ col++;
+ }
+ readPointer += fgRunCount;
+ drawPointer += fgRunCount;
+ }
+ } else {
+ for (;;) {
+ bgRunCount = *readPointer++;
+ widthCount += bgRunCount;
+ if (widthCount >= SAGA_ISOTILE_WIDTH) {
+ break;
+ }
+
+ fgRunCount = *readPointer++;
+ widthCount += fgRunCount;
+
+ readPointer += fgRunCount;
+ }
+ }
+ }
+
+}
+
+bool IsoMap::checkDragonPoint(int16 u, int16 v, uint16 direction) {
+ DragonPathCell *pathCell;
+
+ if ((u < 1) || (u >= SAGA_DRAGON_SEARCH_DIAMETER - 1) || (v < 1) || (v >= SAGA_DRAGON_SEARCH_DIAMETER - 1)) {
+ return false;
+ }
+
+ pathCell = _dragonSearchArray.getPathCell(u, v);
+
+ if (pathCell->visited) {
+ return false;
+ }
+
+ pathCell->visited = 1;
+ pathCell->direction = direction;
+ return true;
+}
+
+void IsoMap::pushDragonPoint(int16 u, int16 v, uint16 direction) {
+ DragonTilePoint *tilePoint;
+ DragonPathCell *pathCell;
+
+ if ((u < 1) || (u >= SAGA_DRAGON_SEARCH_DIAMETER - 1) || (v < 1) || (v >= SAGA_DRAGON_SEARCH_DIAMETER - 1)) {
+ return;
+ }
+
+ pathCell = _dragonSearchArray.getPathCell(u, v);
+
+ if (pathCell->visited) {
+ return;
+ }
+
+ tilePoint = _dragonSearchArray.getQueue(_queueCount);
+ _queueCount++;
+ if (_queueCount >= SAGA_SEARCH_QUEUE_SIZE) {
+ _queueCount = 0;
+ }
+
+ tilePoint->u = u;
+ tilePoint->v = v;
+ tilePoint->direction = direction;
+
+ pathCell->visited = 1;
+ pathCell->direction = direction;
+}
+
+void IsoMap::pushPoint(int16 u, int16 v, uint16 cost, uint16 direction) {
+ int16 upper;
+ int16 lower;
+ int16 mid;
+ TilePoint *tilePoint;
+ PathCell *pathCell;
+
+ upper = _queueCount;
+ lower = 0;
+
+ if ((u < 1) || (u >= SAGA_SEARCH_DIAMETER - 1) || (v < 1) || (v >= SAGA_SEARCH_DIAMETER - 1)) {
+ return;
+ }
+
+ pathCell = _searchArray.getPathCell(u, v);
+
+ if ((pathCell->visited) && (pathCell->cost <= cost)) {
+ return;
+ }
+
+ if (_queueCount >= SAGA_SEARCH_QUEUE_SIZE) {
+ return;
+ }
+
+ while (1) {
+ mid = (upper + lower) / 2;
+ tilePoint = _searchArray.getQueue(mid);
+
+ if (upper <= lower) {
+ break;
+ }
+
+ if (cost < tilePoint->cost) {
+ lower = mid + 1;
+ } else {
+ upper = mid;
+ }
+ }
+
+ if (mid < _queueCount ) {
+ memmove(tilePoint + 1, tilePoint, (_queueCount - mid) * sizeof (*tilePoint));
+ }
+ _queueCount++;
+
+ tilePoint->u = u;
+ tilePoint->v = v;
+ tilePoint->cost = cost;
+ tilePoint->direction = direction;
+
+ pathCell->visited = 1;
+ pathCell->direction = direction;
+ pathCell->cost = cost;
+}
+
+int16 IsoMap::getTileIndex(int16 u, int16 v, int16 z) {
+ int16 mtileU;
+ int16 mtileV;
+ int16 uc;
+ int16 vc;
+ int16 u0;
+ int16 v0;
+ int16 platformIndex;
+ int16 metaTileIndex;
+
+ mtileU = u >> 3;
+ mtileV = v >> 3;
+ uc = mtileU & (SAGA_TILEMAP_W - 1);
+ vc = mtileV & (SAGA_TILEMAP_W - 1);
+ u0 = u & (SAGA_PLATFORM_W - 1);
+ v0 = v & (SAGA_PLATFORM_W - 1);
+
+ if ((uc != mtileU) || (vc != mtileV)) {
+ metaTileIndex = 0;
+ switch ( _tileMap.edgeType) {
+ case kEdgeTypeBlack:
+ return 0;
+ case kEdgeTypeFill0:
+ break;
+ case kEdgeTypeFill1:
+ metaTileIndex = 1;
+ break;
+ case kEdgeTypeRpt:
+ uc = clamp( 0, mtileU, SAGA_TILEMAP_W - 1);
+ vc = clamp( 0, mtileV, SAGA_TILEMAP_W - 1);
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ break;
+ case kEdgeTypeWrap:
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ break;
+ }
+ } else {
+ metaTileIndex = _tileMap.tilePlatforms[uc][vc];
+ }
+
+ if (_metaTilesCount <= metaTileIndex) {
+ error("IsoMap::getTile wrong metaTileIndex");
+ }
+
+ platformIndex = _metaTileList[metaTileIndex].stack[z];
+ if (platformIndex < 0) {
+ return 0;
+ }
+
+ if (_tilePlatformsCount <= platformIndex) {
+ error("IsoMap::getTile wrong platformIndex");
+ }
+
+ return _tilePlatformList[platformIndex].tiles[u0][v0];
+}
+
+IsoTileData *IsoMap::getTile(int16 u, int16 v, int16 z) {
+ int16 tileIndex;
+
+ tileIndex = getTileIndex(u, v, z);
+
+ if (tileIndex == 0) {
+ return NULL;
+ }
+
+ if (tileIndex & SAGA_MULTI_TILE) {
+ tileIndex = findMulti(tileIndex, u, v, z);
+ }
+
+ return &_tilesTable[tileIndex];
+}
+
+void IsoMap::testPossibleDirections(int16 u, int16 v, uint16 terraComp[8], int skipCenter) {
+ IsoTileData *tile;
+ uint16 fgdMask;
+ uint16 bgdMask;
+ uint16 mask;
+
+
+ memset(terraComp, 0, 8 * sizeof(uint16));
+
+#define FILL_MASK(index, testMask) \
+ if ( mask & testMask) { \
+ terraComp[index] |= fgdMask; \
+ } \
+ if (~mask & testMask) { \
+ terraComp[index] |= bgdMask; \
+ }
+
+#define TEST_TILE_PROLOG(offsetU, offsetV) \
+ tile = getTile(u + offsetU, v + offsetV , _platformHeight); \
+ if (tile != NULL) { \
+ fgdMask = tile->GetFGDMask(); \
+ bgdMask = tile->GetBGDMask(); \
+ mask = tile->terrainMask;
+
+#define TEST_TILE_EPILOG(index) \
+ } else { \
+ if (_vm->_actor->_protagonist->_location.z > 0) { \
+ terraComp[index] = SAGA_IMPASSABLE; \
+ } \
+ }
+
+#define TEST_TILE_END }
+
+ TEST_TILE_PROLOG(0, 0)
+ if (skipCenter) {
+ if ((mask & 0x0660) && (fgdMask & SAGA_IMPASSABLE)) {
+ fgdMask = 0;
+ }
+ if ((~mask & 0x0660) && (bgdMask & SAGA_IMPASSABLE)) {
+ bgdMask = 0;
+ }
+ }
+
+ FILL_MASK(0, 0xcc00)
+ FILL_MASK(1, 0x6600)
+ FILL_MASK(2, 0x3300)
+ FILL_MASK(3, 0x0330)
+ FILL_MASK(4, 0x0033)
+ FILL_MASK(5, 0x0066)
+ FILL_MASK(6, 0x00cc)
+ FILL_MASK(7, 0x0cc0)
+ TEST_TILE_END
+
+ TEST_TILE_PROLOG(1, 1)
+ FILL_MASK(0, 0x0673)
+ TEST_TILE_EPILOG(0)
+
+
+ TEST_TILE_PROLOG(1, 0)
+ FILL_MASK(0, 0x0008)
+ FILL_MASK(1, 0x0666)
+ FILL_MASK(2, 0x0001)
+ TEST_TILE_EPILOG(1)
+
+
+ TEST_TILE_PROLOG(1, -1)
+ FILL_MASK(2, 0x06ec)
+ TEST_TILE_EPILOG(2)
+
+ TEST_TILE_PROLOG(0, 1)
+ FILL_MASK(0, 0x1000)
+ FILL_MASK(7, 0x0770)
+ FILL_MASK(6, 0x0001)
+ TEST_TILE_EPILOG(7)
+
+
+ TEST_TILE_PROLOG(0, -1)
+ FILL_MASK(2, 0x8000)
+ FILL_MASK(3, 0x0ee0)
+ FILL_MASK(4, 0x0008)
+ TEST_TILE_EPILOG(3)
+
+
+ TEST_TILE_PROLOG(-1, 1)
+ FILL_MASK(6, 0x3670)
+ TEST_TILE_EPILOG(6)
+
+
+ TEST_TILE_PROLOG(-1, 0)
+ FILL_MASK(6, 0x8000)
+ FILL_MASK(5, 0x6660)
+ FILL_MASK(4, 0x1000)
+ TEST_TILE_EPILOG(5)
+
+ TEST_TILE_PROLOG(-1, -1)
+ FILL_MASK(4, 0xce60)
+ TEST_TILE_EPILOG(4)
+}
+
+void IsoMap::placeOnTileMap(const Location &start, Location &result, int16 distance, uint16 direction) {
+ int16 bestDistance;
+ int16 bestU;
+ int16 bestV;
+ int16 uBase;
+ int16 vBase;
+ int16 u;
+ int16 v;
+ int i;
+ ActorData *actor;
+ TilePoint tilePoint;
+ uint16 dir;
+ int16 dist;
+ uint16 terraComp[8];
+ const TilePoint *tdir;
+ uint16 terrainMask;
+
+ bestDistance = 0;
+
+
+ uBase = (start.u() >> 4) - SAGA_SEARCH_CENTER;
+ vBase = (start.v() >> 4) - SAGA_SEARCH_CENTER;
+
+ bestU = SAGA_SEARCH_CENTER;
+ bestV = SAGA_SEARCH_CENTER;
+
+ _platformHeight = _vm->_actor->_protagonist->_location.z / 8;
+
+ memset( &_searchArray, 0, sizeof(_searchArray));
+
+ for (i = 0; i < _vm->_actor->_actorsCount; i++) {
+ actor = _vm->_actor->_actors[i];
+ if (!actor->_inScene) continue;
+
+ u = (actor->_location.u() >> 4) - uBase;
+ v = (actor->_location.v() >> 4) - vBase;
+ if ((u >= 0) && (u < SAGA_SEARCH_DIAMETER) &&
+ (v >= 0) && (v < SAGA_SEARCH_DIAMETER) &&
+ ((u != SAGA_SEARCH_CENTER) || (v != SAGA_SEARCH_CENTER))) {
+ _searchArray.getPathCell(u, v)->visited = 1;
+ }
+ }
+
+ _queueCount = 0;
+ pushPoint(SAGA_SEARCH_CENTER, SAGA_SEARCH_CENTER, 0, 0);
+
+ while (_queueCount > 0) {
+
+ _queueCount--;
+ tilePoint = *_searchArray.getQueue(_queueCount);
+
+
+ dist = ABS(tilePoint.u - SAGA_SEARCH_CENTER) + ABS(tilePoint.v - SAGA_SEARCH_CENTER);
+
+ if (dist > bestDistance) {
+ bestU = tilePoint.u;
+ bestV = tilePoint.v;
+ bestDistance = dist;
+
+ if (dist >= distance) {
+ break;
+ }
+ }
+
+ testPossibleDirections(uBase + tilePoint.u, vBase + tilePoint.v, terraComp, 0);
+
+
+ for (dir = 0; dir < 8; dir++) {
+ terrainMask = terraComp[dir];
+
+ if (terrainMask & SAGA_IMPASSABLE ) {
+ continue;
+ }
+
+ if (dir == direction) {
+ tdir = &easyDirTable[ dir ];
+ } else {
+ if (dir + 1 == direction || dir - 1 == direction) {
+ tdir = &normalDirTable[ dir ];
+ } else {
+ tdir = &hardDirTable[ dir ];
+ }
+ }
+
+ pushPoint(tilePoint.u + tdir->u,tilePoint.v + tdir->v, tilePoint.cost + tdir->cost, dir);
+ }
+ }
+
+ result.u() = ((uBase + bestU) << 4) + 8;
+ result.v() = ((vBase + bestV) << 4) + 8;
+}
+
+bool IsoMap::findNearestChasm(int16 &u0, int16 &v0, uint16 &direction) {
+ int16 u, v;
+ uint16 i;
+ u = u0;
+ v = v0;
+
+ for (i = 1; i < 5; i++) {
+ if (getTile( u - i, v, 6) == NULL) {
+ u0 = u - i - 1;
+ v0 = v;
+ direction = kDirDownLeft;
+ return true;
+ }
+
+ if (getTile( u, v - i, 6) == NULL) {
+ u0 = u;
+ v0 = v - i - 1;
+ direction = kDirDownRight;
+ return true;
+ }
+
+ if (getTile( u - i, v - i, 6) == NULL) {
+ u0 = u - i - 1;
+ v0 = v - i - 1;
+ direction = kDirDown;
+ return true;
+ }
+
+ if (getTile( u + i, v - i, 6) == NULL) {
+ u0 = u + i + 1;
+ v0 = v - i - 1;
+ direction = kDirDownRight;
+ return true;
+ }
+
+ if (getTile( u - i, v + i, 6) == NULL) {
+ u0 = u + i + 1;
+ v0 = v - i - 1;
+ direction = kDirLeft;
+ return true;
+ }
+ }
+
+ for (i = 1; i < 5; i++) {
+ if (getTile( u + i, v, 6) == NULL) {
+ u0 = u + i + 1;
+ v0 = v;
+ direction = kDirUpRight;
+ return true;
+ }
+
+ if (getTile( u, v + i, 6) == NULL) {
+ u0 = u;
+ v0 = v + i + 1;
+ direction = kDirUpLeft;
+ return true;
+ }
+
+ if (getTile( u + i, v + i, 6) == NULL) {
+ u0 = u + i + 1;
+ v0 = v + i + 1;
+ direction = kDirUp;
+ return true;
+ }
+ }
+ return false;
+}
+
+void IsoMap::findDragonTilePath(ActorData* actor,const Location &start, const Location &end, uint16 initialDirection) {
+ byte *res;
+ int i;
+ int16 u;
+ int16 v;
+ int16 u1;
+ int16 v1;
+ uint16 dir;
+
+ int16 bestDistance;
+ int16 bestU;
+ int16 bestV;
+
+ int16 uBase;
+ int16 vBase;
+ int16 uFinish;
+ int16 vFinish;
+ DragonPathCell *pcell;
+ IsoTileData *tile;
+ uint16 mask;
+ DragonTilePoint *tilePoint;
+
+ int16 dist;
+ bool first;
+
+ bestDistance = SAGA_DRAGON_SEARCH_DIAMETER;
+ bestU = SAGA_DRAGON_SEARCH_CENTER,
+ bestV = SAGA_DRAGON_SEARCH_CENTER;
+
+ uBase = (start.u() >> 4) - SAGA_DRAGON_SEARCH_CENTER;
+ vBase = (start.v() >> 4) - SAGA_DRAGON_SEARCH_CENTER;
+ uFinish = (end.u() >> 4) - uBase;
+ vFinish = (end.v() >> 4) - vBase;
+
+ _platformHeight = _vm->_actor->_protagonist->_location.z / 8;
+
+ memset( &_dragonSearchArray, 0, sizeof(_dragonSearchArray));
+
+ for (u = 0; u < SAGA_DRAGON_SEARCH_DIAMETER; u++) {
+ for (v = 0; v < SAGA_DRAGON_SEARCH_DIAMETER; v++) {
+
+ pcell = _dragonSearchArray.getPathCell(u, v);
+
+ u1 = uBase + u;
+ v1 = vBase + v;
+
+ if ((u1 > 127) || (u1 < 48) || (v1 > 127) || (v1 < 0)) {
+ pcell->visited = 1;
+ continue;
+ }
+
+ tile = getTile(u1, v1, _platformHeight );
+ if (tile != NULL) {
+ mask = tile->terrainMask;
+ if ( ((mask != 0) && (tile->GetFGDAttr() >= kTerrBlock)) ||
+ ((mask != 0xFFFF) && (tile->GetBGDAttr() >= kTerrBlock)) ) {
+ pcell->visited = 1;
+ }
+ } else {
+ pcell->visited = 1;
+ }
+ }
+ }
+
+ first = true;
+ _queueCount = _readCount = 0;
+ pushDragonPoint( SAGA_DRAGON_SEARCH_CENTER, SAGA_DRAGON_SEARCH_CENTER, initialDirection);
+
+ while (_queueCount != _readCount) {
+
+ tilePoint = _dragonSearchArray.getQueue(_readCount++);
+ if (_readCount >= SAGA_SEARCH_QUEUE_SIZE) {
+ _readCount = 0;
+ }
+
+
+ dist = ABS(tilePoint->u - uFinish) + ABS(tilePoint->v - vFinish);
+
+ if (dist < bestDistance) {
+
+ bestU = tilePoint->u;
+ bestV = tilePoint->v;
+ bestDistance = dist;
+ if (dist == 0) {
+ break;
+ }
+ }
+
+ switch (tilePoint->direction) {
+ case kDirUpRight:
+ if (checkDragonPoint( tilePoint->u + 1, tilePoint->v + 0, kDirUpRight)) {
+ pushDragonPoint( tilePoint->u + 2, tilePoint->v + 0, kDirUpRight);
+ pushDragonPoint( tilePoint->u + 1, tilePoint->v + 1, kDirUpLeft);
+ pushDragonPoint( tilePoint->u + 1, tilePoint->v - 1, kDirDownRight);
+ }
+ break;
+ case kDirDownRight:
+ if (checkDragonPoint( tilePoint->u + 0, tilePoint->v - 1, kDirDownRight)) {
+ pushDragonPoint( tilePoint->u + 0, tilePoint->v - 2, kDirDownRight);
+ pushDragonPoint( tilePoint->u + 1, tilePoint->v - 1, kDirUpRight);
+ pushDragonPoint( tilePoint->u - 1, tilePoint->v - 1, kDirDownLeft);
+ }
+ break;
+ case kDirDownLeft:
+ if (checkDragonPoint( tilePoint->u - 1, tilePoint->v + 0, kDirDownLeft)) {
+ pushDragonPoint( tilePoint->u - 2, tilePoint->v + 0, kDirDownLeft);
+ pushDragonPoint( tilePoint->u - 1, tilePoint->v - 1, kDirDownRight);
+ pushDragonPoint( tilePoint->u - 1, tilePoint->v + 1, kDirUpLeft);
+ }
+ break;
+ case kDirUpLeft:
+ if (checkDragonPoint( tilePoint->u + 0, tilePoint->v + 1, kDirUpLeft)) {
+ pushDragonPoint( tilePoint->u + 0, tilePoint->v + 2, kDirUpLeft);
+ pushDragonPoint( tilePoint->u - 1, tilePoint->v + 1, kDirDownLeft);
+ pushDragonPoint( tilePoint->u + 1, tilePoint->v + 1, kDirUpRight);
+ }
+ break;
+ }
+
+ if (first && (_queueCount == _readCount)) {
+ pushDragonPoint( tilePoint->u + 1, tilePoint->v + 0, kDirUpRight);
+ pushDragonPoint( tilePoint->u + 0, tilePoint->v - 1, kDirDownRight);
+ pushDragonPoint( tilePoint->u - 1, tilePoint->v + 0, kDirDownLeft);
+ pushDragonPoint( tilePoint->u + 0, tilePoint->v + 1, kDirUpLeft);
+ }
+ first = false;
+ }
+
+ res = &_pathDirections[SAGA_MAX_PATH_DIRECTIONS];
+ i = 0;
+ while ((bestU != SAGA_DRAGON_SEARCH_CENTER) || (bestV != SAGA_DRAGON_SEARCH_CENTER)) {
+ pcell = _dragonSearchArray.getPathCell(bestU, bestV);
+
+ *--res = pcell->direction;
+ i++;
+ if (i >= SAGA_MAX_PATH_DIRECTIONS) {
+ break;
+ }
+
+ dir = (pcell->direction + 4) & 0x07;
+
+ bestU += normalDirTable[dir].u;
+ bestV += normalDirTable[dir].v;
+ }
+
+/* if (i > 64) {
+ i = 64;
+ }*/
+
+ actor->_walkStepsCount = i;
+ if (i) {
+ actor->setTileDirectionsSize(i, false);
+ memcpy(actor->_tileDirections, res, i );
+ }
+
+}
+
+void IsoMap::findTilePath(ActorData* actor, const Location &start, const Location &end) {
+ ActorData *other;
+ int i;
+ int16 u;
+ int16 v;
+ int16 bestDistance;
+ int16 bestU;
+ int16 bestV;
+
+ int16 uBase;
+ int16 vBase;
+ int16 uFinish;
+ int16 vFinish;
+
+ TilePoint tilePoint;
+ uint16 dir;
+ int16 dist;
+ uint16 terraComp[8];
+ const TilePoint *tdir;
+ uint16 terrainMask;
+ const PathCell *pcell;
+ byte *res;
+
+
+ bestDistance = SAGA_SEARCH_DIAMETER;
+ bestU = SAGA_SEARCH_CENTER,
+ bestV = SAGA_SEARCH_CENTER;
+
+ uBase = (start.u() >> 4) - SAGA_SEARCH_CENTER;
+ vBase = (start.v() >> 4) - SAGA_SEARCH_CENTER;
+ uFinish = (end.u() >> 4) - uBase;
+ vFinish = (end.v() >> 4) - vBase;
+
+ _platformHeight = _vm->_actor->_protagonist->_location.z / 8;
+
+
+
+ memset( &_searchArray, 0, sizeof(_searchArray));
+
+ if (!(actor->_actorFlags & kActorNoCollide) &&
+ (_vm->_scene->currentSceneResourceId() != RID_ITE_OVERMAP_SCENE)) {
+ for (i = 0; i < _vm->_actor->_actorsCount; i++) {
+ other = _vm->_actor->_actors[i];
+ if (!other->_inScene) continue;
+ if (other == actor) continue;
+
+ u = (other->_location.u() >> 4) - uBase;
+ v = (other->_location.v() >> 4) - vBase;
+ if ((u >= 1) && (u < SAGA_SEARCH_DIAMETER) &&
+ (v >= 1) && (v < SAGA_SEARCH_DIAMETER) &&
+ ((u != SAGA_SEARCH_CENTER) || (v != SAGA_SEARCH_CENTER))) {
+ _searchArray.getPathCell(u, v)->visited = 1;
+ }
+ }
+ }
+
+ _queueCount = 0;
+ pushPoint(SAGA_SEARCH_CENTER, SAGA_SEARCH_CENTER, 0, 0);
+
+
+ while (_queueCount > 0) {
+
+ _queueCount--;
+ tilePoint = *_searchArray.getQueue(_queueCount);
+
+ if (tilePoint.cost > 100 && actor == _vm->_actor->_protagonist) continue;
+
+ dist = ABS(tilePoint.u - uFinish) + ABS(tilePoint.v - vFinish);
+
+ if (dist < bestDistance) {
+ bestU = tilePoint.u;
+ bestV = tilePoint.v;
+ bestDistance = dist;
+
+ if (dist == 0) {
+ break;
+ }
+ }
+
+ testPossibleDirections(uBase + tilePoint.u, vBase + tilePoint.v, terraComp,
+ (tilePoint.u == SAGA_SEARCH_CENTER && tilePoint.v == SAGA_SEARCH_CENTER));
+
+ for (dir = 0; dir < 8; dir++) {
+ terrainMask = terraComp[dir];
+
+ if (terrainMask & SAGA_IMPASSABLE) {
+ continue;
+ } else {
+ if (terrainMask & (1 << kTerrRough)) {
+ tdir = &hardDirTable[ dir ];
+ } else {
+ if (terrainMask & (1 << kTerrNone)) {
+ tdir = &normalDirTable[ dir ];
+ } else {
+ tdir = &easyDirTable[ dir ];
+ }
+ }
+ }
+
+
+ pushPoint(tilePoint.u + tdir->u, tilePoint.v + tdir->v, tilePoint.cost + tdir->cost, dir);
+ }
+ }
+
+ res = &_pathDirections[SAGA_MAX_PATH_DIRECTIONS];
+ i = 0;
+ while ((bestU != SAGA_SEARCH_CENTER) || (bestV != SAGA_SEARCH_CENTER)) {
+ pcell = _searchArray.getPathCell(bestU, bestV);
+
+ *--res = pcell->direction;
+ i++;
+ if (i >= SAGA_MAX_PATH_DIRECTIONS) {
+ break;
+ }
+
+ dir = (pcell->direction + 4) & 0x07;
+
+ bestU += normalDirTable[dir].u;
+ bestV += normalDirTable[dir].v;
+ }
+
+/* if (i > 64) {
+ i = 64;
+ }*/
+ actor->_walkStepsCount = i;
+ if (i) {
+ actor->setTileDirectionsSize(i, false);
+ memcpy(actor->_tileDirections, res, i );
+ }
+}
+
+void IsoMap::setTileDoorState(int doorNumber, int doorState) {
+ MultiTileEntryData *multiTileEntryData;
+
+ if ((doorNumber < 0) || (doorNumber >= _multiCount)) {
+ error("setTileDoorState: doorNumber >= _multiCount");
+ }
+
+ multiTileEntryData = &_multiTable[doorNumber];
+ multiTileEntryData->currentState = doorState;
+}
+
+static const int16 directions[8][2] = {
+ { 16, 16},
+ { 16, 0},
+ { 16, -16},
+ { 0, -16},
+ { -16, -16},
+ { -16, 0},
+ { -16, 16},
+ { 0, 16}
+};
+
+
+
+bool IsoMap::nextTileTarget(ActorData* actor) {
+ uint16 dir;
+
+ if (actor->_walkStepIndex >= actor->_walkStepsCount) {
+ return false;
+ }
+
+
+ actor->_actionDirection = dir = actor->_tileDirections[actor->_walkStepIndex++];
+
+ actor->_partialTarget.u() =
+ (actor->_location.u() & ~0x0f) + 8 + directions[dir][0];
+
+ actor->_partialTarget.v() =
+ (actor->_location.v() & ~0x0f) + 8 + directions[dir][1];
+
+
+ if (dir == 0) {
+ actor->_facingDirection = kDirUp;
+ } else {
+ if (dir == 4) {
+ actor->_facingDirection = kDirDown;
+ } else {
+ if (dir < 4) {
+ actor->_facingDirection = kDirRight;
+ } else {
+ actor->_facingDirection = kDirLeft;
+ }
+ }
+ }
+
+ return true;
+}
+
+void IsoMap::screenPointToTileCoords(const Point &position, Location &location) {
+ Point mPos(position);
+ int x,y;
+
+ if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE){
+ if (mPos.y < 16) {
+ mPos.y = 16;
+ }
+ }
+
+ x = mPos.x + _viewScroll.x - (128 * SAGA_TILEMAP_W) - 16;
+ y = mPos.y + _viewScroll.y - (128 * SAGA_TILEMAP_W) + _vm->_actor->_protagonist->_location.z;
+
+ location.u() = (x - y * 2) >> 1;
+ location.v() = - (x + y * 2) >> 1;
+ location.z = _vm->_actor->_protagonist->_location.z;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/isomap.h b/engines/saga/isomap.h
new file mode 100644
index 0000000000..9264c20fbe
--- /dev/null
+++ b/engines/saga/isomap.h
@@ -0,0 +1,294 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Isometric level module - private header
+
+#ifndef SAGA_ISOMAP_H_
+#define SAGA_ISOMAP_H_
+
+#include "saga/actor.h"
+
+namespace Saga {
+
+#define SAGA_ISOTILEDATA_LEN 8
+#define SAGA_ISOTILE_WIDTH 32
+#define SAGA_ISOTILE_BASEHEIGHT 15
+#define SAGA_TILE_NOMINAL_H 16
+#define SAGA_MAX_TILE_H 64
+
+#define SAGA_TILEPLATFORMDATA_LEN 136
+#define SAGA_PLATFORM_W 8
+#define SAGA_MAX_PLATFORM_H 16
+
+#define SAGA_TILEMAP_LEN 514
+#define SAGA_TILEMAP_W 16
+#define SAGA_TILEMAP_H 16
+
+#define SAGA_METATILEDATA_LEN 36
+
+#define SAGA_MULTI_TILE (1 << 15)
+
+#define SAGA_SCROLL_LIMIT_X1 32
+#define SAGA_SCROLL_LIMIT_X2 64
+#define SAGA_SCROLL_LIMIT_Y1 8
+#define SAGA_SCROLL_LIMIT_Y2 32
+
+#define SAGA_DRAGON_SEARCH_CENTER 24
+#define SAGA_DRAGON_SEARCH_DIAMETER (SAGA_DRAGON_SEARCH_CENTER * 2)
+
+#define SAGA_SEARCH_CENTER 15
+#define SAGA_SEARCH_DIAMETER (SAGA_SEARCH_CENTER * 2)
+#define SAGA_SEARCH_QUEUE_SIZE 128
+#define SAGA_IMPASSABLE ((1 << kTerrBlock) | (1 << kTerrWater))
+
+#define SAGA_STRAIGHT_NORMAL_COST 4
+#define SAGA_DIAG_NORMAL_COST 6
+
+#define SAGA_STRAIGHT_EASY_COST 2
+#define SAGA_DIAG_EASY_COST 3
+
+#define SAGA_STRAIGHT_HARD_COST 9
+#define SAGA_DIAG_HARD_COST 10
+#define SAGA_MAX_PATH_DIRECTIONS 256
+
+enum TerrainTypes {
+ kTerrNone = 0,
+ kTerrPath = 1,
+ kTerrRough = 2,
+ kTerrBlock = 3,
+ kTerrWater = 4,
+ kTerrLast = 5
+};
+
+enum TileMapEdgeType {
+ kEdgeTypeBlack = 0,
+ kEdgeTypeFill0 = 1,
+ kEdgeTypeFill1 = 2,
+ kEdgeTypeRpt = 3,
+ kEdgeTypeWrap = 4
+};
+
+struct IsoTileData {
+ byte height;
+ int8 attributes;
+ size_t offset;
+ uint16 terrainMask;
+ byte FGDBGDAttr;
+ int8 GetMaskRule() const {
+ return attributes & 0x0F;
+ }
+ byte GetFGDAttr() const {
+ return FGDBGDAttr >> 4;
+ }
+ byte GetBGDAttr() const {
+ return FGDBGDAttr & 0x0F;
+ }
+ uint16 GetFGDMask() const {
+ return 1 << GetFGDAttr();
+ }
+ uint16 GetBGDMask() const {
+ return 1 << GetBGDAttr();
+ }
+};
+
+struct TilePlatformData {
+ int16 metaTile;
+ int16 height;
+ int16 highestPixel;
+ byte vBits;
+ byte uBits;
+ int16 tiles[SAGA_PLATFORM_W][SAGA_PLATFORM_W];
+};
+
+struct TileMapData {
+ byte edgeType;
+ int16 tilePlatforms[SAGA_TILEMAP_W][SAGA_TILEMAP_H];
+};
+
+struct MetaTileData {
+ uint16 highestPlatform;
+ uint16 highestPixel;
+ int16 stack[SAGA_MAX_PLATFORM_H];
+};
+
+struct MultiTileEntryData {
+ int16 offset;
+ byte u;
+ byte v;
+ byte h;
+ byte uSize;
+ byte vSize;
+ byte numStates;
+ byte currentState;
+};
+
+
+
+
+
+class IsoMap {
+public:
+ IsoMap(SagaEngine *vm);
+ ~IsoMap() {
+ freeMem();
+ }
+ void loadImages(const byte * resourcePointer, size_t resourceLength);
+ void loadMap(const byte * resourcePointer, size_t resourceLength);
+ void loadPlatforms(const byte * resourcePointer, size_t resourceLength);
+ void loadMetaTiles(const byte * resourcePointer, size_t resourceLength);
+ void loadMulti(const byte * resourcePointer, size_t resourceLength);
+ void freeMem();
+ void draw(Surface *ds);
+ void drawSprite(Surface *ds, SpriteList &spriteList, int spriteNumber, const Location &location, const Point &screenPosition, int scale);
+ void adjustScroll(bool jump);
+ void tileCoordsToScreenPoint(const Location &location, Point &position) {
+ position.x = location.u() - location.v() + (128 * SAGA_TILEMAP_W) - _viewScroll.x + 16;
+ position.y = -(location.uv() >> 1) + (128 * SAGA_TILEMAP_W) - _viewScroll.y - location.z;
+ }
+ void screenPointToTileCoords(const Point &position, Location &location);
+ void placeOnTileMap(const Location &start, Location &result, int16 distance, uint16 direction);
+ void findDragonTilePath(ActorData* actor, const Location &start, const Location &end, uint16 initialDirection);
+ bool findNearestChasm(int16 &u0, int16 &v0, uint16 &direction);
+ void findTilePath(ActorData* actor, const Location &start, const Location &end);
+ bool nextTileTarget(ActorData* actor);
+ void setTileDoorState(int doorNumber, int doorState);
+ Point getMapPosition() { return _mapPosition; }
+ void setMapPosition(int x, int y);
+ int16 getTileIndex(int16 u, int16 v, int16 z);
+
+private:
+ void drawTiles(Surface *ds, const Location *location);
+ void drawMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, int16 absU, int16 absV);
+ void drawSpriteMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, Location &location, int16 absU, int16 absV);
+ void drawPlatform(Surface *ds, uint16 platformIndex, const Point &point, int16 absU, int16 absV, int16 absH);
+ void drawSpritePlatform(Surface *ds, uint16 platformIndex, const Point &point, const Location &location, int16 absU, int16 absV, int16 absH);
+ void drawTile(Surface *ds, uint16 tileIndex, const Point &point, const Location *location);
+ int16 smoothSlide(int16 value, int16 min, int16 max) {
+ if (value < min) {
+ if (value < min - 100 || value > min - 4) {
+ value = min;
+ } else {
+ value += 4;
+ }
+ } else {
+ if (value > max) {
+ if (value > max + 100 || value < max + 4) {
+ value = max;
+ } else {
+ value -= 4;
+ }
+ }
+ }
+ return value;
+ }
+ int16 findMulti(int16 tileIndex, int16 absU, int16 absV, int16 absH);
+ void pushPoint(int16 u, int16 v, uint16 cost, uint16 direction);
+ void pushDragonPoint(int16 u, int16 v, uint16 direction);
+ bool checkDragonPoint(int16 u, int16 v, uint16 direction);
+ void testPossibleDirections(int16 u, int16 v, uint16 terraComp[8], int skipCenter);
+ IsoTileData *getTile(int16 u, int16 v, int16 z);
+
+
+ byte *_tileData;
+ size_t _tileDataLength;
+ uint16 _tilesCount;
+ IsoTileData *_tilesTable;
+
+ uint16 _tilePlatformsCount;
+ TilePlatformData *_tilePlatformList;
+ uint16 _metaTilesCount;
+ MetaTileData *_metaTileList;
+
+ uint16 _multiCount;
+ MultiTileEntryData *_multiTable;
+ uint16 _multiDataCount;
+ int16 *_multiTableData;
+
+ TileMapData _tileMap;
+
+ Point _mapPosition;
+
+// path finding stuff
+ uint16 _platformHeight;
+
+ struct DragonPathCell {
+ uint8 visited:1,direction:3;
+ };
+ struct DragonTilePoint {
+ int8 u, v;
+ uint8 direction:4;
+ };
+ struct PathCell {
+ uint16 visited:1,direction:3,cost:12;
+ };
+
+public:
+ struct TilePoint {
+ int8 u, v;
+ uint16 direction:4,cost:12;
+ };
+
+private:
+ struct DragonSearchArray {
+ DragonPathCell cell[SAGA_DRAGON_SEARCH_DIAMETER][SAGA_DRAGON_SEARCH_DIAMETER];
+ DragonTilePoint queue[SAGA_SEARCH_QUEUE_SIZE];
+ DragonTilePoint *getQueue(uint16 i) {
+ assert(i < SAGA_SEARCH_QUEUE_SIZE);
+ return &queue[i];
+ }
+ DragonPathCell *getPathCell(uint16 u, uint16 v) {
+ assert((u < SAGA_DRAGON_SEARCH_DIAMETER) && (v < SAGA_DRAGON_SEARCH_DIAMETER));
+ return &cell[u][v];
+ }
+ };
+ struct SearchArray {
+ PathCell cell[SAGA_SEARCH_DIAMETER][SAGA_SEARCH_DIAMETER];
+ TilePoint queue[SAGA_SEARCH_QUEUE_SIZE];
+ TilePoint *getQueue(uint16 i) {
+ assert(i < SAGA_SEARCH_QUEUE_SIZE);
+ return &queue[i];
+ }
+ PathCell *getPathCell(uint16 u, uint16 v) {
+ assert((u < SAGA_SEARCH_DIAMETER) && (v < SAGA_SEARCH_DIAMETER));
+ return &cell[u][v];
+ }
+ };
+
+ int16 _queueCount;
+ int16 _readCount;
+ SearchArray _searchArray;
+ DragonSearchArray _dragonSearchArray;
+ byte _pathDirections[SAGA_MAX_PATH_DIRECTIONS];
+
+
+ int _viewDiff;
+ Point _viewScroll;
+ Rect _tileClip;
+
+ SagaEngine *_vm;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/ite_introproc.cpp b/engines/saga/ite_introproc.cpp
new file mode 100644
index 0000000000..abe2deb4a5
--- /dev/null
+++ b/engines/saga/ite_introproc.cpp
@@ -0,0 +1,1028 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+
+// Intro sequence scene procedures
+
+#include "saga/saga.h"
+#include "saga/gfx.h"
+
+#include "saga/animation.h"
+#include "saga/events.h"
+#include "saga/font.h"
+#include "saga/sndres.h"
+#include "saga/palanim.h"
+#include "saga/music.h"
+
+#include "saga/scene.h"
+#include "saga/resnames.h"
+#include "saga/rscfile.h"
+
+namespace Saga {
+
+using Common::UNK_LANG;
+using Common::EN_USA;
+using Common::DE_DEU;
+
+LoadSceneParams ITE_IntroList[] = {
+ {RID_ITE_INTRO_ANIM_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_CAVE_SCENE_1, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave1Proc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_CAVE_SCENE_2, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave2Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_CAVE_SCENE_3, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave3Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_CAVE_SCENE_4, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave4Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_VALLEY_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_TREEHOUSE_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroTreeHouseProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_FAIREPATH_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroFairePathProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_FAIRETENT_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}
+};
+
+int Scene::ITEStartProc() {
+ size_t scenesCount;
+ size_t i;
+
+ LoadSceneParams firstScene;
+ LoadSceneParams tempScene;
+
+ scenesCount = ARRAYSIZE(ITE_IntroList);
+
+ for (i = 0; i < scenesCount; i++) {
+ tempScene = ITE_IntroList[i];
+ tempScene.sceneDescriptor = _vm->_resource->convertResourceId(tempScene.sceneDescriptor);
+ _vm->_scene->queueScene(&tempScene);
+ }
+
+
+ firstScene.loadFlag = kLoadBySceneNumber;
+ firstScene.sceneDescriptor = _vm->getStartSceneNumber();
+ firstScene.sceneDescription = NULL;
+ firstScene.sceneSkipTarget = true;
+ firstScene.sceneProc = NULL;
+ firstScene.transitionType = kTransitionFade;
+ firstScene.actorsEntrance = 0;
+ firstScene.chapter = -1;
+
+ _vm->_scene->queueScene(&firstScene);
+
+ return SUCCESS;
+}
+
+Event *Scene::ITEQueueDialogue(Event *q_event, int n_dialogues, const IntroDialogue dialogue[]) {
+ TextListEntry textEntry;
+ TextListEntry *entry;
+ Event event;
+ int voice_len;
+ int i;
+
+ // Queue narrator dialogue list
+ textEntry.knownColor = kKnownColorSubtitleTextColor;
+ textEntry.effectKnownColor = kKnownColorTransparent;
+ textEntry.useRect = true;
+ textEntry.rect.left = 0;
+ textEntry.rect.right = _vm->getDisplayWidth();
+ textEntry.rect.top = (_vm->getLanguage() == Common::DE_DEU) ? INTRO_DE_CAPTION_Y : INTRO_CAPTION_Y;
+ textEntry.rect.bottom = _vm->getDisplayHeight();
+ textEntry.font = kKnownFontMedium;
+ textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered);
+
+ for (i = 0; i < n_dialogues; i++) {
+ textEntry.text = dialogue[i].i_str;
+ entry = _vm->_scene->_textList.addEntry(textEntry);
+
+ // Display text
+ event.type = kEvTOneshot;
+ event.code = kTextEvent;
+ event.op = kEventDisplay;
+ event.data = entry;
+ event.time = (i == 0) ? 0 : VOICE_PAD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Play voice
+ event.type = kEvTOneshot;
+ event.code = kVoiceEvent;
+ event.op = kEventPlay;
+ event.param = dialogue[i].i_voice_rn;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ voice_len = _vm->_sndRes->getVoiceLength(dialogue[i].i_voice_rn);
+ if (voice_len < 0) {
+ voice_len = strlen(dialogue[i].i_str) * VOICE_LETTERLEN;
+ }
+
+ // Remove text
+ event.type = kEvTOneshot;
+ event.code = kTextEvent;
+ event.op = kEventRemove;
+ event.data = entry;
+ event.time = voice_len;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ }
+
+ return q_event;
+}
+
+enum {
+ kCHeader,
+ kCText
+};
+
+enum {
+ kITEPC = (1 << 0),
+ kITEPCCD = (1 << 1),
+ kITEMac = (1 << 2),
+ kITEWyrmKeep = (1 << 3),
+ kITEAny = 0xffff,
+ kITENotWyrmKeep = kITEAny & ~kITEWyrmKeep
+};
+
+// Queue a page of credits text. The original interpreter did word-wrapping
+// automatically. We currently don't.
+
+Event *Scene::ITEQueueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]) {
+ int game;
+ Common::Language lang;
+
+ // The assumption here is that all WyrmKeep versions have the same
+ // credits, regardless of which operating system they're for.
+
+ lang = _vm->getLanguage();
+
+ if (_vm->getFeatures() & GF_WYRMKEEP) {
+ game = kITEWyrmKeep;
+ } else if (_vm->getPlatform() == Common::kPlatformMacintosh) {
+ game = kITEMac;
+ } else if (_vm->getGameId() == GID_ITE_CD_G) {
+ game = kITEPCCD;
+ } else {
+ game = kITEPC;
+ }
+
+ int line_spacing = 0;
+ int paragraph_spacing;
+ KnownFont font = kKnownFontSmall;
+ int i;
+
+ int n_paragraphs = 0;
+ int credits_height = 0;
+
+ for (i = 0; i < n_credits; i++) {
+ if (credits[i].lang != lang && credits[i].lang != UNK_LANG) {
+ continue;
+ }
+
+ if (!(credits[i].game & game)) {
+ continue;
+ }
+
+ switch (credits[i].type) {
+ case kCHeader:
+ font = kKnownFontSmall;
+ line_spacing = 4;
+ n_paragraphs++;
+ break;
+ case kCText:
+ font = kKnownFontMedium;
+ line_spacing = 2;
+ break;
+ default:
+ error("Unknown credit type");
+ }
+
+ credits_height += (_vm->_font->getHeight(font) + line_spacing);
+ }
+
+ paragraph_spacing = (200 - credits_height) / (n_paragraphs + 3);
+ credits_height += (n_paragraphs * paragraph_spacing);
+
+ int y = paragraph_spacing;
+
+ TextListEntry textEntry;
+ TextListEntry *entry;
+ Event event;
+ Event *q_event = NULL;
+
+ textEntry.knownColor = kKnownColorSubtitleTextColor;
+ textEntry.effectKnownColor = kKnownColorTransparent;
+ textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered);
+ textEntry.point.x = 160;
+
+ for (i = 0; i < n_credits; i++) {
+ if (credits[i].lang != lang && credits[i].lang != UNK_LANG) {
+ continue;
+ }
+
+ if (!(credits[i].game & game)) {
+ continue;
+ }
+
+ switch (credits[i].type) {
+ case kCHeader:
+ font = kKnownFontSmall;
+ line_spacing = 4;
+ y += paragraph_spacing;
+ break;
+ case kCText:
+ font = kKnownFontMedium;
+ line_spacing = 2;
+ break;
+ default:
+ break;
+ }
+
+ textEntry.text = credits[i].string;
+ textEntry.font = font;
+ textEntry.point.y = y;
+
+ entry = _vm->_scene->_textList.addEntry(textEntry);
+
+ // Display text
+ event.type = kEvTOneshot;
+ event.code = kTextEvent;
+ event.op = kEventDisplay;
+ event.data = entry;
+ event.time = delta_time;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Remove text
+ event.type = kEvTOneshot;
+ event.code = kTextEvent;
+ event.op = kEventRemove;
+ event.data = entry;
+ event.time = duration;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ y += (_vm->_font->getHeight(font) + line_spacing);
+ }
+
+ return q_event;
+}
+
+int Scene::SC_ITEIntroAnimProc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroAnimProc(param);
+}
+
+// Handles the introductory Dreamer's Guild / NWC logo animation scene.
+int Scene::ITEIntroAnimProc(int param) {
+ Event event;
+ Event *q_event;
+
+ switch (param) {
+ case SCENE_BEGIN:{
+ // Background for intro scene is the first frame of the
+ // intro animation; display it and set the palette
+ event.type = kEvTOneshot;
+ event.code = kBgEvent;
+ event.op = kEventDisplay;
+ event.param = kEvPSetPalette;
+ event.time = 0;
+
+ q_event = _vm->_events->queue(&event);
+
+ debug(3, "Intro animation procedure started.");
+ debug(3, "Linking animation resources...");
+
+ _vm->_anim->setFrameTime(0, ITE_INTRO_FRAMETIME);
+
+ // Link this scene's animation resources for continuous
+ // playback
+ int lastAnim;
+
+ if (_vm->getFeatures() & GF_WYRMKEEP) {
+ if (_vm->getPlatform() == Common::kPlatformMacintosh) {
+ lastAnim = 3;
+ } else {
+ lastAnim = 2;
+ }
+ } else {
+ if (_vm->getPlatform() == Common::kPlatformMacintosh) {
+ lastAnim = 4;
+ } else {
+ lastAnim = 5;
+ }
+ }
+
+ for (int i = 0; i < lastAnim; i++)
+ _vm->_anim->link(i, i+1);
+
+ _vm->_anim->setFlag(lastAnim, ANIM_FLAG_ENDSCENE);
+
+ debug(3, "Beginning animation playback.");
+
+ // Begin the animation
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.param = 0;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue intro music playback
+ event.type = kEvTOneshot;
+ event.code = kMusicEvent;
+ event.param = MUSIC_1;
+ event.param2 = MUSIC_LOOP;
+ event.op = kEventPlay;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ }
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure parameter");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroCave1Proc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroCave1Proc(param);
+}
+
+// Handles first introductory cave painting scene
+int Scene::ITEIntroCave1Proc(int param) {
+ Event event;
+ Event *q_event;
+ int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0;
+
+ static const IntroDialogue dialogue[][4] = {
+ { { // English
+ RID_CAVE_VOICE_0,
+ "We see the sky, we see the land, we see the water, "
+ "and we wonder: Are we the only ones?"
+ },
+ {
+ RID_CAVE_VOICE_1,
+ "Long before we came to exist, the humans ruled the "
+ "Earth."
+ },
+ {
+ RID_CAVE_VOICE_2,
+ "They made marvelous things, and moved whole "
+ "mountains."
+ },
+ {
+ RID_CAVE_VOICE_3,
+ "They knew the Secret of Flight, the Secret of "
+ "Happiness, and other secrets beyond our imagining."
+ } },
+ { { // German
+ RID_CAVE_VOICE_0,
+ "Um uns sind der Himmel, das Land und die Seen; und "
+ "wir fragen uns - sind wir die einzigen?"
+ },
+ {
+ RID_CAVE_VOICE_1,
+ "Lange vor unserer Zeit herrschten die Menschen "
+ "\201ber die Erde."
+ },
+ {
+ RID_CAVE_VOICE_2,
+ "Sie taten wundersame Dinge und versetzten ganze "
+ "Berge."
+ },
+ {
+ RID_CAVE_VOICE_3,
+ "Sie kannten das Geheimnis des Fluges, das Geheimnis "
+ "der Fr\224hlichkeit und andere Geheimnisse, die "
+ "unsere Vorstellungskraft \201bersteigen."
+ } }
+ };
+
+ int n_dialogues = ARRAYSIZE(dialogue[lang]);
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Begin palette cycling animation for candles
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStart;
+ event.time = 0;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Queue narrator dialogue list
+ q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]);
+
+ // End scene after last dialogue over
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = VOICE_PAD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ case SCENE_END:
+ break;
+
+ default:
+ warning("Illegal scene procedure paramater");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroCave2Proc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroCave2Proc(param);
+}
+
+// Handles second introductory cave painting scene
+int Scene::ITEIntroCave2Proc(int param) {
+ Event event;
+ Event *q_event;
+ int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0;
+
+ static const IntroDialogue dialogue[][3] = {
+ { { // English
+ RID_CAVE_VOICE_4,
+ "The humans also knew the Secret of Life, and they "
+ "used it to give us the Four Great Gifts:"
+ },
+ {
+ RID_CAVE_VOICE_5,
+ "Thinking minds, feeling hearts, speaking mouths, and "
+ "reaching hands."
+ },
+ {
+ RID_CAVE_VOICE_6,
+ "We are their children."
+ } },
+ { { // German
+ RID_CAVE_VOICE_4,
+ "Au$erdem kannten die Menschen das Geheimnis des "
+ "Lebens. Und sie nutzten es, um uns die vier gro$en "
+ "Geschenke zu geben -"
+ },
+ {
+ RID_CAVE_VOICE_5,
+ "den denkenden Geist, das f\201hlende Herz, den "
+ "sprechenden Mund und die greifende Hand."
+ },
+ {
+ RID_CAVE_VOICE_6,
+ "Wir sind ihre Kinder."
+ } }
+ };
+
+ int n_dialogues = ARRAYSIZE(dialogue[lang]);
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Start 'dissolve' transition to new scene background
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolve;
+ event.time = 0;
+ event.duration = DISSOLVE_DURATION;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Begin palette cycling animation for candles
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStart;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue narrator dialogue list
+ q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]);
+
+ // End scene after last dialogue over
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = VOICE_PAD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure paramater");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroCave3Proc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroCave3Proc(param);
+}
+
+// Handles third introductory cave painting scene
+int Scene::ITEIntroCave3Proc(int param) {
+ Event event;
+ Event *q_event;
+ int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0;
+
+ static const IntroDialogue dialogue[][3] = {
+ { { // English
+ RID_CAVE_VOICE_7,
+ "They taught us how to use our hands, and how to "
+ "speak."
+ },
+ {
+ RID_CAVE_VOICE_8,
+ "They showed us the joy of using our minds."
+ },
+ {
+ RID_CAVE_VOICE_9,
+ "They loved us, and when we were ready, they surely "
+ "would have given us the Secret of Happiness."
+ } },
+ { { // German
+ RID_CAVE_VOICE_7,
+ "Sie lehrten uns zu sprechen und unsere H\204nde zu "
+ "benutzen."
+ },
+ {
+ RID_CAVE_VOICE_8,
+ "Sie zeigten uns die Freude am Denken."
+ },
+ {
+ RID_CAVE_VOICE_9,
+ "Sie liebten uns, und w\204ren wir bereit gewesen, "
+ "h\204tten sie uns sicherlich das Geheimnis der "
+ "Fr\224hlichkeit offenbart."
+ } }
+ };
+
+ int n_dialogues = ARRAYSIZE(dialogue[lang]);
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Start 'dissolve' transition to new scene background
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolve;
+ event.time = 0;
+ event.duration = DISSOLVE_DURATION;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Begin palette cycling animation for candles
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStart;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue narrator dialogue list
+ q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]);
+
+ // End scene after last dialogue over
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = VOICE_PAD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure paramater");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroCave4Proc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroCave4Proc(param);
+}
+
+// Handles fourth introductory cave painting scene
+int Scene::ITEIntroCave4Proc(int param) {
+ Event event;
+ Event *q_event;
+ int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0;
+
+ static const IntroDialogue dialogue[][4] = {
+ { { // English
+ RID_CAVE_VOICE_10,
+ "And now we see the sky, the land, and the water that "
+ "we are heirs to, and we wonder: why did they leave?"
+ },
+ {
+ RID_CAVE_VOICE_11,
+ "Do they live still, in the stars? In the oceans "
+ "depths? In the wind?"
+ },
+ {
+ RID_CAVE_VOICE_12,
+ "We wonder, was their fate good or evil?"
+ },
+ {
+ RID_CAVE_VOICE_13,
+ "And will we also share the same fate one day?"
+ } },
+ { { // German
+ RID_CAVE_VOICE_10,
+ "Und nun sehen wir den Himmel, das Land und die "
+ "Seen - unser Erbe. Und wir fragen uns - warum "
+ "verschwanden sie?"
+ },
+ {
+ RID_CAVE_VOICE_11,
+ "Leben sie noch in den Sternen? In den Tiefen des "
+ "Ozeans? Im Wind?"
+ },
+ {
+ RID_CAVE_VOICE_12,
+ "Wir fragen uns - war ihr Schicksal gut oder b\224se?"
+ },
+ {
+ RID_CAVE_VOICE_13,
+ "Und wird uns eines Tages das gleiche Schicksal "
+ "ereilen?"
+ } }
+ };
+
+ int n_dialogues = ARRAYSIZE(dialogue[lang]);
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Start 'dissolve' transition to new scene background
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolve;
+ event.time = 0;
+ event.duration = DISSOLVE_DURATION;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Begin palette cycling animation for candles
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStart;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue narrator dialogue list
+ q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]);
+
+ // End scene after last dialogue over
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = VOICE_PAD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure paramater");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroValleyProc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroValleyProc(param);
+}
+
+// Handles intro title scene (valley overlook)
+int Scene::ITEIntroValleyProc(int param) {
+ Event event;
+ Event *q_event;
+
+ static const IntroCredit credits[] = {
+ {EN_USA, kITEAny, kCHeader, "Producer"},
+ {DE_DEU, kITEAny, kCHeader, "Produzent"},
+ {UNK_LANG, kITEAny, kCText, "Walter Hochbrueckner"},
+ {EN_USA, kITEAny, kCHeader, "Executive Producer"},
+ {DE_DEU, kITEAny, kCHeader, "Ausf\201hrender Produzent"},
+ {UNK_LANG, kITEAny, kCText, "Robert McNally"},
+ {UNK_LANG, kITEWyrmKeep, kCHeader, "2nd Executive Producer"},
+ {EN_USA, kITENotWyrmKeep, kCHeader, "Publisher"},
+ {DE_DEU, kITENotWyrmKeep, kCHeader, "Herausgeber"},
+ {UNK_LANG, kITEAny, kCText, "Jon Van Caneghem"}
+ };
+
+ int n_credits = ARRAYSIZE(credits);
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Begin title screen background animation
+ _vm->_anim->setCycles(0, -1);
+
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.param = 0;
+ event.time = 0;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Begin ITE title theme music
+ _vm->_music->stop();
+
+ event.type = kEvTOneshot;
+ event.code = kMusicEvent;
+ event.param = MUSIC_2;
+ event.param2 = MUSIC_NORMAL;
+ event.op = kEventPlay;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Pause animation before logo
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventStop;
+ event.param = 0;
+ event.time = 3000;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Display logo
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolveBGMask;
+ event.time = 0;
+ event.duration = LOGO_DISSOLVE_DURATION;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Remove logo
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolve;
+ event.time = 3000;
+ event.duration = LOGO_DISSOLVE_DURATION;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Unpause animation before logo
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.time = 0;
+ event.param = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue game credits list
+ q_event = ITEQueueCredits(9000, CREDIT_DURATION1, n_credits, credits);
+
+ // End scene after credit display
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = 1000;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure parameter");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroTreeHouseProc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroTreeHouseProc(param);
+}
+
+// Handles second intro credit screen (treehouse view)
+int Scene::ITEIntroTreeHouseProc(int param) {
+ Event event;
+ Event *q_event;
+
+ static const IntroCredit credits1[] = {
+ {EN_USA, kITEAny, kCHeader, "Game Design"},
+ {DE_DEU, kITEAny, kCHeader, "Spielentwurf"},
+ {UNK_LANG, kITEAny, kCText, "Talin, Joe Pearce, Robert McNally"},
+ {EN_USA, kITEAny, kCText, "and Carolly Hauksdottir"},
+ {DE_DEU, kITEAny, kCText, "und Carolly Hauksdottir"},
+ {EN_USA, kITEAny, kCHeader, "Screenplay and Dialog"},
+ {EN_USA, kITEAny, kCText, "Robert Leh, Len Wein, and Bill Rotsler"},
+ {DE_DEU, kITEAny, kCHeader, "Geschichte und Dialoge"},
+ {DE_DEU, kITEAny, kCText, "Robert Leh, Len Wein und Bill Rotsler"}
+ };
+
+ int n_credits1 = ARRAYSIZE(credits1);
+
+ static const IntroCredit credits2[] = {
+ {UNK_LANG, kITEWyrmKeep, kCHeader, "Art Direction"},
+ {UNK_LANG, kITEWyrmKeep, kCText, "Allison Hershey"},
+ {EN_USA, kITEAny, kCHeader, "Art"},
+ {DE_DEU, kITEAny, kCHeader, "Grafiken"},
+ {UNK_LANG, kITEWyrmKeep, kCText, "Ed Lacabanne, Glenn Price, April Lee,"},
+ {UNK_LANG, kITENotWyrmKeep, kCText, "Edward Lacabanne, Glenn Price, April Lee,"},
+ {UNK_LANG, kITEWyrmKeep, kCText, "Lisa Sample, Brian Dowrick, Reed Waller,"},
+ {EN_USA, kITEWyrmKeep, kCText, "Allison Hershey and Talin"},
+ {DE_DEU, kITEWyrmKeep, kCText, "Allison Hershey und Talin"},
+ {EN_USA, kITENotWyrmKeep, kCText, "Lisa Iennaco, Brian Dowrick, Reed"},
+ {EN_USA, kITENotWyrmKeep, kCText, "Waller, Allison Hershey and Talin"},
+ {DE_DEU, kITEAny, kCText, "Waller, Allison Hershey und Talin"},
+ {EN_USA, kITENotWyrmKeep, kCHeader, "Art Direction"},
+ {DE_DEU, kITENotWyrmKeep, kCHeader, "Grafische Leitung"},
+ {UNK_LANG, kITENotWyrmKeep, kCText, "Allison Hershey"}
+ };
+
+ int n_credits2 = ARRAYSIZE(credits2);
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Start 'dissolve' transition to new scene background
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolve;
+ event.time = 0;
+ event.duration = DISSOLVE_DURATION;
+
+ q_event = _vm->_events->queue(&event);
+
+ if (_vm->_anim->hasAnimation(0)) {
+ // Begin title screen background animation
+ _vm->_anim->setFrameTime(0, 100);
+
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.param = 0;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ }
+
+ // Queue game credits list
+ q_event = ITEQueueCredits(DISSOLVE_DURATION + 2000, CREDIT_DURATION1, n_credits1, credits1);
+ q_event = ITEQueueCredits(DISSOLVE_DURATION + 7000, CREDIT_DURATION1, n_credits2, credits2);
+
+ // End scene after credit display
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = 1000;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure parameter");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroFairePathProc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroFairePathProc(param);
+}
+
+// Handles third intro credit screen (path to puzzle tent)
+int Scene::ITEIntroFairePathProc(int param) {
+ Event event;
+ Event *q_event;
+
+ static const IntroCredit credits1[] = {
+ {EN_USA, kITEAny, kCHeader, "Programming"},
+ {DE_DEU, kITEAny, kCHeader, "Programmiert von"},
+ {UNK_LANG, kITEAny, kCText, "Talin, Walter Hochbrueckner,"},
+ {EN_USA, kITEAny, kCText, "Joe Burks and Robert Wiggins"},
+ {DE_DEU, kITEAny, kCText, "Joe Burks und Robert Wiggins"},
+ {EN_USA, kITEPCCD | kITEWyrmKeep, kCHeader, "Additional Programming"},
+ {EN_USA, kITEPCCD | kITEWyrmKeep, kCText, "John Bolton"},
+ {UNK_LANG, kITEMac, kCHeader, "Macintosh Version"},
+ {UNK_LANG, kITEMac, kCText, "Michael McNally and Robert McNally"},
+ {EN_USA, kITEAny, kCHeader, "Music and Sound"},
+ {DE_DEU, kITEAny, kCHeader, "Musik und Sound"},
+ {UNK_LANG, kITEAny, kCText, "Matt Nathan"}
+ };
+
+ int n_credits1 = ARRAYSIZE(credits1);
+
+ static const IntroCredit credits2[] = {
+ {EN_USA, kITEAny, kCHeader, "Directed by"},
+ {DE_DEU, kITEAny, kCHeader, "Regie"},
+ {UNK_LANG, kITEAny, kCText, "Talin"}
+ };
+
+ int n_credits2 = ARRAYSIZE(credits2);
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Start 'dissolve' transition to new scene background
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolve;
+ event.time = 0;
+ event.duration = DISSOLVE_DURATION;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Begin title screen background animation
+ _vm->_anim->setCycles(0, -1);
+
+ event.type = kEvTOneshot;
+ event.code = kAnimEvent;
+ event.op = kEventPlay;
+ event.param = 0;
+ event.time = 0;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Queue game credits list
+ q_event = ITEQueueCredits(DISSOLVE_DURATION + 2000, CREDIT_DURATION1, n_credits1, credits1);
+ q_event = ITEQueueCredits(DISSOLVE_DURATION + 7000, CREDIT_DURATION1, n_credits2, credits2);
+
+ // End scene after credit display
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = 1000;
+
+ q_event = _vm->_events->chain(q_event, &event);
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure parameter");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroFaireTentProc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroFaireTentProc(param);
+}
+
+// Handles fourth intro credit screen (treehouse view)
+int Scene::ITEIntroFaireTentProc(int param) {
+ Event event;
+ Event *q_event;
+ Event *q_event_start;
+
+ switch (param) {
+ case SCENE_BEGIN:
+
+ // Start 'dissolve' transition to new scene background
+ event.type = kEvTContinuous;
+ event.code = kTransitionEvent;
+ event.op = kEventDissolve;
+ event.time = 0;
+ event.duration = DISSOLVE_DURATION;
+
+ q_event_start = _vm->_events->queue(&event);
+
+ // End scene after momentary pause
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = 5000;
+ q_event = _vm->_events->chain(q_event_start, &event);
+ break;
+ case SCENE_END:
+ break;
+ default:
+ warning("Illegal scene procedure parameter");
+ break;
+ }
+
+ return 0;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/itedata.cpp b/engines/saga/itedata.cpp
new file mode 100644
index 0000000000..3329ab1bbd
--- /dev/null
+++ b/engines/saga/itedata.cpp
@@ -0,0 +1,484 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Actor and Object data tables
+#include "saga/saga.h"
+#include "saga/itedata.h"
+#include "saga/resnames.h"
+#include "saga/sndres.h"
+
+namespace Saga {
+
+ActorTableData ITE_ActorTable[ITE_ACTORCOUNT] = {
+ // Original used so called permanent actors for first three and that was designed by
+ // EXTENDED object flag. They contained frames in more than one resource. We use
+ // different technique here see "Appending to sprite list" in loadActorResources()
+
+// flags name scene x y z spr frm scp col
+// ------------ ---- ---- ---- ----- ---- ---- ---- --- ---- -- -- --
+ { kProtagonist | kExtended,
+ 0, 1, 0, 0, 0, 37, 135, 0, 1, 0, 0, 0}, // map party
+ // spr and frm numbers taken from permanent actors list
+ { kFollower | kExtended,
+ 1, 0, 0, 0, 0, 45, 177, 1, 132, 0, 0, 0}, // Okk
+ { kFollower | kExtended,
+ 2, 0, 0, 0, 0, 48, 143, 2, 161, 0, 0, 0}, // Eeah
+ { 0, 3, 0, 240, 480, 0, 115, 206, 0, 25, 0, 0, 0}, // albino ferret
+ { 0, 4, 17, 368, 400, 0, 115, 206, 4, 49, 0, 0, 0}, // moneychanger
+ { 0, 5, 11, 552, 412, 0, 54, 152, 1, 171, 0, 0, 0}, // Sist
+ { 0, 17, 2, 1192, 888, 0, 57, 153, 17, 49, 0, 0, 0}, // worker ferret 1
+ { 0, 17, 2, 816, 1052, 0, 57, 153, 18, 49, 0, 0, 0}, // worker ferret 2
+ { 0, 17, 2, 928, 932, 0, 58, 153, 19, 49, 0, 0, 0}, // worker ferret 3
+ { 0, 17, 2, 1416, 1160, 0, 58, 153, 20, 49, 0, 0, 0}, // worker ferret 4
+ { 0, 19, 49, 1592, 1336, 0, 92, 175, 15, 162, 0, 0, 0}, // faire merchant 1 (bear)
+ { 0, 20, 49, 744, 824, 0, 63, 156, 19, 112, 0, 4, 4}, // faire merchant 2 (ferret)
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire merchant 3
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire merchant 4
+ { 0, 9, 49, 1560, 1624, 0, 94, 147, 18, 132, 0, 4, 4}, // faire goer 1a (rat)
+ { 0, 56, 49, 1384, 792, 0, 95, 193, 20, 72, 0, 0, 0}, // faire goer 1b (otter)
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 2a
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 2b
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 3a
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 3b
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 4a
+ { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 4b
+ { 0, 18, 32, 764, 448, 0, 55, 150, 0, 48, 10, 4, 4}, // Scorry
+ { 0, 35, 32, 0, 0, 0, 56, 151, 0, 112, 0, 0, 0}, // grand puzzler
+ { 0, 36, 32, 0, 0, 0, 105, 142, 0, 155, 0, 0, 0}, // Rhene
+ { 0, 32, 32, 0, 0, 0, 91, 190, 0, 98, 0, 0, 0}, // elk captain
+ { 0, 31, 32, 0, 0, 0, 90, 189, 0, 171, 0, 0, 0}, // elk guard 1
+ { 0, 31, 32, 0, 0, 0, 90, 189, 0, 171, 0, 0, 0}, // elk guard 2
+ { 0, 31, 32, 0, 0, 0, 90, 189, 0, 171, 0, 0, 0}, // elk guard 3
+ { 0, 31, 32, 0, 0, 0, 79, 172, 0, 18, 0, 0, 0}, // boar sergeant
+ { 0, 21, 50, 664, 400, 0, 76, 171, 2, 74, 0, 4, 4}, // boar sentry 1
+ { 0, 21, 50, 892, 428, 0, 76, 171, 2, 74, 0, 4, 4}, // boar sentry 2
+ { 0, 9, 51, 904, 936, 0, 51, 145, 35, 5, 0, 0, 0}, // hall rat 1
+ { 0, 9, 51, 872, 840, 0, 51, 145, 36, 5, 0, 0, 0}, // hall rat 2
+ { 0, 9, 51, 1432, 344, 0, 51, 145, 37, 5, 0, 0, 0}, // hall rat 3
+ { 0, 9, 51, 664, 472, 0, 51, 145, 38, 5, 0, 0, 0}, // hall rat 4
+ { 0, 10, 51, 1368, 1464, 0, 80, 146, 39, 147, 0, 0, 0}, // book rat 1
+ { 0, 10, 51, 1416, 1624, 0, 80, 146, 40, 147, 0, 0, 0}, // book rat 2
+ { 0, 10, 51, 1752, 120, 0, 80, 146, 41, 147, 0, 0, 0}, // book rat 3
+ { 0, 10, 51, 984, 408, 0, 80, 146, 42, 147, 0, 0, 0}, // book rat 4
+ { 0, 14, 52, 856, 376, 0, 82, 174, 8, 73, 0, 0, 0}, // grounds servant 1
+ { 0, 14, 52, 808, 664, 0, 82, 174, 9, 73, 0, 0, 0}, // grounds servant 2
+ { 0, 14, 52, 440, 568, 0, 82, 174, 10, 73, 0, 0, 0}, // grounds servant 3
+ { 0, 14, 52, 392, 776, 0, 82, 174, 11, 73, 0, 0, 0}, // grounds servant 4
+ { 0, 21, 4, 240, 384, 0, 79, 172, 0, 18, 0, 2, 2}, // boar sentry 3 (by doorway)
+ { 0, 23, 4, 636, 268, 0, 77, 173, 0, 74, 0, 4, 4}, // boar courtier
+ { 0, 22, 4, 900, 320, 0, 78, 179, 0, 60, 0, 4, 4}, // boar king
+ { 0, 14, 4, 788, 264, 0, 75, 170, 0, 171, 0, 2, 2}, // boar servant 1
+ { 0, 14, 4, 1088, 264, 0, 75, 170, 0, 171, 0, 6, 6}, // boar servant 2
+ { 0, 24, 19, 728, 396, 0, 65, 181, 47, 146, 0, 6, 6}, // glass master
+ { 0, 24, 21, -20, -20, 0, 66, 182, 0, 146, 0, 4, 4}, // glass master (with orb)
+ { kCycle, 25, 19, 372, 464, 0, 67, 183, 73, 146, 0, 2, 2}, // glass worker
+ { 0, 26, 5, 564, 476, 27, 53, 149, 1, 5, 0, 4, 4}, // door rat
+ { kCycle, 27, 31, 868, 344, 0, 81, 180, 0, 171, 0, 4, 4}, // bees
+ { 0, 28, 73, 568, 380, 0, 83, 176, 30, 120, 0, 4, 4}, // fortune teller
+ { 0, 14, 7, 808, 480, 0, 82, 174, 9, 73, 0, 0, 0}, // orb messenger
+ { 0, 29, 10, 508, 432, 0, 84, 186, 6, 112, 0, 4, 4}, // elk king
+ { 0, 33, 10, 676, 420, 0, 86, 184, 6, 171, 0, 4, 4}, // elk chancellor
+ { 0, 30, 10, 388, 452, 0, 88, 185, 6, 171, 0, 4, 4}, // elk courtier 1
+ { 0, 30, 10, 608, 444, 0, 89, 185, 6, 171, 0, 4, 4}, // elk courtier 2
+ { 0, 31, 10, 192, 468, 0, 90, 189, 6, 171, 0, 4, 4}, // elk throne guard 1
+ { 0, 31, 10, 772, 432, 0, 90, 189, 6, 171, 0, 4, 4}, // elk throne guard 2
+ { 0, 14, 10, 1340, 444, 0, 87, 188, 6, 171, 0, 4, 4}, // elk servant
+ { 0, 20, 18, 808, 360, 7, 60, 154, 64, 88, 0, 4, 4}, // hardware ferret
+ { 0, 34, 49, 1128, 1256, 0, 96, 191, 16, 35, 0, 4, 4}, // porcupine
+ { 0, 34, 49, 1384, 792, 0, 93, 192, 17, 66, 0, 4, 4}, // faire ram
+ { 0, 24, 21, 0, -40, 0, 65, 181, 50, 146, 0, 6, 6}, // glass master 2
+ { 0, 3, 21, 0, -40, 0, 64, 158, 49, 112, 0, 0, 0}, // Sakka
+ { 0, 17, 21, 0, -40, 0, 62, 157, 74, 48, 0, 0, 0}, // lodge ferret 1
+ { 0, 17, 21, 0, -40, 0, 62, 157, 74, 49, 0, 0, 0}, // lodge ferret 2
+ { 0, 17, 21, 0, -40, 0, 62, 157, 74, 50, 0, 0, 0}, // lodge ferret 3
+ { 0, 12, 244, 1056, 504, 0, 107, 167, 21, 124, 0, 6, 6}, // Elara
+ { 0, 8, 33, 248, 440, 0, 68, 169, 14, 112, 0, 0, 0}, // Tycho
+ { 0, 11, 23, 308, 424, 0, 106, 166, 6, 48, 0, 2, 2}, // Alamma
+ { 0, 17, 2, 1864, 1336, 0, 58, 153, 21, 49, 0, 0, 0}, // worker ferret 5
+ { 0, 17, 2, 760, 216, 0, 58, 153, 22, 49, 0, 0, 0}, // worker ferret 6
+ { 0, 44, 29, 0, 0, 0, 72, 159, 0, 112, 0, 0, 0}, // Prince
+ { 0, 45, 29, 0, 0, 0, 71, 163, 0, 146, 0, 6, 6}, // harem girl 1
+ { 0, 45, 29, 0, 0, 0, 71, 163, 0, 124, 0, 2, 2}, // harem girl 2
+ { 0, 45, 29, 0, 0, 0, 71, 163, 0, 169, 0, 0, 0}, // harem girl 3
+ { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // dog sergeant
+ { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 1
+ { 0, 7, 257, 552, 408, 0, 70, 165, 0, 4, 0, 2, 2}, // throne dog guard 2
+ { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 3
+ { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 4
+ { 0, 7, 257, 712, 380, 0, 69, 164, 0, 4, 0, 4, 4}, // throne dog guard 5
+ { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 6
+ { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 7
+ { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 8
+ { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 9
+ { 0, 7, 0, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 10
+ { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 11
+ { 0, 47, 30, 0, 0, 0, 102, 199, 1, 186, 0, 0, 0}, // old wolf ferryman
+ { 0, 48, 69, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // cat village wildcat
+ { 0, 49, 69, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // cat village attendant
+ { 0, 50, 69, 0, 0, 0, 111, 203, 16, 67, 0, 0, 0}, // cat village Prowwa
+ { 0, 51, 20, 0, 0, 0, 112, 204, 15, 26, 0, 0, 0}, // Prowwa hut Mirrhp
+ { 0, 50, 20, 0, 0, 0, 111, 203, 14, 67, 0, 0, 0}, // Prowwa hut Prowwa
+ { 0, 49, 20, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // Prowwa hut attendant
+ { 0, 48, 256, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // wildcat sentry
+ { 0, 21, 32, 0, 0, 0, 76, 171, 0, 171, 0, 0, 0}, // boar warrior 1
+ { 0, 21, 32, 0, 0, 0, 76, 171, 0, 171, 0, 0, 0}, // boar warrior 2
+ { 0, 21, 32, 0, 0, 0, 76, 171, 0, 171, 0, 0, 0}, // boar warrior 3
+ { 0, 52, 15, 152, 400, 0, 108, 168, 19, 48, 10, 2, 2}, // Alamma's voice
+ { 0, 47, 251, 640, 360, 0, 113, 205, 5, 186, 10, 2, 2}, // ferry on ocean
+ { 0, 41, 75, 152, 400, 0, 100, 197, 5, 81, 0, 0, 0}, // Shiala
+ { 0, 44, 9, 0, 0, 0, 73, 160, 54, 112, 0, 0, 0}, // Prince (asleep)
+ { 0, 0, 22, -20, -20, 0, 118, 209, 0, 171, 0, 0, 0}, // Rif and Eeah (at rockslide)
+ { 0, 1, 22, 0, 0, 0, 119, 210, 0, 171, 0, 0, 0}, // Okk (at rockslide)
+ { 0, 0, 22, -20, -20, 0, 118, 209, 0, 171, 0, 0, 0}, // Rif and Eeah (at rockslide w. rope)
+ { 0, 1, 22, 0, 0, 0, 119, 210, 0, 171, 0, 0, 0}, // Okk (at rockslide w. rope)
+ { 0, 53, 42, 640, 400, 0, 104, 201, 8, 141, 0, 0, 0}, // Kylas Honeyfoot
+ { 0, 54, 21, -20, -20, 0, 120, 211, 48, 238, 0, 0, 0}, // Orb of Hands
+ { 0, 0, 4, -20, -20, 0, 42, 140, 0, 1, 0, 0, 0}, // Rif (muddy)
+ { 0, 26, 5, -20, -20, 27, 52, 148, 1, 5, 0, 4, 4}, // door rat (standing)
+ { 0, 36, 4, -20, -20, 0, 116, 207, 0, 155, 0, 0, 0}, // boar with Rhene 1
+ { 0, 36, 0, -20, -20, 0, 117, 208, 0, 155, 0, 0, 0}, // boar with Rhene 2
+ { 0, 46, 252, -20, -20, 0, 74, 162, 29, 34, 0, 0, 0}, // dog jailer
+ { 0, 0, 32, -20, -20, 0, 41, 137, 0, 1, 0, 0, 0}, // Rif (tourney)
+ { 0, 0, 259, -20, -20, 0, 44, 138, 0, 1, 0, 0, 0}, // cliff rat
+ { 0, 0, 5, -20, -20, 0, 43, 139, 0, 1, 0, 0, 0}, // Rif (cloaked)
+ { 0, 0, 31, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (oak tree scene)
+ { 0, 0, 252, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (jail cell scene)
+ { 0, 0, 15, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (outside Alamma's)
+ { 0, 0, 20, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (sick tent)
+ { 0, 0, 25, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (gem room)
+ { 0, 0, 272, -20, -20, 0, 40, 141, 0, 1, 0, 0, 0}, // Rif (dragon maze)
+ { 0, 0, 50, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (boar entry gate)
+ { 0, 50, 71, -20, -20, 0, 111, 203, 0, 67, 0, 0, 0}, // Prowwa (dog castle back)
+ { 0, 50, 274, -20, -20, 0, 111, 203, 0, 67, 0, 0, 0}, // Prowwa (cat festival)
+ { 0, 50, 274, -20, -20, 0, 110, 212, 0, 171, 0, 0, 0}, // cat festival dancer 1
+ { 0, 50, 274, -20, -20, 0, 110, 212, 0, 171, 0, 0, 0}, // cat festival dancer 2
+ { 0, 50, 274, -20, -20, 0, 110, 212, 0, 171, 0, 0, 0}, // cat festival dancer 3
+ { 0, 57, 272, 909, 909, 48, 121, 213, 0, 171, 0, 0, 0}, // komodo dragon
+ { 0, 58, 15, -20, -20, 0, 122, 214, 0, 171, 0, 0, 0}, // letter from Elara
+ { 0, 37, 246, -20, -20, 0, 97, 194, 0, 141, 0, 0, 0}, // Gar (wolves' cage)
+ { 0, 38, 246, -20, -20, 0, 98, 195, 0, 27, 0, 0, 0}, // Wrah (wolves' cage)
+ { 0, 59, 246, -20, -20, 0, 103, 200, 0, 26, 0, 0, 0}, // Chota (wolves' cage)
+ { 0, 41, 245, -20, -20, 0, 100, 197, 0, 81, 0, 0, 0}, // Shiala (wolves' cage)
+ { 0, 47, 250, 640, 360, 0, 114, 205, 0, 186, 10, 2, 2}, // ferry on ocean
+ { 0, 0, 278, -20, -20, 0, 40, 141, 0, 1, 0, 0, 0}, // Rif (falling in tunnel trap door)
+ { 0, 0, 272, -20, -20, 0, 40, 141, 0, 1, 0, 0, 0}, // Rif (falling in dragon maze)
+ { 0, 41, 77, -20, -20, 0, 100, 197, 24, 81, 0, 0, 0}, // Shiala (grotto)
+ { 0, 37, 261, -20, -20, 0, 97, 194, 0, 141, 0, 0, 0}, // Gar (ambush)
+ { 0, 38, 261, -20, -20, 0, 98, 195, 0, 27, 0, 0, 0}, // Wrah (ambush)
+ { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush)
+ { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush)
+ { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush)
+ { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush)
+ { 0, 59, 279, -20, -20, 0, 103, 200, 0, 26, 0, 0, 0}, // Chota (top of dam)
+ { 0, 38, 279, -20, -20, 0, 98, 195, 0, 27, 0, 0, 0}, // Wrah (top of dam)
+ { 0, 42, 77, -20, -20, 0, 101, 198, 25, 171, 0, 0, 0}, // Shiala's spear
+ { 0, 59, 281, -20, -20, 0, 103, 200, 26, 26, 0, 0, 0}, // Chota (lab)
+ { 0, 59, 279, -20, -20, 0, 123, 215, 0, 1, 0, 0, 0}, // Rif (finale)
+ { 0, 59, 279, -20, -20, 0, 123, 215, 0, 132, 0, 0, 0}, // Okk (finale)
+ { 0, 59, 279, -20, -20, 0, 123, 215, 0, 161, 0, 0, 0}, // Eeah (finale)
+ { 0, 54, 279, -20, -20, 0, 120, 211, 0, 133, 0, 6, 6}, // Orb of Storms (top of dam)
+ { 0, 44, 9, -20, -20, 0, 124, 161, 0, 171, 0, 6, 6}, // Prince's snores
+ { 0, 7, 255, 588, 252, 0, 70, 165, 0, 3, 0, 2, 2}, // hall dog guard 1
+ { 0, 7, 255, 696, 252, 0, 70, 165, 0, 5, 0, 6, 6}, // hall dog guard 2
+ { 0, 36, 4, 0, 0, 0, 105, 142, 0, 155, 0, 0, 0}, // Rhene
+ { 0, 44, 272, 1124, 1124, 120, 72, 159, 0, 112, 0, 0, 0}, // Prince (dragon maze)
+ { 0, 7, 272, 1124, 1108, 120, 70, 165, 0, 4, 0, 0, 0}, // dog heckler 1 (dragon maze)
+ { 0, 7, 272, 1108, 1124, 120, 70, 165, 0, 4, 0, 0, 0}, // dog heckler 2 (dragon maze)
+ { 0, 29, 288, 508, 432, 0, 85, 187, 0, 112, 0, 4, 4}, // elk king (finale)
+ { 0, 29, 0, 508, 432, 0, 84, 186, 0, 99, 0, 4, 4}, // crowd voice 1 (finale)
+ { 0, 29, 0, 508, 432, 0, 84, 186, 0, 98, 0, 4, 4}, // crowd voice 2 (finale)
+ { 0, 29, 0, 508, 432, 0, 84, 186, 0, 104, 0, 4, 4}, // crowd voice 3 (finale)
+ { 0, 29, 0, 508, 432, 0, 84, 186, 0, 99, 0, 4, 4}, // crowd voice 4 (finale)
+ { 0, 36, 288, 0, 0, 0, 105, 142, 0, 155, 0, 0, 0}, // Rhene (finale)
+ { 0, 1, 27, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (temple gate)
+ { 0, 1, 252, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (jail cell)
+ { 0, 1, 25, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (gem room)
+ { 0, 1, 259, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (cliff)
+ { 0, 1, 279, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (dam top)
+ { 0, 1, 273, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (human ruins)
+ { 0, 1, 26, -20, -20, 0, 8, 178, 0, 171, 0, 0, 0}, // puzzle pieces
+ { 0, 1, 0, -20, -20, 0, 0, 0, 0, 50, 0, 0, 0}, // poker dog 1
+ { 0, 1, 0, -20, -20, 0, 0, 0, 0, 82, 0, 0, 0}, // poker dog 2
+ { 0, 1, 0, -20, -20, 0, 0, 0, 0, 35, 0, 0, 0}, // poker dog 3
+ { 0, 9, 74, -20, -20, 0, 51, 145, 0, 5, 0, 0, 0} // sundial rat
+};
+
+
+ObjectTableData ITE_ObjectTable[ITE_OBJECTCOUNT] = {
+ { 8, 49, 1256, 760, 0, 9, 5, kObjNotFlat }, // Magic Hat
+ { 9, 52, 1080, 1864, 0, 68, 4, kObjUseWith }, // Berries
+ { 10, 259, 744, 524, 0, 79, 42, kObjUseWith }, // Card Key
+ { 11, 0, 480, 480, 0, 69, 6, 0 }, // Foot Print
+ { 12, 0, 480, 480, 0, 13, 38, kObjUseWith }, // Power Cell
+ { 13, 28, 640, 412, 40, 14, 15, kObjUseWith }, // Digital Clock
+ { 14, 0, 480, 480, 0, 15, 41, kObjUseWith }, // Oil Lamp
+ { 15, 24, 868, 456, 35, 46, 13, kObjUseWith }, // Magnetic Key
+ { 16, 0, 480, 480, 0, 17, 7, kObjUseWith }, // Plaster
+ { 17, 249, 320, 476, 45, 18, 44, 0 }, // Trophy
+ { 18, 0, 480, 480, 0, 19, 20, 0 }, // Coins
+ { 19, 19, 600, 480, 0, 20, 8, 0 }, // Lens Fragments
+ { 20, 0, 1012, 568, 80, 44, 10, kObjUseWith }, // Key to jail cell
+ { 21, 0, 480, 480, 0, 22, 9, 0 }, // Remade lens
+ { 22, 0, 480, 480, 0, 23, 21, 0 }, // Tycho's Map
+ { 23, 0, 480, 480, 0, 24, 23, 0 }, // Silver Medallion
+ { 24, 0, 480, 480, 0, 25, 24, 0 }, // Mud in Fur
+ { 25, 0, 480, 480, 0, 26, 25, 0 }, // Gold Ring
+ { 27, 13, 1036, 572, 40, 47, 14, kObjUseWith }, // Screwdriver
+ { 28, 0, 480, 480, 0, 29, 26, 0 }, // Apple Token
+ { 29, 0, 480, 480, 0, 30, 22, kObjUseWith }, // Letter from Elara
+ { 30, 0, 164, 440, 0, 31, 16, kObjUseWith }, // Spoon
+ { 32, 0, 480, 480, 0, 33, 43, 0 }, // Catnip
+ { 33, 31, 580, 392, 0, 45, 11, 0 }, // Twigs
+ { 35, 0, 468, 480, 0, 36, 12, kObjUseWith }, // Empty Bowl (also bowl of honey)
+ { 37, 0, 480, 480, 0, 38, 45, kObjUseWith }, // Needle and Thread
+ { 38, 25, 332, 328, 0, 48, 19, 0 }, // Rock Crystal
+ { 39, 0, 480, 480, 0, 40, 0, kObjUseWith }, // Salve
+ { 40, 269, 644, 416, 0, 41, 39, kObjNotFlat }, // Electrical Cable
+ { 41, 12, 280, 516, 0, 43, 17, kObjUseWith }, // Piece of flint
+ { 42, 5, 876, 332, 32, 65, 18, 0 }, // Rat Cloak
+ { 43, 52, 556, 1612, 0, 49, 28, kObjUseWith | kObjNotFlat }, // Bucket
+ { 48, 52, 732, 948, 0, 50, 27, kObjUseWith }, // Cup
+ { 49, 52, 520, 1872, 0, 53, 29, 0 }, // Fertilizer
+ { 50, 52, 1012, 1268, 0, 52, 30, 0 }, // Feeder
+ { 51, 252, -20, -20, 0, 71, 32, kObjUseWith | kObjNotFlat }, // Bowl in jail cell
+ { 53, 252, 1148, 388, 0, 70, 33, 0 }, // Loose stone block in jail cell
+ { 26, 12, 496, 368, 0, 76, 31, 0 }, // Coil of Rope from Quarry
+ { 54, 281, 620, 352, 0, 80, 46, 0 } // Orb of Storms in Dam Lab
+};
+
+FxTable ITE_SfxTable[ITE_SFXCOUNT] = {
+ { FX_DOOR_OPEN, 127 },
+ { FX_DOOR_CLOSE, 127 },
+ { FX_RUSH_WATER, 63 }, // Floppy volume: 127
+ { FX_RUSH_WATER, 26 }, // Floppy volume: 40
+ { FX_CRICKET, 64 },
+ { FX_PORTICULLIS, 84 }, // Floppy volume: 127
+ { FX_CLOCK_1, 64 },
+ { FX_CLOCK_2, 64 },
+ { FX_DAM_MACHINE, 64 },
+ { FX_DAM_MACHINE, 40 },
+ { FX_HUM1, 64 },
+ { FX_HUM2, 64 },
+ { FX_HUM3, 64 },
+ { FX_HUM4, 64 },
+ { FX_WATER_LOOP_S, 32 }, // Floppy volume: 64
+ { FX_SURF, 42 }, // Floppy volume: 127
+ { FX_SURF, 32 }, // Floppy volume: 64
+ { FX_FIRELOOP, 64 }, // Floppy volume: 96
+ { FX_SCRAPING, 84 }, // Floppy volume: 127
+ { FX_BEE_SWARM, 64 }, // Floppy volume: 96
+ { FX_BEE_SWARM, 26 }, // Floppy volume: 40
+ { FX_SQUEAKBOARD, 64 },
+ { FX_KNOCK, 127 },
+ { FX_COINS, 32 }, // Floppy volume: 48
+ { FX_STORM, 84 }, // Floppy volume: 127
+ { FX_DOOR_CLOSE_2, 84 }, // Floppy volume: 127
+ { FX_ARCWELD, 84 }, // Floppy volume: 127
+ { FX_RETRACT_ORB, 127 },
+ { FX_DRAGON, 127 },
+ { FX_SNORES, 127 },
+ { FX_SPLASH, 127 },
+ { FX_LOBBY_DOOR, 127 },
+ { FX_CHIRP_LOOP, 26 }, // Floppy volume: 40
+ { FX_DOOR_CREAK, 96 },
+ { FX_SPOON_DIG, 64 },
+ { FX_CROW, 96 },
+ { FX_COLDWIND, 42 }, // Floppy volume: 64
+ { FX_TOOL_SND_1, 96 },
+ { FX_TOOL_SND_2, 127 },
+ { FX_TOOL_SND_3, 64 },
+ { FX_DOOR_METAL, 96 },
+ { FX_WATER_LOOP_S, 32 },
+ { FX_WATER_LOOP_L, 32 }, // Floppy volume: 64
+ { FX_DOOR_OPEN_2, 127 },
+ { FX_JAIL_DOOR, 64 },
+ { FX_KILN_FIRE, 53 }, // Floppy volume: 80
+
+ // Only in the CD version
+ { FX_CROWD_01, 64 },
+ { FX_CROWD_02, 64 },
+ { FX_CROWD_03, 64 },
+ { FX_CROWD_04, 64 },
+ { FX_CROWD_05, 64 },
+ { FX_CROWD_06, 64 },
+ { FX_CROWD_07, 64 },
+ { FX_CROWD_08, 64 },
+ { FX_CROWD_09, 64 },
+ { FX_CROWD_10, 64 },
+ { FX_CROWD_11, 64 },
+ { FX_CROWD_12, 64 },
+ { FX_CROWD_13, 64 },
+ { FX_CROWD_14, 64 },
+ { FX_CROWD_15, 64 },
+ { FX_CROWD_16, 64 },
+ { FX_CROWD_17, 64 }
+};
+
+const char *ITEinterfaceTextStrings[][52] = {
+ {
+ "Walk to", "Look At", "Pick Up", "Talk to", "Open",
+ "Close", "Use", "Give", "Options", "Test",
+ "Demo", "Help", "Quit Game", "Fast", "Slow",
+ "On", "Off", "Continue Playing", "Load", "Save",
+ "Game Options", "Reading Speed", "Music", "Sound", "Cancel",
+ "Quit", "OK", "Mid", "Click", "10%",
+ "20%", "30%", "40%", "50%", "60%",
+ "70%", "80%", "90%", "Max", "Quit the Game?",
+ "Load Successful!", "Enter Save Game Name", "Give %s to %s", "Use %s with %s",
+ "[New Save Game]",
+ "I can't pick that up.",
+ "I see nothing special about it.",
+ "There's no place to open it.",
+ "There's no opening to close.",
+ "I don't know how to do that.",
+ "Show Dialog",
+ "What is Rif's reply?"
+ },
+ // German
+ {
+ "Gehe zu", "Schau an", "Nimm", "Rede mit", "\231ffne",
+ "Schlie$e", "Benutze", "Gib", "Optionen", "Test",
+ "Demo", "Hilfe", "Spiel beenden", "S", "L",
+ "An", "Aus", "Weiterspielen", "Laden", "Sichern",
+ "Spieleoptionen", "Lesegeschw.", "Musik", "Sound", "Abbr.",
+ "Beenden", NULL, "M", "Klick", "10%",
+ "20%", "30%", "40%", "50%", "60%",
+ "70%", "80%", "90%", "Max", "Spiel beenden?",
+ "Spielstand geladen!", "Bitte Namen eingeben", "Gib %s zu %s", "Benutze %s mit %s",
+ "[Neuer Spielstand]",
+ "Das kann ich nicht aufnehmen.",
+ "Ich sehe nichts besonderes.",
+ "Das kann man nicht \224ffnen.",
+ "Hier ist keine \231ffnung zum Schlie$en.",
+ "Ich wei$ nicht, wie ich das machen soll.",
+ "Text zeigen",
+ "Wie lautet die Antwort?"
+ }
+};
+
+Point pieceOrigins[PUZZLE_PIECES] = {
+ Point(268, 18),
+ Point(270, 51),
+ Point( 19, 51),
+ Point( 73, 0),
+ Point( 0, 34),
+ Point(215, 0),
+ Point(159, 0),
+ Point( 9, 69),
+ Point(288, 18),
+ Point(112, 0),
+ Point( 27, 88),
+ Point( 43, 0),
+ Point( 0, 0),
+ Point(262, 0),
+ Point(271, 103)
+};
+
+const char *pieceNames[][PUZZLE_PIECES] = {
+ { "screwdriver", "pliers", "c-clamp", "wood clamp", "level",
+ "twine", "wood plane", "claw hammer", "tape measure", "hatchet",
+ "shears", "ruler", "saw", "mallet", "paint brush"
+ },
+ { "Schraubendreher", "Zange", "Schraubzwinge", "Holzzwinge", "Wasserwaage",
+ "Bindfaden", "Hobel", "Schusterhammer", "Bandma$", "Beil",
+ "Schere", "Winkel", "S\204ge", "Hammer", "Pinsel"
+ }
+};
+
+const char *hintStr[][4] = {
+ { "Check which pieces could fit in each corner first.",
+ "Check which corner has the least number of pieces that can fit and start from there.",
+ "Check each new corner and any new side for pieces that fit.",
+ "I don't see anything out of place."
+ },
+ { "\232berpr\201fe zun\204chst, welche die Eckteile sein k\224nnten.",
+ "Schau, in welche Ecke die wenigsten Teile passen, und fang dort an.",
+ "Untersuche jede Ecke und jede Seite auf Teile, die dort passen k\224nnen.",
+ "Ich sehe nichts an der falschen Stelle."
+ }
+};
+
+const char *solicitStr[][NUM_SOLICIT_REPLIES] = {
+ { "Hey, Fox! Would you like a hint?",
+ "Would you like some help?",
+ "Umm...Umm...",
+ "Psst! want a hint?",
+ "I would have done this differently, you know."
+ },
+ { "Hey, Fuchs! Brauchst Du \047nen Tip?",
+ "M\224chtest Du etwas Hilfe?"
+ "\231hm...\216hm..."
+ "Psst! \047n Tip vielleicht?"
+ "Ja, wei$t Du... ich h\204tte das anders gemacht."
+ }
+};
+
+const char portraitList[] = {
+ RID_ITE_JFERRET_SERIOUS,
+ RID_ITE_JFERRET_GOOFY,
+ RID_ITE_JFERRET_SERIOUS,
+ RID_ITE_JFERRET_GOOFY,
+ RID_ITE_JFERRET_ALOOF
+};
+
+const char *sakkaStr[][NUM_SAKKA] = {
+ { "Hey, you're not supposed to help the applicants!",
+ "Guys! This is supposed to be a test!",
+ "C'mon fellows, that's not in the rules!"
+ },
+ { "Hey, Du darfst dem Pr\201fling nicht helfen!",
+ "Hallo?! Dies soll eine Pr\201fung sein!",
+ "Also, Jungs. Schummeln steht nicht in den Regeln!"
+ }
+};
+
+const char *whineStr[][NUM_WHINES] = {
+ { "Aww, c'mon Sakka!",
+ "One hint won't hurt, will it?",
+ "Sigh...",
+ "I think that clipboard has gone to your head, Sakka!",
+ "Well, I don't recall any specific rule against hinting."
+ },
+ { "Och, sei nicht so, Sakka!"
+ "EIN Tip wird schon nicht schaden, oder?",
+ "Seufz..."
+ "Ich glaube, Du hast ein Brett vor dem Kopf, Sakka!",
+ "Hm, ich kann mich an keine Regel erinnern, die Tips verbietet."
+ }
+};
+
+const char *optionsStr[][4] = {
+ { "\"I'll do this puzzle later.\"",
+ "\"Yes, I'd like a hint please.\"",
+ "\"No, thank you, I'd like to try and solve it myself.\"",
+ "I think the %s is in the wrong place."
+ },
+ { "\"Ich l\224se das Puzzle sp\204ter.\"",
+ "\"Ja, ich m\224chte einen Tip, bitte.\"",
+ "\"Nein danke, ich m\224chte das alleine l\224sen.\"",
+ "Pssst... %s... falsche Stelle..."
+ }
+};
+
+} // End of namespace Saga
diff --git a/engines/saga/itedata.h b/engines/saga/itedata.h
new file mode 100644
index 0000000000..45d045356d
--- /dev/null
+++ b/engines/saga/itedata.h
@@ -0,0 +1,113 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Actor data table header file
+
+#ifndef SAGA_ITEDATA_H
+#define SAGA_ITEDATA_H
+
+namespace Saga {
+
+enum ActorFlags {
+ kProtagonist = 0x01, // Actor is protagonist
+ kFollower = 0x02, // Actor is follower
+ kCycle = 0x04, // Actor stand has a cycle
+ kFaster = 0x08, // Actor is fast
+ kFastest = 0x10, // Actor is faster
+ kExtended = 0x20 // Actor uses extended sprites
+};
+
+// TODO: This doesn't quite correspond to the original Actor struct, so I'm not
+// sure if I got it right.
+
+struct ActorTableData {
+ byte flags;
+ byte nameIndex;
+ int32 sceneIndex;
+ int16 x;
+ int16 y;
+ int16 z;
+ int32 spriteListResourceId;
+ int32 frameListResourceId;
+ byte scriptEntrypointNumber;
+ byte speechColor;
+ byte currentAction;
+ byte facingDirection;
+ byte actionDirection;
+};
+
+#define ITE_ACTORCOUNT 181
+
+extern ActorTableData ITE_ActorTable[ITE_ACTORCOUNT];
+
+enum {
+ kObjUseWith = 0x01,
+ kObjNotFlat = 0x02
+};
+
+struct ObjectTableData {
+ byte nameIndex;
+ int32 sceneIndex;
+ int16 x;
+ int16 y;
+ int16 z;
+ int32 spriteListResourceId;
+ byte scriptEntrypointNumber;
+ uint16 interactBits;
+};
+
+struct FxTable {
+ int res;
+ int vol;
+};
+
+#define ITE_OBJECTCOUNT 39
+#define ITE_SFXCOUNT 63
+
+extern ObjectTableData ITE_ObjectTable[ITE_OBJECTCOUNT];
+extern FxTable ITE_SfxTable[ITE_SFXCOUNT];
+
+extern const char *ITEinterfaceTextStrings[][52];
+
+#define PUZZLE_PIECES 15
+
+extern Point pieceOrigins[PUZZLE_PIECES];
+extern const char *pieceNames[][PUZZLE_PIECES];
+
+#define NUM_SOLICIT_REPLIES 5
+extern const char *solicitStr[][NUM_SOLICIT_REPLIES];
+
+#define NUM_SAKKA 3
+extern const char *sakkaStr[][NUM_SAKKA];
+
+#define NUM_WHINES 5
+extern const char *whineStr[][NUM_WHINES];
+
+extern const char *hintStr[][4];
+extern const char portraitList[];
+extern const char *optionsStr[][4];
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/list.h b/engines/saga/list.h
new file mode 100644
index 0000000000..d002f405de
--- /dev/null
+++ b/engines/saga/list.h
@@ -0,0 +1,156 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * 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 SAGA_LIST_H__
+#define SAGA_LIST_H__
+
+#include "common/list.h"
+
+namespace Saga {
+
+template <class T>
+class SortedList : public Common::List<T> {
+public:
+ typedef int (*CompareFunction) (const T& a, const T& b);
+
+ typedef typename Common::List<T>::iterator iterator;
+ typedef typename Common::List<T>::const_iterator const_iterator;
+ typedef typename Common::List<T> Common_List;
+
+public:
+
+ iterator pushFront(const T& element) {
+ return insert(Common::List<T>::begin(), element);
+ }
+
+ iterator pushBack(const T& element) {
+ return insert(Common_List::end(), element);
+ }
+
+ iterator insert(iterator pos, const T& element) {
+ Common_List::insert(pos, element);
+ return --pos;
+ }
+
+ iterator pushFront() {
+ return insert(Common_List::begin());
+ }
+
+ iterator pushBack() {
+ return insert(Common_List::end());
+ }
+
+ iterator insert(iterator pos) {
+ T init;
+ return insert(pos, init);
+ }
+
+ iterator pushFront(const T& element, CompareFunction compareFunction) {
+ return insert(Common::List<T>::begin(), element, compareFunction);
+ }
+
+ iterator pushBack(const T& element, CompareFunction compareFunction) {
+ return insert(Common_List::end(), element, compareFunction);
+ }
+
+ iterator insert(iterator pos, const T& element, CompareFunction compareFunction) {
+ int res;
+
+ for (iterator i = Common_List::begin(); i != Common_List::end(); ++i) {
+ res = compareFunction(element, i.operator*());
+ if (res < 0) {
+ return insert(i, element);
+ }
+ }
+ return pushBack(element);
+ }
+
+ iterator reorderUp(iterator pos, CompareFunction compareFunction) {
+ iterator i(pos);
+ int res;
+
+ --i;
+ while (i != Common::List<T>::end()) {
+ res = compareFunction(i.operator*(), pos.operator*());
+ if (res <= 0) {
+
+ T temp(*pos);
+ erase(pos);
+ ++i;
+ return insert(i, temp);
+ }
+ --i;
+ }
+ return pos;
+ }
+
+ iterator reorderDown(iterator pos, CompareFunction compareFunction) {
+ iterator i(pos);
+ int res;
+
+ ++i;
+ while (i != Common::List<T>::end()) {
+ res = compareFunction(i.operator*(), pos.operator*());
+ if (res >= 0) {
+
+ T temp(*pos);
+ erase(pos);
+ return insert(i, temp);
+ }
+ ++i;
+ }
+ return pos;
+ }
+
+ iterator eraseAndPrev(iterator pos) {
+ assert(pos != Common_List::end());
+ iterator res(pos);
+
+ --res;
+ erase(pos);
+ return res;
+ }
+
+ void remove(const T* val) {
+ for (iterator i = Common_List::begin(); i != Common_List::end(); ++i)
+ if (val == i.operator->()) {
+ erase(i);
+ return;
+ }
+ }
+
+ bool locate(const T* val, iterator& foundedIterator) {
+
+ for (iterator i = Common::List<T>::begin(); i != Common::List<T>::end(); ++i)
+ if (val == i.operator->())
+ {
+ foundedIterator = i;
+ return true;
+ }
+
+ return false;
+ }
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/module.mk b/engines/saga/module.mk
new file mode 100644
index 0000000000..3d39108444
--- /dev/null
+++ b/engines/saga/module.mk
@@ -0,0 +1,44 @@
+MODULE := engines/saga
+
+MODULE_OBJS := \
+ engines/saga/actor.o \
+ engines/saga/animation.o \
+ engines/saga/console.o \
+ engines/saga/events.o \
+ engines/saga/font.o \
+ engines/saga/font_map.o \
+ engines/saga/game.o \
+ engines/saga/gfx.o \
+ engines/saga/ihnm_introproc.o \
+ engines/saga/image.o \
+ engines/saga/interface.o \
+ engines/saga/isomap.o \
+ engines/saga/ite_introproc.o \
+ engines/saga/itedata.o \
+ engines/saga/objectmap.o \
+ engines/saga/puzzle.o \
+ engines/saga/palanim.o \
+ engines/saga/render.o \
+ engines/saga/rscfile.o \
+ engines/saga/saga.o \
+ engines/saga/saveload.o \
+ engines/saga/scene.o \
+ engines/saga/script.o \
+ engines/saga/sfuncs.o \
+ engines/saga/sndres.o \
+ engines/saga/sprite.o \
+ engines/saga/sthread.o \
+ engines/saga/input.o \
+ engines/saga/music.o \
+ engines/saga/sound.o
+
+MODULE_DIRS += \
+ engines/saga
+
+# This module can be built as a plugin
+ifdef BUILD_PLUGINS
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/common.rules
diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp
new file mode 100644
index 0000000000..e352818ee6
--- /dev/null
+++ b/engines/saga/music.cpp
@@ -0,0 +1,524 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 "saga/saga.h"
+
+#include "saga/rscfile.h"
+#include "saga/music.h"
+#include "saga/stream.h"
+#include "sound/audiostream.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "sound/mp3.h"
+#include "sound/vorbis.h"
+#include "sound/flac.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+
+namespace Saga {
+
+#define BUFFER_SIZE 4096
+
+struct TrackFormat {
+ DigitalTrackInfo* (*openTrackFunction)(int);
+};
+
+static const TrackFormat TRACK_FORMATS[] = {
+#ifdef USE_FLAC
+ { getFlacTrack },
+#endif
+#ifdef USE_VORBIS
+ { getVorbisTrack },
+#endif
+#ifdef USE_MAD
+ { getMP3Track },
+#endif
+
+ { NULL } // Terminator
+};
+
+// I haven't decided yet if it's a good idea to make looping part of the audio
+// stream class, or if I should use a "wrapper" class, like I did for Broken
+// Sword 2, to make it easier to add support for compressed music... but I'll
+// worry about that later.
+
+class RAWInputStream : public AudioStream {
+private:
+ ResourceContext *_context;
+ Common::File *_file;
+ uint32 _filePos;
+ uint32 _startPos;
+ uint32 _endPos;
+ bool _finished;
+ bool _looping;
+ int16 _buf[BUFFER_SIZE];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+ const GameSoundInfo *_musicInfo;
+
+ void refill();
+ bool eosIntern() const {
+ return _pos >= _bufferEnd;
+ }
+
+public:
+ RAWInputStream(SagaEngine *vm, ResourceContext *context, uint32 resourceId, bool looping);
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return eosIntern(); }
+ bool isStereo() const { return _musicInfo->stereo; }
+ int getRate() const { return _musicInfo->frequency; }
+};
+
+RAWInputStream::RAWInputStream(SagaEngine *vm, ResourceContext *context, uint32 resourceId, bool looping)
+ : _context(context), _finished(false), _looping(looping), _bufferEnd(_buf + BUFFER_SIZE) {
+
+ ResourceData * resourceData;
+
+ resourceData = vm->_resource->getResourceData(context, resourceId);
+ _file = context->getFile(resourceData);
+ _musicInfo = vm->getMusicInfo();
+
+ if (_musicInfo == NULL) {
+ error("RAWInputStream() wrong musicInfo");
+ }
+
+ // Determine the end position
+ _startPos = resourceData->offset;
+ _endPos = _startPos + resourceData->size;
+ _filePos = _startPos;
+
+ // Read in initial data
+ refill();
+}
+
+int RAWInputStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ while (samples < numSamples && !eosIntern()) {
+ const int len = MIN(numSamples - samples, (int) (_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ refill();
+ }
+ }
+ return samples;
+}
+
+void RAWInputStream::refill() {
+ if (_finished)
+ return;
+
+ uint32 lengthLeft;
+ byte *ptr = (byte *) _buf;
+
+
+ _file->seek(_filePos, SEEK_SET);
+
+ if (_looping)
+ lengthLeft = 2 * BUFFER_SIZE;
+ else
+ lengthLeft = MIN((uint32) (2 * BUFFER_SIZE), _endPos - _filePos);
+
+ while (lengthLeft > 0) {
+ uint32 len = _file->read(ptr, MIN(lengthLeft, _endPos - _file->pos()));
+
+ if (len & 1)
+ len--;
+
+#ifdef SCUMM_BIG_ENDIAN
+ if (!_context->isBigEndian) {
+#else
+ if (_context->isBigEndian) {
+#endif
+ uint16 *ptr16 = (uint16 *)ptr;
+ for (uint32 i = 0; i < (len / 2); i++)
+ ptr16[i] = SWAP_BYTES_16(ptr16[i]);
+ }
+
+ lengthLeft -= len;
+ ptr += len;
+
+ if (lengthLeft > 0)
+ _file->seek(_startPos);
+ }
+
+ _filePos = _file->pos();
+ _pos = _buf;
+ _bufferEnd = (int16 *)ptr;
+
+ if (!_looping && _filePos >= _endPos) {
+ _finished = true;
+ }
+}
+
+
+MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false), _passThrough(false), _isGM(false) {
+ memset(_channel, 0, sizeof(_channel));
+ _masterVolume = 0;
+ this->open();
+}
+
+MusicPlayer::~MusicPlayer() {
+ _driver->setTimerCallback(NULL, NULL);
+ stopMusic();
+ this->close();
+}
+
+void MusicPlayer::setVolume(int volume) {
+ 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 MusicPlayer::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 MusicPlayer::close() {
+ stopMusic();
+ if (_driver)
+ _driver->close();
+ _driver = 0;
+}
+
+void MusicPlayer::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 MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
+ // FIXME: The "elkfanfare" is played much too quickly. There are some
+ // meta events that we don't handle. Perhaps there is a
+ // connection...?
+
+ switch (type) {
+ case 0x2F: // End of Track
+ if (_looping)
+ _parser->jumpToTick(0);
+ else
+ stopMusic();
+ break;
+ default:
+ //warning("Unhandled meta event: %02x", type);
+ break;
+ }
+}
+
+void MusicPlayer::onTimer(void *refCon) {
+ MusicPlayer *music = (MusicPlayer *)refCon;
+ if (music->_isPlaying)
+ music->_parser->onTimer();
+}
+
+void MusicPlayer::playMusic() {
+ _isPlaying = true;
+}
+
+void MusicPlayer::stopMusic() {
+ _isPlaying = false;
+ if (_parser) {
+ _parser->unloadMusic();
+ _parser = NULL;
+ }
+}
+
+Music::Music(SagaEngine *vm, Audio::Mixer *mixer, MidiDriver *driver, int enabled) : _vm(vm), _mixer(mixer), _enabled(enabled), _adlib(false) {
+ _player = new MusicPlayer(driver);
+ _currentVolume = 0;
+
+ xmidiParser = MidiParser::createParser_XMIDI();
+ smfParser = MidiParser::createParser_SMF();
+
+ _musicContext = _vm->_resource->getContext(GAME_MUSICFILE);
+
+ _songTableLen = 0;
+ _songTable = 0;
+
+ _track = NULL;
+}
+
+Music::~Music() {
+ _mixer->stopHandle(_musicHandle);
+ delete _player;
+ xmidiParser->setMidiDriver(NULL);
+ smfParser->setMidiDriver(NULL);
+ delete xmidiParser;
+ delete smfParser;
+
+ free(_songTable);
+}
+
+void Music::musicVolumeGaugeCallback(void *refCon) {
+ ((Music *)refCon)->musicVolumeGauge();
+}
+
+void Music::musicVolumeGauge() {
+ int volume;
+
+ _currentVolumePercent += 10;
+
+ if (_currentVolume - _targetVolume > 0) { // Volume decrease
+ volume = _targetVolume + (_currentVolume - _targetVolume) * (100 - _currentVolumePercent) / 100;
+ } else {
+ volume = _currentVolume + (_targetVolume - _currentVolume) * _currentVolumePercent / 100;
+ }
+
+ if (volume < 0)
+ volume = 1;
+
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+ _player->setVolume(volume);
+
+ if (_currentVolumePercent == 100) {
+ Common::g_timer->removeTimerProc(&musicVolumeGaugeCallback);
+ _currentVolume = _targetVolume;
+ }
+}
+
+void Music::setVolume(int volume, int time) {
+ _targetVolume = volume * 2; // ScummVM has different volume scale
+ _currentVolumePercent = 0;
+
+ if (volume == -1) // Set Full volume
+ volume = 255;
+
+ if (time == 1) {
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+ _player->setVolume(volume);
+ Common::g_timer->removeTimerProc(&musicVolumeGaugeCallback);
+ _currentVolume = volume;
+ return;
+ }
+
+ Common::g_timer->installTimerProc(&musicVolumeGaugeCallback, time * 100L, this);
+}
+
+bool Music::isPlaying() {
+ return _mixer->isSoundHandleActive(_musicHandle) || _player->isPlaying();
+}
+
+void Music::play(uint32 resourceId, MusicFlags flags) {
+ AudioStream *audioStream = NULL;
+ MidiParser *parser;
+ ResourceContext *context;
+ byte *resourceData;
+ size_t resourceSize;
+
+ debug(2, "Music::play %d, %d", resourceId, flags);
+
+ if (!_enabled) {
+ return;
+ }
+
+ if (isPlaying() && _trackNumber == resourceId) {
+ return;
+ }
+
+ _trackNumber = resourceId;
+ _player->stopMusic();
+ _mixer->stopHandle(_musicHandle);
+
+ int realTrackNumber;
+
+ if (_vm->getGameType() == GType_ITE) {
+ if (flags == MUSIC_DEFAULT) {
+ if (resourceId == 13 || resourceId == 19) {
+ flags = MUSIC_NORMAL;
+ } else {
+ flags = MUSIC_LOOP;
+ }
+ }
+ realTrackNumber = resourceId - 8;
+ } else {
+ realTrackNumber = resourceId + 1;
+ }
+
+ // Try to open standalone digital track
+ for (int i = 0; i < ARRAYSIZE(TRACK_FORMATS) - 1; ++i)
+ if ((_track = TRACK_FORMATS[i].openTrackFunction(realTrackNumber))) {
+ break;
+ }
+ if (_track) {
+ _track->play(_mixer, &_musicHandle, (flags == MUSIC_LOOP) ? -1 : 1, 10000);
+ return;
+ }
+
+ if (_vm->getGameType() == GType_ITE) {
+ if (resourceId >= 9 && resourceId <= 34) {
+ if (_musicContext != NULL) {
+ //TODO: check resource size
+ audioStream = new RAWInputStream(_vm, _musicContext, resourceId - 9, flags == MUSIC_LOOP);
+ }
+ }
+ }
+
+ if (audioStream) {
+ debug(2, "Playing digitized music");
+ _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_musicHandle, audioStream);
+ return;
+ }
+
+ if (flags == MUSIC_DEFAULT) {
+ flags = MUSIC_NORMAL;
+ }
+
+ // FIXME: Is resource_data ever freed?
+ // Load MIDI/XMI resource data
+
+ if (_vm->getGameType() == GType_ITE) {
+ context = _vm->_resource->getContext(GAME_MUSICFILE_GM);
+ if (context == NULL) {
+ context = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ }
+ } else {
+ // I've listened to music from both the FM and the GM
+ // file, and I've tentatively reached the conclusion
+ // that they are both General MIDI. My guess is that
+ // the FM file has been reorchestrated to sound better
+ // on Adlib and other FM synths.
+ //
+ // Sev says the Adlib music does not sound like in the
+ // original, but I still think assuming General MIDI is
+ // the right thing to do. Some music, like the End
+ // Title (song 0) sound absolutely atrocious when piped
+ // through our MT-32 to GM mapping.
+ //
+ // It is, however, quite possible that the original
+ // used a different GM to FM mapping. If the original
+ // sounded markedly better, perhaps we should add some
+ // way of replacing our stock mapping in adlib.cpp?
+ //
+ // For the composer's own recording of the End Title,
+ // see http://www.johnottman.com/
+
+ // Oddly enough, the intro music (song 1) is very
+ // different in the two files. I have no idea why.
+
+ if (hasAdlib()) {
+ context = _vm->_resource->getContext(GAME_MUSICFILE_FM);
+ } else {
+ context = _vm->_resource->getContext(GAME_MUSICFILE_GM);
+ }
+ }
+
+ _player->setGM(true);
+
+ _vm->_resource->loadResource(context, resourceId, resourceData, resourceSize);
+
+ if (resourceSize < 4) {
+ error("Music::play() wrong music resource size");
+ }
+
+ if (xmidiParser->loadMusic(resourceData, resourceSize)) {
+ if (_vm->getGameType() == GType_ITE)
+ _player->setGM(false);
+
+ parser = xmidiParser;
+ } else {
+ if (smfParser->loadMusic(resourceData, resourceSize)) {
+ parser = smfParser;
+ } else {
+ error("Music::play() wrong music resource");
+ }
+ }
+
+ parser->setTrack(0);
+ parser->setMidiDriver(_player);
+ parser->setTimerRate(_player->getBaseTempo());
+ parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+
+ _player->_parser = parser;
+ _player->setVolume(_vm->_musicVolume == 10 ? 255 : _vm->_musicVolume * 25);
+
+ if (flags & MUSIC_LOOP)
+ _player->setLoop(true);
+ else
+ _player->setLoop(false);
+
+ _player->playMusic();
+}
+
+void Music::pause(void) {
+ //TODO: do it
+}
+
+void Music::resume(void) {
+ //TODO: do it}
+}
+
+void Music::stop(void) {
+ //TODO: do it
+}
+
+} // End of namespace Saga
+
diff --git a/engines/saga/music.h b/engines/saga/music.h
new file mode 100644
index 0000000000..377a5bf1d8
--- /dev/null
+++ b/engines/saga/music.h
@@ -0,0 +1,148 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 SAGA_MUSIC_H_
+#define SAGA_MUSIC_H_
+
+#include "sound/audiocd.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+
+namespace Saga {
+
+enum MusicFlags {
+ MUSIC_NORMAL = 0,
+ MUSIC_LOOP = 0x0001,
+ MUSIC_DEFAULT = 0xffff
+};
+
+class MusicPlayer : public MidiDriver {
+public:
+ MusicPlayer(MidiDriver *driver);
+ ~MusicPlayer();
+
+ bool isPlaying() { return _isPlaying; }
+
+ void setVolume(int volume);
+ int getVolume() { return _masterVolume; }
+
+ void setNativeMT32(bool b) { _nativeMT32 = b; }
+ bool hasNativeMT32() { return _nativeMT32; }
+ void playMusic();
+ void stopMusic();
+ void setLoop(bool loop) { _looping = loop; }
+ 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; }
+
+ MidiParser *_parser;
+
+protected:
+
+ static void onTimer(void *data);
+
+ MidiChannel *_channel[16];
+ MidiDriver *_driver;
+ byte _channelVolume[16];
+ bool _nativeMT32;
+ bool _isGM;
+ bool _passThrough;
+
+ bool _isPlaying;
+ bool _looping;
+ bool _randomLoop;
+ byte _masterVolume;
+
+ byte *_musicData;
+ uint16 *_buf;
+ uint32 _musicDataSize;
+};
+
+class Music {
+public:
+
+ Music(SagaEngine *vm, Audio::Mixer *mixer, MidiDriver *driver, int enabled);
+ ~Music(void);
+ void setNativeMT32(bool b) { _player->setNativeMT32(b); }
+ bool hasNativeMT32() { return _player->hasNativeMT32(); }
+ void setAdlib(bool b) { _adlib = b; }
+ bool hasAdlib() { return _adlib; }
+ void setPassThrough(bool b) { _player->setPassThrough(b); }
+ bool isPlaying(void);
+
+ void play(uint32 resourceId, MusicFlags flags = MUSIC_DEFAULT);
+ void pause(void);
+ void resume(void);
+ void stop(void);
+
+ void setVolume(int volume, int time = 1);
+ int getVolume() { return _currentVolume; }
+
+ int32 *_songTable;
+ int _songTableLen;
+
+private:
+ SagaEngine *_vm;
+ Audio::Mixer *_mixer;
+
+ MusicPlayer *_player;
+ Audio::SoundHandle _musicHandle;
+ uint32 _trackNumber;
+
+ int _enabled;
+ bool _adlib;
+
+ int _targetVolume;
+ int _currentVolume;
+ int _currentVolumePercent;
+
+ ResourceContext *_musicContext;
+ MidiParser *xmidiParser;
+ MidiParser *smfParser;
+
+ DigitalTrackInfo *_track;
+
+ static void musicVolumeGaugeCallback(void *refCon);
+ void musicVolumeGauge(void);
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/objectmap.cpp b/engines/saga/objectmap.cpp
new file mode 100644
index 0000000000..d0a302b0d7
--- /dev/null
+++ b/engines/saga/objectmap.cpp
@@ -0,0 +1,267 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Object map / Object click-area module
+
+// Polygon Hit Test code ( HitTestPoly() ) adapted from code (C) Eric Haines
+// appearing in Graphics Gems IV, "Point in Polygon Strategies."
+// p. 24-46, code: p. 34-45
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/console.h"
+#include "saga/font.h"
+#include "saga/interface.h"
+#include "saga/objectmap.h"
+#include "saga/stream.h"
+#include "saga/actor.h"
+#include "saga/scene.h"
+#include "saga/isomap.h"
+
+namespace Saga {
+
+HitZone::HitZone(MemoryReadStreamEndian *readStream, int index): _index(index) {
+ int i, j;
+ HitZone::ClickArea *clickArea;
+ Point *point;
+
+ _flags = readStream->readByte();
+ _clickAreasCount = readStream->readByte();
+ _rightButtonVerb = readStream->readByte();
+ readStream->readByte(); // pad
+ _nameIndex = readStream->readUint16();
+ _scriptNumber = readStream->readUint16();
+
+ _clickAreas = (HitZone::ClickArea *)malloc(_clickAreasCount * sizeof(*_clickAreas));
+
+ if (_clickAreas == NULL) {
+ memoryError("HitZone::HitZone");
+ }
+
+ for (i = 0; i < _clickAreasCount; i++) {
+ clickArea = &_clickAreas[i];
+ clickArea->pointsCount = readStream->readUint16LE();
+
+ assert(clickArea->pointsCount);
+
+ clickArea->points = (Point *)malloc(clickArea->pointsCount * sizeof(*(clickArea->points)));
+ if (clickArea->points == NULL) {
+ memoryError("HitZone::HitZone");
+ }
+
+ for (j = 0; j < clickArea->pointsCount; j++) {
+ point = &clickArea->points[j];
+ point->x = readStream->readSint16();
+ point->y = readStream->readSint16();
+ }
+ }
+}
+
+HitZone::~HitZone() {
+ for (int i = 0; i < _clickAreasCount; i++) {
+ free(_clickAreas[i].points);
+ }
+ free(_clickAreas);
+}
+
+bool HitZone::getSpecialPoint(Point &specialPoint) const {
+ int i, pointsCount;
+ HitZone::ClickArea *clickArea;
+ Point *points;
+
+ for (i = 0; i < _clickAreasCount; i++) {
+ clickArea = &_clickAreas[i];
+ pointsCount = clickArea->pointsCount;
+ points = clickArea->points;
+ if (pointsCount == 1) {
+ specialPoint = points[0];
+ return true;
+ }
+ }
+ return false;
+}
+bool HitZone::hitTest(const Point &testPoint) {
+ int i, pointsCount;
+ HitZone::ClickArea *clickArea;
+ Point *points;
+
+ if (_flags & kHitZoneEnabled) {
+ for (i = 0; i < _clickAreasCount; i++) {
+ clickArea = &_clickAreas[i];
+ pointsCount = clickArea->pointsCount;
+ points = clickArea->points;
+
+ if (pointsCount == 2) {
+ // Hit-test a box region
+ if ((testPoint.x >= points[0].x) &&
+ (testPoint.x <= points[1].x) &&
+ (testPoint.y >= points[0].y) &&
+ (testPoint.y <= points[1].y)) {
+ return true;
+ }
+ } else {
+ if (pointsCount > 2) {
+ // Hit-test a polygon
+ if (hitTestPoly(points, pointsCount, testPoint)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+void HitZone::draw(SagaEngine *vm, Surface *ds, int color) {
+ int i, pointsCount, j;
+ Location location;
+ HitZone::ClickArea *clickArea;
+ Point *points;
+ for (i = 0; i < _clickAreasCount; i++) {
+ clickArea = &_clickAreas[i];
+ pointsCount = clickArea->pointsCount;
+ if (vm->_scene->getFlags() & kSceneFlagISO) {
+ points = (Point*)malloc(sizeof(Point) * pointsCount);
+ for (j = 0; j < pointsCount; j++) {
+ location.u() = clickArea->points[j].x;
+ location.v() = clickArea->points[j].y;
+ location.z = 0;
+ vm->_isoMap->tileCoordsToScreenPoint(location, points[j]);
+ }
+ } else {
+ points = clickArea->points;
+ }
+
+ if (pointsCount == 2) {
+ // 2 points represent a box
+ ds->drawFrame(points[0], points[1], color);
+ } else {
+ if (pointsCount > 2) {
+ // Otherwise draw a polyline
+ ds->drawPolyLine(points, pointsCount, color);
+ }
+ }
+ if (vm->_scene->getFlags() & kSceneFlagISO) {
+ free(points);
+ }
+
+ }
+}
+
+
+// Loads an object map resource ( objects ( clickareas ( points ) ) )
+void ObjectMap::load(const byte *resourcePointer, size_t resourceLength) {
+ int i;
+
+ if (resourceLength == 0) {
+ return;
+ }
+
+ if (resourceLength < 4) {
+ error("ObjectMap::load wrong resourceLength");
+ }
+
+ MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian());
+
+ _hitZoneListCount = readS.readSint16();
+ if (_hitZoneListCount < 0) {
+ error("ObjectMap::load _hitZoneListCount < 0");
+ }
+
+ if (_hitZoneList)
+ error("ObjectMap::load _hitZoneList != NULL");
+
+ _hitZoneList = (HitZone **) malloc(_hitZoneListCount * sizeof(HitZone *));
+ if (_hitZoneList == NULL) {
+ memoryError("ObjectMap::load");
+ }
+
+ for (i = 0; i < _hitZoneListCount; i++) {
+ _hitZoneList[i] = new HitZone(&readS, i);
+ }
+}
+
+void ObjectMap::freeMem() {
+ int i;
+
+ if (_hitZoneList) {
+ for (i = 0; i < _hitZoneListCount; i++) {
+ delete _hitZoneList[i];
+ }
+
+ free(_hitZoneList);
+ _hitZoneList = NULL;
+ }
+ _hitZoneListCount = 0;
+}
+
+
+
+void ObjectMap::draw(Surface *ds, const Point& testPoint, int color, int color2) {
+ int i;
+ int hitZoneIndex;
+ char txtBuf[32];
+ Point pickPoint;
+ Point textPoint;
+ Location pickLocation;
+ pickPoint = testPoint;
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ assert(_vm->_actor->_protagonist);
+ pickPoint.y -= _vm->_actor->_protagonist->_location.z;
+ _vm->_isoMap->screenPointToTileCoords(pickPoint, pickLocation);
+ pickLocation.toScreenPointUV(pickPoint);
+ }
+
+ hitZoneIndex = hitTest(pickPoint);
+
+ for (i = 0; i < _hitZoneListCount; i++) {
+ _hitZoneList[i]->draw(_vm, ds, (hitZoneIndex == i) ? color2 : color);
+ }
+
+ if (hitZoneIndex != -1) {
+ snprintf(txtBuf, sizeof(txtBuf), "hitZone %d", hitZoneIndex);
+ textPoint.x = 2;
+ textPoint.y = 2;
+ _vm->_font->textDraw(kKnownFontSmall, ds, txtBuf, textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline);
+ }
+}
+
+int ObjectMap::hitTest(const Point& testPoint) {
+ int i;
+
+ // Loop through all scene objects
+ for (i = 0; i < _hitZoneListCount; i++) {
+ if (_hitZoneList[i]->hitTest(testPoint)) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+void ObjectMap::cmdInfo(void) {
+ _vm->_console->DebugPrintf("%d zone(s) loaded.\n\n", _hitZoneListCount);
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/objectmap.h b/engines/saga/objectmap.h
new file mode 100644
index 0000000000..43356d3579
--- /dev/null
+++ b/engines/saga/objectmap.h
@@ -0,0 +1,128 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Object map / Object click-area module header file
+
+#ifndef SAGA_OBJECTMAP_H_
+#define SAGA_OBJECTMAP_H_
+
+#include "saga/stream.h"
+
+namespace Saga {
+
+
+class HitZone {
+private:
+ struct ClickArea {
+ int pointsCount;
+ Point *points;
+ };
+
+public:
+ HitZone(MemoryReadStreamEndian *readStream, int index);
+ ~HitZone();
+
+ int getNameIndex() const {
+ return _nameIndex;
+ }
+ int getSceneNumber() const {
+ return _nameIndex;
+ }
+ int getActorsEntrance() const {
+ return _scriptNumber;
+ }
+ int getScriptNumber() const {
+ return _scriptNumber;
+ }
+ int getRightButtonVerb() const {
+ return _rightButtonVerb;
+ }
+ int getFlags() const {
+ return _flags;
+ }
+ void setFlag(HitZoneFlags flag) {
+ _flags |= flag;
+ }
+ void clearFlag(HitZoneFlags flag) {
+ _flags &= ~flag;
+ }
+ int getDirection() const {
+ return ((_flags >> 4) & 0xF);
+ }
+ uint16 getHitZoneId() const {
+ return objectIndexToId(kGameObjectHitZone, _index);
+ }
+ uint16 getStepZoneId() const {
+ return objectIndexToId(kGameObjectStepZone, _index);
+ }
+ bool getSpecialPoint(Point &specialPoint) const;
+ void draw(SagaEngine *vm, Surface *ds, int color);
+ bool hitTest(const Point &testPoint);
+
+private:
+ int _flags; // Saga::HitZoneFlags
+ int _clickAreasCount;
+ int _rightButtonVerb;
+ int _nameIndex;
+ int _scriptNumber;
+ int _index;
+
+ ClickArea *_clickAreas;
+};
+
+
+class ObjectMap {
+public:
+ ObjectMap(SagaEngine *vm) : _vm(vm) {
+ _hitZoneList = NULL;
+ _hitZoneListCount = 0;
+
+ }
+ ~ObjectMap(void) {
+ freeMem();
+ }
+ void load(const byte *resourcePointer, size_t resourceLength);
+ void freeMem(void);
+
+ void draw(Surface *drawSurface, const Point& testPoint, int color, int color2);
+ int hitTest(const Point& testPoint);
+ HitZone *getHitZone(int16 index) {
+ if ((index < 0) || (index >= _hitZoneListCount)) {
+ error("ObjectMap::getHitZone wrong index 0x%X", index);
+ }
+ return _hitZoneList[index];
+ }
+
+ void cmdInfo(void);
+
+private:
+ SagaEngine *_vm;
+
+ int _hitZoneListCount;
+ HitZone **_hitZoneList;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/palanim.cpp b/engines/saga/palanim.cpp
new file mode 100644
index 0000000000..4cfaf5e0fd
--- /dev/null
+++ b/engines/saga/palanim.cpp
@@ -0,0 +1,209 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Palette animation module
+#include "saga/saga.h"
+#include "saga/gfx.h"
+
+#include "saga/events.h"
+
+#include "saga/palanim.h"
+#include "saga/stream.h"
+
+namespace Saga {
+
+PalAnim::PalAnim(SagaEngine *vm) : _vm(vm) {
+ _loaded = false;
+ _entryCount = 0;
+ _entries = NULL;
+}
+
+PalAnim::~PalAnim(void) {
+}
+
+int PalAnim::loadPalAnim(const byte *resdata, size_t resdata_len) {
+ void *test_p;
+
+ uint16 i;
+
+ if (_loaded) {
+ freePalAnim();
+ }
+
+ if (resdata == NULL) {
+ return FAILURE;
+ }
+
+ MemoryReadStreamEndian readS(resdata, resdata_len, _vm->isBigEndian());
+
+ if (_vm->getGameType() == GType_IHNM) {
+ return SUCCESS;
+ }
+
+ _entryCount = readS.readUint16();
+
+ debug(3, "PalAnim::loadPalAnim(): Loading %d PALANIM entries.", _entryCount);
+
+ test_p = calloc(_entryCount, sizeof(PalanimEntry));
+ if (test_p == NULL) {
+ warning("PalAnim::loadPalAnim(): Allocation failure");
+ return MEM;
+ }
+
+ _entries = (PalanimEntry *)test_p;
+
+ for (i = 0; i < _entryCount; i++) {
+ int color_count;
+ int pal_count;
+ int p, c;
+
+ color_count = readS.readUint16();
+ pal_count = readS.readUint16();
+
+ _entries[i].pal_count = pal_count;
+ _entries[i].color_count = color_count;
+
+ debug(2, "PalAnim::loadPalAnim(): Entry %d: Loading %d palette indices.\n", i, pal_count);
+
+ test_p = calloc(1, sizeof(char) * pal_count);
+ if (test_p == NULL) {
+ warning("PalAnim::loadPalAnim(): Allocation failure");
+ return MEM;
+ }
+
+ _entries[i].pal_index = (byte *)test_p;
+
+ debug(2, "PalAnim::loadPalAnim(): Entry %d: Loading %d SAGA_COLOR structures.", i, color_count);
+
+ test_p = calloc(1, sizeof(Color) * color_count);
+ if (test_p == NULL) {
+ warning("PalAnim::loadPalAnim(): Allocation failure");
+ return MEM;
+ }
+
+ _entries[i].colors = (Color *)test_p;
+
+ for (p = 0; p < pal_count; p++) {
+ _entries[i].pal_index[p] = readS.readByte();
+ }
+
+ for (c = 0; c < color_count; c++) {
+ _entries[i].colors[c].red = readS.readByte();
+ _entries[i].colors[c].green = readS.readByte();
+ _entries[i].colors[c].blue = readS.readByte();
+ }
+ }
+
+ _loaded = true;
+ return SUCCESS;
+}
+
+int PalAnim::cycleStart() {
+ Event event;
+
+ if (!_loaded) {
+ return FAILURE;
+ }
+
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStep;
+ event.time = PALANIM_CYCLETIME;
+
+ _vm->_events->queue(&event);
+
+ return SUCCESS;
+}
+
+int PalAnim::cycleStep(int vectortime) {
+ static PalEntry pal[256];
+ uint16 pal_index;
+ uint16 col_index;
+
+ uint16 i, j;
+ uint16 cycle;
+ uint16 cycle_limit;
+
+ Event event;
+
+ if (!_loaded) {
+ return FAILURE;
+ }
+
+ _vm->_gfx->getCurrentPal(pal);
+
+ for (i = 0; i < _entryCount; i++) {
+ cycle = _entries[i].cycle;
+ cycle_limit = _entries[i].color_count;
+ for (j = 0; j < _entries[i].pal_count; j++) {
+ pal_index = (unsigned char)_entries[i].pal_index[j];
+ col_index = (cycle + j) % cycle_limit;
+ pal[pal_index].red = (byte) _entries[i].colors[col_index].red;
+ pal[pal_index].green = (byte) _entries[i].colors[col_index].green;
+ pal[pal_index].blue = (byte) _entries[i].colors[col_index].blue;
+ }
+
+ _entries[i].cycle++;
+
+ if (_entries[i].cycle == cycle_limit) {
+ _entries[i].cycle = 0;
+ }
+ }
+
+ _vm->_gfx->setPalette(pal);
+
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStep;
+ event.time = vectortime + PALANIM_CYCLETIME;
+
+ _vm->_events->queue(&event);
+
+ return SUCCESS;
+}
+
+int PalAnim::freePalAnim() {
+ uint16 i;
+
+ if (!_loaded) {
+ return FAILURE;
+ }
+
+ for (i = 0; i < _entryCount; i++) {
+ debug(2, "PalAnim::freePalAnim(): Entry %d: Freeing colors.", i);
+ free(_entries[i].colors);
+ debug(2, "PalAnim::freePalAnim(): Entry %d: Freeing indices.", i);
+ free(_entries[i].pal_index);
+ }
+
+ debug(3, "PalAnim::freePalAnim(): Freeing entries.");
+
+ free(_entries);
+
+ _loaded = false;
+
+ return SUCCESS;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/palanim.h b/engines/saga/palanim.h
new file mode 100644
index 0000000000..e2ca695eb6
--- /dev/null
+++ b/engines/saga/palanim.h
@@ -0,0 +1,63 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Palette animation module header file
+
+#ifndef SAGA_PALANIM_H
+#define SAGA_PALANIM_H
+
+namespace Saga {
+
+#define PALANIM_CYCLETIME 100
+
+struct PalanimEntry {
+ uint16 pal_count;
+ uint16 color_count;
+ uint16 cycle;
+ byte *pal_index;
+ Color *colors;
+};
+
+class PalAnim {
+ public:
+ PalAnim(SagaEngine *vm);
+ ~PalAnim(void);
+
+ int loadPalAnim(const byte *, size_t);
+ int cycleStart();
+ int cycleStep(int vectortime);
+ int freePalAnim();
+
+ private:
+ SagaEngine *_vm;
+
+ bool _loaded;
+ uint16 _entryCount;
+ PalanimEntry *_entries;
+};
+
+} // End of namespace Saga
+
+#endif
+
diff --git a/engines/saga/puzzle.cpp b/engines/saga/puzzle.cpp
new file mode 100644
index 0000000000..c09a9c0462
--- /dev/null
+++ b/engines/saga/puzzle.cpp
@@ -0,0 +1,560 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2005-2006 The ScummVM project
+ *
+ * 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 "saga/saga.h"
+
+#include "saga/actor.h"
+#include "saga/interface.h"
+#include "saga/scene.h"
+#include "saga/sprite.h"
+#include "saga/puzzle.h"
+#include "saga/render.h"
+#include "saga/resnames.h"
+
+#include "common/system.h"
+#include "common/timer.h"
+
+namespace Saga {
+
+#define PUZZLE_X_OFFSET 72
+#define PUZZLE_Y_OFFSET 46
+
+#define PUZZLE_FIT 0x01 // 1 when in correct position
+#define PUZZLE_MOVED 0x04 // 1 when somewhere in the box
+#define PUZZLE_ALL_SET PUZZLE_FIT | PUZZLE_MOVED
+
+
+enum rifOptions {
+ kROLater = 0,
+ kROAccept = 1,
+ kRODecline = 2,
+ kROHint = 3
+};
+
+Puzzle::Puzzle(SagaEngine *vm) : _vm(vm), _solved(false), _active(false) {
+ _lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0;
+
+ _hintRqState = kRQNoHint;
+ _hintOffer = 0;
+ _hintCount = 0;
+ _helpCount = 0;
+ _puzzlePiece = -1;
+ _newPuzzle = true;
+ _sliding = false;
+ _hintBox.left = 70;
+ _hintBox.top = 105;
+ _hintBox.setWidth(240);
+ _hintBox.setHeight(30);
+
+ initPieceInfo( 0, 268, 18, 0, 0, 0 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 3,
+ Point(0, 1), Point(0, 62), Point(15, 31), Point(0, 0), Point(0, 0), Point(0,0));
+ initPieceInfo( 1, 270, 52, 0, 0, 0 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 31), Point(0, 47), Point(39, 47), Point(15, 1), Point(0, 0), Point(0, 0));
+ initPieceInfo( 2, 19, 51, 0, 0, 0 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 0), Point(23, 46), Point(39, 15), Point(31, 0), Point(0, 0), Point(0, 0));
+ initPieceInfo( 3, 73, 0, 0, 0, 32 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 6,
+ Point(0, 0), Point(8, 16), Point(0, 31), Point(31, 31), Point(39, 15), Point(31, 0));
+ initPieceInfo( 4, 0, 35, 0, 0, 64 + PUZZLE_X_OFFSET, 16 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 15), Point(15, 46), Point(23, 32), Point(7, 1), Point(0, 0), Point(0, 0));
+ initPieceInfo( 5, 215, 0, 0, 0, 24 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 6,
+ Point(0, 15), Point(8, 31), Point(39, 31), Point(47, 16), Point(39, 0), Point(8, 0));
+ initPieceInfo( 6, 159, 0, 0, 0, 32 + PUZZLE_X_OFFSET, 48 + PUZZLE_Y_OFFSET, 0, 5,
+ Point(0, 16), Point(8, 31), Point(55, 31), Point(39, 1), Point(32, 15), Point(0, 0));
+ initPieceInfo( 7, 9, 70, 0, 0, 80 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 5,
+ Point(0, 31), Point(8, 47), Point(23, 47), Point(31, 31), Point(15, 1), Point(0, 0));
+ initPieceInfo( 8, 288, 18, 0, 0, 96 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 31), Point(15, 62), Point(31, 32), Point(15, 1), Point(0, 0), Point(0, 0));
+ initPieceInfo( 9, 112, 0, 0, 0, 112 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 0), Point(16, 31), Point(47, 31), Point(31, 0), Point(0, 0), Point(0, 0));
+ initPieceInfo(10, 27, 89, 0, 0, 104 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 47), Point(31, 47), Point(31, 0), Point(24, 0), Point(0, 0), Point(0, 0));
+ initPieceInfo(11, 43, 0, 0, 0, 136 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 6,
+ Point(0, 0), Point(0, 47), Point(15, 47), Point(15, 15), Point(31, 15), Point(23, 0));
+ initPieceInfo(12, 0, 0, 0, 0, 144 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 0), Point(24, 47), Point(39, 47), Point(39, 0), Point(0, 0), Point(0, 0));
+ initPieceInfo(13, 262, 0, 0, 0, 64 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 3,
+ Point(0, 0), Point(23, 46), Point(47, 0), Point(0, 0), Point(0, 0), Point(0, 0));
+ initPieceInfo(14, 271, 103, 0, 0, 152 + PUZZLE_X_OFFSET, 48 + PUZZLE_Y_OFFSET, 0, 4,
+ Point(0, 0), Point(0, 31), Point(31, 31), Point(31, 0), Point(0, 0), Point(0, 0));
+}
+
+void Puzzle::initPieceInfo(int i, int16 curX, int16 curY, byte offX, byte offY, int16 trgX,
+ int16 trgY, uint8 flag, uint8 count, Point point0, Point point1,
+ Point point2, Point point3, Point point4, Point point5) {
+ _pieceInfo[i].curX = curX;
+ _pieceInfo[i].curY = curY;
+ _pieceInfo[i].offX = offX;
+ _pieceInfo[i].offY = offY;
+ _pieceInfo[i].trgX = trgX;
+ _pieceInfo[i].trgY = trgY;
+ _pieceInfo[i].flag = flag;
+ _pieceInfo[i].count = count;
+ _pieceInfo[i].point[0] = point0;
+ _pieceInfo[i].point[1] = point1;
+ _pieceInfo[i].point[2] = point2;
+ _pieceInfo[i].point[3] = point3;
+ _pieceInfo[i].point[4] = point4;
+ _pieceInfo[i].point[5] = point5;
+}
+
+
+void Puzzle::execute(void) {
+ _active = true;
+ Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this);
+
+ initPieces();
+
+ showPieces();
+
+ _vm->_interface->setMode(kPanelConverse);
+ clearHint();
+ //_solved = true; // Cheat
+ //exitPuzzle();
+}
+
+void Puzzle::exitPuzzle(void) {
+ _active = false;
+
+ Common::g_timer->removeTimerProc(&hintTimerCallback);
+
+ _vm->_scene->changeScene(ITE_SCENE_LODGE, 0, kTransitionNoFade);
+ _vm->_interface->setMode(kPanelMain);
+}
+
+void Puzzle::initPieces(void) {
+ SpriteInfo *spI;
+ ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
+ int frameNumber;
+ SpriteList *spriteList;
+ _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
+
+ for (int i = 0; i < PUZZLE_PIECES; i++) {
+ spI = &(spriteList->infoList[i]);
+ _pieceInfo[i].offX = (byte)(spI->width >> 1);
+ _pieceInfo[i].offY = (byte)(spI->height >> 1);
+
+ if (_newPuzzle) {
+ _pieceInfo[i].curX = pieceOrigins[i].x;
+ _pieceInfo[i].curY = pieceOrigins[i].y;
+ }
+ _piecePriority[i] = i;
+ }
+ _newPuzzle = false;
+}
+
+void Puzzle::showPieces(void) {
+ ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
+ int frameNumber;
+ SpriteList *spriteList;
+ Surface *backBuffer = _vm->_gfx->getBackBuffer();
+ _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
+
+ for (int j = PUZZLE_PIECES - 1 ; j >= 0; j--) {
+ int num = _piecePriority[j];
+
+ if (_puzzlePiece != num) {
+ _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), *spriteList, num, Point(_pieceInfo[num].curX, _pieceInfo[num].curY), 256);
+ }
+ }
+}
+
+void Puzzle::drawCurrentPiece() {
+ ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
+ Surface *backBuffer = _vm->_gfx->getBackBuffer();
+ int frameNumber;
+ SpriteList *spriteList;
+ _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
+
+ _vm->_sprite->draw(backBuffer, _vm->_scene->getSceneClip(), *spriteList, _puzzlePiece,
+ Point(_pieceInfo[_puzzlePiece].curX, _pieceInfo[_puzzlePiece].curY), 256);
+}
+
+void Puzzle::movePiece(Point mousePt) {
+ int newx, newy;
+
+ showPieces();
+
+ if (_puzzlePiece == -1)
+ return;
+
+ if (_sliding) {
+ newx = _slidePointX;
+ newy = _slidePointY;
+ } else {
+ if (mousePt.y >= 137)
+ return;
+
+ newx = mousePt.x;
+ newy = mousePt.y;
+ }
+
+ newx -= _pieceInfo[_puzzlePiece].offX;
+ newy -= _pieceInfo[_puzzlePiece].offY;
+
+ _pieceInfo[_puzzlePiece].curX = newx;
+ _pieceInfo[_puzzlePiece].curY = newy;
+
+ drawCurrentPiece();
+}
+
+void Puzzle::handleClick(Point mousePt) {
+ if (_puzzlePiece != -1) {
+ dropPiece(mousePt);
+
+ if (!_active)
+ return; // we won
+
+ drawCurrentPiece();
+ _puzzlePiece = -1;
+
+ return;
+ }
+
+ for (int j = 0; j < PUZZLE_PIECES; j++) {
+ int i = _piecePriority[j];
+ int adjX = mousePt.x - _pieceInfo[i].curX;
+ int adjY = mousePt.y - _pieceInfo[i].curY;
+
+ if (hitTestPoly(&_pieceInfo[i].point[0], _pieceInfo[i].count, Point(adjX, adjY))) {
+ _puzzlePiece = i;
+ break;
+ }
+ }
+
+ if (_puzzlePiece == -1)
+ return;
+
+ alterPiecePriority();
+
+ // Display scene background
+ _vm->_scene->draw();
+ showPieces();
+
+ int newx = mousePt.x - _pieceInfo[_puzzlePiece].offX;
+ int newy = mousePt.y - _pieceInfo[_puzzlePiece].offY;
+
+ if (newx != _pieceInfo[_puzzlePiece].curX
+ || newy != _pieceInfo[_puzzlePiece].curY) {
+ _pieceInfo[_puzzlePiece].curX = newx;
+ _pieceInfo[_puzzlePiece].curY = newy;
+ }
+ _vm->_interface->setStatusText(pieceNames[_lang][_puzzlePiece]);
+}
+
+void Puzzle::alterPiecePriority(void) {
+ for (int i = 1; i < PUZZLE_PIECES; i++) {
+ if (_puzzlePiece == _piecePriority[i]) {
+ for (int j = i - 1; j >= 0; j--)
+ _piecePriority[j+1] = _piecePriority[j];
+ _piecePriority[0] = _puzzlePiece;
+ break;
+ }
+ }
+}
+
+void Puzzle::slidePiece(int x1, int y1, int x2, int y2) {
+ int count;
+ Point slidePoints[320];
+
+ x1 += _pieceInfo[_puzzlePiece].offX;
+ y1 += _pieceInfo[_puzzlePiece].offY;
+
+ count = pathLine(&slidePoints[0], Point(x1, y1),
+ Point(x2 + _pieceInfo[_puzzlePiece].offX, y2 + _pieceInfo[_puzzlePiece].offY));
+
+ if (count > 1) {
+ int factor = count / 4;
+ _sliding = true;
+
+ if (!factor)
+ factor++;
+
+ for (int i = 1; i < count; i += factor) {
+ _slidePointX = slidePoints[i].x;
+ _slidePointY = slidePoints[i].y;
+ _vm->_render->drawScene();
+ _vm->_system->delayMillis(10);
+ }
+ _sliding = false;
+ }
+
+ _pieceInfo[_puzzlePiece].curX = x2;
+ _pieceInfo[_puzzlePiece].curY = y2;
+}
+
+void Puzzle::dropPiece(Point mousePt) {
+ int boxx = PUZZLE_X_OFFSET;
+ int boxy = PUZZLE_Y_OFFSET;
+ int boxw = boxx + 184;
+ int boxh = boxy + 80;
+
+ // if the center is within the box quantize within
+ // else move it back to its original start point
+ if (mousePt.x >= boxx && mousePt.x < boxw && mousePt.y >= boxy && mousePt.y <= boxh) {
+ ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
+ SpriteInfo *spI;
+ int frameNumber;
+ SpriteList *spriteList;
+ _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
+
+ int newx = mousePt.x - _pieceInfo[_puzzlePiece].offX;
+ int newy = mousePt.y - _pieceInfo[_puzzlePiece].offY;
+
+ if (newx < boxx)
+ newx = PUZZLE_X_OFFSET;
+ if (newy < boxy)
+ newy = PUZZLE_Y_OFFSET;
+
+ spI = &(spriteList->infoList[_puzzlePiece]);
+
+ if (newx + spI->width > boxw)
+ newx = boxw - spI->width ;
+ if (newy + spI->height > boxh)
+ newy = boxh - spI->height ;
+
+ int x1 = ((newx - PUZZLE_X_OFFSET) & ~7) + PUZZLE_X_OFFSET;
+ int y1 = ((newy - PUZZLE_Y_OFFSET) & ~7) + PUZZLE_Y_OFFSET;
+ int x2 = x1 + 8;
+ int y2 = y1 + 8;
+ newx = (x2 - newx < newx - x1) ? x2 : x1;
+ newy = (y2 - newy < newy - y1) ? y2 : y1;
+
+ // if any part of the puzzle piece falls outside the box
+ // force it back in
+
+ // is the piece at the target location
+ if (newx == _pieceInfo[_puzzlePiece].trgX
+ && newy == _pieceInfo[_puzzlePiece].trgY) {
+ _pieceInfo[_puzzlePiece].flag |= (PUZZLE_MOVED | PUZZLE_FIT);
+ } else {
+ _pieceInfo[_puzzlePiece].flag &= ~PUZZLE_FIT;
+ _pieceInfo[_puzzlePiece].flag |= PUZZLE_MOVED;
+ }
+ _pieceInfo[_puzzlePiece].curX = newx;
+ _pieceInfo[_puzzlePiece].curY = newy;
+ } else {
+ int newx = pieceOrigins[_puzzlePiece].x;
+ int newy = pieceOrigins[_puzzlePiece].y;
+ _pieceInfo[_puzzlePiece].flag &= ~(PUZZLE_FIT | PUZZLE_MOVED);
+
+ // slide piece from current position to new position
+ slidePiece(_pieceInfo[_puzzlePiece].curX, _pieceInfo[_puzzlePiece].curY,
+ newx, newy);
+ }
+
+ // is the puzzle completed?
+
+ _solved = true;
+ for (int i = 0; i < PUZZLE_PIECES; i++)
+ if ((_pieceInfo[i].flag & PUZZLE_FIT) == 0) {
+ _solved = false;
+ break;
+ }
+
+ if (_solved)
+ exitPuzzle();
+}
+
+void Puzzle::hintTimerCallback(void *refCon) {
+ ((Puzzle *)refCon)->solicitHint();
+}
+
+void Puzzle::solicitHint(void) {
+ int i;
+
+ _vm->_actor->setSpeechColor(1, kITEColorBlack);
+
+ Common::g_timer->removeTimerProc(&hintTimerCallback);
+
+ switch (_hintRqState) {
+ case kRQSpeaking:
+ if (_vm->_actor->isSpeaking()) {
+ Common::g_timer->installTimerProc(&hintTimerCallback, 50000, this);
+ break;
+ }
+
+ _hintRqState = _hintNextRqState;
+ Common::g_timer->installTimerProc(&hintTimerCallback, 333333, this);
+ break;
+
+ case kRQNoHint:
+ // Pick a random hint request.
+ i = _hintOffer++;
+ if (_hintOffer >= NUM_SOLICIT_REPLIES)
+ _hintOffer = 0;
+
+ // Determine which of the journeymen will offer then
+ // hint, and then show that character's portrait.
+ _hintGiver = portraitList[i];
+ _hintSpeaker = _hintGiver - RID_ITE_JFERRET_SERIOUS;
+ _vm->_interface->setRightPortrait(_hintGiver);
+
+ _vm->_actor->nonActorSpeech(_hintBox, &solicitStr[_lang][i], 1, PUZZLE_SOLICIT_SOUNDS + i * 3 + _hintSpeaker, 0);
+
+ // Add Rif's reply to the list.
+ clearHint();
+
+ // Roll to see if Sakka scolds
+ if (_vm->_rnd.getRandomNumber(1)) {
+ _hintRqState = kRQSakkaDenies;
+ Common::g_timer->installTimerProc(&hintTimerCallback, 200000, this);
+ } else {
+ _hintRqState = kRQSpeaking;
+ _hintNextRqState = kRQHintRequested;
+ Common::g_timer->installTimerProc(&hintTimerCallback, 50000, this);
+ }
+
+ break;
+
+ case kRQHintRequested:
+ i = _vm->_rnd.getRandomNumber(NUM_SAKKA - 1);
+ _vm->_actor->nonActorSpeech(_hintBox, &sakkaStr[_lang][i], 1, PUZZLE_SAKKA_SOUNDS + i, 0);
+
+ _vm->_interface->setRightPortrait(RID_ITE_SAKKA_APPRAISING);
+
+ _hintRqState = kRQSpeaking;
+ _hintNextRqState = kRQHintRequestedStage2;
+ Common::g_timer->installTimerProc(&hintTimerCallback, 50000, this);
+
+ _vm->_interface->converseClear();
+ _vm->_interface->converseAddText(optionsStr[_lang][kROAccept], 1, 0, 0 );
+ _vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 2, 0, 0 );
+ _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0 );
+ _vm->_interface->converseDisplayText();
+ break;
+
+ case kRQHintRequestedStage2:
+ if (_vm->_rnd.getRandomNumber(1)) { // Skip Reply part
+ i = _vm->_rnd.getRandomNumber(NUM_WHINES - 1);
+ _vm->_actor->nonActorSpeech(_hintBox, &whineStr[_lang][i], 1, PUZZLE_WHINE_SOUNDS + i * 3 + _hintSpeaker, 0);
+ }
+
+ _vm->_interface->setRightPortrait(_hintGiver);
+
+ _hintRqState = kRQSakkaDenies;
+ break;
+
+ case kRQSakkaDenies:
+ _vm->_interface->converseClear();
+ _vm->_interface->converseAddText(optionsStr[_lang][kROAccept], 1, 0, 0);
+ _vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 2, 0, 0);
+ _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0);
+ _vm->_interface->converseDisplayText();
+
+ Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this);
+
+ _hintRqState = kRQSkipEverything;
+ break;
+
+ default:
+ break;
+ }
+}
+
+void Puzzle::handleReply(int reply) {
+ switch(reply) {
+ case 0: // Quit the puzzle
+ exitPuzzle();
+ break;
+
+ case 1: // Accept the hint
+ giveHint();
+ break;
+
+ case 2: // Decline the hint
+ _vm->_actor->abortSpeech();
+ _hintRqState = kRQNoHint;
+ Common::g_timer->removeTimerProc(&hintTimerCallback);
+ Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime * 2, this);
+ clearHint();
+ break;
+ }
+}
+
+void Puzzle::giveHint(void) {
+ int i, total = 0;
+
+ _vm->_interface->converseClear();
+
+ _vm->_actor->abortSpeech();
+ _vm->_interface->setRightPortrait(_hintGiver);
+
+ for (i = 0; i < PUZZLE_PIECES; i++)
+ total += _pieceInfo[i].flag & PUZZLE_FIT;
+
+ if (_hintCount == 0 && (_pieceInfo[1].flag & PUZZLE_FIT
+ || _pieceInfo[12].flag & PUZZLE_FIT))
+ _hintCount++;
+ if (_hintCount == 1 && _pieceInfo[14].flag & PUZZLE_FIT)
+ _hintCount++;
+
+ if (_hintCount == 2 && total > 3)
+ _hintCount++;
+
+ _vm->_actor->setSpeechColor(1, kITEColorBlack);
+
+ if (_hintCount < 3) {
+ _vm->_actor->nonActorSpeech(_hintBox, &hintStr[_lang][_hintCount], 1, PUZZLE_HINT_SOUNDS + _hintCount * 3 + _hintSpeaker, 0);
+ } else {
+ int piece = 0;
+
+ for (i = PUZZLE_PIECES - 1; i >= 0; i--) {
+ piece = _piecePriority[i];
+ if (_pieceInfo[piece].flag & PUZZLE_MOVED
+ && !(_pieceInfo[piece].flag & PUZZLE_FIT)) {
+ if (_helpCount < 12)
+ _helpCount++;
+ break;
+ }
+ }
+
+ if (i >= 0) {
+ static char hintBuf[64];
+ static const char *hintPtr = hintBuf;
+ sprintf(hintBuf, optionsStr[_lang][kROHint], pieceNames[_lang][piece]);
+
+ _vm->_actor->nonActorSpeech(_hintBox, &hintPtr, 1, PUZZLE_TOOL_SOUNDS + _hintSpeaker + piece * 3, 0);
+ }
+ else {
+ // If no pieces are in the wrong place
+ _vm->_actor->nonActorSpeech(_hintBox, &hintStr[_lang][3], 1, PUZZLE_HINT_SOUNDS + 3 * 3 + _hintSpeaker, 0);
+ }
+ }
+ _hintCount++;
+
+ _hintRqState = kRQNoHint;
+
+ _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0);
+ _vm->_interface->converseDisplayText();
+
+ Common::g_timer->removeTimerProc(&hintTimerCallback);
+ Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this);
+}
+
+void Puzzle::clearHint(void) {
+ _vm->_interface->converseClear();
+ _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0);
+ _vm->_interface->converseDisplayText();
+ _vm->_interface->setStatusText(" ");
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/puzzle.h b/engines/saga/puzzle.h
new file mode 100644
index 0000000000..b5c9b7c15c
--- /dev/null
+++ b/engines/saga/puzzle.h
@@ -0,0 +1,120 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2005-2006 The ScummVM project
+ *
+ * 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 SAGA_PUZZLE_H_
+#define SAGA_PUZZLE_H_
+
+namespace Saga {
+
+
+#define PUZZLE_SOUNDS 3622
+#define PUZZLE_TOOL_SOUNDS (PUZZLE_SOUNDS + 0)
+#define PUZZLE_HINT_SOUNDS (PUZZLE_SOUNDS + 45)
+#define PUZZLE_SOLICIT_SOUNDS (PUZZLE_SOUNDS + 57)
+#define PUZZLE_WHINE_SOUNDS (PUZZLE_SOUNDS + 72)
+#define PUZZLE_SAKKA_SOUNDS (PUZZLE_SOUNDS + 87)
+
+class Puzzle {
+private:
+ enum kRQStates {
+ kRQNoHint = 0,
+ kRQHintRequested = 1,
+ kRQHintRequestedStage2 = 2,
+ kRQSakkaDenies = 3,
+ kRQSkipEverything = 4,
+ kRQSpeaking = 5
+ };
+
+ SagaEngine *_vm;
+
+ bool _solved;
+ bool _active;
+ bool _newPuzzle;
+ bool _sliding;
+
+ kRQStates _hintRqState;
+ kRQStates _hintNextRqState;
+ int _hintGiver;
+ int _hintSpeaker;
+ int _hintOffer;
+ int _hintCount;
+ int _helpCount;
+
+ int _puzzlePiece;
+ int _piecePriority[PUZZLE_PIECES];
+
+ int _lang;
+
+public:
+ Puzzle(SagaEngine *vm);
+
+ void execute(void);
+ void exitPuzzle(void);
+
+ bool isSolved(void) { return _solved; }
+ bool isActive(void) { return _active; }
+
+ void handleReply(int reply);
+ void handleClick(Point mousePt);
+
+ void movePiece(Point mousePt);
+
+private:
+ void initPieceInfo(int i, int16 curX, int16 curY, byte offX, byte offY, int16 trgX,
+ int16 trgY, uint8 flag, uint8 count, Point point0, Point point1,
+ Point point2, Point point3, Point point4, Point point5);
+
+ static void hintTimerCallback(void *refCon);
+
+ void solicitHint(void);
+
+ void initPieces(void);
+ void showPieces(void);
+ void slidePiece(int x1, int y1, int x2, int y2);
+ void dropPiece(Point mousePt);
+ void alterPiecePriority(void);
+ void drawCurrentPiece(void);
+
+ void giveHint(void);
+ void clearHint(void);
+
+private:
+ struct PieceInfo {
+ int16 curX;
+ int16 curY;
+ byte offX;
+ byte offY;
+ int16 trgX;
+ int16 trgY;
+ uint8 flag;
+ uint8 count;
+ Point point[6];
+ };
+
+ PieceInfo _pieceInfo[PUZZLE_PIECES];
+ int _slidePointX, _slidePointY;
+ Rect _hintBox;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/render.cpp b/engines/saga/render.cpp
new file mode 100644
index 0000000000..7d312fb920
--- /dev/null
+++ b/engines/saga/render.cpp
@@ -0,0 +1,189 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Main rendering loop
+#include "saga/saga.h"
+
+#include "saga/actor.h"
+#include "saga/font.h"
+#include "saga/gfx.h"
+#include "saga/interface.h"
+#include "saga/objectmap.h"
+#include "saga/puzzle.h"
+#include "saga/render.h"
+#include "saga/scene.h"
+
+#include "common/timer.h"
+#include "common/system.h"
+
+namespace Saga {
+
+const char *test_txt = "The quick brown fox jumped over the lazy dog. She sells sea shells down by the sea shore.";
+const char *pauseString = "PAWS GAME";
+
+Render::Render(SagaEngine *vm, OSystem *system) {
+ _vm = vm;
+ _system = system;
+ _initialized = false;
+
+ // Initialize FPS timer callback
+ Common::g_timer->installTimerProc(&fpsTimerCallback, 1000000, this);
+
+ _backGroundSurface.create(_vm->getDisplayWidth(), _vm->getDisplayHeight(), 1);
+
+ _flags = 0;
+
+ _initialized = true;
+}
+
+Render::~Render(void) {
+ Common::g_timer->removeTimerProc(&fpsTimerCallback);
+ _backGroundSurface.free();
+
+ _initialized = false;
+}
+
+bool Render::initialized() {
+ return _initialized;
+}
+
+void Render::drawScene() {
+ Surface *backBufferSurface;
+ char txtBuffer[20];
+ Point mousePoint;
+ Point textPoint;
+
+ assert(_initialized);
+
+ _frameCount++;
+
+ backBufferSurface = _vm->_gfx->getBackBuffer();
+
+ // Get mouse coordinates
+ mousePoint = _vm->mousePos();
+
+ if (!(_flags & (RF_DEMO_SUBST | RF_PLACARD | RF_MAP))) {
+ // Display scene background
+ _vm->_scene->draw();
+
+ if (_vm->_interface->getFadeMode() != kFadeOut) {
+ if (_vm->_puzzle->isActive()) {
+ _vm->_puzzle->movePiece(mousePoint);
+ _vm->_actor->drawSpeech();
+ } else {
+ // Draw queued actors
+ if (!(_flags & RF_DISABLE_ACTORS))
+ _vm->_actor->drawActors();
+ }
+
+ if (getFlags() & RF_OBJECTMAP_TEST) {
+ if (_vm->_scene->_objectMap)
+ _vm->_scene->_objectMap->draw(backBufferSurface, mousePoint, kITEColorBrightWhite, kITEColorBlack);
+ if (_vm->_scene->_actionMap)
+ _vm->_scene->_actionMap->draw(backBufferSurface, mousePoint, kITEColorRed, kITEColorBlack);
+ }
+ if (getFlags() & RF_ACTOR_PATH_TEST) {
+ _vm->_actor->drawPathTest();
+ }
+ }
+ }
+
+ if (_flags & RF_MAP)
+ _vm->_interface->mapPanelDrawCrossHair();
+
+ if ((_vm->_interface->getMode() == kPanelOption) ||
+ (_vm->_interface->getMode() == kPanelQuit) ||
+ (_vm->_interface->getMode() == kPanelLoad) ||
+ (_vm->_interface->getMode() == kPanelSave)) {
+ _vm->_interface->drawOption();
+
+ if (_vm->_interface->getMode() == kPanelQuit) {
+ _vm->_interface->drawQuit();
+ }
+ if (_vm->_interface->getMode() == kPanelLoad) {
+ _vm->_interface->drawLoad();
+ }
+ if (_vm->_interface->getMode() == kPanelSave) {
+ _vm->_interface->drawSave();
+ }
+ }
+
+ if (_vm->_interface->getMode() == kPanelProtect) {
+ _vm->_interface->drawProtect();
+ }
+
+ // Draw queued text strings
+ _vm->_scene->drawTextList(backBufferSurface);
+
+ // Handle user input
+ _vm->processInput();
+
+ // Display rendering information
+ if (_flags & RF_SHOW_FPS) {
+ sprintf(txtBuffer, "%d", _fps);
+ textPoint.x = backBufferSurface->w - _vm->_font->getStringWidth(kKnownFontSmall, txtBuffer, 0, kFontOutline);
+ textPoint.y = 2;
+
+ _vm->_font->textDraw(kKnownFontSmall, backBufferSurface, txtBuffer, textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline);
+ }
+
+ // Display "paused game" message, if applicable
+ if (_flags & RF_RENDERPAUSE) {
+ textPoint.x = (backBufferSurface->w - _vm->_font->getStringWidth(kKnownFontPause, pauseString, 0, kFontOutline)) / 2;
+ textPoint.y = 90;
+
+ _vm->_font->textDraw(kKnownFontPause, backBufferSurface, pauseString, textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline);
+ }
+
+ // Update user interface
+ _vm->_interface->update(mousePoint, UPDATE_MOUSEMOVE);
+
+ // Display text formatting test, if applicable
+ if (_flags & RF_TEXT_TEST) {
+ Rect rect(mousePoint.x, mousePoint.y, mousePoint.x + 100, mousePoint.y + 50);
+ _vm->_font->textDrawRect(kKnownFontMedium, backBufferSurface, test_txt, rect,
+ kITEColorBrightWhite, kITEColorBlack, (FontEffectFlags)(kFontOutline | kFontCentered));
+ }
+
+ // Display palette test, if applicable
+ if (_flags & RF_PALETTE_TEST) {
+ backBufferSurface->drawPalette();
+ }
+
+ _system->copyRectToScreen((byte *)backBufferSurface->pixels, backBufferSurface->w, 0, 0,
+ backBufferSurface->w, backBufferSurface->h);
+
+ _system->updateScreen();
+}
+
+void Render::fpsTimerCallback(void *refCon) {
+ ((Render *)refCon)->fpsTimer();
+}
+
+void Render::fpsTimer(void) {
+ _fps = _frameCount;
+ _frameCount = 0;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/render.h b/engines/saga/render.h
new file mode 100644
index 0000000000..f6187393d2
--- /dev/null
+++ b/engines/saga/render.h
@@ -0,0 +1,93 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Main rendering loop - private header
+
+#ifndef SAGA_RENDER_H_
+#define SAGA_RENDER_H_
+
+#include "saga/sprite.h"
+
+namespace Saga {
+
+enum RENDER_FLAGS {
+ RF_SHOW_FPS = (1 << 0),
+ RF_PALETTE_TEST = (1 << 1),
+ RF_TEXT_TEST = (1 << 2),
+ RF_OBJECTMAP_TEST = (1 << 3),
+ RF_RENDERPAUSE = (1 << 4),
+ RF_GAMEPAUSE = (1 << 5),
+ RF_PLACARD = (1 << 6),
+ RF_ACTOR_PATH_TEST = (1 << 7),
+ RF_MAP = (1 << 8),
+ RF_DISABLE_ACTORS = (1 << 9),
+ RF_DEMO_SUBST = (1 << 10)
+};
+
+class Render {
+public:
+ Render(SagaEngine *vm, OSystem *system);
+ ~Render(void);
+ bool initialized();
+ void drawScene(void);
+
+ unsigned int getFlags() const {
+ return _flags;
+ }
+
+ void setFlag(unsigned int flag) {
+ _flags |= flag;
+ }
+
+ void clearFlag(unsigned int flag) {
+ _flags &= ~flag;
+ }
+
+ void toggleFlag(unsigned int flag) {
+ _flags ^= flag;
+ }
+
+ Surface *getBackGroundSurface() {
+ return &_backGroundSurface;
+ }
+
+private:
+ static void fpsTimerCallback(void *refCon);
+ void fpsTimer(void);
+
+ SagaEngine *_vm;
+ OSystem *_system;
+ bool _initialized;
+
+ // Module data
+ Surface _backGroundSurface;
+
+ unsigned int _fps;
+ unsigned int _frameCount;
+ uint32 _flags;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/resnames.h b/engines/saga/resnames.h
new file mode 100644
index 0000000000..01522a9ca7
--- /dev/null
+++ b/engines/saga/resnames.h
@@ -0,0 +1,253 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Descriptive names for game resource numbers
+
+#ifndef SAGA_RESOURCENAMES_H_
+#define SAGA_RESOURCENAMES_H_
+
+namespace Saga {
+
+// Prefix RID_ means Resource Id
+
+// Lookup tables
+#define RID_ITE_SCENE_LUT 1806
+#define RID_ITE_SCRIPT_LUT 216
+
+#define RID_ITEDEMO_SCENE_LUT 318
+#define RID_ITEDEMO_SCRIPT_LUT 146
+
+#define RID_IHNM_SCENE_LUT 1272
+#define RID_IHNM_SCRIPT_LUT 29
+#define RID_IHNM_SFX_LUT 265
+
+#define RID_IHNMDEMO_SCENE_LUT 286
+#define RID_IHNMDEMO_SCRIPT_LUT 18
+
+//obj names
+#define ITE_OBJ_MAP 14
+#define ITE_OBJ_MAGIC_HAT 0
+
+#define IHNM_OBJ_PROFILE 0x4000
+
+#define RID_IHNM_DEFAULT_PALETTE 1
+
+//actor names
+#define ITE_ACTOR_PUZZLE 176
+
+// SCENES
+#define ITE_SCENE_INV -1
+#define ITE_SCENE_PUZZLE 26
+#define ITE_SCENE_LODGE 21
+#define ITE_SCENE_ENDCREDIT1 295
+
+#define ITE_DEFAULT_SCENE 32
+#define IHNM_DEFAULT_SCENE 151
+
+#define ITEDEMO_DEFAULT_SCENE 68
+
+// FONTS
+#define RID_MEDIUM_FONT 0
+#define RID_BIG_FONT 1
+#define RID_SMALL_FONT 2
+
+// INTERFACE IMAGES
+#define RID_ITE_MAIN_PANEL 3
+#define RID_ITE_CONVERSE_PANEL 4
+#define RID_ITE_OPTION_PANEL 5
+#define RID_ITE_MAIN_SPRITES 6
+#define RID_ITE_MAIN_PANEL_SPRITES 7
+#define RID_ITE_MAIN_STRINGS 35 //main strings
+#define RID_ITE_ACTOR_NAMES 36 //actors names
+#define RID_ITE_DEFAULT_PORTRAITS 125
+
+#define RID_ITEDEMO_MAIN_PANEL 2
+#define RID_ITEDEMO_CONVERSE_PANEL 3
+#define RID_ITEDEMO_OPTION_PANEL 3 // FIXME: should be 4 but it is an empty resource.
+#define RID_ITEDEMO_MAIN_SPRITES 5 // Proper fix would be not load options panel when demo is running
+#define RID_ITEDEMO_MAIN_PANEL_SPRITES 6
+#define RID_ITEDEMO_MAIN_STRINGS 8 //main strings
+#define RID_ITEDEMO_ACTOR_NAMES 9 //actors names
+#define RID_ITEDEMO_DEFAULT_PORTRAITS 80
+
+#define RID_ITE_TYCHO_MAP 1686
+#define RID_ITE_SPR_XHAIR1 (73 + 9)
+#define RID_ITE_SPR_XHAIR2 (74 + 9)
+
+#define RID_IHNM_MAIN_PANEL 9
+#define RID_IHNM_CONVERSE_PANEL 10
+#define RID_IHNM_HOURGLASS_CURSOR 11
+#define RID_IHNM_MAIN_SPRITES 12 // TODO: verify this
+#define RID_IHNM_MAIN_PANEL_SPRITES 12
+#define RID_IHNM_ARROW_SPRITES 13
+#define RID_IHNM_SAVEREMINDER_SPRITES 14
+#define RID_IHNM_OPTION_PANEL 15
+#define RID_IHNM_WARNING_PANEL 17
+#define RID_IHNM_BOSS_SCREEN 19
+#define RID_IHNM_PROFILE_BG 20
+#define RID_IHNM_MAIN_STRINGS 21
+
+// Puzzle portraits
+#define RID_ITE_SAKKA_APPRAISING 6
+#define RID_ITE_SAKKA_DENIAL 7
+#define RID_ITE_SAKKA_EXCITED 8
+#define RID_ITE_JFERRET_SERIOUS 9
+#define RID_ITE_JFERRET_GOOFY 10
+#define RID_ITE_JFERRET_ALOOF 11
+
+// ITE Scene resource numbers
+#define RID_ITE_OVERMAP_SCENE 226
+#define RID_ITE_INTRO_ANIM_SCENE 1538
+#define RID_ITE_CAVE_SCENE_1 1542
+#define RID_ITE_CAVE_SCENE_2 1545
+#define RID_ITE_CAVE_SCENE_3 1548
+#define RID_ITE_CAVE_SCENE_4 1551
+
+#define RID_ITE_VALLEY_SCENE 1556
+#define RID_ITE_TREEHOUSE_SCENE 1560
+#define RID_ITE_FAIREPATH_SCENE 1564
+#define RID_ITE_FAIRETENT_SCENE 1567
+
+#define RID_ITE_INTRO_ANIM_STARTFRAME 1529
+
+#define RID_ITE_INTRO_ANIM_1 1530
+#define RID_ITE_INTRO_ANIM_2 1531
+#define RID_ITE_INTRO_ANIM_3 1532
+#define RID_ITE_INTRO_ANIM_4 1533
+#define RID_ITE_INTRO_ANIM_5 1534
+#define RID_ITE_INTRO_ANIM_6 1535
+#define RID_ITE_INTRO_ANIM_7 1536
+
+#define RID_ITE_CAVE_IMG_1 1540
+#define RID_ITE_CAVE_IMG_2 1543
+#define RID_ITE_CAVE_IMG_3 1546
+#define RID_ITE_CAVE_IMG_4 1549
+
+#define RID_ITE_INTRO_IMG_1 1552
+#define RID_ITE_INTRO_IMG_2 1557
+#define RID_ITE_INTRO_IMG_3 1561
+#define RID_ITE_INTRO_IMG_4 1565
+
+// ITE_VOICES
+#define RID_CAVE_VOICE_0 0
+#define RID_CAVE_VOICE_1 1
+#define RID_CAVE_VOICE_2 2
+#define RID_CAVE_VOICE_3 3
+#define RID_CAVE_VOICE_4 4
+#define RID_CAVE_VOICE_5 5
+#define RID_CAVE_VOICE_6 6
+#define RID_CAVE_VOICE_7 7
+#define RID_CAVE_VOICE_8 8
+#define RID_CAVE_VOICE_9 9
+#define RID_CAVE_VOICE_10 10
+#define RID_CAVE_VOICE_11 11
+#define RID_CAVE_VOICE_12 12
+#define RID_CAVE_VOICE_13 13
+
+#define RID_SCENE1_VOICE_009 57
+//TODO: fill it
+#define RID_SCENE1_VOICE_138 186
+
+#define RID_BOAR_VOICE_000 239
+#define RID_BOAR_VOICE_002 241
+#define RID_BOAR_VOICE_005 244
+#define RID_BOAR_VOICE_006 245
+#define RID_BOAR_VOICE_007 246
+
+// MUSIC
+#define MUSIC_1 9
+#define MUSIC_2 10
+
+// TODO: If the sound effects are numbered sequentially, we don't really need
+// these constants. But for now they might be useful for debugging.
+
+// SOUND EFFECTS
+
+#define FX_DOOR_OPEN 14
+#define FX_DOOR_CLOSE 15
+#define FX_RUSH_WATER 16
+#define FX_CRICKET 17
+#define FX_PORTICULLIS 18
+#define FX_CLOCK_1 19
+#define FX_CLOCK_2 20
+#define FX_DAM_MACHINE 21
+#define FX_HUM1 22
+#define FX_HUM2 23
+#define FX_HUM3 24
+#define FX_HUM4 25
+#define FX_STREAM 26
+#define FX_SURF 27
+#define FX_FIRELOOP 28
+#define FX_SCRAPING 29
+#define FX_BEE_SWARM 30
+#define FX_SQUEAKBOARD 31
+#define FX_KNOCK 32
+#define FX_COINS 33
+#define FX_STORM 34
+#define FX_DOOR_CLOSE_2 35
+#define FX_ARCWELD 36
+#define FX_RETRACT_ORB 37
+#define FX_DRAGON 38
+#define FX_SNORES 39
+#define FX_SPLASH 40
+#define FX_LOBBY_DOOR 41
+#define FX_CHIRP_LOOP 42
+#define FX_DOOR_CREAK 43
+#define FX_SPOON_DIG 44
+#define FX_CROW 45
+#define FX_COLDWIND 46
+#define FX_TOOL_SND_1 47
+#define FX_TOOL_SND_2 48
+#define FX_TOOL_SND_3 49
+#define FX_DOOR_METAL 50
+#define FX_WATER_LOOP_S 51
+#define FX_WATER_LOOP_L 52
+#define FX_DOOR_OPEN_2 53
+#define FX_JAIL_DOOR 54
+#define FX_KILN_FIRE 55
+#define FX_DUMMY 56
+
+// These are only in the CD version
+
+#define FX_CROWD_01 57
+#define FX_CROWD_02 58
+#define FX_CROWD_03 59
+#define FX_CROWD_04 60
+#define FX_CROWD_05 61
+#define FX_CROWD_06 62
+#define FX_CROWD_07 63
+#define FX_CROWD_08 64
+#define FX_CROWD_09 65
+#define FX_CROWD_10 66
+#define FX_CROWD_11 67
+#define FX_CROWD_12 68
+#define FX_CROWD_13 69
+#define FX_CROWD_14 70
+#define FX_CROWD_15 71
+#define FX_CROWD_16 72
+#define FX_CROWD_17 73
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/rscfile.cpp b/engines/saga/rscfile.cpp
new file mode 100644
index 0000000000..24d1262d56
--- /dev/null
+++ b/engines/saga/rscfile.cpp
@@ -0,0 +1,615 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// RSC Resource file management module
+#include "saga/saga.h"
+
+#include "saga/actor.h"
+#include "saga/animation.h"
+#include "saga/interface.h"
+#include "saga/music.h"
+#include "saga/rscfile.h"
+#include "saga/scene.h"
+#include "saga/sndres.h"
+#include "saga/stream.h"
+
+namespace Saga {
+
+struct MacResMap {
+ int16 resAttr;
+ int16 typeOffset;
+ int16 nameOffset;
+ int16 numTypes;
+};
+
+struct MacResource {
+ int16 id;
+ int16 nameOffset;
+ byte attr;
+ int32 dataOffset;
+ byte name[255];
+};
+
+struct MacResType {
+ uint32 id;
+ int16 items;
+ int16 maxItemId;
+ int16 offset;
+ MacResource *resources;
+};
+
+
+#define ID_MIDI MKID_BE('Midi')
+
+Resource::Resource(SagaEngine *vm): _vm(vm) {
+ _contexts = NULL;
+ _contextsCount = 0;
+}
+
+Resource::~Resource() {
+ clearContexts();
+}
+
+bool Resource::loadSagaContext(ResourceContext *context, uint32 contextOffset, uint32 contextSize) {
+ size_t i;
+ bool result;
+ byte tableInfo[RSC_TABLEINFO_SIZE];
+ byte *tableBuffer;
+ size_t tableSize;
+ uint32 resourceTableOffset;
+ ResourceData *resourceData;
+
+ if (contextSize < RSC_MIN_FILESIZE) {
+ return false;
+ }
+
+ context->file->seek(contextOffset + contextSize - RSC_TABLEINFO_SIZE);
+
+ if (context->file->read(tableInfo, RSC_TABLEINFO_SIZE) != RSC_TABLEINFO_SIZE) {
+ return false;
+ }
+
+ MemoryReadStreamEndian readS(tableInfo, RSC_TABLEINFO_SIZE, context->isBigEndian);
+
+ resourceTableOffset = readS.readUint32();
+ context->count = readS.readUint32();
+
+ // Check for sane table offset
+ if (resourceTableOffset != contextSize - RSC_TABLEINFO_SIZE - RSC_TABLEENTRY_SIZE * context->count) {
+ return false;
+ }
+
+ // Load resource table
+ tableSize = RSC_TABLEENTRY_SIZE * context->count;
+
+ tableBuffer = (byte *)malloc(tableSize);
+
+ context->file->seek(resourceTableOffset + contextOffset, SEEK_SET);
+
+ result = (context->file->read(tableBuffer, tableSize) == tableSize);
+ if (result) {
+ context->table = (ResourceData *)calloc(context->count, sizeof(*context->table));
+
+ MemoryReadStreamEndian readS1(tableBuffer, tableSize, context->isBigEndian);
+
+ for (i = 0; i < context->count; i++) {
+ resourceData = &context->table[i];
+ resourceData->offset = contextOffset + readS1.readUint32();
+ resourceData->size = readS1.readUint32();
+ //sanity check
+ if ((resourceData->offset > context->file->size()) || (resourceData->size > contextSize)) {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ free(tableBuffer);
+ return result;
+}
+
+bool Resource::loadMacContext(ResourceContext *context) {
+ int32 macDataSize, macDataSizePad;
+ int32 macResSize, macResSizePad;
+ int32 macResOffset;
+
+ uint32 macMapLength;
+ uint32 macDataLength;
+ uint32 macMapOffset;
+ uint32 macDataOffset;
+
+ MacResMap macResMap;
+ MacResType *macResTypes;
+
+ MacResType *macResType;
+ MacResource *macResource;
+ int i, j;
+ byte macNameLen;
+ bool notSagaContext = false;
+
+ if (context->file->size() < RSC_MIN_FILESIZE + MAC_BINARY_HEADER_SIZE) {
+ return false;
+ }
+
+ if (context->file->readByte() != 0) {
+ return false;
+ }
+ context->file->readByte(); //MAX Name Len
+ context->file->seek(74);
+ if (context->file->readByte() != 0) {
+ return false;
+ }
+ context->file->seek(82);
+ if (context->file->readByte() != 0) {
+ return false;
+ }
+
+ macDataSize = context->file->readSint32BE();
+ macResSize = context->file->readSint32BE();
+ macDataSizePad = (((macDataSize + 127) >> 7) << 7);
+ macResSizePad = (((macResSize + 127) >> 7) << 7);
+
+ macResOffset = MAC_BINARY_HEADER_SIZE + macDataSizePad;
+ context->file->seek(macResOffset);
+
+ macDataOffset = context->file->readUint32BE() + macResOffset;
+ macMapOffset = context->file->readUint32BE() + macResOffset;
+ macDataLength = context->file->readUint32BE();
+ macMapLength = context->file->readUint32BE();
+
+ if (macDataOffset >= context->file->size() || macMapOffset >= context->file->size() ||
+ macDataLength + macMapLength > context->file->size()) {
+ return false;
+ }
+
+ context->file->seek(macMapOffset + 22);
+
+ macResMap.resAttr = context->file->readUint16BE();
+ macResMap.typeOffset = context->file->readUint16BE();
+ macResMap.nameOffset = context->file->readUint16BE();
+ macResMap.numTypes = context->file->readUint16BE();
+ macResMap.numTypes++;
+
+ context->file->seek(macMapOffset + macResMap.typeOffset + 2);
+
+ macResTypes = (MacResType *)calloc(macResMap.numTypes, sizeof(*macResTypes));
+
+ for (i = macResMap.numTypes, macResType = macResTypes; i > 0; i--, macResType++) {
+ macResType->id = context->file->readUint32BE();
+ macResType->items = context->file->readUint16BE();
+ macResType->offset = context->file->readUint16BE();
+ macResType->items++;
+ macResType->resources = (MacResource*)calloc(macResType->items, sizeof(*macResType->resources));
+ }
+
+ for (i = macResMap.numTypes, macResType = macResTypes; i > 0; i--, macResType++) {
+ context->file->seek(macResType->offset + macMapOffset + macResMap.typeOffset);
+
+ for (j = macResType->items, macResource = macResType->resources; j > 0; j--, macResource++) {
+ macResource->id = context->file->readUint16BE();
+ macResource->nameOffset = context->file->readUint16BE();
+ macResource->dataOffset = context->file->readUint32BE();
+ macResSize = context->file->readUint32BE();
+
+ macResource->attr = macResource->dataOffset >> 24;
+ macResource->dataOffset &= 0xFFFFFF;
+ if (macResource->id > macResType->maxItemId) {
+ macResType->maxItemId = macResource->id;
+ }
+ }
+
+ for (j = macResType->items, macResource = macResType->resources; j > 0; j--, macResource++) {
+ if (macResource->nameOffset != -1) {
+ context->file->seek(macResource->nameOffset + macMapOffset + macResMap.nameOffset);
+ macNameLen = context->file->readByte();
+ context->file->read(macResource->name, macNameLen);
+ }
+ }
+ }
+
+//
+ for (i = macResMap.numTypes, macResType = macResTypes; i > 0; i--, macResType++) {
+ //getting offsets & sizes of midi
+ if (((context->fileType & GAME_MUSICFILE_GM) > 0) && (macResType->id == ID_MIDI)) {
+
+ context->count = macResType->maxItemId + 1;
+ context->table = (ResourceData *)calloc(context->count, sizeof(*context->table));
+ for (j = macResType->items, macResource = macResType->resources; j > 0; j--, macResource++) {
+ context->file->seek(macDataOffset + macResource->dataOffset);
+ context->table[macResource->id].size = context->file->readUint32BE();
+ context->table[macResource->id].offset = context->file->pos();
+ }
+ notSagaContext = true;
+ break;
+ }
+ }
+
+//free
+ for (i = 0; i < macResMap.numTypes; i++) {
+ free(macResTypes[i].resources);
+ }
+ free(macResTypes);
+
+ if ((!notSagaContext) && (!loadSagaContext(context, MAC_BINARY_HEADER_SIZE, macDataSize))) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Resource::loadContext(ResourceContext *context) {
+ size_t i;
+ int j;
+ GamePatchDescription *patchDescription;
+ ResourceData *resourceData;
+ uint16 subjectResourceType;
+ ResourceContext *subjectContext;
+ uint32 subjectResourceId;
+ uint32 patchResourceId;
+ ResourceData *subjectResourceData;
+ byte *tableBuffer;
+ size_t tableSize;
+ bool isMacBinary;
+
+ if (!context->file->open(context->fileName)) {
+ return false;
+ }
+
+ context->isBigEndian = _vm->isBigEndian();
+
+ if (context->fileType & GAME_SWAPENDIAN)
+ context->isBigEndian = !context->isBigEndian;
+
+ isMacBinary = (context->fileType & GAME_MACBINARY) > 0;
+ context->fileType &= ~GAME_MACBINARY;
+
+ if (isMacBinary) {
+ if (!loadMacContext(context)) {
+ return false;
+ }
+ } else {
+ if (!loadSagaContext(context, 0, context->file->size())) {
+ return false;
+ }
+ }
+
+ //process internal patch files
+ if (GAME_PATCHFILE & context->fileType) {
+ subjectResourceType = ~GAME_PATCHFILE & context->fileType;
+ subjectContext = getContext((GameFileTypes)subjectResourceType);
+ if (subjectContext == NULL) {
+ error("Resource::loadContext() Subject context not found");
+ }
+ loadResource(context, context->count - 1, tableBuffer, tableSize);
+
+ MemoryReadStreamEndian readS2(tableBuffer, tableSize, context->isBigEndian);
+ for (i = 0; i < tableSize / 8; i++) {
+ subjectResourceId = readS2.readUint32();
+ patchResourceId = readS2.readUint32();
+ subjectResourceData = getResourceData(subjectContext, subjectResourceId);
+ resourceData = getResourceData(context, patchResourceId);
+ subjectResourceData->patchData = new PatchData(context->file);
+ subjectResourceData->offset = resourceData->offset;
+ subjectResourceData->size = resourceData->size;
+ }
+
+ }
+
+ //process external patch files
+ for (j = 0; j < _vm->getGameDescription()->patchesCount; j++) {
+ patchDescription = &_vm->getGameDescription()->patchDescriptions[j];
+ if ((patchDescription->fileType & context->fileType) != 0) {
+ if (patchDescription->resourceId < context->count) {
+ resourceData = &context->table[patchDescription->resourceId];
+ resourceData->patchData = new PatchData(patchDescription);
+ if (resourceData->patchData->_patchFile->open(patchDescription->fileName)) {
+ resourceData->offset = 0;
+ resourceData->size = resourceData->patchData->_patchFile->size();
+ } else {
+ delete resourceData->patchData;
+ resourceData->patchData = NULL;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Resource::createContexts() {
+ int i;
+ ResourceContext *context;
+ _contextsCount = _vm->getGameDescription()->filesCount;
+ _contexts = (ResourceContext*)calloc(_contextsCount, sizeof(*_contexts));
+
+ for (i = 0; i < _contextsCount; i++) {
+ context = &_contexts[i];
+ context->file = new Common::File();
+ context->fileName = _vm->getGameDescription()->filesDescriptions[i].fileName;
+ context->fileType = _vm->getGameDescription()->filesDescriptions[i].fileType;
+ context->serial = 0;
+
+ // IHNM has serveral different voice files, so we need to allow
+ // multiple resource contexts of the same type. We tell them
+ // apart by assigning each of the duplicates an unique serial
+ // number. The default behaviour when requesting a context will
+ // be to look for serial number 0.
+
+ for (int j = i - 1; j >= 0; j--) {
+ if (_contexts[j].fileType & context->fileType) {
+ context->serial = _contexts[j].serial + 1;
+ break;
+ }
+ }
+
+ if (!loadContext(context)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void Resource::clearContexts() {
+ int i;
+ size_t j;
+ ResourceContext *context;
+ if (_contexts == NULL) {
+ return;
+ }
+ for(i = 0; i < _contextsCount; i++) {
+ context = &_contexts[i];
+ delete context->file;
+ if (context->table != NULL) {
+ for(j = 0; j < context->count; j++) {
+ delete context->table[j].patchData;
+ }
+ }
+ free(context->table);
+ }
+ free(_contexts);
+ _contexts = NULL;
+}
+
+uint32 Resource::convertResourceId(uint32 resourceId) {
+
+ if (_vm->getGameType() == GType_ITE && _vm->isMacResources()) {
+ if (resourceId > 1537) {
+ return resourceId - 2;
+ } else {
+ if (resourceId == 1535 || resourceId == 1536) {
+ error ("Wrong resource number %d for Mac ITE", resourceId);
+ }
+ }
+ }
+
+ return resourceId;
+}
+
+void Resource::loadResource(ResourceContext *context, uint32 resourceId, byte*&resourceBuffer, size_t &resourceSize) {
+ Common::File *file;
+ uint32 resourceOffset;
+ ResourceData *resourceData;
+
+ debug(8, "loadResource %d", resourceId);
+
+ resourceData = getResourceData(context, resourceId);
+
+ file = context->getFile(resourceData);
+
+ resourceOffset = resourceData->offset;
+ resourceSize = resourceData->size;
+
+ resourceBuffer = (byte*)malloc(resourceSize);
+
+ file->seek((long)resourceOffset, SEEK_SET);
+
+ if (file->read(resourceBuffer, resourceSize) != resourceSize) {
+ error("Resource::loadResource() failed to read");
+ }
+}
+
+static int metaResourceTable[] = { 0, 326, 517, 677, 805, 968, 1165, 0, 1271 };
+
+void Resource::loadGlobalResources(int chapter, int actorsEntrance) {
+ if (chapter < 0)
+ chapter = 8;
+
+ // TODO
+ //if (module.voiceLUT)
+ // free module.voiceLUT;
+
+ // TODO: close chapter context, or rather reassign it in our case
+
+ ResourceContext *resourceContext;
+ ResourceContext *soundContext;
+ int i;
+
+ resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (resourceContext == NULL) {
+ error("Resource::loadGlobalResources() resource context not found");
+ }
+
+ soundContext = _vm->_resource->getContext(GAME_SOUNDFILE);
+ if (soundContext == NULL) {
+ error("Resource::loadGlobalResources() sound context not found");
+ }
+
+ byte *resourcePointer;
+ size_t resourceLength;
+
+ _vm->_resource->loadResource(resourceContext, metaResourceTable[chapter],
+ resourcePointer, resourceLength);
+
+ if (resourceLength == 0) {
+ error("Resource::loadGlobalResources wrong metaResource");
+ }
+
+ MemoryReadStream metaS(resourcePointer, resourceLength);
+
+ _metaResource.sceneIndex = metaS.readSint16LE();
+ _metaResource.objectCount = metaS.readSint16LE();
+ _metaResource.objectsStringsResourceID = metaS.readSint32LE();
+ _metaResource.inventorySpritesID = metaS.readSint32LE();
+ _metaResource.mainSpritesID = metaS.readSint32LE();
+ _metaResource.objectsResourceID = metaS.readSint32LE();
+ _metaResource.actorCount = metaS.readSint16LE();
+ _metaResource.actorsStringsResourceID = metaS.readSint32LE();
+ _metaResource.actorsResourceID = metaS.readSint32LE();
+ _metaResource.protagFaceSpritesID = metaS.readSint32LE();
+ _metaResource.field_22 = metaS.readSint32LE();
+ _metaResource.field_26 = metaS.readSint16LE();
+ _metaResource.protagStatesCount = metaS.readSint16LE();
+ _metaResource.protagStatesResourceID = metaS.readSint32LE();
+ _metaResource.cutawayListResourceID = metaS.readSint32LE();
+ _metaResource.songTableID = metaS.readSint32LE();
+
+ free(resourcePointer);
+
+ _vm->_actor->loadActorList(actorsEntrance, _metaResource.actorCount,
+ _metaResource.actorsResourceID, _metaResource.protagStatesCount,
+ _metaResource.protagStatesResourceID);
+
+ _vm->_actor->_protagonist->_sceneNumber = _metaResource.sceneIndex;
+
+ _vm->_actor->_objectsStrings.freeMem();
+
+ _vm->_resource->loadResource(resourceContext, _metaResource.objectsStringsResourceID, resourcePointer, resourceLength);
+ _vm->loadStrings(_vm->_actor->_objectsStrings, resourcePointer, resourceLength);
+ free(resourcePointer);
+
+ if (chapter >= _vm->_sndRes->_fxTableIDsLen) {
+ error("Chapter ID exceeds fxTableIDs length");
+ }
+
+ debug(0, "Going to read %d of %d", chapter, _vm->_sndRes->_fxTableIDs[chapter]);
+ _vm->_resource->loadResource(soundContext, _vm->_sndRes->_fxTableIDs[chapter],
+ resourcePointer, resourceLength);
+
+ if (resourceLength == 0) {
+ error("Resource::loadGlobalResources Can't load sound effects for current track");
+ }
+
+ free(_vm->_sndRes->_fxTable);
+
+ _vm->_sndRes->_fxTableLen = resourceLength / 4;
+ _vm->_sndRes->_fxTable = (FxTable *)malloc(sizeof(FxTable) * _vm->_sndRes->_fxTableLen);
+
+ MemoryReadStream fxS(resourcePointer, resourceLength);
+
+ for (i = 0; i < _vm->_sndRes->_fxTableLen; i++) {
+ _vm->_sndRes->_fxTable[i].res = fxS.readSint16LE();
+ _vm->_sndRes->_fxTable[i].vol = fxS.readSint16LE();
+ }
+ free(resourcePointer);
+
+ _vm->_interface->_defPortraits.freeMem();
+ _vm->_sprite->loadList(_metaResource.protagFaceSpritesID, _vm->_interface->_defPortraits);
+
+ _vm->_actor->_actorsStrings.freeMem();
+
+ _vm->_resource->loadResource(resourceContext, _metaResource.actorsStringsResourceID, resourcePointer, resourceLength);
+ _vm->loadStrings(_vm->_actor->_actorsStrings, resourcePointer, resourceLength);
+ free(resourcePointer);
+
+ _vm->_sprite->_inventorySprites.freeMem();
+ _vm->_sprite->loadList(_metaResource.inventorySpritesID, _vm->_sprite->_inventorySprites);
+
+ _vm->_sprite->_mainSprites.freeMem();
+ _vm->_sprite->loadList(_metaResource.mainSpritesID, _vm->_sprite->_mainSprites);
+
+ _vm->_actor->loadObjList(_metaResource.objectCount, _metaResource.objectsResourceID);
+
+ _vm->_resource->loadResource(resourceContext, _metaResource.cutawayListResourceID, resourcePointer, resourceLength);
+
+ if (resourceLength == 0) {
+ error("Resource::loadGlobalResources Can't load cutaway list");
+ }
+
+ _vm->_anim->loadCutawayList(resourcePointer, resourceLength);
+
+ _vm->_resource->loadResource(resourceContext, _metaResource.songTableID, resourcePointer, resourceLength);
+
+ if (resourceLength == 0) {
+ error("Resource::loadGlobalResources Can't load songs list for current track");
+ }
+
+ free(_vm->_music->_songTable);
+
+ _vm->_music->_songTableLen = resourceLength / 4;
+ _vm->_music->_songTable = (int32 *)malloc(sizeof(int32) * _vm->_music->_songTableLen);
+
+ MemoryReadStream songS(resourcePointer, resourceLength);
+
+ for (i = 0; i < _vm->_music->_songTableLen; i++)
+ _vm->_music->_songTable[i] = songS.readSint32LE();
+ free(resourcePointer);
+
+ int voiceLUTResourceID = 0;
+
+ _vm->_script->_globalVoiceLUT.freeMem();
+
+ switch (chapter) {
+ case 1:
+ _vm->_sndRes->setVoiceBank(1);
+ voiceLUTResourceID = 23;
+ break;
+ case 2:
+ _vm->_sndRes->setVoiceBank(2);
+ voiceLUTResourceID = 24;
+ break;
+ case 3:
+ _vm->_sndRes->setVoiceBank(3);
+ voiceLUTResourceID = 25;
+ break;
+ case 4:
+ _vm->_sndRes->setVoiceBank(4);
+ voiceLUTResourceID = 26;
+ break;
+ case 5:
+ _vm->_sndRes->setVoiceBank(5);
+ voiceLUTResourceID = 27;
+ break;
+ case 6:
+ _vm->_sndRes->setVoiceBank(6);
+ voiceLUTResourceID = 28;
+ break;
+ case 7:
+ break;
+ case 8:
+ _vm->_sndRes->setVoiceBank(0);
+ voiceLUTResourceID = 22;
+ break;
+ }
+
+ if (voiceLUTResourceID) {
+ _vm->_resource->loadResource(resourceContext, voiceLUTResourceID, resourcePointer, resourceLength);
+ _vm->_script->loadVoiceLUT(_vm->_script->_globalVoiceLUT, resourcePointer, resourceLength);
+ free(resourcePointer);
+ }
+
+ _vm->_spiritualBarometer = 0;
+ _vm->_scene->setChapterNumber(chapter);
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/rscfile.h b/engines/saga/rscfile.h
new file mode 100644
index 0000000000..434a6d9a96
--- /dev/null
+++ b/engines/saga/rscfile.h
@@ -0,0 +1,173 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// RSC Resource file management header file
+
+#ifndef SAGA_RSCFILE_H__
+#define SAGA_RSCFILE_H__
+
+#include "common/file.h"
+
+namespace Saga {
+
+#define MAC_BINARY_HEADER_SIZE 128
+#define RSC_TABLEINFO_SIZE 8
+#define RSC_TABLEENTRY_SIZE 8
+
+#define RSC_MIN_FILESIZE (RSC_TABLEINFO_SIZE + RSC_TABLEENTRY_SIZE + 1)
+
+struct PatchData {
+ bool _deletePatchFile;
+ Common::File *_patchFile;
+ GamePatchDescription *_patchDescription;
+
+ PatchData(GamePatchDescription *patchDescription): _patchDescription(patchDescription), _deletePatchFile(true) {
+ _patchFile = new Common::File();
+ }
+ PatchData(Common::File *patchFile): _patchDescription(NULL), _patchFile(patchFile), _deletePatchFile(false) {
+ }
+
+ ~PatchData() {
+ if (_deletePatchFile) {
+ delete _patchFile;
+ }
+ }
+};
+
+struct ResourceData {
+ size_t offset;
+ size_t size;
+ PatchData *patchData;
+ void fillSoundPatch(const GameSoundInfo *&soundInfo) {
+ if (patchData != NULL) {
+ if (patchData->_patchDescription != NULL) {
+ if (patchData->_patchDescription->soundInfo != NULL) {
+ soundInfo = patchData->_patchDescription->soundInfo;
+ }
+ }
+ }
+ }
+};
+
+struct ResourceContext {
+ const char *fileName;
+ uint16 fileType;
+ Common::File *file;
+ int serial;
+
+ bool isBigEndian;
+ ResourceData *table;
+ size_t count;
+
+ Common::File *getFile(ResourceData *resourceData) const {
+ if (resourceData->patchData != NULL) {
+ return resourceData->patchData->_patchFile;
+ } else {
+ return file;
+ }
+ }
+};
+
+struct MetaResource {
+ int16 sceneIndex;
+ int16 objectCount;
+ int32 objectsStringsResourceID;
+ int32 inventorySpritesID;
+ int32 mainSpritesID;
+ int32 objectsResourceID;
+ int16 actorCount;
+ int32 actorsStringsResourceID;
+ int32 actorsResourceID;
+ int32 protagFaceSpritesID;
+ int32 field_22;
+ int16 field_26;
+ int16 protagStatesCount;
+ int32 protagStatesResourceID;
+ int32 cutawayListResourceID;
+ int32 songTableID;
+
+ MetaResource() {
+ memset(this, 0, sizeof(*this));
+ }
+};
+
+class Resource {
+public:
+ Resource(SagaEngine *vm);
+ ~Resource();
+ bool createContexts();
+ void clearContexts();
+ void loadResource(ResourceContext *context, uint32 resourceId, byte*&resourceBuffer, size_t &resourceSize);
+ size_t getResourceSize(ResourceContext *context, uint32 resourceId);
+ uint32 convertResourceId(uint32 resourceId);
+
+ void loadGlobalResources(int chapter, int actorsEntrance);
+
+ ResourceContext *getContext(uint16 fileType, int serial = 0) {
+ int i;
+ for (i = 0; i < _contextsCount; i++) {
+ if ((_contexts[i].fileType & fileType) && _contexts[i].serial == serial) {
+ return &_contexts[i];
+ }
+ }
+ return NULL;
+ }
+
+ bool validResourceId(ResourceContext *context, uint32 resourceId) const {
+ return (resourceId < context->count);
+ }
+
+ size_t getResourceSize(ResourceContext *context, uint32 resourceId) const {
+ return getResourceData(context, resourceId)->size;
+ }
+
+ size_t getResourceOffset(ResourceContext *context, uint32 resourceId) const {
+ return getResourceData(context, resourceId)->offset;
+ }
+
+ ResourceData *getResourceData(ResourceContext *context, uint32 resourceId) const {
+ if (!validResourceId(context, resourceId)) {
+ warning("Resource::getResourceData() wrong resourceId %d", resourceId);
+ assert(0);
+ }
+ return &context->table[resourceId];
+ }
+
+private:
+ SagaEngine *_vm;
+ ResourceContext *_contexts;
+ int _contextsCount;
+
+ bool loadContext(ResourceContext *context);
+ bool loadMacContext(ResourceContext *context);
+ bool loadSagaContext(ResourceContext *context, uint32 contextOffset, uint32 contextSize);
+
+
+public:
+ MetaResource _metaResource;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp
new file mode 100644
index 0000000000..4adda480c6
--- /dev/null
+++ b/engines/saga/saga.cpp
@@ -0,0 +1,505 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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/stdafx.h"
+
+#include "base/gameDetector.h"
+#include "base/plugins.h"
+#include "backends/fs/fs.h"
+
+#include "common/file.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+
+#include "sound/mixer.h"
+
+#include "saga/saga.h"
+
+#include "saga/rscfile.h"
+#include "saga/gfx.h"
+#include "saga/render.h"
+#include "saga/actor.h"
+#include "saga/animation.h"
+#include "saga/console.h"
+#include "saga/events.h"
+#include "saga/font.h"
+#include "saga/interface.h"
+#include "saga/isomap.h"
+#include "saga/puzzle.h"
+#include "saga/script.h"
+#include "saga/scene.h"
+#include "saga/sndres.h"
+#include "saga/sprite.h"
+#include "saga/sound.h"
+#include "saga/music.h"
+#include "saga/palanim.h"
+#include "saga/objectmap.h"
+#include "saga/resnames.h"
+
+static const GameSettings saga_games[] = {
+ {"ite", "Inherit the Earth", 0},
+ {"ihnm", "I Have No Mouth and I Must Scream", GF_DEFAULT_TO_1X_SCALER },
+ {0, 0, 0}
+};
+
+GameList Engine_SAGA_gameList() {
+ GameList games;
+ const GameSettings *g = saga_games;
+
+ while (g->gameid) {
+ games.push_back(*g);
+ g++;
+ }
+
+ return games;
+}
+
+DetectedGameList Engine_SAGA_detectGames(const FSList &fslist) {
+ return Saga::GAME_ProbeGame(fslist);
+}
+
+Engine *Engine_SAGA_create(GameDetector *detector, OSystem *syst) {
+ return new Saga::SagaEngine(detector, syst);
+}
+
+REGISTER_PLUGIN(SAGA, "SAGA Engine")
+
+namespace Saga {
+
+#define MAX_TIME_DELTA 100
+
+SagaEngine::SagaEngine(GameDetector *detector, OSystem *syst)
+ : Engine(syst),
+ _targetName(detector->_targetName) {
+
+ _leftMouseButtonPressed = _rightMouseButtonPressed = false;
+
+ _console = NULL;
+ _quit = false;
+
+ _resource = NULL;
+ _sndRes = NULL;
+ _events = NULL;
+ _font = NULL;
+ _sprite = NULL;
+ _anim = NULL;
+ _script = NULL;
+ _interface = NULL;
+ _actor = NULL;
+ _palanim = NULL;
+ _scene = NULL;
+ _isoMap = NULL;
+ _gfx = NULL;
+ _console = NULL;
+ _render = NULL;
+ _music = NULL;
+ _sound = NULL;
+ _puzzle = NULL;
+
+ _frameCount = 0;
+ _globalFlags = 0;
+ memset(_ethicsPoints, 0, sizeof(_ethicsPoints));
+
+ // The Linux version of Inherit the Earth puts all data files in an
+ // 'itedata' sub-directory, except for voices.rsc
+ Common::File::addDefaultDirectory(_gameDataPath + "itedata/");
+
+ // The Windows version of Inherit the Earth puts various data files in
+ // other subdirectories.
+ Common::File::addDefaultDirectory(_gameDataPath + "graphics/");
+ Common::File::addDefaultDirectory(_gameDataPath + "music/");
+ Common::File::addDefaultDirectory(_gameDataPath + "sound/");
+
+ // The Multi-OS version puts the voices file in the root directory of
+ // the CD. The rest of the data files are in game/itedata
+ Common::File::addDefaultDirectory(_gameDataPath + "game/itedata/");
+
+ // Mac CD Wyrmkeep
+ Common::File::addDefaultDirectory(_gameDataPath + "patch/");
+
+ // Setup mixer
+ if (!_mixer->isReady()) {
+ warning("Sound initialization failed.");
+ }
+
+ _displayClip.left = _displayClip.top = 0;
+}
+
+SagaEngine::~SagaEngine() {
+ if (_scene != NULL) {
+ if (_scene->isSceneLoaded()) {
+ _scene->endScene();
+ }
+ }
+
+ delete _puzzle;
+ delete _sndRes;
+ delete _events;
+ delete _font;
+ delete _sprite;
+ delete _anim;
+ delete _script;
+ delete _interface;
+ delete _actor;
+ delete _palanim;
+ delete _scene;
+ delete _isoMap;
+ delete _render;
+ delete _music;
+ delete _sound;
+ delete _gfx;
+ delete _console;
+
+ delete _resource;
+}
+
+void SagaEngine::errorString(const char *buf1, char *buf2) {
+ strcpy(buf2, buf1);
+}
+
+int SagaEngine::init(GameDetector &detector) {
+ _soundVolume = ConfMan.getInt("sfx_volume") / 25;
+ _musicVolume = ConfMan.getInt("music_volume") / 25;
+ _subtitlesEnabled = ConfMan.getBool("subtitles");
+ _readingSpeed = ConfMan.getInt("talkspeed");
+ _copyProtection = ConfMan.getBool("copy_protection");
+
+ if (_readingSpeed > 3)
+ _readingSpeed = 0;
+
+ _resource = new Resource(this);
+
+ // Add some default directories
+ // Win32 demo & full game
+ Common::File::addDefaultDirectory("graphics");
+ Common::File::addDefaultDirectory("music");
+ Common::File::addDefaultDirectory("sound");
+
+ // Linux demo
+ Common::File::addDefaultDirectory("itedata");
+
+ // Mac demos & full game
+ Common::File::addDefaultDirectory("patch");
+
+ // Process command line
+
+ // Detect game and open resource files
+ if (!initGame()) {
+ return FAILURE;
+ }
+
+ // Initialize engine modules
+ _sndRes = new SndRes(this);
+ _events = new Events(this);
+ _font = new Font(this);
+ _sprite = new Sprite(this);
+ _anim = new Anim(this);
+ _script = new Script(this);
+ _interface = new Interface(this); // requires script module
+ _scene = new Scene(this);
+ _actor = new Actor(this);
+ _palanim = new PalAnim(this);
+ _isoMap = new IsoMap(this);
+ _puzzle = new Puzzle(this);
+
+ // System initialization
+
+ _previousTicks = _system->getMillis();
+
+ // Initialize graphics
+ _gfx = new Gfx(this, _system, getDisplayWidth(), getDisplayHeight(), detector);
+
+ // Graphics driver should be initialized before console
+ _console = new Console(this);
+
+ // Graphics should be initialized before music
+ int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
+ bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
+ bool adlib = (midiDriver == MD_ADLIB);
+
+ MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+ if (native_mt32)
+ driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+
+ _music = new Music(this, _mixer, driver, _musicVolume);
+ _music->setNativeMT32(native_mt32);
+ _music->setAdlib(adlib);
+
+ if (!_musicVolume) {
+ debug(1, "Music disabled.");
+ }
+
+ _render = new Render(this, _system);
+ if (!_render->initialized()) {
+ return FAILURE;
+ }
+
+ // Initialize system specific sound
+ _sound = new Sound(this, _mixer, _soundVolume);
+ if (!_soundVolume) {
+ debug(1, "Sound disabled.");
+ }
+
+ _interface->converseInit();
+ _script->setVerb(_script->getVerbType(kVerbWalkTo));
+
+ _music->setVolume(-1, 1);
+
+ _gfx->initPalette();
+
+ // FIXME: This is the ugly way of reducing redraw overhead. It works
+ // well for 320x200 but it's unclear how well it will work for
+ // 640x480.
+
+ if (getGameType() == GType_ITE)
+ _system->setFeatureState(OSystem::kFeatureAutoComputeDirtyRects, true);
+
+ return SUCCESS;
+}
+
+int SagaEngine::go() {
+ int msec = 0;
+
+ _previousTicks = _system->getMillis();
+
+ if (ConfMan.hasKey("start_scene")) {
+ _scene->changeScene(ConfMan.getInt("start_scene"), 0, kTransitionNoFade);
+ } else if (ConfMan.hasKey("boot_param")) {
+ if (getGameType() == GType_ITE)
+ _interface->addToInventory(_actor->objIndexToId(ITE_OBJ_MAGIC_HAT));
+ _scene->changeScene(ConfMan.getInt("boot_param"), 0, kTransitionNoFade);
+ } else if (ConfMan.hasKey("save_slot")) {
+ // First scene sets up palette
+ _scene->changeScene(getStartSceneNumber(), 0, kTransitionNoFade);
+ _events->handleEvents(0); // Process immediate events
+
+ char *fileName;
+ fileName = calcSaveFileName(ConfMan.getInt("save_slot"));
+ load(fileName);
+ _interface->setMode(kPanelMain);
+ } else {
+ _framesEsc = 0;
+ _scene->startScene();
+ }
+
+ uint32 currentTicks;
+
+ while (!_quit) {
+ if (_console->isAttached())
+ _console->onFrame();
+
+ if (_render->getFlags() & RF_RENDERPAUSE) {
+ // Freeze time while paused
+ _previousTicks = _system->getMillis();
+ } else {
+ currentTicks = _system->getMillis();
+ // Timer has rolled over after 49 days
+ if (currentTicks < _previousTicks)
+ msec = 0;
+ else {
+ msec = currentTicks - _previousTicks;
+ _previousTicks = currentTicks;
+ }
+ if (msec > MAX_TIME_DELTA) {
+ msec = MAX_TIME_DELTA;
+ }
+
+ // Since Puzzle is actorless, we do it here
+ if (_puzzle->isActive()) {
+ _actor->handleSpeech(msec);
+ } else if (!_scene->isInIntro()) {
+ if (_interface->getMode() == kPanelMain ||
+ _interface->getMode() == kPanelConverse ||
+ _interface->getMode() == kPanelCutaway ||
+ _interface->getMode() == kPanelNull ||
+ _interface->getMode() == kPanelChapterSelection)
+ _actor->direct(msec);
+ }
+
+ _events->handleEvents(msec);
+ _script->executeThreads(msec);
+ }
+ // Per frame processing
+ _render->drawScene();
+ _system->delayMillis(10);
+ }
+
+ return 0;
+}
+
+void SagaEngine::loadStrings(StringsTable &stringsTable, const byte *stringsPointer, size_t stringsLength) {
+ uint16 stringsCount;
+ size_t offset;
+ int i;
+
+ if (stringsLength == 0) {
+ error("SagaEngine::loadStrings() Error loading strings list resource");
+ }
+
+ stringsTable.stringsPointer = (byte*)malloc(stringsLength);
+ memcpy(stringsTable.stringsPointer, stringsPointer, stringsLength);
+
+
+ MemoryReadStreamEndian scriptS(stringsTable.stringsPointer, stringsLength, isBigEndian()); //TODO: get endianess from context
+
+ offset = scriptS.readUint16();
+ stringsCount = offset / 2;
+ stringsTable.strings = (const char **)malloc(stringsCount * sizeof(*stringsTable.strings));
+ i = 0;
+ scriptS.seek(0);
+ while (i < stringsCount) {
+ offset = scriptS.readUint16();
+ if (offset == stringsLength) {
+ stringsCount = i;
+ stringsTable.strings = (const char **)realloc(stringsTable.strings, stringsCount * sizeof(*stringsTable.strings));
+ break;
+ }
+ if (offset > stringsLength) {
+ error("SagaEngine::loadStrings wrong strings table");
+ }
+ stringsTable.strings[i] = (const char *)stringsTable.stringsPointer + offset;
+ debug(9, "string[%i]=%s", i, stringsTable.strings[i]);
+ i++;
+ }
+ stringsTable.stringsCount = stringsCount;
+}
+
+const char *SagaEngine::getObjectName(uint16 objectId) {
+ ActorData *actor;
+ ObjectData *obj;
+ const HitZone *hitZone;
+ switch (objectTypeId(objectId)) {
+ case kGameObjectObject:
+ obj = _actor->getObj(objectId);
+ if (getGameType() == GType_ITE)
+ return _script->_mainStrings.getString(obj->_nameIndex);
+ return _actor->_objectsStrings.getString(obj->_nameIndex);
+ case kGameObjectActor:
+ actor = _actor->getActor(objectId);
+ return _actor->_actorsStrings.getString(actor->_nameIndex);
+ case kGameObjectHitZone:
+ hitZone = _scene->_objectMap->getHitZone(objectIdToIndex(objectId));
+ return _scene->_sceneStrings.getString(hitZone->getNameIndex());
+ }
+ warning("SagaEngine::getObjectName name not found for 0x%X", objectId);
+ return NULL;
+}
+
+const char *SagaEngine::getTextString(int textStringId) {
+ const char *string;
+ int lang = (getLanguage() == Common::DE_DEU) ? 1 : 0;
+
+ string = ITEinterfaceTextStrings[lang][textStringId];
+ if (!string)
+ string = ITEinterfaceTextStrings[0][textStringId];
+
+ return string;
+}
+
+void SagaEngine::getExcuseInfo(int verb, const char *&textString, int &soundResourceId) {
+ textString = NULL;
+
+ if (verb == _script->getVerbType(kVerbPickUp)) {
+ textString = getTextString(kTextICantPickup);
+ soundResourceId = RID_BOAR_VOICE_007;
+ } else
+ if (verb == _script->getVerbType(kVerbLookAt)) {
+ textString = getTextString(kTextNothingSpecial);
+ soundResourceId = RID_BOAR_VOICE_006;
+ }
+ if (verb == _script->getVerbType(kVerbOpen)) {
+ textString = getTextString(kTextNoPlaceToOpen);
+ soundResourceId = RID_BOAR_VOICE_000;
+ }
+ if (verb == _script->getVerbType(kVerbClose)) {
+ textString = getTextString(kTextNoOpening);
+ soundResourceId = RID_BOAR_VOICE_002;
+ }
+ if (verb == _script->getVerbType(kVerbUse)) {
+ textString = getTextString(kTextDontKnow);
+ soundResourceId = RID_BOAR_VOICE_005;
+ }
+}
+
+ColorId SagaEngine::KnownColor2ColorId(KnownColor knownColor) {
+ ColorId colorId = kITEColorTransBlack;
+
+ if (getGameType() == GType_ITE) {
+ switch (knownColor) {
+ case(kKnownColorTransparent):
+ colorId = kITEColorTransBlack;
+ break;
+
+ case (kKnownColorBrightWhite):
+ colorId = kITEColorBrightWhite;
+ break;
+ case (kKnownColorBlack):
+ colorId = kITEColorBlack;
+ break;
+
+ case (kKnownColorSubtitleTextColor):
+ colorId = (ColorId)255;
+ break;
+ case (kKnownColorVerbText):
+ colorId = kITEColorBlue;
+ break;
+ case (kKnownColorVerbTextShadow):
+ colorId = kITEColorBlack;
+ break;
+ case (kKnownColorVerbTextActive):
+ colorId = (ColorId)96;
+ break;
+
+ default:
+ error("SagaEngine::KnownColor2ColorId unknown color %i", knownColor);
+ }
+ } else if (getGameType() == GType_IHNM) {
+ switch (knownColor)
+ {
+ case(kKnownColorTransparent):
+ colorId = kITEColorTransBlack;
+ break;
+
+ case (kKnownColorBlack):
+ colorId = kIHNMColorBlack;
+ break;
+
+ case (kKnownColorVerbText):
+ colorId = (ColorId)253;
+ break;
+ case (kKnownColorVerbTextShadow):
+ colorId = (ColorId)15;
+ break;
+ case (kKnownColorVerbTextActive):
+ colorId = (ColorId)252;
+ break;
+
+ default:
+ error("SagaEngine::KnownColor2ColorId unknown color %i", knownColor);
+ }
+ }
+ return colorId;
+}
+
+
+} // End of namespace Saga
diff --git a/engines/saga/saga.h b/engines/saga/saga.h
new file mode 100644
index 0000000000..f2111265bd
--- /dev/null
+++ b/engines/saga/saga.h
@@ -0,0 +1,741 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 SAGA_H
+#define SAGA_H
+
+#include "base/engine.h"
+#include "base/plugins.h"
+
+#include "common/stream.h"
+
+#include "saga/gfx.h"
+#include "saga/list.h"
+
+namespace Saga {
+
+class SndRes;
+class Sound;
+class Music;
+class Anim;
+class Render;
+class IsoMap;
+class Gfx;
+class Script;
+class Actor;
+class Font;
+class Sprite;
+class Scene;
+class Interface;
+class Console;
+class Events;
+class PalAnim;
+class Puzzle;
+class Resource;
+
+struct ResourceContext;
+struct StringList;
+
+//#define MIN_IMG_RLECODE 3
+//#define MODEX_SCANLINE_LIMIT 200 //TODO: remove
+
+#define SAGA_IMAGE_DATA_OFFSET 776
+#define SAGA_IMAGE_HEADER_LEN 8
+
+#define MAXPATH 512 //TODO: remove
+
+#define SAVE_TITLE_SIZE 28
+#define MAX_SAVES 96
+#define MAX_FILE_NAME 256
+
+#define ID_NOTHING 0
+#define ID_PROTAG 1
+#define OBJECT_TYPE_SHIFT 13
+#define OBJECT_TYPE_MASK ((1 << OBJECT_TYPE_SHIFT) - 1)
+
+#define OBJ_SPRITE_BASE 9
+
+#define memoryError(Place) error("%s Memory allocation error.", Place)
+
+enum ERRORCODE {
+ MEM = -2,//todo: remove
+ FAILURE = -1,
+ SUCCESS = 0
+};
+
+enum SAGAGameType {
+ GType_ITE,
+ GType_IHNM
+};
+
+enum GameObjectTypes {
+ kGameObjectNone = 0,
+ kGameObjectActor = 1,
+ kGameObjectObject = 2,
+ kGameObjectHitZone = 3,
+ kGameObjectStepZone = 4
+};
+
+enum ScriptTimings {
+ kScriptTimeTicksPerSecond = (728L/10L),
+ kRepeatSpeedTicks = (728L/10L)/3,
+ kNormalFadeDuration = 320, // 64 steps, 5 msec each
+ kQuickFadeDuration = 64, // 64 steps, 1 msec each
+ kPuzzleHintTime = 30000000L // 30 secs. used in timer
+};
+
+enum Directions {
+ kDirUp = 0,
+ kDirUpRight = 1,
+ kDirRight = 2,
+ kDirDownRight = 3,
+ kDirDown = 4,
+ kDirDownLeft = 5,
+ kDirLeft = 6,
+ kDirUpLeft = 7
+};
+
+enum HitZoneFlags {
+ kHitZoneEnabled = (1 << 0), // Zone is enabled
+ kHitZoneExit = (1 << 1), // Causes char to exit
+
+ // The following flag causes the zone to act differently.
+ // When the actor hits the zone, it will immediately begin walking
+ // in the specified direction, and the actual specified effect of
+ // the zone will be delayed until the actor leaves the zone.
+ kHitZoneAutoWalk = (1 << 2),
+
+ // When set on a hit zone, this causes the character not to walk
+ // to the object (but they will look at it).
+ kHitZoneNoWalk = (1 << 2),
+
+ // zone activates only when character stops walking
+ kHitZoneTerminus = (1 << 3),
+
+ // Hit zones only - when the zone is clicked on it projects the
+ // click point downwards from the middle of the zone until it
+ // reaches the lowest point in the zone.
+ kHitZoneProject = (1 << 3)
+};
+
+
+enum PanelButtonType {
+ kPanelButtonVerb = 1 << 0,
+ kPanelButtonArrow = 1 << 1,
+ kPanelButtonConverseText = 1 << 2,
+ kPanelButtonInventory = 1 << 3,
+
+ kPanelButtonOption = 1 << 4,
+ kPanelButtonOptionSlider = 1 << 5,
+ kPanelButtonOptionSaveFiles = 1 << 6,
+ kPanelButtonOptionText = 1 << 7,
+
+ kPanelButtonQuit = 1 << 8,
+ kPanelButtonQuitText = 1 << 9,
+
+ kPanelButtonLoad = 1 << 10,
+ kPanelButtonLoadText = 1 << 11,
+
+ kPanelButtonSave = 1 << 12,
+ kPanelButtonSaveText = 1 << 13,
+ kPanelButtonSaveEdit = 1 << 14,
+
+ kPanelButtonProtectText = 1 << 15,
+ kPanelButtonProtectEdit = 1 << 16,
+
+ kPanelAllButtons = 0xFFFFF
+};
+
+enum TextStringIds {
+ kTextWalkTo,
+ kTextLookAt,
+ kTextPickUp,
+ kTextTalkTo,
+ kTextOpen,
+ kTextClose,
+ kTextUse,
+ kTextGive,
+ kTextOptions,
+ kTextTest,
+ kTextDemo,
+ kTextHelp,
+ kTextQuitGame,
+ kTextFast,
+ kTextSlow,
+ kTextOn,
+ kTextOff,
+ kTextContinuePlaying,
+ kTextLoad,
+ kTextSave,
+ kTextGameOptions,
+ kTextReadingSpeed,
+ kTextMusic,
+ kTextSound,
+ kTextCancel,
+ kTextQuit,
+ kTextOK,
+ kTextMid,
+ kTextClick,
+ kText10Percent,
+ kText20Percent,
+ kText30Percent,
+ kText40Percent,
+ kText50Percent,
+ kText60Percent,
+ kText70Percent,
+ kText80Percent,
+ kText90Percent,
+ kTextMax,
+ kTextQuitTheGameQuestion,
+ kTextLoadSuccessful,
+ kTextEnterSaveGameName,
+ kTextGiveTo,
+ kTextUseWidth,
+ kTextNewSave,
+ kTextICantPickup,
+ kTextNothingSpecial,
+ kTextNoPlaceToOpen,
+ kTextNoOpening,
+ kTextDontKnow,
+ kTextShowDialog,
+ kTextEnterProtectAnswer
+};
+
+struct ImageHeader {
+ int width;
+ int height;
+};
+
+struct StringsTable {
+ byte *stringsPointer;
+ int stringsCount;
+ const char **strings;
+
+ const char *getString(int index) const {
+ if ((stringsCount <= index) || (index < 0)) {
+ error("StringList::getString wrong index 0x%X (%d)", index, stringsCount);
+ }
+ return strings[index];
+ }
+
+ void freeMem() {
+ free(strings);
+ free(stringsPointer);
+ memset(this, 0, sizeof(*this));
+ }
+
+ StringsTable() {
+ memset(this, 0, sizeof(*this));
+ }
+ ~StringsTable() {
+ freeMem();
+ }
+};
+
+enum GameIds {
+ // Dreamers Guild
+ GID_ITE_DEMO_G = 0,
+ GID_ITE_DISK_G,
+ GID_ITE_DISK_G2,
+ GID_ITE_CD_G,
+ GID_ITE_CD_G2,
+ GID_ITE_MACCD_G,
+
+ // Wyrmkeep
+ GID_ITE_CD, // data for Win rerelease is same as in old DOS
+ GID_ITE_WINCD, // but it has a bunch of patch files
+ GID_ITE_MACCD,
+ GID_ITE_LINCD,
+ GID_ITE_MULTICD, // Wyrmkeep combined Windows/Mac/Linux version
+ GID_ITE_WINDEMO1, // older Wyrmkeep windows demo
+ GID_ITE_MACDEMO1, // older Wyrmkeep mac demo
+ GID_ITE_LINDEMO,
+ GID_ITE_WINDEMO2,
+ GID_ITE_MACDEMO2,
+
+ // German
+ GID_ITE_DISK_DE,
+ GID_ITE_DISK_DE2,
+ GID_ITE_AMIGACD_DE, // TODO
+ GID_ITE_OLDMAC_DE, // TODO
+ GID_ITE_AMIGA_FL_DE,// TODO
+ GID_ITE_CD_DE, // reported by mld. Bestsellergamers cover disk
+ GID_ITE_CD_DE2,
+ GID_ITE_AMIGA_AGA_DEMO, // TODO
+ GID_ITE_AMIGA_ECS_DEMO, // TODO
+
+ GID_IHNM_DEMO,
+ GID_IHNM_CD,
+ GID_IHNM_CD_DE, // reported by mld. German retail
+ GID_IHNM_CD_ES,
+ GID_IHNM_CD_RU,
+ GID_IHNM_CD_FR
+};
+
+enum GameFileTypes {
+ GAME_RESOURCEFILE = 1 << 0,
+ GAME_SCRIPTFILE = 1 << 1,
+ GAME_SOUNDFILE = 1 << 2,
+ GAME_VOICEFILE = 1 << 3,
+ GAME_DEMOFILE = 1 << 4,
+ GAME_MUSICFILE = 1 << 5,
+ GAME_MUSICFILE_GM = 1 << 6,
+ GAME_MUSICFILE_FM = 1 << 7,
+ GAME_PATCHFILE = 1 << 8,
+ GAME_MACBINARY = 1 << 9,
+ GAME_SWAPENDIAN = 1 << 10
+};
+
+enum GameSoundTypes {
+ kSoundPCM = 0,
+ kSoundVOX = 1,
+ kSoundVOC = 2,
+ kSoundWAV = 3,
+ kSoundMacPCM = 4
+};
+
+enum GameFeatures {
+ GF_BIG_ENDIAN_DATA = 1 << 0,
+ GF_WYRMKEEP = 1 << 1,
+ GF_CD_FX = 1 << 2,
+ GF_SCENE_SUBSTITUTES = 1 << 3
+};
+
+enum ColorId {
+ kITEColorTransBlack = 0x00,
+ kITEColorBrightWhite = 0x01,
+ kITEColorWhite = 0x02,
+ kITEColorLightGrey = 0x04,
+ kITEColorGrey = 0x0a,
+ kITEColorDarkGrey = 0x0b,
+ kITEColorDarkGrey0C = 0x0C,
+ kITEColorBlack = 0x0f,
+ kITEColorRed = 0x65,
+ kITEColorDarkBlue8a = 0x8a,
+ kITEColorBlue89 = 0x89,
+ kITEColorLightBlue92 = 0x92,
+ kITEColorBlue = 0x93,
+ kITEColorLightBlue94 = 0x94,
+ kITEColorLightBlue96 = 0x96,
+ kITEColorGreen = 0xba,
+
+ kIHNMColorBlack = 0xfa,
+ kIHNMColorPortrait = 0xfe
+};
+
+enum KnownColor {
+ kKnownColorTransparent,
+ kKnownColorBrightWhite,
+ kKnownColorBlack,
+
+ kKnownColorSubtitleTextColor,
+ kKnownColorVerbText,
+ kKnownColorVerbTextShadow,
+ kKnownColorVerbTextActive
+};
+
+struct GameSoundInfo {
+ GameSoundTypes resourceType;
+ long frequency;
+ int sampleBits;
+ bool stereo;
+ bool isBigEndian;
+ bool isSigned;
+};
+
+struct GameFontDescription {
+ uint32 fontResourceId;
+};
+
+struct GameResourceDescription {
+ uint32 sceneLUTResourceId;
+ uint32 moduleLUTResourceId;
+ uint32 mainPanelResourceId;
+ uint32 conversePanelResourceId;
+ uint32 optionPanelResourceId;
+ uint32 mainSpritesResourceId;
+ uint32 mainPanelSpritesResourceId;
+ uint32 defaultPortraitsResourceId;
+ uint32 mainStringsResourceId;
+ uint32 actorsStringsResourceId;
+};
+
+struct GameFileDescription {
+ const char *fileName;
+ uint16 fileType;
+};
+
+struct GamePatchDescription {
+ const char *fileName;
+ uint16 fileType;
+ uint32 resourceId;
+ GameSoundInfo *soundInfo;
+};
+
+struct PanelButton {
+ PanelButtonType type;
+ int xOffset;
+ int yOffset;
+ int width;
+ int height;
+ int id;
+ uint16 ascii;
+ int state;
+ int upSpriteNumber;
+ int downSpriteNumber;
+ int overSpriteNumber;
+};
+
+struct GameDisplayInfo {
+ int logicalWidth;
+ int logicalHeight;
+
+ int pathStartY;
+ int sceneHeight;
+
+ int statusXOffset;
+ int statusYOffset;
+ int statusWidth;
+ int statusHeight;
+ int statusTextY;
+ int statusTextColor;
+ int statusBGColor;
+
+ int saveReminderXOffset;
+ int saveReminderYOffset;
+ int saveReminderWidth;
+ int saveReminderHeight;
+ int saveReminderFirstSpriteNumber;
+ int saveReminderSecondSpriteNumber;
+
+ int leftPortraitXOffset;
+ int leftPortraitYOffset;
+ int rightPortraitXOffset;
+ int rightPortraitYOffset;
+
+ int inventoryUpButtonIndex;
+ int inventoryDownButtonIndex;
+ int inventoryRows;
+ int inventoryColumns;
+
+ int mainPanelXOffset;
+ int mainPanelYOffset;
+ int mainPanelButtonsCount;
+ PanelButton *mainPanelButtons;
+
+ int converseMaxTextWidth;
+ int converseTextHeight;
+ int converseTextLines;
+ int converseUpButtonIndex;
+ int converseDownButtonIndex;
+
+ int conversePanelXOffset;
+ int conversePanelYOffset;
+ int conversePanelButtonsCount;
+ PanelButton *conversePanelButtons;
+
+ int optionSaveFilePanelIndex;
+ int optionSaveFileSliderIndex;
+ uint optionSaveFileVisible;
+
+ int optionPanelXOffset;
+ int optionPanelYOffset;
+ int optionPanelButtonsCount;
+ PanelButton *optionPanelButtons;
+
+ int quitPanelXOffset;
+ int quitPanelYOffset;
+ int quitPanelWidth;
+ int quitPanelHeight;
+ int quitPanelButtonsCount;
+ PanelButton *quitPanelButtons;
+
+ int loadPanelXOffset;
+ int loadPanelYOffset;
+ int loadPanelWidth;
+ int loadPanelHeight;
+ int loadPanelButtonsCount;
+ PanelButton *loadPanelButtons;
+
+ int saveEditIndex;
+ int savePanelXOffset;
+ int savePanelYOffset;
+ int savePanelWidth;
+ int savePanelHeight;
+ int savePanelButtonsCount;
+ PanelButton *savePanelButtons;
+
+ int protectEditIndex;
+ int protectPanelXOffset;
+ int protectPanelYOffset;
+ int protectPanelWidth;
+ int protectPanelHeight;
+ int protectPanelButtonsCount;
+ PanelButton *protectPanelButtons;
+};
+
+
+struct GameDescription {
+ const char *name;
+ SAGAGameType gameType;
+ GameIds gameId;
+ const char *title;
+ GameDisplayInfo *gameDisplayInfo;
+ int startSceneNumber;
+ GameResourceDescription *resourceDescription;
+ int filesCount;
+ GameFileDescription *filesDescriptions;
+ int fontsCount;
+ GameFontDescription *fontDescriptions;
+ GameSoundInfo *voiceInfo;
+ GameSoundInfo *sfxInfo;
+ GameSoundInfo *musicInfo;
+ int patchesCount;
+ GamePatchDescription *patchDescriptions;
+ uint32 features;
+ Common::Language language;
+ Common::Platform platform;
+
+ GameSettings toGameSettings() const {
+ GameSettings dummy = { name, title, features };
+ return dummy;
+ }
+};
+
+struct SaveFileData {
+ char name[SAVE_TITLE_SIZE];
+ uint slotNumber;
+};
+
+struct SaveGameHeader {
+ uint32 type;
+ uint32 size;
+ uint32 version;
+ char name[SAVE_TITLE_SIZE];
+};
+
+inline int ticksToMSec(int tick) {
+ return tick * 1000 / kScriptTimeTicksPerSecond;
+}
+
+inline int clamp(int minValue, int value, int maxValue) {
+ if (value <= minValue) {
+ return minValue;
+ } else {
+ if (value >= maxValue) {
+ return maxValue;
+ } else {
+ return value;
+ }
+ }
+}
+
+inline int integerCompare(int i1, int i2) {
+ return ((i1) > (i2) ? 1 : ((i1) < (i2) ? -1 : 0));
+}
+
+inline int objectTypeId(uint16 objectId) {
+ return objectId >> OBJECT_TYPE_SHIFT;
+}
+
+inline int objectIdToIndex(uint16 objectId) {
+ return OBJECT_TYPE_MASK & objectId;
+}
+
+inline uint16 objectIndexToId(int type, int index) {
+ return (type << OBJECT_TYPE_SHIFT) | (OBJECT_TYPE_MASK & index);
+}
+
+
+DetectedGameList GAME_ProbeGame(const FSList &fslist, int **matches = NULL);
+
+class SagaEngine : public Engine {
+ friend class Scene;
+
+ void errorString(const char *buf_input, char *buf_output);
+
+protected:
+ int go();
+ int init(GameDetector &detector);
+public:
+ SagaEngine(GameDetector * detector, OSystem * syst);
+ virtual ~SagaEngine();
+ void shutDown() { _quit = true; }
+
+ void save(const char *fileName, const char *saveName);
+ void load(const char *fileName);
+ uint32 getCurrentLoadVersion() {
+ return _saveHeader.version;
+ }
+ void fillSaveList();
+ char *calcSaveFileName(uint slotNumber);
+
+ SaveFileData *getSaveFile(uint idx);
+ uint getSaveSlotNumber(uint idx);
+ uint getNewSaveSlotNumber();
+ bool locateSaveFile(char *saveName, uint &titleNumber);
+ bool isSaveListFull() const {
+ return _saveFilesMaxCount == _saveFilesCount;
+ }
+ uint getSaveFilesCount() const {
+ return isSaveListFull() ? _saveFilesCount : _saveFilesCount + 1;
+ }
+
+ int16 _framesEsc;
+
+ uint32 _globalFlags;
+ int16 _ethicsPoints[8];
+ int _spiritualBarometer;
+
+ int _soundVolume;
+ int _musicVolume;
+ bool _subtitlesEnabled;
+ int _readingSpeed;
+
+ bool _copyProtection;
+
+ SndRes *_sndRes;
+ Sound *_sound;
+ Music *_music;
+ Anim *_anim;
+ Render *_render;
+ IsoMap *_isoMap;
+ Gfx *_gfx;
+ Script *_script;
+ Actor *_actor;
+ Font *_font;
+ Sprite *_sprite;
+ Scene *_scene;
+ Interface *_interface;
+ Console *_console;
+ Events *_events;
+ PalAnim *_palanim;
+ Puzzle *_puzzle;
+ Resource *_resource;
+
+
+ /** Random number generator */
+ Common::RandomSource _rnd;
+
+private:
+ int decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, byte *outbuf, size_t outbuf_len);
+ int flipImage(byte *img_buf, int columns, int scanlines);
+ int unbankBGImage(byte *dest_buf, const byte *src_buf, int columns, int scanlines);
+ uint32 _previousTicks;
+
+public:
+ int decodeBGImage(const byte *image_data, size_t image_size,
+ byte **output_buf, size_t *output_buf_len, int *w, int *h, bool flip = false);
+ const byte *getImagePal(const byte *image_data, size_t image_size);
+ void loadStrings(StringsTable &stringsTable, const byte *stringsPointer, size_t stringsLength);
+
+ const char *getObjectName(uint16 objectId);
+public:
+ int processInput(void);
+ const Point &mousePos() const {
+ return _mousePos;
+ }
+
+ const bool leftMouseButtonPressed() const {
+ return _leftMouseButtonPressed;
+ }
+
+ const bool rightMouseButtonPressed() const {
+ return _rightMouseButtonPressed;
+ }
+
+ const bool mouseButtonPressed() const {
+ return _leftMouseButtonPressed || _rightMouseButtonPressed;
+ }
+
+ private:
+ Common::String _targetName;
+
+ uint _saveFilesMaxCount;
+ uint _saveFilesCount;
+ SaveFileData _saveFiles[MAX_SAVES];
+ bool _saveMarks[MAX_SAVES];
+ SaveGameHeader _saveHeader;
+
+ Point _mousePos;
+ bool _leftMouseButtonPressed;
+ bool _rightMouseButtonPressed;
+
+ bool _quit;
+
+//current game description
+ int _gameNumber;
+ GameDescription *_gameDescription;
+ Common::Rect _displayClip;
+
+protected:
+ GameDisplayInfo _gameDisplayInfo;
+
+public:
+ int32 _frameCount;
+
+public:
+ bool initGame(void);
+public:
+ const GameDescription *getGameDescription() const { return _gameDescription; }
+ const bool isBigEndian() const { return (_gameDescription->features & GF_BIG_ENDIAN_DATA) != 0; }
+ const bool isMacResources() const { return (getPlatform() == Common::kPlatformMacintosh); }
+ const GameResourceDescription *getResourceDescription() { return _gameDescription->resourceDescription; }
+ const GameSoundInfo *getVoiceInfo() const { return _gameDescription->voiceInfo; }
+ const GameSoundInfo *getSfxInfo() const { return _gameDescription->sfxInfo; }
+ const GameSoundInfo *getMusicInfo() const { return _gameDescription->musicInfo; }
+
+ const GameFontDescription *getFontDescription(int index) {
+ assert(index < _gameDescription->fontsCount);
+ return &_gameDescription->fontDescriptions[index];
+ }
+ int getFontsCount() const { return _gameDescription->fontsCount; }
+
+ int getGameId() const { return _gameDescription->gameId; }
+ int getGameType() const { return _gameDescription->gameType; }
+ uint32 getFeatures() const { return _gameDescription->features; }
+ Common::Language getLanguage() const { return _gameDescription->language; }
+ Common::Platform getPlatform() const { return _gameDescription->platform; }
+ int getGameNumber() const { return _gameNumber; }
+ int getStartSceneNumber() const { return _gameDescription->startSceneNumber; }
+
+
+ const Common::Rect &getDisplayClip() const { return _displayClip;}
+ int getDisplayWidth() const { return _gameDisplayInfo.logicalWidth; }
+ int getDisplayHeight() const { return _gameDisplayInfo.logicalHeight;}
+ const GameDisplayInfo & getDisplayInfo() { return _gameDisplayInfo; }
+
+ const char *getTextString(int textStringId);
+ void getExcuseInfo(int verb, const char *&textString, int &soundResourceId);
+
+private:
+
+public:
+ ColorId KnownColor2ColorId(KnownColor knownColor);
+};
+
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/saveload.cpp b/engines/saga/saveload.cpp
new file mode 100644
index 0000000000..761ae49521
--- /dev/null
+++ b/engines/saga/saveload.cpp
@@ -0,0 +1,299 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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/stdafx.h"
+
+#include "common/config-manager.h"
+#include "common/savefile.h"
+#include "common/system.h"
+#include "common/file.h"
+
+#include "saga/saga.h"
+#include "saga/actor.h"
+#include "saga/events.h"
+#include "saga/interface.h"
+#include "saga/isomap.h"
+#include "saga/music.h"
+#include "saga/render.h"
+#include "saga/resnames.h"
+#include "saga/scene.h"
+#include "saga/script.h"
+
+#define CURRENT_SAGA_VER 5
+
+namespace Saga {
+
+static SaveFileData emptySlot = {
+ "", 0
+};
+
+//TODO:
+// - delete savegame
+
+char* SagaEngine::calcSaveFileName(uint slotNumber) {
+ static char name[MAX_FILE_NAME];
+ sprintf(name, "%s.s%02d", _targetName.c_str(), slotNumber);
+ return name;
+}
+
+SaveFileData *SagaEngine::getSaveFile(uint idx) {
+ if (idx >= _saveFilesMaxCount) {
+ error("getSaveFileName wrong idx");
+ }
+ if (isSaveListFull()) {
+ return &_saveFiles[_saveFilesCount - idx - 1];
+ } else {
+ if (!emptySlot.name[0])
+ strcpy(emptySlot.name, getTextString(kTextNewSave));
+
+ return (idx == 0) ? &emptySlot : &_saveFiles[_saveFilesCount - idx];
+ }
+}
+
+bool SagaEngine::locateSaveFile(char *saveName, uint &titleNumber) {
+ uint i;
+ for (i = 0; i < _saveFilesCount; i++) {
+ if (strcmp(saveName, _saveFiles[i].name) == 0) {
+ if (isSaveListFull()) {
+ titleNumber = _saveFilesCount - i - 1;
+ } else {
+ titleNumber = _saveFilesCount - i;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+uint SagaEngine::getNewSaveSlotNumber() {
+ uint i, j;
+ bool found;
+ if (isSaveListFull()) {
+ error("getNewSaveSlotNumber save list is full");
+ }
+ for (i = 0; i < MAX_SAVES; i++) {
+ if (_saveMarks[i]) {
+ found = false;
+ for (j = 0; j < _saveFilesCount; j++) {
+ if (_saveFiles[j].slotNumber == i) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return i;
+ }
+ }
+ }
+
+ error("getNewSaveSlotNumber save list is full");
+}
+
+void SagaEngine::fillSaveList() {
+ int i;
+ Common::InSaveFile *in;
+ char *name;
+
+ name = calcSaveFileName(MAX_SAVES);
+ name[strlen(name) - 2] = 0;
+ _saveFileMan->listSavefiles(name, _saveMarks, MAX_SAVES);
+
+ _saveFilesMaxCount = 0;
+ for (i = 0; i < MAX_SAVES; i++) {
+ if (_saveMarks[i]) {
+ _saveFilesMaxCount++;
+ }
+ _saveFiles[i].name[0] = 0;
+ _saveFiles[i].slotNumber = (uint)-1;
+ }
+
+ _saveFilesCount = 0;
+
+ i = 0;
+ while (i < MAX_SAVES) {
+ if (_saveMarks[i]) {
+ name = calcSaveFileName(i);
+ if ((in = _saveFileMan->openForLoading(name)) != NULL) {
+ in->read(&_saveHeader, sizeof(_saveHeader));
+
+ if (_saveHeader.type != MKID('SAGA')) {
+ error("SagaEngine::load wrong format");
+ }
+ strcpy(_saveFiles[_saveFilesCount].name, _saveHeader.name);
+ _saveFiles[_saveFilesCount].slotNumber = i;
+ delete in;
+ _saveFilesCount++;
+ }
+ }
+ i++;
+ }
+/* 4debug
+ for (i = 0; i < 14; i++) {
+ sprintf(_saveFiles[i].name,"test%i", i);
+ _saveFiles[i].slotNumber = i;
+ }
+ _saveFilesCount = 14;
+ _saveFilesMaxCount = 14;
+ */
+}
+
+
+#define TITLESIZE 80
+void SagaEngine::save(const char *fileName, const char *saveName) {
+ Common::OutSaveFile *out;
+ char title[TITLESIZE];
+
+ if (!(out = _saveFileMan->openForSaving(fileName))) {
+ return;
+ }
+
+ _saveHeader.type = MKID('SAGA');
+ _saveHeader.size = 0;
+ _saveHeader.version = TO_LE_32(CURRENT_SAGA_VER);
+ strncpy(_saveHeader.name, saveName, SAVE_TITLE_SIZE);
+
+ out->write(&_saveHeader, sizeof(_saveHeader));
+
+ // Original game title
+ memset(title, 0, TITLESIZE);
+ strncpy(title, getGameDescription()->title, TITLESIZE);
+ out->write(title, TITLESIZE);
+
+ // Surrounding scene
+ out->writeSint32LE(_scene->getOutsetSceneNumber());
+
+ // Inset scene
+ out->writeSint32LE(_scene->currentSceneNumber());
+
+ if (getGameType() != GType_ITE) {
+ out->writeUint32LE(_globalFlags);
+ for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
+ out->writeSint16LE(_ethicsPoints[i]);
+ }
+
+ _interface->saveState(out);
+
+ _actor->saveState(out);
+
+ out->writeSint16LE(_script->_commonBufferSize);
+
+ out->write(_script->_commonBuffer, _script->_commonBufferSize);
+
+ out->writeSint16LE(_isoMap->getMapPosition().x);
+ out->writeSint16LE(_isoMap->getMapPosition().y);
+
+ out->flush();
+
+ // TODO: Check out->ioFailed()
+
+ delete out;
+}
+
+void SagaEngine::load(const char *fileName) {
+ Common::InSaveFile *in;
+ int commonBufferSize;
+ int sceneNumber, insetSceneNumber;
+ int mapx, mapy;
+ char title[TITLESIZE];
+
+ if (!(in = _saveFileMan->openForLoading(fileName))) {
+ return;
+ }
+
+ in->read(&_saveHeader, sizeof(_saveHeader));
+
+ _saveHeader.size = FROM_LE_32(_saveHeader.size);
+ _saveHeader.version = FROM_LE_32(_saveHeader.version);
+
+ // This save was written in native endianness (fix that, so warning will show up)
+ if (_saveHeader.version > CURRENT_SAGA_VER) {
+#ifdef SCUMM_LITTLE_ENDIAN
+ _saveHeader.version = TO_BE_32(_saveHeader.version);
+#else
+ _saveHeader.version = TO_LE_32(_saveHeader.version);
+#endif
+ }
+
+ debug(2, "Save version: %x", _saveHeader.version);
+
+ if (_saveHeader.version < 4)
+ warning("This savegame is not endian-safe. There may be problems");
+
+ if (_saveHeader.type != MKID('SAGA')) {
+ error("SagaEngine::load wrong format");
+ }
+
+ if (_saveHeader.version > 4) {
+ in->read(title, TITLESIZE);
+ debug(0, "Save is for: %s", title);
+ }
+
+ // Surrounding scene
+ sceneNumber = in->readSint32LE();
+
+ // Inset scene
+ insetSceneNumber = in->readSint32LE();
+
+ if (getGameType() != GType_ITE) {
+ _globalFlags = in->readUint32LE();
+ for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
+ _ethicsPoints[i] = in->readSint16LE();
+ }
+
+ _interface->loadState(in);
+
+ _actor->loadState(in);
+
+ commonBufferSize = in->readSint16LE();
+ in->read(_script->_commonBuffer, commonBufferSize);
+
+ mapx = in->readSint16LE();
+ mapy = in->readSint16LE();
+
+ delete in;
+
+ // Mute volume to prevent outScene music play
+ int volume = _music->getVolume();
+ _music->setVolume(0);
+
+ _isoMap->setMapPosition(mapx, mapy);
+
+ _scene->clearSceneQueue();
+ _scene->changeScene(sceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);
+
+ _events->handleEvents(0); //dissolve backgrounds
+
+ if (insetSceneNumber != sceneNumber) {
+ _render->setFlag(RF_DISABLE_ACTORS);
+ _render->drawScene();
+ _render->clearFlag(RF_DISABLE_ACTORS);
+ _scene->changeScene(insetSceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);
+ }
+
+ _music->setVolume(volume);
+
+ _interface->draw();
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/scene.cpp b/engines/saga/scene.cpp
new file mode 100644
index 0000000000..708800dc41
--- /dev/null
+++ b/engines/saga/scene.cpp
@@ -0,0 +1,1283 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Scene management module
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/animation.h"
+#include "saga/console.h"
+#include "saga/interface.h"
+#include "saga/events.h"
+#include "saga/isomap.h"
+#include "saga/objectmap.h"
+#include "saga/palanim.h"
+#include "saga/puzzle.h"
+#include "saga/render.h"
+#include "saga/script.h"
+#include "saga/sound.h"
+#include "saga/music.h"
+
+#include "saga/scene.h"
+#include "saga/stream.h"
+#include "saga/actor.h"
+#include "saga/rscfile.h"
+#include "saga/resnames.h"
+
+#include "graphics/ilbm.h"
+#include "common/util.h"
+
+namespace Saga {
+
+static int initSceneDoors[SCENE_DOORS_MAX] = {
+ 0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+static SAGAResourceTypes ITESceneResourceTypes[26] = {
+ SAGA_ACTOR,
+ SAGA_OBJECT,
+ SAGA_BG_IMAGE,
+ SAGA_BG_MASK,
+SAGA_UNKNOWN,
+ SAGA_STRINGS,
+ SAGA_OBJECT_MAP,
+ SAGA_ACTION_MAP,
+ SAGA_ISO_IMAGES,
+ SAGA_ISO_MAP,
+ SAGA_ISO_PLATFORMS,
+ SAGA_ISO_METATILES,
+ SAGA_ENTRY,
+SAGA_UNKNOWN,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ISO_MULTI,
+ SAGA_PAL_ANIM,
+ SAGA_FACES,
+ SAGA_PALETTE
+};
+
+static SAGAResourceTypes IHNMSceneResourceTypes[28] = {
+ SAGA_ACTOR,
+SAGA_UNKNOWN,
+ SAGA_BG_IMAGE,
+ SAGA_BG_MASK,
+SAGA_UNKNOWN,
+ SAGA_STRINGS,
+ SAGA_OBJECT_MAP,
+ SAGA_ACTION_MAP,
+ SAGA_ISO_IMAGES,
+ SAGA_ISO_MAP,
+ SAGA_ISO_PLATFORMS,
+ SAGA_ISO_METATILES,
+ SAGA_ENTRY,
+SAGA_UNKNOWN,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ANIM,
+ SAGA_ISO_MULTI,
+ SAGA_PAL_ANIM,
+ SAGA_FACES,
+ SAGA_PALETTE
+};
+
+const char *SAGAResourceTypesString[] = {
+ "SAGA_UNKNOWN",
+ "SAGA_ACTOR",
+ "SAGA_OBJECT",
+ "SAGA_BG_IMAGE",
+ "SAGA_BG_MASK",
+ "SAGA_STRINGS",
+ "SAGA_OBJECT_MAP",
+ "SAGA_ACTION_MAP",
+ "SAGA_ISO_IMAGES",
+ "SAGA_ISO_MAP",
+ "SAGA_ISO_PLATFORMS",
+ "SAGA_ISO_METATILES",
+ "SAGA_ENTRY",
+ "SAGA_ANIM",
+ "SAGA_ISO_MULTI",
+ "SAGA_PAL_ANIM",
+ "SAGA_FACES",
+ "SAGA_PALETTE"
+};
+
+Scene::Scene(SagaEngine *vm) : _vm(vm) {
+ byte *sceneLUTPointer;
+ size_t sceneLUTLength;
+ uint32 resourceId;
+ int i;
+
+ // Load scene module resource context
+ _sceneContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (_sceneContext == NULL) {
+ error("Scene::Scene() scene context not found");
+ }
+
+ // Load scene lookup table
+ resourceId = _vm->_resource->convertResourceId(_vm->getResourceDescription()->sceneLUTResourceId);
+ debug(3, "Loading scene LUT from resource %i", resourceId);
+ _vm->_resource->loadResource(_sceneContext, resourceId, sceneLUTPointer, sceneLUTLength);
+ if (sceneLUTLength == 0) {
+ error("Scene::Scene() sceneLUTLength == 0");
+ }
+ _sceneCount = sceneLUTLength / 2;
+ _sceneLUT = (int *)malloc(_sceneCount * sizeof(*_sceneLUT));
+ if (_sceneLUT == NULL) {
+ memoryError("Scene::Scene()");
+ }
+
+ MemoryReadStreamEndian readS(sceneLUTPointer, sceneLUTLength, _sceneContext->isBigEndian);
+
+ for (i = 0; i < _sceneCount; i++) {
+ _sceneLUT[i] = readS.readUint16();
+ debug(8, "sceneNumber %i has resourceId %i", i, _sceneLUT[i]);
+ }
+
+ free(sceneLUTPointer);
+
+#define DUMP_SCENES_LEVEL 10
+
+ if (DUMP_SCENES_LEVEL <= gDebugLevel) {
+ uint j;
+ int backUpDebugLevel = gDebugLevel;
+ SAGAResourceTypes *types;
+ int typesCount;
+ SAGAResourceTypes resType;
+
+ getResourceTypes(types, typesCount);
+
+ for (i = 0; i < _sceneCount; i++) {
+ gDebugLevel = -1;
+ loadSceneDescriptor(_sceneLUT[i]);
+ loadSceneResourceList(_sceneDescription.resourceListResourceId);
+ gDebugLevel = backUpDebugLevel;
+ debug(DUMP_SCENES_LEVEL, "Dump Scene: number %i, descriptor resourceId %i, resourceList resourceId %i", i, _sceneLUT[i], _sceneDescription.resourceListResourceId);
+ debug(DUMP_SCENES_LEVEL, "\tresourceListCount %i", _resourceListCount);
+ for (j = 0; j < _resourceListCount; j++) {
+ if (_resourceList[j].resourceType >= typesCount) {
+ error("wrong resource type %i", _resourceList[j].resourceType);
+ }
+ resType = types[_resourceList[j].resourceType];
+
+ debug(DUMP_SCENES_LEVEL, "\t%s resourceId %i", SAGAResourceTypesString[resType], _resourceList[j].resourceId);
+ }
+ free(_resourceList);
+ }
+ }
+
+
+ debug(3, "LUT has %d entries.", _sceneCount);
+
+ _sceneLoaded = false;
+ _sceneNumber = 0;
+ _sceneResourceId = 0;
+ _inGame = false;
+ _loadDescription = false;
+ memset(&_sceneDescription, 0, sizeof(_sceneDescription));
+ _resourceListCount = 0;
+ _resourceList = NULL;
+ _sceneProc = NULL;
+ _objectMap = new ObjectMap(_vm);
+ _actionMap = new ObjectMap(_vm);
+ memset(&_bg, 0, sizeof(_bg));
+ memset(&_bgMask, 0, sizeof(_bgMask));
+}
+
+Scene::~Scene() {
+ delete _actionMap;
+ delete _objectMap;
+ free(_sceneLUT);
+}
+
+void Scene::getResourceTypes(SAGAResourceTypes *&types, int &typesCount) {
+ if (_vm->getGameType() == GType_IHNM) {
+ typesCount = ARRAYSIZE(IHNMSceneResourceTypes);
+ types = IHNMSceneResourceTypes;
+ } else {
+ typesCount = ARRAYSIZE(ITESceneResourceTypes);
+ types = ITESceneResourceTypes;
+ }
+}
+
+void Scene::drawTextList(Surface *ds) {
+ TextListEntry *entry;
+
+ for (TextList::iterator textIterator = _textList.begin(); textIterator != _textList.end(); ++textIterator) {
+ entry = (TextListEntry *)textIterator.operator->();
+ if (entry->display) {
+
+ if (entry->useRect) {
+ _vm->_font->textDrawRect(entry->font, ds, entry->text, entry->rect, _vm->KnownColor2ColorId(entry->knownColor), _vm->KnownColor2ColorId(entry->effectKnownColor), entry->flags);
+ } else {
+ _vm->_font->textDraw(entry->font, ds, entry->text, entry->point, _vm->KnownColor2ColorId(entry->knownColor), _vm->KnownColor2ColorId(entry->effectKnownColor), entry->flags);
+ }
+ }
+ }
+}
+
+void Scene::startScene() {
+ SceneQueueList::iterator queueIterator;
+ LoadSceneParams *sceneQueue;
+ Event event;
+
+ if (_sceneLoaded) {
+ error("Scene::start(): Error: Can't start game...scene already loaded");
+ }
+
+ if (_inGame) {
+ error("Scene::start(): Error: Can't start game...game already started");
+ }
+
+ // Hide cursor during intro
+ event.type = kEvTOneshot;
+ event.code = kCursorEvent;
+ event.op = kEventHide;
+ _vm->_events->queue(&event);
+
+ switch (_vm->getGameType()) {
+ case GType_ITE:
+ ITEStartProc();
+ break;
+ case GType_IHNM:
+ IHNMStartProc();
+ break;
+ default:
+ error("Scene::start(): Error: Can't start game... gametype not supported");
+ break;
+ }
+
+ // Load the head in scene queue
+ queueIterator = _sceneQueue.begin();
+ if (queueIterator == _sceneQueue.end()) {
+ return;
+ }
+
+ sceneQueue = queueIterator.operator->();
+
+ loadScene(sceneQueue);
+}
+
+void Scene::nextScene() {
+ SceneQueueList::iterator queueIterator;
+ LoadSceneParams *sceneQueue;
+
+ if (!_sceneLoaded) {
+ error("Scene::next(): Error: Can't advance scene...no scene loaded");
+ }
+
+ if (_inGame) {
+ error("Scene::next(): Error: Can't advance scene...game already started");
+ }
+
+ endScene();
+
+ // Delete the current head in scene queue
+ queueIterator = _sceneQueue.begin();
+ if (queueIterator == _sceneQueue.end()) {
+ return;
+ }
+
+ queueIterator = _sceneQueue.erase(queueIterator);
+
+ if (queueIterator == _sceneQueue.end()) {
+ return;
+ }
+
+ // Load the head in scene queue
+ sceneQueue = queueIterator.operator->();
+
+ loadScene(sceneQueue);
+}
+
+void Scene::skipScene() {
+ SceneQueueList::iterator queueIterator;
+
+ LoadSceneParams *sceneQueue = NULL;
+ LoadSceneParams *skipQueue = NULL;
+
+ if (!_sceneLoaded) {
+ error("Scene::skip(): Error: Can't skip scene...no scene loaded");
+ }
+
+ if (_inGame) {
+ error("Scene::skip(): Error: Can't skip scene...game already started");
+ }
+
+ // Walk down scene queue and try to find a skip target
+ queueIterator = _sceneQueue.begin();
+ if (queueIterator == _sceneQueue.end()) {
+ error("Scene::skip(): Error: Can't skip scene...no scenes in queue");
+ }
+
+ ++queueIterator;
+ while (queueIterator != _sceneQueue.end()) {
+ sceneQueue = queueIterator.operator->();
+ assert(sceneQueue != NULL);
+
+ if (sceneQueue->sceneSkipTarget) {
+ skipQueue = sceneQueue;
+ break;
+ }
+ ++queueIterator;
+ }
+
+ // If skip target found, remove preceding scenes and load
+ if (skipQueue != NULL) {
+ _sceneQueue.erase(_sceneQueue.begin(), queueIterator);
+
+ endScene();
+ loadScene(skipQueue);
+ }
+}
+
+static struct SceneSubstitutes {
+ int sceneId;
+ const char *message;
+ const char *title;
+ const char *image;
+} sceneSubstitutes[] = {
+ {
+ 7,
+ "Tycho says he knows much about the northern lands. Can Rif convince "
+ "the Dog to share this knowledge?",
+ "The Home of Tycho Northpaw",
+ "tycho.bbm"
+ },
+
+ {
+ 27,
+ "The scene of the crime may hold many clues, but will the servants of "
+ "the Sanctuary trust Rif?",
+ "The Sanctuary of the Orb",
+ "sanctuar.bbm"
+ },
+
+ {
+ 5,
+ "The Rats hold many secrets that could guide Rif on his quest -- assuming "
+ "he can get past the doorkeeper.",
+ "The Rat Complex",
+ "ratdoor.bbm"
+ },
+
+ {
+ 2,
+ "The Ferrets enjoy making things and have the materials to do so. How can "
+ "that help Rif?",
+ "The Ferret Village",
+ "ferrets.bbm"
+ },
+
+ {
+ 67,
+ "What aid can the noble King of the Elks provide to Rif and his companions?",
+ "The Realm of the Forest King",
+ "elkenter.bbm"
+ },
+
+ {
+ 3,
+ "The King holds Rif's sweetheart hostage. Will the Boar provide any "
+ "assistance to Rif?",
+ "The Great Hall of the Boar King",
+ "boarhall.bbm"
+ }
+};
+
+void Scene::changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionType transitionType, int chapter) {
+
+ debug(5, "Scene::changeScene(%d, %d, %d, %d)", sceneNumber, actorsEntrance, transitionType, chapter);
+
+ // This is used for latter ITE demos where all places on world map except
+ // Tent Faire are substituted with LBM picture and short description
+ if (_vm->getFeatures() & GF_SCENE_SUBSTITUTES) {
+ for (int i = 0; i < ARRAYSIZE(sceneSubstitutes); i++) {
+ if (sceneSubstitutes[i].sceneId == sceneNumber) {
+ Surface *backBuffer = _vm->_gfx->getBackBuffer();
+ Surface bbmBuffer;
+ byte *pal, *colors;
+ Common::File file;
+ Rect rect;
+ PalEntry cPal[PAL_ENTRIES];
+
+ _vm->_interface->setMode(kPanelSceneSubstitute);
+
+ if (file.open(sceneSubstitutes[i].image)) {
+ Graphics::decodeILBM(file, bbmBuffer, pal);
+ colors = pal;
+ rect.setWidth(bbmBuffer.w);
+ rect.setHeight(bbmBuffer.h);
+ backBuffer->blit(rect, (const byte*)bbmBuffer.pixels);
+ for (int j = 0; j < PAL_ENTRIES; j++) {
+ cPal[j].red = *pal++;
+ cPal[j].green = *pal++;
+ cPal[j].blue = *pal++;
+ }
+ free(colors);
+ _vm->_gfx->setPalette(cPal);
+
+ }
+
+ _vm->_interface->setStatusText("Click or Press Return to continue. Press Q to quit.", 96);
+ _vm->_font->textDrawRect(kKnownFontMedium, backBuffer, sceneSubstitutes[i].title,
+ Common::Rect(0, 7, _vm->getDisplayWidth(), 27), _vm->KnownColor2ColorId(kKnownColorBrightWhite), _vm->KnownColor2ColorId(kKnownColorBlack), kFontOutline);
+ _vm->_font->textDrawRect(kKnownFontMedium, backBuffer, sceneSubstitutes[i].message,
+ Common::Rect(24, getHeight() - 33, _vm->getDisplayWidth() - 11,
+ getHeight()), _vm->KnownColor2ColorId(kKnownColorBrightWhite), _vm->KnownColor2ColorId(kKnownColorBlack), kFontOutline);
+ return;
+ }
+ }
+ }
+
+ LoadSceneParams sceneParams;
+
+ sceneParams.actorsEntrance = actorsEntrance;
+ sceneParams.loadFlag = kLoadBySceneNumber;
+ sceneParams.sceneDescriptor = sceneNumber;
+ sceneParams.transitionType = transitionType;
+ sceneParams.sceneProc = NULL;
+ sceneParams.sceneSkipTarget = false;
+ sceneParams.chapter = chapter;
+
+ if (sceneNumber != -2) {
+ endScene();
+ }
+ loadScene(&sceneParams);
+}
+
+void Scene::getSlopes(int &beginSlope, int &endSlope) {
+ beginSlope = getHeight() - _sceneDescription.beginSlope;
+ endSlope = getHeight() - _sceneDescription.endSlope;
+}
+
+void Scene::getBGInfo(BGInfo &bgInfo) {
+ bgInfo.buffer = _bg.buf;
+ bgInfo.bufferLength = _bg.buf_len;
+ bgInfo.bounds.left = 0;
+ bgInfo.bounds.top = 0;
+
+ if (_bg.w < _vm->getDisplayWidth()) {
+ bgInfo.bounds.left = (_vm->getDisplayWidth() - _bg.w) / 2;
+ }
+
+ if (_bg.h < getHeight()) {
+ bgInfo.bounds.top = (getHeight() - _bg.h) / 2;
+ }
+
+ bgInfo.bounds.setWidth(_bg.w);
+ bgInfo.bounds.setHeight(_bg.h);
+}
+
+int Scene::getBGMaskType(const Point &testPoint) {
+ uint offset;
+ if (!_bgMask.loaded) {
+ return 0;
+ }
+ offset = testPoint.x + testPoint.y * _bgMask.w;
+ if (offset >= _bgMask.buf_len) {
+ error("Scene::getBGMaskType offset 0x%X exceed bufferLength 0x%X", offset, _bgMask.buf_len);
+ }
+
+ return (_bgMask.buf[offset] >> 4) & 0x0f;
+}
+
+bool Scene::validBGMaskPoint(const Point &testPoint) {
+ if (!_bgMask.loaded) {
+ error("Scene::validBGMaskPoint _bgMask not loaded");
+ }
+
+ return !((testPoint.x < 0) || (testPoint.x >= _bgMask.w) ||
+ (testPoint.y < 0) || (testPoint.y >= _bgMask.h));
+}
+
+bool Scene::canWalk(const Point &testPoint) {
+ int maskType;
+
+ if (!_bgMask.loaded) {
+ return true;
+ }
+ if (!validBGMaskPoint(testPoint)) {
+ return true;
+ }
+
+ maskType = getBGMaskType(testPoint);
+ return getDoorState(maskType) == 0;
+}
+
+bool Scene::offscreenPath(Point &testPoint) {
+ Point point;
+
+ if (!_bgMask.loaded) {
+ return false;
+ }
+
+ point.x = clamp( 0, testPoint.x, _bgMask.w - 1 );
+ point.y = clamp( 0, testPoint.y, _bgMask.h - 1 );
+ if (point == testPoint) {
+ return false;
+ }
+
+ if (point.y >= _bgMask.h - 1) {
+ point.y = _bgMask.h - 2;
+ }
+ testPoint = point;
+
+ return true;
+}
+
+
+void Scene::getBGMaskInfo(int &width, int &height, byte *&buffer, size_t &bufferLength) {
+ if (!_bgMask.loaded) {
+ error("Scene::getBGMaskInfo _bgMask not loaded");
+ }
+
+ width = _bgMask.w;
+ height = _bgMask.h;
+ buffer = _bgMask.buf;
+ bufferLength = _bgMask.buf_len;
+}
+
+void Scene::setDoorState(int doorNumber, int doorState) {
+ if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX))
+ error("Scene::setDoorState wrong doorNumber");
+
+ _sceneDoors[doorNumber] = doorState;
+}
+
+int Scene::getDoorState(int doorNumber) {
+ if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX))
+ error("Scene::getDoorState wrong doorNumber");
+
+ return _sceneDoors[doorNumber];
+}
+
+void Scene::initDoorsState() {
+ memcpy(_sceneDoors, initSceneDoors, sizeof (_sceneDoors) );
+}
+
+void Scene::loadScene(LoadSceneParams *loadSceneParams) {
+ size_t i;
+ Event event;
+ Event *q_event;
+ static PalEntry current_pal[PAL_ENTRIES];
+
+ if ((_vm->getGameType() == GType_IHNM) && (loadSceneParams->chapter != NO_CHAPTER_CHANGE)) {
+ if (loadSceneParams->loadFlag != kLoadBySceneNumber) {
+ error("loadScene wrong usage");
+ }
+
+ if (loadSceneParams->chapter == 6)
+ _vm->_interface->setLeftPortrait(0);
+
+ _vm->_anim->freeCutawayList();
+ _vm->_script->freeModules();
+ // deleteAllScenes();
+
+ // installSomeAlarm()
+
+ _vm->_interface->clearInventory();
+ _vm->_resource->loadGlobalResources(loadSceneParams->chapter, loadSceneParams->actorsEntrance);
+ _vm->_interface->addToInventory(IHNM_OBJ_PROFILE);
+ _vm->_interface->activate();
+
+ if (loadSceneParams->chapter == 8 || loadSceneParams->chapter == -1)
+ _vm->_interface->setMode(kPanelChapterSelection);
+ else
+ _vm->_interface->setMode(kPanelMain);
+
+ _inGame = true;
+
+ _vm->_script->setVerb(_vm->_script->getVerbType(kVerbWalkTo));
+
+ if (loadSceneParams->sceneDescriptor == -2) {
+ return;
+ }
+ }
+
+ if (_sceneLoaded) {
+ error("Scene::loadScene(): Error, a scene is already loaded");
+ }
+
+ _loadDescription = true;
+
+ if (_vm->getGameType() == GType_IHNM) {
+ if (loadSceneParams->loadFlag == kLoadBySceneNumber) // When will we get rid of it?
+ if (loadSceneParams->sceneDescriptor <= 0)
+ loadSceneParams->sceneDescriptor = _vm->_resource->_metaResource.sceneIndex;
+ }
+
+ switch (loadSceneParams->loadFlag) {
+ case kLoadByResourceId:
+ _sceneNumber = 0; // original assign zero for loaded by resource id
+ _sceneResourceId = loadSceneParams->sceneDescriptor;
+ break;
+ case kLoadBySceneNumber:
+ _sceneNumber = loadSceneParams->sceneDescriptor;
+ _sceneResourceId = getSceneResourceId(_sceneNumber);
+ break;
+ case kLoadByDescription:
+ _sceneNumber = -1;
+ _sceneResourceId = -1;
+ assert(loadSceneParams->sceneDescription != NULL);
+ assert(loadSceneParams->sceneDescription->resourceList != NULL);
+ _loadDescription = false;
+ _sceneDescription = *loadSceneParams->sceneDescription;
+ _resourceList = loadSceneParams->sceneDescription->resourceList;
+ _resourceListCount = loadSceneParams->sceneDescription->resourceListCount;
+ break;
+ }
+
+ debug(3, "Loading scene number %d:", _sceneNumber);
+
+ // Load scene descriptor and resource list resources
+ if (_loadDescription) {
+ debug(3, "Loading scene resource %i", _sceneResourceId);
+
+ loadSceneDescriptor(_sceneResourceId);
+
+ loadSceneResourceList(_sceneDescription.resourceListResourceId);
+ } else {
+ debug(3, "Loading memory scene resource");
+ }
+
+ // Load resources from scene resource list
+ for (i = 0; i < _resourceListCount; i++) {
+ if (!_resourceList[i].invalid) {
+ _vm->_resource->loadResource(_sceneContext, _resourceList[i].resourceId,
+ _resourceList[i].buffer, _resourceList[i].size);
+
+
+ if (_resourceList[i].size >= 6) {
+ if (!memcmp(_resourceList[i].buffer, "DUMMY!", 6)) {
+ _resourceList[i].invalid = true;
+ warning("DUMMY resource %i", _resourceList[i].resourceId);
+ }
+ }
+ }
+ }
+
+ // Process resources from scene resource list
+ processSceneResources();
+
+ if (_sceneDescription.flags & kSceneFlagISO) {
+ _outsetSceneNumber = _sceneNumber;
+
+ _sceneClip.left = 0;
+ _sceneClip.top = 0;
+ _sceneClip.right = _vm->getDisplayWidth();
+ _sceneClip.bottom = getHeight();
+ } else {
+ BGInfo backGroundInfo;
+ getBGInfo(backGroundInfo);
+ _sceneClip = backGroundInfo.bounds;
+ if (!(_bg.w < _vm->getDisplayWidth() || _bg.h < getHeight()))
+ _outsetSceneNumber = _sceneNumber;
+ }
+
+ _sceneLoaded = true;
+
+ q_event = NULL;
+
+ //fix placard bug
+ //i guess we should remove RF_PLACARD flag - and use _interface->getMode()
+ event.type = kEvTOneshot;
+ event.code = kGraphicsEvent;
+ event.op = kEventClearFlag;
+ event.param = RF_PLACARD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ if (loadSceneParams->transitionType == kTransitionFade) {
+
+ _vm->_interface->setFadeMode(kFadeOut);
+
+ // Fade to black out
+ _vm->_gfx->getCurrentPal(current_pal);
+ event.type = kEvTImmediate;
+ event.code = kPalEvent;
+ event.op = kEventPalToBlack;
+ event.time = 0;
+ event.duration = kNormalFadeDuration;
+ event.data = current_pal;
+ q_event = _vm->_events->queue(&event);
+
+ // set fade mode
+ event.type = kEvTImmediate;
+ event.code = kInterfaceEvent;
+ event.op = kEventSetFadeMode;
+ event.param = kNoFade;
+ event.time = 0;
+ event.duration = 0;
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Display scene background, but stay with black palette
+ event.type = kEvTImmediate;
+ event.code = kBgEvent;
+ event.op = kEventDisplay;
+ event.param = kEvPNoSetPalette;
+ event.time = 0;
+ event.duration = 0;
+ q_event = _vm->_events->chain(q_event, &event);
+
+ }
+
+ // Start the scene pre script, but stay with black palette
+ if (_sceneDescription.startScriptEntrypointNumber > 0) {
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventExecBlocking;
+ event.time = 0;
+ event.param = _sceneDescription.scriptModuleNumber;
+ event.param2 = _sceneDescription.startScriptEntrypointNumber;
+ event.param3 = 0; // Action
+ event.param4 = _sceneNumber; // Object
+ event.param5 = loadSceneParams->actorsEntrance; // With Object
+ event.param6 = 0; // Actor
+
+ q_event = _vm->_events->chain(q_event, &event);
+ }
+
+ if (loadSceneParams->transitionType == kTransitionFade) {
+
+ // set fade mode
+ event.type = kEvTImmediate;
+ event.code = kInterfaceEvent;
+ event.op = kEventSetFadeMode;
+ event.param = kFadeIn;
+ event.time = 0;
+ event.duration = 0;
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Fade in from black to the scene background palette
+ event.type = kEvTImmediate;
+ event.code = kPalEvent;
+ event.op = kEventBlackToPal;
+ event.time = 0;
+ event.duration = kNormalFadeDuration;
+ event.data = _bg.pal;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // set fade mode
+ event.type = kEvTImmediate;
+ event.code = kInterfaceEvent;
+ event.op = kEventSetFadeMode;
+ event.param = kNoFade;
+ event.time = 0;
+ event.duration = 0;
+ q_event = _vm->_events->chain(q_event, &event);
+ }
+
+ if (loadSceneParams->sceneProc == NULL) {
+ if (!_inGame && _vm->getGameType() == GType_ITE) {
+ _inGame = true;
+ _vm->_interface->setMode(kPanelMain);
+ }
+
+ _vm->_sound->stopAll();
+
+ // FIXME: Does IHNM use scene background music, or is all the
+ // music scripted? At the very least, it shouldn't try
+ // to start song 0 at the beginning of the game, since
+ // it's the end credits music.
+
+ if (_vm->getGameType() == GType_ITE) {
+ if (_sceneDescription.musicResourceId >= 0) {
+ event.type = kEvTOneshot;
+ event.code = kMusicEvent;
+ event.param = _sceneDescription.musicResourceId;
+ event.param2 = MUSIC_DEFAULT;
+ event.op = kEventPlay;
+ event.time = 0;
+
+ _vm->_events->queue(&event);
+ } else {
+ event.type = kEvTOneshot;
+ event.code = kMusicEvent;
+ event.op = kEventStop;
+ event.time = 0;
+
+ _vm->_events->queue(&event);
+ }
+ }
+
+ // Set scene background
+ event.type = kEvTOneshot;
+ event.code = kBgEvent;
+ event.op = kEventDisplay;
+ event.param = kEvPSetPalette;
+ event.time = 0;
+
+ _vm->_events->queue(&event);
+
+ // Begin palette cycle animation if present
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStart;
+ event.time = 0;
+
+ q_event = _vm->_events->queue(&event);
+
+ // Start the scene main script
+ if (_sceneDescription.sceneScriptEntrypointNumber > 0) {
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventExecNonBlocking;
+ event.time = 0;
+ event.param = _sceneDescription.scriptModuleNumber;
+ event.param2 = _sceneDescription.sceneScriptEntrypointNumber;
+ event.param3 = _vm->_script->getVerbType(kVerbEnter); // Action
+ event.param4 = _sceneNumber; // Object
+ event.param5 = loadSceneParams->actorsEntrance; // With Object
+ event.param6 = 0; // Actor
+
+ _vm->_events->queue(&event);
+ }
+
+ debug(3, "Scene started");
+
+ } else {
+ loadSceneParams->sceneProc(SCENE_BEGIN, this);
+ }
+
+
+
+ // We probably don't want "followers" to go into scene -1 , 0. At the very
+ // least we don't want garbage to be drawn that early in the ITE intro.
+ if (_sceneNumber > 0 && _sceneNumber != ITE_SCENE_PUZZLE)
+ _vm->_actor->updateActorsScene(loadSceneParams->actorsEntrance);
+
+ if (_sceneNumber == ITE_SCENE_PUZZLE)
+ _vm->_puzzle->execute();
+
+ if (getFlags() & kSceneFlagShowCursor) {
+ // Activate user interface
+ event.type = kEvTOneshot;
+ event.code = kInterfaceEvent;
+ event.op = kEventActivate;
+ event.time = 0;
+ _vm->_events->queue(&event);
+ }
+}
+
+void Scene::loadSceneDescriptor(uint32 resourceId) {
+ byte *sceneDescriptorData;
+ size_t sceneDescriptorDataLength;
+
+ memset(&_sceneDescription, 0, sizeof(_sceneDescription));
+
+ if (resourceId == 0) {
+ return;
+ }
+
+ _vm->_resource->loadResource(_sceneContext, resourceId, sceneDescriptorData, sceneDescriptorDataLength);
+
+ if (sceneDescriptorDataLength == 16) {
+ MemoryReadStreamEndian readS(sceneDescriptorData, sceneDescriptorDataLength, _sceneContext->isBigEndian);
+
+ _sceneDescription.flags = readS.readSint16();
+ _sceneDescription.resourceListResourceId = readS.readSint16();
+ _sceneDescription.endSlope = readS.readSint16();
+ _sceneDescription.beginSlope = readS.readSint16();
+ _sceneDescription.scriptModuleNumber = readS.readUint16();
+ _sceneDescription.sceneScriptEntrypointNumber = readS.readUint16();
+ _sceneDescription.startScriptEntrypointNumber = readS.readUint16();
+ _sceneDescription.musicResourceId = readS.readSint16();
+ }
+
+ free(sceneDescriptorData);
+}
+
+void Scene::loadSceneResourceList(uint32 resourceId) {
+ byte *resourceListData;
+ size_t resourceListDataLength;
+ size_t i;
+
+ _resourceListCount = 0;
+ _resourceList = NULL;
+
+ if (resourceId == 0) {
+ return;
+ }
+
+ // Load the scene resource table
+ _vm->_resource->loadResource(_sceneContext, resourceId, resourceListData, resourceListDataLength);
+
+ if ((resourceListDataLength % SAGA_RESLIST_ENTRY_LEN) == 0) {
+ MemoryReadStreamEndian readS(resourceListData, resourceListDataLength, _sceneContext->isBigEndian);
+
+ // Allocate memory for scene resource list
+ _resourceListCount = resourceListDataLength / SAGA_RESLIST_ENTRY_LEN;
+ debug(3, "Scene resource list contains %i entries", _resourceListCount);
+ _resourceList = (SceneResourceData *)calloc(_resourceListCount, sizeof(*_resourceList));
+
+ // Load scene resource list from raw scene
+ // resource table
+ debug(3, "Loading scene resource list");
+
+ for (i = 0; i < _resourceListCount; i++) {
+ _resourceList[i].resourceId = readS.readUint16();
+ _resourceList[i].resourceType = readS.readUint16();
+ // demo version may contain invalid resourceId
+ _resourceList[i].invalid = !_vm->_resource->validResourceId(_sceneContext, _resourceList[i].resourceId);
+ }
+
+ }
+ free(resourceListData);
+}
+
+void Scene::processSceneResources() {
+ byte *resourceData;
+ size_t resourceDataLength;
+ const byte *palPointer;
+ size_t i;
+ SAGAResourceTypes *types;
+ int typesCount;
+ SAGAResourceTypes resType;
+
+ getResourceTypes(types, typesCount);
+
+ // Process the scene resource list
+ for (i = 0; i < _resourceListCount; i++) {
+ if (_resourceList[i].invalid) {
+ continue;
+ }
+ resourceData = _resourceList[i].buffer;
+ resourceDataLength = _resourceList[i].size;
+
+ if (_resourceList[i].resourceType >= typesCount) {
+ error("Scene::processSceneResources() wrong resource type %i", _resourceList[i].resourceType);
+ }
+
+ resType = types[_resourceList[i].resourceType];
+
+ switch (resType) {
+ case SAGA_UNKNOWN:
+ warning("UNKNOWN resourceType %i", _resourceList[i].resourceType);
+ break;
+ case SAGA_ACTOR:
+ //for (a = actorsInScene; a; a = a->nextInScene)
+ // if (a->obj.figID == glist->file_id)
+ // if (_vm->getGameType() == GType_ITE ||
+ // ((a->obj.flags & ACTORF_FINAL_FACE) & 0xff))
+ // a->sprites = (xSpriteSet *)glist->offset;
+ warning("STUB: unimplemeted handler of SAGA_ACTOR resource");
+ break;
+ case SAGA_OBJECT:
+ break;
+ case SAGA_BG_IMAGE: // Scene background resource
+ if (_bg.loaded) {
+ error("Scene::processSceneResources() Multiple background resources encountered");
+ }
+
+ debug(3, "Loading background resource.");
+ _bg.res_buf = resourceData;
+ _bg.res_len = resourceDataLength;
+ _bg.loaded = 1;
+
+ if (_vm->decodeBGImage(_bg.res_buf,
+ _bg.res_len,
+ &_bg.buf,
+ &_bg.buf_len,
+ &_bg.w,
+ &_bg.h) != SUCCESS) {
+ error("Scene::processSceneResources() Error loading background resource %i", _resourceList[i].resourceId);
+ }
+
+ palPointer = _vm->getImagePal(_bg.res_buf, _bg.res_len);
+ memcpy(_bg.pal, palPointer, sizeof(_bg.pal));
+ break;
+ case SAGA_BG_MASK: // Scene background mask resource
+ if (_bgMask.loaded) {
+ error("Scene::ProcessSceneResources(): Duplicate background mask resource encountered");
+ }
+ debug(3, "Loading BACKGROUND MASK resource.");
+ _bgMask.res_buf = resourceData;
+ _bgMask.res_len = resourceDataLength;
+ _bgMask.loaded = 1;
+ _vm->decodeBGImage(_bgMask.res_buf, _bgMask.res_len, &_bgMask.buf,
+ &_bgMask.buf_len, &_bgMask.w, &_bgMask.h, true);
+
+ // At least in ITE the mask needs to be clipped.
+
+ _bgMask.w = MIN(_bgMask.w, _vm->getDisplayWidth());
+ _bgMask.h = MIN(_bgMask.h, getHeight());
+
+ debug(4, "BACKGROUND MASK width=%d height=%d length=%d", _bgMask.w, _bgMask.h, _bgMask.buf_len);
+ break;
+ case SAGA_STRINGS:
+ debug(3, "Loading scene strings resource...");
+ _vm->loadStrings(_sceneStrings, resourceData, resourceDataLength);
+ break;
+ case SAGA_OBJECT_MAP:
+ debug(3, "Loading object map resource...");
+ _objectMap->load(resourceData, resourceDataLength);
+ break;
+ case SAGA_ACTION_MAP:
+ debug(3, "Loading action map resource...");
+ _actionMap->load(resourceData, resourceDataLength);
+ break;
+ case SAGA_ISO_IMAGES:
+ if (!(_sceneDescription.flags & kSceneFlagISO)) {
+ error("Scene::ProcessSceneResources(): not Iso mode");
+ }
+
+ debug(3, "Loading isometric images resource.");
+
+ _vm->_isoMap->loadImages(resourceData, resourceDataLength);
+ break;
+ case SAGA_ISO_MAP:
+ if (!(_sceneDescription.flags & kSceneFlagISO)) {
+ error("Scene::ProcessSceneResources(): not Iso mode");
+ }
+
+ debug(3, "Loading isometric map resource.");
+
+ _vm->_isoMap->loadMap(resourceData, resourceDataLength);
+ break;
+ case SAGA_ISO_PLATFORMS:
+ if (!(_sceneDescription.flags & kSceneFlagISO)) {
+ error("Scene::ProcessSceneResources(): not Iso mode");
+ }
+
+ debug(3, "Loading isometric platforms resource.");
+
+ _vm->_isoMap->loadPlatforms(resourceData, resourceDataLength);
+ break;
+ case SAGA_ISO_METATILES:
+ if (!(_sceneDescription.flags & kSceneFlagISO)) {
+ error("Scene::ProcessSceneResources(): not Iso mode");
+ }
+
+ debug(3, "Loading isometric metatiles resource.");
+
+ _vm->_isoMap->loadMetaTiles(resourceData, resourceDataLength);
+ break;
+ case SAGA_ANIM:
+ {
+ uint16 animId = _resourceList[i].resourceType - 14;
+
+ debug(3, "Loading animation resource animId=%i", animId);
+
+ _vm->_anim->load(animId, resourceData, resourceDataLength);
+ }
+ break;
+ case SAGA_ENTRY:
+ debug(3, "Loading entry list resource...");
+ loadSceneEntryList(resourceData, resourceDataLength);
+ break;
+ case SAGA_ISO_MULTI:
+ if (!(_sceneDescription.flags & kSceneFlagISO)) {
+ error("Scene::ProcessSceneResources(): not Iso mode");
+ }
+
+ debug(3, "Loading isometric multi resource.");
+
+ _vm->_isoMap->loadMulti(resourceData, resourceDataLength);
+ break;
+ case SAGA_PAL_ANIM:
+ debug(3, "Loading palette animation resource.");
+ _vm->_palanim->loadPalAnim(resourceData, resourceDataLength);
+ break;
+ case SAGA_FACES:
+ if (_vm->getGameType() == GType_ITE)
+ _vm->_interface->loadScenePortraits(_resourceList[i].resourceId);
+ break;
+ case SAGA_PALETTE:
+ {
+ PalEntry pal[PAL_ENTRIES];
+ byte *palPtr = resourceData;
+
+ if (resourceDataLength < 3 * PAL_ENTRIES)
+ error("Too small scene palette %i", resourceDataLength);
+
+ for (uint16 c = 0; c < PAL_ENTRIES; c++) {
+ pal[c].red = *palPtr++;
+ pal[c].green = *palPtr++;
+ pal[c].blue = *palPtr++;
+ }
+ _vm->_gfx->setPalette(pal);
+ }
+ break;
+ default:
+ error("Scene::ProcessSceneResources() Encountered unknown resource type %i", _resourceList[i].resourceType);
+ break;
+ }
+ }
+}
+
+void Scene::draw() {
+ Surface *backBuffer;
+ Surface *backGroundSurface;
+ Rect rect;
+
+ backBuffer = _vm->_gfx->getBackBuffer();
+
+ backGroundSurface = _vm->_render->getBackGroundSurface();
+
+ if (_sceneDescription.flags & kSceneFlagISO) {
+ _vm->_isoMap->adjustScroll(false);
+ _vm->_isoMap->draw(backBuffer);
+ } else {
+ backGroundSurface->getRect(rect);
+ if (_sceneClip.bottom < rect.bottom) {
+ rect.bottom = getHeight();
+ }
+ backBuffer->blit(rect, (const byte *)backGroundSurface->pixels);
+ }
+}
+
+void Scene::endScene() {
+ Surface *backBuffer;
+ Surface *backGroundSurface;
+ Rect rect;
+ size_t i;
+
+ if (!_sceneLoaded)
+ return;
+
+ debug(3, "Ending scene...");
+
+ if (_sceneProc != NULL) {
+ _sceneProc(SCENE_END, this);
+ }
+
+ //
+ _vm->_script->abortAllThreads();
+ _vm->_script->_skipSpeeches = false;
+
+ // Copy current screen to render buffer so inset rooms will get proper background
+ backGroundSurface = _vm->_render->getBackGroundSurface();
+ if (!(_sceneDescription.flags & kSceneFlagISO) && !_vm->_scene->isInIntro()) {
+ BGInfo bgInfo;
+
+ _vm->_scene->getBGInfo(bgInfo);
+ backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer);
+ } else {
+ backBuffer = _vm->_gfx->getBackBuffer();
+ backBuffer->getRect(rect);
+ backGroundSurface->blit(rect, (const byte *)backBuffer->pixels);
+ }
+
+ // Free scene background
+ if (_bg.loaded) {
+ free(_bg.buf);
+ _bg.loaded = 0;
+ }
+
+ // Free scene background mask
+ if (_bgMask.loaded) {
+ free(_bgMask.buf);
+ _bgMask.loaded = 0;
+ }
+
+ // Free scene resource list
+ for (i = 0; i < _resourceListCount; i++) {
+ free(_resourceList[i].buffer);
+ }
+
+ if (_loadDescription) {
+ free(_resourceList);
+ }
+
+ // Free animation info list
+ _vm->_anim->reset();
+
+ _vm->_palanim->freePalAnim();
+
+ _objectMap->freeMem();
+ _actionMap->freeMem();
+ _entryList.freeMem();
+ _sceneStrings.freeMem();
+ _vm->_isoMap->freeMem();
+
+ _vm->_events->clearList();
+ _textList.clear();
+
+ _sceneLoaded = false;
+
+}
+
+void Scene::cmdSceneChange(int argc, const char **argv) {
+ int scene_num = 0;
+
+ scene_num = atoi(argv[1]);
+
+ if ((scene_num < 1) || (scene_num >= _sceneCount)) {
+ _vm->_console->DebugPrintf("Invalid scene number.\n");
+ return;
+ }
+
+ clearSceneQueue();
+
+ changeScene(scene_num, 0, kTransitionNoFade);
+}
+
+void Scene::cmdActionMapInfo() {
+ _actionMap->cmdInfo();
+}
+
+void Scene::cmdObjectMapInfo() {
+ _objectMap->cmdInfo();
+}
+
+
+void Scene::loadSceneEntryList(const byte* resourcePointer, size_t resourceLength) {
+ int i;
+
+ _entryList.entryListCount = resourceLength / 8;
+
+ MemoryReadStreamEndian readS(resourcePointer, resourceLength, _sceneContext->isBigEndian);
+
+
+ if (_entryList.entryList)
+ error("Scene::loadSceneEntryList entryList != NULL");
+
+ _entryList.entryList = (SceneEntry *) malloc(_entryList.entryListCount * sizeof(*_entryList.entryList));
+ if (_entryList.entryList == NULL) {
+ memoryError("Scene::loadSceneEntryList");
+ }
+
+ for (i = 0; i < _entryList.entryListCount; i++) {
+ _entryList.entryList[i].location.x = readS.readSint16();
+ _entryList.entryList[i].location.y = readS.readSint16();
+ _entryList.entryList[i].location.z = readS.readSint16();
+ _entryList.entryList[i].facing = readS.readUint16();
+ }
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/scene.h b/engines/saga/scene.h
new file mode 100644
index 0000000000..7f99140d10
--- /dev/null
+++ b/engines/saga/scene.h
@@ -0,0 +1,370 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Scene management module private header file
+
+#ifndef SAGA_SCENE_H
+#define SAGA_SCENE_H
+
+#include "saga/font.h"
+#include "saga/list.h"
+#include "saga/actor.h"
+#include "saga/interface.h"
+
+namespace Saga {
+
+#define SCENE_DOORS_MAX 16
+
+#define NO_CHAPTER_CHANGE -2
+
+class ObjectMap;
+
+struct Event;
+
+enum SceneFlags {
+ kSceneFlagISO = 1,
+ kSceneFlagShowCursor = 2
+};
+
+struct BGInfo {
+ Rect bounds;
+ byte *buffer;
+ size_t bufferLength;
+};
+
+typedef int (SceneProc) (int, void *);
+
+
+enum SCENE_PROC_PARAMS {
+ SCENE_BEGIN = 0,
+ SCENE_END
+};
+
+// Resource type numbers
+enum SAGAResourceTypes {
+ SAGA_UNKNOWN,
+ SAGA_ACTOR,
+ SAGA_OBJECT,
+ SAGA_BG_IMAGE,
+ SAGA_BG_MASK,
+ SAGA_STRINGS,
+ SAGA_OBJECT_MAP,
+ SAGA_ACTION_MAP,
+ SAGA_ISO_IMAGES,
+ SAGA_ISO_MAP,
+ SAGA_ISO_PLATFORMS,
+ SAGA_ISO_METATILES,
+ SAGA_ENTRY,
+ SAGA_ANIM,
+ SAGA_ISO_MULTI,
+ SAGA_PAL_ANIM,
+ SAGA_FACES,
+ SAGA_PALETTE
+};
+
+#define SAGA_RESLIST_ENTRY_LEN 4
+
+struct SceneResourceData {
+ uint32 resourceId;
+ int resourceType;
+ byte *buffer;
+ size_t size;
+ bool invalid;
+};
+
+#define SAGA_SCENE_DESC_LEN 16
+
+struct SceneDescription {
+ int16 flags;
+ int16 resourceListResourceId;
+ int16 endSlope;
+ int16 beginSlope;
+ uint16 scriptModuleNumber;
+ uint16 sceneScriptEntrypointNumber;
+ uint16 startScriptEntrypointNumber;
+ int16 musicResourceId;
+ SceneResourceData *resourceList;
+ size_t resourceListCount;
+};
+
+struct SceneEntry {
+ Location location;
+ int facing;
+};
+
+struct SceneEntryList {
+ SceneEntry *entryList;
+ int entryListCount;
+
+ const SceneEntry * getEntry(int index) {
+ if ((index < 0) || (index >= entryListCount)) {
+ error("SceneEntryList::getEntry wrong index (%d)", index);
+ }
+ return &entryList[index];
+ }
+ void freeMem() {
+ free(entryList);
+ memset(this, 0, sizeof(*this));
+ }
+ SceneEntryList() {
+ memset(this, 0, sizeof(*this));
+ }
+ ~SceneEntryList() {
+ freeMem();
+ }
+};
+
+struct SceneImage {
+ int loaded;
+ int w;
+ int h;
+ int p;
+ byte *buf;
+ size_t buf_len;
+ byte *res_buf;
+ size_t res_len;
+ PalEntry pal[256];
+};
+
+
+enum SceneTransitionType {
+ kTransitionNoFade,
+ kTransitionFade
+};
+
+enum SceneLoadFlags {
+ kLoadByResourceId,
+ kLoadBySceneNumber,
+ kLoadByDescription
+};
+
+struct LoadSceneParams {
+ int32 sceneDescriptor;
+ SceneLoadFlags loadFlag;
+ SceneDescription* sceneDescription;
+ SceneProc *sceneProc;
+ bool sceneSkipTarget;
+ SceneTransitionType transitionType;
+ int actorsEntrance;
+ int chapter;
+};
+
+typedef Common::List<LoadSceneParams> SceneQueueList;
+
+///// IHNM-specific stuff
+#define IHNM_PALFADE_TIME 1000
+#define IHNM_INTRO_FRAMETIME 80
+#define IHNM_DGLOGO_TIME 8000
+#define IHNM_TITLE_TIME_GM 28750
+#define IHNM_TITLE_TIME_FM 19500
+
+///// ITE-specific stuff
+#define ITE_INTRO_FRAMETIME 90
+
+#define INTRO_CAPTION_Y 170
+#define INTRO_DE_CAPTION_Y 160
+#define VOICE_PAD 50
+#define VOICE_LETTERLEN 90
+
+#define PALETTE_FADE_DURATION 1000
+#define DISSOLVE_DURATION 3000
+#define LOGO_DISSOLVE_DURATION 1000
+
+#define CREDIT_DURATION1 4000
+
+struct IntroDialogue {
+ uint32 i_voice_rn;
+ const char *i_str;
+};
+
+struct IntroCredit {
+ Common::Language lang;
+ int game;
+ int type;
+ const char *string;
+};
+
+
+class Scene {
+ public:
+ Scene(SagaEngine *vm);
+ ~Scene();
+
+// Console functions
+ void cmdActionMapInfo();
+ void cmdObjectMapInfo();
+
+ void cmdSceneChange(int argc, const char **argv);
+
+ void startScene();
+ void nextScene();
+ void skipScene();
+ void endScene();
+ void queueScene(LoadSceneParams *sceneQueue) {
+ _sceneQueue.push_back(*sceneQueue);
+ }
+
+ void draw();
+ int getFlags() const { return _sceneDescription.flags; }
+ int getScriptModuleNumber() const { return _sceneDescription.scriptModuleNumber; }
+ bool isInIntro() { return !_inGame; }
+ const Rect& getSceneClip() const { return _sceneClip; }
+
+ void getBGMaskInfo(int &width, int &height, byte *&buffer, size_t &bufferLength);
+ int isBGMaskPresent() { return _bgMask.loaded; }
+ int getBGMaskType(const Point &testPoint);
+ bool validBGMaskPoint(const Point &testPoint);
+ bool canWalk(const Point &testPoint);
+ bool offscreenPath(Point &testPoint);
+
+ void setDoorState(int doorNumber, int doorState);
+ int getDoorState(int doorNumber);
+ void initDoorsState();
+
+ void getBGInfo(BGInfo &bgInfo);
+ void getBGPal(PalEntry *&pal) {
+ pal = (PalEntry *)_bg.pal;
+ }
+
+ void getSlopes(int &beginSlope, int &endSlope);
+
+ void clearSceneQueue(void) {
+ _sceneQueue.clear();
+ }
+ void changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionType transitionType, int chapter = NO_CHAPTER_CHANGE);
+
+ bool isSceneLoaded() const { return _sceneLoaded; }
+
+ int getSceneResourceId(int sceneNumber) {
+ if ((sceneNumber < 0) || (sceneNumber >= _sceneCount)) {
+ error("getSceneResourceId: wrong sceneNumber %i", sceneNumber);
+ }
+ return _sceneLUT[sceneNumber];
+ }
+ int currentSceneNumber() const { return _sceneNumber; }
+ int currentChapterNumber() const { return _chapterNumber; }
+ void setChapterNumber(int ch) { _chapterNumber = ch; }
+ int getOutsetSceneNumber() const { return _outsetSceneNumber; }
+ int currentSceneResourceId() const { return _sceneResourceId; }
+ void cutawaySkip() {
+ if (_vm->_scene->isInIntro())
+ _vm->_framesEsc = 2;
+ else
+ _vm->_framesEsc = 1;
+ }
+
+ void drawTextList(Surface *ds);
+
+ int getHeight() const {
+ if (_vm->_interface->getMode() == kPanelChapterSelection)
+ return _vm->_gameDisplayInfo.logicalHeight;
+ else
+ return _vm->_gameDisplayInfo.sceneHeight;
+ }
+
+ private:
+ void loadScene(LoadSceneParams *loadSceneParams);
+ void loadSceneDescriptor(uint32 resourceId);
+ void loadSceneResourceList(uint32 resourceId);
+ void loadSceneEntryList(const byte* resourcePointer, size_t resourceLength);
+ void processSceneResources();
+ void getResourceTypes(SAGAResourceTypes *&types, int &typesCount);
+
+
+ SagaEngine *_vm;
+
+ ResourceContext *_sceneContext;
+ int *_sceneLUT;
+ int _sceneCount;
+ SceneQueueList _sceneQueue;
+ bool _sceneLoaded;
+ int _sceneNumber;
+ int _chapterNumber;
+ int _outsetSceneNumber;
+ int _sceneResourceId;
+ bool _inGame;
+ bool _loadDescription;
+ SceneDescription _sceneDescription;
+ size_t _resourceListCount;
+ SceneResourceData *_resourceList;
+ SceneProc *_sceneProc;
+ SceneImage _bg;
+ SceneImage _bgMask;
+ Common::Rect _sceneClip;
+
+ int _sceneDoors[SCENE_DOORS_MAX];
+
+
+ public:
+ ObjectMap *_actionMap;
+ ObjectMap *_objectMap;
+ SceneEntryList _entryList;
+ StringsTable _sceneStrings;
+ TextList _textList;
+
+ private:
+ int IHNMStartProc();
+ int ITEStartProc();
+
+ public:
+ static int SC_IHNMIntroMovieProc1(int param, void *refCon);
+ static int SC_IHNMIntroMovieProc2(int param, void *refCon);
+ static int SC_IHNMIntroMovieProc3(int param, void *refCon);
+ static int SC_IHNMHateProc(int param, void *refCon);
+
+ private:
+ int IHNMIntroMovieProc1(int param);
+ int IHNMIntroMovieProc2(int param);
+ int IHNMIntroMovieProc3(int param);
+ int IHNMHateProc(int param);
+
+ public:
+ static int SC_ITEIntroAnimProc(int param, void *refCon);
+ static int SC_ITEIntroCave1Proc(int param, void *refCon);
+ static int SC_ITEIntroCave2Proc(int param, void *refCon);
+ static int SC_ITEIntroCave3Proc(int param, void *refCon);
+ static int SC_ITEIntroCave4Proc(int param, void *refCon);
+ static int SC_ITEIntroValleyProc(int param, void *refCon);
+ static int SC_ITEIntroTreeHouseProc(int param, void *refCon);
+ static int SC_ITEIntroFairePathProc(int param, void *refCon);
+ static int SC_ITEIntroFaireTentProc(int param, void *refCon);
+
+ private:
+ Event *ITEQueueDialogue(Event *q_event, int n_dialogues, const IntroDialogue dialogue[]);
+ Event *ITEQueueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]);
+ int ITEIntroAnimProc(int param);
+ int ITEIntroCave1Proc(int param);
+ int ITEIntroCave2Proc(int param);
+ int ITEIntroCave3Proc(int param);
+ int ITEIntroCave4Proc(int param);
+ int ITEIntroValleyProc(int param);
+ int ITEIntroTreeHouseProc(int param);
+ int ITEIntroFairePathProc(int param);
+ int ITEIntroFaireTentProc(int param);
+
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp
new file mode 100644
index 0000000000..3d3a626e9f
--- /dev/null
+++ b/engines/saga/script.cpp
@@ -0,0 +1,806 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Scripting module: Script resource handling functions
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/console.h"
+
+#include "saga/script.h"
+#include "saga/stream.h"
+#include "saga/interface.h"
+#include "saga/itedata.h"
+#include "saga/scene.h"
+#include "saga/events.h"
+#include "saga/actor.h"
+#include "saga/objectmap.h"
+#include "saga/isomap.h"
+#include "saga/rscfile.h"
+
+namespace Saga {
+
+
+
+// Initializes the scripting module.
+// Loads script resource look-up table, initializes script data system
+Script::Script(SagaEngine *vm) : _vm(vm) {
+ ResourceContext *resourceContext;
+ byte *resourcePointer;
+ size_t resourceLength;
+ int prevTell;
+ int i, j;
+ byte *stringsPointer;
+ size_t stringsLength;
+
+ //initialize member variables
+ _abortEnabled = true;
+ _skipSpeeches = false;
+ _conversingThread = NULL;
+
+ _firstObjectSet = false;
+ _secondObjectNeeded = false;
+ _pendingVerb = getVerbType(kVerbNone);
+ _currentVerb = getVerbType(kVerbNone);
+ _stickyVerb = getVerbType(kVerbWalkTo);
+ _leftButtonVerb = getVerbType(kVerbNone);
+ _rightButtonVerb = getVerbType(kVerbNone);
+ _pointerObject = ID_NOTHING;
+
+ _staticSize = 0;
+ _commonBufferSize = COMMON_BUFFER_SIZE;
+ _commonBuffer = (byte*)malloc(_commonBufferSize);
+ memset(_commonBuffer, 0, _commonBufferSize);
+
+ debug(8, "Initializing scripting subsystem");
+ // Load script resource file context
+ _scriptContext = _vm->_resource->getContext(GAME_SCRIPTFILE);
+ if (_scriptContext == NULL) {
+ error("Script::Script() script context not found");
+ }
+
+ resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (resourceContext == NULL) {
+ error("Script::Script() resource context not found");
+ }
+
+ debug(3, "Loading module LUT from resource %i", _vm->getResourceDescription()->moduleLUTResourceId);
+ _vm->_resource->loadResource(resourceContext, _vm->getResourceDescription()->moduleLUTResourceId, resourcePointer, resourceLength);
+
+
+ // Create logical script LUT from resource
+ if (resourceLength % S_LUT_ENTRYLEN_ITECD == 0) {
+ _modulesLUTEntryLen = S_LUT_ENTRYLEN_ITECD;
+ } else if (resourceLength % S_LUT_ENTRYLEN_ITEDISK == 0) {
+ _modulesLUTEntryLen = S_LUT_ENTRYLEN_ITEDISK;
+ } else {
+ error("Script::Script() Invalid script lookup table length (%i)", resourceLength);
+ }
+
+ // Calculate number of entries
+ _modulesCount = resourceLength / _modulesLUTEntryLen;
+
+ debug(3, "LUT has %i entries", _modulesCount);
+
+ // Allocate space for logical LUT
+ _modules = (ModuleData *)malloc(_modulesCount * sizeof(*_modules));
+ if (_modules == NULL) {
+ memoryError("Script::Script()");
+ }
+
+ // Convert LUT resource to logical LUT
+ MemoryReadStreamEndian scriptS(resourcePointer, resourceLength, resourceContext->isBigEndian);
+ for (i = 0; i < _modulesCount; i++) {
+ memset(&_modules[i], 0, sizeof(ModuleData));
+
+ prevTell = scriptS.pos();
+ _modules[i].scriptResourceId = scriptS.readUint16();
+ _modules[i].stringsResourceId = scriptS.readUint16();
+ _modules[i].voicesResourceId = scriptS.readUint16();
+
+ // Skip the unused portion of the structure
+ for (j = scriptS.pos(); j < prevTell + _modulesLUTEntryLen; j++) {
+ if (scriptS.readByte() != 0)
+ warning("Unused scriptLUT part isn't really unused for LUT %d (pos: %d)", i, j);
+ }
+ }
+
+ free(resourcePointer);
+
+ // TODO
+ //
+ // In ITE, the "main strings" resource contains both the verb strings
+ // and the object names.
+ //
+ // In IHNM, the "main strings" contains the verb strings, but not the
+ // object names. At least, I think that's the case.
+
+ _vm->_resource->loadResource(resourceContext, _vm->getResourceDescription()->mainStringsResourceId, stringsPointer, stringsLength);
+
+ _vm->loadStrings(_mainStrings, stringsPointer, stringsLength);
+ free(stringsPointer);
+
+ setupScriptFuncList();
+}
+
+// Shut down script module gracefully; free all allocated module resources
+Script::~Script() {
+
+ debug(8, "Shutting down scripting subsystem.");
+
+ _mainStrings.freeMem();
+
+ freeModules();
+ free(_modules);
+
+ free(_commonBuffer);
+}
+
+void Script::loadModule(int scriptModuleNumber) {
+ byte *resourcePointer;
+ size_t resourceLength;
+
+ // Validate script number
+ if ((scriptModuleNumber < 0) || (scriptModuleNumber >= _modulesCount)) {
+ error("Script::loadScript() Invalid script module number");
+ }
+
+ if (_modules[scriptModuleNumber].loaded) {
+ return;
+ }
+
+ // Initialize script data structure
+ debug(3, "Loading script module #%d", scriptModuleNumber);
+
+ _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].scriptResourceId, resourcePointer, resourceLength);
+
+ loadModuleBase(_modules[scriptModuleNumber], resourcePointer, resourceLength);
+ free(resourcePointer);
+
+ _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].stringsResourceId, resourcePointer, resourceLength);
+
+ _vm->loadStrings(_modules[scriptModuleNumber].strings, resourcePointer, resourceLength);
+ free(resourcePointer);
+
+ if (_modules[scriptModuleNumber].voicesResourceId > 0) {
+ _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].voicesResourceId, resourcePointer, resourceLength);
+
+ loadVoiceLUT(_modules[scriptModuleNumber].voiceLUT, resourcePointer, resourceLength);
+ free(resourcePointer);
+ }
+
+ _modules[scriptModuleNumber].staticOffset = _staticSize;
+ _staticSize += _modules[scriptModuleNumber].staticSize;
+ if (_staticSize > _commonBufferSize) {
+ error("Script::loadModule() _staticSize > _commonBufferSize");
+ }
+ _modules[scriptModuleNumber].loaded = true;
+}
+
+void Script::freeModules() {
+ int i;
+ for (i = 0; i < _modulesCount; i++) {
+ if (_modules[i].loaded) {
+ _modules[i].freeMem();
+ }
+ }
+ _staticSize = 0;
+}
+
+void Script::loadModuleBase(ModuleData &module, const byte *resourcePointer, size_t resourceLength) {
+ int i;
+
+ debug(3, "Loading module base...");
+
+ module.moduleBase = (byte*)malloc(resourceLength);
+ module.moduleBaseSize = resourceLength;
+
+ memcpy(module.moduleBase, resourcePointer, resourceLength);
+
+ MemoryReadStreamEndian scriptS(module.moduleBase, module.moduleBaseSize, _scriptContext->isBigEndian);
+
+ module.entryPointsCount = scriptS.readUint16();
+ scriptS.readUint16(); //skip
+ module.entryPointsTableOffset = scriptS.readUint16();
+ scriptS.readUint16(); //skip
+
+ if ((module.moduleBaseSize - module.entryPointsTableOffset) < (module.entryPointsCount * SCRIPT_TBLENTRY_LEN)) {
+ error("Script::loadModuleBase() Invalid table offset");
+ }
+
+ if (module.entryPointsCount > SCRIPT_MAX) {
+ error("Script::loadModuleBase()Script limit exceeded");
+ }
+
+ module.entryPoints = (EntryPoint *)malloc(module.entryPointsCount * sizeof(*module.entryPoints));
+ if (module.entryPoints == NULL) {
+ memoryError("Script::loadModuleBase");
+ }
+
+ // Read in the entrypoint table
+
+ module.staticSize = scriptS.readUint16();
+ while (scriptS.pos() < module.entryPointsTableOffset)
+ scriptS.readByte();
+
+ for (i = 0; i < module.entryPointsCount; i++) {
+ // First uint16 is the offset of the entrypoint name from the start
+ // of the bytecode resource, second uint16 is the offset of the
+ // bytecode itself for said entrypoint
+ module.entryPoints[i].nameOffset = scriptS.readUint16();
+ module.entryPoints[i].offset = scriptS.readUint16();
+
+ // Perform a simple range check on offset values
+ if ((module.entryPoints[i].nameOffset >= module.moduleBaseSize) || (module.entryPoints[i].offset >= module.moduleBaseSize)) {
+ error("Script::loadModuleBase() Invalid offset encountered in script entrypoint table");
+ }
+ }
+}
+
+void Script::loadVoiceLUT(VoiceLUT &voiceLUT, const byte *resourcePointer, size_t resourceLength) {
+ uint16 i;
+
+ voiceLUT.voicesCount = resourceLength / 2;
+
+ voiceLUT.voices = (uint16 *)malloc(voiceLUT.voicesCount * sizeof(*voiceLUT.voices));
+ if (voiceLUT.voices == NULL) {
+ error("Script::loadVoiceLUT() not enough memory");
+ }
+
+ MemoryReadStreamEndian scriptS(resourcePointer, resourceLength, _scriptContext->isBigEndian);
+
+ for (i = 0; i < voiceLUT.voicesCount; i++) {
+ voiceLUT.voices[i] = scriptS.readUint16();
+ }
+}
+
+// verb
+void Script::showVerb(int statusColor) {
+ const char *verbName;
+ const char *object1Name;
+ const char *object2Name;
+ char statusString[STATUS_TEXT_LEN];
+
+ if (_leftButtonVerb == getVerbType(kVerbNone)) {
+ _vm->_interface->setStatusText("");
+ return;
+ }
+
+ verbName = _mainStrings.getString(_leftButtonVerb - 1);
+
+ if (objectTypeId(_currentObject[0]) == kGameObjectNone) {
+ _vm->_interface->setStatusText(verbName, statusColor);
+ return;
+ }
+
+ object1Name = _vm->getObjectName(_currentObject[0]);
+
+ if (!_secondObjectNeeded) {
+ snprintf(statusString, STATUS_TEXT_LEN, "%s %s", verbName, object1Name);
+ _vm->_interface->setStatusText(statusString, statusColor);
+ return;
+ }
+
+
+ if (objectTypeId(_currentObject[1]) != kGameObjectNone) {
+ object2Name = _vm->getObjectName(_currentObject[1]);
+ } else {
+ object2Name = "";
+ }
+
+ if (_leftButtonVerb == getVerbType(kVerbGive)) {
+ snprintf(statusString, STATUS_TEXT_LEN, _vm->getTextString(kTextGiveTo), object1Name, object2Name);
+ _vm->_interface->setStatusText(statusString, statusColor);
+ } else {
+ if (_leftButtonVerb == getVerbType(kVerbUse)) {
+ snprintf(statusString, STATUS_TEXT_LEN, _vm->getTextString(kTextUseWidth), object1Name, object2Name);
+ _vm->_interface->setStatusText(statusString, statusColor);
+ } else {
+ snprintf(statusString, STATUS_TEXT_LEN, "%s %s", verbName, object1Name);
+ _vm->_interface->setStatusText(statusString, statusColor);
+ }
+ }
+}
+
+int Script::getVerbType(VerbTypes verbType) {
+ if (_vm->getGameType() == GType_ITE) {
+ switch (verbType) {
+ case kVerbNone:
+ return kVerbITENone;
+ case kVerbWalkTo:
+ return kVerbITEWalkTo;
+ case kVerbGive:
+ return kVerbITEGive;
+ case kVerbUse:
+ return kVerbITEUse;
+ case kVerbEnter:
+ return kVerbITEEnter;
+ case kVerbLookAt:
+ return kVerbITELookAt;
+ case kVerbPickUp:
+ return kVerbITEPickUp;
+ case kVerbOpen:
+ return kVerbITEOpen;
+ case kVerbClose:
+ return kVerbITEClose;
+ case kVerbTalkTo:
+ return kVerbITETalkTo;
+ case kVerbWalkOnly:
+ return kVerbITEWalkOnly;
+ case kVerbLookOnly:
+ return kVerbITELookOnly;
+ case kVerbOptions:
+ return kVerbITEOptions;
+ }
+ }
+ else {
+ switch (verbType) {
+ case kVerbNone:
+ return kVerbIHNMNone;
+ case kVerbWalkTo:
+ return kVerbIHNMWalk;
+ case kVerbGive:
+ return kVerbIHNMGive;
+ case kVerbUse:
+ return kVerbIHNMUse;
+ case kVerbEnter:
+ return kVerbIHNMEnter;
+ case kVerbLookAt:
+ return kVerbIHNMLookAt;
+ case kVerbPickUp:
+ return kVerbIHNMTake;
+ case kVerbOpen:
+ return -2;
+ case kVerbClose:
+ return -2;
+ case kVerbTalkTo:
+ return kVerbIHNMTalkTo;
+ case kVerbWalkOnly:
+ return kVerbIHNMWalkOnly;
+ case kVerbLookOnly:
+ return kVerbIHNMLookOnly;
+ case kVerbOptions:
+ return kVerbIHNMOptions;
+ }
+ }
+ error("Script::getVerbType() unknown verb type %d", verbType);
+}
+
+void Script::setVerb(int verb) {
+ _pendingObject[0] = ID_NOTHING;
+ _currentObject[0] = ID_NOTHING;
+ _pendingObject[1] = ID_NOTHING;
+ _currentObject[1] = ID_NOTHING;
+ _firstObjectSet = false;
+ _secondObjectNeeded = false;
+
+ // The pointer object will be updated again immediately. This way the
+ // new verb will be applied to it. It's not exactly how the original
+ // engine did it, but it appears to work.
+ _pointerObject = ID_NOTHING;
+
+ setLeftButtonVerb( verb );
+ showVerb();
+}
+
+void Script::setLeftButtonVerb(int verb) {
+ int oldVerb = _currentVerb;
+
+ _currentVerb = _leftButtonVerb = verb;
+
+ if ((_currentVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){
+ if (oldVerb > getVerbType(kVerbNone))
+ _vm->_interface->setVerbState(oldVerb, 2);
+
+ if (_currentVerb > getVerbType(kVerbNone))
+ _vm->_interface->setVerbState(_currentVerb, 2);
+ }
+}
+
+void Script::setRightButtonVerb(int verb) {
+ int oldVerb = _rightButtonVerb;
+
+ _rightButtonVerb = verb;
+
+ if ((_rightButtonVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){
+ if (oldVerb > getVerbType(kVerbNone))
+ _vm->_interface->setVerbState(oldVerb, 2);
+
+ if (_rightButtonVerb > getVerbType(kVerbNone))
+ _vm->_interface->setVerbState(_rightButtonVerb, 2);
+ }
+}
+
+void Script::doVerb() {
+ int scriptEntrypointNumber = 0;
+ int scriptModuleNumber = 0;
+ int objectType;
+ Event event;
+ const char *excuseText;
+ int excuseSampleResourceId;
+ const HitZone *hitZone;
+
+ objectType = objectTypeId(_pendingObject[0]);
+
+ if (_pendingVerb == getVerbType(kVerbGive)) {
+ scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[1]);
+ if (_vm->_actor->getObjectFlags(_pendingObject[1]) & (kFollower|kProtagonist|kExtended)) {
+ scriptModuleNumber = 0;
+ } else {
+ scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
+ }
+ } else {
+ if (_pendingVerb == getVerbType(kVerbUse)) {
+ if ((objectTypeId(_pendingObject[1]) > kGameObjectNone) && (objectType < objectTypeId(_pendingObject[1]))) {
+ SWAP(_pendingObject[0], _pendingObject[1]);
+ objectType = objectTypeId(_pendingObject[0]);
+ }
+ }
+
+ if (objectType == kGameObjectHitZone) {
+ scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
+ hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0]));
+ if ((hitZone->getFlags() & kHitZoneExit) == 0) {
+ scriptEntrypointNumber = hitZone->getScriptNumber();
+ }
+ } else {
+ if (objectType & (kGameObjectActor | kGameObjectObject)) {
+ scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[0]);
+
+ if ((objectType == kGameObjectActor) && !(_vm->_actor->getObjectFlags(_pendingObject[0]) & (kFollower|kProtagonist|kExtended))) {
+ scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
+ } else {
+ scriptModuleNumber = 0;
+ }
+ }
+ }
+ }
+
+ if (scriptEntrypointNumber > 0) {
+
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventExecNonBlocking;
+ event.time = 0;
+ event.param = scriptModuleNumber;
+ event.param2 = scriptEntrypointNumber;
+ event.param3 = _pendingVerb; // Action
+ event.param4 = _pendingObject[0]; // Object
+ event.param5 = _pendingObject[1]; // With Object
+ event.param6 = (objectType == kGameObjectActor) ? _pendingObject[0] : ID_PROTAG; // Actor
+
+ _vm->_events->queue(&event);
+
+ } else {
+ _vm->getExcuseInfo(_pendingVerb, excuseText, excuseSampleResourceId);
+ if (excuseText) {
+ // In Floppy versions we don't have excuse texts
+ if (!(_vm->getFeatures() & GF_CD_FX))
+ excuseSampleResourceId = -1;
+
+ _vm->_actor->actorSpeech(ID_PROTAG, &excuseText, 1, excuseSampleResourceId, 0);
+ }
+ }
+
+ if ((_currentVerb == getVerbType(kVerbWalkTo)) || (_currentVerb == getVerbType(kVerbLookAt))) {
+ _stickyVerb = _currentVerb;
+ }
+
+ _pendingVerb = getVerbType(kVerbNone);
+ _currentObject[0] = _currentObject[1] = ID_NOTHING;
+ setLeftButtonVerb(_stickyVerb);
+
+ setPointerVerb();
+}
+
+void Script::setPointerVerb() {
+ if (_vm->_interface->isActive()) {
+ _pointerObject = ID_PROTAG;
+ whichObject(_vm->mousePos());
+ }
+}
+
+void Script::hitObject(bool leftButton) {
+ int verb;
+ verb = leftButton ? _leftButtonVerb : _rightButtonVerb;
+
+ if (verb > getVerbType(kVerbNone)) {
+ if (_firstObjectSet) {
+ if (_secondObjectNeeded) {
+ _pendingObject[0] = _currentObject[0];
+ _pendingObject[1] = _currentObject[1];
+ _pendingVerb = verb;
+
+ _leftButtonVerb = verb;
+ if (_pendingVerb > getVerbType(kVerbNone))
+ showVerb(kITEColorBrightWhite);
+ else
+ showVerb();
+
+ _secondObjectNeeded = false;
+ _firstObjectSet = false;
+ return;
+ }
+ } else {
+ if (verb == getVerbType(kVerbGive)) {
+ _secondObjectNeeded = true;
+ } else {
+ if (verb == getVerbType(kVerbUse)) {
+
+ if (_currentObjectFlags[0] & kObjUseWith) {
+ _secondObjectNeeded = true;
+ }
+ }
+ }
+
+ if (!_secondObjectNeeded) {
+ _pendingObject[0] = _currentObject[0];
+ _pendingObject[1] = ID_NOTHING;
+ _pendingVerb = verb;
+
+ _secondObjectNeeded = false;
+ _firstObjectSet = false;
+ } else {
+ _firstObjectSet = true;
+ }
+ }
+
+ _leftButtonVerb = verb;
+ if (_pendingVerb > getVerbType(kVerbNone))
+ showVerb(kITEColorBrightWhite);
+ else
+ showVerb();
+ }
+
+}
+
+void Script::playfieldClick(const Point& mousePoint, bool leftButton) {
+ Location pickLocation;
+ const HitZone *hitZone;
+ Point specialPoint;
+
+ _vm->_actor->abortSpeech();
+
+ if ((_vm->_actor->_protagonist->_currentAction != kActionWait) &&
+ (_vm->_actor->_protagonist->_currentAction != kActionFreeze) &&
+ (_vm->_actor->_protagonist->_currentAction != kActionWalkToLink) &&
+ (_vm->_actor->_protagonist->_currentAction != kActionWalkToPoint)) {
+ return;
+ }
+ if (_pendingVerb > getVerbType(kVerbNone)) {
+ setLeftButtonVerb(getVerbType(kVerbWalkTo));
+ }
+
+ if (_pointerObject != ID_NOTHING) {
+ hitObject( leftButton );
+ } else {
+ _pendingObject[0] = ID_NOTHING;
+ _pendingObject[1] = ID_NOTHING;
+ _pendingVerb = getVerbType(kVerbWalkTo);
+ }
+
+
+ // tiled stuff
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->screenPointToTileCoords(mousePoint, pickLocation);
+ } else {
+ pickLocation.fromScreenPoint(mousePoint);
+ }
+
+
+ hitZone = NULL;
+
+ if (objectTypeId(_pendingObject[0]) == kGameObjectHitZone) {
+ hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0]));
+ } else {
+ if ((_pendingVerb == getVerbType(kVerbUse)) && (objectTypeId(_pendingObject[1]) == kGameObjectHitZone)) {
+ hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[1]));
+ }
+ }
+
+ if (hitZone != NULL) {
+ if (hitZone->getFlags() & kHitZoneNoWalk) {
+ _vm->_actor->actorFaceTowardsPoint(ID_PROTAG, pickLocation);
+ doVerb();
+ return;
+ }
+
+ if (hitZone->getFlags() & kHitZoneProject) {
+ if (!hitZone->getSpecialPoint(specialPoint)) {
+ // Original behaved this way and this prevents from crash
+ // at ruins. See bug #1257459
+ specialPoint.x = specialPoint.y = 0;
+ }
+
+ // tiled stuff
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ pickLocation.u() = specialPoint.x;
+ pickLocation.v() = specialPoint.y;
+ pickLocation.z = _vm->_actor->_protagonist->_location.z;
+ } else {
+ pickLocation.fromScreenPoint(specialPoint);
+ }
+ }
+ }
+
+ if ((_pendingVerb == getVerbType(kVerbWalkTo)) ||
+ (_pendingVerb == getVerbType(kVerbPickUp)) ||
+ (_pendingVerb == getVerbType(kVerbOpen)) ||
+ (_pendingVerb == getVerbType(kVerbClose)) ||
+ (_pendingVerb == getVerbType(kVerbUse))) {
+ _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
+ } else {
+ if (_pendingVerb == getVerbType(kVerbLookAt)) {
+ if (objectTypeId(_pendingObject[0]) != kGameObjectActor ) {
+ _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
+ } else {
+ doVerb();
+ }
+ } else {
+ if ((_pendingVerb == getVerbType(kVerbTalkTo)) ||
+ (_pendingVerb == getVerbType(kVerbGive))) {
+ doVerb();
+ }
+ }
+ }
+}
+
+void Script::whichObject(const Point& mousePoint) {
+ uint16 objectId;
+ int16 objectFlags;
+ int newRightButtonVerb;
+ uint16 newObjectId;
+ ActorData *actor;
+ ObjectData *obj;
+ Point pickPoint;
+ Location pickLocation;
+ int hitZoneIndex;
+ const HitZone * hitZone;
+ PanelButton * panelButton;
+
+ objectId = ID_NOTHING;
+ objectFlags = 0;
+ _leftButtonVerb = _currentVerb;
+ newRightButtonVerb = getVerbType(kVerbNone);
+
+ if (_vm->_actor->_protagonist->_currentAction != kActionWalkDir) {
+ if (_vm->_scene->getHeight() >= mousePoint.y) {
+ newObjectId = _vm->_actor->hitTest(mousePoint, true);
+
+ if (newObjectId != ID_NOTHING) {
+ if (objectTypeId(newObjectId) == kGameObjectObject) {
+ objectId = newObjectId;
+ objectFlags = 0;
+ newRightButtonVerb = getVerbType(kVerbLookAt);
+
+ if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
+ objectId = ID_NOTHING;
+ newObjectId = ID_NOTHING;
+ }
+ } else {
+ actor = _vm->_actor->getActor(newObjectId);
+ objectId = newObjectId;
+ objectFlags = kObjUseWith;
+ newRightButtonVerb = getVerbType(kVerbTalkTo);
+
+ if ((_currentVerb == getVerbType(kVerbPickUp)) ||
+ (_currentVerb == getVerbType(kVerbOpen)) ||
+ (_currentVerb == getVerbType(kVerbClose)) ||
+ ((_currentVerb == getVerbType(kVerbGive)) && !_firstObjectSet) ||
+ ((_currentVerb == getVerbType(kVerbUse)) && !(actor->_flags & kFollower))) {
+ objectId = ID_NOTHING;
+ newObjectId = ID_NOTHING;
+ }
+ }
+ }
+
+ if (newObjectId == ID_NOTHING) {
+
+ pickPoint = mousePoint;
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ pickPoint.y -= _vm->_actor->_protagonist->_location.z;
+ _vm->_isoMap->screenPointToTileCoords(pickPoint, pickLocation);
+ pickLocation.toScreenPointUV(pickPoint);
+ }
+
+ hitZoneIndex = _vm->_scene->_objectMap->hitTest(pickPoint);
+
+ if ((hitZoneIndex != -1)) {
+ hitZone = _vm->_scene->_objectMap->getHitZone(hitZoneIndex);
+ objectId = hitZone->getHitZoneId();
+ objectFlags = 0;
+ newRightButtonVerb = hitZone->getRightButtonVerb() & 0x7f;
+
+ if (_vm->getGameType() == GType_ITE) {
+
+ if (newRightButtonVerb == getVerbType(kVerbWalkOnly)) {
+ if (_firstObjectSet) {
+ objectId = ID_NOTHING;
+ } else {
+ newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbWalkTo);
+ }
+ } else {
+ if (newRightButtonVerb == getVerbType(kVerbLookOnly)) {
+ if (_firstObjectSet) {
+ objectId = ID_NOTHING;
+ } else {
+ newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbLookAt);
+ }
+ }
+ }
+
+ if (newRightButtonVerb >= getVerbType(kVerbOptions)) {
+ newRightButtonVerb = getVerbType(kVerbNone);
+ }
+ }
+
+ if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
+ objectId = ID_NOTHING;
+ newObjectId = ID_NOTHING;
+ }
+
+ if ((_leftButtonVerb == getVerbType(kVerbUse)) && (hitZone->getRightButtonVerb() & 0x80)) {
+ objectFlags = kObjUseWith;
+ }
+ }
+ }
+ } else {
+ if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
+ // no way
+ } else {
+ panelButton = _vm->_interface->inventoryHitTest(mousePoint);
+ if (panelButton) {
+ objectId = _vm->_interface->getInventoryContentByPanelButton(panelButton);
+ if (objectId != 0) {
+ obj = _vm->_actor->getObj(objectId);
+ newRightButtonVerb = getVerbType(kVerbLookAt);
+ if (obj->_interactBits & kObjUseWith) {
+ objectFlags = kObjUseWith;
+ }
+ }
+ }
+ }
+
+ if ((_currentVerb == getVerbType(kVerbPickUp)) || (_currentVerb == getVerbType(kVerbTalkTo)) || (_currentVerb == getVerbType(kVerbWalkTo))) {
+ _leftButtonVerb = getVerbType(kVerbLookAt);
+ }
+ }
+ }
+
+ if (objectId != _pointerObject) {
+ _pointerObject = objectId;
+ _currentObject[_firstObjectSet ? 1 : 0] = objectId;
+ _currentObjectFlags[_firstObjectSet ? 1 : 0] = objectFlags;
+ if (_pendingVerb == getVerbType(kVerbNone)) {
+ showVerb();
+ }
+ }
+
+ if (newRightButtonVerb != _rightButtonVerb) {
+ setRightButtonVerb(newRightButtonVerb);
+ }
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/script.h b/engines/saga/script.h
new file mode 100644
index 0000000000..bc11013b3f
--- /dev/null
+++ b/engines/saga/script.h
@@ -0,0 +1,637 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Scripting module private header
+
+#ifndef SAGA_SCRIPT_H
+#define SAGA_SCRIPT_H
+
+#include "saga/font.h"
+#include "saga/list.h"
+
+namespace Saga {
+
+#define COMMON_BUFFER_SIZE 1024 // Why 1024?
+
+#define S_LUT_ENTRYLEN_ITECD 22
+#define S_LUT_ENTRYLEN_ITEDISK 16
+
+#define SCRIPT_TBLENTRY_LEN 4
+
+#define SCRIPT_MAX 5000
+
+#define ITE_SCRIPT_FUNCTION_MAX 78
+#define IHNM_SCRIPT_FUNCTION_MAX 105
+#define DEFAULT_THREAD_STACK_SIZE 256
+
+enum AddressTypes {
+ kAddressCommon = 0, // offset from global variables
+ kAddressStatic = 1, // offset from global variables
+ kAddressModule = 2, // offset from start of module
+ kAddressStack = 3, // offset from stack
+ kAddressThread = 4 // offset from thread structure
+/* kAddressId = 5, // offset from const id object
+ kAddressIdIndirect = 6, // offset from stack id object
+ kAddressIndex = 7 // index from id*/
+};
+
+enum VerbTypeIds {
+ kVerbITENone = 0,
+ kVerbITEPickUp = 1,
+ kVerbITELookAt = 2,
+ kVerbITEWalkTo = 3,
+ kVerbITETalkTo = 4,
+ kVerbITEOpen = 5,
+ kVerbITEClose = 6,
+ kVerbITEGive = 7,
+ kVerbITEUse = 8,
+ kVerbITEOptions = 9,
+ kVerbITEEnter = 10,
+ kVerbITELeave = 11,
+ kVerbITEBegin = 12,
+ kVerbITEWalkOnly = 13,
+ kVerbITELookOnly = 14,
+
+
+ kVerbIHNMNone = 0,
+ kVerbIHNMWalk = 1,
+ kVerbIHNMLookAt = 2,
+ kVerbIHNMTake = 3,
+ kVerbIHNMUse = 4,
+ kVerbIHNMTalkTo = 5,
+ kVerbIHNMSwallow = 6,
+ kVerbIHNMGive = 7,
+ kVerbIHNMPush = 8,
+ kVerbIHNMOptions = 9,
+ kVerbIHNMEnter = 10,
+ kVerbIHNMLeave = 11,
+ kVerbIHNMBegin = 12,
+ kVerbIHNMWalkOnly = 13,
+ kVerbIHNMLookOnly = 14,
+
+ kVerbTypeIdsMax = kVerbITELookOnly + 1
+};
+
+enum VerbTypes {
+ kVerbNone,
+ kVerbWalkTo,
+ kVerbGive,
+ kVerbUse,
+ kVerbEnter,
+ kVerbLookAt,
+ kVerbPickUp,
+ kVerbOpen,
+ kVerbClose,
+ kVerbTalkTo,
+ kVerbWalkOnly,
+ kVerbLookOnly,
+ kVerbOptions
+};
+
+#define STHREAD_TIMESLICE 8
+
+enum ThreadVarTypes {
+ kThreadVarObject = 0,
+ kThreadVarWithObject = 1,
+ kThreadVarAction = 2,
+ kThreadVarActor = 3,
+
+ kThreadVarMax = kThreadVarActor + 1
+};
+
+enum ThreadFlags {
+ kTFlagNone = 0,
+ kTFlagWaiting = 1, // wait for even denoted in waitType
+ kTFlagFinished = 2,
+ kTFlagAborted = 4,
+ kTFlagAsleep = kTFlagWaiting | kTFlagFinished | kTFlagAborted // Combination of all flags which can halt a thread
+};
+
+enum ThreadWaitTypes {
+ kWaitTypeNone = 0, // waiting for nothing
+ kWaitTypeDelay = 1, // waiting for a timer
+ kWaitTypeSpeech = 2, // waiting for speech to finish
+ kWaitTypeDialogEnd = 3, // waiting for my dialog to finish
+ kWaitTypeDialogBegin = 4, // waiting for other dialog to finish
+ kWaitTypeWalk = 5, // waiting to finish walking
+ kWaitTypeRequest = 6, // a request is up
+ kWaitTypePause = 7,
+ kWaitTypePlacard = 8,
+ kWaitTypeStatusTextInput = 9,
+ kWaitTypeWaitFrames = 10 // IHNM. waiting for a frame count
+};
+
+enum OpCodes {
+ opNextBlock = 0x01,
+ opDup = 0x02,
+ opDrop = 0x03,
+ opZero = 0x04,
+ opOne = 0x05,
+ opConstint = 0x06,
+//...
+ opStrlit = 0x08,
+//...
+ opGetFlag = 0x0B,
+ opGetInt = 0x0C,
+//...
+ opPutFlag = 0x0F,
+ opPutInt = 0x10,
+ //...
+ opPutFlagV = 0x13,
+ opPutIntV = 0x14,
+//...
+ opCall = 0x17,
+ opCcall = 0x18,
+ opCcallV = 0x19,
+ opEnter = 0x1A,
+ opReturn = 0x1B,
+ opReturnV = 0x1C,
+ opJmp = 0x1D,
+ opJmpTrueV = 0x1E,
+ opJmpFalseV = 0x1F,
+ opJmpTrue = 0x20,
+ opJmpFalse = 0x21,
+ opJmpSwitch = 0x22,
+//...
+ opJmpRandom = 0x24,
+ opNegate = 0x25,
+ opNot = 0x26,
+ opCompl = 0x27,
+ opIncV = 0x28,
+ opDecV = 0x29,
+ opPostInc = 0x2A,
+ opPostDec = 0x2B,
+ opAdd = 0x2C,
+ opSub = 0x2D,
+ opMul = 0x2E,
+ opDiv = 0x2F,
+ opMod = 0x30,
+//...
+ opEq = 0x33,
+ opNe = 0x34,
+ opGt = 0x35,
+ opLt = 0x36,
+ opGe = 0x37,
+ opLe = 0x38,
+//...
+ opRsh = 0x3F,
+ opLsh = 0x40,
+ opAnd = 0x41,
+ opOr = 0x42,
+ opXor = 0x43,
+ opLAnd = 0x44,
+ opLOr = 0x45,
+ opLXor = 0x46,
+//...
+ opSpeak = 0x53,
+ opDialogBegin = 0x54,
+ opDialogEnd = 0x55,
+ opReply = 0x56,
+ opAnimate = 0x57
+};
+
+enum CycleFlags {
+ kCyclePong = 1 << 0,
+ kCycleOnce = 1 << 1,
+ kCycleRandom = 1 << 2,
+ kCycleReverse = 1 << 3
+};
+
+enum WalkFlags {
+ kWalkBackPedal = 1 << 0,
+ kWalkAsync = 1 << 1,
+ kWalkUseAngle = 1 << 2,
+ kWalkFace = 1 << 5
+};
+
+enum ReplyFlags {
+ kReplyOnce = 1 << 0,
+ kReplySummary = 1 << 1,
+ kReplyCondition = 1 << 2
+};
+
+struct EntryPoint {
+ uint16 nameOffset;
+ uint16 offset;
+};
+
+struct VoiceLUT {
+ uint16 voicesCount;
+ uint16 *voices;
+ void freeMem() {
+ voicesCount = 0;
+ free(voices);
+ }
+ VoiceLUT() {
+ memset(this, 0, sizeof(*this));
+ }
+};
+
+struct ModuleData {
+ bool loaded; // is it loaded or not?
+ int scriptResourceId;
+ int stringsResourceId;
+ int voicesResourceId;
+
+ byte *moduleBase; // all base module
+ uint16 moduleBaseSize; // base module size
+ uint16 staticSize; // size of static data
+ uint staticOffset; // offset of static data begining in _commonBuffer
+
+ uint16 entryPointsTableOffset; // offset of entrypoint table in moduleBase
+ uint16 entryPointsCount;
+ EntryPoint *entryPoints;
+
+ StringsTable strings;
+ VoiceLUT voiceLUT;
+ void freeMem() {
+ strings.freeMem();
+ voiceLUT.freeMem();
+ free(moduleBase);
+ free(entryPoints);
+ memset(this, 0x0, sizeof(*this));
+ }
+};
+
+class ScriptThread {
+public:
+ uint16 *_stackBuf;
+ uint16 _stackSize; // stack size in uint16
+
+ uint16 _stackTopIndex;
+ uint16 _frameIndex;
+
+ uint16 _threadVars[kThreadVarMax];
+
+ byte *_moduleBase; //
+ uint16 _moduleBaseSize;
+
+ byte *_commonBase; //
+ byte *_staticBase; //
+ VoiceLUT *_voiceLUT; //
+ StringsTable *_strings; //
+
+ int _flags; // ThreadFlags
+ int _waitType; // ThreadWaitTypes
+ uint _sleepTime;
+ void *_threadObj; // which object we're handling
+
+ int16 _returnValue;
+
+ uint16 _instructionOffset; // Instruction offset
+
+ int32 _frameWait;
+
+public:
+ byte *baseAddress(byte addrMode) {
+ switch (addrMode) {
+ case kAddressCommon:
+ return _commonBase;
+ case kAddressStatic:
+ return _staticBase;
+ case kAddressModule:
+ return _moduleBase;
+ case kAddressStack:
+ return (byte*)&_stackBuf[_frameIndex];
+ case kAddressThread:
+ return (byte*)_threadVars;
+ default:
+ return _commonBase;
+ }
+ }
+
+ int16 stackTop() {
+ return (int16)_stackBuf[_stackTopIndex];
+ }
+
+ uint pushedSize() {
+ return _stackSize - _stackTopIndex - 2;
+ }
+
+ void push(int16 value) {
+ if (_stackTopIndex <= 0) {
+ error("ScriptThread::push() stack overflow");
+ }
+ _stackBuf[--_stackTopIndex] = (uint16)value;
+ }
+
+ int16 pop() {
+ if (_stackTopIndex >= _stackSize) {
+ error("ScriptThread::pop() stack underflow");
+ }
+ return (int16)_stackBuf[_stackTopIndex++];
+ }
+
+
+// wait stuff
+ void wait(int waitType) {
+ _waitType = waitType;
+ _flags |= kTFlagWaiting;
+ }
+
+ void waitWalk(void *threadObj) {
+ wait(kWaitTypeWalk);
+ _threadObj = threadObj;
+ }
+
+ void waitDelay(int sleepTime) {
+ wait(kWaitTypeDelay);
+ _sleepTime = sleepTime;
+ }
+
+ ScriptThread() {
+ memset(this, 0xFE, sizeof(*this));
+ _stackBuf = NULL;
+ }
+ ~ScriptThread() {
+ free(_stackBuf);
+ }
+};
+
+typedef SortedList<ScriptThread> ScriptThreadList;
+
+
+#define SCRIPTFUNC_PARAMS ScriptThread *thread, int nArgs, bool &disContinue
+
+class Script {
+public:
+ StringsTable _mainStrings;
+
+ Script(SagaEngine *vm);
+ ~Script();
+
+ void loadModule(int scriptModuleNumber);
+ void freeModules();
+
+ void doVerb();
+ void showVerb(int statusColor = -1);
+ void setVerb(int verb);
+ int getCurrentVerb() const { return _currentVerb; }
+ void setPointerVerb();
+ void whichObject(const Point& mousePoint);
+ void hitObject(bool leftButton);
+ void playfieldClick(const Point& mousePoint, bool leftButton);
+
+ void setLeftButtonVerb(int verb);
+ int getLeftButtonVerb() const { return _leftButtonVerb; }
+ void setRightButtonVerb(int verb);
+ int getRightButtonVerb() const { return _rightButtonVerb; }
+ void setNonPlayfieldVerb() {
+ setRightButtonVerb(getVerbType(kVerbNone));
+ _pointerObject = ID_NOTHING;
+ _currentObject[_firstObjectSet ? 1 : 0] = ID_NOTHING;
+ }
+ void setNoPendingVerb() {
+ _pendingVerb = getVerbType(kVerbNone);
+ _currentObject[0] = _currentObject[0] = ID_NOTHING;
+ setPointerVerb();
+ }
+ int getVerbType(VerbTypes verbType);
+
+private:
+ // When reading or writing data to the common buffer, we have to use a
+ // well-defined byte order since it's stored in savegames. Otherwise,
+ // we use native byte ordering since that data may be accessed in other
+ // ways than through these functions.
+ //
+ // The "module" area is a special case, which possibly never ever
+ // happens. But if it does, we need well-defined byte ordering.
+
+ uint16 readUint16(byte *addr, byte addrMode) {
+ switch (addrMode) {
+ case kAddressCommon:
+ case kAddressStatic:
+ case kAddressModule:
+ return READ_LE_UINT16(addr);
+ default:
+ return READ_UINT16(addr);
+ }
+ }
+
+ void writeUint16(byte *addr, uint16 value, byte addrMode) {
+ switch (addrMode) {
+ case kAddressCommon:
+ case kAddressStatic:
+ case kAddressModule:
+ WRITE_LE_UINT16(addr, value);
+ break;
+ default:
+ WRITE_UINT16(addr, value);
+ break;
+ }
+ }
+
+ SagaEngine *_vm;
+ ResourceContext *_scriptContext;
+
+ uint16 _modulesLUTEntryLen;
+ ModuleData *_modules;
+ int _modulesCount;
+ TextListEntry *_placardTextEntry;
+
+protected:
+ friend class SagaEngine;
+ byte *_commonBuffer;
+ uint _commonBufferSize;
+
+private:
+ uint _staticSize;
+
+ ScriptThreadList _threadList;
+
+ ScriptThread *_conversingThread;
+
+//verb
+ bool _firstObjectSet;
+ bool _secondObjectNeeded;
+ uint16 _currentObject[2];
+ int16 _currentObjectFlags[2];
+ int _currentVerb;
+ int _stickyVerb;
+ int _leftButtonVerb;
+ int _rightButtonVerb;
+
+public:
+ uint16 _pendingObject[2];
+ int _pendingVerb;
+ uint16 _pointerObject;
+
+ bool _skipSpeeches;
+ bool _abortEnabled;
+
+ VoiceLUT _globalVoiceLUT;
+
+public:
+ ScriptThread *createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber);
+ int executeThread(ScriptThread *thread, int entrypointNumber);
+ void executeThreads(uint msec);
+ void completeThread(void);
+ void abortAllThreads(void);
+
+ void wakeUpActorThread(int waitType, void *threadObj);
+ void wakeUpThreads(int waitType);
+ void wakeUpThreadsDelayed(int waitType, int sleepTime);
+
+ void loadVoiceLUT(VoiceLUT &voiceLUT, const byte *resourcePointer, size_t resourceLength);
+
+private:
+ void loadModuleBase(ModuleData &module, const byte *resourcePointer, size_t resourceLength);
+
+ // runThread returns true if we should break running of other threads
+ bool runThread(ScriptThread *thread, uint instructionLimit);
+ void setThreadEntrypoint(ScriptThread *thread, int entrypointNumber);
+
+public:
+ void finishDialog(int replyID, int flags, int bitOffset);
+
+private:
+
+ typedef void (Script::*ScriptFunctionType)(SCRIPTFUNC_PARAMS);
+
+ struct ScriptFunctionDescription {
+ ScriptFunctionType scriptFunction;
+ const char *scriptFunctionName;
+ };
+ const ScriptFunctionDescription *_scriptFunctionsList;
+
+ void setupScriptFuncList(void);
+
+ void sfPutString(SCRIPTFUNC_PARAMS);
+ void sfWait(SCRIPTFUNC_PARAMS);
+ void sfTakeObject(SCRIPTFUNC_PARAMS);
+ void sfIsCarried(SCRIPTFUNC_PARAMS);
+ void sfStatusBar(SCRIPTFUNC_PARAMS);
+ void sfMainMode(SCRIPTFUNC_PARAMS);
+ void sfScriptWalkTo(SCRIPTFUNC_PARAMS);
+ void sfScriptDoAction(SCRIPTFUNC_PARAMS);
+ void sfSetActorFacing(SCRIPTFUNC_PARAMS);
+ void sfStartBgdAnim(SCRIPTFUNC_PARAMS);
+ void sfStopBgdAnim(SCRIPTFUNC_PARAMS);
+ void sfLockUser(SCRIPTFUNC_PARAMS);
+ void sfPreDialog(SCRIPTFUNC_PARAMS);
+ void sfKillActorThreads(SCRIPTFUNC_PARAMS);
+ void sfFaceTowards(SCRIPTFUNC_PARAMS);
+ void sfSetFollower(SCRIPTFUNC_PARAMS);
+ void sfScriptGotoScene(SCRIPTFUNC_PARAMS);
+
+ void sfSetObjImage(SCRIPTFUNC_PARAMS);
+ void sfSetObjName(SCRIPTFUNC_PARAMS);
+ void sfGetObjImage(SCRIPTFUNC_PARAMS);
+ void sfGetNumber(SCRIPTFUNC_PARAMS);
+ void sfScriptOpenDoor(SCRIPTFUNC_PARAMS);
+ void sfScriptCloseDoor(SCRIPTFUNC_PARAMS);
+ void sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS);
+ void SF_cycleColors(SCRIPTFUNC_PARAMS);
+ void sfDoCenterActor(SCRIPTFUNC_PARAMS);
+ void sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS);
+ void sfScriptWalkToAsync(SCRIPTFUNC_PARAMS);
+ void sfEnableZone(SCRIPTFUNC_PARAMS);
+ void sfSetActorState(SCRIPTFUNC_PARAMS);
+ void sfScriptMoveTo(SCRIPTFUNC_PARAMS);
+ void sfSceneEq(SCRIPTFUNC_PARAMS);
+ void sfDropObject(SCRIPTFUNC_PARAMS);
+ void sfFinishBgdAnim(SCRIPTFUNC_PARAMS);
+ void sfSwapActors(SCRIPTFUNC_PARAMS);
+ void sfSimulSpeech(SCRIPTFUNC_PARAMS);
+ void sfScriptWalk(SCRIPTFUNC_PARAMS);
+ void sfCycleFrames(SCRIPTFUNC_PARAMS);
+ void sfSetFrame(SCRIPTFUNC_PARAMS);
+ void sfSetPortrait(SCRIPTFUNC_PARAMS);
+ void sfSetProtagPortrait(SCRIPTFUNC_PARAMS);
+ void sfChainBgdAnim(SCRIPTFUNC_PARAMS);
+ void sfScriptSpecialWalk(SCRIPTFUNC_PARAMS);
+ void sfPlaceActor(SCRIPTFUNC_PARAMS);
+ void sfCheckUserInterrupt(SCRIPTFUNC_PARAMS);
+ void sfScriptWalkRelative(SCRIPTFUNC_PARAMS);
+ void sfScriptMoveRelative(SCRIPTFUNC_PARAMS);
+ void sfSimulSpeech2(SCRIPTFUNC_PARAMS);
+ void sfPlacard(SCRIPTFUNC_PARAMS);
+ void sfPlacardOff(SCRIPTFUNC_PARAMS);
+ void sfSetProtagState(SCRIPTFUNC_PARAMS);
+ void sfResumeBgdAnim(SCRIPTFUNC_PARAMS);
+ void sfThrowActor(SCRIPTFUNC_PARAMS);
+ void sfWaitWalk(SCRIPTFUNC_PARAMS);
+ void sfScriptSceneID(SCRIPTFUNC_PARAMS);
+ void sfChangeActorScene(SCRIPTFUNC_PARAMS);
+ void sfScriptClimb(SCRIPTFUNC_PARAMS);
+ void sfSetDoorState(SCRIPTFUNC_PARAMS);
+ void sfSetActorZ(SCRIPTFUNC_PARAMS);
+ void sfScriptText(SCRIPTFUNC_PARAMS);
+ void sfGetActorX(SCRIPTFUNC_PARAMS);
+ void sfGetActorY(SCRIPTFUNC_PARAMS);
+ void sfEraseDelta(SCRIPTFUNC_PARAMS);
+ void sfPlayMusic(SCRIPTFUNC_PARAMS);
+ void sfPickClimbOutPos(SCRIPTFUNC_PARAMS);
+ void sfTossRif(SCRIPTFUNC_PARAMS);
+ void sfShowControls(SCRIPTFUNC_PARAMS);
+ void sfShowMap(SCRIPTFUNC_PARAMS);
+ void sfPuzzleWon(SCRIPTFUNC_PARAMS);
+ void sfEnableEscape(SCRIPTFUNC_PARAMS);
+ void sfPlaySound(SCRIPTFUNC_PARAMS);
+ void sfPlayLoopedSound(SCRIPTFUNC_PARAMS);
+ void sfGetDeltaFrame(SCRIPTFUNC_PARAMS);
+ void sfShowProtect(SCRIPTFUNC_PARAMS);
+ void sfProtectResult(SCRIPTFUNC_PARAMS);
+ void sfRand(SCRIPTFUNC_PARAMS);
+ void sfFadeMusic(SCRIPTFUNC_PARAMS);
+ void sfScriptStartCutAway(SCRIPTFUNC_PARAMS);
+ void sfReturnFromCutAway(SCRIPTFUNC_PARAMS);
+ void sfEndCutAway(SCRIPTFUNC_PARAMS);
+ void sfGetMouseClicks(SCRIPTFUNC_PARAMS);
+ void sfResetMouseClicks(SCRIPTFUNC_PARAMS);
+ void sfWaitFrames(SCRIPTFUNC_PARAMS);
+ void sfScriptFade(SCRIPTFUNC_PARAMS);
+ void sfPlayVoice(SCRIPTFUNC_PARAMS);
+ void sfVstopFX(SCRIPTFUNC_PARAMS);
+ void sfVstopLoopedFX(SCRIPTFUNC_PARAMS);
+ void sfDemoIsInteractive(SCRIPTFUNC_PARAMS);
+ void sfVsetTrack(SCRIPTFUNC_PARAMS);
+ void sfDebugShowData(SCRIPTFUNC_PARAMS);
+ void sfNull(SCRIPTFUNC_PARAMS);
+ void sfWaitFramesEsc(SCRIPTFUNC_PARAMS);
+ void sfPsychicProfile(SCRIPTFUNC_PARAMS);
+ void sfPsychicProfileOff(SCRIPTFUNC_PARAMS);
+ void sfSetSpeechBox(SCRIPTFUNC_PARAMS);
+ void sfSetChapterPoints(SCRIPTFUNC_PARAMS);
+ void sfSetPortraitBgColor(SCRIPTFUNC_PARAMS);
+ void sfScriptStartVideo(SCRIPTFUNC_PARAMS);
+ void sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS);
+ void sfScriptEndVideo(SCRIPTFUNC_PARAMS);
+ void sf87(SCRIPTFUNC_PARAMS);
+ void sf88(SCRIPTFUNC_PARAMS);
+ void sf89(SCRIPTFUNC_PARAMS);
+ void sfGetPoints(SCRIPTFUNC_PARAMS);
+ void sfSetGlobalFlag(SCRIPTFUNC_PARAMS);
+ void sfClearGlobalFlag(SCRIPTFUNC_PARAMS);
+ void sfTestGlobalFlag(SCRIPTFUNC_PARAMS);
+ void sfSetPoints(SCRIPTFUNC_PARAMS);
+ void sf103(SCRIPTFUNC_PARAMS);
+ void sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS);
+
+ void SF_stub(const char *name, ScriptThread *thread, int nArgs);
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/sfuncs.cpp b/engines/saga/sfuncs.cpp
new file mode 100644
index 0000000000..9caa15150b
--- /dev/null
+++ b/engines/saga/sfuncs.cpp
@@ -0,0 +1,2038 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Scripting module script function component
+
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/actor.h"
+#include "saga/animation.h"
+#include "saga/console.h"
+#include "saga/events.h"
+#include "saga/font.h"
+#include "saga/interface.h"
+#include "saga/music.h"
+#include "saga/itedata.h"
+#include "saga/puzzle.h"
+#include "saga/render.h"
+#include "saga/sound.h"
+#include "saga/sndres.h"
+
+#include "saga/script.h"
+#include "saga/objectmap.h"
+
+#include "saga/scene.h"
+#include "saga/isomap.h"
+#include "saga/resnames.h"
+
+#include "common/config-manager.h"
+
+namespace Saga {
+
+#define OPCODE(x) {&Script::x, #x}
+
+void Script::setupScriptFuncList(void) {
+ static const ScriptFunctionDescription ITEscriptFunctionsList[ITE_SCRIPT_FUNCTION_MAX] = {
+ OPCODE(sfPutString),
+ OPCODE(sfWait),
+ OPCODE(sfTakeObject),
+ OPCODE(sfIsCarried),
+ OPCODE(sfStatusBar),
+ OPCODE(sfMainMode),
+ OPCODE(sfScriptWalkTo),
+ OPCODE(sfScriptDoAction),
+ OPCODE(sfSetActorFacing),
+ OPCODE(sfStartBgdAnim),
+ OPCODE(sfStopBgdAnim),
+ OPCODE(sfLockUser),
+ OPCODE(sfPreDialog),
+ OPCODE(sfKillActorThreads),
+ OPCODE(sfFaceTowards),
+ OPCODE(sfSetFollower),
+ OPCODE(sfScriptGotoScene),
+ OPCODE(sfSetObjImage),
+ OPCODE(sfSetObjName),
+ OPCODE(sfGetObjImage),
+ OPCODE(sfGetNumber),
+ OPCODE(sfScriptOpenDoor),
+ OPCODE(sfScriptCloseDoor),
+ OPCODE(sfSetBgdAnimSpeed),
+ OPCODE(SF_cycleColors),
+ OPCODE(sfDoCenterActor),
+ OPCODE(sfStartBgdAnimSpeed),
+ OPCODE(sfScriptWalkToAsync),
+ OPCODE(sfEnableZone),
+ OPCODE(sfSetActorState),
+ OPCODE(sfScriptMoveTo),
+ OPCODE(sfSceneEq),
+ OPCODE(sfDropObject),
+ OPCODE(sfFinishBgdAnim),
+ OPCODE(sfSwapActors),
+ OPCODE(sfSimulSpeech),
+ OPCODE(sfScriptWalk),
+ OPCODE(sfCycleFrames),
+ OPCODE(sfSetFrame),
+ OPCODE(sfSetPortrait),
+ OPCODE(sfSetProtagPortrait),
+ OPCODE(sfChainBgdAnim),
+ OPCODE(sfScriptSpecialWalk),
+ OPCODE(sfPlaceActor),
+ OPCODE(sfCheckUserInterrupt),
+ OPCODE(sfScriptWalkRelative),
+ OPCODE(sfScriptMoveRelative),
+ OPCODE(sfSimulSpeech2),
+ OPCODE(sfPlacard),
+ OPCODE(sfPlacardOff),
+ OPCODE(sfSetProtagState),
+ OPCODE(sfResumeBgdAnim),
+ OPCODE(sfThrowActor),
+ OPCODE(sfWaitWalk),
+ OPCODE(sfScriptSceneID),
+ OPCODE(sfChangeActorScene),
+ OPCODE(sfScriptClimb),
+ OPCODE(sfSetDoorState),
+ OPCODE(sfSetActorZ),
+ OPCODE(sfScriptText),
+ OPCODE(sfGetActorX),
+ OPCODE(sfGetActorY),
+ OPCODE(sfEraseDelta),
+ OPCODE(sfPlayMusic),
+ OPCODE(sfPickClimbOutPos),
+ OPCODE(sfTossRif),
+ OPCODE(sfShowControls),
+ OPCODE(sfShowMap),
+ OPCODE(sfPuzzleWon),
+ OPCODE(sfEnableEscape),
+ OPCODE(sfPlaySound),
+ OPCODE(sfPlayLoopedSound),
+ OPCODE(sfGetDeltaFrame),
+ OPCODE(sfShowProtect),
+ OPCODE(sfProtectResult),
+ OPCODE(sfRand),
+ OPCODE(sfFadeMusic),
+ OPCODE(sfPlayVoice)
+ };
+
+static const ScriptFunctionDescription IHNMscriptFunctionsList[IHNM_SCRIPT_FUNCTION_MAX] = {
+ OPCODE(sfNull),
+ OPCODE(sfWait),
+ OPCODE(sfTakeObject),
+ OPCODE(sfIsCarried),
+ OPCODE(sfStatusBar),
+ OPCODE(sfMainMode),
+ OPCODE(sfScriptWalkTo),
+ OPCODE(sfScriptDoAction),
+ OPCODE(sfSetActorFacing),
+ OPCODE(sfStartBgdAnim),
+ OPCODE(sfStopBgdAnim),
+ OPCODE(sfNull),
+ OPCODE(sfPreDialog),
+ OPCODE(sfKillActorThreads),
+ OPCODE(sfFaceTowards),
+ OPCODE(sfSetFollower),
+ OPCODE(sfScriptGotoScene),
+ OPCODE(sfSetObjImage),
+ OPCODE(sfSetObjName),
+ OPCODE(sfGetObjImage),
+ OPCODE(sfGetNumber),
+ OPCODE(sfScriptOpenDoor),
+ OPCODE(sfScriptCloseDoor),
+ OPCODE(sfSetBgdAnimSpeed),
+ OPCODE(SF_cycleColors),
+ OPCODE(sfDoCenterActor),
+ OPCODE(sfStartBgdAnimSpeed),
+ OPCODE(sfScriptWalkToAsync),
+ OPCODE(sfEnableZone),
+ OPCODE(sfSetActorState),
+ OPCODE(sfScriptMoveTo),
+ OPCODE(sfSceneEq),
+ OPCODE(sfDropObject),
+ OPCODE(sfFinishBgdAnim),
+ OPCODE(sfSwapActors),
+ OPCODE(sfSimulSpeech),
+ OPCODE(sfScriptWalk),
+ OPCODE(sfCycleFrames),
+ OPCODE(sfSetFrame),
+ OPCODE(sfSetPortrait),
+ OPCODE(sfSetProtagPortrait),
+ OPCODE(sfChainBgdAnim),
+ OPCODE(sfScriptSpecialWalk),
+ OPCODE(sfPlaceActor),
+ OPCODE(sfCheckUserInterrupt),
+ OPCODE(sfScriptWalkRelative),
+ OPCODE(sfScriptMoveRelative),
+ OPCODE(sfSimulSpeech2),
+ OPCODE(sfPsychicProfile),
+ OPCODE(sfPsychicProfileOff),
+ OPCODE(sfSetProtagState),
+ OPCODE(sfResumeBgdAnim),
+ OPCODE(sfThrowActor),
+ OPCODE(sfWaitWalk),
+ OPCODE(sfScriptSceneID),
+ OPCODE(sfChangeActorScene),
+ OPCODE(sfScriptClimb),
+ OPCODE(sfSetDoorState),
+ OPCODE(sfSetActorZ),
+ OPCODE(sfScriptText),
+ OPCODE(sfGetActorX),
+ OPCODE(sfGetActorY),
+ OPCODE(sfEraseDelta),
+ OPCODE(sfPlayMusic),
+ OPCODE(sfNull),
+ OPCODE(sfEnableEscape),
+ OPCODE(sfPlaySound),
+ OPCODE(sfPlayLoopedSound),
+ OPCODE(sfGetDeltaFrame),
+ OPCODE(sfNull),
+ OPCODE(sfNull),
+ OPCODE(sfRand),
+ OPCODE(sfFadeMusic),
+ OPCODE(sfNull),
+ OPCODE(sfSetChapterPoints),
+ OPCODE(sfSetPortraitBgColor),
+ OPCODE(sfScriptStartCutAway),
+ OPCODE(sfReturnFromCutAway),
+ OPCODE(sfEndCutAway),
+ OPCODE(sfGetMouseClicks),
+ OPCODE(sfResetMouseClicks),
+ OPCODE(sfWaitFrames),
+ OPCODE(sfScriptFade),
+ OPCODE(sfScriptStartVideo),
+ OPCODE(sfScriptReturnFromVideo),
+ OPCODE(sfScriptEndVideo),
+ OPCODE(sfSetActorZ),
+ OPCODE(sf87),
+ OPCODE(sf88),
+ OPCODE(sf89),
+ OPCODE(sfVstopFX),
+ OPCODE(sfVstopLoopedFX),
+ OPCODE(sfNull),
+ OPCODE(sfDemoIsInteractive),
+ OPCODE(sfVsetTrack),
+ OPCODE(sfGetPoints),
+ OPCODE(sfSetGlobalFlag),
+ OPCODE(sfClearGlobalFlag),
+ OPCODE(sfTestGlobalFlag),
+ OPCODE(sfSetPoints),
+ OPCODE(sfSetSpeechBox),
+ OPCODE(sfDebugShowData),
+ OPCODE(sfWaitFramesEsc),
+ OPCODE(sf103),
+ OPCODE(sfDisableAbortSpeeches)
+ };
+ if (_vm->getGameType() == GType_IHNM)
+ _scriptFunctionsList = IHNMscriptFunctionsList;
+ else
+ _scriptFunctionsList = ITEscriptFunctionsList;
+}
+
+// Script function #0 (0x00)
+// Print a debugging message
+void Script::sfPutString(SCRIPTFUNC_PARAMS) {
+ const char *str;
+ str = thread->_strings->getString(thread->pop());
+
+ _vm->_console->DebugPrintf("sfPutString: %s\n",str);
+ debug(0, "sfPutString: %s", str);
+}
+
+// Script function #1 (0x01) blocking
+// Param1: time in ticks
+void Script::sfWait(SCRIPTFUNC_PARAMS) {
+ int16 time;
+ time = thread->pop();
+
+ if (!_skipSpeeches) {
+ thread->waitDelay(ticksToMSec(time)); // put thread to sleep
+ }
+}
+
+// Script function #2 (0x02)
+void Script::sfTakeObject(SCRIPTFUNC_PARAMS) {
+ uint16 objectId = thread->pop();
+ ObjectData *obj;
+ obj = _vm->_actor->getObj(objectId);
+ if (obj->_sceneNumber != ITE_SCENE_INV) {
+ obj->_sceneNumber = ITE_SCENE_INV;
+ //4debug for (int j=0;j<17;j++)
+ _vm->_interface->addToInventory(objectId);
+ }
+}
+
+// Script function #3 (0x03)
+// Check if an object is carried.
+void Script::sfIsCarried(SCRIPTFUNC_PARAMS) {
+ uint16 objectId = thread->pop();
+ CommonObjectData *object;
+ if (_vm->_actor->validObjId(objectId)) {
+ object = _vm->_actor->getObj(objectId);
+ thread->_returnValue = (object->_sceneNumber == ITE_SCENE_INV) ? 1 : 0;
+ } else {
+ thread->_returnValue = 0;
+ }
+
+
+}
+
+// Script function #4 (0x04) nonblocking
+// Set the command display to the specified text string
+// Param1: dialogue index of string
+void Script::sfStatusBar(SCRIPTFUNC_PARAMS) {
+ int16 stringIndex = thread->pop();
+
+ _vm->_interface->setStatusText(thread->_strings->getString(stringIndex));
+}
+
+// Script function #5 (0x05)
+void Script::sfMainMode(SCRIPTFUNC_PARAMS) {
+ _vm->_actor->_centerActor = _vm->_actor->_protagonist;
+
+ showVerb();
+ _vm->_interface->activate();
+ _vm->_interface->setMode(kPanelMain);
+
+ if (_vm->getGameType() == GType_ITE)
+ setPointerVerb();
+}
+
+// Script function #6 (0x06) blocking
+// Param1: actor id
+// Param2: actor x
+// Param3: actor y
+void Script::sfScriptWalkTo(SCRIPTFUNC_PARAMS) {
+ uint16 actorId;
+ Location actorLocation;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ actorLocation.x = thread->pop();
+ actorLocation.y = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actorLocation.z = actor->_location.z;
+
+ actor->_flags &= ~kFollower;
+
+ if (_vm->_actor->actorWalkTo(actorId, actorLocation)) {
+ thread->waitWalk(actor);
+ }
+}
+
+// Script function #7 (0x07)
+// Param1: actor id
+// Param2: action
+// Param3: theObject
+// Param4: withObject
+void Script::sfScriptDoAction(SCRIPTFUNC_PARAMS) {
+ uint16 objectId;
+ uint16 action;
+ uint16 theObject;
+ uint16 withObject;
+ int16 scriptEntryPointNumber;
+ int16 moduleNumber;
+ ActorData *actor;
+ ObjectData *obj;
+ const HitZone *hitZone;
+ Event event;
+
+ objectId = thread->pop();
+ action = thread->pop();
+ theObject = thread->pop();
+ withObject = thread->pop();
+
+ switch (objectTypeId(objectId)) {
+ case kGameObjectObject:
+ obj = _vm->_actor->getObj(objectId);
+ scriptEntryPointNumber = obj->_scriptEntrypointNumber;
+ if (scriptEntryPointNumber <= 0) {
+ return;
+ }
+ moduleNumber = 0;
+ break;
+ case kGameObjectActor:
+ actor = _vm->_actor->getActor(objectId);
+ scriptEntryPointNumber = actor->_scriptEntrypointNumber;
+ if (scriptEntryPointNumber <= 0) {
+ return;
+ }
+ if (actor->_flags & (kProtagonist | kFollower)) {
+ moduleNumber = 0;
+ } else {
+ moduleNumber = _vm->_scene->getScriptModuleNumber();
+ }
+ break;
+ case kGameObjectHitZone:
+ case kGameObjectStepZone:
+ if (objectTypeId(objectId) == kGameObjectHitZone) {
+ hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId));
+ } else {
+ hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId));
+ }
+ scriptEntryPointNumber = hitZone->getScriptNumber();
+ moduleNumber = _vm->_scene->getScriptModuleNumber();
+ break;
+ default:
+ error("Script::sfScriptDoAction wrong object type 0x%X", objectId);
+ }
+
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventExecNonBlocking;
+ event.time = 0;
+ event.param = moduleNumber;
+ event.param2 = scriptEntryPointNumber;
+ event.param3 = action; // Action
+ event.param4 = theObject; // Object
+ event.param5 = withObject; // With Object
+ event.param6 = objectId;
+
+ _vm->_events->queue(&event);
+}
+
+// Script function #8 (0x08) nonblocking
+// Param1: actor id
+// Param2: actor orientation
+void Script::sfSetActorFacing(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int actorDirection;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ actorDirection = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actor->_facingDirection = actor->_actionDirection = actorDirection;
+ actor->_targetObject = ID_NOTHING;
+}
+
+// Script function #9 (0x09)
+void Script::sfStartBgdAnim(SCRIPTFUNC_PARAMS) {
+ int16 animId = thread->pop();
+ int16 cycles = thread->pop();
+
+ _vm->_anim->setCycles(animId, cycles);
+ _vm->_anim->setFrameTime(animId, ticksToMSec(kRepeatSpeedTicks));
+ _vm->_anim->play(animId, 0);
+
+ debug(1, "sfStartBgdAnim(%d, %d)", animId, cycles);
+}
+
+// Script function #10 (0x0A)
+void Script::sfStopBgdAnim(SCRIPTFUNC_PARAMS) {
+ int16 animId = thread->pop();
+
+ _vm->_anim->stop(animId);
+
+ debug(1, "sfStopBgdAnim(%d)", animId);
+}
+
+// Script function #11 (0x0B) nonblocking
+// If the parameter is true, the user interface is disabled while script
+// continues to run. If the parameter is false, the user interface is
+// reenabled.
+// Param1: boolean
+void Script::sfLockUser(SCRIPTFUNC_PARAMS) {
+ int16 lock;
+
+ lock = thread->pop();
+
+ if (lock) {
+ _vm->_interface->deactivate();
+ } else {
+ _vm->_interface->activate();
+ }
+
+}
+
+// Script function #12 (0x0C)
+// Disables mouse input, etc.
+void Script::sfPreDialog(SCRIPTFUNC_PARAMS) {
+ _vm->_interface->deactivate();
+ _vm->_interface->converseClear();
+ if (_vm->_interface->isInMainMode())
+ _vm->_interface->setMode(kPanelConverse);
+ else
+ _vm->_interface->converseDisplayText();
+
+ _vm->_interface->setMode(kPanelNull);
+}
+
+// Script function #13 (0x0D)
+void Script::sfKillActorThreads(SCRIPTFUNC_PARAMS) {
+ ScriptThread *anotherThread;
+ ScriptThreadList::iterator threadIterator;
+ int16 actorId;
+
+ actorId = thread->pop();
+
+
+ for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
+ anotherThread = threadIterator.operator->();
+ if ((anotherThread != thread) && (anotherThread->_threadVars[kThreadVarActor] == actorId)) {
+ anotherThread->_flags &= ~kTFlagWaiting;
+ anotherThread->_flags |= kTFlagAborted;
+ }
+ }
+}
+
+// Script function #14 (0x0E)
+// Param1: actor id
+// Param2: object id
+void Script::sfFaceTowards(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int16 targetObject;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ targetObject = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actor->_targetObject = targetObject;
+}
+
+// Script function #15 (0x0F)
+// Param1: actor id
+// Param2: target object
+void Script::sfSetFollower(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int16 targetObject;
+
+ ActorData *actor;
+
+ actorId = thread->pop();
+ targetObject = thread->pop();
+
+ debug(1, "sfSetFollower(%d, %d) [%d]", actorId, targetObject, _vm->_actor->actorIdToIndex(actorId));
+
+ actor = _vm->_actor->getActor(actorId);
+ actor->_targetObject = targetObject;
+ if (targetObject != ID_NOTHING) {
+ actor->_flags |= kFollower;
+ actor->_actorFlags &= ~kActorNoFollow;
+ } else {
+ actor->_flags &= ~kFollower;
+ }
+}
+
+// Script function #16 (0x10)
+void Script::sfScriptGotoScene(SCRIPTFUNC_PARAMS) {
+ int16 sceneNumber;
+ int16 entrance;
+
+ sceneNumber = thread->pop();
+ entrance = thread->pop();
+ if (sceneNumber < 0) {
+ _vm->shutDown();
+ return;
+ }
+
+ if (_vm->getGameType() == GType_IHNM) {
+ warning("FIXME: implement sfScriptGotoScene differences for IHNM");
+
+ // Since it doesn't look like the IHNM scripts remove the
+ // cutaway after the intro, this is probably the best place to
+ // to it.
+ _vm->_anim->clearCutaway();
+ }
+
+ // It is possible to leave scene when converse panel is on,
+ // particulalrly it may happen at Moneychanger tent. This
+ // prevent this from happening.
+ if (_vm->_interface->getMode() == kPanelConverse) {
+ _vm->_interface->setMode(kPanelMain);
+ }
+
+ _vm->_scene->changeScene(sceneNumber, entrance, (sceneNumber == ITE_SCENE_ENDCREDIT1) ? kTransitionFade : kTransitionNoFade);
+
+ //TODO: placard stuff
+ _pendingVerb = _vm->_script->getVerbType(kVerbNone);
+ _currentObject[0] = _currentObject[1] = ID_NOTHING;
+ showVerb();
+}
+
+// Script function #17 (0x11)
+// Param1: object id
+// Param2: sprite index
+void Script::sfSetObjImage(SCRIPTFUNC_PARAMS) {
+ uint16 objectId;
+ uint16 spriteId;
+ ObjectData *obj;
+
+ objectId = thread->pop();
+ spriteId = thread->pop();
+
+ obj = _vm->_actor->getObj(objectId);
+ obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId;
+ _vm->_interface->refreshInventory();
+}
+
+// Script function #18 (0x12)
+// Param1: object id
+// Param2: name index
+void Script::sfSetObjName(SCRIPTFUNC_PARAMS) {
+ uint16 objectId;
+ uint16 nameIdx;
+ ObjectData *obj;
+
+ objectId = thread->pop();
+ nameIdx = thread->pop();
+
+ obj = _vm->_actor->getObj(objectId);
+ obj->_nameIndex = nameIdx;
+}
+
+// Script function #19 (0x13)
+// Param1: object id
+void Script::sfGetObjImage(SCRIPTFUNC_PARAMS) {
+ uint16 objectId;
+ ObjectData *obj;
+
+ objectId = thread->pop();
+
+ obj = _vm->_actor->getObj(objectId);
+
+ if (_vm->getGameType() == GType_IHNM)
+ thread->_returnValue = obj->_spriteListResourceId;
+ else
+ thread->_returnValue = obj->_spriteListResourceId - OBJ_SPRITE_BASE;
+}
+
+// Script function #20 (0x14)
+void Script::sfGetNumber(SCRIPTFUNC_PARAMS) {
+ if (_vm->_interface->_statusTextInputState == kStatusTextInputFirstRun) {
+ _vm->_interface->enterStatusString();
+ thread->wait(kWaitTypeStatusTextInput);
+ disContinue = true;
+ } else {
+ if (_vm->_interface->_statusTextInputState == kStatusTextInputAborted) {
+ thread->_returnValue = -1;
+ } else {
+ thread->_returnValue = atoi(_vm->_interface->_statusTextInputString);
+ }
+
+ _vm->_interface->_statusTextInputState = kStatusTextInputFirstRun;
+ }
+}
+
+// Script function #21 (0x15)
+// Param1: door #
+void Script::sfScriptOpenDoor(SCRIPTFUNC_PARAMS) {
+ int16 doorNumber;
+ doorNumber = thread->pop();
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->setTileDoorState(doorNumber, 1);
+ } else {
+ _vm->_scene->setDoorState(doorNumber, 0);
+ }
+}
+
+// Script function #22 (0x16)
+// Param1: door #
+void Script::sfScriptCloseDoor(SCRIPTFUNC_PARAMS) {
+ int16 doorNumber;
+ doorNumber = thread->pop();
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->setTileDoorState(doorNumber, 0);
+ } else {
+ _vm->_scene->setDoorState(doorNumber, 0xff);
+ }
+}
+
+// Script function #23 (0x17)
+void Script::sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS) {
+ int16 animId = thread->pop();
+ int16 speed = thread->pop();
+
+ _vm->_anim->setFrameTime(animId, ticksToMSec(speed));
+ debug(1, "sfSetBgdAnimSpeed(%d, %d)", animId, speed);
+}
+
+// Script function #24 (0x18)
+void Script::SF_cycleColors(SCRIPTFUNC_PARAMS) {
+ SF_stub("SF_cycleColors", thread, nArgs);
+
+ error("Please, report this to sev");
+}
+
+// Script function #25 (0x19)
+// Param1: actor id
+void Script::sfDoCenterActor(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ actorId = thread->pop();
+
+ _vm->_actor->_centerActor = _vm->_actor->getActor(actorId);
+}
+
+// Script function #26 (0x1A) nonblocking
+// Starts the specified animation
+void Script::sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS) {
+ int16 animId = thread->pop();
+ int16 cycles = thread->pop();
+ int16 speed = thread->pop();
+
+ _vm->_anim->setCycles(animId, cycles);
+ _vm->_anim->setFrameTime(animId, ticksToMSec(speed));
+ _vm->_anim->play(animId, 0);
+
+ debug(1, "sfStartBgdAnimSpeed(%d, %d, %d)", animId, cycles, speed);
+}
+
+// Script function #27 (0x1B) nonblocking
+// Param1: actor id
+// Param2: actor x
+// Param3: actor y
+void Script::sfScriptWalkToAsync(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ Location actorLocation;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ actorLocation.x = thread->pop();
+ actorLocation.y = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actorLocation.z = actor->_location.z;
+
+ actor->_flags &= ~kFollower;
+
+ _vm->_actor->actorWalkTo(actorId, actorLocation);
+}
+
+// Script function #28 (0x1C)
+void Script::sfEnableZone(SCRIPTFUNC_PARAMS) {
+ uint16 objectId = thread->pop();
+ int16 flag = thread->pop();
+ HitZone *hitZone;
+
+ if (objectTypeId(objectId) == kGameObjectHitZone) {
+ hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId));
+ } else {
+ hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId));
+ }
+
+ if (flag) {
+ hitZone->setFlag(kHitZoneEnabled);
+ } else {
+ hitZone->clearFlag(kHitZoneEnabled);
+ _vm->_actor->_protagonist->_lastZone = NULL;
+ }
+}
+
+// Script function #29 (0x1D)
+// Param1: actor id
+// Param2: current action
+void Script::sfSetActorState(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int currentAction;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ currentAction = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+
+ if ((currentAction >= kActionWalkToPoint) && (currentAction <= kActionWalkToPoint)) {
+ wakeUpActorThread(kWaitTypeWalk, actor);
+ }
+ actor->_currentAction = currentAction;
+ actor->_actorFlags &= ~kActorBackwards;
+}
+
+// Script function #30 (0x1E) nonblocking
+// Param1: actor id
+// Param2: actor pos x
+// Param3: actor pos y
+void Script::sfScriptMoveTo(SCRIPTFUNC_PARAMS) {
+ int16 objectId;
+ Location location;
+ ActorData *actor;
+ ObjectData *obj;
+
+ objectId = thread->pop();
+ location.x = thread->pop();
+ location.y = thread->pop();
+
+ if (_vm->_actor->validActorId(objectId)) {
+ actor = _vm->_actor->getActor(objectId);
+
+ actor->_location.x = location.x;
+ actor->_location.y = location.y;
+ } else {
+ if (_vm->_actor->validObjId(objectId)) {
+ obj = _vm->_actor->getObj(objectId);
+ obj->_location.x = location.x;
+ obj->_location.y = location.y;
+ }
+ }
+}
+
+// Script function #31 (0x21)
+// Param1: sceneNumber
+void Script::sfSceneEq(SCRIPTFUNC_PARAMS) {
+ int16 sceneNumber = thread->pop();
+
+ if (_vm->_scene->getSceneResourceId(sceneNumber) == _vm->_scene->currentSceneResourceId())
+ thread->_returnValue = 1;
+ else
+ thread->_returnValue = 0;
+}
+
+// Script function #32 (0x20)
+void Script::sfDropObject(SCRIPTFUNC_PARAMS) {
+ uint16 objectId;
+ uint16 spriteId;
+ int16 x;
+ int16 y;
+ ObjectData *obj;
+
+ objectId = thread->pop();
+ spriteId = thread->pop();
+ x = thread->pop();
+ y = thread->pop();
+
+ obj = _vm->_actor->getObj(objectId);
+
+ if (obj->_sceneNumber == ITE_SCENE_INV) {
+ _vm->_interface->removeFromInventory(objectId);
+ }
+
+ obj->_sceneNumber = _vm->_scene->currentSceneNumber();
+
+ if (_vm->getGameType() == GType_IHNM)
+ obj->_spriteListResourceId = spriteId;
+ else
+ obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId;
+
+ obj->_location.x = x;
+ obj->_location.y = y;
+}
+
+// Script function #33 (0x21)
+void Script::sfFinishBgdAnim(SCRIPTFUNC_PARAMS) {
+ int16 animId = thread->pop();
+
+ _vm->_anim->finish(animId);
+
+ debug(1, "sfFinishBgdAnim(%d)", animId);
+}
+
+// Script function #34 (0x22)
+// Param1: actor id 1
+// Param2: actor id 2
+void Script::sfSwapActors(SCRIPTFUNC_PARAMS) {
+ int16 actorId1;
+ int16 actorId2;
+ ActorData *actor1;
+ ActorData *actor2;
+
+ actorId1 = thread->pop();
+ actorId2 = thread->pop();
+
+ actor1 = _vm->_actor->getActor(actorId1);
+ actor2 = _vm->_actor->getActor(actorId2);
+
+ SWAP(actor1->_location, actor2->_location);
+
+ if (actor1->_flags & kProtagonist) {
+ actor1->_flags &= ~kProtagonist;
+ actor2->_flags |= kProtagonist;
+ _vm->_actor->_protagonist = _vm->_actor->_centerActor = actor2;
+ } else if (actor2->_flags & kProtagonist) {
+ actor2->_flags &= ~kProtagonist;
+ actor1->_flags |= kProtagonist;
+ _vm->_actor->_protagonist = _vm->_actor->_centerActor = actor1;
+ }
+
+ // Here non-protagonist ID gets saved in variable
+ if (_vm->getGameType() == GType_IHNM)
+ warning("sfSwapActors: incomplete implementation");
+}
+
+// Script function #35 (0x23)
+// Param1: string rid
+// Param2: actorscount
+// Param3: actor id1
+///....
+// Param3: actor idN
+void Script::sfSimulSpeech(SCRIPTFUNC_PARAMS) {
+ int16 stringId;
+ int16 actorsCount;
+ int i;
+ uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX];
+ const char *string;
+ int16 sampleResourceId = -1;
+
+ stringId = thread->pop();
+ actorsCount = thread->pop();
+
+ if (actorsCount > ACTOR_SPEECH_ACTORS_MAX)
+ error("sfSimulSpeech actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount);
+
+ for (i = 0; i < actorsCount; i++)
+ actorsIds[i] = thread->pop();
+
+ string = thread->_strings->getString(stringId);
+
+ if (thread->_voiceLUT->voices) {
+ if (_vm->getGameType() == GType_IHNM && stringId >= 338) {
+ sampleResourceId = -1;
+ } else {
+ sampleResourceId = thread->_voiceLUT->voices[stringId];
+ if (sampleResourceId <= 0 || sampleResourceId > 4000)
+ sampleResourceId = -1;
+ }
+ }
+
+ _vm->_actor->simulSpeech(string, actorsIds, actorsCount, 0, sampleResourceId);
+ thread->wait(kWaitTypeSpeech);
+}
+
+// Script function #36 (0x24) ?
+// Param1: actor id
+// Param2: actor x
+// Param3: actor y
+// Param4: actor walk flag
+void Script::sfScriptWalk(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ Location actorLocation;
+ ActorData *actor;
+ uint16 walkFlags;
+
+ actorId = thread->pop();
+ actorLocation.x = thread->pop();
+ actorLocation.y = thread->pop();
+ walkFlags = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actorLocation.z = actor->_location.z;
+
+ _vm->_actor->realLocation(actorLocation, ID_NOTHING, walkFlags);
+
+ actor->_flags &= ~kFollower;
+
+ if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) {
+ thread->waitWalk(actor);
+ }
+
+ if (walkFlags & kWalkBackPedal) {
+ actor->_actorFlags |= kActorBackwards;
+ }
+
+ actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
+}
+
+// Script function #37 (0x25) nonblocking
+// Param1: actor id
+// Param2: flags telling how to cycle the frames
+// Param3: cycle frame number
+// Param4: cycle delay
+void Script::sfCycleFrames(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int16 flags;
+ int cycleFrameSequence;
+ int cycleDelay;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ flags = thread->pop();
+ cycleFrameSequence = thread->pop();
+ cycleDelay = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+
+ if (flags & kCyclePong) {
+ actor->_currentAction = kActionPongFrames;
+ } else {
+ actor->_currentAction = kActionCycleFrames;
+ }
+
+ actor->_actorFlags &= ~(kActorContinuous | kActorRandom | kActorBackwards);
+
+ if (!(flags & kCycleOnce)) {
+ actor->_actorFlags |= kActorContinuous;
+ }
+ if (flags & kCycleRandom) {
+ actor->_actorFlags |= kActorRandom;
+ }
+ if (flags & kCycleReverse) {
+ actor->_actorFlags |= kActorBackwards;
+ }
+
+ actor->_cycleFrameSequence = cycleFrameSequence;
+ actor->_cycleTimeCount = 0;
+ actor->_cycleDelay = cycleDelay;
+ actor->_actionCycle = 0;
+}
+
+// Script function #38 (0x26) nonblocking
+// Param1: actor id
+// Param2: frame type
+// Param3: frame offset
+void Script::sfSetFrame(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int frameType;
+ int frameOffset;
+ ActorData *actor;
+ ActorFrameRange *frameRange;
+
+ actorId = thread->pop();
+ frameType = thread->pop();
+ frameOffset = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+
+ frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);
+
+ actor->_frameNumber = frameRange->frameIndex + frameOffset;
+
+ if (actor->_currentAction != kActionFall) {
+ actor->_currentAction = kActionFreeze;
+ }
+}
+
+// Script function #39 (0x27)
+// Sets the right-hand portrait
+void Script::sfSetPortrait(SCRIPTFUNC_PARAMS) {
+ int16 param = thread->pop();
+
+ _vm->_interface->setRightPortrait(param);
+}
+
+// Script function #40 (0x28)
+// Sets the left-hand portrait
+void Script::sfSetProtagPortrait(SCRIPTFUNC_PARAMS) {
+ int16 param = thread->pop();
+
+ _vm->_interface->setLeftPortrait(param);
+}
+
+// Script function #41 (0x29) nonblocking
+// Links the specified animations for playback
+
+// Param1: ?
+// Param2: total linked frame count
+// Param3: animation id link target
+// Param4: animation id link source
+void Script::sfChainBgdAnim(SCRIPTFUNC_PARAMS) {
+ int16 animId1 = thread->pop();
+ int16 animId = thread->pop();
+ int16 cycles = thread->pop();
+ int16 speed = thread->pop();
+
+ if (speed >= 0) {
+ _vm->_anim->setCycles(animId, cycles);
+ _vm->_anim->stop(animId);
+ _vm->_anim->setFrameTime(animId, ticksToMSec(speed));
+ }
+
+ _vm->_anim->link(animId1, animId);
+ debug(1, "sfChainBgdAnim(%d, %d, %d, %d)", animId1, animId, cycles, speed);
+}
+
+// Script function #42 (0x2A)
+// Param1: actor id
+// Param2: actor x
+// Param3: actor y
+// Param4: frame seq
+void Script::sfScriptSpecialWalk(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int16 walkFrameSequence;
+ Location actorLocation;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ actorLocation.x = thread->pop();
+ actorLocation.y = thread->pop();
+ walkFrameSequence = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actorLocation.z = actor->_location.z;
+
+ _vm->_actor->actorWalkTo(actorId, actorLocation);
+
+ actor->_walkFrameSequence = walkFrameSequence;
+}
+
+// Script function #43 (0x2B) nonblocking
+// Param1: actor id
+// Param2: actor x
+// Param3: actor y
+// Param4: actor direction
+// Param5: actor action
+// Param6: actor frame number
+void Script::sfPlaceActor(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ Location actorLocation;
+ int actorDirection;
+ int frameType;
+ int frameOffset;
+ ActorData *actor;
+ ActorFrameRange *frameRange;
+
+ actorId = thread->pop();
+ actorLocation.x = thread->pop();
+ actorLocation.y = thread->pop();
+ actorDirection = thread->pop();
+ frameType = thread->pop();
+ frameOffset = thread->pop();
+
+ debug(1, "sfPlaceActor(id = 0x%x, x=%d, y=%d, dir=%d, frameType=%d, frameOffset=%d)", actorId, actorLocation.x,
+ actorLocation.y, actorDirection, frameType, frameOffset);
+
+ actor = _vm->_actor->getActor(actorId);
+ actor->_location.x = actorLocation.x;
+ actor->_location.y = actorLocation.y;
+ actor->_facingDirection = actor->_actionDirection = actorDirection;
+
+ if (!actor->_frames)
+ _vm->_actor->loadActorResources(actor); //? is not it already loaded ?
+
+ if (frameType >= 0) {
+ frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);
+
+ if (frameRange->frameCount <= frameOffset) {
+ error("Wrong frameOffset 0x%X", frameOffset);
+ }
+
+ actor->_frameNumber = frameRange->frameIndex + frameOffset;
+ actor->_currentAction = kActionFreeze;
+ } else {
+ actor->_currentAction = kActionWait;
+ }
+
+ actor->_targetObject = ID_NOTHING;
+}
+
+// Script function #44 (0x2C) nonblocking
+// Checks to see if the user has interrupted a currently playing
+// game cinematic. Pushes a zero or positive value if the game
+// has not been interrupted.
+void Script::sfCheckUserInterrupt(SCRIPTFUNC_PARAMS) {
+ thread->_returnValue = (_skipSpeeches == true);
+}
+
+// Script function #45 (0x2D)
+// Param1: actor id
+// Param2: object id
+// Param3: actor x
+// Param4: actor y
+// Param5: actor walk flag
+void Script::sfScriptWalkRelative(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int16 objectId;
+ uint16 walkFlags;
+ Location actorLocation;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ objectId = thread->pop();
+ actorLocation.x = thread->pop();
+ actorLocation.y = thread->pop();
+ walkFlags = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actorLocation.z = actor->_location.z;
+
+ _vm->_actor->realLocation(actorLocation, objectId, walkFlags);
+
+ actor->_flags &= ~kFollower;
+
+ if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) {
+ thread->waitWalk(actor);
+ }
+
+ if (walkFlags & kWalkBackPedal) {
+ actor->_actorFlags |= kActorBackwards;
+ }
+
+ actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
+}
+
+// Script function #46 (0x2E)
+// Param1: actor id
+// Param2: object id
+// Param3: actor x
+// Param4: actor y
+// Param5: actor walk flag
+void Script::sfScriptMoveRelative(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int16 objectId;
+ uint16 walkFlags;
+ Location actorLocation;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ objectId = thread->pop();
+ actorLocation.x = thread->pop();
+ actorLocation.y = thread->pop();
+ walkFlags = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actorLocation.z = actor->_location.z;
+
+ _vm->_actor->realLocation(actorLocation, objectId, walkFlags);
+
+
+ actor->_location = actorLocation;
+ actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
+}
+
+// Script function #47 (0x2F)
+void Script::sfSimulSpeech2(SCRIPTFUNC_PARAMS) {
+ int16 stringId;
+ int16 actorsCount;
+ int16 speechFlags;
+ int i;
+ uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX];
+ const char *string;
+ int16 sampleResourceId = -1;
+
+ stringId = thread->pop();
+ actorsCount = thread->pop();
+ speechFlags = thread->pop();
+
+ if (actorsCount > ACTOR_SPEECH_ACTORS_MAX)
+ error("sfSimulSpeech2 actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount);
+
+ for (i = 0; i < actorsCount; i++)
+ actorsIds[i] = thread->pop();
+
+ string = thread->_strings->getString(stringId);
+
+ if (thread->_voiceLUT->voices) {
+ sampleResourceId = thread->_voiceLUT->voices[stringId];
+ if (sampleResourceId <= 0 || sampleResourceId > 4000)
+ sampleResourceId = -1;
+ }
+
+ _vm->_actor->simulSpeech(string, actorsIds, actorsCount, speechFlags, sampleResourceId);
+ thread->wait(kWaitTypeSpeech);
+}
+
+
+// Script function #48 (0x30)
+// Param1: string rid
+void Script::sfPlacard(SCRIPTFUNC_PARAMS) {
+ int stringId;
+ Surface *backBuffer = _vm->_gfx->getBackBuffer();
+ static PalEntry cur_pal[PAL_ENTRIES];
+ PalEntry *pal;
+ Event event;
+ Event *q_event;
+
+ if (_vm->getGameType() == GType_IHNM) {
+ warning("Psychic profile is not implemented");
+ return;
+ }
+
+ thread->wait(kWaitTypePlacard);
+
+ _vm->_interface->rememberMode();
+ _vm->_interface->setMode(kPanelPlacard);
+
+ stringId = thread->pop();
+
+ event.type = kEvTOneshot;
+ event.code = kCursorEvent;
+ event.op = kEventHide;
+
+ q_event = _vm->_events->queue(&event);
+
+ _vm->_gfx->getCurrentPal(cur_pal);
+
+ event.type = kEvTImmediate;
+ event.code = kPalEvent;
+ event.op = kEventPalToBlack;
+ event.time = 0;
+ event.duration = kNormalFadeDuration;
+ event.data = cur_pal;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kInterfaceEvent;
+ event.op = kEventClearStatus;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kGraphicsEvent;
+ event.op = kEventSetFlag;
+ event.param = RF_PLACARD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kGraphicsEvent;
+ event.op = kEventFillRect;
+ event.data = backBuffer;
+ event.param = 138;
+ event.param2 = 0;
+ event.param3 = _vm->_scene->getHeight();
+ event.param4 = 0;
+ event.param5 = _vm->getDisplayWidth();
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ // Put the text in the center of the viewport, assuming it will fit on
+ // one line. If we cannot make that assumption we'll need to extend
+ // the text drawing function so that it can center text around a point.
+ // It doesn't end up in exactly the same spot as the original did it,
+ // but it's close enough for now at least.
+
+ TextListEntry textEntry;
+
+ textEntry.knownColor = kKnownColorBrightWhite;
+ textEntry.effectKnownColor = kKnownColorBlack;
+ textEntry.point.x = _vm->getDisplayWidth() / 2;
+ textEntry.point.y = (_vm->_scene->getHeight() - _vm->_font->getHeight(kKnownFontMedium)) / 2;
+ textEntry.font = kKnownFontMedium;
+ textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered);
+ textEntry.text = thread->_strings->getString(stringId);
+
+ _placardTextEntry = _vm->_scene->_textList.addEntry(textEntry);
+
+ event.type = kEvTOneshot;
+ event.code = kTextEvent;
+ event.op = kEventDisplay;
+ event.data = _placardTextEntry;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ _vm->_scene->getBGPal(pal);
+
+ event.type = kEvTImmediate;
+ event.code = kPalEvent;
+ event.op = kEventBlackToPal;
+ event.time = 0;
+ event.duration = kNormalFadeDuration;
+ event.data = pal;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventThreadWake;
+ event.param = kWaitTypePlacard;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+}
+
+// Script function #49 (0x31)
+void Script::sfPlacardOff(SCRIPTFUNC_PARAMS) {
+ static PalEntry cur_pal[PAL_ENTRIES];
+ PalEntry *pal;
+ Event event;
+ Event *q_event;
+
+ thread->wait(kWaitTypePlacard);
+
+ _vm->_interface->restoreMode();
+
+ _vm->_gfx->getCurrentPal(cur_pal);
+
+ event.type = kEvTImmediate;
+ event.code = kPalEvent;
+ event.op = kEventPalToBlack;
+ event.time = 0;
+ event.duration = kNormalFadeDuration;
+ event.data = cur_pal;
+
+ q_event = _vm->_events->queue(&event);
+
+ event.type = kEvTOneshot;
+ event.code = kGraphicsEvent;
+ event.op = kEventClearFlag;
+ event.param = RF_PLACARD;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kTextEvent;
+ event.op = kEventRemove;
+ event.data = _placardTextEntry;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ _vm->_scene->getBGPal(pal);
+
+ event.type = kEvTImmediate;
+ event.code = kPalEvent;
+ event.op = kEventBlackToPal;
+ event.time = 0;
+ event.duration = kNormalFadeDuration;
+ event.data = pal;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kCursorEvent;
+ event.op = kEventShow;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+ event.type = kEvTOneshot;
+ event.code = kScriptEvent;
+ event.op = kEventThreadWake;
+ event.param = kWaitTypePlacard;
+
+ q_event = _vm->_events->chain(q_event, &event);
+
+}
+
+void Script::sfPsychicProfile(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfPsychicProfile", thread, nArgs);
+}
+
+void Script::sfPsychicProfileOff(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfPsychicProfileOff", thread, nArgs);
+}
+
+// Script function #50 (0x32)
+void Script::sfSetProtagState(SCRIPTFUNC_PARAMS) {
+ int protagState = thread->pop();
+
+ _vm->_actor->setProtagState(protagState);
+}
+
+// Script function #51 (0x33)
+void Script::sfResumeBgdAnim(SCRIPTFUNC_PARAMS) {
+ int16 animId = thread->pop();
+ int16 cycles = thread->pop();
+
+ _vm->_anim->resume(animId, cycles);
+ debug(1, "sfResumeBgdAnimSpeed(%d, %d)", animId, cycles);
+
+}
+
+// Script function #52 (0x34)
+// Param1: actor id
+// Param2: x
+// Param3: y
+// Param4: unknown
+// Param5: actionCycle
+// Param6: flags
+void Script::sfThrowActor(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ ActorData *actor;
+ int16 flags;
+ int32 actionCycle;
+ Location location;
+
+ actorId = thread->pop();
+ location.x = thread->pop();
+ location.y = thread->pop();
+ thread->pop();
+ actionCycle = thread->pop();
+ flags = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ location.z = actor->_location.z;
+ actor->_currentAction = kActionFall;
+ actor->_actionCycle = actionCycle;
+ actor->_fallAcceleration = -20;
+ actor->_fallVelocity = - (actor->_fallAcceleration * actor->_actionCycle) / 2;
+ actor->_fallPosition = actor->_location.z << 4;
+
+ actor->_finalTarget = location;
+ actor->_actionCycle--;
+ if (!(flags & kWalkAsync)) {
+ thread->waitWalk(actor);
+ }
+}
+
+// Script function #53 (0x35)
+// Param1: actor id
+// Param2: target object
+void Script::sfWaitWalk(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ actor = _vm->_actor->getActor(actorId);
+
+ if ((actor->_currentAction == kActionWalkToPoint) ||
+ (actor->_currentAction == kActionWalkToLink) ||
+ (actor->_currentAction == kActionFall)) {
+ thread->waitWalk(actor);
+ }
+}
+
+// Script function #54 (0x36)
+void Script::sfScriptSceneID(SCRIPTFUNC_PARAMS) {
+ thread->_returnValue = _vm->_scene->currentSceneNumber();
+}
+
+// Script function #55 (0x37)
+// Param1: actor id
+// Param2: scene number
+void Script::sfChangeActorScene(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int32 sceneNumber;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ sceneNumber = thread->pop();
+ actor = _vm->_actor->getActor(actorId);
+ actor->_sceneNumber = sceneNumber;
+}
+
+// Script function #56 (0x38)
+// Param1: actor id
+// Param2: z
+// Param3: frame seq
+// Param4: flags
+void Script::sfScriptClimb(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ int16 z;
+ ActorData *actor;
+ uint16 flags;
+ int cycleFrameSequence;
+
+ actorId = thread->pop();
+ z = thread->pop();
+ cycleFrameSequence = thread->pop();
+ flags = thread->pop();
+
+ actor = _vm->_actor->getActor(actorId);
+ actor->_finalTarget.z = z;
+ actor->_flags &= ~kFollower;
+ actor->_actionCycle = 1;
+ actor->_cycleFrameSequence = cycleFrameSequence;
+ actor->_currentAction = kActionClimb;
+ if (!(flags & kWalkAsync)) {
+ thread->waitWalk(actor);
+ }
+}
+
+// Script function #57 (0x39)
+// Param1: door #
+// Param2: door state
+void Script::sfSetDoorState(SCRIPTFUNC_PARAMS) {
+ int16 doorNumber;
+ int16 doorState;
+ doorNumber = thread->pop();
+ doorState = thread->pop();
+
+ if (_vm->_scene->getFlags() & kSceneFlagISO) {
+ _vm->_isoMap->setTileDoorState(doorNumber, doorState);
+ } else {
+ _vm->_scene->setDoorState(doorNumber, doorState);
+ }
+}
+
+// Script function #58 (0x3A)
+// Param1: actor id
+// Param2: z
+void Script::sfSetActorZ(SCRIPTFUNC_PARAMS) {
+ int16 objectId;
+ ActorData *actor;
+ ObjectData *obj;
+ int16 z;
+
+ objectId = thread->pop();
+ z = thread->pop();
+
+
+ if (_vm->_actor->validActorId(objectId)) {
+ actor = _vm->_actor->getActor(objectId);
+ actor->_location.z = z;
+ } else {
+ if (_vm->_actor->validObjId(objectId)) {
+ obj = _vm->_actor->getObj(objectId);
+ obj->_location.z = z;
+ }
+ }
+}
+
+// Script function #59 (0x3B)
+// Param1: stringId
+// Param2: flags
+// Param3: color
+// Param4: x
+// Param5: y
+void Script::sfScriptText(SCRIPTFUNC_PARAMS) {
+ int16 stringId;
+ int16 flags;
+ Rect rect;
+ int color;
+ Point point;
+ int width;
+ const char*text;
+ stringId = thread->pop();
+ flags = thread->pop();
+ color = thread->pop();
+ point.x = thread->pop();
+ point.y = thread->pop();
+
+ text = thread->_strings->getString(stringId);
+
+ width = _vm->_font->getStringWidth(kKnownFontScript, text, 0, kFontOutline);
+ rect.top = point.y - 6;
+ rect.setHeight(12);
+ rect.left = point.x - width / 2;
+ rect.setWidth(width);
+
+ _vm->_actor->setSpeechColor(color, _vm->KnownColor2ColorId(kKnownColorBlack));
+ _vm->_actor->nonActorSpeech(rect, &text, 1, -1, flags);
+}
+
+// Script function #60 (0x3C)
+// Param1: actor id
+void Script::sfGetActorX(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ actor = _vm->_actor->getActor(actorId);
+
+ thread->_returnValue = actor->_location.x >> 2;
+}
+
+// Script function #61 (0x3D)
+// Param1: actor id
+void Script::sfGetActorY(SCRIPTFUNC_PARAMS) {
+ int16 actorId;
+ ActorData *actor;
+
+ actorId = thread->pop();
+ actor = _vm->_actor->getActor(actorId);
+
+ thread->_returnValue = actor->_location.y >> 2;
+}
+
+// Script function #62 (0x3E)
+void Script::sfEraseDelta(SCRIPTFUNC_PARAMS) {
+ Surface *backGroundSurface;
+ BGInfo backGroundInfo;
+
+ backGroundSurface = _vm->_render->getBackGroundSurface();
+ _vm->_scene->getBGInfo(backGroundInfo);
+
+ backGroundSurface->blit(backGroundInfo.bounds, backGroundInfo.buffer);
+}
+
+// Script function #63 (0x3F)
+void Script::sfPlayMusic(SCRIPTFUNC_PARAMS) {
+ if (_vm->getGameType() == GType_ITE) {
+ int16 param = thread->pop() + 9;
+
+ if (param >= 9 && param <= 34) {
+ _vm->_music->setVolume(-1, 1);
+ _vm->_music->play(param);
+ } else {
+ _vm->_music->stop();
+ }
+ } else {
+ int16 param1 = thread->pop();
+ int16 param2 = thread->pop();
+
+ if (param1 < 0) {
+ _vm->_music->stop();
+ return;
+ }
+
+ if (param1 >= _vm->_music->_songTableLen) {
+ warning("sfPlayMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTableLen - 1);
+ } else {
+ _vm->_music->setVolume(-1, 1);
+ _vm->_music->play(_vm->_music->_songTable[param1], param2 ? MUSIC_LOOP : MUSIC_NORMAL);
+ }
+ }
+}
+
+// Script function #64 (0x40)
+void Script::sfPickClimbOutPos(SCRIPTFUNC_PARAMS) {
+ int16 u, v, t;
+ ActorData *protagonist = _vm->_actor->_protagonist;
+ while (true) {
+
+ u = (_vm->_rnd.getRandomNumber(63) & 63) + 40;
+ v = (_vm->_rnd.getRandomNumber(63) & 63) + 40;
+ t = _vm->_isoMap->getTileIndex(u, v, 6);
+ if (t == 65) {
+ protagonist->_location.u() = (u << 4) + 4;
+ protagonist->_location.v() = (v << 4) + 4;
+ protagonist->_location.z = 48;
+ break;
+ }
+
+ }
+}
+
+// Script function #65 (0x41)
+void Script::sfTossRif(SCRIPTFUNC_PARAMS) {
+ int16 uc , vc;
+ uint16 direction;
+ ActorData *protagonist = _vm->_actor->_protagonist;
+
+ uc = protagonist->_location.u() >> 4;
+ vc = protagonist->_location.v() >> 4;
+ if (_vm->_isoMap->findNearestChasm(uc, vc, direction)) {
+ uc <<= 4;
+ vc <<= 4;
+ protagonist->_facingDirection = direction;
+
+ protagonist->_finalTarget.u() = uc;
+ protagonist->_finalTarget.v() = vc;
+ protagonist->_finalTarget.z = -40;
+ protagonist->_currentAction = kActionFall;
+ protagonist->_actionCycle = 24;
+ protagonist->_fallAcceleration = - 20;
+ protagonist->_fallVelocity = - (protagonist->_fallAcceleration * 16) / 2 - (44 / 12);
+ protagonist->_fallPosition = protagonist->_location.z << 4;
+ protagonist->_actionCycle--;
+ }
+}
+
+// Script function #66 (0x42)
+void Script::sfShowControls(SCRIPTFUNC_PARAMS) {
+ // It has zero implementation in Win rerelase, and in DOS
+ // release it deals with video ports.
+}
+
+// Script function #67 (0x43)
+void Script::sfShowMap(SCRIPTFUNC_PARAMS) {
+ _vm->_interface->setMode(kPanelMap);
+}
+
+// Script function #68 (0x44)
+void Script::sfPuzzleWon(SCRIPTFUNC_PARAMS) {
+ thread->_returnValue = _vm->_puzzle->isSolved();
+}
+
+// Script function #69 (0x45)
+void Script::sfEnableEscape(SCRIPTFUNC_PARAMS) {
+ if (thread->pop())
+ _abortEnabled = true;
+ else {
+ _skipSpeeches = false;
+ _abortEnabled = false;
+ }
+}
+
+// Script function #70 (0x46)
+void Script::sfPlaySound(SCRIPTFUNC_PARAMS) {
+ int16 param = thread->pop();
+ int res;
+
+ if (param >= 0 && param < _vm->_sndRes->_fxTableLen) {
+ res = _vm->_sndRes->_fxTable[param].res;
+ if (_vm->getFeatures() & GF_CD_FX)
+ res -= 14;
+ _vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, false);
+ } else {
+ _vm->_sound->stopSound();
+ }
+}
+
+// Script function #71 (0x47)
+void Script::sfPlayLoopedSound(SCRIPTFUNC_PARAMS) {
+ int16 param = thread->pop();
+ int res;
+
+ if (param >= 0 && param < _vm->_sndRes->_fxTableLen) {
+ res = _vm->_sndRes->_fxTable[param].res;
+ if (_vm->getFeatures() & GF_CD_FX)
+ res -= 14;
+
+ _vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, true);
+ } else {
+ _vm->_sound->stopSound();
+ }
+}
+
+// Script function #72 (0x48)
+void Script::sfGetDeltaFrame(SCRIPTFUNC_PARAMS) {
+ uint16 animId = (uint16)thread->pop();
+
+ thread->_returnValue = _vm->_anim->getCurrentFrame(animId);
+}
+
+// Script function #73 (0x49)
+void Script::sfShowProtect(SCRIPTFUNC_PARAMS) {
+ if (_vm->_copyProtection) {
+ thread->wait(kWaitTypeRequest);
+
+ _vm->_interface->setMode(kPanelProtect);
+ }
+}
+
+// Script function #74 (0x4A)
+void Script::sfProtectResult(SCRIPTFUNC_PARAMS) {
+ if (_vm->_copyProtection) {
+ thread->_returnValue = _vm->_interface->getProtectHash();
+ } else {
+ int protectHash;
+
+ //cheating
+ protectHash = thread->pop();
+ thread->push(protectHash);
+ thread->_returnValue = protectHash;
+ }
+}
+
+// Script function #75 (0x4b)
+void Script::sfRand(SCRIPTFUNC_PARAMS) {
+ int16 param;
+
+ param = thread->pop();
+ thread->_returnValue = _vm->_rnd.getRandomNumber(param - 1);
+}
+
+// Script function #76 (0x4c)
+void Script::sfFadeMusic(SCRIPTFUNC_PARAMS) {
+ _vm->_music->setVolume(0, 1000);
+}
+
+// Script function #77 (0x4d)
+void Script::sfPlayVoice(SCRIPTFUNC_PARAMS) {
+ int16 param = thread->pop();
+
+ warning("sfPlayVoice(%d)", param);
+ if (param > 0) {
+ _vm->_sndRes->playVoice(param + 3712);
+ } else {
+ _vm->_sound->stopSound();
+ }
+}
+
+void Script::finishDialog(int replyID, int flags, int bitOffset) {
+ byte *addr;
+
+ if (_conversingThread) {
+ _vm->_interface->setMode(kPanelNull);
+
+ _conversingThread->_flags &= ~kTFlagWaiting;
+
+ _conversingThread->push(replyID);
+
+ if (flags & kReplyOnce) {
+ addr = _conversingThread->_staticBase + (bitOffset >> 3);
+ *addr |= (1 << (bitOffset & 7));
+ }
+ }
+
+ _conversingThread = NULL;
+ wakeUpThreads(kWaitTypeDialogBegin);
+}
+
+void Script::sfSetChapterPoints(SCRIPTFUNC_PARAMS) {
+ int16 ethics = thread->pop();
+ int16 barometer = thread->pop();
+ int chapter = _vm->_scene->currentChapterNumber();
+
+ _vm->_ethicsPoints[chapter] = ethics;
+ _vm->_spiritualBarometer = ethics * 256 / barometer;
+}
+
+void Script::sfSetPortraitBgColor(SCRIPTFUNC_PARAMS) {
+ int16 red = thread->pop();
+ int16 green = thread->pop();
+ int16 blue = thread->pop();
+
+ _vm->_interface->setPortraitBgColor(red, green, blue);
+}
+
+void Script::sfScriptStartCutAway(SCRIPTFUNC_PARAMS) {
+ int16 cut;
+ int16 fade;
+
+ cut = thread->pop();
+ thread->pop(); // Not used
+ fade = thread->pop();
+
+ _vm->_anim->playCutaway(cut, fade != 0);
+}
+
+void Script::sfReturnFromCutAway(SCRIPTFUNC_PARAMS) {
+ _vm->_anim->returnFromCutaway();
+}
+
+void Script::sfEndCutAway(SCRIPTFUNC_PARAMS) {
+ _vm->_anim->endCutaway();
+}
+
+void Script::sfGetMouseClicks(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfGetMouseClicks", thread, nArgs);
+}
+
+void Script::sfResetMouseClicks(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfResetMouseClicks", thread, nArgs);
+}
+
+void Script::sfWaitFrames(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfWaitFrames", thread, nArgs);
+}
+
+void Script::sfScriptFade(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfScriptFade", thread, nArgs);
+}
+
+void Script::sfScriptStartVideo(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfScriptStartVideo", thread, nArgs);
+}
+
+void Script::sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfScriptReturnFromVideo", thread, nArgs);
+}
+
+void Script::sfScriptEndVideo(SCRIPTFUNC_PARAMS) {
+ SF_stub("sfScriptEndVideo", thread, nArgs);
+}
+
+void Script::sf87(SCRIPTFUNC_PARAMS) {
+ SF_stub("sf87", thread, nArgs);
+}
+
+void Script::sf88(SCRIPTFUNC_PARAMS) {
+ SF_stub("sf88", thread, nArgs);
+}
+
+void Script::sf89(SCRIPTFUNC_PARAMS) {
+ SF_stub("sf89", thread, nArgs);
+}
+
+void Script::sfVstopFX(SCRIPTFUNC_PARAMS) {
+ _vm->_sound->stopSound();
+}
+
+void Script::sfVstopLoopedFX(SCRIPTFUNC_PARAMS) {
+ _vm->_sound->stopSound();
+}
+
+void Script::sfDemoIsInteractive(SCRIPTFUNC_PARAMS) {
+ thread->_returnValue = 0;
+}
+
+void Script::sfVsetTrack(SCRIPTFUNC_PARAMS) {
+ int16 chapter = thread->pop();
+ int16 sceneNumber = thread->pop();
+ int16 actorsEntrance = thread->pop();
+
+ debug(2, "sfVsetTrrack(%d, %d, %d)", chapter, sceneNumber, actorsEntrance);
+
+ _vm->_scene->changeScene(sceneNumber, actorsEntrance, kTransitionFade, chapter);
+}
+
+void Script::sfGetPoints(SCRIPTFUNC_PARAMS) {
+ int16 index = thread->pop();
+
+ if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints))
+ thread->_returnValue = _vm->_ethicsPoints[index];
+ else
+ thread->_returnValue = 0;
+}
+
+void Script::sfSetGlobalFlag(SCRIPTFUNC_PARAMS) {
+ int16 flag = thread->pop();
+
+ if (flag >= 0 && flag < 32)
+ _vm->_globalFlags |= (1 << flag);
+}
+
+void Script::sfClearGlobalFlag(SCRIPTFUNC_PARAMS) {
+ int16 flag = thread->pop();
+
+ if (flag >= 0 && flag < 32)
+ _vm->_globalFlags &= ~(1 << flag);
+}
+
+void Script::sfTestGlobalFlag(SCRIPTFUNC_PARAMS) {
+ int16 flag = thread->pop();
+
+ if (flag >= 0 && flag < 32 && _vm->_globalFlags & (1 << flag))
+ thread->_returnValue = 1;
+ else
+ thread->_returnValue = 0;
+}
+
+void Script::sfSetPoints(SCRIPTFUNC_PARAMS) {
+ int16 index = thread->pop();
+ int16 points = thread->pop();
+
+ if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints))
+ _vm->_ethicsPoints[index] = points;
+}
+
+void Script::sfSetSpeechBox(SCRIPTFUNC_PARAMS) {
+ int16 param1 = thread->pop();
+ int16 param2 = thread->pop();
+ int16 param3 = thread->pop();
+ int16 param4 = thread->pop();
+
+ _vm->_actor->_speechBoxScript.left = param1;
+ _vm->_actor->_speechBoxScript.top = param2;
+ _vm->_actor->_speechBoxScript.setWidth(param3);
+ _vm->_actor->_speechBoxScript.setHeight(param4);
+}
+
+void Script::sfDebugShowData(SCRIPTFUNC_PARAMS) {
+ int16 param = thread->pop();
+ char buf[50];
+
+ snprintf(buf, 50, "Reached breakpoint %d", param);
+
+ _vm->_interface->setStatusText(buf);
+}
+
+void Script::sfWaitFramesEsc(SCRIPTFUNC_PARAMS) {
+ thread->_returnValue = _vm->_framesEsc;
+}
+
+void Script::sf103(SCRIPTFUNC_PARAMS) {
+ SF_stub("sf103", thread, nArgs);
+}
+
+void Script::sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS) {
+ int value = thread->pop();
+
+ _vm->_interface->disableAbortSpeeches(value != 0);
+}
+
+void Script::sfNull(SCRIPTFUNC_PARAMS) {
+ for (int i = 0; i < nArgs; i++)
+ thread->pop();
+}
+
+void Script::SF_stub(const char *name, ScriptThread *thread, int nArgs) {
+ char buf[256], buf1[100];
+
+ snprintf(buf, 256, "STUB: %s(", name);
+
+ for (int i = 0; i < nArgs; i++) {
+ snprintf(buf1, 100, "%d", thread->pop());
+ strncat(buf, buf1, 256);
+ if (i + 1 < nArgs)
+ strncat(buf, ", ", 256);
+ }
+
+ debug(0, "%s)", buf);
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/sndres.cpp b/engines/saga/sndres.cpp
new file mode 100644
index 0000000000..d3679599fd
--- /dev/null
+++ b/engines/saga/sndres.cpp
@@ -0,0 +1,297 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 resource management class
+
+#include "saga/saga.h"
+
+#include "saga/itedata.h"
+#include "saga/resnames.h"
+#include "saga/rscfile.h"
+#include "saga/sndres.h"
+#include "saga/sound.h"
+#include "saga/stream.h"
+
+#include "common/file.h"
+
+#include "sound/voc.h"
+#include "sound/wave.h"
+#include "sound/adpcm.h"
+#include "sound/audiostream.h"
+
+namespace Saga {
+
+SndRes::SndRes(SagaEngine *vm) : _vm(vm) {
+ /* Load sound module resource file contexts */
+ _sfxContext = _vm->_resource->getContext(GAME_SOUNDFILE);
+ if (_sfxContext == NULL) {
+ error("SndRes::SndRes resource context not found");
+ }
+
+ _voiceSerial = -1;
+
+ setVoiceBank(0);
+
+ if (_vm->getGameType() == GType_ITE) {
+ _fxTable = ITE_SfxTable;
+ _fxTableLen = ITE_SFXCOUNT;
+ } else {
+ ResourceContext *resourceContext;
+
+ resourceContext = _vm->_resource->getContext(GAME_SOUNDFILE);
+ if (resourceContext == NULL) {
+ error("Resource::loadGlobalResources() resource context not found");
+ }
+
+ byte *resourcePointer;
+ size_t resourceLength;
+
+ _vm->_resource->loadResource(resourceContext, RID_IHNM_SFX_LUT,
+ resourcePointer, resourceLength);
+
+ if (resourceLength == 0) {
+ error("Sndres::SndRes can't read SfxIDs table");
+ }
+
+ _fxTableIDsLen = resourceLength / 2;
+ _fxTableIDs = (int16 *)malloc(_fxTableIDsLen * sizeof(int16));
+
+ MemoryReadStream metaS(resourcePointer, resourceLength);
+ for (int i = 0; i < _fxTableIDsLen; i++)
+ _fxTableIDs[i] = metaS.readSint16LE();
+
+ free(resourcePointer);
+
+ _fxTable = 0;
+ _fxTableLen = 0;
+ }
+}
+
+SndRes::~SndRes() {
+ if (_vm->getGameType() == GType_IHNM) {
+ free(_fxTable);
+ free(_fxTableIDs);
+ }
+}
+
+void SndRes::setVoiceBank(int serial)
+{
+ if (_voiceSerial == serial) return;
+
+ _voiceSerial = serial;
+
+ _voiceContext = _vm->_resource->getContext(GAME_VOICEFILE, _voiceSerial);
+ if (_voiceContext == NULL) {
+ error("SndRes::SndRes resource context not found");
+ }
+
+
+}
+
+void SndRes::playSound(uint32 resourceId, int volume, bool loop) {
+ SoundBuffer buffer;
+
+ debug(4, "SndRes::playSound %i", resourceId);
+
+ if (!load(_sfxContext, resourceId, buffer, false)) {
+ warning("Failed to load sound");
+ return;
+ }
+
+ _vm->_sound->playSound(buffer, volume, loop);
+}
+
+void SndRes::playVoice(uint32 resourceId) {
+ SoundBuffer buffer;
+
+ debug(4, "SndRes::playVoice %i", resourceId);
+
+ if (!load(_voiceContext, resourceId, buffer, false)) {
+ warning("Failed to load voice");
+ return;
+ }
+
+ _vm->_sound->playVoice(buffer);
+}
+
+bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buffer, bool onlyHeader) {
+ byte *soundResource;
+ AudioStream *voxStream;
+ size_t soundResourceLength;
+ bool result = false;
+ GameSoundTypes resourceType;
+ byte *data;
+ int rate;
+ int size;
+ byte flags;
+ size_t voxSize;
+ const GameSoundInfo *soundInfo;
+
+ if (resourceId == (uint32)-1) {
+ return false;
+ }
+
+
+ _vm->_resource->loadResource(context, resourceId, soundResource, soundResourceLength);
+
+ if ((context->fileType & GAME_VOICEFILE) != 0) {
+ soundInfo = _vm->getVoiceInfo();
+ } else {
+ soundInfo = _vm->getSfxInfo();
+ }
+
+ context->table[resourceId].fillSoundPatch(soundInfo);
+
+ MemoryReadStream readS(soundResource, soundResourceLength);
+
+ resourceType = soundInfo->resourceType;
+ buffer.isBigEndian = soundInfo->isBigEndian;
+
+ if (soundResourceLength >= 8) {
+ if (!memcmp(soundResource, "Creative", 8)) {
+ resourceType = kSoundVOC;
+ } else if (!memcmp(soundResource, "RIFF", 4) != 0) {
+ resourceType = kSoundWAV;
+ }
+ }
+
+
+ switch (resourceType) {
+ case kSoundPCM:
+ buffer.frequency = soundInfo->frequency;
+ buffer.isSigned = soundInfo->isSigned;
+ buffer.sampleBits = soundInfo->sampleBits;
+ buffer.size = soundResourceLength;
+ buffer.stereo = soundInfo->stereo;
+ if (onlyHeader) {
+ buffer.buffer = NULL;
+ free(soundResource);
+ } else {
+ buffer.buffer = soundResource;
+ }
+ result = true;
+ break;
+ case kSoundMacPCM:
+ buffer.frequency = soundInfo->frequency;
+ buffer.isSigned = soundInfo->isSigned;
+ buffer.sampleBits = soundInfo->sampleBits;
+ buffer.size = soundResourceLength - 36;
+ buffer.stereo = soundInfo->stereo;
+ if (onlyHeader) {
+ buffer.buffer = NULL;
+ } else {
+ buffer.buffer = (byte *)malloc(buffer.size);
+ memcpy(buffer.buffer, soundResource + 36, buffer.size);
+ }
+ free(soundResource);
+ result = true;
+ break;
+ case kSoundVOX:
+ buffer.frequency = soundInfo->frequency;
+ buffer.isSigned = soundInfo->isSigned;
+ buffer.sampleBits = soundInfo->sampleBits;
+ buffer.stereo = soundInfo->stereo;
+ buffer.size = soundResourceLength * 4;
+ if (onlyHeader) {
+ buffer.buffer = NULL;
+ free(soundResource);
+ } else {
+ voxStream = new ADPCMInputStream(&readS, soundResourceLength, kADPCMOki);
+ buffer.buffer = (byte *)malloc(buffer.size);
+ voxSize = voxStream->readBuffer((int16*)buffer.buffer, soundResourceLength * 2);
+ if (voxSize != soundResourceLength * 2) {
+ error("SndRes::load() wrong VOX output size");
+ }
+ delete voxStream;
+ }
+ result = true;
+ break;
+ case kSoundVOC:
+ data = loadVOCFromStream(readS, size, rate);
+ if (data) {
+ buffer.frequency = rate;
+ buffer.sampleBits = 8;
+ buffer.stereo = false;
+ buffer.isSigned = false;
+ buffer.size = size;
+ if (onlyHeader) {
+ buffer.buffer = NULL;
+ free(data);
+ } else {
+ buffer.buffer = data;
+ }
+ result = true;
+ }
+ free(soundResource);
+ break;
+ case kSoundWAV:
+ if (loadWAVFromStream(readS, size, rate, flags)) {
+ buffer.frequency = rate;
+ buffer.sampleBits = 16;
+ buffer.stereo = ((flags & Audio::Mixer::FLAG_STEREO) != 0);
+ buffer.isSigned = true;
+ buffer.size = size;
+ if (onlyHeader) {
+ buffer.buffer = NULL;
+ } else {
+ buffer.buffer = (byte *)malloc(size);
+ readS.read(buffer.buffer, size);
+ }
+ result = true;
+ }
+ free(soundResource);
+ break;
+ default:
+ error("SndRes::load Unknown sound type");
+ }
+
+ // In ITE CD De some voices are absent and contain just 5 bytes header
+ // Round it to even number so soundmanager will not crash.
+ // See bug #1256701
+ buffer.size &= ~(0x1);
+
+ return result;
+}
+
+int SndRes::getVoiceLength(uint32 resourceId) {
+ double msDouble;
+ SoundBuffer buffer;
+
+ if (!load(_voiceContext, resourceId, buffer, true)) {
+ return -1;
+ }
+
+ msDouble = (double)buffer.size;
+ if (buffer.sampleBits == 16) {
+ msDouble /= 2.0;
+ }
+ if (buffer.stereo) {
+ msDouble /= 2.0;
+ }
+
+ msDouble = msDouble / buffer.frequency * 1000.0;
+ return (int)msDouble;
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/sndres.h b/engines/saga/sndres.h
new file mode 100644
index 0000000000..638fb80849
--- /dev/null
+++ b/engines/saga/sndres.h
@@ -0,0 +1,68 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 resource class header
+
+#ifndef SAGA_SNDRES_H_
+#define SAGA_SNDRES_H_
+
+#include "saga/itedata.h"
+#include "saga/sound.h"
+
+namespace Saga {
+
+class SndRes {
+public:
+
+ SndRes(SagaEngine *vm);
+ ~SndRes();
+
+ int loadSound(uint32 resourceId);
+ void playSound(uint32 resourceId, int volume, bool loop);
+ void playVoice(uint32 resourceId);
+ int getVoiceLength(uint32 resourceId);
+ void setVoiceBank(int serial);
+
+ FxTable *_fxTable;
+ int _fxTableLen;
+
+ int16 *_fxTableIDs;
+ int _fxTableIDsLen;
+
+ private:
+ bool load(ResourceContext *context, uint32 resourceId, SoundBuffer &buffer, bool onlyHeader);
+ bool loadVocSound(byte *soundResource, size_t soundResourceLength, SoundBuffer &buffer);
+ bool loadWavSound(byte *soundResource, size_t soundResourceLength, SoundBuffer &buffer);
+
+ ResourceContext *_sfxContext;
+ ResourceContext *_voiceContext;
+
+ int _voiceSerial; // voice bank number
+
+ SagaEngine *_vm;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/sound.cpp b/engines/saga/sound.cpp
new file mode 100644
index 0000000000..37f9cf174f
--- /dev/null
+++ b/engines/saga/sound.cpp
@@ -0,0 +1,149 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 "saga/saga.h"
+
+#include "saga/sound.h"
+
+#include "sound/audiostream.h"
+#include "sound/mixer.h"
+#include "sound/adpcm.h"
+
+namespace Saga {
+
+Sound::Sound(SagaEngine *vm, Audio::Mixer *mixer, int volume) :
+ _vm(vm), _mixer(mixer), _voxStream(0) {
+
+ for (int i = 0; i < SOUND_HANDLES; i++)
+ _handles[i].type = kFreeHandle;
+
+ setVolume(volume == 10 ? 255 : volume * 25);
+}
+
+Sound::~Sound() {
+ delete _voxStream;
+}
+
+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;
+}
+
+void Sound::playSoundBuffer(Audio::SoundHandle *handle, SoundBuffer &buffer, int volume, bool loop) {
+ byte flags;
+
+ flags = Audio::Mixer::FLAG_AUTOFREE;
+
+ if (loop)
+ flags |= Audio::Mixer::FLAG_LOOP;
+
+ if (buffer.sampleBits == 16) {
+ flags |= Audio::Mixer::FLAG_16BITS;
+
+ if (!buffer.isBigEndian)
+ flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN;
+ }
+ if (buffer.stereo)
+ flags |= Audio::Mixer::FLAG_STEREO;
+ if (!buffer.isSigned)
+ flags |= Audio::Mixer::FLAG_UNSIGNED;
+
+ _mixer->playRaw(handle, buffer.buffer, buffer.size, buffer.frequency, flags, -1, volume);
+}
+
+void Sound::playSound(SoundBuffer &buffer, int volume, bool loop) {
+ SndHandle *handle = getHandle();
+
+ handle->type = kEffectHandle;
+ playSoundBuffer(&handle->handle, buffer, 2 * volume, loop);
+}
+
+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() {
+ 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(SoundBuffer &buffer) {
+ SndHandle *handle = getHandle();
+
+ handle->type = kVoiceHandle;
+ playSoundBuffer(&handle->handle, buffer, 255, false);
+}
+
+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);
+}
+
+} // End of namespace Saga
diff --git a/engines/saga/sound.h b/engines/saga/sound.h
new file mode 100644
index 0000000000..85c3eda748
--- /dev/null
+++ b/engines/saga/sound.h
@@ -0,0 +1,97 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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 SAGA_SOUND_H_
+#define SAGA_SOUND_H_
+
+#include "sound/mixer.h"
+
+namespace Saga {
+
+#define SOUND_HANDLES 10
+
+enum SOUND_FLAGS {
+ SOUND_LOOP = 1
+};
+
+struct SoundBuffer {
+ uint16 frequency;
+ int sampleBits;
+ bool stereo;
+ bool isSigned;
+
+ byte *buffer;
+ size_t size;
+ bool isBigEndian;
+};
+
+enum sndHandleType {
+ kFreeHandle,
+ kEffectHandle,
+ kVoiceHandle
+};
+
+struct SndHandle {
+ Audio::SoundHandle handle;
+ sndHandleType type;
+};
+
+class Sound {
+public:
+
+ Sound(SagaEngine *vm, Audio::Mixer *mixer, int volume);
+ ~Sound();
+
+ void playSound(SoundBuffer &buffer, int volume, bool loop);
+ void pauseSound();
+ void resumeSound();
+ void stopSound();
+
+ void playVoice(SoundBuffer &buffer);
+ void pauseVoice();
+ void resumeVoice();
+ void stopVoice();
+
+ void stopAll();
+
+ void setVolume(int volume);
+
+ private:
+
+ void playSoundBuffer(Audio::SoundHandle *handle, SoundBuffer &buffer, int volume, bool loop);
+
+ SndHandle *getHandle();
+
+ SagaEngine *_vm;
+ Audio::Mixer *_mixer;
+ Common::MemoryReadStream *_voxStream;
+
+ SndHandle _handles[SOUND_HANDLES];
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp
new file mode 100644
index 0000000000..5309b1f109
--- /dev/null
+++ b/engines/saga/sprite.cpp
@@ -0,0 +1,444 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Sprite management module
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/scene.h"
+#include "saga/resnames.h"
+#include "saga/rscfile.h"
+#include "saga/font.h"
+
+#include "saga/sprite.h"
+#include "saga/stream.h"
+
+namespace Saga {
+
+Sprite::Sprite(SagaEngine *vm) : _vm(vm) {
+ debug(8, "Initializing sprite subsystem...");
+
+ // Load sprite module resource context
+ _spriteContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ if (_spriteContext == NULL) {
+ error("Sprite::Sprite resource context not found");
+ }
+
+ _decodeBufLen = DECODE_BUF_LEN;
+
+ _decodeBuf = (byte *)malloc(_decodeBufLen);
+ if (_decodeBuf == NULL) {
+ memoryError("Sprite::Sprite");
+ }
+
+ if (_vm->getGameType() == GType_ITE) {
+ loadList(_vm->getResourceDescription()->mainSpritesResourceId, _mainSprites);
+ _arrowSprites = _saveReminderSprites = _inventorySprites = _mainSprites;
+ } else {
+ loadList(RID_IHNM_ARROW_SPRITES, _arrowSprites);
+ loadList(RID_IHNM_SAVEREMINDER_SPRITES, _saveReminderSprites);
+ }
+}
+
+Sprite::~Sprite(void) {
+ debug(8, "Shutting down sprite subsystem...");
+ _mainSprites.freeMem();
+ free(_decodeBuf);
+}
+
+void Sprite::loadList(int resourceId, SpriteList &spriteList) {
+ SpriteInfo *spriteInfo;
+ byte *spriteListData;
+ size_t spriteListLength;
+ uint16 oldSpriteCount;
+ uint16 newSpriteCount;
+ uint16 spriteCount;
+ int i;
+ int outputLength, inputLength;
+ uint32 offset;
+ const byte *spritePointer;
+ const byte *spriteDataPointer;
+
+ _vm->_resource->loadResource(_spriteContext, resourceId, spriteListData, spriteListLength);
+
+ if (spriteListLength == 0) {
+ return;
+ }
+
+ MemoryReadStreamEndian readS(spriteListData, spriteListLength, _spriteContext->isBigEndian);
+
+ spriteCount = readS.readUint16();
+
+ debug(9, "Sprites: %d", spriteCount);
+
+ oldSpriteCount = spriteList.spriteCount;
+ newSpriteCount = spriteList.spriteCount + spriteCount;
+
+ spriteList.infoList = (SpriteInfo *)realloc(spriteList.infoList, newSpriteCount * sizeof(*spriteList.infoList));
+ if (spriteList.infoList == NULL) {
+ memoryError("Sprite::loadList");
+ }
+
+ spriteList.spriteCount = newSpriteCount;
+
+ bool bigHeader = _vm->getGameType() != GType_ITE || _vm->isMacResources();
+
+ for (i = oldSpriteCount; i < spriteList.spriteCount; i++) {
+ spriteInfo = &spriteList.infoList[i];
+ if (bigHeader)
+ offset = readS.readUint32();
+ else
+ offset = readS.readUint16();
+
+ if (offset >= spriteListLength) {
+ error("Sprite::loadList offset exceed");
+ }
+
+ spritePointer = spriteListData;
+ spritePointer += offset;
+
+ if (bigHeader) {
+ MemoryReadStreamEndian readS2(spritePointer, 8, _spriteContext->isBigEndian);
+
+ spriteInfo->xAlign = readS2.readSint16();
+ spriteInfo->yAlign = readS2.readSint16();
+
+ spriteInfo->width = readS2.readUint16();
+ spriteInfo->height = readS2.readUint16();
+
+ spriteDataPointer = spritePointer + readS2.pos();
+ } else {
+ MemoryReadStreamEndian readS2(spritePointer, 4);
+
+ spriteInfo->xAlign = readS2.readSByte();
+ spriteInfo->yAlign = readS2.readSByte();
+
+ spriteInfo->width = readS2.readByte();
+ spriteInfo->height = readS2.readByte();
+ spriteDataPointer = spritePointer + readS2.pos();
+ }
+
+ outputLength = spriteInfo->width * spriteInfo->height;
+ inputLength = spriteListLength - (spriteDataPointer - spriteListData);
+ decodeRLEBuffer(spriteDataPointer, inputLength, outputLength);
+ spriteInfo->decodedBuffer = (byte *) malloc(outputLength);
+ if (spriteInfo->decodedBuffer == NULL) {
+ memoryError("Sprite::loadList");
+ }
+
+ // IHNM sprites are upside-down, for reasons which i can only
+ // assume are perverse. To simplify things, flip them now. Not
+ // at drawing time.
+
+ if (_vm->getGameType() == GType_IHNM) {
+ byte *src = _decodeBuf + spriteInfo->width * (spriteInfo->height - 1);
+ byte *dst = spriteInfo->decodedBuffer;
+
+ for (int j = 0; j < spriteInfo->height; j++) {
+ memcpy(dst, src, spriteInfo->width);
+ src -= spriteInfo->width;
+ dst += spriteInfo->width;
+ }
+ } else
+ memcpy(spriteInfo->decodedBuffer, _decodeBuf, outputLength);
+ }
+
+ free(spriteListData);
+}
+
+void Sprite::getScaledSpriteBuffer(SpriteList &spriteList, int spriteNumber, int scale, int &width, int &height, int &xAlign, int &yAlign, const byte *&buffer) {
+ SpriteInfo *spriteInfo;
+ assert(spriteList.spriteCount>spriteNumber);
+ spriteInfo = &spriteList.infoList[spriteNumber];
+
+ if (scale < 256) {
+ xAlign = (spriteInfo->xAlign * scale) >> 8;
+ yAlign = (spriteInfo->yAlign * scale) >> 8;
+ height = (spriteInfo->height * scale + 0x7f) >> 8;
+ width = (spriteInfo->width * scale + 0x7f) >> 8;
+ scaleBuffer(spriteInfo->decodedBuffer, spriteInfo->width, spriteInfo->height, scale);
+ buffer = _decodeBuf;
+ } else {
+ xAlign = spriteInfo->xAlign;
+ yAlign = spriteInfo->yAlign;
+ height = spriteInfo->height;
+ width = spriteInfo->width;
+ buffer = spriteInfo->decodedBuffer;
+ }
+}
+
+void Sprite::drawClip(Surface *ds, const Rect &clipRect, const Point &spritePointer, int width, int height, const byte *spriteBuffer) {
+ int clipWidth;
+ int clipHeight;
+
+ int i, j, jo, io;
+ byte *bufRowPointer;
+ const byte *srcRowPointer;
+
+ bufRowPointer = (byte *)ds->pixels + ds->pitch * spritePointer.y;
+ srcRowPointer = spriteBuffer;
+
+ clipWidth = width;
+ if (width > (clipRect.right - spritePointer.x)) {
+ clipWidth = (clipRect.right - spritePointer.x);
+ }
+
+ clipHeight = height;
+ if (height > (clipRect.bottom - spritePointer.y)) {
+ clipHeight = (clipRect.bottom - spritePointer.y);
+ }
+
+ jo = 0;
+ io = 0;
+ if (spritePointer.x < clipRect.left) {
+ jo = clipRect.left - spritePointer.x;
+ }
+ if (spritePointer.y < clipRect.top) {
+ io = clipRect.top - spritePointer.y;
+ bufRowPointer += ds->pitch * io;
+ srcRowPointer += width * io;
+ }
+
+ for (i = io; i < clipHeight; i++) {
+ for (j = jo; j < clipWidth; j++) {
+ assert((byte *)ds->pixels <= (byte *)(bufRowPointer + j + spritePointer.x));
+ assert(((byte *)ds->pixels + (_vm->getDisplayWidth() *
+ _vm->getDisplayHeight())) > (byte *)(bufRowPointer + j + spritePointer.x));
+ assert((const byte *)spriteBuffer <= (const byte *)(srcRowPointer + j));
+ assert(((const byte *)spriteBuffer + (width * height)) > (const byte *)(srcRowPointer + j));
+
+ if (*(srcRowPointer + j) != 0) {
+ *(bufRowPointer + j + spritePointer.x) = *(srcRowPointer + j);
+ }
+ }
+ bufRowPointer += ds->pitch;
+ srcRowPointer += width;
+ }
+}
+
+void Sprite::draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Point &screenCoord, int scale) {
+ const byte *spriteBuffer;
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+ Point spritePointer;
+
+ getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
+
+ spritePointer.x = screenCoord.x + xAlign;
+ spritePointer.y = screenCoord.y + yAlign;
+
+ drawClip(ds, clipRect, spritePointer, width, height, spriteBuffer);
+}
+
+void Sprite::draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Rect &screenRect, int scale) {
+ const byte *spriteBuffer;
+ int width;
+ int height;
+ int xAlign, spw;
+ int yAlign, sph;
+ Point spritePointer;
+
+ getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
+ spw = (screenRect.width() - width) / 2;
+ sph = (screenRect.height() - height) / 2;
+ if (spw < 0) {
+ spw = 0;
+ }
+ if (sph < 0) {
+ sph = 0;
+ }
+ spritePointer.x = screenRect.left + xAlign + spw;
+ spritePointer.y = screenRect.top + yAlign + sph;
+ drawClip(ds, clipRect, spritePointer, width, height, spriteBuffer);
+}
+
+bool Sprite::hitTest(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, const Point &testPoint) {
+ const byte *spriteBuffer;
+ int i, j;
+ const byte *srcRowPointer;
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+ Point spritePointer;
+
+ getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
+
+ spritePointer.x = screenCoord.x + xAlign;
+ spritePointer.y = screenCoord.y + yAlign;
+
+ if ((testPoint.y < spritePointer.y) || (testPoint.y >= spritePointer.y + height)) {
+ return false;
+ }
+ if ((testPoint.x < spritePointer.x) || (testPoint.x >= spritePointer.x + width)) {
+ return false;
+ }
+ i = testPoint.y - spritePointer.y;
+ j = testPoint.x - spritePointer.x;
+ srcRowPointer = spriteBuffer + j + i * width;
+ return *srcRowPointer != 0;
+}
+
+void Sprite::drawOccluded(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, int depth) {
+ const byte *spriteBuffer;
+ int x, y;
+ byte *destRowPointer;
+ const byte *sourceRowPointer;
+ const byte *sourcePointer;
+ byte *destPointer;
+ byte *maskPointer;
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+
+ ClipData clipData;
+
+ // BG mask variables
+ int maskWidth;
+ int maskHeight;
+ byte *maskBuffer;
+ size_t maskBufferLength;
+ byte *maskRowPointer;
+ int maskZ;
+
+ if (!_vm->_scene->isBGMaskPresent()) {
+ draw(ds, clipRect, spriteList, spriteNumber, screenCoord, scale);
+ return;
+ }
+
+ _vm->_scene->getBGMaskInfo(maskWidth, maskHeight, maskBuffer, maskBufferLength);
+
+ getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
+
+ clipData.destPoint.x = screenCoord.x + xAlign;
+ clipData.destPoint.y = screenCoord.y + yAlign;
+
+ clipData.sourceRect.left = 0;
+ clipData.sourceRect.top = 0;
+ clipData.sourceRect.right = width;
+ clipData.sourceRect.bottom = height;
+
+ clipData.destRect = clipRect;
+
+ if (!clipData.calcClip()) {
+ return;
+ }
+
+ // Finally, draw the occluded sprite
+
+ sourceRowPointer = spriteBuffer + clipData.drawSource.x + (clipData.drawSource.y * width);
+ destRowPointer = (byte *)ds->pixels + clipData.drawDest.x + (clipData.drawDest.y * ds->pitch);
+ maskRowPointer = maskBuffer + clipData.drawDest.x + (clipData.drawDest.y * maskWidth);
+
+ for (y = 0; y < clipData.drawHeight; y++) {
+ sourcePointer = sourceRowPointer;
+ destPointer = destRowPointer;
+ maskPointer = maskRowPointer;
+ for (x = 0; x < clipData.drawWidth; x++) {
+ if (*sourcePointer != 0) {
+ maskZ = *maskPointer & SPRITE_ZMASK;
+ if (maskZ > depth) {
+ *destPointer = *sourcePointer;
+ }
+ }
+ sourcePointer++;
+ destPointer++;
+ maskPointer++;
+ }
+ destRowPointer += ds->pitch;
+ maskRowPointer += maskWidth;
+ sourceRowPointer += width;
+ }
+}
+
+void Sprite::decodeRLEBuffer(const byte *inputBuffer, size_t inLength, size_t outLength) {
+ int bg_runcount;
+ int fg_runcount;
+ byte *outPointer;
+ byte *outPointerEnd;
+ int c;
+
+ if (outLength > _decodeBufLen) { // TODO: may we should make dynamic growing?
+ error("Sprite::decodeRLEBuffer outLength > _decodeBufLen");
+ }
+
+ outPointer = _decodeBuf;
+ outPointerEnd = _decodeBuf + outLength;
+ outPointerEnd--;
+
+ memset(outPointer, 0, outLength);
+
+ MemoryReadStream readS(inputBuffer, inLength);
+
+ while (!readS.eos() && (outPointer < outPointerEnd)) {
+ bg_runcount = readS.readByte();
+ fg_runcount = readS.readByte();
+
+ for (c = 0; c < bg_runcount && !readS.eos(); c++) {
+ *outPointer = (byte) 0;
+ if (outPointer < outPointerEnd)
+ outPointer++;
+ else
+ return;
+ }
+
+ for (c = 0; c < fg_runcount && !readS.eos(); c++) {
+ *outPointer = readS.readByte();
+ if (outPointer < outPointerEnd)
+ outPointer++;
+ else
+ return;
+ }
+ }
+}
+
+void Sprite::scaleBuffer(const byte *src, int width, int height, int scale) {
+ byte skip = 256 - scale; // skip factor
+ byte vskip = 0x80, hskip;
+ byte *dst = _decodeBuf;
+
+ for (int i = 0; i < height; i++) {
+ vskip += skip;
+
+ if (vskip < skip) { // We had an overflow
+ src += width;
+ } else {
+ hskip = 0x80;
+
+ for (int j = 0; j < width; j++) {
+ *dst++ = *src++;
+
+ hskip += skip;
+ if (hskip < skip) // overflow
+ dst--;
+ }
+ }
+ }
+}
+
+
+} // End of namespace Saga
diff --git a/engines/saga/sprite.h b/engines/saga/sprite.h
new file mode 100644
index 0000000000..03bde8e050
--- /dev/null
+++ b/engines/saga/sprite.h
@@ -0,0 +1,102 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Sprite management module private header file
+
+#ifndef SAGA_SPRITE_H__
+#define SAGA_SPRITE_H__
+
+namespace Saga {
+
+#define SPRITE_ZMAX 16
+#define SPRITE_ZMASK 0x0F
+
+#define DECODE_BUF_LEN 64000
+
+struct SpriteInfo {
+ byte *decodedBuffer;
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+};
+
+struct SpriteList {
+ int spriteListResourceId;
+ int spriteCount;
+ SpriteInfo *infoList;
+
+ void freeMem() {
+ int i;
+ for (i = 0; i < spriteCount; i++) {
+ free(infoList[i].decodedBuffer);
+ }
+ free(infoList);
+ memset(this, 0, sizeof(*this));
+ }
+
+ SpriteList() {
+ memset(this, 0, sizeof(*this));
+ }
+};
+
+
+class Sprite {
+public:
+ SpriteList _mainSprites;
+ SpriteList _saveReminderSprites;
+ SpriteList _arrowSprites;
+ SpriteList _inventorySprites;
+
+ Sprite(SagaEngine *vm);
+ ~Sprite(void);
+
+ // draw scaled sprite using background scene mask
+ void drawOccluded(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, int depth);
+
+ // draw scaled sprite using background scene mask
+ void draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Point &screenCoord, int scale);
+
+ // main function
+ void drawClip(Surface *ds, const Rect &clipRect, const Point &spritePointer, int width, int height, const byte *spriteBuffer);
+
+ void draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Rect &screenRect, int scale);
+
+ void loadList(int resourceId, SpriteList &spriteList); // load or append spriteList
+ bool hitTest(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, const Point &testPoint);
+ void getScaledSpriteBuffer(SpriteList &spriteList, int spriteNumber, int scale, int &width, int &height, int &xAlign, int &yAlign, const byte *&buffer);
+
+private:
+ void decodeRLEBuffer(const byte *inputBuffer, size_t inLength, size_t outLength);
+ void scaleBuffer(const byte *src, int width, int height, int scale);
+
+ SagaEngine *_vm;
+ ResourceContext *_spriteContext;
+ byte *_decodeBuf;
+ size_t _decodeBufLen;
+};
+
+} // End of namespace Saga
+
+#endif
diff --git a/engines/saga/sthread.cpp b/engines/saga/sthread.cpp
new file mode 100644
index 0000000000..a6724b588c
--- /dev/null
+++ b/engines/saga/sthread.cpp
@@ -0,0 +1,746 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+
+// Scripting module thread management component
+#include "saga/saga.h"
+
+#include "saga/gfx.h"
+#include "saga/actor.h"
+#include "saga/console.h"
+#include "saga/interface.h"
+
+#include "saga/script.h"
+
+#include "saga/stream.h"
+#include "saga/scene.h"
+#include "saga/resnames.h"
+
+namespace Saga {
+
+ScriptThread *Script::createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber) {
+ ScriptThread *newThread;
+
+ loadModule(scriptModuleNumber);
+ if (_modules[scriptModuleNumber].entryPointsCount <= scriptEntryPointNumber) {
+ error("Script::createThread wrong scriptEntryPointNumber");
+ }
+
+ newThread = _threadList.pushFront().operator->();
+ newThread->_flags = kTFlagNone;
+ newThread->_stackSize = DEFAULT_THREAD_STACK_SIZE;
+ newThread->_stackBuf = (uint16 *)malloc(newThread->_stackSize * sizeof(*newThread->_stackBuf));
+ newThread->_stackTopIndex = newThread->_stackSize - 2;
+ newThread->_instructionOffset = _modules[scriptModuleNumber].entryPoints[scriptEntryPointNumber].offset;
+ newThread->_commonBase = _commonBuffer;
+ newThread->_staticBase = _commonBuffer + _modules[scriptModuleNumber].staticOffset;
+ newThread->_moduleBase = _modules[scriptModuleNumber].moduleBase;
+ newThread->_moduleBaseSize = _modules[scriptModuleNumber].moduleBaseSize;
+
+ newThread->_strings = &_modules[scriptModuleNumber].strings;
+
+ if (_vm->getGameType() == GType_IHNM)
+ newThread->_voiceLUT = &_globalVoiceLUT;
+ else
+ newThread->_voiceLUT = &_modules[scriptModuleNumber].voiceLUT;
+
+ return newThread;
+}
+
+void Script::wakeUpActorThread(int waitType, void *threadObj) {
+ ScriptThread *thread;
+ ScriptThreadList::iterator threadIterator;
+
+ for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
+ thread = threadIterator.operator->();
+ if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType) && (thread->_threadObj == threadObj)) {
+ thread->_flags &= ~kTFlagWaiting;
+ }
+ }
+}
+
+void Script::wakeUpThreads(int waitType) {
+ ScriptThread *thread;
+ ScriptThreadList::iterator threadIterator;
+
+ for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
+ thread = threadIterator.operator->();
+ if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType)) {
+ thread->_flags &= ~kTFlagWaiting;
+ }
+ }
+}
+
+void Script::wakeUpThreadsDelayed(int waitType, int sleepTime) {
+ ScriptThread *thread;
+ ScriptThreadList::iterator threadIterator;
+
+ for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
+ thread = threadIterator.operator->();
+ if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType)) {
+ thread->_waitType = kWaitTypeDelay;
+ thread->_sleepTime = sleepTime;
+ }
+ }
+}
+
+void Script::executeThreads(uint msec) {
+ ScriptThread *thread;
+ ScriptThreadList::iterator threadIterator;
+
+ if (_vm->_interface->_statusTextInput) {
+ return;
+ }
+
+ threadIterator = _threadList.begin();
+
+ while (threadIterator != _threadList.end()) {
+ thread = threadIterator.operator->();
+
+ if (thread->_flags & (kTFlagFinished | kTFlagAborted)) {
+ if (thread->_flags & kTFlagFinished)
+ setPointerVerb();
+
+ if (_vm->getGameType() == GType_IHNM) {
+ thread->_flags &= ~kTFlagFinished;
+ thread->_flags |= kTFlagAborted;
+ ++threadIterator;
+ } else {
+ threadIterator = _threadList.erase(threadIterator);
+ }
+ continue;
+ }
+
+ if (thread->_flags & kTFlagWaiting) {
+
+ switch (thread->_waitType) {
+ case kWaitTypeDelay:
+ if (thread->_sleepTime < msec) {
+ thread->_sleepTime = 0;
+ } else {
+ thread->_sleepTime -= msec;
+ }
+
+ if (thread->_sleepTime == 0)
+ thread->_flags &= ~kTFlagWaiting;
+ break;
+
+ case kWaitTypeWalk:
+ {
+ ActorData *actor;
+ actor = (ActorData *)thread->_threadObj;
+ if (actor->_currentAction == kActionWait) {
+ thread->_flags &= ~kTFlagWaiting;
+ }
+ }
+ break;
+
+ case kWaitTypeWaitFrames: // IHNM
+ if (thread->_frameWait < _vm->_frameCount)
+ thread->_flags &= ~kTFlagWaiting;
+ break;
+ }
+ }
+
+ if (!(thread->_flags & kTFlagWaiting)) {
+ if (runThread(thread, STHREAD_TIMESLICE)) {
+ break;
+ }
+ }
+
+ ++threadIterator;
+ }
+
+}
+
+void Script::abortAllThreads(void) {
+ ScriptThread *thread;
+ ScriptThreadList::iterator threadIterator;
+
+ threadIterator = _threadList.begin();
+
+ while (threadIterator != _threadList.end()) {
+ thread = threadIterator.operator->();
+ thread->_flags |= kTFlagAborted;
+ ++threadIterator;
+ }
+ executeThreads(0);
+}
+
+void Script::completeThread(void) {
+ int limit = (_vm->getGameType() == GType_IHNM) ? 100 : 40;
+
+ for (int i = 0; i < limit && !_threadList.isEmpty(); i++)
+ executeThreads(0);
+}
+
+bool Script::runThread(ScriptThread *thread, uint instructionLimit) {
+ const char*operandName;
+ uint instructionCount;
+ uint16 savedInstructionOffset;
+
+ byte *addr;
+ byte mode;
+ uint16 jmpOffset1;
+ int16 iparam1;
+ int16 iparam2;
+ int16 iparam3;
+
+ bool disContinue;
+ byte argumentsCount;
+ uint16 functionNumber;
+ uint16 checkStackTopIndex;
+ ScriptFunctionType scriptFunction;
+
+ int operandChar;
+ int i;
+
+ MemoryReadStream scriptS(thread->_moduleBase, thread->_moduleBaseSize);
+
+ scriptS.seek(thread->_instructionOffset);
+
+ for (instructionCount = 0; instructionCount < instructionLimit; instructionCount++) {
+ if (thread->_flags & (kTFlagAsleep))
+ break;
+
+ savedInstructionOffset = thread->_instructionOffset;
+ operandChar = scriptS.readByte();
+
+
+#define CASEOP(opName) case opName: \
+ if (operandChar == opName) { \
+ operandName = #opName; \
+ debug(2, operandName); \
+ _vm->_console->DebugPrintf("%s\n", operandName); \
+ }
+
+ debug(8, "Executing thread offset: %lu (%x) stack: %d", thread->_instructionOffset, operandChar, thread->pushedSize());
+ operandName="";
+ switch (operandChar) {
+ CASEOP(opNextBlock)
+ // Some sort of "jump to the start of the next memory
+ // page" instruction, I think.
+ thread->_instructionOffset = (((thread->_instructionOffset) >> 10) + 1) << 10;
+ break;
+
+// STACK INSTRUCTIONS
+ CASEOP(opDup)
+ thread->push(thread->stackTop());
+ break;
+ CASEOP(opDrop)
+ thread->pop();
+ break;
+ CASEOP(opZero)
+ thread->push(0);
+ break;
+ CASEOP(opOne)
+ thread->push(1);
+ break;
+ CASEOP(opConstint)
+ CASEOP(opStrlit)
+ iparam1 = scriptS.readSint16LE();
+ thread->push(iparam1);
+ debug(8, "0x%X", iparam1);
+ break;
+
+// DATA INSTRUCTIONS
+ CASEOP(opGetFlag)
+ addr = thread->baseAddress(scriptS.readByte());
+ iparam1 = scriptS.readSint16LE();
+ addr += (iparam1 >> 3);
+ iparam1 = (1 << (iparam1 & 7));
+ thread->push((*addr) & iparam1 ? 1 : 0);
+ break;
+ CASEOP(opGetInt)
+ mode = scriptS.readByte();
+ addr = thread->baseAddress(mode);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ thread->push(readUint16(addr, mode));
+ debug(8, "0x%X", readUint16(addr, mode));
+ break;
+ CASEOP(opPutFlag)
+ addr = thread->baseAddress(scriptS.readByte());
+ iparam1 = scriptS.readSint16LE();
+ addr += (iparam1 >> 3);
+ iparam1 = (1 << (iparam1 & 7));
+ if (thread->stackTop()) {
+ *addr |= iparam1;
+ } else {
+ *addr &= ~iparam1;
+ }
+ break;
+ CASEOP(opPutInt)
+ mode = scriptS.readByte();
+ addr = thread->baseAddress(mode);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ writeUint16(addr, thread->stackTop(), mode);
+ break;
+ CASEOP(opPutFlagV)
+ addr = thread->baseAddress(scriptS.readByte());
+ iparam1 = scriptS.readSint16LE();
+ addr += (iparam1 >> 3);
+ iparam1 = (1 << (iparam1 & 7));
+ if (thread->pop()) {
+ *addr |= iparam1;
+ } else {
+ *addr &= ~iparam1;
+ }
+ break;
+ CASEOP(opPutIntV)
+ mode = scriptS.readByte();
+ addr = thread->baseAddress(mode);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ writeUint16(addr, thread->pop(), mode);
+ break;
+
+// FUNCTION CALL INSTRUCTIONS
+ CASEOP(opCall)
+ argumentsCount = scriptS.readByte();
+ iparam1 = scriptS.readByte();
+ if (iparam1 != kAddressModule) {
+ error("Script::runThread iparam1 != kAddressModule");
+ }
+ addr = thread->baseAddress(iparam1);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ thread->push(argumentsCount);
+
+ jmpOffset1 = scriptS.pos();
+ // NOTE: The original pushes the program
+ // counter as a pointer here. But I don't think
+ // we will have to do that.
+ thread->push(jmpOffset1);
+ // NOTE2: program counter is 32bit - so we should "emulate" it size - because kAddressStack relies on it
+ thread->push(0);
+ thread->_instructionOffset = iparam1;
+
+ break;
+ CASEOP(opCcall)
+ CASEOP(opCcallV)
+ argumentsCount = scriptS.readByte();
+ functionNumber = scriptS.readUint16LE();
+ if (functionNumber >= ((_vm->getGameType() == GType_IHNM) ?
+ IHNM_SCRIPT_FUNCTION_MAX : ITE_SCRIPT_FUNCTION_MAX)) {
+ error("Script::runThread() Invalid script function number (%d)", functionNumber);
+ }
+
+ debug(2, "Calling #%d %s argCount=%i", functionNumber, _scriptFunctionsList[functionNumber].scriptFunctionName, argumentsCount);
+ scriptFunction = _scriptFunctionsList[functionNumber].scriptFunction;
+ checkStackTopIndex = thread->_stackTopIndex + argumentsCount;
+ disContinue = false;
+ (this->*scriptFunction)(thread, argumentsCount, disContinue);
+ if (disContinue) {
+ return true;
+ }
+ if (scriptFunction == &Saga::Script::sfScriptGotoScene ||
+ scriptFunction == &Saga::Script::sfVsetTrack) {
+ return true; // cause abortAllThreads called and _this_ thread destroyed
+ }
+
+ thread->_stackTopIndex = checkStackTopIndex;
+
+ if (operandChar == opCcall) {// CALL function
+ thread->push(thread->_returnValue);
+ }
+
+ if (thread->_flags & kTFlagAsleep)
+ instructionCount = instructionLimit; // break out of loop!
+ break;
+ CASEOP(opEnter)
+ thread->push(thread->_frameIndex);
+ thread->_frameIndex = thread->_stackTopIndex;
+ thread->_stackTopIndex -= (scriptS.readSint16LE() / 2);
+ break;
+ CASEOP(opReturn)
+ thread->_returnValue = thread->pop();
+ CASEOP(opReturnV)
+ thread->_stackTopIndex = thread->_frameIndex;
+ thread->_frameIndex = thread->pop();
+ if (thread->pushedSize() == 0) {
+ thread->_flags |= kTFlagFinished;
+ return true;
+ } else {
+ thread->pop(); //cause it 0
+ thread->_instructionOffset = thread->pop();
+
+ // Pop all the call parameters off the stack
+ iparam1 = thread->pop();
+ while (iparam1--) {
+ thread->pop();
+ }
+
+ if (operandChar == opReturn) {
+ thread->push(thread->_returnValue);
+ }
+ }
+ break;
+
+// BRANCH INSTRUCTIONS
+ CASEOP(opJmp)
+ jmpOffset1 = scriptS.readUint16LE();
+ thread->_instructionOffset = jmpOffset1;
+ break;
+ CASEOP(opJmpTrueV)
+ jmpOffset1 = scriptS.readUint16LE();
+ if (thread->pop()) {
+ thread->_instructionOffset = jmpOffset1;
+ }
+ break;
+ CASEOP(opJmpFalseV)
+ jmpOffset1 = scriptS.readUint16LE();
+ if (!thread->pop()) {
+ thread->_instructionOffset = jmpOffset1;
+ }
+ break;
+ CASEOP(opJmpTrue)
+ jmpOffset1 = scriptS.readUint16LE();
+ if (thread->stackTop()) {
+ thread->_instructionOffset = jmpOffset1;
+ }
+ break;
+ CASEOP(opJmpFalse)
+ jmpOffset1 = scriptS.readUint16LE();
+ if (!thread->stackTop()) {
+ thread->_instructionOffset = jmpOffset1;
+ }
+ break;
+ CASEOP(opJmpSwitch)
+ iparam1 = scriptS.readSint16LE();
+ iparam2 = thread->pop();
+ while (iparam1--) {
+ iparam3 = scriptS.readUint16LE();
+ thread->_instructionOffset = scriptS.readUint16LE();
+ if (iparam3 == iparam2) {
+ break;
+ }
+ }
+ if (iparam1 < 0) {
+ thread->_instructionOffset = scriptS.readUint16LE();
+ }
+ break;
+ CASEOP(opJmpRandom)
+ // Supposedly the number of possible branches.
+ // The original interpreter ignores it.
+ scriptS.readUint16LE();
+ iparam1 = scriptS.readSint16LE();
+ iparam1 = _vm->_rnd.getRandomNumber(iparam1 - 1);
+ while (1) {
+ iparam2 = scriptS.readSint16LE();
+ thread->_instructionOffset = scriptS.readUint16LE();
+
+ iparam1 -= iparam2;
+ if (iparam1 < 0) {
+ break;
+ }
+ }
+ break;
+
+// UNARY INSTRUCTIONS
+ CASEOP(opNegate)
+ thread->push(-thread->pop());
+ break;
+ CASEOP(opNot)
+ thread->push(!thread->pop());
+ break;
+ CASEOP(opCompl)
+ thread->push(~thread->pop());
+ break;
+
+ CASEOP(opIncV)
+ mode = scriptS.readByte();
+ addr = thread->baseAddress(mode);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ iparam1 = readUint16(addr, mode);
+ writeUint16(addr, iparam1 + 1, mode);
+ break;
+ CASEOP(opDecV)
+ mode = scriptS.readByte();
+ addr = thread->baseAddress(mode);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ iparam1 = readUint16(addr, mode);
+ writeUint16(addr, iparam1 - 1, mode);
+ break;
+ CASEOP(opPostInc)
+ mode = scriptS.readByte();
+ addr = thread->baseAddress(mode);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ iparam1 = readUint16(addr, mode);
+ thread->push(iparam1);
+ writeUint16(addr, iparam1 + 1, mode);
+ break;
+ CASEOP(opPostDec)
+ mode = scriptS.readByte();
+ addr = thread->baseAddress(mode);
+ iparam1 = scriptS.readSint16LE();
+ addr += iparam1;
+ iparam1 = readUint16(addr, mode);
+ thread->push(iparam1);
+ writeUint16(addr, iparam1 - 1, mode);
+ break;
+
+// ARITHMETIC INSTRUCTIONS
+ CASEOP(opAdd)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 += iparam2;
+ thread->push(iparam1);
+ break;
+ CASEOP(opSub)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 -= iparam2;
+ thread->push(iparam1);
+ break;
+ CASEOP(opMul)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 *= iparam2;
+ thread->push(iparam1);
+ break;
+ CASEOP(opDiv)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 /= iparam2;
+ thread->push(iparam1);
+ break;
+ CASEOP(opMod)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 %= iparam2;
+ thread->push(iparam1);
+ break;
+
+// COMPARISION INSTRUCTIONS
+ CASEOP(opEq)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 == iparam2) ? 1 : 0);
+ debug(8, "0x%X 0x%X", iparam1, iparam2);
+ break;
+ CASEOP(opNe)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 != iparam2) ? 1 : 0);
+ break;
+ CASEOP(opGt)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 > iparam2) ? 1 : 0);
+ break;
+ CASEOP(opLt)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 < iparam2) ? 1 : 0);
+ break;
+ CASEOP(opGe)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 >= iparam2) ? 1 : 0);
+ break;
+ CASEOP(opLe)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 <= iparam2) ? 1 : 0);
+ break;
+
+// SHIFT INSTRUCTIONS
+ CASEOP(opRsh)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 >>= iparam2;
+ thread->push(iparam1);
+ break;
+ CASEOP(opLsh)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 <<= iparam2;
+ thread->push(iparam1);
+ break;
+
+// BITWISE INSTRUCTIONS
+ CASEOP(opAnd)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 &= iparam2;
+ thread->push(iparam1);
+ break;
+ CASEOP(opOr)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 |= iparam2;
+ thread->push(iparam1);
+ break;
+ CASEOP(opXor)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ iparam1 ^= iparam2;
+ thread->push(iparam1);
+ break;
+
+// LOGICAL INSTRUCTIONS
+ CASEOP(opLAnd)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 && iparam2) ? 1 : 0);
+ break;
+ CASEOP(opLOr)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push((iparam1 || iparam2) ? 1 : 0);
+ break;
+ CASEOP(opLXor)
+ iparam2 = thread->pop();
+ iparam1 = thread->pop();
+ thread->push(((iparam1 && !iparam2) || (!iparam1 && iparam2)) ? 1 : 0);
+ break;
+
+// GAME INSTRUCTIONS
+ CASEOP(opSpeak) {
+ int stringsCount;
+ uint16 actorId;
+ uint16 speechFlags;
+ int sampleResourceId = -1;
+ int16 first;
+ const char *strings[ACTOR_SPEECH_STRING_MAX];
+
+ if (_vm->_actor->isSpeaking()) {
+ thread->wait(kWaitTypeSpeech);
+ return false;
+ }
+
+ stringsCount = scriptS.readByte();
+ actorId = scriptS.readUint16LE();
+ speechFlags = scriptS.readByte();
+ scriptS.readUint16LE(); // x,y skip
+
+ if (stringsCount == 0)
+ error("opSpeak stringsCount == 0");
+
+ if (stringsCount > ACTOR_SPEECH_STRING_MAX)
+ error("opSpeak stringsCount=0x%X exceed ACTOR_SPEECH_STRING_MAX", stringsCount);
+
+ iparam1 = first = thread->stackTop();
+ for (i = 0; i < stringsCount; i++) {
+ iparam1 = thread->pop();
+ strings[i] = thread->_strings->getString(iparam1);
+ }
+ // now data contains last string index
+
+ if (_vm->getGameId() == GID_ITE_DISK_G) { // special ITE dos
+ if ((_vm->_scene->currentSceneNumber() == ITE_DEFAULT_SCENE) &&
+ (iparam1 >= 288) && (iparam1 <= (RID_SCENE1_VOICE_138 - RID_SCENE1_VOICE_009 + 288))) {
+ sampleResourceId = RID_SCENE1_VOICE_009 + iparam1 - 288;
+ }
+ } else {
+ if (thread->_voiceLUT->voicesCount > first) {
+ sampleResourceId = thread->_voiceLUT->voices[first];
+ }
+ }
+
+ if (sampleResourceId < 0 || sampleResourceId > 4000)
+ sampleResourceId = -1;
+
+ if (_vm->getGameType() == GType_ITE && !sampleResourceId)
+ sampleResourceId = -1;
+
+ _vm->_actor->actorSpeech(actorId, strings, stringsCount, sampleResourceId, speechFlags);
+
+ if (!(speechFlags & kSpeakAsync)) {
+ thread->wait(kWaitTypeSpeech);
+ }
+ }
+ break;
+ CASEOP(opDialogBegin)
+ if (_conversingThread) {
+ thread->wait(kWaitTypeDialogBegin);
+ return false;
+ }
+ _conversingThread = thread;
+ _vm->_interface->converseClear();
+ break;
+ CASEOP(opDialogEnd)
+ if (thread == _conversingThread) {
+ _vm->_interface->activate();
+ _vm->_interface->setMode(kPanelConverse);
+ thread->wait(kWaitTypeDialogEnd);
+ return false;
+ }
+ break;
+ CASEOP(opReply) {
+ const char *str;
+ byte replyNum;
+ byte flags;
+ replyNum = scriptS.readByte();
+ flags = scriptS.readByte();
+ iparam1 = 0;
+
+ if (flags & kReplyOnce) {
+ iparam1 = scriptS.readSint16LE();
+ addr = thread->_staticBase + (iparam1 >> 3);
+ if (*addr & (1 << (iparam1 & 7))) {
+ break;
+ }
+ }
+
+ str = thread->_strings->getString(thread->pop());
+ if (_vm->_interface->converseAddText(str, replyNum, flags, iparam1))
+ warning("Error adding ConverseText (%s, %d, %d, %d)", str, replyNum, flags, iparam1);
+ }
+ break;
+ CASEOP(opAnimate)
+ scriptS.readUint16LE();
+ scriptS.readUint16LE();
+ jmpOffset1 = scriptS.readByte();
+ thread->_instructionOffset += jmpOffset1;
+ break;
+
+ default:
+ error("Script::runThread() Invalid opcode encountered 0x%X", operandChar);
+ }
+
+ if (thread->_flags & (kTFlagFinished | kTFlagAborted)) {
+ error("Wrong flags %d in thread", thread->_flags);
+ }
+
+ // Set instruction offset only if a previous instruction didn't branch
+ if (savedInstructionOffset == thread->_instructionOffset) {
+ thread->_instructionOffset = scriptS.pos();
+ } else {
+ if (thread->_instructionOffset >= scriptS.size()) {
+ error("Script::runThread() Out of range script execution");
+ }
+
+ scriptS.seek(thread->_instructionOffset);
+ }
+ }
+ return false;
+}
+
+} // End of namespace Saga
+
diff --git a/engines/saga/stream.h b/engines/saga/stream.h
new file mode 100644
index 0000000000..4a407216b2
--- /dev/null
+++ b/engines/saga/stream.h
@@ -0,0 +1,57 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * 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 SAGA_STREAM_H__
+#define SAGA_STREAM_H__
+
+#include "common/stream.h"
+
+namespace Saga {
+
+using Common::MemoryReadStream;
+
+class MemoryReadStreamEndian : public Common::MemoryReadStream {
+private:
+public:
+ bool _bigEndian;
+ MemoryReadStreamEndian(const byte *buf, uint32 len, bool bigEndian = false) : MemoryReadStream(buf, len), _bigEndian(bigEndian) {}
+
+ uint16 readUint16() {
+ return (_bigEndian) ? readUint16BE(): readUint16LE();
+ }
+
+ uint32 readUint32() {
+ return (_bigEndian) ? readUint32BE(): readUint32LE();
+ }
+
+ inline int16 readSint16() {
+ return (int16)readUint16();
+ }
+
+
+ inline int32 readSint32() {
+ return (int32)readUint32();
+ }
+};
+
+} // End of namespace Saga
+#endif
diff --git a/engines/saga/xref.txt b/engines/saga/xref.txt
new file mode 100644
index 0000000000..1d3aff0824
--- /dev/null
+++ b/engines/saga/xref.txt
@@ -0,0 +1,139 @@
+$Id$
+
+Cross-reference for functions and variables for the original source code and
+the ScummVM implementation.
+
+Watcom C++ arguments order:
+
+ eax, edx, ebx, ecx, stack
+
+Sceneres.h
+==========
+ LOADREQ_FIGURE
+ LOADREQ_OBJECT
+ LOADREQ_BACKGROUND SAGA_BG_IMAGE
+ LOADREQ_ZBUF SAGA_BG_MASK
+ LOADREQ_SCENE_SCRIPT
+ LOADREQ_STRINGS SAGA_OBJECT_NAME_LIST
+ LOADREQ_HITZONES SAGA_OBJECT_MAP
+ LOADREQ_STEPZONES SAGA_ACTION_MAP
+ LOADREQ_TILE_IMAGES SAGA_ISO_TILESET
+ LOADREQ_TILE_MAP SAGA_ISO_METAMAP
+ LOADREQ_TILE_PLATFORMS SAGA_ISO_METATILESET
+ LOADREQ_TILE_METATILES
+ LOADREQ_ENTRY SAGA_ENTRY
+ LOADREQ_FRAMELIST
+
+ LOADREQ_ANIM_0 SAGA_ANIM_1
+ LOADREQ_ANIM_1 SAGA_ANIM_2
+ LOADREQ_ANIM_2 SAGA_ANIM_3
+ LOADREQ_ANIM_3 SAGA_ANIM_4
+ LOADREQ_ANIM_4 SAGA_ANIM_5
+ LOADREQ_ANIM_5 SAGA_ANIM_6
+ LOADREQ_ANIM_6 SAGA_ANIM_7
+ LOADREQ_ANIM_7
+
+ LOADREQ_TILE_MULTI
+ LOADREQ_CYCLES SAGA_PAL_ANIM
+ LOADREQ_FACES SAGA_FACES
+ LOADREQ_PALETTE
+
+ hitZone _objectMap
+ stepZone _actionMap
+
+ HZONEF_EXIT OBJECT_EXIT (in Verb.c), ACTION_EXIT (in Actor.c)
+ HZONEF_ENABLED OBJECT_ENABLED (in Verb.c), ACTION_ENABLED (in Actor.c)
+ HZONEF_NOWALK OBJECT_NOWALK
+ HZONEF_PROJECT OBJECT_PROJECT
+ HZONEF_AUTOWALK ACTION_AUTOWALK
+ HZONEF_TERMINUS ACTION_TERMINUS
+
+ FrameRange.startFrame ACTORACTIONITEM.frame_index
+ FrameRange.frameCount ACTORACTIONITEM.frame_count
+
+ FrameSequence.right ACTORACTION.dir[0]
+ FrameSequence.left ACTORACTION.dir[1]
+ FrameSequence.back ACTORACTION.dir[2]
+ FrameSequence.forward ACTORACTION.dir[3]
+
+Scene.c
+=======
+ ResToImage() _vm->decodeBGImage()
+ resInfo->sceneFlags _desc.flags
+ resInfo->loadList _desc.resListRN
+ resInfo->horizon _desc.endSlope
+ resInfo->nearFigureLimit _desc.beginSlope
+ resInfo->scriptModule _desc.scriptModuleNumber
+ resInfo->entryScript _desc.sceneScriptEntrypointNumber
+ resInfo->preScript _desc.startScriptEntrypointNumber
+ resInfo->backgroundMusic _desc.musicRN
+ thisScene->ID currentSceneNumber()
+
+Interp.c
+========
+ dispatchThreads() executeThreads()
+ runThread() SThreadCompleteThread()
+ moduleList _scriptLUT
+ ModuleEntry->codeID _scriptLUT->script_rn
+ ModuleEntry->strID _scriptLUT->diag_list_rn
+ ModuleEntry->vtableID _scriptLUT->voice_lut_rn
+ threadBase.theAction threadVars[kVarAction]
+ threadBase.theObject threadVars[kVarObject]
+ threadBase.withObject threadVars[kVarWithObject]
+ threadBase.theActor threadVars[kVarActor]
+
+Actor.h
+=======
+ GOF_PROTAGONIST kProtagonist
+ GOF_FOLLOWER kFollower
+ GOF_CYCLE kCycle
+ GOF_FASTER kFaster
+ GOF_FASTEST kFastest
+ GOF_EXTENDED kExtended
+
+Actor.c
+=======
+ abortAllSpeeches() abortAllSpeeches()
+
+Main.c
+======
+ sceneIndexTable _scene->getSceneLUT()
+
+Main.h
+======
+BRIGHT_WHITE kITEColorBrightWhite
+WHITE_02 kITEColorWhite
+GREY_0A kITEColorGrey
+DK_GREY_0B kITEColorDarkGrey
+PITCH_BLACK kITEColorBlack
+RED_65 kITEColorRed
+BLUE_93 kITEColorBlue
+GREEB_BA kITEColorGreen
+
+Note that ScummVM's kITEColorLightGrey does not have any corresponding
+constant in the original SAGA engine. We use it for the ITE mouse cursor. See
+PtrData[] in Main.c and setCursor() in gfx.cpp
+
+Tile.h
+======
+ isoTile.height ISOTILE_ENTRY.tile_h
+ isoTile.attributes ISOTILE_ENTRY.mask_rule
+ isoTile.offset ISOTILE_ENTRY.tile_offset
+ isoTile.terrain_mask ISOTILE_ENTRY.terrain_mask
+ isoTile.fgd_bgd_attr ISOTILE_ENTRY.mask
+
+ tilePlatform.metaTile ISO_METATILE_ENTRY.mtile_n
+ tilePlatform.height ISO_METATILE_ENTRY.height
+ tilePlatform.highestPixel ISO_METATILE_ENTRY.highest_pixel
+ tilePlatform.vBits ISO_METATILE_ENTRY.v_bits
+ tilePlatform.uBits ISO_METATILE_ENTRY.u_bits
+
+Resource.h
+==========
+ PicHeader.width IMAGE_HEADER.width
+ PicHeader.height IMAGE_HEADER.height
+
+
+Process.c
+=========
+ mainPanelMode Interface::_inMainMode