aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/engines.mk5
-rw-r--r--engines/tinsel/actors.cpp896
-rw-r--r--engines/tinsel/actors.h126
-rw-r--r--engines/tinsel/anim.cpp404
-rw-r--r--engines/tinsel/anim.h72
-rw-r--r--engines/tinsel/background.cpp240
-rw-r--r--engines/tinsel/background.h165
-rw-r--r--engines/tinsel/bg.cpp189
-rw-r--r--engines/tinsel/cliprect.cpp312
-rw-r--r--engines/tinsel/cliprect.h76
-rw-r--r--engines/tinsel/config.cpp125
-rw-r--r--engines/tinsel/config.h72
-rw-r--r--engines/tinsel/coroutine.h124
-rw-r--r--engines/tinsel/cursor.cpp647
-rw-r--r--engines/tinsel/cursor.h56
-rw-r--r--engines/tinsel/debugger.cpp162
-rw-r--r--engines/tinsel/debugger.h49
-rw-r--r--engines/tinsel/detection.cpp278
-rw-r--r--engines/tinsel/dw.h119
-rw-r--r--engines/tinsel/effect.cpp134
-rw-r--r--engines/tinsel/events.cpp439
-rw-r--r--engines/tinsel/events.h84
-rw-r--r--engines/tinsel/faders.cpp175
-rw-r--r--engines/tinsel/faders.h55
-rw-r--r--engines/tinsel/film.h50
-rw-r--r--engines/tinsel/font.cpp96
-rw-r--r--engines/tinsel/font.h48
-rw-r--r--engines/tinsel/graphics.cpp437
-rw-r--r--engines/tinsel/graphics.h103
-rw-r--r--engines/tinsel/handle.cpp366
-rw-r--r--engines/tinsel/handle.h53
-rw-r--r--engines/tinsel/heapmem.cpp594
-rw-r--r--engines/tinsel/heapmem.h110
-rw-r--r--engines/tinsel/inventory.cpp4533
-rw-r--r--engines/tinsel/inventory.h143
-rw-r--r--engines/tinsel/mareels.cpp132
-rw-r--r--engines/tinsel/module.mk52
-rw-r--r--engines/tinsel/move.cpp1618
-rw-r--r--engines/tinsel/move.h43
-rw-r--r--engines/tinsel/multiobj.cpp533
-rw-r--r--engines/tinsel/multiobj.h124
-rw-r--r--engines/tinsel/music.cpp554
-rw-r--r--engines/tinsel/music.h118
-rw-r--r--engines/tinsel/object.cpp527
-rw-r--r--engines/tinsel/object.h207
-rw-r--r--engines/tinsel/palette.cpp424
-rw-r--r--engines/tinsel/palette.h157
-rw-r--r--engines/tinsel/pcode.cpp597
-rw-r--r--engines/tinsel/pcode.h158
-rw-r--r--engines/tinsel/pdisplay.cpp649
-rw-r--r--engines/tinsel/pid.h72
-rw-r--r--engines/tinsel/play.cpp507
-rw-r--r--engines/tinsel/polygons.cpp1805
-rw-r--r--engines/tinsel/polygons.h186
-rw-r--r--engines/tinsel/rince.cpp708
-rw-r--r--engines/tinsel/rince.h209
-rw-r--r--engines/tinsel/saveload.cpp472
-rw-r--r--engines/tinsel/savescn.cpp335
-rw-r--r--engines/tinsel/savescn.h103
-rw-r--r--engines/tinsel/scene.cpp308
-rw-r--r--engines/tinsel/scene.h79
-rw-r--r--engines/tinsel/sched.cpp344
-rw-r--r--engines/tinsel/sched.h100
-rw-r--r--engines/tinsel/scn.cpp80
-rw-r--r--engines/tinsel/scn.h68
-rw-r--r--engines/tinsel/scroll.cpp432
-rw-r--r--engines/tinsel/scroll.h77
-rw-r--r--engines/tinsel/serializer.h131
-rw-r--r--engines/tinsel/sound.cpp257
-rw-r--r--engines/tinsel/sound.h83
-rw-r--r--engines/tinsel/strres.cpp209
-rw-r--r--engines/tinsel/strres.h69
-rw-r--r--engines/tinsel/text.cpp279
-rw-r--r--engines/tinsel/text.h101
-rw-r--r--engines/tinsel/timers.cpp192
-rw-r--r--engines/tinsel/timers.h53
-rw-r--r--engines/tinsel/tinlib.cpp2980
-rw-r--r--engines/tinsel/tinlib.h41
-rw-r--r--engines/tinsel/tinsel.cpp1005
-rw-r--r--engines/tinsel/tinsel.h141
-rw-r--r--engines/tinsel/token.cpp129
-rw-r--r--engines/tinsel/token.h57
82 files changed, 29042 insertions, 0 deletions
diff --git a/engines/engines.mk b/engines/engines.mk
index cfb8e69f3e..4dba913173 100644
--- a/engines/engines.mk
+++ b/engines/engines.mk
@@ -97,6 +97,11 @@ DEFINES += -DENABLE_SWORD2=$(ENABLE_SWORD2)
MODULES += engines/sword2
endif
+ifdef ENABLE_TINSEL
+DEFINES += -DENABLE_TINSEL=$(ENABLE_TINSEL)
+MODULES += engines/tinsel
+endif
+
ifdef ENABLE_TOUCHE
DEFINES += -DENABLE_TOUCHE=$(ENABLE_TOUCHE)
MODULES += engines/touche
diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp
new file mode 100644
index 0000000000..e0689f1374
--- /dev/null
+++ b/engines/tinsel/actors.cpp
@@ -0,0 +1,896 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles things to do with actors, delegates much moving actor stuff.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/events.h"
+#include "tinsel/film.h" // for FREEL
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h" // INV_NOICON
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h" // for POBJECT
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/serializer.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+
+//----------------- LOCAL DEFINES --------------------
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** actor struct - one per actor */
+struct ACTOR_STRUC {
+ int32 masking; //!< type of actor masking
+ SCNHANDLE hActorId; //!< handle actor ID string index
+ SCNHANDLE hActorCode; //!< handle to actor script
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int LeadActorId = 0; // The lead actor
+
+static int NumActors = 0; // The total number of actors in the game
+
+struct ACTORINFO {
+ bool alive; // TRUE == alive
+ bool hidden; // TRUE == hidden
+ bool completed; // TRUE == script played out
+
+ int x, y, z;
+
+ int32 mtype; // DEFAULT(b'ground), MASK, ALWAYS
+ SCNHANDLE actorCode; // The actor's script
+
+ const FREEL *presReel; // the present reel
+ int presRnum; // the present reel number
+ SCNHANDLE presFilm; // the film that reel belongs to
+ OBJECT *presObj; // reference for position information
+ int presX, presY;
+
+ bool tagged; // actor tagged?
+ SCNHANDLE hTag; // handle to tag text
+ int tType; // e.g. TAG_Q1TO3
+
+ bool escOn;
+ int escEv;
+
+ COLORREF tColour; // Text colour
+
+ SCNHANDLE playFilm; // revert to this after talks
+ SCNHANDLE talkFilm; // this be deleted in the future!
+ SCNHANDLE latestFilm; // the last film ordered
+ bool talking;
+
+ int steps;
+
+};
+
+static ACTORINFO *actorInfo = 0;
+
+static COLORREF defaultColour = 0; // Text colour
+
+static bool bActorsOn = false;
+
+static int ti = 0;
+
+/**
+ * Called once at start-up time, and again at restart time.
+ * Registers the total number of actors in the game.
+ * @param num Chunk Id
+ */
+void RegisterActors(int num) {
+ if (actorInfo == NULL) {
+ // Store the total number of actors in the game
+ NumActors = num;
+
+ // Check we can save so many
+ assert(NumActors <= MAX_SAVED_ALIVES);
+
+ // Allocate RAM for actorInfo
+ // FIXME: For now, we always allocate MAX_SAVED_ALIVES blocks,
+ // as this makes the save/load code simpler
+ actorInfo = (ACTORINFO *)calloc(MAX_SAVED_ALIVES, sizeof(ACTORINFO));
+
+ // make sure memory allocated
+ if (actorInfo == NULL) {
+ error("Cannot allocate memory for actors");
+ }
+ } else {
+ // Check the total number of actors is still the same
+ assert(num == NumActors);
+
+ memset(actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO));
+ }
+
+ // All actors start off alive.
+ while (num--)
+ actorInfo[num].alive = true;
+}
+
+void FreeActors() {
+ if (actorInfo) {
+ free(actorInfo);
+ actorInfo = NULL;
+ }
+}
+
+/**
+ * Called from dec_lead(), i.e. normally once at start of master script.
+ * @param leadID Lead Id
+ */
+void setleadid(int leadID) {
+ LeadActorId = leadID;
+ actorInfo[leadID-1].mtype = ACT_MASK;
+}
+
+/**
+ * No comment.
+ */
+int LeadId(void) {
+ return LeadActorId;
+}
+
+struct ATP_INIT {
+ int id; // Actor number
+ USER_EVENT event; // Event
+ BUTEVENT bev; // Causal mouse event
+};
+
+/**
+ * Runs actor's glitter code.
+ */
+static void ActorTinselProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PINT_CONTEXT pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ ATP_INIT *atp = (ATP_INIT *)ProcessGetParamsSelf();
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, atp->bev); // May kill us if single click
+
+ // Run the Glitter code
+ assert(actorInfo[atp->id - 1].actorCode); // no code to run
+
+ _ctx->pic = InitInterpretContext(GS_ACTOR, actorInfo[atp->id - 1].actorCode, atp->event, NOPOLY, atp->id, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // If it gets here, actor's code has run to completion
+ actorInfo[atp->id - 1].completed = true;
+
+ CORO_END_CODE;
+}
+
+
+//---------------------------------------------------------------------------
+
+struct RATP_INIT {
+ PINT_CONTEXT pic;
+ int id; // Actor number
+};
+
+static void ActorRestoredProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PINT_CONTEXT pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ RATP_INIT *r = (RATP_INIT *)ProcessGetParamsSelf();
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pic = RestoreInterpretContext(r->pic);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // If it gets here, actor's code has run to completion
+ actorInfo[r->id - 1].completed = true;
+
+ CORO_END_CODE;
+}
+
+void RestoreActorProcess(int id, PINT_CONTEXT pic) {
+ RATP_INIT r = { pic, id };
+
+ CoroutineInstall(PID_TCODE, ActorRestoredProcess, &r, sizeof(r));
+}
+
+/**
+ * Starts up process to runs actor's glitter code.
+ * @param ano Actor Id
+ * @param event Event structure
+ * @param be ButEvent
+ */
+void actorEvent(int ano, USER_EVENT event, BUTEVENT be) {
+ ATP_INIT atp;
+
+ // Only if there is Glitter code associated with this actor.
+ if (actorInfo[ano - 1].actorCode) {
+ atp.id = ano;
+ atp.event = event;
+ atp.bev = be;
+ CoroutineInstall(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
+ }
+}
+
+/**
+ * Called at the start of each scene for each actor with a code block.
+ * @param as Actor structure
+ * @param bRunScript Flag for whether to run actor's script for the scene
+ */
+void StartActor(const ACTOR_STRUC *as, bool bRunScript) {
+ SCNHANDLE hActorId = FROM_LE_32(as->hActorId);
+
+ // Zero-out many things
+ actorInfo[hActorId - 1].hidden = false;
+ actorInfo[hActorId - 1].completed = false;
+ actorInfo[hActorId - 1].x = 0;
+ actorInfo[hActorId - 1].y = 0;
+ actorInfo[hActorId - 1].presReel = NULL;
+ actorInfo[hActorId - 1].presFilm = 0;
+ actorInfo[hActorId - 1].presObj = NULL;
+
+ // Store current scene's parameters for this actor
+ actorInfo[hActorId - 1].mtype = FROM_LE_32(as->masking);
+ actorInfo[hActorId - 1].actorCode = FROM_LE_32(as->hActorCode);
+
+ // Run actor's script for this scene
+ if (bRunScript) {
+ if (bActorsOn)
+ actorInfo[hActorId - 1].alive = true;
+
+ if (actorInfo[hActorId - 1].alive && FROM_LE_32(as->hActorCode))
+ actorEvent(hActorId, STARTUP, BE_NONE);
+ }
+}
+
+/**
+ * Called at the start of each scene. Start each actor with a code block.
+ * @param ah Scene handle
+ * @param numActors Number of actors
+ * @param bRunScript Flag for whether to run actor scene scripts
+ */
+void StartActors(SCNHANDLE ah, int numActors, bool bRunScript) {
+ int i;
+
+ // Only actors with code blocks got (x, y) re-initialised, so...
+ for (i = 0; i < NumActors; i++) {
+ actorInfo[i].x = actorInfo[i].y = 0;
+ actorInfo[i].mtype = 0;
+ }
+
+ const ACTOR_STRUC *as = (const ACTOR_STRUC *)LockMem(ah);
+ for (i = 0; i < numActors; i++, as++) {
+ StartActor(as, bRunScript);
+ }
+}
+
+/**
+ * Called between scenes, zeroises all actors.
+ */
+void DropActors(void) {
+ for (int i = 0; i < NumActors; i++) {
+ actorInfo[i].actorCode = 0; // No script
+ actorInfo[i].presReel = NULL; // No reel running
+ actorInfo[i].presFilm = 0; // ditto
+ actorInfo[i].presObj = NULL; // No object
+ actorInfo[i].x = 0; // No position
+ actorInfo[i].y = 0; // ditto
+
+ actorInfo[i].talkFilm = 0;
+ actorInfo[i].latestFilm = 0;
+ actorInfo[i].playFilm = 0;
+ actorInfo[i].talking = false;
+ }
+}
+
+/**
+ * Kill actors.
+ * @param ano Actor Id
+ */
+void DisableActor(int ano) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].alive = false; // Record as dead
+ actorInfo[ano - 1].x = actorInfo[ano - 1].y = 0;
+
+ // Kill off moving actor properly
+ pActor = GetMover(ano);
+ if (pActor)
+ KillMActor(pActor);
+}
+
+/**
+ * Enable actors.
+ * @param ano Actor Id
+ */
+void EnableActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ // Re-incarnate only if it's dead, or it's script ran to completion
+ if (!actorInfo[ano - 1].alive || actorInfo[ano - 1].completed) {
+ actorInfo[ano - 1].alive = true;
+ actorInfo[ano - 1].hidden = false;
+ actorInfo[ano - 1].completed = false;
+
+ // Re-run actor's script for this scene
+ if (actorInfo[ano-1].actorCode)
+ actorEvent(ano, STARTUP, BE_NONE);
+ }
+}
+
+/**
+ * Returns the aliveness (to coin a word) of the actor.
+ * @param ano Actor Id
+ */
+bool actorAlive(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].alive;
+}
+
+/**
+ * Define an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void Tag_Actor(int ano, SCNHANDLE tagtext, int tp) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano-1].tagged = true;
+ actorInfo[ano-1].hTag = tagtext;
+ actorInfo[ano-1].tType = tp;
+}
+
+/**
+ * Undefine an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void UnTagActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano-1].tagged = false;
+}
+
+/**
+ * Redefine an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void ReTagActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (actorInfo[ano-1].hTag)
+ actorInfo[ano-1].tagged = true;
+}
+
+/**
+ * Returns a tagged actor's tag type. e.g. TAG_Q1TO3
+ * @param ano Actor Id
+ */
+int TagType(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano-1].tType;
+}
+
+/**
+ * Returns handle to tagged actor's tag text
+ * @param ano Actor Id
+ */
+SCNHANDLE GetActorTag(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].hTag;
+}
+
+/**
+ * Called from TagProcess, FirstTaggedActor() resets the index, then
+ * NextTagged Actor is repeatedly called until the caller gets fed up
+ * or there are no more tagged actors to look at.
+ */
+void FirstTaggedActor(void) {
+ ti = 0;
+}
+
+/**
+ * Called from TagProcess, FirstTaggedActor() resets the index, then
+ * NextTagged Actor is repeatedly called until the caller gets fed up
+ * or there are no more tagged actors to look at.
+ */
+int NextTaggedActor(void) {
+ PMACTOR pActor;
+ bool hid;
+
+ do {
+ if (actorInfo[ti].tagged) {
+ pActor = GetMover(ti+1);
+ if (pActor)
+ hid = getMActorHideState(pActor);
+ else
+ hid = actorInfo[ti].hidden;
+
+ if (!hid) {
+ return ++ti;
+ }
+ }
+ } while (++ti < NumActors);
+
+ return 0;
+}
+
+/**
+ * Returns the masking type of the actor.
+ * @param ano Actor Id
+ */
+int32 actorMaskType(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].mtype;
+}
+
+/**
+ * Store/Return the currently stored co-ordinates of the actor.
+ * Delegate the task for moving actors.
+ * @param ano Actor Id
+ * @param x X position
+ * @param y Y position
+ */
+void storeActorPos(int ano, int x, int y) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].x = x;
+ actorInfo[ano - 1].y = y;
+}
+
+void storeActorSteps(int ano, int steps) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].steps = steps;
+}
+
+int getActorSteps(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].steps;
+}
+
+void storeActorZpos(int ano, int z) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].z = z;
+}
+
+
+void GetActorPos(int ano, int *x, int *y) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor
+
+ pActor = GetMover(ano);
+
+ if (pActor)
+ GetMActorPosition(pActor, x, y);
+ else {
+ *x = actorInfo[ano - 1].x;
+ *y = actorInfo[ano - 1].y;
+ }
+}
+
+/**
+ * Returns the position of the mid-top of the actor.
+ * Delegate the task for moving actors.
+ * @param ano Actor Id
+ * @param x Output x
+ * @param y Output y
+ */
+void GetActorMidTop(int ano, int *x, int *y) {
+ // Not used in JAPAN version
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor
+
+ pActor = GetMover(ano);
+
+ if (pActor)
+ GetMActorMidTopPosition(pActor, x, y);
+ else if (actorInfo[ano - 1].presObj) {
+ *x = (MultiLeftmost(actorInfo[ano - 1].presObj)
+ + MultiRightmost(actorInfo[ano - 1].presObj)) / 2;
+ *y = MultiHighest(actorInfo[ano - 1].presObj);
+ } else
+ GetActorPos(ano, x, y); // The best we can do!
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorLeft(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiLeftmost(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorRight(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiRightmost(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorTop(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiHighest(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ */
+int GetActorBottom(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiLowest(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Set actor hidden status to true.
+ * For a moving actor, actually hide it.
+ * @param ano Actor Id
+ */
+void HideActor(int ano) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ if (pActor)
+ hideMActor(pActor, 0);
+ else
+ actorInfo[ano - 1].hidden = true;
+}
+
+/**
+ * Hide an actor if it's a moving actor.
+ * @param ano Actor Id
+ * @param sf sf
+ */
+bool HideMovingActor(int ano, int sf) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ if (pActor) {
+ hideMActor(pActor, sf);
+ return true;
+ } else {
+ if (actorInfo[ano - 1].presObj != NULL)
+ MultiHideObject(actorInfo[ano - 1].presObj); // Hidee object
+ return false;
+ }
+}
+
+/**
+ * Unhide an actor if it's a moving actor.
+ * @param ano Actor Id
+ */
+void unHideMovingActor(int ano) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ assert(pActor); // not a moving actor
+
+ unhideMActor(pActor);
+}
+
+/**
+ * Called after a moving actor had been replaced by an splay().
+ * Moves the actor to where the splay() left it, and continues the
+ * actor's walk (if any) from the new co-ordinates.
+ */
+void restoreMovement(int ano) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ assert(pActor); // not a moving actor
+
+ if (pActor->objx == actorInfo[ano - 1].x && pActor->objy == actorInfo[ano - 1].y)
+ return;
+
+ pActor->objx = actorInfo[ano - 1].x;
+ pActor->objy = actorInfo[ano - 1].y;
+
+ if (pActor->actorObj)
+ SSetActorDest(pActor);
+}
+
+/**
+ * More properly should be called:
+ * 'store_actor_reel_and/or_film_and/or_object()'
+ */
+void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ pActor = GetMover(ano);
+
+ // Only store the reel and film for a moving actor if NOT called from MActorProcess()
+ // (MActorProcess() calls with reel=film=NULL, pobj not NULL)
+ if (!pActor
+ || !(reel == NULL && film == 0 && pobj != NULL)) {
+ actorInfo[ano - 1].presReel = reel; // Store reel
+ actorInfo[ano - 1].presRnum = reelnum; // Store reel number
+ actorInfo[ano - 1].presFilm = film; // Store film
+ actorInfo[ano - 1].presX = x;
+ actorInfo[ano - 1].presY = y;
+ }
+
+ // Only store the object for a moving actor if called from MActorProcess()
+ if (!pActor) {
+ actorInfo[ano - 1].presObj = pobj; // Store object
+ } else if (reel == NULL && film == 0 && pobj != NULL) {
+ actorInfo[ano - 1].presObj = pobj; // Store object
+ }
+}
+
+/**
+ * Return the present reel/film of the actor.
+ */
+const FREEL *actorReel(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].presReel; // the present reel
+}
+
+/***************************************************************************/
+
+void setActorPlayFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].playFilm = film;
+}
+
+SCNHANDLE getActorPlayFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].playFilm;
+}
+
+void setActorTalkFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].talkFilm = film;
+}
+
+SCNHANDLE getActorTalkFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].talkFilm;
+}
+
+void setActorTalking(int ano, bool tf) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].talking = tf;;
+}
+
+bool isActorTalking(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].talking;
+}
+
+void setActorLatestFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].latestFilm = film;
+ actorInfo[ano - 1].steps = 0;
+}
+
+SCNHANDLE getActorLatestFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].latestFilm;
+}
+
+/***************************************************************************/
+
+void updateActorEsc(int ano, bool escOn, int escEvent) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].escOn = escOn;
+ actorInfo[ano - 1].escEv = escEvent;
+}
+
+bool actorEsc(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].escOn;
+}
+
+int actorEev(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].escEv;
+}
+
+/**
+ * Guess what these do.
+ */
+int AsetZPos(OBJECT *pObj, int y, int32 z) {
+ int zPos;
+
+ z += z ? -1 : 0;
+
+ zPos = y + (z << 10);
+ MultiSetZPosition(pObj, zPos);
+ return zPos;
+}
+
+/**
+ * Guess what these do.
+ */
+void MAsetZPos(PMACTOR pActor, int y, int32 zFactor) {
+ if (!pActor->aHidden)
+ AsetZPos(pActor->actorObj, y, zFactor);
+}
+
+/**
+ * Stores actor's attributes.
+ * Currently only the speech colours.
+ */
+void storeActorAttr(int ano, int r1, int g1, int b1) {
+ assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number
+
+ if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure
+ if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits
+ if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // }
+
+ if (ano == -1)
+ defaultColour = RGB(r1, g1, b1);
+ else
+ actorInfo[ano - 1].tColour = RGB(r1, g1, b1);
+}
+
+/**
+ * Get the actor's stored speech colour.
+ * @param ano Actor Id
+ */
+COLORREF getActorTcol(int ano) {
+ // Not used in JAPAN version
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (actorInfo[ano - 1].tColour)
+ return actorInfo[ano - 1].tColour;
+ else
+ return defaultColour;
+}
+
+/**
+ * Store relevant information pertaining to currently existing actors.
+ */
+int SaveActors(PSAVED_ACTOR sActorInfo) {
+ int i, j;
+
+ for (i = 0, j = 0; i < NumActors; i++) {
+ if (actorInfo[i].presObj != NULL) {
+ assert(j < MAX_SAVED_ACTORS); // Saving too many actors
+
+// sActorInfo[j].hidden = actorInfo[i].hidden;
+ sActorInfo[j].bAlive = actorInfo[i].alive;
+// sActorInfo[j].x = (short)actorInfo[i].x;
+// sActorInfo[j].y = (short)actorInfo[i].y;
+ sActorInfo[j].z = (short)actorInfo[i].z;
+// sActorInfo[j].presReel = actorInfo[i].presReel;
+ sActorInfo[j].presRnum = (short)actorInfo[i].presRnum;
+ sActorInfo[j].presFilm = actorInfo[i].presFilm;
+ sActorInfo[j].presX = (short)actorInfo[i].presX;
+ sActorInfo[j].presY = (short)actorInfo[i].presY;
+ sActorInfo[j].actorID = (short)(i+1);
+ j++;
+ }
+ }
+
+ return j;
+}
+
+void setactorson(void) {
+ bActorsOn = true;
+}
+
+void ActorsLife(int ano, bool bAlive) {
+ assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number
+
+ actorInfo[ano-1].alive = bAlive;
+}
+
+
+void syncAllActorsAlive(Serializer &s) {
+ for (int i = 0; i < MAX_SAVED_ALIVES; i++) {
+ s.syncAsByte(actorInfo[i].alive);
+ s.syncAsByte(actorInfo[i].tagged);
+ s.syncAsByte(actorInfo[i].tType);
+ s.syncAsUint32LE(actorInfo[i].hTag);
+ }
+}
+
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/actors.h b/engines/tinsel/actors.h
new file mode 100644
index 0000000000..7707dd636d
--- /dev/null
+++ b/engines/tinsel/actors.h
@@ -0,0 +1,126 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Prototypes of actor functions
+ */
+
+#ifndef TINSEL_ACTOR_H // prevent multiple includes
+#define TINSEL_ACTOR_H
+
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/events.h" // for USER_EVENT
+#include "tinsel/palette.h" // for COLORREF
+
+namespace Tinsel {
+
+struct FREEL;
+struct INT_CONTEXT;
+struct MACTOR;
+struct OBJECT;
+
+
+/*----------------------------------------------------------------------*/
+
+void RegisterActors(int num);
+void FreeActors(void);
+void setleadid(int rid);
+int LeadId(void);
+void StartActors(SCNHANDLE ah, int numActors, bool bRunScript);
+void DropActors(void); // No actor reels running
+void DisableActor(int actor);
+void EnableActor(int actor);
+void Tag_Actor(int ano, SCNHANDLE tagtext, int tp);
+void UnTagActor(int ano);
+void ReTagActor(int ano);
+int TagType(int ano);
+bool actorAlive(int ano);
+int32 actorMaskType(int ano);
+void GetActorPos(int ano, int *x, int *y);
+void SetActorPos(int ano, int x, int y);
+void GetActorMidTop(int ano, int *x, int *y);
+int GetActorLeft(int ano);
+int GetActorRight(int ano);
+int GetActorTop(int ano);
+int GetActorBottom(int ano);
+void HideActor(int ano);
+bool HideMovingActor(int id, int sf);
+void unHideMovingActor(int id);
+void restoreMovement(int id);
+void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y);
+const FREEL *actorReel(int ano);
+SCNHANDLE actorFilm(int ano);
+
+void setActorPlayFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorPlayFilm(int ano);
+void setActorTalkFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorTalkFilm(int ano);
+void setActorTalking(int ano, bool tf);
+bool isActorTalking(int ano);
+void setActorLatestFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorLatestFilm(int ano);
+
+void updateActorEsc(int ano, bool escOn, int escEv);
+bool actorEsc(int ano);
+int actorEev(int ano);
+void storeActorPos(int ano, int x, int y);
+void storeActorSteps(int ano, int steps);
+int getActorSteps(int ano);
+void storeActorZpos(int ano, int z);
+SCNHANDLE GetActorTag(int ano);
+void FirstTaggedActor(void);
+int NextTaggedActor(void);
+int AsetZPos(OBJECT *pObj, int y, int32 zFactor);
+void MAsetZPos(MACTOR *pActor, int y, int32 zFactor);
+void actorEvent(int ano, USER_EVENT event, BUTEVENT be);
+
+void storeActorAttr(int ano, int r1, int g1, int b1);
+COLORREF getActorTcol(int ano);
+
+void setactorson(void);
+
+void ActorsLife(int id, bool bAlive);
+
+/*----------------------------------------------------------------------*/
+
+struct SAVED_ACTOR {
+ short actorID;
+ short z;
+ bool bAlive;
+ SCNHANDLE presFilm; //!< the film that reel belongs to
+ short presRnum; //!< the present reel number
+ short presX, presY;
+};
+typedef SAVED_ACTOR *PSAVED_ACTOR;
+
+int SaveActors(PSAVED_ACTOR sActorInfo);
+
+
+void RestoreActorProcess(int id, INT_CONTEXT *pic);
+
+
+/*----------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_ACTOR_H */
diff --git a/engines/tinsel/anim.cpp b/engines/tinsel/anim.cpp
new file mode 100644
index 0000000000..266c39bca8
--- /dev/null
+++ b/engines/tinsel/anim.cpp
@@ -0,0 +1,404 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains utilities to handle object animation.
+ */
+
+#include "tinsel/anim.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/sched.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+/** Animation script commands */
+enum {
+ ANI_END = 0, //!< end of animation script
+ ANI_JUMP = 1, //!< animation script jump
+ ANI_HFLIP = 2, //!< flip animated object horizontally
+ ANI_VFLIP = 3, //!< flip animated object vertically
+ ANI_HVFLIP = 4, //!< flip animated object in both directions
+ ANI_ADJUSTX = 5, //!< adjust animated object x animation point
+ ANI_ADJUSTY = 6, //!< adjust animated object y animation point
+ ANI_ADJUSTXY = 7, //!< adjust animated object x & y animation points
+ ANI_NOSLEEP = 8, //!< do not sleep for this frame
+ ANI_CALL = 9, //!< call routine
+ ANI_HIDE = 10 //!< hide animated object
+};
+
+/** animation script command possibilities */
+union ANI_SCRIPT {
+ int32 op; //!< treat as an opcode or operand
+ uint32 hFrame; //!< treat as a animation frame handle
+};
+
+/**
+ * Advance to next frame routine.
+ * @param pAnim Animation data structure
+ */
+SCRIPTSTATE DoNextFrame(PANIM pAnim) {
+ // get a pointer to the script
+ const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript);
+
+ while (1) { // repeat until a real image
+
+ switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) {
+ case ANI_END: // end of animation script
+
+ // move to next opcode
+ pAnim->scriptIndex++;
+
+ // indicate script has finished
+ return ScriptFinished;
+
+ case ANI_JUMP: // do animation jump
+
+ // move to jump address
+ pAnim->scriptIndex++;
+
+ // jump to new frame position
+ pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // go fetch a real image
+ break;
+
+ case ANI_HFLIP: // flip animated object horizontally
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_VFLIP: // flip animated object vertically
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiVerticalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_HVFLIP: // flip animated object in both directions
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ MultiVerticalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTX: // adjust animated object x animation point
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTY: // adjust animated object y animation point
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op));
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTXY: // adjust animated object x & y animation points
+ {
+ int x, y;
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+ x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+ y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ MultiAdjustXY(pAnim->pObject, x, y);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+ }
+
+ case ANI_NOSLEEP: // do not sleep for this frame
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // indicate not to sleep
+ return ScriptNoSleep;
+
+ case ANI_CALL: // call routine
+
+ // move to function address
+ pAnim->scriptIndex++;
+
+ // make function call
+
+ // REMOVED BUGGY CODE
+ // pFunc is a function pointer that's part of a union and is assumed to be 32-bits.
+ // There is no known place where a function pointer is stored inside the animation
+ // scripts, something which wouldn't have worked anyway. Having played through the
+ // entire game, there hasn't been any occurence of this case, so just error out here
+ // in case we missed something (highly unlikely though)
+ error("ANI_CALL opcode encountered! Please report this error to the ScummVM team");
+ //(*pAni[pAnim->scriptIndex].pFunc)(pAnim);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_HIDE: // hide animated object
+
+ MultiHideObject(pAnim->pObject);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // dont skip a sleep
+ return ScriptSleep;
+
+ default: // must be an actual animation frame handle
+
+ // set objects new animation frame
+ pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame);
+
+ // re-shape the object
+ MultiReshape(pAnim->pObject);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // dont skip a sleep
+ return ScriptSleep;
+ }
+ }
+}
+
+/**
+ * Init a ANIM structure for single stepping through a animation script.
+ * @param pAnim Animation data structure
+ * @param pAniObj Object to animate
+ * @param hNewScript Script of multipart frames
+ * @param aniSpeed Sets speed of animation in frames
+ */
+void InitStepAnimScript(PANIM pAnim, OBJECT *pAniObj, SCNHANDLE hNewScript, int aniSpeed) {
+ OBJECT *pObj; // multi-object list iterator
+
+ pAnim->aniDelta = 1; // will animate on next call to NextAnimRate
+ pAnim->pObject = pAniObj; // set object to animate
+ pAnim->hScript = hNewScript; // set animation script
+ pAnim->scriptIndex = 0; // start of script
+ pAnim->aniRate = aniSpeed; // set speed of animation
+
+ // reset flip flags for the object - let the script do the flipping
+ for (pObj = pAniObj; pObj != NULL; pObj = pObj->pSlave) {
+ AnimateObjectFlags(pObj, pObj->flags & ~(DMA_FLIPH | DMA_FLIPV),
+ pObj->hImg);
+ }
+}
+
+/**
+ * Execute the next command in a animation script.
+ * @param pAnim Animation data structure
+ */
+SCRIPTSTATE StepAnimScript(PANIM pAnim) {
+ SCRIPTSTATE state;
+
+ if (--pAnim->aniDelta == 0) {
+ // re-init animation delta counter
+ pAnim->aniDelta = pAnim->aniRate;
+
+ // move to next frame
+ while ((state = DoNextFrame(pAnim)) == ScriptNoSleep)
+ ;
+
+ return state;
+ }
+
+ // indicate calling task should sleep
+ return ScriptSleep;
+}
+
+/**
+ * Skip the specified number of frames.
+ * @param pAnim Animation data structure
+ * @param numFrames Number of frames to skip
+ */
+void SkipFrames(PANIM pAnim, int numFrames) {
+ // get a pointer to the script
+ const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript);
+
+ if (numFrames <= 0)
+ // do nothing
+ return;
+
+ while (1) { // repeat until a real image
+
+ switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) {
+ case ANI_END: // end of animation script
+ // going off the end is probably a error
+ error("SkipFrames(): formally 'assert(0)!'");
+ break;
+
+ case ANI_JUMP: // do animation jump
+
+ // move to jump address
+ pAnim->scriptIndex++;
+
+ // jump to new frame position
+ pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+ break;
+
+ case ANI_HFLIP: // flip animated object horizontally
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ break;
+
+ case ANI_VFLIP: // flip animated object vertically
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiVerticalFlip(pAnim->pObject);
+ break;
+
+ case ANI_HVFLIP: // flip animated object in both directions
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ MultiVerticalFlip(pAnim->pObject);
+ break;
+
+ case ANI_ADJUSTX: // adjust animated object x animation point
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0);
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_ADJUSTY: // adjust animated object y animation point
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op));
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_ADJUSTXY: // adjust animated object x & y animation points
+ {
+ int x, y;
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+ x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+ y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ MultiAdjustXY(pAnim->pObject, x, y);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ break;
+ }
+
+ case ANI_NOSLEEP: // do not sleep for this frame
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_CALL: // call routine
+
+ // skip function address
+ pAnim->scriptIndex += 2;
+ break;
+
+ case ANI_HIDE: // hide animated object
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ default: // must be an actual animation frame handle
+
+ // one less frame
+ if (numFrames-- > 0) {
+ // next opcode
+ pAnim->scriptIndex++;
+ } else {
+ // set objects new animation frame
+ pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame);
+
+ // re-shape the object
+ MultiReshape(pAnim->pObject);
+
+ // we have skipped to the correct place
+ return;
+ }
+ break;
+ }
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/anim.h b/engines/tinsel/anim.h
new file mode 100644
index 0000000000..a6ac574350
--- /dev/null
+++ b/engines/tinsel/anim.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Object animation definitions
+ */
+
+#ifndef TINSEL_ANIM_H // prevent multiple includes
+#define TINSEL_ANIM_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+struct OBJECT;
+
+/** animation structure */
+struct ANIM {
+ int aniRate; //!< animation speed
+ int aniDelta; //!< animation speed delta counter
+ OBJECT *pObject; //!< object to animate (assumed to be multi-part)
+ uint32 hScript; //!< animation script handle
+ int scriptIndex; //!< current position in animation script
+};
+typedef ANIM *PANIM;
+
+
+/*----------------------------------------------------------------------*\
+|* Anim Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+/** states for DoNextFrame */
+enum SCRIPTSTATE {ScriptFinished, ScriptNoSleep, ScriptSleep};
+
+SCRIPTSTATE DoNextFrame( // Execute the next animation frame of a animation script
+ ANIM *pAnim); // animation data structure
+
+void InitStepAnimScript( // Init a ANIM struct for single stepping through a animation script
+ ANIM *pAnim, // animation data structure
+ OBJECT *pAniObj, // object to animate
+ SCNHANDLE hNewScript, // handle to script of multipart frames
+ int aniSpeed); // sets speed of animation in frames
+
+SCRIPTSTATE StepAnimScript( // Execute the next command in a animation script
+ ANIM *pAnim); // animation data structure
+
+void SkipFrames( // Skip the specified number of frames
+ ANIM *pAnim, // animation data structure
+ int numFrames); // number of frames to skip
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_ANIM_H
diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp
new file mode 100644
index 0000000000..05d36e9412
--- /dev/null
+++ b/engines/tinsel/background.cpp
@@ -0,0 +1,240 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Background handling code.
+ */
+
+#include "tinsel/background.h"
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h"
+#include "tinsel/sched.h" // process sheduler defs
+#include "tinsel/object.h"
+#include "tinsel/pid.h" // process identifiers
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+// screen clipping rectangle
+Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+// current background
+BACKGND *pCurBgnd = NULL;
+
+// scroll flag - when set scrolling and velocity additions are paused
+bool bNoScroll;
+
+/**
+ * Called to initialise a background.
+ * @param pBgnd Pointer to data struct for current background
+ */
+
+void InitBackground(BACKGND *pBgnd) {
+ int i; // playfield counter
+ PPLAYFIELD pPlayfield; // pointer to current playfield
+
+ // set current background
+ pCurBgnd = pBgnd;
+
+ // init background sky colour
+ SetBgndColour(pBgnd->rgbSkyColour);
+
+ // start of playfield array
+ pPlayfield = pBgnd->fieldArray;
+
+ // for each background playfield
+ for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) {
+ // init playfield pos
+ pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x);
+ pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y);
+
+ // no scrolling
+ pPlayfield->fieldXvel = intToFrac(0);
+ pPlayfield->fieldYvel = intToFrac(0);
+
+ // clear playfield display list
+ pPlayfield->pDispList = NULL;
+
+ // clear playfield moved flag
+ pPlayfield->bMoved = false;
+ }
+}
+
+/**
+ * Sets the xy position of the specified playfield in the current background.
+ * @param which Which playfield
+ * @param newXpos New x position
+ * @param newYpos New y position
+ */
+
+void PlayfieldSetPos(int which, int newXpos, int newYpos) {
+ PPLAYFIELD pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // set new integer position
+ pPlayfield->fieldX = intToFrac(newXpos);
+ pPlayfield->fieldY = intToFrac(newYpos);
+
+ // set moved flag
+ pPlayfield->bMoved = true;
+}
+
+/**
+ * Returns the xy position of the specified playfield in the current background.
+ * @param which Which playfield
+ * @param pXpos Returns current x position
+ * @param pYpos Returns current y position
+ */
+
+void PlayfieldGetPos(int which, int *pXpos, int *pYpos) {
+ PPLAYFIELD pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // get current integer position
+ *pXpos = fracToInt(pPlayfield->fieldX);
+ *pYpos = fracToInt(pPlayfield->fieldY);
+}
+
+/**
+ * Returns the display list for the specified playfield.
+ * @param which Which playfield
+ */
+
+OBJECT *GetPlayfieldList(int which) {
+ PPLAYFIELD pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // return the display list pointer for this playfield
+ return (OBJECT *)&pPlayfield->pDispList;
+}
+
+/**
+ * Draws all the playfield object lists for the current background.
+ * The playfield velocity is added to the playfield position in order
+ * to scroll each playfield before it is drawn.
+ */
+
+void DrawBackgnd(void) {
+ int i; // playfield counter
+ PPLAYFIELD pPlay; // playfield pointer
+ int prevX, prevY; // save interger part of position
+ Common::Point ptWin; // window top left
+
+ if (pCurBgnd == NULL)
+ return; // no current background
+
+ // scroll each background playfield
+ for (i = 0; i < pCurBgnd->numPlayfields; i++) {
+ // get pointer to correct playfield
+ pPlay = pCurBgnd->fieldArray + i;
+
+ // save integer part of position
+ prevX = fracToInt(pPlay->fieldX);
+ prevY = fracToInt(pPlay->fieldY);
+
+ if (!bNoScroll) {
+ // update scrolling
+ pPlay->fieldX += pPlay->fieldXvel;
+ pPlay->fieldY += pPlay->fieldYvel;
+
+ // convert fixed point window pos to a int
+ ptWin.x = fracToInt(pPlay->fieldX);
+ ptWin.y = fracToInt(pPlay->fieldY);
+
+ // set the moved flag if the playfield has moved
+ if (prevX != ptWin.x || prevY != ptWin.y)
+ pPlay->bMoved = true;
+ }
+
+ // sort the display list for this background - just in case somebody has changed object Z positions
+ SortObjectList((OBJECT *)&pPlay->pDispList);
+
+ // generate clipping rects for all objects that have moved etc.
+ FindMovingObjects((OBJECT *)&pPlay->pDispList, &ptWin,
+ &pPlay->rcClip, bNoScroll, pPlay->bMoved);
+
+ // clear playfield moved flag
+ pPlay->bMoved = false;
+ }
+
+ // merge the clipping rectangles
+ MergeClipRect();
+
+ // redraw all playfields within the clipping rectangles
+ const RectList &clipRects = GetClipRects();
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ // clear the clip rectangle on the virtual screen
+ // for each background playfield
+ for (i = 0; i < pCurBgnd->numPlayfields; i++) {
+ Common::Rect rcPlayClip; // clip rect for this playfield
+
+ // get pointer to correct playfield
+ pPlay = pCurBgnd->fieldArray + i;
+
+ // convert fixed point window pos to a int
+ ptWin.x = fracToInt(pPlay->fieldX);
+ ptWin.y = fracToInt(pPlay->fieldY);
+
+ if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r))
+ // redraw all objects within this clipping rect
+ UpdateClipRect((OBJECT *)&pPlay->pDispList,
+ &ptWin, &rcPlayClip);
+ }
+ }
+
+ // transfer any new palettes to the video DAC
+ PalettesToVideoDAC();
+
+ // update the screen within the clipping rectangles
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ UpdateScreenRect(*r);
+ }
+
+ // delete all the clipping rectangles
+ ResetClipRect();
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h
new file mode 100644
index 0000000000..7409fd0785
--- /dev/null
+++ b/engines/tinsel/background.h
@@ -0,0 +1,165 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used for handling backgrounds
+ */
+
+#ifndef TINSEL_BACKGND_H // prevent multiple includes
+#define TINSEL_BACKGND_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/palette.h" // palette definitions
+#include "common/frac.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+
+/** Scrolling padding. Needed because scroll process does not normally run on every frame */
+enum {
+ SCROLLX_PAD = 64,
+ SCROLLY_PAD = 64
+};
+
+/** When module BLK_INFO list is this long, switch from a binary to linear search */
+#define LINEAR_SEARCH 5
+
+
+/** structure of each individual background block */
+struct BLOCK {
+ short blkWidth; //!< block width
+ short blkHeight; //!< block height
+ SCNHANDLE hBlkBits; //!< block bitmap handle
+};
+typedef BLOCK *PBLOCK;
+
+
+/** structure to define position of blocks, which block and which palette */
+struct BLK_INFO {
+ uint16 wBlkX; //!< x position of this block
+ uint16 wBlkY; //!< y position of this block
+ uint16 wBlkZ; //!< z position of this block
+ uint8 byBlkFlags; //!< block flags used for drawing object associated with this block
+ uint8 byBlkPal; //!< which palette - index into "blkPals" for this block
+ int32 blkIndex; //!< which block - index into "blocks"
+};
+typedef BLK_INFO *PBLK_INFO;
+
+
+/** background module structure - a module is a container for blocks */
+struct MODULE {
+ int modWidth; //!< width of module
+ int modHeight; //!< height of module
+ int numBlocks; //!< number of blocks in this module
+ BLOCK *blocks; //!< pointer to array of all blocks used by this module
+ uint32 *blkPals; //!< pointer to array of all palette handles used by the blocks in this module
+ BLK_INFO *blkInfo; //!< pointer to array of which block goes where
+ //!< NOTE: This array must be sorted on x position
+};
+typedef MODULE *PMODULE;
+
+
+/**
+ * background module node structure - links a playfields modules together
+ * and specifies each module position. It is done this way so that modules
+ * are position independent and can be reused within a playfield
+ */
+struct MOD_NODE {
+ MOD_NODE *pNext; //!< next module node
+ MODULE *pModule; //!< pointer to actual module definition
+ char *onDispList; //!< pointer to modules (block on object list) flags - should alloc 1 byte per block
+ Common::Point ptModPos; //!< module world start position
+};
+typedef MOD_NODE *PMOD_NODE;
+
+
+/** background playfield structure - a playfield is a container for modules */
+struct PLAYFIELD {
+ MOD_NODE *pModNode; //!< head of module node chain for this playfield
+ OBJECT *pDispList; //!< object display list for this playfield
+ frac_t fieldX; //!< current world x position of playfield
+ frac_t fieldY; //!< current world y position of playfield
+ frac_t fieldXvel; //!< current x velocity of playfield
+ frac_t fieldYvel; //!< current y velocity of playfield
+ Common::Rect rcClip; //!< clip rectangle for this playfield
+ bool bMoved; //!< set when playfield has moved
+};
+typedef PLAYFIELD *PPLAYFIELD;
+
+/** multi-playfield background structure - a backgnd is a container of playfields */
+struct BACKGND {
+ COLORREF rgbSkyColour; //!< background sky colour
+ Common::Point ptInitWorld; //!< initial world position
+ Common::Rect rcScrollLimits; //!< scroll limits
+ int refreshRate; //!< background update process refresh rate
+ frac_t *pXscrollTable; //!< pointer to x direction scroll table for this background
+ frac_t *pYscrollTable; //!< pointer to y direction scroll table for this background
+ int numPlayfields; //!< number of playfields for this background
+ PLAYFIELD *fieldArray; //!< pointer to array of all playfields for this background
+ bool bAutoErase; //!< when set - screen is cleared before anything is plotted (unused)
+};
+
+
+/** screen clipping rect */
+extern Common::Rect rcScreen;
+
+/** scroll flag - when set scrolling and velocity additions are paused */
+extern bool bNoScroll;
+
+
+/*----------------------------------------------------------------------*\
+|* Background Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void InitBackground( // called to initialise a background
+ BACKGND *pBgnd); // pointer to data struct for current background
+
+void StopBgndScrolling(void); // Stops all background playfields from scrolling
+
+void PlayfieldSetPos( // Sets the xy position of the specified playfield in the current background
+ int which, // which playfield
+ int newXpos, // new x position
+ int newYpos); // new y position
+
+void PlayfieldGetPos( // Returns the xy position of the specified playfield in the current background
+ int which, // which playfield
+ int *pXpos, // returns current x position
+ int *pYpos); // returns current y position
+
+OBJECT *GetPlayfieldList( // Returns the display list for the specified playfield
+ int which); // which playfield
+
+void KillPlayfieldList( // Kills all the objects on the display list for the specified playfield
+ int which); // which playfield
+
+void DrawBackgnd(void); // Draws all playfields for the current background
+
+void RedrawBackgnd(void); // Completely redraws all the playfield object lists for the current background
+
+SCNHANDLE BackPal(void);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_BACKGND_H
diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp
new file mode 100644
index 0000000000..cf7e18dc08
--- /dev/null
+++ b/engines/tinsel/bg.cpp
@@ -0,0 +1,189 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Plays the background film of a scene.
+ */
+
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/dw.h"
+#include "tinsel/faders.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pcode.h" // CONTROL_STARTOFF
+#include "tinsel/pid.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h" // For control()
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static SCNHANDLE BackPalette = 0; // Background's palette
+static OBJECT *pBG = 0; // The main picture's object.
+static int BGspeed = 0;
+static SCNHANDLE BgroundHandle = 0; // Current scene handle - stored in case of Save_Scene()
+static bool DoFadeIn = false;
+static ANIM thisAnim; // used by BGmainProcess()
+
+/**
+ * BackPal
+ */
+SCNHANDLE BackPal(void) {
+ return BackPalette;
+}
+
+/**
+ * SetDoFadeIn
+*/
+void SetDoFadeIn(bool tf) {
+ DoFadeIn = tf;
+}
+
+/**
+ * Called before scene change.
+ */
+void DropBackground(void) {
+ pBG = NULL; // No background
+ BackPalette = 0; // No background palette
+}
+
+/**
+ * Return the width of the current background.
+ */
+int BackgroundWidth(void) {
+ assert(pBG);
+ return MultiRightmost(pBG) + 1;
+}
+
+/**
+ * Return the height of the current background.
+ */
+int BackgroundHeight(void) {
+ assert(pBG);
+ return MultiLowest(pBG) + 1;
+}
+
+/**
+ * Run main animation that comprises the scene background.
+ */
+static void BGmainProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+
+ // get the stuff copied to process when it was created
+ pfr = (const FREEL *)ProcessGetParamsSelf();
+
+ if (pBG == NULL) {
+ /*** At start of scene ***/
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj));
+
+ // Initialise and insert the object, and initialise its script.
+ pBG = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG);
+ InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed);
+
+ if (DoFadeIn) {
+ FadeInFast(NULL);
+ DoFadeIn = false;
+ }
+
+ while (StepAnimScript(&thisAnim) != ScriptFinished)
+ CORO_SLEEP(1);
+
+ error("Background animation has finished!");
+ } else {
+ // New background during scene
+
+ // Just re-initialise the script.
+ InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed);
+ StepAnimScript(&thisAnim);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * setBackPal()
+ */
+void setBackPal(SCNHANDLE hPal) {
+ BackPalette = hPal;
+
+ fettleFontPal(BackPalette);
+ CreateTranslucentPalette(BackPalette);
+}
+
+void ChangePalette(SCNHANDLE hPal) {
+ SwapPalette(FindPalette(BackPalette), hPal);
+
+ setBackPal(hPal);
+}
+
+/**
+ * Given the scene background film, extracts the palette handle for
+ * everything else's use, then starts a display process for each reel
+ * in the film.
+ * @param bfilm Scene background film
+ */
+void startupBackground(SCNHANDLE bfilm) {
+ const FILM *pfilm;
+ PIMAGE pim;
+
+ BgroundHandle = bfilm; // Save handle in case of Save_Scene()
+
+ pim = GetImageFromFilm(bfilm, 0, NULL, NULL, &pfilm);
+ setBackPal(FROM_LE_32(pim->hImgPal));
+
+ // Extract the film speed
+ BGspeed = ONE_SECOND / FROM_LE_32(pfilm->frate);
+
+ if (pBG == NULL)
+ control(CONTROL_STARTOFF); // New feature - start scene with control off
+
+ // Start display process for each reel in the film
+ assert(FROM_LE_32(pfilm->numreels) == 1); // Multi-reeled backgrounds withdrawn
+ CoroutineInstall(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL));
+}
+
+/**
+ * Return the current scene handle.
+ */
+SCNHANDLE GetBgroundHandle(void) {
+ return BgroundHandle;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp
new file mode 100644
index 0000000000..632a84f723
--- /dev/null
+++ b/engines/tinsel/cliprect.cpp
@@ -0,0 +1,312 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the clipping rectangle code.
+ */
+
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h" // normal object drawing
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+
+namespace Tinsel {
+
+/** list of all clip rectangles */
+static RectList s_rectList;
+
+/**
+ * Resets the clipping rectangle allocator.
+ */
+void ResetClipRect(void) {
+ s_rectList.clear();
+}
+
+/**
+ * Allocate a clipping rectangle from the free list.
+ * @param pClip clip rectangle dimensions to allocate
+ */
+void AddClipRect(const Common::Rect &pClip) {
+ s_rectList.push_back(pClip);
+}
+
+const RectList &GetClipRects() {
+ return s_rectList;
+}
+
+/**
+ * Creates the intersection of two rectangles.
+ * Returns True if there is a intersection.
+ * @param pDest Pointer to destination rectangle that is to receive the intersection
+ * @param pSrc1 Pointer to a source rectangle
+ * @param pSrc2 Pointer to a source rectangle
+ */
+bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ pDest.left = (pSrc1.left > pSrc2.left) ? pSrc1.left : pSrc2.left;
+ pDest.top = (pSrc1.top > pSrc2.top) ? pSrc1.top : pSrc2.top;
+ pDest.right = (pSrc1.right < pSrc2.right) ? pSrc1.right : pSrc2.right;
+ pDest.bottom = (pSrc1.bottom < pSrc2.bottom) ? pSrc1.bottom : pSrc2.bottom;
+
+ return (pDest.right > pDest.left && pDest.bottom > pDest.top);
+}
+
+/**
+ * Creates the union of two rectangles.
+ * Returns True if there is a union.
+ * @param pDest destination rectangle that is to receive the new union
+ * @param pSrc1 a source rectangle
+ * @param pSrc2 a source rectangle
+ */
+bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ pDest.left = (pSrc1.left < pSrc2.left) ? pSrc1.left : pSrc2.left;
+ pDest.top = (pSrc1.top < pSrc2.top) ? pSrc1.top : pSrc2.top;
+ pDest.right = (pSrc1.right > pSrc2.right) ? pSrc1.right : pSrc2.right;
+ pDest.bottom = (pSrc1.bottom > pSrc2.bottom) ? pSrc1.bottom : pSrc2.bottom;
+
+ return (pDest.right > pDest.left && pDest.bottom > pDest.top);
+}
+
+/**
+ * Check if the two rectangles are next to each other.
+ * @param pSrc1 a source rectangle
+ * @param pSrc2 a source rectangle
+ */
+static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ Common::Rect pDest;
+
+ pDest.left = (pSrc1.left > pSrc2.left) ? pSrc1.left : pSrc2.left;
+ pDest.top = (pSrc1.top > pSrc2.top) ? pSrc1.top : pSrc2.top;
+ pDest.right = (pSrc1.right < pSrc2.right) ? pSrc1.right : pSrc2.right;
+ pDest.bottom = (pSrc1.bottom < pSrc2.bottom) ? pSrc1.bottom : pSrc2.bottom;
+
+ return (pDest.right >= pDest.left && pDest.bottom >= pDest.top);
+}
+
+/**
+ * Adds velocities and creates clipping rectangles for all the
+ * objects that have moved on the specified object list.
+ * @param pObjList Playfield display list to draw
+ * @param pWin Playfield window top left position
+ * @param pClip Playfield clipping rectangle
+ * @param bNoVelocity When reset, objects pos is updated with velocity
+ * @param bScrolled) When set, playfield has scrolled
+ */
+void FindMovingObjects(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) {
+ OBJECT *pObj; // object list traversal pointer
+
+ for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) {
+ if (!bNoVelocity) {
+ // we want to add velocities to objects position
+
+ if (bScrolled) {
+ // this playfield has scrolled
+
+ // indicate change
+ pObj->flags |= DMA_CHANGED;
+ }
+ }
+
+ if ((pObj->flags & DMA_CHANGED) || // object changed
+ HasPalMoved(pObj->pPal)) { // or palette moved
+ // object has changed in some way
+
+ Common::Rect rcClip; // objects clipped bounding rectangle
+ Common::Rect rcObj; // objects bounding rectangle
+
+ // calc intersection of objects previous bounding rectangle
+ // NOTE: previous position is in screen co-ords
+ if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) {
+ // previous position is within clipping rect
+ AddClipRect(rcClip);
+ }
+
+ // calc objects current bounding rectangle
+ if (pObj->flags & DMA_ABS) {
+ // object position is absolute
+ rcObj.left = fracToInt(pObj->xPos);
+ rcObj.top = fracToInt(pObj->yPos);
+ } else {
+ // object position is relative to window
+ rcObj.left = fracToInt(pObj->xPos) - pWin->x;
+ rcObj.top = fracToInt(pObj->yPos) - pWin->y;
+ }
+ rcObj.right = rcObj.left + pObj->width;
+ rcObj.bottom = rcObj.top + pObj->height;
+
+ // calc intersection of object with clipping rect
+ if (IntersectRectangle(rcClip, rcObj, *pClip)) {
+ // current position is within clipping rect
+ AddClipRect(rcClip);
+
+ // update previous position
+ pObj->rcPrev = rcClip;
+ } else {
+ // clear previous position
+ pObj->rcPrev = Common::Rect();
+ }
+
+ // clear changed flag
+ pObj->flags &= ~DMA_CHANGED;
+ }
+ }
+}
+
+/**
+ * Merges any clipping rectangles that overlap to try and reduce
+ * the total number of clip rectangles.
+ */
+void MergeClipRect(void) {
+ if (s_rectList.size() > 1) {
+ RectList::iterator rOuter, rInner;
+
+ for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) {
+ rInner = rOuter;
+ while (++rInner != s_rectList.end()) {
+
+ if (LooseIntersectRectangle(*rOuter, *rInner)) {
+ // these two rectangles overlap or
+ // are next to each other - merge them
+
+ UnionRectangle(*rOuter, *rOuter, *rInner);
+
+ // remove the inner rect from the list
+ s_rectList.erase(rInner);
+
+ // move back to beginning of list
+ rInner = rOuter;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Redraws all objects within this clipping rectangle.
+ * @param pObjList Object list to draw
+ * @param pWin Window top left position
+ * @param pClip Pointer to clip rectangle
+ */
+void UpdateClipRect(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip) {
+ int x, y, right, bottom; // object corners
+ int hclip, vclip; // total size of object clipping
+ DRAWOBJECT currentObj; // filled in to draw the current object in list
+ OBJECT *pObj; // object list iterator
+
+ // Initialise the fields of the drawing object to empty
+ memset(&currentObj, 0, sizeof(DRAWOBJECT));
+
+ for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) {
+ if (pObj->flags & DMA_ABS) {
+ // object position is absolute
+ x = fracToInt(pObj->xPos);
+ y = fracToInt(pObj->yPos);
+ } else {
+ // object position is relative to window
+ x = fracToInt(pObj->xPos) - pWin->x;
+ y = fracToInt(pObj->yPos) - pWin->y;
+ }
+
+ // calc object right
+ right = x + pObj->width;
+ if (right < 0)
+ // totally clipped if negative
+ continue;
+
+ // calc object bottom
+ bottom = y + pObj->height;
+ if (bottom < 0)
+ // totally clipped if negative
+ continue;
+
+ // bottom clip = low right y - clip low right y
+ currentObj.botClip = bottom - pClip->bottom;
+ if (currentObj.botClip < 0) {
+ // negative - object is not clipped
+ currentObj.botClip = 0;
+ }
+
+ // right clip = low right x - clip low right x
+ currentObj.rightClip = right - pClip->right;
+ if (currentObj.rightClip < 0) {
+ // negative - object is not clipped
+ currentObj.rightClip = 0;
+ }
+
+ // top clip = clip top left y - top left y
+ currentObj.topClip = pClip->top - y;
+ if (currentObj.topClip < 0) {
+ // negative - object is not clipped
+ currentObj.topClip = 0;
+ } else { // clipped - adjust start position to top of clip rect
+ y = pClip->top;
+ }
+
+ // left clip = clip top left x - top left x
+ currentObj.leftClip = pClip->left - x;
+ if (currentObj.leftClip < 0) {
+ // negative - object is not clipped
+ currentObj.leftClip = 0;
+ }
+ else
+ // NOTE: This else statement is disabled in tinsel v1
+ { // clipped - adjust start position to left of clip rect
+ x = pClip->left;
+ }
+
+ // calc object total horizontal clipping
+ hclip = currentObj.leftClip + currentObj.rightClip;
+
+ // calc object total vertical clipping
+ vclip = currentObj.topClip + currentObj.botClip;
+
+ if (hclip + vclip != 0) {
+ // object is clipped in some way
+
+ if (pObj->width <= hclip)
+ // object totally clipped horizontally - ignore
+ continue;
+
+ if (pObj->height <= vclip)
+ // object totally clipped vertically - ignore
+ continue;
+
+ // set clip bit in objects flags
+ currentObj.flags = pObj->flags | DMA_CLIP;
+ } else { // object is not clipped - copy flags
+ currentObj.flags = pObj->flags;
+ }
+
+ // copy objects properties to local object
+ currentObj.width = pObj->width;
+ currentObj.height = pObj->height;
+ currentObj.xPos = (short) x;
+ currentObj.yPos = (short) y;
+ currentObj.pPal = pObj->pPal;
+ currentObj.constant = pObj->constant;
+ currentObj.hBits = pObj->hBits;
+
+ // draw the object
+ DrawObject(&currentObj);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h
new file mode 100644
index 0000000000..96b4b3314e
--- /dev/null
+++ b/engines/tinsel/cliprect.h
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Clipping rectangle defines
+ */
+
+#ifndef TINSEL_CLIPRECT_H // prevent multiple includes
+#define TINSEL_CLIPRECT_H
+
+#include "common/list.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+typedef Common::List<Common::Rect> RectList;
+
+/*----------------------------------------------------------------------*\
+|* Clip Rect Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ResetClipRect(void); // Resets the clipping rectangle allocator
+
+void AddClipRect( // Allocate a clipping rectangle from the free list
+ const Common::Rect &pClip); // clip rectangle dimensions to allocate
+
+const RectList &GetClipRects();
+
+bool IntersectRectangle( // Creates the intersection of two rectangles
+ Common::Rect &pDest, // pointer to destination rectangle that is to receive the intersection
+ const Common::Rect &pSrc1, // pointer to a source rectangle
+ const Common::Rect &pSrc2); // pointer to a source rectangle
+
+bool UnionRectangle( // Creates the union of two rectangles
+ Common::Rect &pDest, // destination rectangle that is to receive the new union
+ const Common::Rect &pSrc1, // a source rectangle
+ const Common::Rect &pSrc2); // a source rectangle
+
+void FindMovingObjects( // Creates clipping rectangles for all the objects that have moved on the specified object list
+ OBJECT *pObjList, // playfield display list to draw
+ Common::Point *pWin, // playfield window top left position
+ Common::Rect *pClip, // playfield clipping rectangle
+ bool bVelocity, // when set, objects pos is updated with velocity
+ bool bScrolled); // when set, playfield has scrolled
+
+void MergeClipRect(void); // Merges any clipping rectangles that overlap
+
+void UpdateClipRect( // Redraws all objects within this clipping rectangle
+ OBJECT *pObjList, // object list to draw
+ Common::Point *pWin, // window top left position
+ Common::Rect *pClip); // pointer to clip rectangle
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_CLIPRECT_H
diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp
new file mode 100644
index 0000000000..4c143f1b8d
--- /dev/null
+++ b/engines/tinsel/config.cpp
@@ -0,0 +1,125 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains configuration functionality
+ */
+
+//#define USE_3FLAGS 1
+
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+
+#include "common/file.h"
+#include "common/config-manager.h"
+
+#include "sound/mixer.h"
+
+namespace Tinsel {
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+int dclickSpeed = DOUBLE_CLICK_TIME;
+int volMidi = MAXMIDIVOL;
+int volSound = MAXSAMPVOL;
+int volVoice = MAXSAMPVOL;
+int speedText = DEFTEXTSPEED;
+int bSubtitles = false;
+int bSwapButtons = 0;
+LANGUAGE language = TXT_ENGLISH;
+int bAmerica = 0;
+
+
+// Shouldn't really be here, but time is short...
+bool bNoBlocking;
+
+/**
+ * WriteConfig()
+ */
+
+void WriteConfig(void) {
+ ConfMan.setInt("dclick_speed", dclickSpeed);
+ ConfMan.setInt("music_volume", (volMidi * Audio::Mixer::kMaxChannelVolume) / MAXMIDIVOL);
+ ConfMan.setInt("sfx_volume", (volSound * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL);
+ ConfMan.setInt("speech_volume", (volVoice * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL);
+ ConfMan.setInt("talkspeed", (speedText * 255) / 100);
+ ConfMan.setBool("subtitles", bSubtitles);
+ //ConfMan.setBool("swap_buttons", bSwapButtons ? 1 : 0);
+ //ConfigData.language = language; // not necessary, as language has been set in the launcher
+ //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB
+}
+
+/*---------------------------------------------------------------------*\
+| ReadConfig() |
+|-----------------------------------------------------------------------|
+|
+\*---------------------------------------------------------------------*/
+void ReadConfig(void) {
+ if (ConfMan.hasKey("dclick_speed"))
+ dclickSpeed = ConfMan.getInt("dclick_speed");
+
+ volMidi = (ConfMan.getInt("music_volume") * MAXMIDIVOL) / Audio::Mixer::kMaxChannelVolume;
+ volSound = (ConfMan.getInt("sfx_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume;
+ volVoice = (ConfMan.getInt("speech_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume;
+
+ if (ConfMan.hasKey("talkspeed"))
+ speedText = (ConfMan.getInt("talkspeed") * 100) / 255;
+ if (ConfMan.hasKey("subtitles"))
+ bSubtitles = ConfMan.getBool("subtitles");
+
+ // FIXME: If JAPAN is set, subtitles are forced OFF in the original engine
+
+ //bSwapButtons = ConfMan.getBool("swap_buttons") == 1 ? true : false;
+ //ConfigData.language = language; // not necessary, as language has been set in the launcher
+ //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB
+
+// The flags here control how many country flags are displayed in one of the option dialogs.
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ language = ConfigData.language;
+ #ifdef USE_3FLAGS
+ if (language == TXT_ENGLISH || language == TXT_ITALIAN) {
+ language = TXT_GERMAN;
+ bSubtitles = true;
+ }
+ #endif
+ #ifdef USE_4FLAGS
+ if (language == TXT_ENGLISH) {
+ language = TXT_GERMAN;
+ bSubtitles = true;
+ }
+ #endif
+#else
+ language = TXT_ENGLISH;
+#endif
+}
+
+bool isJapanMode() {
+#ifdef JAPAN
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/config.h b/engines/tinsel/config.h
new file mode 100644
index 0000000000..73cc411cb6
--- /dev/null
+++ b/engines/tinsel/config.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_CONFIG_H
+#define TINSEL_CONFIG_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// None of these defined -> 1 language, in ENGLISH.TXT
+//#define USE_5FLAGS 1 // All 5 flags
+//#define USE_4FLAGS 1 // French, German, Italian, Spanish
+//#define USE_3FLAGS 1 // French, German, Spanish
+
+// The Hebrew version appears to the software as being English
+// but it needs to have subtitles on...
+//#define HEBREW 1
+
+//#define JAPAN 1
+
+
+// double click timer initial value
+#define DOUBLE_CLICK_TIME 6 // 6 @ 18Hz = .33 sec
+
+#define DEFTEXTSPEED 0
+
+
+extern int dclickSpeed;
+extern int volMidi;
+extern int volSound;
+extern int volVoice;
+extern int speedText;
+extern int bSubtitles;
+extern int bSwapButtons;
+extern LANGUAGE language;
+extern int bAmerica;
+
+void WriteConfig(void);
+void ReadConfig(void);
+
+extern bool isJapanMode();
+
+
+// Shouldn't really be here, but time is short...
+extern bool bNoBlocking;
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/coroutine.h b/engines/tinsel/coroutine.h
new file mode 100644
index 0000000000..4a8997fe00
--- /dev/null
+++ b/engines/tinsel/coroutine.h
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_COROUTINE_H
+#define TINSEL_COROUTINE_H
+
+#include "common/scummsys.h"
+
+namespace Tinsel {
+
+// The following is loosely based on <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>,
+// Proper credit to Simon Tatham shall be given.
+
+/*
+
+ * `ccr' macros for re-entrant coroutines.
+
+ */
+
+struct CoroBaseContext {
+ int _line;
+ int _sleep;
+ CoroBaseContext *_subctx;
+ CoroBaseContext() : _line(0), _sleep(0), _subctx(0) {}
+ ~CoroBaseContext() { delete _subctx; }
+};
+
+typedef CoroBaseContext *CoroContext;
+
+
+/**
+ * Wrapper class which holds a pointer to a pointer to a CoroBaseContext.
+ * The interesting part is the destructor, which kills the context being held,
+ * but ONLY if the _sleep val of that context is zero.
+ */
+struct CoroContextHolder {
+ CoroContext &_ctx;
+ CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {}
+ ~CoroContextHolder() { if (_ctx && _ctx->_sleep == 0) { delete _ctx; _ctx = 0; } }
+};
+
+#define CORO_PARAM CoroContext &coroParam
+
+
+#define CORO_BEGIN_CONTEXT struct CoroContextTag : CoroBaseContext { int DUMMY
+#define CORO_END_CONTEXT(x) } *x = (CoroContextTag *)coroParam
+
+#define CORO_BEGIN_CODE(x) \
+ if (!x) {coroParam = x = new CoroContextTag();}\
+ assert(coroParam);\
+ assert(coroParam->_sleep >= 0);\
+ coroParam->_sleep = 0;\
+ CoroContextHolder tmpHolder(coroParam);\
+ switch(coroParam->_line) { case 0:;
+
+#define CORO_END_CODE \
+ }
+
+#define CORO_SLEEP(delay) \
+ do {\
+ coroParam->_line=__LINE__;\
+ coroParam->_sleep=delay;\
+ return; case __LINE__:;\
+ } while (0)
+
+// Stop the currently running coroutine
+#define CORO_KILL_SELF() do { coroParam->_sleep = -1; return; } while(0)
+
+//#define CORO_ABORT() do { delete (ctx); ctx = 0; } while (0)
+
+#define CORO_INVOKE_ARGS(subCoro, ARGS) \
+ do {\
+ coroParam->_line=__LINE__;\
+ coroParam->_subctx = 0;\
+ do {\
+ subCoro ARGS;\
+ if (!coroParam->_subctx) break;\
+ coroParam->_sleep = coroParam->_subctx->_sleep;\
+ return; case __LINE__:;\
+ } while(1);\
+ } while (0)
+
+#define CORO_SUBCTX coroParam->_subctx
+
+#define CORO_INVOKE_0(subCoroutine) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX))
+#define CORO_INVOKE_1(subCoroutine, a0) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0))
+#define CORO_INVOKE_2(subCoroutine, a0,a1) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1))
+#define CORO_INVOKE_3(subCoroutine, a0,a1,a2) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2))
+#define CORO_INVOKE_4(subCoroutine, a0,a1,a2,a3) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2,a3))
+#define CORO_INVOKE_5(subCoroutine, a0,a1,a2,a3,a4) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2,a3,a4))
+
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_COROUTINE_H
diff --git a/engines/tinsel/cursor.cpp b/engines/tinsel/cursor.cpp
new file mode 100644
index 0000000000..087730c165
--- /dev/null
+++ b/engines/tinsel/cursor.cpp
@@ -0,0 +1,647 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Cursor and cursor trails.
+ */
+
+#include "tinsel/cursor.h"
+
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // For EventsManager class
+#include "tinsel/film.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h" // resetidletime()
+#include "tinsel/tinsel.h" // For engine access
+
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+#define ITERATION_BASE FRAC_ONE
+#define ITER_ACCELLERATION (10L << (FRAC_BITS - 4))
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static OBJECT *McurObj = 0; // Main cursor object
+static OBJECT *AcurObj = 0; // Auxiliary cursor object
+
+static ANIM McurAnim = {0,0,0,0,0}; // Main cursor animation structure
+static ANIM AcurAnim = {0,0,0,0,0}; // Auxiliary cursor animation structure
+
+static bool bHiddenCursor = false; // Set when cursor is hidden
+static bool bTempNoTrailers = false; // Set when cursor trails are hidden
+
+static bool bFrozenCursor = false; // Set when cursor position is frozen
+
+static frac_t IterationSize = 0;
+
+static SCNHANDLE CursorHandle = 0; // Handle to cursor reel data
+
+static int numTrails = 0;
+static int nextTrail = 0;
+
+static bool bWhoa = false; // Set by DropCursor() at the end of a scene
+ // - causes cursor processes to do nothing
+ // Reset when main cursor has re-initialised
+
+static bool restart = false; // When main cursor has been bWhoa-ed, it waits
+ // for this to be set to true.
+
+static short ACoX = 0, ACoY = 0; // Auxillary cursor image's animation offsets
+
+
+
+#define MAX_TRAILERS 10
+
+static struct {
+
+ ANIM trailAnim; // Animation structure
+ OBJECT *trailObj; // This trailer's object
+
+} ntrailData [MAX_TRAILERS];
+
+static int lastCursorX = 0, lastCursorY = 0;
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void MoveCursor(void);
+
+/**
+ * Initialise and insert a cursor trail object, set its Z-pos, and hide
+ * it. Also initialise its animation script.
+ */
+static void InitCurTrailObj(int i, int x, int y) {
+ const FREEL *pfr; // pointer to reel
+ PIMAGE pim; // pointer to image
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+
+ const FILM *pfilm;
+
+ if (!numTrails)
+ return;
+
+ // Get rid of old object
+ if (ntrailData[i].trailObj != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+
+ pim = GetImageFromFilm(CursorHandle, i+1, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // No background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Initialise and insert the object, set its Z-pos, and hide it
+ ntrailData[i].trailObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ MultiSetZPosition(ntrailData[i].trailObj, Z_CURSORTRAIL);
+ MultiSetAniXY(ntrailData[i].trailObj, x, y);
+
+ // Initialise the animation script
+ InitStepAnimScript(&ntrailData[i].trailAnim, ntrailData[i].trailObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ StepAnimScript(&ntrailData[i].trailAnim);
+}
+
+/**
+ * Get the cursor position from the mouse driver.
+ */
+static bool GetDriverPosition(int *x, int *y) {
+ Common::Point ptMouse = _vm->getMousePosition();
+ *x = ptMouse.x;
+ *y = ptMouse.y;
+
+ return(*x >= 0 && *x <= SCREEN_WIDTH-1 &&
+ *y >= 0 && *y <= SCREEN_HEIGHT-1);
+}
+
+/**
+ * Move the cursor relative to current position.
+ */
+void AdjustCursorXY(int deltaX, int deltaY) {
+ int x, y;
+
+ if (deltaX || deltaY) {
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(x + deltaX, y + deltaY));
+ }
+ MoveCursor();
+}
+
+/**
+ * Move the cursor to an absolute position.
+ */
+void SetCursorXY(int newx, int newy) {
+ int x, y;
+ int Loffset, Toffset; // Screen offset
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ newx -= Loffset;
+ newy -= Toffset;
+
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(newx, newy));
+ MoveCursor();
+}
+
+/**
+ * Move the cursor to a screen position.
+ */
+void SetCursorScreenXY(int newx, int newy) {
+ int x, y;
+
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(newx, newy));
+ MoveCursor();
+}
+
+/**
+ * Called by the world and his brother.
+ * Returns the cursor's animation position in (x,y).
+ * Returns false if there is no cursor object.
+ */
+bool GetCursorXYNoWait(int *x, int *y, bool absolute) {
+ if (McurObj == NULL) {
+ *x = *y = 0;
+ return false;
+ }
+
+ GetAniPosition(McurObj, x, y);
+
+ if (absolute) {
+ int Loffset, Toffset; // Screen offset
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ *x += Loffset;
+ *y += Toffset;
+ }
+
+ return true;
+}
+
+/**
+ * Called by the world and his brother.
+ * Returns the cursor's animation position.
+ * If called while there is no cursor object, the calling process ends
+ * up waiting until there is.
+ */
+void GetCursorXY(int *x, int *y, bool absolute) {
+ //while (McurObj == NULL)
+ // ProcessSleepSelf();
+ assert(McurObj);
+ GetCursorXYNoWait(x, y, absolute);
+}
+
+/**
+ * Re-initialise the main cursor to use the main cursor reel.
+ * Called from TINLIB.C to restore cursor after hiding it.
+ * Called from INVENTRY.C to restore cursor after customising it.
+ */
+void RestoreMainCursor(void) {
+ const FILM *pfilm;
+
+ if (McurObj != NULL) {
+ pfilm = (const FILM *)LockMem(CursorHandle);
+
+ InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfilm->reels->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ StepAnimScript(&McurAnim);
+ }
+ bHiddenCursor = false;
+ bFrozenCursor = false;
+}
+
+/**
+ * Called from INVENTRY.C to customise the main cursor.
+ */
+void SetTempCursor(SCNHANDLE pScript) {
+ if (McurObj != NULL)
+ InitStepAnimScript(&McurAnim, McurObj, pScript, 2);
+}
+
+/**
+ * Hide the cursor.
+ */
+void DwHideCursor(void) {
+ int i;
+
+ bHiddenCursor = true;
+
+ if (McurObj)
+ MultiHideObject(McurObj);
+ if (AcurObj)
+ MultiHideObject(AcurObj);
+
+ for (i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * Unhide the cursor.
+ */
+void UnHideCursor(void) {
+ bHiddenCursor = false;
+}
+
+/**
+ * Freeze the cursor.
+ */
+void FreezeCursor(void) {
+ bFrozenCursor = true;
+}
+
+/**
+ * HideCursorTrails
+ */
+void HideCursorTrails(void) {
+ int i;
+
+ bTempNoTrailers = true;
+
+ for (i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * UnHideCursorTrails
+ */
+void UnHideCursorTrails(void) {
+ bTempNoTrailers = false;
+}
+
+/**
+ * Get pointer to image from a film reel. And the rest.
+ */
+IMAGE *GetImageFromReel(const FREEL *pfr, const MULTI_INIT **ppmi) {
+ const MULTI_INIT *pmi;
+ const FRAME *pFrame;
+
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj));
+ if (ppmi)
+ *ppmi = pmi;
+
+ pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame));
+
+ // get pointer to image
+ return (IMAGE *)LockMem(READ_LE_UINT32(pFrame));
+}
+
+/**
+ * Get pointer to image from a film. And the rest.
+ */
+IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr, const MULTI_INIT **ppmi, const FILM **ppfilm) {
+ const FILM *pfilm;
+ const FREEL *pfr;
+
+ pfilm = (const FILM *)LockMem(hFilm);
+ if (ppfilm)
+ *ppfilm = pfilm;
+
+ pfr = &pfilm->reels[reel];
+ if (ppfr)
+ *ppfr = pfr;
+
+ return GetImageFromReel(pfr, ppmi);
+}
+
+/**
+ * Delete auxillary cursor. Restore animation offsets in the image.
+ */
+void DelAuxCursor(void) {
+ if (AcurObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), AcurObj);
+ AcurObj = NULL;
+ }
+}
+
+/**
+ * Set auxillary cursor.
+ * Save animation offsets from the image if required.
+ */
+void SetAuxCursor(SCNHANDLE hFilm) {
+ PIMAGE pim; // Pointer to auxillary cursor's image
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+ const FILM *pfilm;
+ int x, y; // Cursor position
+
+ DelAuxCursor(); // Get rid of previous
+
+ GetCursorXY(&x, &y, false); // Note: also waits for cursor to appear
+
+ pim = GetImageFromFilm(hFilm, 0, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // no background palette
+ pim->hImgPal = TO_LE_32(BackPal()); // Poke in the background palette
+
+ ACoX = (short)(FROM_LE_16(pim->imgWidth)/2 - FROM_LE_16(pim->anioffX));
+ ACoY = (short)(FROM_LE_16(pim->imgHeight)/2 - FROM_LE_16(pim->anioffY));
+
+ // Initialise and insert the auxillary cursor object
+ AcurObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), AcurObj);
+
+ // Initialise the animation and set its position
+ InitStepAnimScript(&AcurAnim, AcurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ MultiSetAniXY(AcurObj, x - ACoX, y - ACoY);
+ MultiSetZPosition(AcurObj, Z_ACURSOR);
+
+ if (bHiddenCursor)
+ MultiHideObject(AcurObj);
+}
+
+/**
+ * MoveCursor
+ */
+static void MoveCursor(void) {
+ int startX, startY;
+ Common::Point ptMouse;
+ frac_t newX, newY;
+ unsigned dir;
+
+ // get cursors start animation position
+ GetCursorXYNoWait(&startX, &startY, false);
+
+ // get mouse drivers current position
+ ptMouse = _vm->getMousePosition();
+
+ // convert to fixed point
+ newX = intToFrac(ptMouse.x);
+ newY = intToFrac(ptMouse.y);
+
+ // modify mouse driver position depending on cursor keys
+ if ((dir = _vm->getKeyDirection()) != 0) {
+ if (dir & MSK_LEFT)
+ newX -= IterationSize;
+
+ if (dir & MSK_RIGHT)
+ newX += IterationSize;
+
+ if (dir & MSK_UP)
+ newY -= IterationSize;
+
+ if (dir & MSK_DOWN)
+ newY += IterationSize;
+
+ IterationSize += ITER_ACCELLERATION;
+
+ // set new mouse driver position
+ _vm->setMousePosition(Common::Point(fracToInt(newX), fracToInt(newY)));
+ } else
+
+ IterationSize = ITERATION_BASE;
+
+ // get new mouse driver position - could have been modified
+ ptMouse = _vm->getMousePosition();
+
+ if (lastCursorX != ptMouse.x || lastCursorY != ptMouse.y) {
+ resetUserEventTime();
+
+ if (!bTempNoTrailers && !bHiddenCursor) {
+ InitCurTrailObj(nextTrail++, lastCursorX, lastCursorY);
+ if (nextTrail == numTrails)
+ nextTrail = 0;
+ }
+ }
+
+ // adjust cursor to new mouse position
+ if (McurObj)
+ MultiSetAniXY(McurObj, ptMouse.x, ptMouse.y);
+ if (AcurObj != NULL)
+ MultiSetAniXY(AcurObj, ptMouse.x - ACoX, ptMouse.y - ACoY);
+
+ if (InventoryActive() && McurObj) {
+ // Notify the inventory
+ Xmovement(ptMouse.x - startX);
+ Ymovement(ptMouse.y - startY);
+ }
+
+ lastCursorX = ptMouse.x;
+ lastCursorY = ptMouse.y;
+}
+
+/**
+ * Initialise cursor object.
+ */
+static void InitCurObj(void) {
+ const FILM *pfilm;
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+ PIMAGE pim;
+
+ pim = GetImageFromFilm(CursorHandle, 0, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // no background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+//---
+
+ AcurObj = NULL; // No auxillary cursor
+
+ McurObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), McurObj);
+
+ InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+}
+
+/**
+ * Initialise the cursor position.
+ */
+static void InitCurPos(void) {
+ Common::Point ptMouse = _vm->getMousePosition();
+ lastCursorX = ptMouse.x;
+ lastCursorY = ptMouse.y;
+
+ MultiSetZPosition(McurObj, Z_CURSOR);
+ MoveCursor();
+ MultiHideObject(McurObj);
+
+ IterationSize = ITERATION_BASE;
+}
+
+/**
+ * CursorStoppedCheck
+ */
+static void CursorStoppedCheck(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // If scene is closing down
+ if (bWhoa) {
+ // ...wait for next scene start-up
+ while (!restart)
+ CORO_SLEEP(1);
+
+ // Re-initialise
+ InitCurObj();
+ InitCurPos();
+ InventoryIconCursor(); // May be holding something
+
+ // Re-start the cursor trails
+ restart = false; // set all bits
+ bWhoa = false;
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * The main cursor process.
+ */
+void CursorProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (!CursorHandle || !BackPal())
+ CORO_SLEEP(1);
+
+ InitCurObj();
+ InitCurPos();
+ InventoryIconCursor(); // May be holding something
+
+ bWhoa = false;
+ restart = false;
+
+ while (1) {
+ // allow rescheduling
+ CORO_SLEEP(1);
+
+ // Stop/start between scenes
+ CORO_INVOKE_0(CursorStoppedCheck);
+
+ // Step the animation script(s)
+ StepAnimScript(&McurAnim);
+ if (AcurObj != NULL)
+ StepAnimScript(&AcurAnim);
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ if (StepAnimScript(&ntrailData[i].trailAnim) == ScriptFinished) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+ }
+
+ // Move the cursor as appropriate
+ if (!bFrozenCursor)
+ MoveCursor();
+
+ // If the cursor should be hidden...
+ if (bHiddenCursor) {
+ // ...hide the cursor object(s)
+ MultiHideObject(McurObj);
+ if (AcurObj)
+ MultiHideObject(AcurObj);
+
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL)
+ MultiHideObject(ntrailData[i].trailObj);
+ }
+
+ // Wait 'til cursor is again required.
+ while (bHiddenCursor) {
+ CORO_SLEEP(1);
+
+ // Stop/start between scenes
+ CORO_INVOKE_0(CursorStoppedCheck);
+ }
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Called from dec_cursor() Glitter function.
+ * Register the handle to cursor reel data.
+ */
+void DwInitCursor(SCNHANDLE bfilm) {
+ const FILM *pfilm;
+
+ CursorHandle = bfilm;
+
+ pfilm = (const FILM *)LockMem(CursorHandle);
+ numTrails = FROM_LE_32(pfilm->numreels) - 1;
+
+ assert(numTrails <= MAX_TRAILERS);
+}
+
+/**
+ * DropCursor is called when a scene is closing down.
+ */
+void DropCursor(void) {
+ AcurObj = NULL; // No auxillary cursor
+ McurObj = NULL; // No cursor object (imminently deleted elsewhere)
+ bHiddenCursor = false; // Not hidden in next scene
+ bTempNoTrailers = false; // Trailers not hidden in next scene
+ bWhoa = true; // Suspend cursor processes
+
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * RestartCursor is called when a new scene is starting up.
+ */
+void RestartCursor(void) {
+ restart = true; // Get the main cursor to re-initialise
+}
+
+/**
+ * Called when restarting the game, ensures correct re-start with NULL
+ * pointers etc.
+ */
+void RebootCursor(void) {
+ McurObj = AcurObj = NULL;
+ for (int i = 0; i < MAX_TRAILERS; i++)
+ ntrailData[i].trailObj = NULL;
+
+ bHiddenCursor = bTempNoTrailers = bFrozenCursor = false;
+
+ CursorHandle = 0;
+
+ bWhoa = false;
+ restart = false;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cursor.h b/engines/tinsel/cursor.h
new file mode 100644
index 0000000000..15349dda26
--- /dev/null
+++ b/engines/tinsel/cursor.h
@@ -0,0 +1,56 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Clipping rectangle defines
+ */
+
+#ifndef TINSEL_CURSOR_H // prevent multiple includes
+#define TINSEL_CURSOR_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+void AdjustCursorXY(int deltaX, int deltaY);
+void SetCursorXY(int x, int y);
+void SetCursorScreenXY(int newx, int newy);
+void GetCursorXY(int *x, int *y, bool absolute);
+bool GetCursorXYNoWait(int *x, int *y, bool absolute);
+
+void RestoreMainCursor(void);
+void SetTempCursor(SCNHANDLE pScript);
+void DwHideCursor(void);
+void UnHideCursor(void);
+void FreezeCursor(void);
+void HideCursorTrails(void);
+void UnHideCursorTrails(void);
+void DelAuxCursor(void);
+void SetAuxCursor(SCNHANDLE hFilm);
+void DwInitCursor(SCNHANDLE bfilm);
+void DropCursor(void);
+void RestartCursor(void);
+void RebootCursor(void);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_CURSOR_H
diff --git a/engines/tinsel/debugger.cpp b/engines/tinsel/debugger.cpp
new file mode 100644
index 0000000000..dc37e6a9a1
--- /dev/null
+++ b/engines/tinsel/debugger.cpp
@@ -0,0 +1,162 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "tinsel/tinsel.h"
+#include "tinsel/debugger.h"
+#include "tinsel/inventory.h"
+#include "tinsel/pcode.h"
+#include "tinsel/scene.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+#include "tinsel/font.h"
+#include "tinsel/strres.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// In PDISPLAY.CPP
+extern void TogglePathDisplay(void);
+// In tinsel.cpp
+extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+// In scene.cpp
+extern SCNHANDLE GetSceneHandle(void);
+
+//----------------- SUPPORT FUNCTIONS ---------------------
+
+//static
+int strToInt(const char *s) {
+ if (!*s)
+ // No string at all
+ return 0;
+ else if (toupper(s[strlen(s) - 1]) != 'H')
+ // Standard decimal string
+ return atoi(s);
+
+ // Hexadecimal string
+ uint tmp;
+ sscanf(s, "%xh", &tmp);
+ return (int)tmp;
+}
+
+//----------------- CONSOLE CLASS ---------------------
+
+Console::Console() : GUI::Debugger() {
+ DCmd_Register("item", WRAP_METHOD(Console, cmd_item));
+ DCmd_Register("scene", WRAP_METHOD(Console, cmd_scene));
+ DCmd_Register("music", WRAP_METHOD(Console, cmd_music));
+ DCmd_Register("sound", WRAP_METHOD(Console, cmd_sound));
+ DCmd_Register("string", WRAP_METHOD(Console, cmd_string));
+}
+
+Console::~Console() {
+}
+
+bool Console::cmd_item(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s item_number\n", argv[0]);
+ DebugPrintf("Sets the currently active 'held' item\n");
+ return true;
+ }
+
+ HoldItem(INV_NOICON);
+ HoldItem(strToInt(argv[1]));
+ return false;
+}
+
+bool Console::cmd_scene(int argc, const char **argv) {
+ if (argc < 1 || argc > 3) {
+ DebugPrintf("%s [scene_number [entry number]]\n", argv[0]);
+ DebugPrintf("If no parameters are given, prints the current scene.\n");
+ DebugPrintf("Otherwise changes to the specified scene number. Entry number defaults to 1 if none provided\n");
+ return true;
+ }
+
+ if (argc == 1) {
+ DebugPrintf("Current scene is %d\n", GetSceneHandle() >> SCNHANDLE_SHIFT);
+ return true;
+ }
+
+ uint32 sceneNumber = (uint32)strToInt(argv[1]) << SCNHANDLE_SHIFT;
+ int entryNumber = (argc >= 3) ? strToInt(argv[2]) : 1;
+
+ SetNewScene(sceneNumber, entryNumber, TRANS_CUT);
+ return false;
+}
+
+bool Console::cmd_music(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s track_number or %s -offset\n", argv[0], argv[0]);
+ DebugPrintf("Plays the MIDI track number provided, or the offset inside midi.dat\n");
+ DebugPrintf("A positive number signifies a track number, whereas a negative signifies an offset\n");
+ return true;
+ }
+
+ int param = strToInt(argv[1]);
+ if (param == 0) {
+ DebugPrintf("Track number/offset can't be 0!\n", argv[0]);
+ } else if (param > 0) {
+ // Track provided
+ PlayMidiSequence(GetTrackOffset(param - 1), false);
+ } else if (param < 0) {
+ // Offset provided
+ param = param * -1;
+ PlayMidiSequence(param, false);
+ }
+ return true;
+}
+
+bool Console::cmd_sound(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s id\n", argv[0]);
+ DebugPrintf("Plays the sound with the given ID\n");
+ return true;
+ }
+
+ int id = strToInt(argv[1]);
+ if (_vm->_sound->sampleExists(id))
+ _vm->_sound->playSample(id, Audio::Mixer::kSpeechSoundType);
+ else
+ DebugPrintf("Sample %d does not exist!\n", id);
+
+ return true;
+}
+
+bool Console::cmd_string(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s id\n", argv[0]);
+ DebugPrintf("Prints the string with the given ID\n");
+ return true;
+ }
+
+ char tmp[TBUFSZ];
+ int id = strToInt(argv[1]);
+ LoadStringRes(id, tmp, TBUFSZ);
+ DebugPrintf("%s\n", tmp);
+
+ return true;
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/debugger.h b/engines/tinsel/debugger.h
new file mode 100644
index 0000000000..219bc71224
--- /dev/null
+++ b/engines/tinsel/debugger.h
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_DEBUGGER_H
+#define TINSEL_DEBUGGER_H
+
+#include "gui/debugger.h"
+
+namespace Tinsel {
+
+class TinselEngine;
+
+class Console: public GUI::Debugger {
+protected:
+ bool cmd_item(int argc, const char **argv);
+ bool cmd_scene(int argc, const char **argv);
+ bool cmd_music(int argc, const char **argv);
+ bool cmd_sound(int argc, const char **argv);
+ bool cmd_string(int argc, const char **argv);
+public:
+ Console();
+ virtual ~Console(void);
+};
+
+} // End of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp
new file mode 100644
index 0000000000..a638dde2c5
--- /dev/null
+++ b/engines/tinsel/detection.cpp
@@ -0,0 +1,278 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "base/plugins.h"
+
+#include "common/advancedDetector.h"
+#include "common/file.h"
+
+#include "tinsel/tinsel.h"
+
+
+namespace Tinsel {
+
+struct TinselGameDescription {
+ Common::ADGameDescription desc;
+
+ int gameID;
+ int gameType;
+ uint32 features;
+ uint16 version;
+};
+
+uint32 TinselEngine::getGameID() const {
+ return _gameDescription->gameID;
+}
+
+uint32 TinselEngine::getFeatures() const {
+ return _gameDescription->features;
+}
+
+Common::Language TinselEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+Common::Platform TinselEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+uint16 TinselEngine::getVersion() const {
+ return _gameDescription->version;
+}
+
+}
+
+static const PlainGameDescriptor tinselGames[] = {
+ {"tinsel", "Tinsel engine game"},
+ {"dw", "Discworld"},
+ {"dw2", "Discworld 2: Mortality Bytes!"},
+ {0, 0}
+};
+
+
+namespace Tinsel {
+
+static const TinselGameDescription gameDescriptions[] = {
+
+ // Note: versions with *.gra files use tinsel v1 (28/2/1995), whereas
+ // versions with *.scn files tinsel v2 (7/5/1995)
+ // Update: this is not entirely true, there were some versions released
+ // with *.gra files and used tinsel v2
+
+ {
+ { // This version has *.gra files but uses tinsel v2
+ "dw",
+ "Floppy",
+ AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_FLOPPY,
+ TINSEL_V2,
+ },
+
+ { // English CD v1. This version has *.gra files but uses tinsel v2
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.gra", 0, "c8808ccd988d603dd35dff42013ae7fd", 781656},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD,
+ TINSEL_V2,
+ },
+
+ { // English CD v2
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.scn", 0, "70955425870c7720d6eebed903b2ef41", 776188},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD | GF_SCNFILES,
+ TINSEL_V2,
+ },
+
+#if 0
+ { // English Saturn CD
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.scn", 0, "6803f293c88758057cc685b9437f7637", 382248},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD,
+ TINSEL_V2,
+ },
+#endif
+
+ { // Demo from http://www.adventure-treff.de/specials/dl_demos.php
+ {
+ "dw",
+ "Demo",
+ AD_ENTRY1s("dw.gra", "ce1b57761ba705221bcf70955b827b97", 441192),
+ //AD_ENTRY1s("dw.scn", "ccd72f02183d0e96b6e7d8df9492cda8", 23308),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GID_DW1,
+ 0,
+ GF_DEMO,
+ TINSEL_V1,
+ },
+
+ { // German CD re-release "Neon Edition"
+ // Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT
+ {
+ "dw",
+ "CD",
+ AD_ENTRY1s("dw.scn", "6182c7986eaec893c62fb6ea13a9f225", 774556),
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD | GF_SCNFILES,
+ TINSEL_V2,
+ },
+
+ { AD_TABLE_END_MARKER, 0, 0, 0, 0 }
+};
+
+/**
+ * The fallback game descriptor used by the Tinsel engine's fallbackDetector.
+ * Contents of this struct are to be overwritten by the fallbackDetector.
+ */
+static TinselGameDescription g_fallbackDesc = {
+ {
+ "",
+ "",
+ AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor
+ Common::UNK_LANG,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+} // End of namespace Tinsel
+
+static const Common::ADParams detectionParams = {
+ // Pointer to ADGameDescription or its superset structure
+ (const byte *)Tinsel::gameDescriptions,
+ // Size of that superset structure
+ sizeof(Tinsel::TinselGameDescription),
+ // Number of bytes to compute MD5 sum for
+ 5000,
+ // List of all engine targets
+ tinselGames,
+ // Structure for autoupgrading obsolete targets
+ 0,
+ // Name of single gameid (optional)
+ "tinsel",
+ // List of files for file-based fallback detection (optional)
+ 0,
+ // Flags
+ 0
+};
+
+class TinselMetaEngine : public Common::AdvancedMetaEngine {
+public:
+ TinselMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {}
+
+ virtual const char *getName() const {
+ return "Tinsel Engine";
+ }
+
+ virtual const char *getCopyright() const {
+ return "Tinsel Engine";
+ }
+
+ virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const;
+
+ const Common::ADGameDescription *fallbackDetect(const FSList *fslist) const;
+
+};
+
+bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const {
+ const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc;
+ if (gd) {
+ *engine = new Tinsel::TinselEngine(syst, gd);
+ }
+ return gd != 0;
+}
+
+const Common::ADGameDescription *TinselMetaEngine::fallbackDetect(const FSList *fslist) const {
+ // Set the default values for the fallback descriptor's ADGameDescription part.
+ Tinsel::g_fallbackDesc.desc.language = Common::UNK_LANG;
+ Tinsel::g_fallbackDesc.desc.platform = Common::kPlatformPC;
+ Tinsel::g_fallbackDesc.desc.flags = Common::ADGF_NO_FLAGS;
+
+ // Set default values for the fallback descriptor's TinselGameDescription part.
+ Tinsel::g_fallbackDesc.gameID = 0;
+ Tinsel::g_fallbackDesc.features = 0;
+ Tinsel::g_fallbackDesc.version = 0;
+
+ //return (const Common::ADGameDescription *)&Tinsel::g_fallbackDesc;
+ return NULL;
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(TINSEL)
+ REGISTER_PLUGIN_DYNAMIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
+#endif
diff --git a/engines/tinsel/dw.h b/engines/tinsel/dw.h
new file mode 100644
index 0000000000..d14dd43fa2
--- /dev/null
+++ b/engines/tinsel/dw.h
@@ -0,0 +1,119 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_DW_H
+#define TINSEL_DW_H
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+
+namespace Tinsel {
+
+/** scene handle data type */
+typedef uint32 SCNHANDLE;
+
+/** polygon handle */
+typedef int HPOLYGON;
+
+
+#define EOS_CHAR '\0' // string terminator
+#define LF_CHAR '\x0a' // line feed
+
+// file names
+#define SAMPLE_FILE "english.smp" // all samples
+#define SAMPLE_INDEX "english.idx" // sample index filename
+#define MIDI_FILE "midi.dat" // all MIDI sequences
+#define INDEX_FILENAME "index" // name of index file
+
+#define SCNHANDLE_SHIFT 23 // amount to shift scene handles by
+#define NO_SCNHANDLES 300 // number of memory handles for scenes
+#define MASTER_SCNHANDLE (0 << SCNHANDLE_SHIFT) // master scene memory handle
+
+// the minimum value a integer number can have
+#define MIN_INT (1 << (8*sizeof(int) - 1))
+#define MIN_INT16 (-32767)
+
+// the maximum value a integer number can have
+#define MAX_INT (~MIN_INT)
+
+// TODO: v1->v2 scene files
+#ifdef FILE_SPLIT
+// each scene is split into 2 files
+#define INV_OBJ_SCNHANDLE (2 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects)
+#else
+#define INV_OBJ_SCNHANDLE (1 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects)
+#endif
+
+
+#define FIELD_WORLD 0
+#define FIELD_STATUS 1
+
+
+
+
+// We don't set the Z position for print and talk text
+// i.e. it gets a Z position of 0
+
+#define Z_INV_BRECT 10 // Inventory background rectangle
+#define Z_INV_MFRAME 15 // Inventory window frame
+#define Z_INV_HTEXT 15 // Inventory heading text
+#define Z_INV_ICONS 16 // Icons in inventory
+#define Z_INV_ITEXT 995 // Icon text
+
+#define Z_INV_RFRAME 22 // Re-sizing frame
+
+#define Z_CURSOR 1000 // Cursor
+#define Z_CURSORTRAIL 999 // Cursor trails
+#define Z_ACURSOR 990 // Auxillary cursor
+
+#define Z_TAG_TEXT 995 // In front of auxillary cursor
+
+#define Z_MDGROOVE 20
+#define Z_MDSLIDER 21
+
+#define Z_TOPPLAY 100
+
+#define Z_TOPW_TEXT Z_TAG_TEXT
+
+// Started a collection of assorted maximum numbers here:
+#define MAX_MOVERS 6 // Moving actors using path system
+#define MAX_SAVED_ACTORS 32 // Saved 'Normal' actors
+#define MAX_SAVED_ALIVES 512 // Saves actors'lives
+
+// Legal non-existant entrance number for LoadScene()
+#define NO_ENTRY_NUM (-3458) // Magic unlikely number
+
+
+#define SAMPLETIMEOUT (15*ONE_SECOND)
+
+// Language for the resource strings
+enum LANGUAGE {
+ TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN,
+ TXT_ITALIAN, TXT_SPANISH
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_DW_H
diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp
new file mode 100644
index 0000000000..825825ccec
--- /dev/null
+++ b/engines/tinsel/effect.cpp
@@ -0,0 +1,134 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Handles effect polygons.
+//
+// EffectPolyProcess() monitors triggering of effect code (i.e. a moving
+// actor entering an effect polygon).
+// EffectProcess() runs the appropriate effect code.
+//
+// NOTE: Currently will only run one effect process at a time, i.e.
+// effect polygons will not currently nest. It won't be very difficult
+// to fix this if required.
+
+#include "tinsel/actors.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/pid.h"
+#include "tinsel/pcode.h" // LEAD_ACTOR
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+
+
+namespace Tinsel {
+
+struct EP_INIT {
+ HPOLYGON hEpoly;
+ PMACTOR pActor;
+ int index;
+};
+
+/**
+ * Runs an effect polygon's Glitter code with ENTER event, waits for the
+ * actor to leave that polygon. Then runs the polygon's Glitter code
+ * with LEAVE event.
+ */
+static void EffectProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ EP_INIT *to = (EP_INIT *)ProcessGetParamsSelf(); // get the stuff copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int x, y; // Lead actor position
+
+ // Run effect poly enter script
+ effRunPolyTinselCode(to->hEpoly, ENTER, to->pActor->actorID);
+
+ do {
+ CORO_SLEEP(1);
+ GetMActorPosition(to->pActor, &x, &y);
+ } while (InPolygon(x, y, EFFECT) == to->hEpoly);
+
+ // Run effect poly leave script
+ effRunPolyTinselCode(to->hEpoly, LEAVE, to->pActor->actorID);
+
+ SetMAinEffectPoly(to->index, false);
+
+ CORO_END_CODE;
+}
+
+/**
+ * If the actor was not already in an effect polygon, checks to see if
+ * it has just entered one. If it has, a process is started up to run
+ * the polygon's Glitter code.
+ */
+static void FettleEffectPolys(int x, int y, int index, PMACTOR pActor) {
+ HPOLYGON hPoly;
+ EP_INIT epi;
+
+ // If just entered an effect polygon, the effect should be triggered.
+ if (!IsMAinEffectPoly(index)) {
+ hPoly = InPolygon(x, y, EFFECT);
+ if (hPoly != NOPOLY) {
+ //Just entered effect polygon
+ SetMAinEffectPoly(index, true);
+
+ epi.hEpoly = hPoly;
+ epi.pActor = pActor;
+ epi.index = index;
+ CoroutineInstall(PID_TCODE, EffectProcess, &epi, sizeof(epi));
+ }
+ }
+}
+
+/**
+ * Just calls FettleEffectPolys() every clock tick.
+ */
+void EffectPolyProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ while (1) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ PMACTOR pActor = GetLiveMover(i);
+ if (pActor != NULL) {
+ int x, y;
+ GetMActorPosition(pActor, &x, &y);
+ FettleEffectPolys(x, y, i, pActor);
+ }
+ }
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+ CORO_END_CODE;
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp
new file mode 100644
index 0000000000..8064bdd3f3
--- /dev/null
+++ b/engines/tinsel/events.cpp
@@ -0,0 +1,439 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Main purpose is to process user events.
+ * Also provides a couple of utility functions.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/handle.h" // For LockMem()
+#include "tinsel/inventory.h"
+#include "tinsel/move.h" // For walking lead actor
+#include "tinsel/pcode.h" // For Interpret()
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h" // For walking lead actor
+#include "tinsel/sched.h"
+#include "tinsel/scroll.h" // For DontScrollCursor()
+#include "tinsel/timers.h" // DwGetCurrentTime()
+#include "tinsel/tinlib.h" // For control()
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in PDISPLAY.C
+extern int GetTaggedActor(void);
+extern HPOLYGON GetTaggedPoly(void);
+
+
+//----------------- EXTERNAL GLOBAL DATA ---------------------
+
+extern bool bEnableF1;
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int userEvents = 0; // Whenever a button or a key comes in
+static uint32 lastUserEvent = 0; // Time it hapenned
+static int butEvents = 0; // Single or double, left or right. Or escape key.
+static int escEvents = 0; // Escape key
+
+
+static int eCount = 0;
+
+/**
+ * Gets called before each schedule, only 1 user action per schedule
+ * is allowed.
+ */
+void ResetEcount(void) {
+ eCount = 0;
+}
+
+
+void IncUserEvents(void) {
+ userEvents++;
+ lastUserEvent = DwGetCurrentTime();
+}
+
+/**
+ * If this is a single click, wait to check it's not the first half of a
+ * double click.
+ * If this is a double click, the process from the waiting single click
+ * gets killed.
+ */
+void AllowDclick(CORO_PARAM, BUTEVENT be) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ if (be == BE_SLEFT) {
+ GetToken(TOKEN_LEFT_BUT);
+ CORO_SLEEP(dclickSpeed+1);
+ FreeToken(TOKEN_LEFT_BUT);
+
+ // Prevent activation of 2 events on the same tick
+ if (++eCount != 1)
+ CORO_KILL_SELF();
+
+ break;
+
+ } else if (be == BE_DLEFT) {
+ GetToken(TOKEN_LEFT_BUT);
+ FreeToken(TOKEN_LEFT_BUT);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Take control from player, if the player has it.
+ * Return TRUE if control taken, FALSE if not.
+ */
+
+bool GetControl(int param) {
+ if (TestToken(TOKEN_CONTROL)) {
+ control(param);
+ return true;
+ } else
+ return false;
+}
+
+struct TP_INIT {
+ HPOLYGON hPoly; // Polygon
+ USER_EVENT event; // Trigerring event
+ BUTEVENT bev; // To allow for double clicks
+ bool take_control; // Set if control should be taken
+ // while code is running.
+ int actor;
+};
+
+/**
+ * Runs glitter code associated with a polygon.
+ */
+static void PolyTinselProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PINT_CONTEXT pic;
+ bool took_control; // Set if this function takes control
+ CORO_END_CONTEXT(_ctx);
+
+ TP_INIT *to = (TP_INIT *)ProcessGetParamsSelf(); // get the stuff copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click
+
+ // Control may have gone off during AllowDclick()
+ if (!TestToken(TOKEN_CONTROL)
+ && (to->event == WALKTO || to->event == ACTION || to->event == LOOK))
+ CORO_KILL_SELF();
+
+ // Take control, if requested
+ if (to->take_control)
+ _ctx->took_control = GetControl(CONTROL_OFF);
+ else
+ _ctx->took_control = false;
+
+ // Hide conversation if appropriate
+ if (to->event == CONVERSE)
+ convHide(true);
+
+ // Run the code
+ _ctx->pic = InitInterpretContext(GS_POLYGON, getPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // Free control if we took it
+ if (_ctx->took_control)
+ control(CONTROL_ON);
+
+ // Restore conv window if applicable
+ if (to->event == CONVERSE)
+ convHide(false);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Runs glitter code associated with a polygon.
+ */
+void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc) {
+ TP_INIT to = { hPoly, event, be, tc, 0 };
+
+ CoroutineInstall(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+}
+
+void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor) {
+ TP_INIT to = { hPoly, event, BE_NONE, false, actor };
+
+ CoroutineInstall(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+}
+
+//-----------------------------------------------------------------------
+
+struct WP_INIT {
+ int x; // } Where to walk to
+ int y; // }
+};
+
+/**
+ * Perform a walk directly initiated by a click.
+ */
+static void WalkProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PMACTOR pActor;
+ CORO_END_CONTEXT(_ctx);
+
+ WP_INIT *to = (WP_INIT *)ProcessGetParamsSelf(); // get the co-ordinates - copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pActor = GetMover(LEAD_ACTOR);
+ if (_ctx->pActor->MActorState == NORM_MACTOR) {
+ assert(_ctx->pActor->hCpath != NOPOLY); // Lead actor is not in a path
+
+ GetToken(TOKEN_LEAD);
+ SetActorDest(_ctx->pActor, to->x, to->y, false, 0);
+ DontScrollCursor();
+
+ while (MAmoving(_ctx->pActor))
+ CORO_SLEEP(1);
+
+ FreeToken(TOKEN_LEAD);
+ }
+
+ CORO_END_CODE;
+}
+
+void walkto(int x, int y) {
+ WP_INIT to = { x, y };
+
+ CoroutineInstall(PID_TCODE, WalkProcess, &to, sizeof(to));
+}
+
+/**
+ * Run appropriate actor or polygon glitter code.
+ * If none, and it's a WALKTO event, do a walk.
+ */
+static void User_Event(USER_EVENT uEvent, BUTEVENT be) {
+ int actor;
+ int aniX, aniY;
+ HPOLYGON hPoly;
+
+ // Prevent activation of 2 events on the same tick
+ if (++eCount != 1)
+ return;
+
+ if ((actor = GetTaggedActor()) != 0)
+ actorEvent(actor, uEvent, be);
+ else if ((hPoly = GetTaggedPoly()) != NOPOLY)
+ RunPolyTinselCode(hPoly, uEvent, be, false);
+ else {
+ GetCursorXY(&aniX, &aniY, true);
+
+ // There could be a poly involved which has no tag.
+ if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY
+ || (hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY) {
+ RunPolyTinselCode(hPoly, uEvent, be, false);
+ } else if (uEvent == WALKTO)
+ walkto(aniX, aniY);
+ }
+}
+
+
+/**
+ * ProcessButEvent
+ */
+void ProcessButEvent(BUTEVENT be) {
+ IncUserEvents();
+
+ if (bSwapButtons) {
+ switch (be) {
+ case BE_SLEFT:
+ be = BE_SRIGHT;
+ break;
+ case BE_DLEFT:
+ be = BE_DRIGHT;
+ break;
+ case BE_SRIGHT:
+ be = BE_SLEFT;
+ break;
+ case BE_DRIGHT:
+ be = BE_DLEFT;
+ break;
+ case BE_LDSTART:
+ be = BE_RDSTART;
+ break;
+ case BE_LDEND:
+ be = BE_RDEND;
+ break;
+ case BE_RDSTART:
+ be = BE_LDSTART;
+ break;
+ case BE_RDEND:
+ be = BE_LDEND;
+ break;
+ default:
+ break;
+ }
+ }
+
+// if (be == BE_SLEFT || be == BE_DLEFT || be == BE_SRIGHT || be == BE_DRIGHT)
+ if (be == BE_SLEFT || be == BE_SRIGHT)
+ butEvents++;
+
+ if (!TestToken(TOKEN_CONTROL) && be != BE_LDEND)
+ return;
+
+ if (InventoryActive()) {
+ ButtonToInventory(be);
+ } else {
+ switch (be) {
+ case BE_SLEFT:
+ User_Event(WALKTO, BE_SLEFT);
+ break;
+
+ case BE_DLEFT:
+ User_Event(ACTION, BE_DLEFT);
+ break;
+
+ case BE_SRIGHT:
+ User_Event(LOOK, BE_SRIGHT);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * ProcessKeyEvent
+ */
+
+void ProcessKeyEvent(KEYEVENT ke) {
+ // This stuff to allow F1 key during startup.
+ if (bEnableF1 && ke == OPTION_KEY)
+ control(CONTROL_ON);
+ else
+ IncUserEvents();
+
+ if (ke == ESC_KEY) {
+ escEvents++;
+ butEvents++; // Yes, I do mean this
+ }
+
+ // FIXME: This comparison is weird - I added (BUTEVENT) cast for now to suppress warning
+ if (!TestToken(TOKEN_CONTROL) && (BUTEVENT)ke != BE_LDEND)
+ return;
+
+ switch (ke) {
+ case QUIT_KEY:
+ PopUpConf(QUIT);
+ break;
+
+ case OPTION_KEY:
+ PopUpConf(OPTION);
+ break;
+
+ case SAVE_KEY:
+ PopUpConf(SAVE);
+ break;
+
+ case LOAD_KEY:
+ PopUpConf(LOAD);
+ break;
+
+ case WALKTO_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_SLEFT);
+ else
+ User_Event(WALKTO, BE_NONE);
+ break;
+
+ case ACTION_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_DLEFT);
+ else
+ User_Event(ACTION, BE_NONE);
+ break;
+
+ case LOOK_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_SRIGHT);
+ else
+ User_Event(LOOK, BE_NONE);
+ break;
+
+ case ESC_KEY:
+ case PGUP_KEY:
+ case PGDN_KEY:
+ case HOME_KEY:
+ case END_KEY:
+ if (InventoryActive())
+ KeyToInventory(ke);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * For ESCapable Glitter sequences
+ */
+
+int GetEscEvents(void) {
+ return escEvents;
+}
+
+/**
+ * For cutting short talk()s etc.
+ */
+
+int GetLeftEvents(void) {
+ return butEvents;
+}
+
+/**
+ * For waitkey() Glitter function
+ */
+
+int getUserEvents(void) {
+ return userEvents;
+}
+
+uint32 getUserEventTime(void) {
+ return DwGetCurrentTime() - lastUserEvent;
+}
+
+void resetUserEventTime(void) {
+ lastUserEvent = DwGetCurrentTime();
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h
new file mode 100644
index 0000000000..bc49d68717
--- /dev/null
+++ b/engines/tinsel/events.h
@@ -0,0 +1,84 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * User events processing and utility functions
+ */
+
+#ifndef TINSEL_EVENTS_H
+#define TINSEL_EVENTS_H
+
+#include "tinsel/dw.h"
+#include "tinsel/coroutine.h"
+
+namespace Tinsel {
+
+enum BUTEVENT {
+ BE_NONE, BE_SLEFT, BE_DLEFT, BE_SRIGHT, BE_DRIGHT,
+ BE_LDSTART, BE_LDEND, BE_RDSTART, BE_RDEND,
+ BE_UNKNOWN
+};
+
+
+enum KEYEVENT {
+ ESC_KEY, QUIT_KEY, SAVE_KEY, LOAD_KEY, OPTION_KEY,
+ PGUP_KEY, PGDN_KEY, HOME_KEY, END_KEY,
+ WALKTO_KEY, ACTION_KEY, LOOK_KEY,
+ NOEVENT_KEY
+};
+
+
+/**
+ * Reasons for running Glitter code.
+ * Do not re-order these as equivalent CONSTs are defined in the master
+ * scene Glitter source file for testing against the event() library function.
+ */
+enum USER_EVENT {
+ POINTED, WALKTO, ACTION, LOOK,
+ ENTER, LEAVE, STARTUP, CONVERSE,
+ UNPOINT, PUTDOWN,
+ NOEVENT
+};
+
+
+void AllowDclick(CORO_PARAM, BUTEVENT be);
+bool GetControl(int param);
+
+void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc);
+void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor);
+
+void ProcessButEvent(BUTEVENT be);
+void ProcessKeyEvent(KEYEVENT ke);
+
+
+int GetEscEvents(void);
+int GetLeftEvents(void);
+int getUserEvents(void);
+
+uint32 getUserEventTime(void);
+void resetUserEventTime(void);
+
+void ResetEcount(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_EVENTS_H */
diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp
new file mode 100644
index 0000000000..9ac742800f
--- /dev/null
+++ b/engines/tinsel/faders.cpp
@@ -0,0 +1,175 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Fader and Flasher processes.
+ */
+
+#include "tinsel/pid.h" // list of all process IDs
+#include "tinsel/sched.h" // scheduler defs
+#include "tinsel/faders.h" // fader defs
+#include "tinsel/handle.h"
+#include "tinsel/palette.h" // Palette Manager defs
+
+namespace Tinsel {
+
+/** structure used by the "FadeProcess" process */
+struct FADE {
+ const long *pColourMultTable; // list of fixed point colour multipliers - terminated with negative entry
+ PPALQ pPalQ; // palette queue entry to fade
+};
+
+// fixed point fade multiplier tables
+//const long fadeout[] = {0xf000, 0xd000, 0xb000, 0x9000, 0x7000, 0x5000, 0x3000, 0x1000, 0, -1};
+const long fadeout[] = {0xd000, 0xa000, 0x7000, 0x4000, 0x1000, 0, -1};
+//const long fadein[] = {0, 0x1000, 0x3000, 0x5000, 0x7000, 0x9000, 0xb000, 0xd000, 0x10000L, -1};
+const long fadein[] = {0, 0x1000, 0x4000, 0x7000, 0xa000, 0xd000, 0x10000L, -1};
+
+/**
+ * Scale 'colour' by the fixed point colour multiplier 'colourMult'
+ * @param colour Colour to scale
+ * @param colourMult Fixed point multiplier
+ */
+static COLORREF ScaleColour(COLORREF colour, uint32 colourMult) {
+ // apply multiplier to RGB components
+ uint32 red = ((GetRValue(colour) * colourMult) << 8) >> 24;
+ uint32 green = ((GetGValue(colour) * colourMult) << 8) >> 24;
+ uint32 blue = ((GetBValue(colour) * colourMult) << 8) >> 24;
+
+ // return new colour
+ return RGB(red, green, blue);
+}
+
+/**
+ * Applies the fixed point multiplier 'mult' to all colours in
+ * 'pOrig' to produce 'pNew'. Each colour in the palette will be
+ * multiplied by 'mult'.
+ * @param pNew Pointer to new palette
+ * @param pOrig Pointer to original palette
+ * @param numColours Number of colours in the above palettes
+ * @param mult Fixed point multiplier
+ */
+static void FadePalette(COLORREF *pNew, COLORREF *pOrig, int numColours, uint32 mult) {
+ for (int i = 0; i < numColours; i++, pNew++, pOrig++) {
+ // apply multiplier to RGB components
+ *pNew = ScaleColour(*pOrig, mult);
+ }
+}
+
+/**
+ * Process to fade one palette.
+ * A pointer to a 'FADE' structure must be passed to this process when
+ * it is created.
+ */
+static void FadeProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ COLORREF fadeRGB[MAX_COLOURS]; // local copy of palette
+ const long *pColMult; // pointer to colour multiplier table
+ PALETTE *pPalette; // pointer to palette
+ CORO_END_CONTEXT(_ctx);
+
+ // get the fade data structure - copied to process when it was created
+ FADE *pFade = (FADE *)ProcessGetParamsSelf();
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // get pointer to palette - reduce pointer indirection a bit
+ _ctx->pPalette = (PALETTE *)LockMem(pFade->pPalQ->hPal);
+
+ for (_ctx->pColMult = pFade->pColourMultTable; *_ctx->pColMult >= 0; _ctx->pColMult++) {
+ // go through all multipliers in table - until a negative entry
+
+ // fade palette using next multiplier
+ FadePalette(_ctx->fadeRGB, _ctx->pPalette->palRGB,
+ FROM_LE_32(_ctx->pPalette->numColours), (uint32) *_ctx->pColMult);
+
+ // send new palette to video DAC
+ UpdateDACqueue(pFade->pPalQ->posInDAC, FROM_LE_32(_ctx->pPalette->numColours), _ctx->fadeRGB);
+
+ // allow time for video DAC to be updated
+ CORO_SLEEP(1);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Generic palette fader/unfader. Creates a 'FadeProcess' process
+ * for each palette that is to fade.
+ * @param multTable Fixed point colour multiplier table
+ * @param noFadeTable List of palettes not to fade
+ */
+static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) {
+ PPALQ pPal; // palette manager iterator
+
+ // create a process for each palette in the palette queue
+ for (pPal = GetNextPalette(NULL); pPal != NULL; pPal = GetNextPalette(pPal)) {
+ bool bFade = true;
+ // assume we want to fade this palette
+
+ // is palette in the list of palettes not to fade
+ if (noFadeTable != NULL) {
+ // there is a list of palettes not to fade
+ for (int i = 0; noFadeTable[i] != 0; i++) {
+ if (pPal->hPal == noFadeTable[i]) {
+ // palette is in the list - dont fade it
+ bFade = false;
+
+ // leave loop prematurely
+ break;
+ }
+ }
+ }
+
+ if (bFade) {
+ FADE fade;
+
+ // fill in FADE struct
+ fade.pColourMultTable = multTable;
+ fade.pPalQ = pPal;
+
+ // create a fader process for this palette
+ CoroutineInstall(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE));
+ }
+ }
+}
+
+/**
+ * Fades a list of palettes down to black.
+ * @param noFadeTable A NULL terminated list of palettes not to fade.
+ */
+void FadeOutFast(SCNHANDLE noFadeTable[]) {
+ // call generic fader
+ Fader(fadeout, noFadeTable);
+}
+
+/**
+ * Fades a list of palettes from black to their current colours.
+ * @param noFadeTable A NULL terminated list of palettes not to fade.
+ */
+void FadeInFast(SCNHANDLE noFadeTable[]) {
+ // call generic fader
+ Fader(fadein, noFadeTable);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/faders.h b/engines/tinsel/faders.h
new file mode 100644
index 0000000000..1e9336fae8
--- /dev/null
+++ b/engines/tinsel/faders.h
@@ -0,0 +1,55 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used by the fader and flasher processes
+ */
+
+#ifndef TINSEL_FADERS_H // prevent multiple includes
+#define TINSEL_FADERS_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+enum {
+ /**
+ * Number of iterations in a fade out.
+ * Must match which FadeOut() is in use.
+ */
+ COUNTOUT_COUNT = 6
+};
+
+/*----------------------------------------------------------------------*\
+|* Fader Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+// usefull palette faders - they all need a list of palettes that
+// should not be faded. This parameter can be
+// NULL - fade all palettes.
+
+void FadeOutFast(SCNHANDLE noFadeTable[]);
+void FadeInFast(SCNHANDLE noFadeTable[]);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_FADERS_H
diff --git a/engines/tinsel/film.h b/engines/tinsel/film.h
new file mode 100644
index 0000000000..c8bf4604bc
--- /dev/null
+++ b/engines/tinsel/film.h
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_FILM_H // prevent multiple includes
+#define TINSEL_FILM_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+struct FREEL {
+ SCNHANDLE mobj;
+ SCNHANDLE script;
+} PACKED_STRUCT;
+
+struct FILM {
+ int32 frate;
+ int32 numreels;
+ FREEL reels[1];
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/font.cpp b/engines/tinsel/font.cpp
new file mode 100644
index 0000000000..335ba031fe
--- /dev/null
+++ b/engines/tinsel/font.cpp
@@ -0,0 +1,96 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/object.h"
+#include "tinsel/text.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static char tBuffer[TBUFSZ];
+
+static SCNHANDLE hTagFont = 0, hTalkFont = 0;
+
+
+/**
+ * Return address of tBuffer
+ */
+char *tBufferAddr() {
+ return tBuffer;
+}
+
+/**
+ * Return hTagFont handle.
+ */
+SCNHANDLE hTagFontHandle() {
+ return hTagFont;
+}
+
+/**
+ * Return hTalkFont handle.
+ */
+SCNHANDLE hTalkFontHandle() {
+ return hTalkFont;
+}
+
+/**
+ * Called from dec_tagfont() Glitter function. Store the tag font handle.
+ */
+void TagFontHandle(SCNHANDLE hf) {
+ hTagFont = hf; // Store the font handle
+}
+
+/**
+ * Called from dec_talkfont() Glitter function.
+ * Store the talk font handle.
+ */
+void TalkFontHandle(SCNHANDLE hf) {
+ hTalkFont = hf; // Store the font handle
+}
+
+/**
+ * Poke the background palette into character 0's images.
+ */
+void fettleFontPal(SCNHANDLE fontPal) {
+ const FONT *pFont;
+ PIMAGE pImg;
+
+ assert(fontPal);
+ assert(hTagFont); // Tag font not declared
+ assert(hTalkFont); // Talk font not declared
+
+ pFont = (const FONT *)LockMem(hTagFont);
+ pImg = (PIMAGE)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0
+ pImg->hImgPal = TO_LE_32(fontPal);
+
+ pFont = (const FONT *)LockMem(hTalkFont);
+ pImg = (PIMAGE)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0
+ pImg->hImgPal = TO_LE_32(fontPal);
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/font.h b/engines/tinsel/font.h
new file mode 100644
index 0000000000..b75c36191c
--- /dev/null
+++ b/engines/tinsel/font.h
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_FONT_H // prevent multiple includes
+#define TINSEL_FONT_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// A temporary buffer for extracting text into is defined in font.c
+// Accessed using tBufferAddr(), this is how big it is:
+#define TBUFSZ 512
+
+
+char *tBufferAddr(void);
+SCNHANDLE hTagFontHandle(void);
+SCNHANDLE hTalkFontHandle(void);
+
+void TagFontHandle(SCNHANDLE hf);
+void TalkFontHandle(SCNHANDLE hf);
+void fettleFontPal(SCNHANDLE fontPal);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_FONT_H
diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp
new file mode 100644
index 0000000000..751b8929ef
--- /dev/null
+++ b/engines/tinsel/graphics.cpp
@@ -0,0 +1,437 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Low level graphics interface.
+ */
+
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h" // LockMem()
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+// Defines used in graphic drawing
+#define CHARPTR_OFFSET 16
+#define CHAR_WIDTH 4
+#define CHAR_HEIGHT 4
+
+extern uint8 transPalette[MAX_COLOURS];
+
+//----------------- SUPPORT FUNCTIONS ---------------------
+
+/**
+ * Straight rendering with transparency support
+ */
+
+static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
+ // Set up the offset between destination blocks
+ int rightClip = applyClipping ? pObj->rightClip : 0;
+ Common::Rect boxBounds;
+
+ if (applyClipping) {
+ // Adjust the height down to skip any bottom clipping
+ pObj->height -= pObj->botClip;
+
+ // Make adjustment for the top clipping row
+ srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
+ pObj->height -= pObj->topClip;
+ pObj->topClip %= 4;
+ }
+
+ // Vertical loop
+ while (pObj->height > 0) {
+ // Get the start of the next line output
+ uint8 *tempDest = destP;
+
+ // Get the line width, and figure out which row range within the 4 row high blocks
+ // will be displayed if clipping is to be taken into account
+ int width = pObj->width;
+
+ if (!applyClipping) {
+ // No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
+ boxBounds.top = 0;
+ boxBounds.bottom = 3;
+ boxBounds.left = 0;
+ } else {
+ // Handle any possible clipping at the top of the char block.
+ // We already handled topClip partially at the beginning of this function.
+ // Hence the only non-zero values it can assume at this point are 1,2,3,
+ // and that only during the very first iteration (i.e. when the top char
+ // block is drawn only partially). In particular, we set topClip to zero,
+ // as all following blocks are not to be top clipped.
+ boxBounds.top = pObj->topClip;
+ pObj->topClip = 0;
+
+ boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
+
+ // Handle any possible clipping at the start of the line
+ boxBounds.left = pObj->leftClip;
+ if (boxBounds.left >= 4) {
+ srcP += sizeof(uint16) * (boxBounds.left >> 2);
+ width -= boxBounds.left & 0xfffc;
+ boxBounds.left %= 4;
+ }
+
+ width -= boxBounds.left;
+ }
+
+ // Horizontal loop
+ while (width > rightClip) {
+ boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
+ assert(boxBounds.bottom >= boxBounds.top);
+ assert(boxBounds.right >= boxBounds.left);
+
+ int16 indexVal = READ_LE_UINT16(srcP);
+ srcP += sizeof(uint16);
+
+ if (indexVal >= 0) {
+ // Draw a 4x4 block based on the opcode as in index into the block list
+ const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4);
+ p += boxBounds.top * sizeof(uint32);
+ for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) {
+ Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
+ }
+
+ } else {
+ // Draw a 4x4 block with transparency support
+ indexVal &= 0x7fff;
+
+ // If index is zero, then skip drawing the block completely
+ if (indexVal > 0) {
+ // Use the index along with the object's translation offset
+ const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4);
+
+ // Loop through each pixel - only draw a pixel if it's non-zero
+ p += boxBounds.top * sizeof(uint32);
+ for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) {
+ p += boxBounds.left;
+ for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) {
+ if (*p)
+ *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p;
+ }
+ p += 3 - boxBounds.right;
+ }
+ }
+ }
+
+ tempDest += boxBounds.right - boxBounds.left + 1;
+ width -= 3 - boxBounds.left + 1;
+
+ // None of the remaining horizontal blocks should be left clipped
+ boxBounds.left = 0;
+ }
+
+ // If there is any width remaining, there must be a right edge clipping
+ if (width >= 0)
+ srcP += sizeof(uint16) * ((width + 3) >> 2);
+
+ // Move to next line line
+ pObj->height -= boxBounds.bottom - boxBounds.top + 1;
+ destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
+ }
+}
+
+/**
+ * Fill the destination area with a constant colour
+ */
+
+static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
+ if (applyClipping) {
+ pObj->height -= pObj->topClip + pObj->botClip;
+ pObj->width -= pObj->leftClip + pObj->rightClip;
+
+ if (pObj->width <= 0)
+ return;
+ }
+
+ // Loop through any remaining lines
+ while (pObj->height > 0) {
+ Common::set_to(destP, destP + pObj->width, pObj->constant);
+
+ --pObj->height;
+ destP += SCREEN_WIDTH;
+ }
+}
+
+/**
+ * Translates the destination surface within the object's bounds using the transparency
+ * lookup table from transpal.cpp (the contents of which have been moved into palette.cpp)
+ */
+
+static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
+ if (applyClipping) {
+ pObj->height -= pObj->topClip + pObj->botClip;
+ pObj->width -= pObj->leftClip + pObj->rightClip;
+
+ if (pObj->width <= 0)
+ return;
+ }
+
+ // Set up the offset between destination lines
+ int lineOffset = SCREEN_WIDTH - pObj->width;
+
+ // Loop through any remaining lines
+ while (pObj->height > 0) {
+ for (int i = 0; i < pObj->width; ++i, ++destP)
+ *destP = transPalette[*destP];
+
+ --pObj->height;
+ destP += lineOffset;
+ }
+}
+
+
+#if 0
+// This commented out code is the untested original WrtNonZero/ClpWrtNonZero combo method
+// from the v1 source. It may be needed to be included later on to support v1 gfx files
+
+/**
+ * Straight rendering with transparency support
+ * Possibly only used in the Discworld Demo
+ */
+
+static void DemoWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
+ // FIXME: If this method is used for the demo, it still needs to be made Endian safe
+
+ // Set up the offset between destination lines
+ pObj->lineoffset = SCREEN_WIDTH - pObj->width - (applyClipping ? pObj->leftClip - pObj->rightClip : 0);
+
+ // Top clipped line handling
+ while (applyClipping && (pObj->topClip > 0)) {
+ // Loop through discarding the data for the line
+ int width = pObj->width;
+ while (width > 0) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Dump the data
+ srcP += ((opcodeOrLen + 3) / 4) * 4;
+ width -= opcodeOrLen;
+ } else {
+ // Dump the run-length opcode
+ width -= -opcodeOrLen;
+ }
+ }
+
+ --pObj->height;
+ --pObj->topClip;
+ }
+
+ // Loop for the required number of rows
+ while (pObj->height > 0) {
+
+ int width = pObj->width;
+
+ // Handling for left edge clipping - this basically involves dumping data until we reach
+ // the part of the line to be displayed
+ int clipLeft = pObj->leftClip;
+ while (applyClipping && (clipLeft > 0)) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Copy a specified number of bytes
+ // Make adjustments for past the clipping width
+ int remainder = 4 - (opcodeOrLen % 4);
+ srcP += MIN(clipLeft, opcodeOrLen);
+ opcodeOrLen -= MIN(clipLeft, opcodeOrLen);
+ clipLeft -= MIN(clipLeft, opcodeOrLen);
+ width -= opcodeOrLen;
+
+
+ // Handle any right edge clipping (if run length covers entire width)
+ if (width < pObj->rightClip) {
+ remainder += (pObj->rightClip - width);
+ opcodeOrLen -= (pObj->rightClip - width);
+ }
+
+ if (opcodeOrLen > 0)
+ Common::copy(srcP, srcP + opcodeOrLen, destP);
+
+ } else {
+ // Output a run length number of bytes
+ // Get data for byte value and run length
+ opcodeOrLen = -opcodeOrLen;
+ int runLength = opcodeOrLen & 0xff;
+ uint8 colourVal = (opcodeOrLen >> 8) & 0xff;
+
+ // Make adjustments for past the clipping width
+ runLength -= MIN(clipLeft, runLength);
+ clipLeft -= MIN(clipLeft, runLength);
+ width -= runLength;
+
+ // Handle any right edge clipping (if run length covers entire width)
+ if (width < pObj->rightClip)
+ runLength -= (pObj->rightClip - width);
+
+ if (runLength > 0) {
+ // Displayable part starts partway through the slice
+ if (colourVal != 0)
+ Common::set_to(destP, destP + runLength, colourVal);
+ destP += runLength;
+ }
+ }
+
+ if (width < pObj->rightClip)
+ width = 0;
+ }
+
+ // Handling for the visible part of the line
+ int endWidth = applyClipping ? pObj->rightClip : 0;
+ while (width > endWidth) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Copy the specified number of bytes
+ int remainder = 4 - (opcodeOrLen % 4);
+
+ if (width < endWidth) {
+ // Shorten run length by right clipping
+ remainder += (pObj->rightClip - width);
+ opcodeOrLen -= (pObj->rightClip - width);
+ }
+
+ Common::copy(srcP, srcP + opcodeOrLen, destP);
+ srcP += opcodeOrLen + remainder;
+ destP += opcodeOrLen;
+ width -= opcodeOrLen;
+
+ } else {
+ // Handle a given run length
+ opcodeOrLen = -opcodeOrLen;
+ int runLength = opcodeOrLen & 0xff;
+ uint8 colourVal = (opcodeOrLen >> 8) & 0xff;
+
+ if (width < endWidth)
+ // Shorten run length by right clipping
+ runLength -= (pObj->rightClip - width);
+
+ // Only set pixels if colourVal non-zero (0 signifies transparency)
+ if (colourVal != 0)
+ // Fill out a run length of a specified colour
+ Common::set_to(destP, destP + runLength, colourVal);
+
+ destP += runLength;
+ width -= runLength;
+ }
+ }
+
+ // If right edge clipping is being applied, then width may still be non-zero - in
+ // that case all remaining line data until the end of the line must be ignored
+ while (width > 0) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Dump the data
+ srcP += ((opcodeOrLen + 3) / 4) * 4;
+ width -= opcodeOrLen;
+ } else {
+ // Dump the run-length opcode
+ width -= -opcodeOrLen;
+ }
+ }
+
+ --pObj->height;
+ destP += pObj->lineoffset;
+ }
+}
+#endif
+
+//----------------- MAIN FUNCTIONS ---------------------
+
+/**
+ * Clears both the screen surface buffer and screen to the specified value
+ */
+void ClearScreen(uint32 val) {
+ uint32 *pDest = (uint32 *)_vm->screen().getData();
+ Common::set_to(pDest, (uint32 *)((byte *)pDest + SCREEN_WIDTH * SCREEN_HEIGHT), val);
+ _vm->screen().update();
+}
+
+/**
+ * Updates the screen surface within the following rectangle
+ */
+void UpdateScreenRect(const Common::Rect &pClip) {
+ _vm->screen().updateRect(pClip);
+}
+
+/**
+ * Draws the specified object onto the screen surface buffer
+ */
+void DrawObject(DRAWOBJECT *pObj) {
+ uint8 *srcPtr = NULL;
+ uint8 *destPtr;
+
+ if ((pObj->width <= 0) || (pObj->height <= 0))
+ // Empty image, so return immediately
+ return;
+
+ // If writing constant data, don't bother locking the data pointer and reading src details
+ if ((pObj->flags & DMA_CONST) == 0) {
+ byte *p = (byte *)LockMem(pObj->hBits & 0xFF800000);
+
+ srcPtr = p + (pObj->hBits & 0x7FFFFF);
+ pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
+ pObj->transOffset = READ_LE_UINT32(p + 0x14);
+ }
+
+ // Get destination starting point
+ destPtr = _vm->screen().getBasePtr(pObj->xPos, pObj->yPos);
+
+ // Handle various draw types
+ uint8 typeId = pObj->flags & 0xff;
+ switch (typeId) {
+ case 0x01:
+ case 0x08:
+ case 0x41:
+ case 0x48:
+ WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40);
+ break;
+
+ case 0x04:
+ case 0x44:
+ // ClpWrtConst with/without clipping
+ WrtConst(pObj,destPtr, typeId == 0x44);
+ break;
+
+ case 0x84:
+ case 0xC4:
+ // WrtTrans with/without clipping
+ WrtTrans(pObj, destPtr, typeId == 0xC4);
+ break;
+
+ default:
+ // NoOp
+ error("Unknown drawing type %d", typeId);
+ break;
+ }
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/graphics.h b/engines/tinsel/graphics.h
new file mode 100644
index 0000000000..e080e08f58
--- /dev/null
+++ b/engines/tinsel/graphics.h
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Low level graphics interface.
+ */
+
+#ifndef TINSEL_GRAPHICS_H // prevent multiple includes
+#define TINSEL_GRAPHICS_H
+
+#include "tinsel/dw.h"
+
+#include "common/rect.h"
+#include "common/system.h"
+#include "graphics/surface.h"
+
+namespace Tinsel {
+
+struct PALQ;
+
+
+#define SCREEN_WIDTH 320 // PC screen dimensions
+#define SCREEN_HEIGHT 200
+#define SCRN_CENTRE_X ((SCREEN_WIDTH - 1) / 2) // screen centre x
+#define SCRN_CENTRE_Y ((SCREEN_HEIGHT - 1) / 2) // screen centre y
+
+/** Class representing either a buffered surface or the physical screen. */
+class Surface : public Graphics::Surface {
+private:
+ bool _isScreen;
+public:
+ Surface(bool isScreen = false) { _isScreen = isScreen; }
+ Surface(int Width, int Height) { create(Width, Height, 1); _isScreen = false; }
+
+ // Surface methods
+ byte *getData() { return (byte *)pixels; }
+ byte *getBasePtr(int x, int y) { return (byte *)Graphics::Surface::getBasePtr(x, y); }
+
+ void update() {
+ if (_isScreen) {
+ g_system->copyRectToScreen((const byte *)pixels, pitch, 0, 0, w, h);
+ g_system->updateScreen();
+ }
+ }
+ void updateRect(const Common::Rect &r) {
+ g_system->copyRectToScreen(getBasePtr(r.left, r.top), pitch, r.left, r.top, r.width(), r.height());
+ g_system->updateScreen();
+ }
+
+};
+
+/** draw object structure - only used when drawing objects */
+struct DRAWOBJECT {
+ char *charBase; // character set base address
+ int transOffset; // transparent character offset
+ int flags; // object flags - see above for list
+ PALQ *pPal; // objects palette Q position
+ int constant; // which colour in palette for monochrome objects
+ int width; // width of object
+ int height; // height of object
+ SCNHANDLE hBits; // image bitmap handle
+ int lineoffset; // offset to next line
+ int leftClip; // amount to clip off object left
+ int rightClip; // amount to clip off object right
+ int topClip; // amount to clip off object top
+ int botClip; // amount to clip off object bottom
+ short xPos; // x position of object
+ short yPos; // y position of object
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ClearScreen(uint32 val);
+void DrawObject(DRAWOBJECT *pObj);
+
+// called to update a rectangle on the video screen from a video page
+void UpdateScreenRect(const Common::Rect &pClip);
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp
new file mode 100644
index 0000000000..610f23012f
--- /dev/null
+++ b/engines/tinsel/handle.cpp
@@ -0,0 +1,366 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager code
+ */
+
+#define BODGE
+
+#include "common/file.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/scn.h" // name of "index" file
+#include "tinsel/handle.h"
+#include "tinsel/heapmem.h" // heap memory manager
+
+
+// these are included only so the relevant structs can be used in convertLEStructToNative()
+#include "tinsel/anim.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/film.h"
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/text.h"
+#include "tinsel/scene.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+bool bLockedScene = 0;
+#endif
+
+
+//----------------- LOCAL DEFINES --------------------
+
+struct MEMHANDLE {
+ char szName[12]; //!< 00 - file name of graphics file
+ int32 filesize; //!< 12 - file size and flags
+ PMEM_NODE pNode; //!< 16 - memory node for the graphics
+};
+
+
+/** memory allocation flags - stored in the top bits of the filesize field */
+enum {
+ fPreload = 0x01000000L, //!< preload memory
+ fDiscard = 0x02000000L, //!< discard memory
+ fSound = 0x04000000L, //!< sound data
+ fGraphic = 0x08000000L, //!< graphic data
+ fCompressed = 0x10000000L, //!< compressed data
+ fLoaded = 0x20000000L //!< set when file data has been loaded
+};
+#define FSIZE_MASK 0x00FFFFFFL //!< mask to isolate the filesize
+#define MALLOC_MASK 0xFF000000L //!< mask to isolate the memory allocation flags
+#define OFFSETMASK 0x007fffffL //!< get offset of address
+//#define HANDLEMASK 0xFF800000L //!< get handle of address
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+// handle table gets loaded from index file at runtime
+static MEMHANDLE *handleTable = 0;
+
+// number of handles in the handle table
+static uint numHandles = 0;
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void LoadFile(MEMHANDLE *pH, bool bWarn); // load a memory block as a file
+
+
+/**
+ * Loads the graphics handle table index file and preloads all the
+ * permanent graphics etc.
+ */
+void SetupHandleTable(void) {
+ enum { RECORD_SIZE = 20 };
+
+ int len;
+ uint i;
+ MEMHANDLE *pH;
+ Common::File f;
+
+ if (f.open(INDEX_FILENAME)) {
+ // get size of index file
+ len = f.size();
+
+ if (len > 0) {
+ if ((len % RECORD_SIZE) != 0) {
+ // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+
+ // calc number of handles
+ numHandles = len / RECORD_SIZE;
+
+ // allocate memory for the index file
+ handleTable = (MEMHANDLE *)calloc(numHandles, sizeof(struct MEMHANDLE));
+
+ // make sure memory allocated
+ assert(handleTable);
+
+ // load data
+ for (i = 0; i < numHandles; i++) {
+ f.read(handleTable[i].szName, 12);
+ handleTable[i].filesize = f.readUint32LE();
+ // The pointer should always be NULL. We don't
+ // need to read that from the file.
+ handleTable[i].pNode = NULL;
+ f.seek(4, SEEK_CUR);
+ }
+
+ if (f.ioFailed()) {
+ // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+
+ // close the file
+ f.close();
+ } else { // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+ } else { // cannot find the index file
+ error("Cannot find file %s", INDEX_FILENAME);
+ }
+
+ // allocate memory nodes and load all permanent graphics
+ for (i = 0, pH = handleTable; i < numHandles; i++, pH++) {
+ if (pH->filesize & fPreload) {
+ // allocate a fixed memory node for permanent files
+ pH->pNode = MemoryAlloc(DWM_FIXED, pH->filesize & FSIZE_MASK);
+
+ // make sure memory allocated
+ assert(pH->pNode);
+
+ // load the data
+ LoadFile(pH, true);
+ }
+#ifdef BODGE
+ else if ((pH->filesize & FSIZE_MASK) == 8) {
+ pH->pNode = NULL;
+ }
+#endif
+ else {
+ // allocate a discarded memory node for other files
+ pH->pNode = MemoryAlloc(
+ DWM_MOVEABLE | DWM_DISCARDABLE | DWM_NOALLOC,
+ pH->filesize & FSIZE_MASK);
+
+ // make sure memory allocated
+ assert(pH->pNode);
+ }
+ }
+}
+
+void FreeHandleTable(void) {
+ if (handleTable) {
+ free(handleTable);
+ handleTable = NULL;
+ }
+}
+
+/**
+ * Loads a memory block as a file.
+ * @param pH Memory block pointer
+ * @param bWarn If set, treat warnings as errors
+ */
+void LoadFile(MEMHANDLE *pH, bool bWarn) {
+ Common::File f;
+ char szFilename[sizeof(pH->szName) + 1];
+
+ if (pH->filesize & fCompressed) {
+ error("Compression handling has been removed!");
+ }
+
+ // extract and zero terminate the filename
+ strncpy(szFilename, pH->szName, sizeof(pH->szName));
+ szFilename[sizeof(pH->szName)] = 0;
+
+ if (f.open(szFilename)) {
+ // read the data
+ int bytes;
+ uint8 *addr;
+
+ if (pH->filesize & fPreload)
+ // preload - no need to lock the memory
+ addr = (uint8 *)pH->pNode;
+ else {
+ // discardable - lock the memory
+ addr = (uint8 *)MemoryLock(pH->pNode);
+ }
+#ifdef DEBUG
+ if (addr == NULL) {
+ if (pH->filesize & fPreload)
+ // preload - no need to lock the memory
+ addr = (uint8 *)pH->pNode;
+ else {
+ // discardable - lock the memory
+ addr = (uint8 *)MemoryLock(pH->pNode);
+ }
+ }
+#endif
+
+ // make sure address is valid
+ assert(addr);
+
+ bytes = f.read(addr, pH->filesize & FSIZE_MASK);
+
+ // close the file
+ f.close();
+
+ if ((pH->filesize & fPreload) == 0) {
+ // discardable - unlock the memory
+ MemoryUnlock(pH->pNode);
+ }
+
+ // set the loaded flag
+ pH->filesize |= fLoaded;
+
+ if (bytes == (pH->filesize & FSIZE_MASK)) {
+ return;
+ }
+
+ if (bWarn)
+ // file is corrupt
+ error("File %s is corrupt", szFilename);
+ }
+
+ if (bWarn)
+ // cannot find file
+ error("Cannot find file %s", szFilename);
+}
+
+/**
+ * Returns the address of a image, given its memory handle.
+ * @param offset Handle and offset to data
+ */
+uint8 *LockMem(SCNHANDLE offset) {
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ if (pH->filesize & fPreload) {
+ // permanent files are already loaded
+ return (uint8 *)pH->pNode + (offset & OFFSETMASK);
+ } else {
+ if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded))
+ // already allocated and loaded
+ return pH->pNode->pBaseAddr + (offset & OFFSETMASK);
+
+ if (pH->pNode->pBaseAddr == NULL)
+ // must have been discarded - reallocate the memory
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK,
+ DWM_MOVEABLE | DWM_DISCARDABLE);
+
+ if (pH->pNode->pBaseAddr == NULL)
+ error("Out of memory");
+
+ LoadFile(pH, true);
+
+ // make sure address is valid
+ assert(pH->pNode->pBaseAddr);
+
+ return pH->pNode->pBaseAddr + (offset & OFFSETMASK);
+ }
+}
+
+/**
+ * Called to make the current scene non-discardable.
+ * @param offset Handle and offset to data
+ */
+void LockScene(SCNHANDLE offset) {
+
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+#ifdef DEBUG
+ assert(!bLockedScene); // Trying to lock more than one scene
+#endif
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ // compact the heap to avoid fragmentation while scene is non-discardable
+ HeapCompact(MAX_INT, false);
+
+ if ((pH->filesize & fPreload) == 0) {
+ // change the flags for the node
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE);
+#ifdef DEBUG
+ bLockedScene = true;
+#endif
+ }
+}
+
+/**
+ * Called to make the current scene discardable again.
+ * @param offset Handle and offset to data
+ */
+void UnlockScene(SCNHANDLE offset) {
+
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ if ((pH->filesize & fPreload) == 0) {
+ // change the flags for the node
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE | DWM_DISCARDABLE);
+#ifdef DEBUG
+ bLockedScene = false;
+#endif
+ }
+}
+
+/*----------------------------------------------------------------------*/
+
+#ifdef BODGE
+
+/**
+ * Validates that a specified handle pointer is valid
+ * @param offset Handle and offset to data
+ */
+bool ValidHandle(SCNHANDLE offset) {
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ return (pH->filesize & FSIZE_MASK) != 8;
+}
+#endif
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h
new file mode 100644
index 0000000000..2cb1638d9d
--- /dev/null
+++ b/engines/tinsel/handle.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Graphics Memory Manager data structures
+ * TODO: This should really be named dos_hand.h, or the dos_hand.cpp should be renamed
+ */
+
+#ifndef TINSEL_HANDLE_H // prevent multiple includes
+#define TINSEL_HANDLE_H
+
+#include "tinsel/dw.h" // new data types
+
+namespace Tinsel {
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void SetupHandleTable(void); // Loads the graphics handle table index file and preloads all the permanent graphics etc.
+void FreeHandleTable(void);
+
+uint8 *LockMem( // returns the addr of a image, given its memory handle
+ SCNHANDLE offset); // handle and offset to data
+
+void LockScene( // Called to make the current scene non-discardable
+ SCNHANDLE offset); // handle and offset to data
+
+void UnlockScene( // Called to make the current scene discardable again
+ SCNHANDLE offset); // handle and offset to data
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_HANDLE_H
diff --git a/engines/tinsel/heapmem.cpp b/engines/tinsel/heapmem.cpp
new file mode 100644
index 0000000000..f3df3d4391
--- /dev/null
+++ b/engines/tinsel/heapmem.cpp
@@ -0,0 +1,594 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager code.
+ */
+
+#include "tinsel/heapmem.h"
+#include "tinsel/timers.h" // For DwGetCurrentTime
+
+namespace Tinsel {
+
+// minimum memory required for MS-DOS version of game
+#define MIN_MEM 2506752L
+
+// list of all memory nodes
+MEM_NODE mnodeList[NUM_MNODES];
+
+// pointer to the linked list of free mnodes
+static PMEM_NODE pFreeMemNodes;
+
+#ifdef DEBUG
+// diagnostic mnode counters
+static int numNodes;
+static int maxNodes;
+#endif
+
+// the mnode heap sentinel
+static MEM_NODE heapSentinel;
+
+//
+static PMEM_NODE AllocMemNode(void);
+
+
+/**
+ * Initialises the memory manager.
+ */
+void MemoryInit(void) {
+ PMEM_NODE pNode;
+
+#ifdef DEBUG
+ // clear number of nodes in use
+ numNodes = 0;
+#endif
+
+ // place first node on free list
+ pFreeMemNodes = mnodeList;
+
+ // link all other objects after first
+ for (int i = 1; i < NUM_MNODES; i++) {
+ mnodeList[i - 1].pNext = mnodeList + i;
+ }
+
+ // null the last mnode
+ mnodeList[NUM_MNODES - 1].pNext = NULL;
+
+ // allocatea big chunk of memory
+ const uint32 size = 2*MIN_MEM+655360L;
+ uint8 *mem = (uint8 *)malloc(size);
+ assert(mem);
+
+ // allocate a mnode for this memory
+ pNode = AllocMemNode();
+
+ // make sure mnode was allocated
+ assert(pNode);
+
+ // convert segment to memory address
+ pNode->pBaseAddr = mem;
+
+ // set size of the memory heap
+ pNode->size = size;
+
+ // clear the memory
+ memset(pNode->pBaseAddr, 0, size);
+
+ // set cyclic links to the sentinel
+ heapSentinel.pPrev = pNode;
+ heapSentinel.pNext = pNode;
+ pNode->pPrev = &heapSentinel;
+ pNode->pNext = &heapSentinel;
+
+ // flag sentinel as locked
+ heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of mnodes used at once.
+ */
+
+void MemoryStats(void) {
+ printf("%i mnodes of %i used.\n", maxNodes, NUM_MNODES);
+}
+#endif
+
+/**
+ * Allocate a mnode from the free list.
+ */
+static PMEM_NODE AllocMemNode(void) {
+ // get the first free mnode
+ PMEM_NODE pMemNode = pFreeMemNodes;
+
+ // make sure a mnode is available
+ assert(pMemNode); // Out of memory nodes
+
+ // the next free mnode
+ pFreeMemNodes = pMemNode->pNext;
+
+ // wipe out the mnode
+ memset(pMemNode, 0, sizeof(MEM_NODE));
+
+#ifdef DEBUG
+ // one more mnode in use
+ if (++numNodes > maxNodes)
+ maxNodes = numNodes;
+#endif
+
+ // return new mnode
+ return pMemNode;
+}
+
+/**
+ * Return a mnode back to the free list.
+ * @param pMemNode Node of the memory object
+ */
+void FreeMemNode(PMEM_NODE pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+#ifdef DEBUG
+ // one less mnode in use
+ --numNodes;
+ assert(numNodes >= 0);
+#endif
+
+ // place free list in mnode next
+ pMemNode->pNext = pFreeMemNodes;
+
+ // add mnode to top of free list
+ pFreeMemNodes = pMemNode;
+}
+
+
+/**
+ * Tries to make space for the specified number of bytes on the specified heap.
+ * @param size Number of bytes to free up
+ * @param bDiscard When set - will discard blocks to fullfill the request
+ */
+bool HeapCompact(long size, bool bDiscard) {
+ PMEM_NODE pHeap = &heapSentinel;
+ PMEM_NODE pPrev, pCur, pOldest;
+ long largest; // size of largest free block
+ uint32 oldest; // time of the oldest discardable block
+
+ while (true) {
+ bool bChanged;
+
+ do {
+ bChanged = false;
+ for (pPrev = pHeap->pNext, pCur = pPrev->pNext;
+ pCur != pHeap; pPrev = pCur, pCur = pCur->pNext) {
+ if (pPrev->flags == 0 && pCur->flags == 0) {
+ // set the changed flag
+ bChanged = true;
+
+ // both blocks are free - merge them
+ pPrev->size += pCur->size;
+
+ // unlink the current mnode
+ pPrev->pNext = pCur->pNext;
+ pCur->pNext->pPrev = pPrev;
+
+ // free the current mnode
+ FreeMemNode(pCur);
+
+ // leave the loop
+ break;
+ } else if ((pPrev->flags & (DWM_MOVEABLE | DWM_LOCKED | DWM_DISCARDED)) == DWM_MOVEABLE
+ && pCur->flags == 0) {
+ // a free block after a moveable block - swap them
+
+ // set the changed flag
+ bChanged = true;
+
+ // move the unlocked blocks data up (can overlap)
+ memmove(pPrev->pBaseAddr + pCur->size,
+ pPrev->pBaseAddr, pPrev->size);
+
+ // swap the order in the linked list
+ pPrev->pPrev->pNext = pCur;
+ pCur->pNext->pPrev = pPrev;
+
+ pCur->pPrev = pPrev->pPrev;
+ pPrev->pPrev = pCur;
+
+ pPrev->pNext = pCur->pNext;
+ pCur->pNext = pPrev;
+
+ pCur->pBaseAddr = pPrev->pBaseAddr;
+ pPrev->pBaseAddr += pCur->size;
+
+ // leave the loop
+ break;
+ }
+ }
+ } while (bChanged);
+
+ // find the largest free block
+ for (largest = 0, pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
+ if (pCur->flags == 0 && pCur->size > largest)
+ largest = pCur->size;
+ }
+
+ if (largest >= size)
+ // we have freed enough memory
+ return true;
+
+ if (!bDiscard)
+ // we cannot free enough without discarding blocks
+ return false;
+
+ // find the oldest discardable block
+ oldest = DwGetCurrentTime();
+ pOldest = NULL;
+ for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
+ if ((pCur->flags & (DWM_DISCARDABLE | DWM_DISCARDED | DWM_LOCKED))
+ == DWM_DISCARDABLE) {
+ // found a non-discarded discardable block
+ if (pCur->lruTime < oldest) {
+ oldest = pCur->lruTime;
+ pOldest = pCur;
+ }
+ }
+ }
+
+ if (pOldest)
+ // discard the oldest block
+ MemoryDiscard(pOldest);
+ else
+ // cannot discard any blocks
+ return false;
+ }
+}
+
+/**
+ * Allocates the specified number of bytes from the heap.
+ * @param flags Allocation attributes
+ * @param size Number of bytes to allocate
+ */
+PMEM_NODE MemoryAlloc(int flags, long size) {
+ PMEM_NODE pHeap = &heapSentinel;
+ PMEM_NODE pNode;
+ bool bCompacted = true; // set when heap has been compacted
+
+ // compact the heap if we are allocating fixed memory
+ if (flags & DWM_FIXED)
+ HeapCompact(MAX_INT, false);
+
+ while ((flags & DWM_NOALLOC) == 0 && bCompacted) {
+ // search the heap for a free block
+
+ for (pNode = pHeap->pNext; pNode != pHeap; pNode = pNode->pNext) {
+ if (pNode->flags == 0 && pNode->size >= size) {
+ // a free block of the required size
+ pNode->flags = flags;
+
+ // update the LRU time
+ pNode->lruTime = DwGetCurrentTime() + 1;
+
+ if (pNode->size == size) {
+ // an exact fit
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ if (flags & DWM_FIXED)
+ // lock the memory
+ return (PMEM_NODE)MemoryLock(pNode);
+ else
+ // just return the node
+ return pNode;
+ } else {
+ // allocate a node for the remainder of the free block
+ PMEM_NODE pTemp = AllocMemNode();
+
+ // calc size of the free block
+ long freeSize = pNode->size - size;
+
+ // set size of free block
+ pTemp->size = freeSize;
+
+ // set size of node
+ pNode->size = size;
+
+ if (flags & DWM_FIXED) {
+ // place the free node after pNode
+ pTemp->pBaseAddr = pNode->pBaseAddr + size;
+ pTemp->pNext = pNode->pNext;
+ pTemp->pPrev = pNode;
+ pNode->pNext->pPrev = pTemp;
+ pNode->pNext = pTemp;
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ return (PMEM_NODE)MemoryLock(pNode);
+ } else {
+ // place the free node before pNode
+ pTemp->pBaseAddr = pNode->pBaseAddr;
+ pNode->pBaseAddr += freeSize;
+ pTemp->pNext = pNode;
+ pTemp->pPrev = pNode->pPrev;
+ pNode->pPrev->pNext = pTemp;
+ pNode->pPrev = pTemp;
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ return pNode;
+ }
+ }
+ }
+ }
+ // compact the heap if we get to here
+ bCompacted = HeapCompact(size, (flags & DWM_NOCOMPACT) ? false : true);
+ }
+
+ // not allocated a block if we get to here
+ if (flags & DWM_DISCARDABLE) {
+ // chain a discarded node onto the end of the heap
+ pNode = AllocMemNode();
+ pNode->flags = flags | DWM_DISCARDED;
+
+ // set mnode at the end of the list
+ pNode->pPrev = pHeap->pPrev;
+ pNode->pNext = pHeap;
+
+ // fix links to this mnode
+ pHeap->pPrev->pNext = pNode;
+ pHeap->pPrev = pNode;
+
+ // return the discarded node
+ return pNode;
+ }
+
+ // could not allocate a block
+ return NULL;
+}
+
+/**
+ * Discards the specified memory object.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryDiscard(PMEM_NODE pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // object must be discardable
+ assert(pMemNode->flags & DWM_DISCARDABLE);
+
+ // object cannot be locked
+ assert((pMemNode->flags & DWM_LOCKED) == 0);
+
+ if ((pMemNode->flags & DWM_DISCARDED) == 0) {
+ // allocate a free node to replace this node
+ PMEM_NODE pTemp = AllocMemNode();
+
+ // copy node data
+ memcpy(pTemp, pMemNode, sizeof(MEM_NODE));
+
+ // flag as a free block
+ pTemp->flags = 0;
+
+ // link in the free node
+ pTemp->pPrev->pNext = pTemp;
+ pTemp->pNext->pPrev = pTemp;
+
+ // discard the node
+ pMemNode->flags |= DWM_DISCARDED;
+ pMemNode->pBaseAddr = NULL;
+ pMemNode->size = 0;
+
+ // and place it at the end of the heap
+ while ((pTemp->flags & DWM_SENTINEL) != DWM_SENTINEL)
+ pTemp = pTemp->pNext;
+
+ // pTemp now points to the heap sentinel
+ // set mnode at the end of the list
+ pMemNode->pPrev = pTemp->pPrev;
+ pMemNode->pNext = pTemp;
+
+ // fix links to this mnode
+ pTemp->pPrev->pNext = pMemNode;
+ pTemp->pPrev = pMemNode;
+ }
+}
+
+/**
+ * Frees the specified memory object and invalidates its node.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryFree(PMEM_NODE pMemNode) {
+ PMEM_NODE pPrev, pNext;
+
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // get pointer to the next mnode
+ pNext = pMemNode->pNext;
+
+ // get pointer to the previous mnode
+ pPrev = pMemNode->pPrev;
+
+ if (pPrev->flags == 0) {
+ // there is a previous free mnode
+ pPrev->size += pMemNode->size;
+
+ // unlink this mnode
+ pPrev->pNext = pNext; // previous to next
+ pNext->pPrev = pPrev; // next to previous
+
+ // free this mnode
+ FreeMemNode(pMemNode);
+
+ pMemNode = pPrev;
+ }
+ if (pNext->flags == 0) {
+ // the next mnode is free
+ pMemNode->size += pNext->size;
+
+ // flag as a free block
+ pMemNode->flags = 0;
+
+ // unlink the next mnode
+ pMemNode->pNext = pNext->pNext;
+ pNext->pNext->pPrev = pMemNode;
+
+ // free the next mnode
+ FreeMemNode(pNext);
+ }
+}
+
+/**
+ * Locks a memory object and returns a pointer to the first byte
+ * of the objects memory block.
+ * @param pMemNode Node of the memory object
+ */
+void *MemoryLock(PMEM_NODE pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // make sure memory object is not already locked
+ assert((pMemNode->flags & DWM_LOCKED) == 0);
+
+ // check for a discarded or null memory object
+ if ((pMemNode->flags & DWM_DISCARDED) || pMemNode->size == 0)
+ return NULL;
+
+ // set the lock flag
+ pMemNode->flags |= DWM_LOCKED;
+
+ // return memory objects base address
+ return pMemNode->pBaseAddr;
+}
+
+/**
+ * Changes the size or attributes of a specified memory object.
+ * @param pMemNode Node of the memory object
+ * @param size New size of block
+ * @param flags How to reallocate the object
+ */
+PMEM_NODE MemoryReAlloc(PMEM_NODE pMemNode, long size, int flags) {
+ PMEM_NODE pNew;
+
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // validate the flags
+ // cannot be fixed and moveable
+ assert((flags & (DWM_FIXED | DWM_MOVEABLE)) != (DWM_FIXED | DWM_MOVEABLE));
+
+ // cannot be fixed and discardable
+ assert((flags & (DWM_FIXED | DWM_DISCARDABLE)) != (DWM_FIXED | DWM_DISCARDABLE));
+
+ // must be fixed or moveable
+ assert(flags & (DWM_FIXED | DWM_MOVEABLE));
+
+ // align the size to machine boundary requirements
+ size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1);
+
+ // validate the size
+ assert(size);
+
+ // make sure we want the node on the same heap
+ assert((flags & (DWM_SOUND | DWM_GRAPHIC)) == (pMemNode->flags & (DWM_SOUND | DWM_GRAPHIC)));
+
+ if (size == pMemNode->size) {
+ // must be just a change in flags
+
+ // update the nodes flags
+ pMemNode->flags = flags;
+ } else {
+ // unlink the mnode from the current heap
+ pMemNode->pNext->pPrev = pMemNode->pPrev;
+ pMemNode->pPrev->pNext = pMemNode->pNext;
+
+ // allocate a new node
+ pNew = MemoryAlloc((flags & ~DWM_FIXED) | DWM_MOVEABLE, size);
+
+ // make sure memory allocated
+ assert(pNew != NULL);
+
+ // update the nodes flags
+ pNew->flags = flags;
+
+ // copy the node to the current node
+ memcpy(pMemNode, pNew, sizeof(MEM_NODE));
+
+ // relink the mnode into the list
+ pMemNode->pPrev->pNext = pMemNode;
+ pMemNode->pNext->pPrev = pMemNode;
+
+ // free the new node
+ FreeMemNode(pNew);
+ }
+
+ if (flags & DWM_FIXED)
+ // lock the memory
+ return (PMEM_NODE)MemoryLock(pMemNode);
+ else
+ // just return the node
+ return pMemNode;
+}
+
+/**
+ * Unlocks a memory object.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryUnlock(PMEM_NODE pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // make sure memory object is already locked
+ assert(pMemNode->flags & DWM_LOCKED);
+
+ // clear the lock flag
+ pMemNode->flags &= ~DWM_LOCKED;
+
+ // update the LRU time
+ pMemNode->lruTime = DwGetCurrentTime();
+}
+
+/**
+ * Retrieves the mnode associated with the specified pointer to a memory object.
+ * @param pMem Address of memory object
+ */
+PMEM_NODE MemoryHandle(void *pMem) {
+ PMEM_NODE pNode;
+ // search the DOS heap
+ for (pNode = heapSentinel.pNext; pNode != &heapSentinel; pNode = pNode->pNext) {
+ if (pNode->pBaseAddr == pMem)
+ // found it
+ return pNode;
+ }
+
+ // not found if we get to here
+ return NULL;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/heapmem.h b/engines/tinsel/heapmem.h
new file mode 100644
index 0000000000..c5d022b7f9
--- /dev/null
+++ b/engines/tinsel/heapmem.h
@@ -0,0 +1,110 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager defines
+ */
+
+#ifndef TINSEL_HEAPMEM_H
+#define TINSEL_HEAPMEM_H
+
+#include "tinsel/dw.h" // new data types
+
+namespace Tinsel {
+
+#define NUM_MNODES 192 // the number of memory management nodes (was 128, then 192)
+
+struct MEM_NODE {
+ MEM_NODE *pNext; // link to the next node in the list
+ MEM_NODE *pPrev; // link to the previous node in the list
+ uint8 *pBaseAddr; // base address of the memory object
+ long size; // size of the memory object
+ uint32 lruTime; // time when memory object was last accessed
+ int flags; // allocation attributes
+};
+typedef MEM_NODE *PMEM_NODE;
+
+// allocation flags for the MemoryAlloc function
+#define DWM_FIXED 0x0001 // allocates fixed memory
+#define DWM_MOVEABLE 0x0002 // allocates movable memory
+#define DWM_DISCARDABLE 0x0004 // allocates discardable memory
+#define DWM_NOALLOC 0x0008 // when used with discardable memory - allocates a discarded block
+#define DWM_NOCOMPACT 0x0010 // does not discard memory to satisfy the allocation request
+#define DWM_ZEROINIT 0x0020 // initialises memory contents to zero
+#define DWM_SOUND 0x0040 // allocate from the sound pool
+#define DWM_GRAPHIC 0x0080 // allocate from the graphics pool
+
+// return value from the MemoryFlags function
+#define DWM_DISCARDED 0x0100 // the objects memory block has been discarded
+
+// internal allocation flags
+#define DWM_LOCKED 0x0200 // the objects memory block is locked
+#define DWM_SENTINEL 0x0400 // the objects memory block is a sentinel
+
+
+/*----------------------------------------------------------------------*\
+|* Memory Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void MemoryInit(void); // initialises the memory manager
+
+#ifdef DEBUG
+void MemoryStats(void); // Shows the maximum number of mnodes used at once
+#endif
+
+PMEM_NODE MemoryAlloc( // allocates the specified number of bytes from the heap
+ int flags, // allocation attributes
+ long size); // number of bytes to allocate
+
+void MemoryDiscard( // discards the specified memory object
+ PMEM_NODE pMemNode); // node of the memory object
+
+int MemoryFlags( // returns information about the specified memory object
+ PMEM_NODE pMemNode); // node of the memory object
+
+void MemoryFree( // frees the specified memory object and invalidates its node
+ PMEM_NODE pMemNode); // node of the memory object
+
+PMEM_NODE MemoryHandle( // Retrieves the mnode associated with the specified pointer to a memory object
+ void *pMem); // address of memory object
+
+void *MemoryLock( // locks a memory object and returns a pointer to the first byte of the objects memory block
+ PMEM_NODE pMemNode); // node of the memory object
+
+PMEM_NODE MemoryReAlloc( // changes the size or attributes of a specified memory object
+ PMEM_NODE pMemNode, // node of the memory object
+ long size, // new size of block
+ int flags); // how to reallocate the object
+
+long MemorySize( // returns the size, in bytes, of the specified memory object
+ PMEM_NODE pMemNode); // node of the memory object
+
+void MemoryUnlock( // unlocks a memory object
+ PMEM_NODE pMemNode); // node of the memory object
+
+bool HeapCompact( // Allocates the specified number of bytes from the specified heap
+ long size, // number of bytes to free up
+ bool bDiscard); // when set - will discard blocks to fullfill the request
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/inventory.cpp b/engines/tinsel/inventory.cpp
new file mode 100644
index 0000000000..96ee01edf6
--- /dev/null
+++ b/engines/tinsel/inventory.cpp
@@ -0,0 +1,4533 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles the inventory and conversation windows.
+ *
+ * And the save/load game windows. Some of this will be platform
+ * specific - I'll try to separate this ASAP.
+ *
+ * And there's still a bit of tidying and commenting to do yet.
+ */
+
+//#define USE_3FLAGS 1
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/music.h"
+#include "tinsel/polygons.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/serializer.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinsel.h" // For engine access
+#include "tinsel/token.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+// In DOS_DW.C
+extern bool bRestart; // restart flag - set to restart the game
+
+#ifdef MAC_OPTIONS
+// In MAC_SOUND.C
+extern int volMaster;
+#endif
+
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// Tag functions in PDISPLAY.C
+extern void EnableTags(void);
+extern void DisableTags(void);
+
+
+//----------------- LOCAL DEFINES --------------------
+
+//#define ALL_CURSORS
+
+#define INV_PICKUP BE_SLEFT // Local names
+#define INV_LOOK BE_SRIGHT // for button events
+#define INV_ACTION BE_DLEFT //
+
+
+// For SlideSlider() and similar
+enum SSFN {
+ S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN
+};
+
+/** attribute values - may become bit field if further attributes are added */
+enum {
+ IO_ONLYINV1 = 0x01,
+ IO_ONLYINV2 = 0x02,
+ IO_DROPCODE = 0x04
+};
+
+//-----------------------
+// Moveable window translucent rectangle position limits
+enum {
+ MAXLEFT = 315, //
+ MINRIGHT = 3, // These values keep 2 pixcells
+ MINTOP = -13, // of header on the screen.
+ MAXTOP = 195 //
+};
+
+//-----------------------
+// Indices into winPartsf's reels
+#define IX_SLIDE 0 // Slider
+#define IX_V26 1
+#define IX_V52 2
+#define IX_V78 3
+#define IX_V104 4
+#define IX_V130 5
+#define IX_H26 6
+#define IX_H52 7
+#define IX_H78 8
+#define IX_H104 9
+#define IX_H130 10
+#define IX_H156 11
+#define IX_H182 12
+#define IX_H208 13
+#define IX_H234 14
+#define IX_TL 15 // Top left corner
+#define IX_TR 16 // Top right corner
+#define IX_BL 17 // Bottom left corner
+#define IX_BR 18 // Bottom right corner
+#define IX_H25 19
+#define IX_V11 20
+#define IX_RTL 21 // Re-sizing top left corner
+#define IX_RTR 22 // Re-sizing top right corner
+#define IX_RBR 23 // Re-sizing bottom right corner
+#define IX_CURLR 24 // }
+#define IX_CURUD 25 // }
+#define IX_CURDU 26 // } Custom cursors
+#define IX_CURDD 27 // }
+#define IX_CURUP 28 // }
+#define IX_CURDOWN 29 // }
+#define IX_MDGROOVE 30 // 'Mixing desk' slider background
+#define IX_MDSLIDER 34 // 'Mixing desk' slider
+
+#define IX_BLANK1 35 //
+#define IX_BLANK2 36 //
+#define IX_BLANK3 37 //
+#define IX_CIRCLE1 38 //
+#define IX_CIRCLE2 39 //
+#define IX_CROSS1 40 //
+#define IX_CROSS2 41 //
+#define IX_CROSS3 42 //
+#define IX_QUIT1 43 //
+#define IX_QUIT2 44 //
+#define IX_QUIT3 45 //
+#define IX_TICK1 46 //
+#define IX_TICK2 47 //
+#define IX_TICK3 48 //
+#define IX_NTR 49 // New top right corner
+#define HOPEDFORREELS 50
+
+#define NORMGRAPH 0
+#define DOWNGRAPH 1
+#define HIGRAPH 2
+//-----------------------
+#define FIX_UK 0
+#define FIX_FR 1
+#define FIX_GR 2
+#define FIX_IT 3
+#define FIX_SP 4
+#define FIX_USA 5
+#define HOPEDFORFREELS 6 // Expected flag reels
+//-----------------------
+
+#define MAX_ININV 150 // Max in an inventory
+#define MAX_CONVBASIC 10 // Max permanent conversation icons
+
+#define MAXHICONS 10 // Max dimensions of
+#define MAXVICONS 6 // an inventory window
+
+#define ITEM_WIDTH 25 // Dimensions of an icon
+#define ITEM_HEIGHT 25 //
+
+// Number of objects that makes up an empty window
+#define MAX_WCOMP 21 // 4 corners + (3+3) sides + (2+2) extra sides
+ // + Bground + title + slider
+ // + more Needed for save game window
+
+#define MAX_ICONS MAXHICONS*MAXVICONS
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+//----- Permanent data (compiled in) -----
+
+// Save game name editing cursor
+
+#define CURSOR_CHAR '_'
+char sCursor[2] = { CURSOR_CHAR, 0 };
+static const int hFillers[MAXHICONS] = {
+ IX_H26, // 2 icons wide
+ IX_H52, // 3
+ IX_H78, // 4
+ IX_H104, // 5
+ IX_H130, // 6
+ IX_H156, // 7
+ IX_H182, // 8
+ IX_H208, // 9
+ IX_H234 // 10 icons wide
+};
+static const int vFillers[MAXVICONS] = {
+ IX_V26, // 2 icons high
+ IX_V52, // 3
+ IX_V78, // 4
+ IX_V104, // 5
+ IX_V130 // 6 icons high
+};
+
+
+//----- Permanent data (set once) -----
+
+static SCNHANDLE winPartsf = 0; // Window members and cursors' graphic data
+static SCNHANDLE flagFilm = 0; // Window members and cursors' graphic data
+static SCNHANDLE configStrings[20];
+
+static PINV_OBJECT pio = 0; // Inventory objects' data
+static int numObjects = 0; // Number of inventory objects
+
+
+//----- Permanent data (updated, valid while inventory closed) -----
+
+static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} InventoryState;
+
+static int HeldItem = INV_NOICON; // Current held item
+
+struct INV_DEF {
+
+ int MinHicons; // }
+ int MinVicons; // } Dimension limits
+ int MaxHicons; // }
+ int MaxVicons; // }
+
+ int NoofHicons; // }
+ int NoofVicons; // } Current dimentsions
+
+ int ItemOrder[MAX_ININV]; // Contained items
+ int NoofItems; // Current number of held items
+
+ int FirstDisp; // Index to first item currently displayed
+
+ int inventoryX; // } Display position
+ int inventoryY; // }
+ int otherX; // } Display position
+ int otherY; // }
+
+ int MaxInvObj; // Max. allowed contents
+
+ SCNHANDLE hInvTitle; // Window heading
+
+ bool resizable; // Re-sizable window?
+ bool moveable; // Moveable window?
+
+ int sNoofHicons; // }
+ int sNoofVicons; // } Current dimensions
+
+ bool bMax; // Maximised last time open?
+
+};
+typedef INV_DEF *PINV_DEF;
+
+static INV_DEF InvD[NUM_INV]; // Conversation + 2 inventories + ...
+
+
+// Permanent contents of conversation inventory
+static int Inv0Order[MAX_CONVBASIC]; // Basic items i.e. permanent contents
+static int Num0Order = 0; // - copy to conv. inventory at pop-up time
+
+
+
+//----- Data pertinant to current active inventory -----
+
+static int ino = 0; // Which inventory is currently active
+
+static bool InventoryHidden = false;
+static bool InventoryMaximised = false;
+
+static enum { ID_NONE, ID_MOVE, ID_SLIDE,
+ ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT,
+ ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT,
+ ID_CSLIDE, ID_MDCONT } InvDragging;
+
+static int SuppH = 0; // 'Linear' element of
+static int SuppV = 0; // dimensions during re-sizing
+
+static int Ychange = 0; //
+static int Ycompensate = 0; // All to do with re-sizing.
+static int Xchange = 0; //
+static int Xcompensate = 0; //
+
+static bool ItemsChanged = 0; // When set, causes items to be re-drawn
+
+static bool bOpenConf = 0;
+
+static int TL = 0, TR = 0, BL = 0, BR = 0; // Used during window construction
+static int TLwidth = 0, TLheight = 0; //
+static int TRwidth = 0; //
+static int BLheight = 0; //
+
+
+
+static OBJECT *objArray[MAX_WCOMP]; // Current display objects (window)
+static OBJECT *iconArray[MAX_ICONS]; // Current display objects (icons)
+static ANIM iconAnims[MAX_ICONS];
+static OBJECT *DobjArray[MAX_WCOMP]; // Current display objects (re-sizing window)
+
+static OBJECT *RectObject = 0, *SlideObject = 0; // Current display objects, for reference
+ // objects are in objArray.
+
+static int slideY = 0; // For positioning the slider
+static int slideYmax = 0, slideYmin = 0; //
+
+// Also to do with the slider
+static struct { int n; int y; } slideStuff[MAX_ININV+1];
+
+#define MAXSLIDES 4
+struct MDSLIDES {
+ int num;
+ OBJECT *obj;
+ int min, max;
+};
+static MDSLIDES mdSlides[MAXSLIDES];
+static int numMdSlides = 0;
+
+static int GlitterIndex = 0;
+
+static HPOLYGON thisConvPoly = 0; // Conversation code is in a polygon code block
+static int thisConvIcon = 0; // Passed to polygon code via convIcon()
+static int pointedIcon = INV_NOICON; // used by InvLabels - icon pointed to on last call
+static volatile int PointedWaitCount = 0; // used by InvTinselProcess - fix the 'repeated pressing bug'
+static int sX = 0; // used by SlideMSlider() - current x-coordinate
+static int lX = 0; // used by SlideMSlider() - last x-coordinate
+
+//----- Data pertinant to configure (incl. load/save game) -----
+
+#define COL_MAINBOX TBLUE1 // Base blue colour
+#define COL_BOX TBLUE1
+#define COL_HILIGHT TBLUE4
+
+#ifdef JAPAN
+#define BOX_HEIGHT 17
+#define EDIT_BOX1_WIDTH 149
+#else
+#define BOX_HEIGHT 13
+#define EDIT_BOX1_WIDTH 145
+#endif
+#define EDIT_BOX2_WIDTH 166
+
+// RGROUP Radio button group - 1 is selectable at a time. Action on double click
+// ARSBUT Action if a radio button is selected
+// AABUT Action always
+// AATBUT Action always, text box
+// AAGBUT Action always, graphic button
+// SLIDER Not a button at all
+typedef enum {RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER,
+ TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING} BTYPE;
+
+typedef enum {NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN,
+ OPENLOAD, OPENSAVE, OPENREST,
+ OPENSOUND, OPENCONT,
+#ifndef JAPAN
+ OPENSUBT,
+#endif
+ OPENQUIT,
+ INITGAME, MIDIVOL,
+ CLANG, RLANG
+#ifdef MAC_OPTIONS
+ , MASTERVOL, SAMPVOL
+#endif
+ } BFUNC;
+
+typedef struct {
+ BTYPE boxType;
+ BFUNC boxFunc;
+ char *boxText;
+ int ixText;
+ int xpos;
+ int ypos;
+ int w; // Doubles as max value for SLIDERs
+ int h; // Doubles as iteration size for SLIDERs
+ int *ival;
+ int bi; // Base index for AAGBUTs
+} CONFBOX, *PCONFBOX;
+
+
+#define NO_HEADING (-1)
+#define USE_POINTER (-1)
+#define SIX_LOAD_OPTION 0
+#define SIX_SAVE_OPTION 1
+#define SIX_RESTART_OPTION 2
+#define SIX_SOUND_OPTION 3
+#define SIX_CONTROL_OPTION 4
+#ifndef JAPAN
+#define SIX_SUBTITLES_OPTION 5
+#endif
+#define SIX_QUIT_OPTION 6
+#define SIX_RESUME_OPTION 7
+#define SIX_LOAD_HEADING 8
+#define SIX_SAVE_HEADING 9
+#define SIX_RESTART_HEADING 10
+#define SIX_MVOL_SLIDER 11
+#define SIX_SVOL_SLIDER 12
+#define SIX_VVOL_SLIDER 13
+#define SIX_DCLICK_SLIDER 14
+#define SIX_DCLICK_TEST 15
+#define SIX_SWAP_TOGGLE 16
+#define SIX_TSPEED_SLIDER 17
+#define SIX_STITLE_TOGGLE 18
+#define SIX_QUIT_HEADING 19
+
+
+/*-------------------------------------------------------------*\
+| This is the main menu (that comes up when you hit F1 on a PC) |
+\*-------------------------------------------------------------*/
+
+#ifdef JAPAN
+#define FBY 11 // y-offset of first button
+#define FBX 13 // x-offset of first button
+#else
+#define FBY 20 // y-offset of first button
+#define FBX 15 // x-offset of first button
+#endif
+
+CONFBOX optionBox[] = {
+
+ { AATBUT, OPENLOAD, NULL, SIX_LOAD_OPTION, FBX, FBY, EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENSAVE, NULL, SIX_SAVE_OPTION, FBX, FBY + (BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENREST, NULL, SIX_RESTART_OPTION, FBX, FBY + 2*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENSOUND, NULL, SIX_SOUND_OPTION, FBX, FBY + 3*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENCONT, NULL, SIX_CONTROL_OPTION, FBX, FBY + 4*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifdef JAPAN
+// TODO: If in JAPAN mode, simply disable the subtitles button?
+ { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
+#else
+ { AATBUT, OPENSUBT, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 7*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
+#endif
+
+};
+
+/*-------------------------------------------------------------*\
+| These are the load and save game menus. |
+\*-------------------------------------------------------------*/
+
+#ifdef JAPAN
+#define NUM_SL_RGROUP 7 // number of visible slots
+#define SY 32 // y-position of first slot
+#else
+#define NUM_SL_RGROUP 9 // number of visible slots
+#define SY 31 // y-position of first slot
+#endif
+
+CONFBOX loadBox[NUM_SL_RGROUP+2] = {
+
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifndef JAPAN
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#endif
+ { ARSGBUT, LOADGAME, NULL, USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 }
+
+};
+
+CONFBOX saveBox[NUM_SL_RGROUP+2] = {
+
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifndef JAPAN
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#endif
+ { ARSGBUT, SAVEGAME, NULL,USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 }
+
+};
+
+
+/*-------------------------------------------------------------*\
+| This is the restart confirmation 'menu'. |
+\*-------------------------------------------------------------*/
+
+CONFBOX restartBox[] = {
+
+#ifdef JAPAN
+ { AAGBUT, INITGAME, NULL, USE_POINTER, 96, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 56, 44, 23, 19, NULL, IX_CROSS1 }
+#else
+ { AAGBUT, INITGAME, NULL, USE_POINTER, 70, 28, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 }
+#endif
+
+};
+
+
+/*-------------------------------------------------------------*\
+| This is the sound control 'menu'. |
+\*-------------------------------------------------------------*/
+
+#ifdef MAC_OPTIONS
+ CONFBOX soundBox[] = {
+ { SLIDER, MASTERVOL, NULL, SIX_MVOL_SLIDER, 142, 20, 100, 2, &volMaster, 0 },
+ { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 20+40, 100, 2, &volMidi, 0 },
+ { SLIDER, SAMPVOL, NULL, SIX_SVOL_SLIDER, 142, 20+2*40, 100, 2, &volSound, 0 },
+ { SLIDER, SAMPVOL, NULL, SIX_VVOL_SLIDER, 142, 20+3*40, 100, 2, &volVoice, 0 }
+ };
+#else
+CONFBOX soundBox[] = {
+ { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 25, MAXMIDIVOL, 2, &volMidi, 0 },
+ { SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER, 142, 25+40, MAXSAMPVOL, 2, &volSound, 0 },
+ { SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, MAXSAMPVOL, 2, &volVoice, 0 }
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the (mouse) control 'menu'. |
+\*-------------------------------------------------------------*/
+
+int bFlipped; // looks like this is just so the code has something to alter!
+
+
+#ifdef MAC_OPTIONS
+CONFBOX controlBox[] = {
+
+ { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 },
+ { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 }
+
+};
+#else
+CONFBOX controlBox[] = {
+
+ { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 },
+ { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 },
+#ifdef JAPAN
+ { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 205, 25+70, 23, 19, &bSwapButtons, 0 }
+#else
+ { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 155, 25+70, 23, 19, &bSwapButtons, 0 }
+#endif
+
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the subtitles 'menu'. |
+\*-------------------------------------------------------------*/
+
+#ifndef JAPAN
+CONFBOX subtitlesBox[] = {
+
+#ifdef USE_5FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 100, 56, 32, NULL, FIX_UK },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 100, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 100, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 50, 137, 56, 32, NULL, FIX_IT },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 120, 137, 56, 32, NULL, FIX_SP },
+#endif
+#ifdef USE_4FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 20, 100, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 108, 100, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 64, 137, 56, 32, NULL, FIX_IT },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 152, 137, 56, 32, NULL, FIX_SP },
+#endif
+#ifdef USE_3FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 118, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 118, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 118, 56, 32, NULL, FIX_SP },
+#endif
+
+ { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 },
+ { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 },
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 }
+#endif
+
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the quit confirmation 'menu'. |
+\*-------------------------------------------------------------*/
+
+CONFBOX quitBox[] = {
+#ifdef JAPAN
+ { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 44, 23, 19, NULL, IX_CROSS1 }
+#else
+ { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 28, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 }
+#endif
+};
+
+
+CONFBOX topwinBox[] = {
+ { NOTHING, NOFUNC, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 }
+};
+
+
+
+typedef struct {
+ int h;
+ int v;
+ int x;
+ int y;
+ bool bExtraWin;
+ PCONFBOX Box;
+ int NumBoxes;
+ int ixHeading;
+} CONFINIT, *PCONFINIT;
+
+CONFINIT ciOption = { 6, 5, 72, 23, false, optionBox, sizeof(optionBox)/sizeof(CONFBOX), NO_HEADING };
+
+CONFINIT ciLoad = { 10, 6, 20, 16, true, loadBox, sizeof(loadBox)/sizeof(CONFBOX), SIX_LOAD_HEADING };
+CONFINIT ciSave = { 10, 6, 20, 16, true, saveBox, sizeof(saveBox)/sizeof(CONFBOX), SIX_SAVE_HEADING };
+#ifdef JAPAN
+CONFINIT ciRestart = { 6, 2, 72, 53, false, restartBox, sizeof(restartBox)/sizeof(CONFBOX), SIX_RESTART_HEADING };
+#else
+CONFINIT ciRestart = { 4, 2, 98, 53, false, restartBox, sizeof(restartBox)/sizeof(CONFBOX), SIX_RESTART_HEADING };
+#endif
+CONFINIT ciSound = { 10, 5, 20, 16, false, soundBox, sizeof(soundBox)/sizeof(CONFBOX), NO_HEADING };
+#ifdef MAC_OPTIONS
+ CONFINIT ciControl = { 10, 3, 20, 40, false, controlBox, sizeof(controlBox)/sizeof(CONFBOX), NO_HEADING };
+#else
+ CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, sizeof(controlBox)/sizeof(CONFBOX), NO_HEADING };
+#endif
+#ifndef JAPAN
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+CONFINIT ciSubtitles = { 10, 6, 20, 16, false, subtitlesBox, sizeof(subtitlesBox)/sizeof(CONFBOX), NO_HEADING };
+#else
+CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, sizeof(subtitlesBox)/sizeof(CONFBOX), NO_HEADING };
+#endif
+#endif
+CONFINIT ciQuit = { 4, 2, 98, 53, false, quitBox, sizeof(quitBox)/sizeof(CONFBOX), SIX_QUIT_HEADING };
+
+CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING };
+
+#define NOBOX (-1)
+
+// Conf window globals
+static struct {
+ PCONFBOX Box;
+ int NumBoxes;
+ bool bExtraWin;
+ int ixHeading;
+ bool editableRgroup;
+
+ int selBox;
+ int pointBox; // Box pointed to on last call
+ int saveModifier;
+ int fileBase;
+ int numSaved;
+} cd = {
+ NULL, 0, false, 0, false,
+ NOBOX, NOBOX, 0, 0, 0
+};
+
+// For editing save game names
+char sedit[SG_DESC_LEN+2];
+
+#define HL1 0 // Hilight that moves with the cursor
+#define HL2 1 // Hilight on selected RGROUP box
+#define HL3 2 // Text on selected RGROUP box
+#define NUMHL 3
+
+
+// Data for button press/toggle effects
+static struct {
+ bool bButAnim;
+ PCONFBOX box;
+ bool press; // true = button press; false = button toggle
+} g_buttonEffect = { false, 0, false };
+
+
+//----- LOCAL FORWARD REFERENCES -----
+
+enum {
+ IB_NONE = -1, //
+ IB_UP = -2, // negative numbers returned
+ IB_DOWN = -3, // by WhichInvBox()
+ IB_SLIDE = -4, //
+ IB_SLIDE_UP = -5, //
+ IB_SLIDE_DOWN = -6 //
+};
+
+enum {
+ HI_BIT = ((uint)MIN_INT >> 1), // The next to top bit
+ IS_LEFT = HI_BIT,
+ IS_SLIDER = (IS_LEFT >> 1),
+ IS_RIGHT = (IS_SLIDER >> 1),
+ IS_MASK = (IS_LEFT | IS_SLIDER | IS_RIGHT)
+};
+
+static int WhichInvBox(int curX, int curY, bool bSlides);
+static void SlideMSlider(int x, SSFN fn);
+static OBJECT *AddObject(const FREEL *pfreel, int num);
+static void AddBoxes(bool posnSlide);
+
+static void ConfActionSpecial(int i);
+
+
+/*-------------------------------------------------------------------------*/
+/*** Magic numbers ***/
+
+#define M_SW 5 // Side width
+#define M_TH 5 // Top height
+#ifdef JAPAN
+#define M_TOFF 6 // Title text Y offset from top
+#define M_TBB 20 // Title box bottom Y offset
+#else
+#define M_TOFF 4 // Title text Y offset from top
+#define M_TBB 14 // Title box bottom Y offset
+#endif
+#define M_SBL 26 // Scroll bar left X offset
+#define M_SH 5 // Slider height (*)
+#define M_SW 5 // Slider width (*)
+#define M_SXOFF 9 // Slider X offset from right-hand side
+#ifdef JAPAN
+#define M_IUT 22 // Y offset of top of up arrow
+#define M_IUB 30 // Y offset of bottom of up arrow
+#else
+#define M_IUT 16 // Y offset of top of up arrow
+#define M_IUB 24 // Y offset of bottom of up arrow
+#endif
+#define M_IDT 10 // Y offset (from bottom) of top of down arrow
+#define M_IDB 3 // Y offset (from bottom) of bottom of down arrow
+#define M_IAL 12 // X offset (from right) of left of scroll arrows
+#define M_IAR 3 // X offset (from right) of right of scroll arrows
+
+#define START_ICONX (M_SW+1) // } Relative offset of first icon
+#define START_ICONY (M_TBB+M_TH+1) // } within the inventory window
+
+/*-------------------------------------------------------------------------*/
+
+
+
+#ifndef JAPAN
+bool LanguageChange(void) {
+ LANGUAGE nLang;
+
+#ifdef USE_3FLAGS
+ // VERY quick dodgy bodge
+ if (cd.selBox == 0)
+ nLang = TXT_FRENCH;
+ else if (cd.selBox == 1)
+ nLang = TXT_GERMAN;
+ else
+ nLang = TXT_SPANISH;
+ if (nLang != language) {
+#elif defined(USE_4FLAGS)
+ nLang = (LANGUAGE)(cd.selBox + 1);
+ if (nLang != language) {
+#else
+ if (cd.selBox != language) {
+ nLang = (LANGUAGE)cd.selBox;
+#endif
+ KillInventory();
+ ChangeLanguage(nLang);
+ language = nLang;
+ return true;
+ }
+ else
+ return false;
+}
+#endif
+
+/**************************************************************************/
+/******************** Some miscellaneous functions ************************/
+/**************************************************************************/
+
+/*---------------------------------------------------------------------*\
+| DumpIconArray()/DumpDobjArray()/DumpObjArray() |
+|-----------------------------------------------------------------------|
+| Delete all the objects in iconArray[]/DobjArray[]/objArray[] |
+\*---------------------------------------------------------------------*/
+static void DumpIconArray(void){
+ for (int i = 0; i < MAX_ICONS; i++) {
+ if (iconArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[i]);
+ iconArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Delete all the objects in DobjArray[]
+ */
+
+static void DumpDobjArray(void) {
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (DobjArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), DobjArray[i]);
+ DobjArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Delete all the objects in objArray[]
+ */
+
+static void DumpObjArray(void) {
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (objArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), objArray[i]);
+ objArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Convert item ID number to pointer to item's compiled data
+ * i.e. Image data and Glitter code.
+ */
+PINV_OBJECT findInvObject(int num) {
+ PINV_OBJECT retval = pio;
+
+ for (int i = 0; i < numObjects; i++, retval++) {
+ if (retval->id == num)
+ return retval;
+ }
+
+ error("Trying to manipulate undefined inventory icon");
+}
+
+/**
+ * Returns position of an item in one of the inventories.
+ * The actual position is not important for the uses that this is put to.
+ */
+
+int InventoryPos(int num) {
+ int i;
+
+ for (i = 0; i < InvD[INV_1].NoofItems; i++) // First inventory
+ if (InvD[INV_1].ItemOrder[i] == num)
+ return i;
+
+ for (i = 0; i < InvD[INV_2].NoofItems; i++) // Second inventory
+ if (InvD[INV_2].ItemOrder[i] == num)
+ return i;
+
+ if (HeldItem == num)
+ return INV_HELDNOTIN; // Held, but not in either inventory
+
+ return INV_NOICON; // Not held, not in either inventory
+}
+
+bool IsInInventory(int object, int invnum) {
+ assert(invnum == INV_1 || invnum == INV_2);
+
+ for (int i = 0; i < InvD[invnum].NoofItems; i++) // First inventory
+ if (InvD[invnum].ItemOrder[i] == object)
+ return true;
+
+ return false;
+}
+
+/**
+ * Returns which item is held (INV_NOICON (-1) if none)
+ */
+
+int WhichItemHeld(void) {
+ return HeldItem;
+}
+
+/**
+ * Called from the cursor module when it re-initialises (at the start of
+ * a new scene). For if we are holding something at scene-change time.
+ */
+
+void InventoryIconCursor(void) {
+ PINV_OBJECT invObj;
+
+ if (HeldItem != INV_NOICON) {
+ invObj = findInvObject(HeldItem);
+ SetAuxCursor(invObj->hFilm);
+ }
+}
+
+/**
+ * Returns TRUE if the inventory is active.
+ */
+
+bool InventoryActive(void) {
+ return (InventoryState == ACTIVE_INV);
+}
+
+int WhichInventoryOpen(void) {
+ if (InventoryState != ACTIVE_INV)
+ return 0;
+ else
+ return ino;
+}
+
+
+/**************************************************************************/
+/************** Running inventory item's Glitter code *********************/
+/**************************************************************************/
+
+struct ITP_INIT {
+ PINV_OBJECT pinvo;
+ USER_EVENT event;
+ BUTEVENT bev;
+};
+
+/**
+ * Run inventory item's Glitter code
+ */
+static void InvTinselProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PINT_CONTEXT pic;
+ int ThisPointedWait; // Fix the 'repeated pressing bug'
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ ITP_INIT *to = (ITP_INIT *)ProcessGetParamsSelf();
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, to->bev);
+
+ _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ if (to->event == POINTED) {
+ _ctx->ThisPointedWait = ++PointedWaitCount;
+ while (1) {
+ CORO_SLEEP(1);
+ int x, y;
+ GetCursorXY(&x, &y, false);
+ if (InvItemId(x, y) != to->pinvo->id)
+ break;
+
+ // Fix the 'repeated pressing bug'
+ if (_ctx->ThisPointedWait != PointedWaitCount)
+ CORO_KILL_SELF();
+ }
+
+ _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Run inventory item's Glitter code
+ */
+void RunInvTinselCode(PINV_OBJECT pinvo, USER_EVENT event, BUTEVENT be, int index) {
+ ITP_INIT to = { pinvo, event, be };
+
+ if (InventoryHidden)
+ return;
+
+ GlitterIndex = index;
+ CoroutineInstall(PID_TCODE, InvTinselProcess, &to, sizeof(to));
+}
+
+/**************************************************************************/
+/****************** Load/Save game specific functions *********************/
+/**************************************************************************/
+
+/**
+ * Set first load/save file entry displayed.
+ * Point Box[] text pointers to appropriate file descriptions.
+ */
+
+void firstFile(int first) {
+ int i, j;
+
+ i = getList();
+
+ cd.numSaved = i;
+
+ if (first < 0)
+ first = 0;
+ else if (first > MAX_SFILES-NUM_SL_RGROUP)
+ first = MAX_SFILES-NUM_SL_RGROUP;
+
+ if (first == 0 && i < MAX_SFILES && cd.Box == saveBox) {
+ // Blank first entry for new save
+ cd.Box[0].boxText = NULL;
+ cd.saveModifier = j = 1;
+ } else {
+ cd.saveModifier = j = 0;
+ }
+
+ for (i = first; j < NUM_SL_RGROUP; j++, i++) {
+ cd.Box[j].boxText = ListEntry(i, LE_DESC);
+ }
+
+ cd.fileBase = first;
+}
+
+/**
+ * Save the game using filename from selected slot & current description.
+ */
+
+void InvSaveGame(void) {
+ if (cd.selBox != NOBOX) {
+#ifndef JAPAN
+ sedit[strlen(sedit)-1] = 0; // Don't include the cursor!
+#endif
+ SaveGame(ListEntry(cd.selBox-cd.saveModifier+cd.fileBase, LE_NAME), sedit);
+ }
+}
+
+/**
+ * Load the selected saved game.
+ */
+void InvLoadGame(void) {
+ int rGame;
+
+ if (cd.selBox != NOBOX && (cd.selBox+cd.fileBase < cd.numSaved)) {
+ rGame = cd.selBox;
+ cd.selBox = NOBOX;
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+ if (iconArray[HL2] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ iconArray[HL2] = NULL;
+ }
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ RestoreGame(rGame+cd.fileBase);
+ }
+}
+
+/**
+ * Edit the string in sedit[]
+ * Returns TRUE if the string was altered.
+ */
+#ifndef JAPAN
+bool UpdateString(const Common::KeyState &kbd) {
+ int cpos;
+
+ if (!cd.editableRgroup)
+ return false;
+
+ cpos = strlen(sedit)-1;
+
+ if (kbd.keycode == Common::KEYCODE_BACKSPACE) {
+ if (!cpos)
+ return false;
+ sedit[cpos] = 0;
+ cpos--;
+ sedit[cpos] = CURSOR_CHAR;
+ return true;
+// } else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) {
+ } else if (IsCharImage(hTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) {
+ if (cpos == SG_DESC_LEN)
+ return false;
+ sedit[cpos] = kbd.ascii;
+ cpos++;
+ sedit[cpos] = CURSOR_CHAR;
+ sedit[cpos+1] = 0;
+ return true;
+ }
+ return false;
+}
+#endif
+
+/**
+ * Keystrokes get sent here when load/save screen is up.
+ */
+bool InvKeyIn(const Common::KeyState &kbd) {
+ if (kbd.keycode == Common::KEYCODE_PAGEUP ||
+ kbd.keycode == Common::KEYCODE_PAGEDOWN ||
+ kbd.keycode == Common::KEYCODE_HOME ||
+ kbd.keycode == Common::KEYCODE_END)
+ return true; // Key needs processing
+
+ if (kbd.keycode == 0 && kbd.ascii == 0) {
+ ;
+ } else if (kbd.keycode == Common::KEYCODE_RETURN) {
+ return true; // Key needs processing
+ } else if (kbd.keycode == Common::KEYCODE_ESCAPE) {
+ return true; // Key needs processing
+ } else {
+#ifndef JAPAN
+ if (UpdateString(kbd)) {
+ /*
+ * Delete display of text currently being edited,
+ * and replace it with freshly edited text.
+ */
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2,
+ InvD[ino].inventoryY + cd.Box[cd.selBox].ypos,
+ hTagFontHandle(), 0);
+ if (MultiRightmost(iconArray[HL3]) > 213) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE));
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2,
+ InvD[ino].inventoryY + cd.Box[cd.selBox].ypos,
+ hTagFontHandle(), 0);
+ }
+ MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2);
+ }
+#endif
+ }
+ return false;
+}
+
+/*---------------------------------------------------------------------*\
+| Select() |
+|-----------------------------------------------------------------------|
+| Highlights selected box. |
+| If it's editable (save game), copy existing description and add a |
+| cursor. |
+\*---------------------------------------------------------------------*/
+void Select(int i, bool force) {
+#ifdef JAPAN
+ time_t secs_now;
+ struct tm *time_now;
+#endif
+
+ i &= ~IS_MASK;
+
+ if (cd.selBox == i && !force)
+ return;
+
+ cd.selBox = i;
+
+ // Clear previous selected highlight and text
+ if (iconArray[HL2] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ iconArray[HL2] = NULL;
+ }
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+
+ // New highlight box
+ switch (cd.Box[i].boxType) {
+ case RGROUP:
+ iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w, cd.Box[i].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ MultiSetAniXY(iconArray[HL2],
+ InvD[ino].inventoryX + cd.Box[i].xpos,
+ InvD[ino].inventoryY + cd.Box[i].ypos);
+
+ // Z-position of box, and add edit text if appropriate
+ if (cd.editableRgroup) {
+ MultiSetZPosition(iconArray[HL2], Z_INV_ITEXT+1);
+
+ assert(cd.Box[i].ixText == USE_POINTER);
+#ifdef JAPAN
+ // Current date and time
+ time(&secs_now);
+ time_now = localtime(&secs_now);
+ strftime(sedit, SG_DESC_LEN, "%D %H:%M", time_now);
+#else
+ // Current description with cursor appended
+ if (cd.Box[i].boxText != NULL) {
+ strcpy(sedit, cd.Box[i].boxText);
+ strcat(sedit, sCursor);
+ } else {
+ strcpy(sedit, sCursor);
+ }
+#endif
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[i].xpos + 2,
+#ifdef JAPAN
+ InvD[ino].inventoryY + cd.Box[i].ypos + 2,
+#else
+ InvD[ino].inventoryY + cd.Box[i].ypos,
+#endif
+ hTagFontHandle(), 0);
+ MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2);
+ } else {
+ MultiSetZPosition(iconArray[HL2], Z_INV_ICONS + 1);
+ }
+
+ _vm->divertKeyInput(InvKeyIn);
+
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w+6, cd.Box[i].h+6);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ MultiSetAniXY(iconArray[HL2],
+ InvD[ino].inventoryX + cd.Box[i].xpos - 2,
+ InvD[ino].inventoryY + cd.Box[i].ypos - 2);
+ MultiSetZPosition(iconArray[HL2], Z_INV_BRECT+1);
+
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * If the item is not already held, hold it.
+ */
+
+void HoldItem(int item) {
+ PINV_OBJECT invObj;
+
+ if (HeldItem != item) {
+ if (item == INV_NOICON && HeldItem != INV_NOICON)
+ DelAuxCursor(); // no longer aux cursor
+
+ if (item != INV_NOICON) {
+ invObj = findInvObject(item);
+ SetAuxCursor(invObj->hFilm); // and is aux. cursor
+ }
+
+ HeldItem = item; // Item held
+ }
+
+ // Redraw contents - held item not displayed as a content.
+ ItemsChanged = true;
+}
+
+/**
+ * Stop holding an item.
+ */
+
+void DropItem(int item) {
+ if (HeldItem == item) {
+ HeldItem = INV_NOICON; // Item not held
+ DelAuxCursor(); // no longer aux cursor
+ }
+
+ // Redraw contents - held item was not displayed as a content.
+ ItemsChanged = true;
+}
+
+/**
+ * Stick the item into an inventory list (ItemOrder[]), and hold the
+ * item if requested.
+ */
+
+void AddToInventory(int invno, int icon, bool hold) {
+ int i;
+ bool bOpen;
+#ifdef DEBUG
+ PINV_OBJECT invObj;
+#endif
+
+ assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_OPEN)); // Trying to add to illegal inventory
+
+ if (invno == INV_OPEN) {
+ assert(InventoryState == ACTIVE_INV && (ino == INV_1 || ino == INV_2)); // addopeninv() with inventry not open
+ invno = ino;
+ bOpen = true;
+
+ // Make sure it doesn't get in both!
+ RemFromInventory(ino == INV_1 ? INV_2 : INV_1, icon);
+ } else
+ bOpen = false;
+
+#ifdef DEBUG
+ invObj = findInvObject(icon);
+ if ((invObj->attribute & IO_ONLYINV1 && invno != INV_1)
+ || (invObj->attribute & IO_ONLYINV2 && invno != INV_2))
+ error("Trying to add resticted object to wrong inventory");
+#endif
+
+ if (invno == INV_1)
+ RemFromInventory(INV_2, icon);
+ else if (invno == INV_2)
+ RemFromInventory(INV_1, icon);
+
+ // See if it's already there
+ for (i = 0; i < InvD[invno].NoofItems; i++) {
+ if (InvD[invno].ItemOrder[i] == icon)
+ break;
+ }
+
+ // Add it if it isn't already there
+ if (i == InvD[invno].NoofItems) {
+ if (!bOpen) {
+ if (invno == INV_CONV) {
+ // For conversation, insert before last icon
+ // which will always be the goodbye icon
+ InvD[invno].ItemOrder[InvD[invno].NoofItems] = InvD[invno].ItemOrder[InvD[invno].NoofItems-1];
+ InvD[invno].ItemOrder[InvD[invno].NoofItems-1] = icon;
+ InvD[invno].NoofItems++;
+ } else {
+ InvD[invno].ItemOrder[InvD[invno].NoofItems++] = icon;
+ }
+ ItemsChanged = true;
+ } else {
+ // It could be that the index is beyond what you'd expect
+ // as delinv may well have been called
+ if (GlitterIndex < InvD[invno].NoofItems) {
+ memmove(&InvD[invno].ItemOrder[GlitterIndex + 1],
+ &InvD[invno].ItemOrder[GlitterIndex],
+ (InvD[invno].NoofItems-GlitterIndex)*sizeof(int));
+ InvD[invno].ItemOrder[GlitterIndex] = icon;
+ } else {
+ InvD[invno].ItemOrder[InvD[invno].NoofItems] = icon;
+ }
+ InvD[invno].NoofItems++;
+ }
+ }
+
+ // Hold it if requested
+ if (hold)
+ HoldItem(icon);
+}
+
+/**
+ * Take the item from the inventory list (ItemOrder[]).
+ * Return FALSE if item wasn't present, true if it was.
+ */
+
+bool RemFromInventory(int invno, int icon) {
+ int i;
+
+ assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory
+
+ // See if it's there
+ for (i = 0; i < InvD[invno].NoofItems; i++) {
+ if (InvD[invno].ItemOrder[i] == icon)
+ break;
+ }
+
+ if (i == InvD[invno].NoofItems)
+ return false; // Item wasn't there
+ else {
+ memmove(&InvD[invno].ItemOrder[i], &InvD[invno].ItemOrder[i+1], (InvD[invno].NoofItems-i)*sizeof(int));
+ InvD[invno].NoofItems--;
+ ItemsChanged = true;
+ return true; // Item removed
+ }
+}
+
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/*---------------------------------------------------------------------*\
+| InvArea() |
+|-----------------------------------------------------------------------|
+| Work out which area of the inventory window the cursor is in. |
+|-----------------------------------------------------------------------|
+| This used to be worked out with appropriately defined magic numbers. |
+| Then the graphic changed and I got it right again. Then the graphic |
+| changed and I got fed up of faffing about. It's probably easier just |
+| to rework all this. |
+\*---------------------------------------------------------------------*/
+enum { I_NOTIN, I_MOVE, I_BODY,
+ I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT,
+ I_TOP, I_BOTTOM, I_LEFT, I_RIGHT,
+ I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN,
+ I_ENDCHANGE
+};
+
+#define EXTRA 1 // This was introduced when we decided to increase
+ // the active area of the borders for re-sizing.
+
+/*---------------------------------*/
+#define LeftX InvD[ino].inventoryX
+#define TopY InvD[ino].inventoryY
+/*---------------------------------*/
+
+int InvArea(int x, int y) {
+ int RightX = MultiRightmost(RectObject) + 1;
+ int BottomY = MultiLowest(RectObject) + 1;
+
+// Outside the whole rectangle?
+ if (x <= LeftX - EXTRA || x > RightX + EXTRA
+ || y <= TopY - EXTRA || y > BottomY + EXTRA)
+ return I_NOTIN;
+
+// The bottom line
+ if (y > BottomY - 2 - EXTRA) { // Below top of bottom line?
+ if (x <= LeftX + 2 + EXTRA)
+ return I_BLEFT; // Bottom left corner
+ else if (x > RightX - 2 - EXTRA)
+ return I_BRIGHT; // Bottom right corner
+ else
+ return I_BOTTOM; // Just plain bottom
+ }
+
+// The top line
+ if (y <= TopY + 2 + EXTRA) { // Above bottom of top line?
+ if (x <= LeftX + 2 + EXTRA)
+ return I_TLEFT; // Top left corner
+ else if (x > RightX - 2 - EXTRA)
+ return I_TRIGHT; // Top right corner
+ else
+ return I_TOP; // Just plain top
+ }
+
+// Sides
+ if (x <= LeftX + 2 + EXTRA) // Left of right of left side?
+ return I_LEFT;
+ else if (x > RightX - 2 - EXTRA) // Right of left of right side?
+ return I_RIGHT;
+
+// From here down still needs fixing up properly
+/*
+* In the move area?
+*/
+ if (ino != INV_CONF
+ && x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 &&
+ y >= TopY + M_TH - 2 && y < TopY + M_TBB + 2)
+ return I_MOVE;
+
+/*
+* Scroll bits
+*/
+ if (ino == INV_CONF && cd.bExtraWin) {
+ } else {
+ if (x > RightX - M_IAL + 3 && x <= RightX - M_IAR + 1) {
+ if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1)
+ return I_UP;
+ if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1)
+ return I_DOWN;
+
+ if (y >= TopY + slideYmin && y < TopY + slideYmax + M_SH) {
+ if (y < TopY + slideY)
+ return I_SLIDE_UP;
+ if (y < TopY + slideY + M_SH)
+ return I_SLIDE;
+ else
+ return I_SLIDE_DOWN;
+ }
+ }
+ }
+
+ return I_BODY;
+}
+
+/**
+ * Returns the id of the icon displayed under the given position.
+ * Also return co-ordinates of items tag display position, if requested.
+ */
+
+int InvItem(int *x, int *y, bool update) {
+ int itop, ileft;
+ int row, col;
+ int item;
+ int IconsX;
+
+ itop = InvD[ino].inventoryY + START_ICONY;
+
+ IconsX = InvD[ino].inventoryX + START_ICONX;
+
+ for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) {
+ ileft = IconsX;
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++, item++) {
+ if (*x >= ileft && *x < ileft + ITEM_WIDTH &&
+ *y >= itop && *y < itop + ITEM_HEIGHT) {
+ if (update) {
+ *x = ileft + ITEM_WIDTH/2;
+ *y = itop /*+ ITEM_HEIGHT/4*/;
+ }
+ return item;
+ }
+
+ ileft += ITEM_WIDTH + 1;
+ }
+ itop += ITEM_HEIGHT + 1;
+ }
+ return INV_NOICON;
+}
+
+/**
+ * Returns the id of the icon displayed under the given position.
+ */
+
+int InvItemId(int x, int y) {
+ int itop, ileft;
+ int row, col;
+ int item;
+
+ if (InventoryHidden || InventoryState == IDLE_INV)
+ return INV_NOICON;
+
+ itop = InvD[ino].inventoryY + START_ICONY;
+
+ int IconsX = InvD[ino].inventoryX + START_ICONX;
+
+ for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) {
+ ileft = IconsX;
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++, item++) {
+ if (x >= ileft && x < ileft + ITEM_WIDTH &&
+ y >= itop && y < itop + ITEM_HEIGHT) {
+ return InvD[ino].ItemOrder[item];
+ }
+
+ ileft += ITEM_WIDTH + 1;
+ }
+ itop += ITEM_HEIGHT + 1;
+ }
+ return INV_NOICON;
+}
+
+/*---------------------------------------------------------------------*\
+| WhichInvBox() |
+|-----------------------------------------------------------------------|
+| Finds which box the cursor is in. |
+\*---------------------------------------------------------------------*/
+#define MD_YSLIDTOP 7
+#define MD_YSLIDBOT 18
+#define MD_YBUTTOP 9
+#define MD_YBUTBOT 16
+#define MD_XLBUTL 1
+#define MD_XLBUTR 10
+#define MD_XRBUTL 105
+#define MD_XRBUTR 114
+
+static int WhichInvBox(int curX, int curY, bool bSlides) {
+ if (bSlides) {
+ for (int i = 0; i < numMdSlides; i++) {
+ if (curY > MultiHighest(mdSlides[i].obj) && curY < MultiLowest(mdSlides[i].obj)
+ && curX > MultiLeftmost(mdSlides[i].obj) && curX < MultiRightmost(mdSlides[i].obj))
+ return mdSlides[i].num | IS_SLIDER;
+ }
+ }
+
+ curX -= InvD[ino].inventoryX;
+ curY -= InvD[ino].inventoryY;
+
+ for (int i = 0; i < cd.NumBoxes; i++) {
+ switch (cd.Box[i].boxType) {
+ case SLIDER:
+ if (bSlides) {
+ if (curY >= cd.Box[i].ypos+MD_YBUTTOP && curY < cd.Box[i].ypos+MD_YBUTBOT) {
+ if (curX >= cd.Box[i].xpos+MD_XLBUTL && curX < cd.Box[i].xpos+MD_XLBUTR)
+ return i | IS_LEFT;
+ if (curX >= cd.Box[i].xpos+MD_XRBUTL && curX < cd.Box[i].xpos+MD_XRBUTR)
+ return i | IS_RIGHT;
+ }
+ }
+ break;
+
+ case AAGBUT:
+ case ARSGBUT:
+ case TOGGLE:
+ case FLIP:
+ if (curY > cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h
+ && curX > cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w)
+ return i;
+ break;
+
+ default:
+ // 'Normal' box
+ if (curY >= cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h
+ && curX >= cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w)
+ return i;
+ break;
+ }
+ }
+
+ if (cd.bExtraWin) {
+ if (curX > 20 + 181 && curX < 20 + 181 + 8 &&
+ curY > 24 + 2 && curY < 24 + 139 + 5) {
+
+ if (curY < 24 + 2 + 5) {
+ return IB_UP;
+ } else if (curY > 24 + 139) {
+ return IB_DOWN;
+ } else if (curY+InvD[ino].inventoryY >= slideY && curY+InvD[ino].inventoryY < slideY + 5) {
+ return IB_SLIDE;
+ } else if (curY+InvD[ino].inventoryY < slideY) {
+ return IB_SLIDE_UP;
+ } else if (curY+InvD[ino].inventoryY >= slideY + 5) {
+ return IB_SLIDE_DOWN;
+ }
+ }
+ }
+
+ return IB_NONE;
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * InBoxes
+ */
+void InvBoxes(bool InBody, int curX, int curY) {
+ int index; // Box pointed to on this call
+ const FILM *pfilm;
+
+ // Find out which icon is currently pointed to
+ if (!InBody)
+ index = -1;
+ else {
+ index = WhichInvBox(curX, curY, false);
+ }
+
+ // If no icon pointed to, or points to (logical position of)
+ // currently held icon, then no icon is pointed to!
+ if (index < 0) {
+ // unhigh-light box (if one was)
+ cd.pointBox = NOBOX;
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ } else if (index != cd.pointBox) {
+ cd.pointBox = index;
+ // A new box is pointed to - high-light it
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ if ((cd.Box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) ||
+///* I don't agree */ cd.Box[cd.pointBox].boxType == RGROUP ||
+ cd.Box[cd.pointBox].boxType == AATBUT ||
+ cd.Box[cd.pointBox].boxType == AABUT) {
+ iconArray[HL1] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[cd.pointBox].w, cd.Box[cd.pointBox].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ MultiSetAniXY(iconArray[HL1],
+ InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos,
+ InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+ }
+ else if (cd.Box[cd.pointBox].boxType == AAGBUT ||
+ cd.Box[cd.pointBox].boxType == ARSGBUT ||
+ cd.Box[cd.pointBox].boxType == TOGGLE) {
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ iconArray[HL1] = AddObject(&pfilm->reels[cd.Box[cd.pointBox].bi+HIGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1],
+ InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos,
+ InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+ }
+ }
+}
+
+static void ButtonPress(CORO_PARAM, PCONFBOX box) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FILM *pfilm;
+
+ assert(box->boxType == AAGBUT || box->boxType == ARSGBUT);
+
+ // Replace highlight image with normal image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ pfilm = (const FILM *)LockMem(winPartsf);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Replace normal image with depresses image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold depressed image for 2 frames
+ CORO_SLEEP(2);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Replace depressed image with normal image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ CORO_SLEEP(1);
+
+ CORO_END_CODE;
+}
+
+static void ButtonToggle(CORO_PARAM, PCONFBOX box) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FILM *pfilm;
+
+ assert(box->boxType == TOGGLE);
+
+ // Remove hilight image
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // Add depressed image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold depressed image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Toggle state
+ (*box->ival) = *(box->ival) ^ 1; // XOR with true
+ box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1;
+ AddBoxes(false);
+ // Keep highlight (e.g. flag)
+ if (cd.selBox != NOBOX)
+ Select(cd.selBox, true);
+
+ // New state, depressed image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold new depressed image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // New state, normal
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // New state, highlighted
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Monitors for POINTED event for inventory icons.
+ */
+
+void InvLabels(bool InBody, int aniX, int aniY) {
+ int index; // Icon pointed to on this call
+ PINV_OBJECT invObj;
+
+ // Find out which icon is currently pointed to
+ if (!InBody)
+ index = INV_NOICON;
+ else {
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (index >= InvD[ino].NoofItems)
+ index = INV_NOICON;
+ else
+ index = InvD[ino].ItemOrder[index];
+ }
+ }
+
+ // If no icon pointed to, or points to (logical position of)
+ // currently held icon, then no icon is pointed to!
+ if (index == INV_NOICON || index == HeldItem) {
+ pointedIcon = INV_NOICON;
+ } else if (index != pointedIcon) {
+ // A new icon is pointed to - run its script with POINTED event
+ invObj = findInvObject(index);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, POINTED, BE_NONE, index);
+ pointedIcon = index;
+ }
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * All to do with the slider.
+ * I can't remember how it works - or, indeed, what it does.
+ * It seems to set up slideStuff[], an array of possible first-displayed
+ * icons set against the matching y-positions of the slider.
+ */
+
+void AdjustTop(void) {
+ int tMissing, bMissing, nMissing;
+ int nslideY;
+ int rowsWanted;
+ int slideRange;
+ int n, i;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ rowsWanted = (InvD[ino].NoofItems - InvD[ino].FirstDisp + InvD[ino].NoofHicons-1) / InvD[ino].NoofHicons;
+
+ while (rowsWanted < InvD[ino].NoofVicons) {
+ if (InvD[ino].FirstDisp) {
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ rowsWanted++;
+ } else
+ break;
+ }
+ tMissing = InvD[ino].FirstDisp ? (InvD[ino].FirstDisp + InvD[ino].NoofHicons-1)/InvD[ino].NoofHicons : 0;
+ bMissing = (rowsWanted > InvD[ino].NoofVicons) ? rowsWanted - InvD[ino].NoofVicons : 0;
+
+ nMissing = tMissing + bMissing;
+ slideRange = slideYmax - slideYmin;
+
+ if (!tMissing)
+ nslideY = slideYmin;
+ else if (!bMissing)
+ nslideY = slideYmax;
+ else {
+ nslideY = tMissing*slideRange/nMissing;
+ nslideY += slideYmin;
+ }
+
+ if (nMissing) {
+ n = InvD[ino].FirstDisp - tMissing*InvD[ino].NoofHicons;
+ for (i = 0; i <= nMissing; i++, n += InvD[ino].NoofHicons) {
+ slideStuff[i].n = n;
+ slideStuff[i].y = (i*slideRange/nMissing) + slideYmin;
+ }
+ if (slideStuff[0].n < 0)
+ slideStuff[0].n = 0;
+ assert(i < MAX_ININV + 1);
+ slideStuff[i].n = -1;
+ } else {
+ slideStuff[0].n = 0;
+ slideStuff[0].y = slideYmin;
+ slideStuff[1].n = -1;
+ }
+
+ if (nslideY != slideY) {
+ MultiMoveRelXY(SlideObject, 0, nslideY - slideY);
+ slideY = nslideY;
+ }
+}
+
+/**
+ * Insert an inventory icon object onto the display list.
+ */
+
+OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) {
+ PINV_OBJECT invObj; // Icon data
+ const MULTI_INIT *pmi; // Its INIT structure - from the reel
+ PIMAGE pim; // ... you get the picture
+ OBJECT * pPlayObj; // The object we insert
+
+ invObj = findInvObject(num);
+
+ // Get pointer to image
+ pim = GetImageFromFilm(invObj->hFilm, 0, pfreel, &pmi, pfilm);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Set up the multi-object
+ pPlayObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);
+
+ return pPlayObj;
+}
+
+/**
+ * Create display objects for the displayed icons in an inventory window.
+ */
+
+void FillInInventory(void) {
+ int Index; // Index into ItemOrder[]
+ int n = 0; // index into iconArray[]
+ int xpos, ypos;
+ int row, col;
+ const FREEL *pfr;
+ const FILM *pfilm;
+
+ DumpIconArray();
+
+ if (InvDragging != ID_SLIDE)
+ AdjustTop(); // Set up slideStuff[]
+
+ Index = InvD[ino].FirstDisp; // Start from first displayed object
+ n = 0;
+ ypos = START_ICONY; // Y-offset of first display row
+
+ for (row = 0; row < InvD[ino].NoofVicons; row++, ypos += ITEM_HEIGHT + 1) {
+ xpos = START_ICONX; // X-offset of first display column
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++) {
+ if (Index >= InvD[ino].NoofItems)
+ break;
+ else if (InvD[ino].ItemOrder[Index] != HeldItem) {
+ // Create a display object and position it
+ iconArray[n] = AddInvObject(InvD[ino].ItemOrder[Index], &pfr, &pfilm);
+ MultiSetAniXY(iconArray[n], InvD[ino].inventoryX + xpos , InvD[ino].inventoryY + ypos);
+ MultiSetZPosition(iconArray[n], Z_INV_ICONS);
+
+ InitStepAnimScript(&iconAnims[n], iconArray[n], FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+
+ n++;
+ }
+ Index++;
+ xpos += ITEM_WIDTH + 1; // X-offset of next display column
+ }
+ }
+}
+
+/**
+ * Set up a rectangle as the background to the inventory window.
+ * Additionally, sticks the window title up.
+ */
+
+enum {FROM_HANDLE, FROM_STRING};
+
+void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) {
+ // Why not 2 ????
+ int width = TLwidth + extraH + TRwidth - 3;
+ int height = TLheight + extraV + BLheight - 3;
+
+ // Create a rectangle object
+ RectObject = *rect = TranslucentObject(width, height);
+
+ // add it to display list and position it
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect);
+ MultiSetAniXY(*rect, InvD[ino].inventoryX + 1, InvD[ino].inventoryY + 1);
+ MultiSetZPosition(*rect, Z_INV_BRECT);
+
+ // Create text object using title string
+ if (textFrom == FROM_HANDLE) {
+ LoadStringRes(InvD[ino].hInvTitle, tBufferAddr(), TBUFSZ);
+ *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*title); // Inventory title string produced NULL text
+ MultiSetZPosition(*title, Z_INV_HTEXT);
+ } else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) {
+ LoadStringRes(configStrings[cd.ixHeading], tBufferAddr(), TBUFSZ);
+ *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*title); // Inventory title string produced NULL text
+ MultiSetZPosition(*title, Z_INV_HTEXT);
+ }
+}
+
+/**
+ * Insert a part of the inventory window frame onto the display list.
+ */
+
+static OBJECT *AddObject(const FREEL *pfreel, int num) {
+ const MULTI_INIT *pmi; // Get the MULTI_INIT structure
+ PIMAGE pim;
+ OBJECT * pPlayObj;
+
+ // Get pointer to image
+ pim = GetImageFromReel(pfreel, &pmi);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Horrible bodge involving global variables to save
+ // width and/or height of some window frame components
+ if (num == TL) {
+ TLwidth = FROM_LE_16(pim->imgWidth);
+ TLheight = FROM_LE_16(pim->imgHeight);
+ } else if (num == TR) {
+ TRwidth = FROM_LE_16(pim->imgWidth);
+ } else if (num == BL) {
+ BLheight = FROM_LE_16(pim->imgHeight);
+ }
+
+ // Set up and insert the multi-object
+ pPlayObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);
+
+ return pPlayObj;
+}
+
+/**
+ * Display the scroll bar slider.
+ */
+
+void AddSlider(OBJECT **slide, const FILM *pfilm) {
+ SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
+ MultiSetAniXY(*slide, MultiRightmost(RectObject)-M_SXOFF+2, InvD[ino].inventoryY + slideY);
+ MultiSetZPosition(*slide, Z_INV_MFRAME);
+}
+
+enum {
+ SLIDE_RANGE = 81,
+ SLIDE_MINX = 8,
+ SLIDE_MAXX = 8+SLIDE_RANGE,
+
+ MDTEXT_YOFF = 6,
+ MDTEXT_XOFF = -4
+};
+
+/**
+ * Display a box with some text in it.
+ */
+
+void AddBox(int *pi, int i) {
+ int x = InvD[ino].inventoryX + cd.Box[i].xpos;
+ int y = InvD[ino].inventoryY + cd.Box[i].ypos;
+ int *pival = cd.Box[i].ival;
+ int xdisp;
+ const FILM *pfilm;
+
+ switch (cd.Box[i].boxType) {
+ default:
+ // Give us a box
+ iconArray[*pi] = RectangleObject(BackPal(), COL_BOX, cd.Box[i].w, cd.Box[i].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[*pi]);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ if (cd.Box[i].ixText == USE_POINTER) {
+ if (cd.Box[i].boxText != NULL) {
+ if (cd.Box[i].boxType == RGROUP) {
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0,
+#ifdef JAPAN
+ x+2, y+2, hTagFontHandle(), 0);
+#else
+ x+2, y, hTagFontHandle(), 0);
+#endif
+ } else {
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0,
+#ifdef JAPAN
+// Note: it never seems to go here!
+ x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE);
+#else
+ x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE);
+#endif
+ }
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ }
+ } else {
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ assert(cd.Box[i].boxType != RGROUP); // You'll need to add some code!
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+#ifdef JAPAN
+ x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE);
+#else
+ x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE);
+#endif
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ }
+ break;
+
+ case AAGBUT:
+ case ARSGBUT:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ assert(flagFilm != 0); // Language flags not declared!
+
+ pfilm = (const FILM *)LockMem(flagFilm);
+
+ if (bAmerica && cd.Box[i].bi == FIX_UK)
+ cd.Box[i].bi = FIX_USA;
+
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+2);
+ *pi += 1;
+
+ break;
+#endif
+ case FLIP:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ if (*(cd.Box[i].ival))
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1);
+ else
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+1], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+
+ case TOGGLE:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ cd.Box[i].bi = *(cd.Box[i].ival) ? IX_TICK1 : IX_CROSS1;
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+
+ case SLIDER:
+ pfilm = (const FILM *)LockMem(winPartsf);
+ xdisp = SLIDE_RANGE*(*pival)/cd.Box[i].w;
+
+ iconArray[*pi] = AddObject(&pfilm->reels[IX_MDGROOVE], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_MDGROOVE);
+ *pi += 1;
+ iconArray[*pi] = AddObject(&pfilm->reels[IX_MDSLIDER], -1);
+ MultiSetAniXY(iconArray[*pi], x+SLIDE_MINX+xdisp, y);
+ MultiSetZPosition(iconArray[*pi], Z_MDSLIDER);
+ assert(numMdSlides < MAXSLIDES);
+ mdSlides[numMdSlides].num = i;
+ mdSlides[numMdSlides].min = x+SLIDE_MINX;
+ mdSlides[numMdSlides].max = x+SLIDE_MAXX;
+ mdSlides[numMdSlides++].obj = iconArray[*pi];
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+ }
+}
+
+/**
+ * Display some boxes.
+ */
+static void AddBoxes(bool posnSlide) {
+ int oCount = NUMHL; // Object count - allow for HL1, HL2 etc.
+
+ DumpIconArray();
+ numMdSlides = 0;
+
+ for (int i = 0; i < cd.NumBoxes; i++) {
+ AddBox(&oCount, i);
+ }
+
+ if (cd.bExtraWin) {
+ if (posnSlide)
+ slideY = slideYmin + (cd.fileBase*(slideYmax-slideYmin))/(MAX_SFILES-NUM_SL_RGROUP);
+ MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, slideY);
+ }
+
+ assert(oCount < MAX_ICONS); // added too many icons
+}
+
+/**
+ * Display the scroll bar slider.
+ */
+
+void AddEWSlider(OBJECT **slide, const FILM *pfilm) {
+ SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
+ MultiSetAniXY(*slide, InvD[ino].inventoryX + 24 + 127, slideY);
+ MultiSetZPosition(*slide, Z_INV_MFRAME);
+}
+
+/**
+ * AddExtraWindow
+ */
+
+int AddExtraWindow(int x, int y, OBJECT **retObj) {
+ int n = 0;
+ const FILM *pfilm;
+
+ // Get the frame's data
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ x += 20;
+ y += 24;
+
+// Draw the four corners
+ retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1); // Top left
+ MultiSetAniXY(retObj[n], x, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1); // Top right
+ MultiSetAniXY(retObj[n], x + 152, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_BL], -1); // Bottom left
+ MultiSetAniXY(retObj[n], x, y + 124);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_BR], -1); // Bottom right
+ MultiSetAniXY(retObj[n], x + 152, y + 124);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+
+// Draw the edges
+ retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Top
+ MultiSetAniXY(retObj[n], x + 6, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Bottom
+ MultiSetAniXY(retObj[n], x + 6, y + 143);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Left
+ MultiSetAniXY(retObj[n], x, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 1
+ MultiSetAniXY(retObj[n], x + 179, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 2
+ MultiSetAniXY(retObj[n], x + 188, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+
+ slideY = slideYmin = y + 9;
+ slideYmax = y + 134;
+ AddEWSlider(&retObj[n++], pfilm);
+
+ return n;
+}
+
+
+enum InventoryType { EMPTY, FULL, CONF };
+
+/**
+ * Construct an inventory window - either a standard one, with
+ * background, slider and icons, or a re-sizing window.
+ */
+void ConstructInventory(InventoryType filling) {
+ int eH, eV; // Extra width and height
+ int n = 0; // Index into object array
+ int zpos; // Z-position of frame
+ int invX = InvD[ino].inventoryX;
+ int invY = InvD[ino].inventoryY;
+ OBJECT **retObj;
+ const FILM *pfilm;
+
+ extern bool RePosition(void); // Forward reference
+ // Select the object array to use
+ if (filling == FULL || filling == CONF) {
+ retObj = objArray; // Standard window
+ zpos = Z_INV_MFRAME;
+ } else {
+ retObj = DobjArray; // Re-sizing window
+ zpos = Z_INV_RFRAME;
+ }
+
+ // Dispose of anything it may be replacing
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (retObj[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]);
+ retObj[i] = NULL;
+ }
+ }
+
+ // Get the frame's data
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ // Standard window is of granular dimensions
+ if (filling == FULL) {
+ // Round-up/down to nearest number of icons
+ if (SuppH > ITEM_WIDTH / 2)
+ InvD[ino].NoofHicons++;
+ if (SuppV > ITEM_HEIGHT / 2)
+ InvD[ino].NoofVicons++;
+ SuppH = SuppV = 0;
+ }
+
+ // Extra width and height
+ eH = (InvD[ino].NoofHicons - 1) * (ITEM_WIDTH+1) + SuppH;
+ eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+1) + SuppV;
+
+ // Which window frame corners to use
+ if (filling == FULL && ino != INV_CONV) {
+ TL = IX_TL;
+ TR = IX_TR;
+ BL = IX_BL;
+ BR = IX_BR;
+ } else {
+ TL = IX_RTL;
+ TR = IX_RTR;
+ BL = IX_BL;
+ BR = IX_RBR;
+ }
+
+// Draw the four corners
+ retObj[n] = AddObject(&pfilm->reels[TL], TL);
+ MultiSetAniXY(retObj[n], invX, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[TR], TR);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[BL], BL);
+ MultiSetAniXY(retObj[n], invX, invY + TLheight + eV);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[BR], BR);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY + TLheight + eV);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+// Draw extra Top and bottom parts
+ if (InvD[ino].NoofHicons > 1) {
+ // Top side
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Bottom of header box
+ if (filling == FULL) {
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Extra bits for conversation - hopefully temporary
+ if (ino == INV_CONV) {
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth - 2, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ retObj[n] = AddObject(&pfilm->reels[IX_H52], -1);
+ MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ }
+
+ // Bottom side
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY + TLheight + eV + BLheight - M_TH + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ if (SuppH) {
+ int offx = TLwidth + eH - 26;
+ if (offx < TLwidth) // Not too far!
+ offx = TLwidth;
+
+ // Top side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + offx, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Bottom side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + offx, invY + TLheight + eV + BLheight - M_TH + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+// Draw extra side parts
+ if (InvD[ino].NoofVicons > 1) {
+ // Left side
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Left side of scroll bar
+ if (filling == FULL && ino != INV_CONV) {
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + M_SBL + 1, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+ // Right side
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ if (SuppV) {
+ int offy = TLheight + eV - 26;
+ if (offy < 5)
+ offy = 5;
+
+ // Left side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
+ MultiSetAniXY(retObj[n], invX, invY + offy);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Right side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + offy);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+ OBJECT **rect, **title;
+
+// Draw background, slider and icons
+ if (filling == FULL) {
+ rect = &retObj[n++];
+ title = &retObj[n++];
+
+ AddBackground(rect, title, eH, eV, FROM_HANDLE);
+
+ if (ino == INV_CONV)
+ SlideObject = NULL;
+ else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) {
+ slideYmin = TLheight - 2;
+ slideYmax = TLheight + eV + 10;
+ AddSlider(&retObj[n++], pfilm);
+ }
+
+ FillInInventory();
+ }
+ else if (filling == CONF) {
+ rect = &retObj[n++];
+ title = &retObj[n++];
+
+ AddBackground(rect, title, eH, eV, FROM_STRING);
+ if (cd.bExtraWin)
+ n += AddExtraWindow(invX, invY, &retObj[n]);
+ AddBoxes(true);
+ }
+
+ assert(n < MAX_WCOMP); // added more parts than we can handle!
+
+ // Reposition returns TRUE if needs to move
+ if (InvD[ino].moveable && filling == FULL && RePosition()) {
+ ConstructInventory(FULL);
+ }
+}
+
+
+/**
+ * Call this when drawing a 'FULL', movable inventory. Checks that the
+ * position of the Translucent object is within limits. If it isn't,
+ * adjusts the x/y position of the current inventory and returns TRUE.
+ */
+bool RePosition(void) {
+ int p;
+ bool bMoveitMoveit = false;
+
+ assert(RectObject); // no recangle object!
+
+ // Test for off-screen horizontally
+ p = MultiLeftmost(RectObject);
+ if (p > MAXLEFT) {
+ // Too far to the right
+ InvD[ino].inventoryX += MAXLEFT - p;
+ bMoveitMoveit = true; // I like to....
+ } else {
+ // Too far to the left?
+ p = MultiRightmost(RectObject);
+ if (p < MINRIGHT) {
+ InvD[ino].inventoryX += MINRIGHT - p;
+ bMoveitMoveit = true; // I like to....
+ }
+ }
+
+ // Test for off-screen vertically
+ p = MultiHighest(RectObject);
+ if (p < MINTOP) {
+ // Too high
+ InvD[ino].inventoryY += MINTOP - p;
+ bMoveitMoveit = true; // I like to....
+ } else if (p > MAXTOP) {
+ // Too low
+ InvD[ino].inventoryY += MAXTOP - p;
+ bMoveitMoveit = true; // I like to....
+ }
+
+ return bMoveitMoveit;
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * Get the cursor's reel, poke in the background palette,
+ * and customise the cursor.
+ */
+void AlterCursor(int num) {
+ const FREEL *pfreel;
+ PIMAGE pim;
+
+ // Get pointer to image
+ pim = GetImageFromFilm(winPartsf, num, &pfreel);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ SetTempCursor(FROM_LE_32(pfreel->script));
+}
+
+enum InvCursorFN {IC_AREA, IC_DROP};
+
+/**
+ * InvCursor
+ */
+void InvCursor(InvCursorFN fn, int CurX, int CurY) {
+ static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR,
+ IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL; // FIXME: local static var
+
+ int area; // The part of the window the cursor is over
+ bool restoreMain = false;
+
+ // If currently dragging, don't be messing about with the cursor shape
+ if (InvDragging != ID_NONE)
+ return;
+
+ switch (fn) {
+ case IC_DROP:
+ ICursor = IC_NORMAL;
+ InvCursor(IC_AREA, CurX, CurY);
+ break;
+
+ case IC_AREA:
+ area = InvArea(CurX, CurY);
+
+ // Check for POINTED events
+ if (ino == INV_CONF)
+ InvBoxes(area == I_BODY, CurX, CurY);
+ else
+ InvLabels(area == I_BODY, CurX, CurY);
+
+ // No cursor trails while within inventory window
+ if (area == I_NOTIN)
+ UnHideCursorTrails();
+ else
+ HideCursorTrails();
+
+ switch (area) {
+ case I_NOTIN:
+ restoreMain = true;
+ break;
+
+ case I_TLEFT:
+ case I_BRIGHT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_DR) {
+ AlterCursor(IX_CURDD);
+ ICursor = IC_DR;
+ }
+ break;
+
+ case I_TRIGHT:
+ case I_BLEFT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_UR) {
+ AlterCursor(IX_CURDU);
+ ICursor = IC_UR;
+ }
+ break;
+
+ case I_TOP:
+ case I_BOTTOM:
+ if (!InvD[ino].resizable) {
+ restoreMain = true;
+ break;
+ }
+ if (ICursor != IC_TB) {
+ AlterCursor(IX_CURUD);
+ ICursor = IC_TB;
+ }
+ break;
+
+ case I_LEFT:
+ case I_RIGHT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_LR) {
+ AlterCursor(IX_CURLR);
+ ICursor = IC_LR;
+ }
+ break;
+
+ case I_UP:
+ case I_SLIDE_UP:
+ case I_DOWN:
+ case I_SLIDE_DOWN:
+ case I_SLIDE:
+ case I_MOVE:
+ case I_BODY:
+ restoreMain = true;
+ break;
+ }
+ break;
+ }
+
+ if (restoreMain && ICursor != IC_NORMAL) {
+ RestoreMainCursor();
+ ICursor = IC_NORMAL;
+ }
+}
+
+
+
+
+/*-------------------------------------------------------------------------*/
+
+
+/**************************************************************************/
+/******************** Conversation specific functions *********************/
+/**************************************************************************/
+
+
+void ConvAction(int index) {
+ assert(ino == INV_CONV); // not conv. window!
+
+ switch (index) {
+ case INV_NOICON:
+ return;
+
+ case INV_CLOSEICON:
+ thisConvIcon = -1; // Postamble
+ break;
+
+ case INV_OPENICON:
+ thisConvIcon = -2; // Preamble
+ break;
+
+ default:
+ thisConvIcon = InvD[ino].ItemOrder[index];
+ break;
+ }
+
+ RunPolyTinselCode(thisConvPoly, CONVERSE, BE_NONE, true);
+}
+/*-------------------------------------------------------------------------*/
+
+void AddIconToPermanentDefaultList(int icon) {
+ int i;
+
+ // See if it's already there
+ for (i = 0; i < Num0Order; i++) {
+ if (Inv0Order[i] == icon)
+ break;
+ }
+
+ // Add it if it isn't already there
+ if (i == Num0Order) {
+ Inv0Order[Num0Order++] = icon;
+ }
+}
+
+/*-------------------------------------------------------------------------*/
+
+void convPos(int fn) {
+ if (fn == CONV_DEF)
+ InvD[INV_CONV].inventoryY = 8;
+ else if (fn == CONV_BOTTOM)
+ InvD[INV_CONV].inventoryY = 150;
+}
+
+void ConvPoly(HPOLYGON hPoly) {
+ thisConvPoly = hPoly;
+}
+
+int convIcon(void) {
+ return thisConvIcon;
+}
+
+void CloseDownConv(void) {
+ if (InventoryState == ACTIVE_INV && ino == INV_CONV) {
+ KillInventory();
+ }
+}
+
+void convHide(bool hide) {
+ int aniX, aniY;
+ int i;
+
+ if (InventoryState == ACTIVE_INV && ino == INV_CONV) {
+ if (hide) {
+ for (i = 0; objArray[i] && i < MAX_WCOMP; i++) {
+ MultiAdjustXY(objArray[i], 2*SCREEN_WIDTH, 0);
+ }
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++) {
+ MultiAdjustXY(iconArray[i], 2*SCREEN_WIDTH, 0);
+ }
+ InventoryHidden = true;
+
+ InvLabels(false, 0, 0);
+ } else {
+ InventoryHidden = false;
+
+ for (i = 0; objArray[i] && i < MAX_WCOMP; i++) {
+ MultiAdjustXY(objArray[i], -2*SCREEN_WIDTH, 0);
+ }
+
+ // Don't flash if items changed. If they have, will be redrawn anyway.
+ if (!ItemsChanged) {
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++) {
+ MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0);
+ }
+ }
+
+ GetCursorXY(&aniX, &aniY, false);
+ InvLabels(true, aniX, aniY);
+ }
+ }
+}
+
+bool convHid(void) {
+ return InventoryHidden;
+}
+
+
+/**************************************************************************/
+/******************* Open and closing functions ***************************/
+/**************************************************************************/
+
+/**
+ * Start up an inventory window.
+ */
+
+void PopUpInventory(int invno) {
+ assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_CONF)); // Trying to open illegal inventory
+
+ if (InventoryState == IDLE_INV) {
+ bOpenConf = false; // Better safe than sorry...
+
+ DisableTags(); // Tags disabled during inventory
+
+ if (invno == INV_CONV) { // Conversation window?
+ // Start conversation with permanent contents
+ memset(InvD[INV_CONV].ItemOrder, 0, MAX_ININV*sizeof(int));
+ memcpy(InvD[INV_CONV].ItemOrder, Inv0Order, Num0Order*sizeof(int));
+ InvD[INV_CONV].NoofItems = Num0Order;
+ thisConvIcon = 0;
+ } else if (invno == INV_CONF) { // Configuration window?
+ cd.selBox = NOBOX;
+ cd.pointBox = NOBOX;
+ }
+
+ ino = invno; // The open inventory
+
+ ItemsChanged = false; // Nothing changed
+ InvDragging = ID_NONE; // Not dragging
+ InventoryState = ACTIVE_INV; // Inventory actiive
+ InventoryHidden = false; // Not hidden
+ InventoryMaximised = InvD[ino].bMax;
+ if (invno != INV_CONF) // Configuration window?
+ ConstructInventory(FULL); // Draw it up
+ else {
+ ConstructInventory(CONF); // Draw it up
+ }
+ }
+}
+
+void SetConfGlobals(PCONFINIT ci) {
+ InvD[INV_CONF].MinHicons = InvD[INV_CONF].MaxHicons = InvD[INV_CONF].NoofHicons = ci->h;
+ InvD[INV_CONF].MaxVicons = InvD[INV_CONF].MinVicons = InvD[INV_CONF].NoofVicons = ci->v;
+ InvD[INV_CONF].inventoryX = ci->x;
+ InvD[INV_CONF].inventoryY = ci->y;
+ cd.bExtraWin = ci->bExtraWin;
+ cd.Box = ci->Box;
+ cd.NumBoxes = ci->NumBoxes;
+ cd.ixHeading = ci->ixHeading;
+}
+
+/**
+ * PopupConf
+ */
+
+void PopUpConf(CONFTYPE type) {
+ int curX, curY;
+
+ if (InventoryState != IDLE_INV)
+ return;
+
+ InvD[INV_CONF].resizable = false;
+ InvD[INV_CONF].moveable = false;
+
+ switch (type) {
+ case SAVE:
+ case LOAD:
+ if (type == SAVE) {
+ SetCursorScreenXY(262, 91);
+ SetConfGlobals(&ciSave);
+ cd.editableRgroup = true;
+ } else {
+ SetConfGlobals(&ciLoad);
+ cd.editableRgroup = false;
+ }
+ firstFile(0);
+ break;
+
+ case QUIT:
+#ifdef JAPAN
+ SetCursorScreenXY(180, 106);
+#else
+ SetCursorScreenXY(180, 90);
+#endif
+ SetConfGlobals(&ciQuit);
+ break;
+
+ case RESTART:
+#ifdef JAPAN
+ SetCursorScreenXY(180, 106);
+#else
+ SetCursorScreenXY(180, 90);
+#endif
+ SetConfGlobals(&ciRestart);
+ break;
+
+ case OPTION:
+ SetConfGlobals(&ciOption);
+ break;
+
+ case CONTROLS:
+ SetConfGlobals(&ciControl);
+ break;
+
+ case SOUND:
+ SetConfGlobals(&ciSound);
+ break;
+
+#ifndef JAPAN
+ case SUBT:
+ SetConfGlobals(&ciSubtitles);
+ break;
+#endif
+
+ case TOPWIN:
+ SetConfGlobals(&ciTopWin);
+ ino = INV_CONF;
+ ConstructInventory(CONF); // Draw it up
+ InventoryState = BOGUS_INV;
+ return;
+
+ default:
+ return;
+ }
+
+ if (HeldItem != INV_NOICON)
+ DelAuxCursor(); // no longer aux cursor
+
+ PopUpInventory(INV_CONF);
+
+ if (type == SAVE || type == LOAD)
+ Select(0, false);
+#ifndef JAPAN
+#if !defined(USE_3FLAGS) || !defined(USE_4FLAGS) || !defined(USE_5FLAGS)
+ else if (type == SUBT) {
+#ifdef USE_3FLAGS
+ // VERY quick dirty bodges
+ if (language == TXT_FRENCH)
+ Select(0, false);
+ else if (language == TXT_GERMAN)
+ Select(1, false);
+ else
+ Select(2, false);
+#elif defined(USE_4FLAGS)
+ Select(language-1, false);
+#else
+ Select(language, false);
+#endif
+ }
+#endif
+#endif // JAPAN
+
+ GetCursorXY(&curX, &curY, false);
+ InvCursor(IC_AREA, curX, curY);
+}
+
+/**
+ * Close down an inventory window.
+ */
+
+void KillInventory(void) {
+ if (objArray[0] != NULL) {
+ DumpObjArray();
+ DumpDobjArray();
+ DumpIconArray();
+ }
+
+ if (InventoryState == ACTIVE_INV) {
+ EnableTags();
+
+ InvD[ino].bMax = InventoryMaximised;
+
+ UnHideCursorTrails();
+ _vm->divertKeyInput(NULL);
+ }
+
+ InventoryState = IDLE_INV;
+
+ if (bOpenConf) {
+ bOpenConf = false;
+ PopUpConf(OPTION);
+ } else if (ino == INV_CONF)
+ InventoryIconCursor();
+}
+
+void CloseInventory(void) {
+ // If not active, ignore this
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // If hidden, a conversation action is still underway - ignore this
+ if (InventoryHidden)
+ return;
+
+ // If conversation, this is a closeing event
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+
+ KillInventory();
+
+ RestoreMainCursor();
+}
+
+
+
+/**************************************************************************/
+/************************ The inventory process ***************************/
+/**************************************************************************/
+
+/**
+ * Redraws the icons if appropriate. Also handle button press/toggle effects
+ */
+void InventoryProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ CORO_SLEEP(1); // allow scheduling
+
+ if (objArray[0] != NULL) {
+ if (ItemsChanged && ino != INV_CONF && !InventoryHidden) {
+ FillInInventory();
+
+ // Needed when clicking on scroll bar.
+ int curX, curY;
+ GetCursorXY(&curX, &curY, false);
+ InvCursor(IC_AREA, curX, curY);
+
+ ItemsChanged = false;
+ }
+ if (ino != INV_CONF) {
+ for (int i = 0; i < MAX_ICONS; i++) {
+ if (iconArray[i] != NULL)
+ StepAnimScript(&iconAnims[i]);
+ }
+ }
+ if (InvDragging == ID_MDCONT) {
+ // Mixing desk control
+ int sval, index, *pival;
+
+ index = cd.selBox & ~IS_MASK;
+ pival = cd.Box[index].ival;
+ sval = *pival;
+
+ if (cd.selBox & IS_LEFT) {
+ *pival -= cd.Box[index].h;
+ if (*pival < 0)
+ *pival = 0;
+ } else if (cd.selBox & IS_RIGHT) {
+ *pival += cd.Box[index].h;
+ if (*pival > cd.Box[index].w)
+ *pival = cd.Box[index].w;
+ }
+
+ if (sval != *pival) {
+ SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN);
+ }
+ }
+ }
+
+ if (g_buttonEffect.bButAnim) {
+ assert(g_buttonEffect.box);
+ if (g_buttonEffect.press) {
+ if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT)
+ CORO_INVOKE_1(ButtonPress, g_buttonEffect.box);
+ switch (g_buttonEffect.box->boxFunc) {
+ case SAVEGAME:
+ KillInventory();
+ InvSaveGame();
+ break;
+ case LOADGAME:
+ KillInventory();
+ InvLoadGame();
+ break;
+ case IQUITGAME:
+ _vm->quitFlag = true;
+ break;
+ case CLOSEWIN:
+ KillInventory();
+ break;
+ case OPENLOAD:
+ KillInventory();
+ PopUpConf(LOAD);
+ break;
+ case OPENSAVE:
+ KillInventory();
+ PopUpConf(SAVE);
+ break;
+ case OPENREST:
+ KillInventory();
+ PopUpConf(RESTART);
+ break;
+ case OPENSOUND:
+ KillInventory();
+ PopUpConf(SOUND);
+ break;
+ case OPENCONT:
+ KillInventory();
+ PopUpConf(CONTROLS);
+ break;
+ #ifndef JAPAN
+ case OPENSUBT:
+ KillInventory();
+ PopUpConf(SUBT);
+ break;
+ #endif
+ case OPENQUIT:
+ KillInventory();
+ PopUpConf(QUIT);
+ break;
+ case INITGAME:
+ KillInventory();
+ bRestart = true;
+ break;
+ #if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case CLANG:
+ if (!LanguageChange())
+ KillInventory();
+ break;
+ case RLANG:
+ KillInventory();
+ break;
+ #endif
+ default:
+ break;
+ }
+ } else
+ CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box);
+
+ g_buttonEffect.bButAnim = false;
+ }
+
+ }
+ CORO_END_CODE;
+}
+
+/**************************************************************************/
+/*************** Drag stuff - Resizing and moving window ******************/
+/**************************************************************************/
+
+/**
+ * Appears to find the nearest entry in slideStuff[] to the supplied
+ * y-coordinate.
+ */
+int NearestSlideY(int fity) {
+ int nearDist = 1000;
+ int thisDist;
+ int nearI = 0; // Index of nearest fit
+ int i = 0;
+
+ do {
+ thisDist = ABS(slideStuff[i].y - fity);
+ if (thisDist < nearDist) {
+ nearDist = thisDist;
+ nearI = i;
+ }
+ } while (slideStuff[++i].n != -1);
+ return nearI;
+}
+
+/**
+ * Gets called at the start and end of a drag on the slider, and upon
+ * y-movement during such a drag.
+ */
+void SlideSlider(int y, SSFN fn) {
+ static int newY = 0, lasti = 0; // FIXME: local static var
+ int gotoY, ati;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ newY = slideY;
+ lasti = NearestSlideY(slideY);
+ break;
+
+ case S_SLIDE: // Y-movement during drag
+ newY = newY + y; // New y-position
+
+ if (newY < slideYmin)
+ gotoY = slideYmin; // Above top limit
+ else if (newY > slideYmax)
+ gotoY = slideYmax; // Below bottom limit
+ else
+ gotoY = newY; // Hunky-Dory
+
+ // Move slider to new position
+ MultiMoveRelXY(SlideObject, 0, gotoY - slideY);
+ slideY = gotoY;
+
+ // Re-draw icons if necessary
+ ati = NearestSlideY(slideY);
+ if (ati != lasti) {
+ InvD[ino].FirstDisp = slideStuff[ati].n;
+ assert(InvD[ino].FirstDisp >= 0); // negative first displayed
+ ItemsChanged = true;
+ lasti = ati;
+ }
+ break;
+
+ case S_END: // End of a drag on the slider
+ // Draw icons from new start icon
+ ati = NearestSlideY(slideY);
+ InvD[ino].FirstDisp = slideStuff[ati].n;
+ ItemsChanged = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Gets called at the start and end of a drag on the slider, and upon
+ * y-movement during such a drag.
+ */
+
+void SlideCSlider(int y, SSFN fn) {
+ static int newY = 0; // FIXME: local static var
+ int gotoY;
+ int fc;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ newY = slideY;
+ break;
+
+ case S_SLIDE: // Y-movement during drag
+ newY = newY + y; // New y-position
+
+ if (newY < slideYmin)
+ gotoY = slideYmin; // Above top limit
+ else if (newY > slideYmax)
+ gotoY = slideYmax; // Below bottom limit
+ else
+ gotoY = newY; // Hunky-Dory
+
+ slideY = gotoY;
+
+ fc = cd.fileBase;
+ firstFile((slideY-slideYmin)*(MAX_SFILES-NUM_SL_RGROUP)/(slideYmax-slideYmin));
+ if (fc != cd.fileBase) {
+ AddBoxes(false);
+ fc -= cd.fileBase;
+ cd.selBox += fc;
+ if (cd.selBox < 0)
+ cd.selBox = 0;
+ else if (cd.selBox >= NUM_SL_RGROUP)
+ cd.selBox = NUM_SL_RGROUP-1;
+ Select(cd.selBox, true);
+ }
+ break;
+
+ case S_END: // End of a drag on the slider
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Gets called at the start and end of a drag on a mixing desk slider,
+ * and upon x-movement during such a drag.
+ */
+
+static void SlideMSlider(int x, SSFN fn) {
+ static int newX = 0; // FIXME: local static var
+ int gotoX;
+ int index, i;
+
+ if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN)
+ ;
+ else if (!(cd.selBox & IS_SLIDER))
+ return;
+
+ // Work out the indices
+ index = cd.selBox & ~IS_MASK;
+ for (i = 0; i < numMdSlides; i++)
+ if (mdSlides[i].num == index)
+ break;
+ assert(i < numMdSlides);
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ // can use index as a throw-away value
+ GetAniPosition(mdSlides[i].obj, &newX, &index);
+ lX = sX = newX;
+ break;
+
+ case S_SLIDE: // X-movement during drag
+ if (x == 0)
+ return;
+
+ newX = newX + x; // New x-position
+
+ if (newX < mdSlides[i].min)
+ gotoX = mdSlides[i].min; // Below bottom limit
+ else if (newX > mdSlides[i].max)
+ gotoX = mdSlides[i].max; // Above top limit
+ else
+ gotoX = newX; // Hunky-Dory
+
+ // Move slider to new position
+ MultiMoveRelXY(mdSlides[i].obj, gotoX - sX, 0);
+ sX = gotoX;
+
+ if (lX != sX) {
+ *cd.Box[index].ival = (sX - mdSlides[i].min)*cd.Box[index].w/SLIDE_RANGE;
+ if (cd.Box[index].boxFunc == MIDIVOL)
+ SetMidiVolume(*cd.Box[index].ival);
+#ifdef MAC_OPTIONS
+ if (cd.Box[index].boxFunc == MASTERVOL)
+ SetSystemVolume(*cd.Box[index].ival);
+
+ if (cd.Box[index].boxFunc == SAMPVOL)
+ SetSampleVolume(*cd.Box[index].ival);
+#endif
+ lX = sX;
+ }
+ break;
+
+ case S_TIMEUP:
+ case S_TIMEDN:
+ gotoX = SLIDE_RANGE*(*cd.Box[index].ival)/cd.Box[index].w;
+ MultiSetAniX(mdSlides[i].obj, mdSlides[i].min+gotoX);
+
+ if (cd.Box[index].boxFunc == MIDIVOL)
+ SetMidiVolume(*cd.Box[index].ival);
+#ifdef MAC_OPTIONS
+ if (cd.Box[index].boxFunc == MASTERVOL)
+ SetSystemVolume(*cd.Box[index].ival);
+
+ if (cd.Box[index].boxFunc == SAMPVOL)
+ SetSampleVolume(*cd.Box[index].ival);
+#endif
+ break;
+
+ case S_END: // End of a drag on the slider
+ AddBoxes(false); // Might change position slightly
+#ifndef JAPAN
+ if (ino == INV_CONF && cd.Box == subtitlesBox)
+ Select(language, false);
+#endif
+ break;
+ }
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingTaller(void) {
+ if (SuppV) {
+ Ychange += SuppV;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY += SuppV;
+ SuppV = 0;
+ }
+ while (Ychange > (ITEM_HEIGHT+1) && InvD[ino].NoofVicons < InvD[ino].MaxVicons) {
+ Ychange -= (ITEM_HEIGHT+1);
+ InvD[ino].NoofVicons++;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY -= (ITEM_HEIGHT+1);
+ }
+ if (InvD[ino].NoofVicons < InvD[ino].MaxVicons) {
+ SuppV = Ychange;
+ Ychange = 0;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY -= SuppV;
+ }
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingShorter(void) {
+ int StartNvi = InvD[ino].NoofVicons;
+ int StartUv = SuppV;
+
+ if (SuppV) {
+ Ychange += (SuppV - (ITEM_HEIGHT+1));
+ InvD[ino].NoofVicons++;
+ SuppV = 0;
+ }
+ while (Ychange < -(ITEM_HEIGHT+1) && InvD[ino].NoofVicons > InvD[ino].MinVicons) {
+ Ychange += (ITEM_HEIGHT+1);
+ InvD[ino].NoofVicons--;
+ }
+ if (InvD[ino].NoofVicons > InvD[ino].MinVicons && Ychange) {
+ SuppV = (ITEM_HEIGHT+1) + Ychange;
+ InvD[ino].NoofVicons--;
+ Ychange = 0;
+ }
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - InvD[ino].NoofVicons) - (SuppV - StartUv);
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingWider(void) {
+ int StartNhi = InvD[ino].NoofHicons;
+ int StartUh = SuppH;
+
+ if (SuppH) {
+ Xchange += SuppH;
+ SuppH = 0;
+ }
+ while (Xchange > (ITEM_WIDTH+1) && InvD[ino].NoofHicons < InvD[ino].MaxHicons) {
+ Xchange -= (ITEM_WIDTH+1);
+ InvD[ino].NoofHicons++;
+ }
+ if (InvD[ino].NoofHicons < InvD[ino].MaxHicons) {
+ SuppH = Xchange;
+ Xchange = 0;
+ }
+ if (Xcompensate == 'L')
+ InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh);
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingNarrower(void) {
+ int StartNhi = InvD[ino].NoofHicons;
+ int StartUh = SuppH;
+
+ if (SuppH) {
+ Xchange += (SuppH - (ITEM_WIDTH+1));
+ InvD[ino].NoofHicons++;
+ SuppH = 0;
+ }
+ while (Xchange < -(ITEM_WIDTH+1) && InvD[ino].NoofHicons > InvD[ino].MinHicons) {
+ Xchange += (ITEM_WIDTH+1);
+ InvD[ino].NoofHicons--;
+ }
+ if (InvD[ino].NoofHicons > InvD[ino].MinHicons && Xchange) {
+ SuppH = (ITEM_WIDTH+1) + Xchange;
+ InvD[ino].NoofHicons--;
+ Xchange = 0;
+ }
+ if (Xcompensate == 'L')
+ InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh);
+}
+
+
+/**
+ * Called from Xmovement()/Ymovement() during re-sizing.
+ */
+
+void ChangeingSize(void) {
+ /* Make it taller or shorter if necessary. */
+ if (Ychange > 0)
+ GettingTaller();
+ else if (Ychange < 0)
+ GettingShorter();
+
+ /* Make it wider or narrower if necessary. */
+ if (Xchange > 0)
+ GettingWider();
+ else if (Xchange < 0)
+ GettingNarrower();
+
+ ConstructInventory(EMPTY);
+}
+
+/**
+ * Called from cursor module when cursor moves while inventory is up.
+ */
+
+void Xmovement(int x) {
+ int aniX, aniY;
+ int i;
+
+ if (x && objArray[0] != NULL) {
+ switch (InvDragging) {
+ case ID_MOVE:
+ GetAniPosition(objArray[0], &InvD[ino].inventoryX, &aniY);
+ InvD[ino].inventoryX +=x;
+ MultiSetAniX(objArray[0], InvD[ino].inventoryX);
+ for (i = 1; objArray[i] && i < MAX_WCOMP; i++)
+ MultiMoveRelXY(objArray[i], x, 0);
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++)
+ MultiMoveRelXY(iconArray[i], x, 0);
+ break;
+
+ case ID_LEFT:
+ case ID_TLEFT:
+ case ID_BLEFT:
+ Xchange -= x;
+ ChangeingSize();
+ break;
+
+ case ID_RIGHT:
+ case ID_TRIGHT:
+ case ID_BRIGHT:
+ Xchange += x;
+ ChangeingSize();
+ break;
+
+ case ID_NONE:
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_AREA, aniX, aniY);
+ break;
+
+ case ID_MDCONT:
+ SlideMSlider(x, S_SLIDE);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * Called from cursor module when cursor moves while inventory is up.
+ */
+
+void Ymovement(int y) {
+ int aniX, aniY;
+ int i;
+
+ if (y && objArray[0] != NULL) {
+ switch (InvDragging) {
+ case ID_MOVE:
+ GetAniPosition(objArray[0], &aniX, &InvD[ino].inventoryY);
+ InvD[ino].inventoryY +=y;
+ MultiSetAniY(objArray[0], InvD[ino].inventoryY);
+ for (i = 1; objArray[i] && i < MAX_WCOMP; i++)
+ MultiMoveRelXY(objArray[i], 0, y);
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++)
+ MultiMoveRelXY(iconArray[i], 0, y);
+ break;
+
+ case ID_SLIDE:
+ SlideSlider(y, S_SLIDE);
+ break;
+
+ case ID_CSLIDE:
+ SlideCSlider(y, S_SLIDE);
+ break;
+
+ case ID_BOTTOM:
+ case ID_BLEFT:
+ case ID_BRIGHT:
+ Ychange += y;
+ ChangeingSize();
+ break;
+
+ case ID_TOP:
+ case ID_TLEFT:
+ case ID_TRIGHT:
+ Ychange -= y;
+ ChangeingSize();
+ break;
+
+ case ID_NONE:
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_AREA, aniX, aniY);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * Called when a drag is commencing.
+ */
+
+void InvDragStart(void) {
+ int curX, curY; // cursor's animation position
+
+ GetCursorXY(&curX, &curY, false);
+
+/*
+* Do something different for Save/Restore screens
+*/
+ if (ino == INV_CONF) {
+ int whichbox;
+
+ whichbox = WhichInvBox(curX, curY, true);
+
+ if (whichbox == IB_SLIDE) {
+ InvDragging = ID_CSLIDE;
+ SlideCSlider(0, S_START);
+ } else if (whichbox > 0 && (whichbox & IS_MASK)) {
+ InvDragging = ID_MDCONT; // Mixing desk control
+ cd.selBox = whichbox;
+ SlideMSlider(0, S_START);
+ }
+ return;
+ }
+
+/*
+* Normal operation
+*/
+ switch (InvArea(curX, curY)) {
+ case I_MOVE:
+ if (InvD[ino].moveable) {
+ InvDragging = ID_MOVE;
+ }
+ break;
+
+ case I_SLIDE:
+ InvDragging = ID_SLIDE;
+ SlideSlider(0, S_START);
+ break;
+
+ case I_BOTTOM:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ InvDragging = ID_BOTTOM;
+ Ycompensate = 'B';
+ }
+ break;
+
+ case I_TOP:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ InvDragging = ID_TOP;
+ Ycompensate = 'T';
+ }
+ break;
+
+ case I_LEFT:
+ if (InvD[ino].resizable) {
+ Xchange = 0;
+ InvDragging = ID_LEFT;
+ Xcompensate = 'L';
+ }
+ break;
+
+ case I_RIGHT:
+ if (InvD[ino].resizable) {
+ Xchange = 0;
+ InvDragging = ID_RIGHT;
+ Xcompensate = 'R';
+ }
+ break;
+
+ case I_TLEFT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'T';
+ Xchange = 0;
+ Xcompensate = 'L';
+ InvDragging = ID_TLEFT;
+ }
+ break;
+
+ case I_TRIGHT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'T';
+ Xchange = 0;
+ Xcompensate = 'R';
+ InvDragging = ID_TRIGHT;
+ }
+ break;
+
+ case I_BLEFT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'B';
+ Xchange = 0;
+ Xcompensate = 'L';
+ InvDragging = ID_BLEFT;
+ }
+ break;
+
+ case I_BRIGHT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'B';
+ Xchange = 0;
+ Xcompensate = 'R';
+ InvDragging = ID_BRIGHT;
+ }
+ break;
+ }
+}
+
+/**
+ * Called when a drag is over.
+ */
+
+void InvDragEnd(void) {
+ int curX, curY; // cursor's animation position
+
+ GetCursorXY(&curX, &curY, false);
+
+ if (InvDragging != ID_NONE) {
+ if (InvDragging == ID_SLIDE) {
+ SlideSlider(0, S_END);
+ } else if (InvDragging == ID_CSLIDE) {
+ ; // No action
+ } else if (InvDragging == ID_MDCONT) {
+ SlideMSlider(0, S_END);
+ } else if (InvDragging == ID_MOVE) {
+ ; // No action
+ } else {
+ // Were re-sizing. Redraw the whole thing.
+ DumpDobjArray();
+ DumpObjArray();
+ ConstructInventory(FULL);
+
+ // If this was the maximised, it no longer is!
+ if (InventoryMaximised) {
+ InventoryMaximised = false;
+ InvD[ino].otherX = InvD[ino].inventoryX;
+ InvD[ino].otherY = InvD[ino].inventoryY;
+ }
+ }
+ InvDragging = ID_NONE;
+ }
+
+ // Cursor could well now be inappropriate
+ InvCursor(IC_AREA, curX, curY);
+
+ Xchange = Ychange = 0; // Probably no need, but does no harm!
+}
+
+
+/**************************************************************************/
+/************** Incoming events - further processing **********************/
+/**************************************************************************/
+
+/**
+ * ConfAction
+ */
+void ConfAction(int i, bool dbl) {
+
+ if (i >= 0) {
+ switch (cd.Box[i].boxType) {
+ case FLIP:
+ if (dbl) {
+ *(cd.Box[i].ival) ^= 1; // XOR with true
+ AddBoxes(false);
+ }
+ break;
+
+ case TOGGLE:
+ if (!g_buttonEffect.bButAnim) {
+ g_buttonEffect.bButAnim = true;
+ g_buttonEffect.box = &cd.Box[i];
+ g_buttonEffect.press = false;
+ }
+ break;
+
+ case RGROUP:
+ if (dbl) {
+ // Already highlighted
+ switch (cd.Box[i].boxFunc) {
+ case SAVEGAME:
+ KillInventory();
+ InvSaveGame();
+ break;
+ case LOADGAME:
+ KillInventory();
+ InvLoadGame();
+ break;
+ default:
+ break;
+ }
+ } else {
+ Select(i, false);
+ }
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ if (dbl) {
+ Select(i, false);
+ LanguageChange();
+ } else {
+ Select(i, false);
+ }
+ break;
+#endif
+
+ case AAGBUT:
+ case ARSGBUT:
+ case ARSBUT:
+ case AABUT:
+ case AATBUT:
+ if (g_buttonEffect.bButAnim)
+ break;
+
+ g_buttonEffect.bButAnim = true;
+ g_buttonEffect.box = &cd.Box[i];
+ g_buttonEffect.press = true;
+ break;
+ default:
+ break;
+ }
+ } else {
+ ConfActionSpecial(i);
+ }
+}
+
+static void ConfActionSpecial(int i) {
+ switch (i) {
+ case IB_NONE:
+ break;
+ case IB_UP: // Scroll up
+ if (cd.fileBase > 0) {
+ firstFile(cd.fileBase-1);
+ AddBoxes(true);
+ if (cd.selBox < NUM_SL_RGROUP-1)
+ cd.selBox += 1;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_DOWN: // Scroll down
+ if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) {
+ firstFile(cd.fileBase+1);
+ AddBoxes(true);
+ if (cd.selBox)
+ cd.selBox -= 1;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_SLIDE_UP:
+ if (cd.fileBase > 0) {
+ firstFile(cd.fileBase-(NUM_SL_RGROUP-1));
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_SLIDE_DOWN: // Scroll down
+ if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) {
+ firstFile(cd.fileBase+(NUM_SL_RGROUP-1));
+ AddBoxes(true);
+ cd.selBox = NUM_SL_RGROUP-1;
+ Select(cd.selBox, true);
+ }
+ break;
+ }
+}
+// SLIDE_UP and SLIDE_DOWN on d click??????
+
+void InvPutDown(int index) {
+ int aniX, aniY;
+ // index is the drop position
+ int hiIndex; // Current position of held item (if in)
+
+ // Find where the held item is positioned in this inventory (if it is)
+ for (hiIndex = 0; hiIndex < InvD[ino].NoofItems; hiIndex++)
+ if (InvD[ino].ItemOrder[hiIndex] == HeldItem)
+ break;
+
+ // If drop position would leave a gap, move it up
+ if (index >= InvD[ino].NoofItems) {
+ if (hiIndex == InvD[ino].NoofItems) // Not in, add it
+ index = InvD[ino].NoofItems;
+ else
+ index = InvD[ino].NoofItems - 1;
+ }
+
+ if (hiIndex == InvD[ino].NoofItems) { // Not in, add it
+ if (InvD[ino].NoofItems < InvD[ino].MaxInvObj) {
+ InvD[ino].NoofItems++;
+
+ // Don't leave it in the other inventory!
+ if (InventoryPos(HeldItem) != INV_HELDNOTIN)
+ RemFromInventory(ino == INV_1 ? INV_2 : INV_1, HeldItem);
+ } else {
+ // No room at the inn!
+ return;
+ }
+ }
+
+ // Position it in the inventory
+ if (index < hiIndex) {
+ memmove(&InvD[ino].ItemOrder[index + 1], &InvD[ino].ItemOrder[index], (hiIndex-index)*sizeof(int));
+ InvD[ino].ItemOrder[index] = HeldItem;
+ } else if (index > hiIndex) {
+ memmove(&InvD[ino].ItemOrder[hiIndex], &InvD[ino].ItemOrder[hiIndex+1], (index-hiIndex)*sizeof(int));
+ InvD[ino].ItemOrder[index] = HeldItem;
+ } else {
+ InvD[ino].ItemOrder[index] = HeldItem;
+ }
+
+ HeldItem = INV_NOICON;
+ ItemsChanged = true;
+ DelAuxCursor();
+ RestoreMainCursor();
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_DROP, aniX, aniY);
+}
+
+void InvPdProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ GetToken(TOKEN_LEFT_BUT);
+ CORO_SLEEP(dclickSpeed+1);
+ FreeToken(TOKEN_LEFT_BUT);
+
+ // get the stuff copied to process when it was created
+ int *pindex = (int *)ProcessGetParamsSelf();
+
+ InvPutDown(*pindex);
+
+ CORO_END_CODE;
+}
+
+void InvPickup(int index) {
+ PINV_OBJECT invObj;
+
+ if (index != INV_NOICON) {
+ if (HeldItem == INV_NOICON && InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ // Pick-up
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, WALKTO, INV_PICKUP, index);
+ } else if (HeldItem != INV_NOICON) { // Put icon down
+ // Put-down
+ invObj = findInvObject(HeldItem);
+
+ if (invObj->attribute & IO_DROPCODE && invObj->hScript)
+ RunInvTinselCode(invObj, PUTDOWN, INV_PICKUP, index);
+
+ else if (!(invObj->attribute & IO_ONLYINV1 && ino !=INV_1)
+ && !(invObj->attribute & IO_ONLYINV2 && ino !=INV_2))
+ CoroutineInstall(PID_TCODE, InvPdProcess, &index, sizeof(index));
+ }
+ }
+}
+
+/**
+ * Pick up/put down icon
+ */
+void InvSLClick(void) {
+ int i;
+ int aniX, aniY; // Cursor's animation position
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_NOTIN:
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+ KillInventory();
+ break;
+
+ case I_SLIDE_UP:
+ if (InvD[ino].NoofVicons == 1)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+
+ case I_UP:
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+
+ case I_SLIDE_DOWN:
+ if (InvD[ino].NoofVicons == 1)
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++) {
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ }
+ ItemsChanged = true;
+ break;
+
+ case I_DOWN:
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) {
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ ItemsChanged = true;
+ }
+ break;
+
+ case I_BODY:
+ if (ino == INV_CONF) {
+ if (!InventoryHidden)
+ ConfAction(WhichInvBox(aniX, aniY, false), false);
+ } else {
+ i = InvItem(&aniX, &aniY, false);
+
+ // Special bodge for David, to
+ // cater for drop in dead space between icons
+ if (i == INV_NOICON && HeldItem != INV_NOICON && (ino == INV_1 || ino == INV_2)) {
+ aniX += 1; // 1 to the right
+ i = InvItem(&aniX, &aniY, false);
+ if (i == INV_NOICON) {
+ aniX -= 1; // 1 down
+ aniY += 1;
+ i = InvItem(&aniX, &aniY, false);
+ if (i == INV_NOICON) {
+ aniX += 1; // 1 down-right
+ i = InvItem(&aniX, &aniY, false);
+ }
+ }
+ }
+
+ if (ino == INV_CONV) {
+ ConvAction(i);
+ } else
+ InvPickup(i);
+ }
+ break;
+ }
+}
+
+void InvAction(void) {
+ int index;
+ PINV_OBJECT invObj;
+ int aniX, aniY;
+ int i;
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_BODY:
+ if (ino == INV_CONF) {
+ if (!InventoryHidden)
+ ConfAction(WhichInvBox(aniX, aniY, false), true);
+ } else if (ino == INV_CONV) {
+ index = InvItem(&aniX, &aniY, false);
+ ConvAction(index);
+ } else {
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, ACTION, INV_ACTION, index);
+ }
+ }
+ }
+ break;
+
+ case I_MOVE: // Maximise/unmaximise inventory
+ if (!InvD[ino].resizable)
+ break;
+
+ if (!InventoryMaximised) {
+ InvD[ino].sNoofHicons = InvD[ino].NoofHicons;
+ InvD[ino].sNoofVicons = InvD[ino].NoofVicons;
+ InvD[ino].NoofHicons = InvD[ino].MaxHicons;
+ InvD[ino].NoofVicons = InvD[ino].MaxVicons;
+ InventoryMaximised = true;
+
+ i = InvD[ino].inventoryX;
+ InvD[ino].inventoryX = InvD[ino].otherX;
+ InvD[ino].otherX = i;
+ i = InvD[ino].inventoryY;
+ InvD[ino].inventoryY = InvD[ino].otherY;
+ InvD[ino].otherY = i;
+ } else {
+ InvD[ino].NoofHicons = InvD[ino].sNoofHicons;
+ InvD[ino].NoofVicons = InvD[ino].sNoofVicons;
+ InventoryMaximised = false;
+
+ i = InvD[ino].inventoryX;
+ InvD[ino].inventoryX = InvD[ino].otherX;
+ InvD[ino].otherX = i;
+ i = InvD[ino].inventoryY;
+ InvD[ino].inventoryY = InvD[ino].otherY;
+ InvD[ino].otherY = i;
+ }
+
+ // Delete current, and re-draw
+ DumpDobjArray();
+ DumpObjArray();
+ ConstructInventory(FULL);
+ break;
+
+ case I_UP:
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+ case I_DOWN:
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) {
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ ItemsChanged = true;
+ }
+ break;
+ }
+
+}
+
+
+void InvLook(void) {
+ int index;
+ PINV_OBJECT invObj;
+ int aniX, aniY;
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_BODY:
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, LOOK, INV_LOOK, index);
+ }
+ }
+ break;
+
+ case I_NOTIN:
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+ KillInventory();
+ break;
+ }
+}
+
+
+/**************************************************************************/
+/********************* Incoming events ************************************/
+/**************************************************************************/
+
+
+void ButtonToInventory(BUTEVENT be) {
+ if (InventoryHidden)
+ return;
+
+ switch (be) {
+ case INV_PICKUP: // BE_SLEFT
+ InvSLClick();
+ break;
+
+ case INV_LOOK: // BE_SRIGHT
+ if (IsConfWindow())
+ InvSLClick();
+ else
+ InvLook();
+ break;
+
+ case INV_ACTION: // BE_DLEFT
+ if (InvDragging != ID_MDCONT)
+ InvDragEnd();
+ InvAction();
+ break;
+
+ case BE_LDSTART: // Left drag start
+ InvDragStart();
+ break;
+
+ case BE_LDEND: // Left drag end
+ InvDragEnd();
+ break;
+
+// case BE_DLEFT: // Double click left (also ends left drag)
+// ButtonToInventory(LDEND);
+// break;
+
+ case BE_RDSTART:
+ case BE_RDEND:
+ case BE_UNKNOWN:
+ break;
+ default:
+ break;
+ }
+}
+
+void KeyToInventory(KEYEVENT ke) {
+ int i;
+
+ switch (ke) {
+ case ESC_KEY:
+ if (InventoryState == ACTIVE_INV && ino == INV_CONF && cd.Box != optionBox)
+ bOpenConf = true;
+ CloseInventory();
+ break;
+
+ case PGDN_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ ConfActionSpecial(IB_SLIDE_DOWN);
+ } else {
+ // This code is a copy of SLClick on IB_SLIDE_DOWN
+ // TODO: So share this duplicate code
+ if (InvD[ino].NoofVicons == 1)
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++) {
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ }
+ ItemsChanged = true;
+ }
+ break;
+
+ case PGUP_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ ConfActionSpecial(IB_SLIDE_UP);
+ } else {
+ // This code is a copy of SLClick on I_SLIDE_UP
+ // TODO: So share this duplicate code
+ if (InvD[ino].NoofVicons == 1)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ case HOME_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ firstFile(0);
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ } else {
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ case END_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ firstFile(MAX_SFILES); // Will get reduced to appropriate value
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ } else {
+ InvD[ino].FirstDisp = InvD[ino].NoofItems - InvD[ino].NoofHicons*InvD[ino].NoofVicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ default:
+ error("We're at KeyToInventory(), with default");
+ }
+}
+
+/**************************************************************************/
+/************************* Odds and Ends **********************************/
+/**************************************************************************/
+
+/**
+ * Called from Glitter function invdepict()
+ * Changes (permanently) the animation film for that object.
+ */
+
+void invObjectFilm(int object, SCNHANDLE hFilm) {
+ PINV_OBJECT invObj;
+
+ invObj = findInvObject(object);
+ invObj->hFilm = hFilm;
+
+ if (HeldItem != object)
+ ItemsChanged = true;
+}
+
+/**
+ * (Un)serialize the inventory data for save/restore game.
+ */
+
+void syncInvInfo(Serializer &s) {
+ for (int i = 0; i < NUM_INV; i++) {
+ s.syncAsSint32LE(InvD[i].MinHicons);
+ s.syncAsSint32LE(InvD[i].MinVicons);
+ s.syncAsSint32LE(InvD[i].MaxHicons);
+ s.syncAsSint32LE(InvD[i].MaxVicons);
+ s.syncAsSint32LE(InvD[i].NoofHicons);
+ s.syncAsSint32LE(InvD[i].NoofVicons);
+ for (int j = 0; j < MAX_ININV; j++) {
+ s.syncAsSint32LE(InvD[i].ItemOrder[j]);
+ }
+ s.syncAsSint32LE(InvD[i].NoofItems);
+ s.syncAsSint32LE(InvD[i].FirstDisp);
+ s.syncAsSint32LE(InvD[i].inventoryX);
+ s.syncAsSint32LE(InvD[i].inventoryY);
+ s.syncAsSint32LE(InvD[i].otherX);
+ s.syncAsSint32LE(InvD[i].otherY);
+ s.syncAsSint32LE(InvD[i].MaxInvObj);
+ s.syncAsSint32LE(InvD[i].hInvTitle);
+ s.syncAsSint32LE(InvD[i].resizable);
+ s.syncAsSint32LE(InvD[i].moveable);
+ s.syncAsSint32LE(InvD[i].sNoofHicons);
+ s.syncAsSint32LE(InvD[i].sNoofVicons);
+ s.syncAsSint32LE(InvD[i].bMax);
+ }
+}
+
+/**************************************************************************/
+/************************ Initialisation stuff ****************************/
+/**************************************************************************/
+
+/**
+ * Called from PlayGame(), stores handle to inventory objects' data -
+ * its id, animation film and Glitter script.
+ */
+// Note: the SCHANDLE type here has been changed to a void*
+void RegisterIcons(void *cptr, int num) {
+ numObjects = num;
+ pio = (PINV_OBJECT) cptr;
+}
+
+/**
+ * Called from Glitter function 'dec_invw()' - Declare the bits that the
+ * inventory windows are constructed from, and special cursors.
+ */
+
+void setInvWinParts(SCNHANDLE hf) {
+#ifdef DEBUG
+ const FILM *pfilm;
+#endif
+
+ winPartsf = hf;
+
+#ifdef DEBUG
+ pfilm = (const FILM *)LockMem(hf);
+ assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORREELS); // not as many reels as expected
+#endif
+}
+
+/**
+ * Called from Glitter function 'dec_flags()' - Declare the language
+ * flag films
+ */
+
+void setFlagFilms(SCNHANDLE hf) {
+#ifdef DEBUG
+ const FILM *pfilm;
+#endif
+
+ flagFilm = hf;
+
+#ifdef DEBUG
+ pfilm = (const FILM *)LockMem(hf);
+ assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected
+#endif
+}
+
+void setConfigStrings(SCNHANDLE *tp) {
+ memcpy(configStrings, tp, sizeof(configStrings));
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv(int num, SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight,
+ int startx, int starty, bool moveable) {
+ if (MaxWidth > MAXHICONS)
+ MaxWidth = MAXHICONS; // Max window width
+ if (MaxHeight > MAXVICONS)
+ MaxHeight = MAXVICONS; // Max window height
+ if (MaxContents > MAX_ININV)
+ MaxContents = MAX_ININV; // Max contents
+
+ if (StartWidth > MaxWidth)
+ StartWidth = MaxWidth;
+ if (StartHeight > MaxHeight)
+ StartHeight = MaxHeight;
+
+ InventoryState = IDLE_INV;
+
+ InvD[num].MaxHicons = MaxWidth;
+ InvD[num].MinHicons = MinWidth;
+ InvD[num].MaxVicons = MaxHeight;
+ InvD[num].MinVicons = MinHeight;
+
+ InvD[num].NoofHicons = StartWidth;
+ InvD[num].NoofVicons = StartHeight;
+
+ memset(InvD[num].ItemOrder, 0, sizeof(InvD[num].ItemOrder));
+ InvD[num].NoofItems = 0;
+
+ InvD[num].FirstDisp = 0;
+
+ InvD[num].inventoryX = startx;
+ InvD[num].inventoryY = starty;
+ InvD[num].otherX = 21;
+ InvD[num].otherY = 15;
+
+ InvD[num].MaxInvObj = MaxContents;
+
+ InvD[num].hInvTitle = text;
+
+ if (MaxWidth != MinWidth && MaxHeight != MinHeight)
+ InvD[num].resizable = true;
+
+ InvD[num].moveable = moveable;
+
+ InvD[num].bMax = false;
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_convw(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 20, 8, true);
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv1(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 100, 100, true);
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv2(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 100, 100, true);
+}
+
+int InvGetLimit(int invno) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+
+ return InvD[invno].MaxInvObj;
+}
+
+void InvSetLimit(int invno, int MaxContents) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+ assert(MaxContents >= InvD[invno].NoofItems); // can't reduce maximum contents below current contents
+
+ if (MaxContents > MAX_ININV)
+ MaxContents = MAX_ININV; // Max contents
+
+ InvD[invno].MaxInvObj = MaxContents;
+}
+
+void InvSetSize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+
+ if (StartWidth > MaxWidth)
+ StartWidth = MaxWidth;
+ if (StartHeight > MaxHeight)
+ StartHeight = MaxHeight;
+
+ InvD[invno].MaxHicons = MaxWidth;
+ InvD[invno].MinHicons = MinWidth;
+ InvD[invno].MaxVicons = MaxHeight;
+ InvD[invno].MinVicons = MinHeight;
+
+ InvD[invno].NoofHicons = StartWidth;
+ InvD[invno].NoofVicons = StartHeight;
+
+ if (MaxWidth != MinWidth && MaxHeight != MinHeight)
+ InvD[invno].resizable = true;
+ else
+ InvD[invno].resizable = false;
+
+ InvD[invno].bMax = false;
+}
+
+/**************************************************************************/
+
+bool IsTopWindow(void) {
+ return (InventoryState == BOGUS_INV);
+}
+
+
+bool IsConfWindow(void) {
+ return (InventoryState == ACTIVE_INV && ino == INV_CONF);
+}
+
+
+bool IsConvWindow(void) {
+ return (InventoryState == ACTIVE_INV && ino == INV_CONV);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/inventory.h b/engines/tinsel/inventory.h
new file mode 100644
index 0000000000..6b0c10e3de
--- /dev/null
+++ b/engines/tinsel/inventory.h
@@ -0,0 +1,143 @@
+
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Inventory related functions
+ */
+
+#ifndef TINSEL_INVENTORY_H // prevent multiple includes
+#define TINSEL_INVENTORY_H
+
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // for KEYEVENT, BUTEVENT
+
+namespace Tinsel {
+
+class Serializer;
+
+enum {
+ INV_OPEN = -1,
+ INV_CONV = 0,
+ INV_1 = 1,
+ INV_2 = 2,
+ INV_CONF = 3,
+
+ NUM_INV = 4
+};
+
+/** structure of each inventory object */
+struct INV_OBJECT {
+ int32 id; // inventory objects id
+ SCNHANDLE hFilm; // inventory objects animation film
+ SCNHANDLE hScript; // inventory objects event handling script
+ int32 attribute; // inventory object's attribute
+};
+typedef INV_OBJECT *PINV_OBJECT;
+
+void PopUpInventory(int invno);
+
+enum CONFTYPE {
+ SAVE, LOAD, QUIT, OPTION, RESTART, SOUND, CONTROLS, SUBT, TOPWIN
+};
+
+void PopUpConf(CONFTYPE type);
+
+
+void Xmovement(int x);
+void Ymovement(int y);
+
+void ButtonToInventory(BUTEVENT be);
+
+void KeyToInventory(KEYEVENT ke);
+
+
+int WhichItemHeld(void);
+
+void HoldItem(int item);
+void DropItem(int item);
+void AddToInventory(int invno, int icon, bool hold);
+bool RemFromInventory(int invno, int icon);
+
+
+void RegisterIcons(void *cptr, int num);
+
+void idec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+void idec_inv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+void idec_inv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+
+bool InventoryActive(void);
+
+void AddIconToPermanentDefaultList(int icon);
+
+void convPos(int bpos);
+void ConvPoly(HPOLYGON hp);
+int convIcon(void);
+void CloseDownConv(void);
+void convHide(bool hide);
+bool convHid(void);
+
+enum {
+ INV_NOICON = -1,
+ INV_CLOSEICON = -2,
+ INV_OPENICON = -3,
+ INV_HELDNOTIN = -4
+};
+
+void ConvAction(int index);
+
+void InventoryIconCursor(void);
+
+void setInvWinParts(SCNHANDLE hf);
+void setFlagFilms(SCNHANDLE hf);
+void setConfigStrings(SCNHANDLE *tp);
+
+int InvItem(int *x, int *y, bool update);
+int InvItemId(int x, int y);
+
+int InventoryPos(int num);
+
+bool IsInInventory(int object, int invnum);
+
+void KillInventory(void);
+
+void invObjectFilm(int object, SCNHANDLE hFilm);
+
+void syncInvInfo(Serializer &s);
+
+int InvGetLimit(int invno);
+void InvSetLimit(int invno, int n);
+void InvSetSize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+
+int WhichInventoryOpen(void);
+
+bool IsTopWindow(void);
+bool IsConfWindow(void);
+bool IsConvWindow(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_INVENTRY_H */
diff --git a/engines/tinsel/mareels.cpp b/engines/tinsel/mareels.cpp
new file mode 100644
index 0000000000..4c64eaf091
--- /dev/null
+++ b/engines/tinsel/mareels.cpp
@@ -0,0 +1,132 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Functions to set up moving actors' reels.
+ */
+
+#include "tinsel/pcode.h" // For D_UP, D_DOWN
+#include "tinsel/rince.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+enum {
+ NUM_INTERVALS = NUM_MAINSCALES - 1,
+
+ // 2 for up and down, 3 allow enough entries for 3 fully subscribed moving actors' worth
+ MAX_SCRENTRIES = NUM_INTERVALS*2*3
+};
+
+struct SCIdataStruct {
+ int actor;
+ int scale;
+ int direction;
+ SCNHANDLE reels[4];
+};
+
+static SCIdataStruct SCIdata[MAX_SCRENTRIES];
+
+static int scrEntries = 0;
+
+/**
+ * Return handle to actor's talk reel at present scale and direction.
+ */
+SCNHANDLE GetMactorTalkReel(PMACTOR pActor, TFTYPE dirn) {
+ assert(1 <= pActor->scale && pActor->scale <= TOTAL_SCALES);
+ switch (dirn) {
+ case TF_NONE:
+ return pActor->TalkReels[pActor->scale-1][pActor->dirn];
+
+ case TF_UP:
+ return pActor->TalkReels[pActor->scale-1][AWAY];
+
+ case TF_DOWN:
+ return pActor->TalkReels[pActor->scale-1][FORWARD];
+
+ case TF_LEFT:
+ return pActor->TalkReels[pActor->scale-1][LEFTREEL];
+
+ case TF_RIGHT:
+ return pActor->TalkReels[pActor->scale-1][RIGHTREEL];
+
+ default:
+ error("GetMactorTalkReel() - illegal direction!");
+ }
+}
+
+/**
+ * scalingreels
+ */
+void setscalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {
+ assert(scale >= 1 && scale <= NUM_MAINSCALES); // invalid scale
+ assert(!(scale == 1 && direction == D_UP) &&
+ !(scale == NUM_MAINSCALES && direction == D_DOWN)); // illegal direction from scale
+
+ assert(scrEntries < MAX_SCRENTRIES); // Scaling reels limit reached!
+
+ SCIdata[scrEntries].actor = actor;
+ SCIdata[scrEntries].scale = scale;
+ SCIdata[scrEntries].direction = direction;
+ SCIdata[scrEntries].reels[LEFTREEL] = left;
+ SCIdata[scrEntries].reels[RIGHTREEL] = right;
+ SCIdata[scrEntries].reels[FORWARD] = forward;
+ SCIdata[scrEntries].reels[AWAY] = away;
+ scrEntries++;
+}
+
+/**
+ * ScalingReel
+ */
+SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel) {
+ int d; // Direction
+
+ // The smaller the number, the bigger the scale
+ if (scale1 < scale2)
+ d = D_DOWN;
+ else
+ d = D_UP;
+
+ for (int i = 0; i < scrEntries; i++) {
+ if (SCIdata[i].actor == ano && SCIdata[i].scale == scale1 && SCIdata[i].direction == d) {
+ if (SCIdata[i].reels[reel] == TF_NONE)
+ return 0;
+ else
+ return SCIdata[i].reels[reel];
+ }
+ }
+ return 0;
+}
+
+/**
+ * RebootScalingReels
+ */
+void RebootScalingReels(void) {
+ scrEntries = 0;
+ memset(SCIdata, 0, sizeof(SCIdata));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk
new file mode 100644
index 0000000000..b00afcddbc
--- /dev/null
+++ b/engines/tinsel/module.mk
@@ -0,0 +1,52 @@
+MODULE := engines/tinsel
+
+MODULE_OBJS = \
+ actors.o \
+ anim.o \
+ background.o \
+ bg.o \
+ cliprect.o \
+ config.o \
+ cursor.o \
+ debugger.o \
+ detection.o \
+ effect.o \
+ events.o \
+ faders.o \
+ font.o \
+ graphics.o \
+ handle.o \
+ heapmem.o \
+ inventory.o \
+ mareels.o \
+ move.o \
+ multiobj.o \
+ music.o \
+ object.o \
+ palette.o \
+ pcode.o \
+ pdisplay.o \
+ play.o \
+ polygons.o \
+ rince.o \
+ saveload.o \
+ savescn.o \
+ scene.o \
+ sched.o \
+ scn.o \
+ scroll.o \
+ sound.o \
+ strres.o \
+ text.o \
+ timers.o \
+ tinlib.o \
+ tinsel.o \
+ token.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_TINSEL), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp
new file mode 100644
index 0000000000..803bc5fd7b
--- /dev/null
+++ b/engines/tinsel/move.cpp
@@ -0,0 +1,1618 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles walking and use of the path system.
+ *
+ * Contains the dodgiest code in the whole system.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/scroll.h"
+#include "tinsel/tinlib.h" // For stand()
+
+namespace Tinsel {
+
+//----------------- DEVELOPMENT OPTIONS --------------------
+
+#define SLOW_RINCE_DOWN 0
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+// in POLYGONS.C
+// Deliberatley defined here, and not in polygons.h
+HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta);
+
+//----------------- LOCAL DEFINES --------------------
+
+#define XMDIST 4
+#define XHMDIST 2
+#define YMDIST 2
+#define YHMDIST 2
+
+#define XTHERE 1
+#define XRESTRICT 2
+#define YTHERE 4
+#define YRESTRICT 8
+#define STUCK 16
+
+#define LEAVING_PATH 0x100
+#define ENTERING_BLOCK 0x200
+#define ENTERING_MBLOCK 0x400
+
+#define ALL_SORTED 1
+#define NOT_SORTED 0
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+#if SLOW_RINCE_DOWN
+static int Interlude = 0; // For slowing down walking, for testing
+static int BogusVar = 0; // For slowing down walking, for testing
+#endif
+
+static int32 DefaultRefer = 0;
+static int hSlowVar = 0; // used by MoveActor()
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY,
+ int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p,
+ bool bOver, bool bBodge,
+ PMACTOR pActor, PMACTOR *collisionActor = 0);
+
+
+#if SLOW_RINCE_DOWN
+/**
+ * AddInterlude
+ */
+
+void AddInterlude(int n) {
+ Interlude += n;
+ if (Interlude < 0)
+ Interlude = 0;
+}
+#endif
+
+/**
+ * Given (x, y) of a click within a path polygon, checks that the
+ * co-ordinates are not within a blocking polygon. If it is not, the
+ * destination is the click point, otherwise tries to find a legal point
+ * below or above the click point.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int Loffset, Toffset;
+ int i;
+
+ /*--------------------------------------
+ Clicked within a path,
+ go to where requested unless blocked.
+ --------------------------------------*/
+ if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) {
+ // Not in a blocking polygon - go to where requested.
+ *ptgtX = clickX;
+ *ptgtY = clickY;
+ } else {
+ /*------------------------------------------------------
+ In a Blocking polygon - try searching down and up.
+ If still nowhere (for now) give up!
+ ------------------------------------------------------*/
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ for (i = clickY+1; i < SCREEN_HEIGHT + Toffset; i++) {
+ // Don't leave the path system
+ if (InPolygon(clickX, i, PATH) == NOPOLY) {
+ i = SCREEN_HEIGHT;
+ break;
+ }
+ if (InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ }
+ if (i == SCREEN_HEIGHT) {
+ for (i = clickY-1; i >= Toffset; i--) {
+ // Don't leave the path system
+ if (InPolygon(clickX, i, PATH) == NOPOLY) {
+ i = -1;
+ break;
+ }
+ if (InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ }
+ }
+ if (i < 0) {
+ return ALL_SORTED;
+ }
+ }
+ return NOT_SORTED;
+}
+
+/**
+ * Given (x, y) of a click within a referral polygon, works out the
+ * destination according to the referral type.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int i;
+ int end; // Extreme of the scene
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ *ptgtX = *ptgtY = -1;
+
+ switch (PolySubtype(hRefpoly)) {
+ case REF_POINT: // Go to specified node
+ getPolyNode(hRefpoly, ptgtX, ptgtY);
+ assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point
+ break;
+
+ case REF_DOWN: // Search downwards
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY
+ && InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ break;
+
+ case REF_UP: // Search upwards
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY
+ && InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ break;
+
+ case REF_RIGHT: // Search to the right
+ end = BackgroundWidth();
+ for (i = clickX+1; i < end; i++)
+ if (InPolygon(i, clickY, PATH) != NOPOLY
+ && InPolygon(i, clickY, BLOCKING) == NOPOLY) {
+ *ptgtX = i;
+ *ptgtY = clickY;
+ break;
+ }
+ break;
+
+ case REF_LEFT: // Search to the left
+ for (i = clickX-1; i >= 0; i--)
+ if (InPolygon(i, clickY, PATH) != NOPOLY
+ && InPolygon(i, clickY, BLOCKING) == NOPOLY) {
+ *ptgtX = i;
+ *ptgtY = clickY;
+ break;
+ }
+ break;
+ }
+ if (*ptgtX != -1 && *ptgtY != -1) {
+ return NOT_SORTED;
+ } else
+ return ALL_SORTED;
+}
+
+/**
+ * Given (x, y) of a click, works out the destination according to the
+ * default referral type.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int i;
+ int end; // Extreme of the scene
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ switch (DefaultRefer) {
+ case REF_DEFAULT:
+ // Try searching down and up (onscreen).
+ for (i = clickY+1; i < SCREEN_HEIGHT+Toffset; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ for (i = clickY-1; i >= Toffset; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ // Try searching down and up (offscreen).
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_UP:
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_DOWN:
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_LEFT:
+ for (i = clickX-1; i >= 0; i--)
+ if (InPolygon(i, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(i, clickY, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_RIGHT:
+ end = BackgroundWidth();
+ for (i = clickX + 1; i < end; i++)
+ if (InPolygon(i, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(i, clickY, ptgtX, ptgtY);
+ }
+ break;
+ }
+
+ // Going nowhere!
+ return ALL_SORTED;
+}
+
+/**
+ * Given (x, y) of the click, ascertains whether the click is within a
+ * path, within a referral poly, or niether. The appropriate function
+ * then gets called to give us a revised destination.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ HPOLYGON hPoly;
+
+ /*--------------------------------------
+ Clicked within a path?
+ if not, within a referral poly?
+ if not, try and sort something out.
+ ---------------------------------------*/
+ if (InPolygon(clickX, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, clickY, ptgtX, ptgtY);
+ } else if ((hPoly = InPolygon(clickX, clickY, REFER)) != NOPOLY) {
+ return ClickedOnRefer(hPoly, clickX, clickY, ptgtX, ptgtY);
+ } else {
+ return ClickedOnNothing(clickX, clickY, ptgtX, ptgtY);
+ }
+}
+
+/**
+ * Work out which reel to adopt for a section of movement.
+ */
+static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL lastreel, HPOLYGON hPath) {
+ int xchange = 0, ychange = 0;
+ enum {X_NONE, X_LEFT, X_RIGHT, X_NO} xdir;
+ enum {Y_NONE, Y_UP, Y_DOWN, Y_NO} ydir;
+
+ DIRREEL reel = lastreel; // Leave alone if can't decide
+
+ /*
+ * Determine size and direction of X movement.
+ * i.e. left, right, none or not allowed.
+ */
+ if (getPolyReelType(hPath) == REEL_VERT)
+ xdir = X_NO;
+ else if (tox == -1)
+ xdir = X_NONE;
+ else {
+ xchange = tox - fromx;
+ if (xchange > 0)
+ xdir = X_RIGHT;
+ else if (xchange < 0) {
+ xchange = -xchange;
+ xdir = X_LEFT;
+ } else
+ xdir = X_NONE;
+ }
+
+ /*
+ * Determine size and direction of Y movement.
+ * i.e. up, down, none or not allowed.
+ */
+ if (getPolyReelType(hPath) == REEL_HORIZ)
+ ydir = Y_NO;
+ else if (toy == -1)
+ ydir = Y_NONE;
+ else {
+ ychange = toy - fromy;
+ if (ychange > 0)
+ ydir = Y_DOWN;
+ else if (ychange < 0) {
+ ychange = -ychange;
+ ydir = Y_UP;
+ } else
+ ydir = Y_NONE;
+ }
+
+ /*
+ * Some adjustment to allow for different x and y pixell sizes.
+ */
+ ychange += ychange; // Double y distance to cover
+
+ /*
+ * Determine which reel to use.
+ */
+ if (xdir == X_NO) {
+ // Forced to be FORWARD or AWAY
+ switch (ydir) {
+ case Y_DOWN:
+ reel = FORWARD;
+ break;
+ case Y_UP:
+ reel = AWAY;
+ break;
+ default:
+ if (reel != AWAY) // No gratuitous turn
+ reel = FORWARD;
+ break;
+ }
+ } else if (ydir == Y_NO) {
+ // Forced to be LEFTREEL or RIGHTREEL
+ switch (xdir) {
+ case X_LEFT:
+ reel = LEFTREEL;
+ break;
+ case X_RIGHT:
+ reel = RIGHTREEL;
+ break;
+ default:
+ if (reel != LEFTREEL) // No gratuitous turn
+ reel = RIGHTREEL;
+ break;
+ }
+ } else if (xdir != X_NONE || ydir != Y_NONE) {
+ if (xdir == X_NONE)
+ reel = (ydir == Y_DOWN) ? FORWARD : AWAY;
+ else if (ydir == Y_NONE)
+ reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL;
+ else {
+ bool DontBother = false;
+
+ if (xchange <= 4 && ychange <= 4) {
+ switch (reel) {
+ case LEFTREEL:
+ if (xdir == X_LEFT)
+ DontBother = true;
+ break;
+ case RIGHTREEL:
+ if (xdir == X_RIGHT)
+ DontBother = true;
+ break;
+ case FORWARD:
+ if (ydir == Y_DOWN)
+ DontBother = true;
+ break;
+ case AWAY:
+ if (ydir == Y_UP)
+ DontBother = true;
+ break;
+ }
+ }
+ if (!DontBother) {
+ if (xchange > ychange)
+ reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL;
+ else
+ reel = (ydir == Y_DOWN) ? FORWARD : AWAY;
+ }
+ }
+ }
+ return reel;
+}
+
+/**
+ * Haven't moved, look towards the cursor.
+ */
+static void GotThereWithoutMoving(PMACTOR pActor) {
+ int curX, curY;
+ DIRREEL reel;
+
+ if (!pActor->TagReelRunning) {
+ GetCursorXYNoWait(&curX, &curY, true);
+
+ reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath);
+
+ if (reel != pActor->dirn)
+ SetMActorWalkReel(pActor, reel, pActor->scale, false);
+ }
+}
+
+/**
+ * Arrived at final destination.
+ */
+static void GotThere(PMACTOR pActor) {
+ pActor->targetX = pActor->targetY = -1; // 4/1/95
+ pActor->ItargetX = pActor->ItargetY = -1;
+ pActor->UtargetX = pActor->UtargetY = -1;
+
+ // Perhaps we have'nt moved.
+ if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy)
+ GotThereWithoutMoving(pActor);
+
+ ReTagActor(pActor->actorID); // Tag allowed while stationary
+
+ SetMActorStanding(pActor);
+
+ pActor->bMoving = false;
+}
+
+enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY };
+
+/**
+ * Can we get straight there?
+ */
+static cgt CanGetThere(PMACTOR pActor, int tx, int ty) {
+ int s1, s2; // s2 not used here!
+ HPOLYGON hS2p; // nor is s2p!
+ int nextx, nexty;
+
+ int targetX = tx;
+ int targetY = ty; // Ultimate destination
+ int x = pActor->objx;
+ int y = pActor->objy; // Present position
+
+ while (targetX != -1 || targetY != -1) {
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, false, pActor);
+
+ if (s1 == (XTHERE | YTHERE)) {
+ return GT_OK; // Can get there directly.
+ } else if (s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) {
+ return GT_MAY; // Can't get there directly.
+ } else if (s1 & STUCK) {
+ if (s2 == LEAVING_PATH)
+ return GT_NOTL; // Can't get there.
+ else
+ return GT_NOTB; // Can't get there.
+ } else if (x == nextx && y == nexty) {
+ return GT_NOT2; // Can't get there.
+ }
+ x = nextx;
+ y = nexty;
+ }
+ return GT_MAY;
+}
+
+
+/**
+ * Set final destination.
+ */
+static void SetMoverUltDest(PMACTOR pActor, int x, int y) {
+ pActor->UtargetX = x;
+ pActor->UtargetY = y;
+ pActor->hUpath = InPolygon(x, y, PATH);
+
+ assert(pActor->hUpath != NOPOLY || pActor->bIgPath); // Invalid ultimate destination
+}
+
+/**
+ * Set intermediate destination.
+ *
+ * If in final destination path, go straight to target.
+ * If in a neighbouring path to the final destination, if the target path
+ * is a follow nodes path, head for the end node, otherwise head straight
+ * for the target.
+ * Otherwise, head towards the pseudo-centre or end node of the first
+ * en-route path.
+ */
+static void SetMoverIntDest(PMACTOR pActor, int x, int y) {
+ HPOLYGON hIpath, hTpath;
+ int node;
+
+ hTpath = InPolygon(x, y, PATH); // Target path
+#ifdef DEBUG
+ if (!pActor->bIgPath)
+ assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path
+#endif
+
+ if (pActor->hCpath == hTpath || pActor->bIgPath
+ || IsInPolygon(pActor->objx, pActor->objy, hTpath)) {
+ // In destination path - head straight for the target.
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ pActor->hIpath = hTpath;
+ } else if (IsAdjacentPath(pActor->hCpath, hTpath)) {
+ // In path adjacent to target
+ if (PolySubtype(hTpath) != NODE) {
+ // Target path is normal - head for target.
+ // Added 26/01/95, innroom
+ if (CanGetThere(pActor, x, y) == GT_NOTL) {
+ NearestCorner(&x, &y, pActor->hCpath, hTpath);
+ }
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ } else {
+ // Target path is node - head for end node.
+ node = NearestEndNode(hTpath, pActor->objx, pActor->objy);
+ getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY);
+
+ }
+ pActor->hIpath = hTpath;
+ } else {
+ assert(hTpath != NOPOLY); // Error 701
+ hIpath = getPathOnTheWay(pActor->hCpath, hTpath);
+
+ if (hIpath != NOPOLY) {
+ /* Head for an en-route path */
+ if (PolySubtype(hIpath) != NODE) {
+ /* En-route path is normal - head for pseudo centre. */
+ if (CanGetThere(pActor, x, y) == GT_OK) {
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ } else {
+ pActor->ItargetX = PolyCentreX(hIpath);
+ pActor->ItargetY = PolyCentreY(hIpath);
+ }
+ } else {
+ /* En-route path is node - head for end node. */
+ node = NearestEndNode(hIpath, pActor->objx, pActor->objy);
+ getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY);
+ }
+ pActor->hIpath = hIpath;
+ }
+ }
+
+ pActor->InDifficulty = NO_PROB;
+}
+
+/**
+ * Set short-term destination and adopt the appropriate reel.
+ */
+static void SetMoverDest(PMACTOR pActor, int x, int y) {
+ int scale;
+ DIRREEL reel;
+
+ // Set the co-ordinates requested.
+ pActor->targetX = x;
+ pActor->targetY = y;
+ pActor->InDifficulty = NO_PROB;
+
+ reel = GetDirectionReel(pActor->objx, pActor->objy, x, y, pActor->dirn, pActor->hCpath);
+ scale = GetScale(pActor->hCpath, pActor->objy);
+ if (scale != pActor->scale || reel != pActor->dirn) {
+ SetMActorWalkReel(pActor, reel, scale, false);
+ }
+}
+
+/**
+ * SetNextDest
+ */
+static void SetNextDest(PMACTOR pActor) {
+ int targetX, targetY; // Ultimate destination
+ int x, y; // Present position
+ int nextx, nexty;
+ int s1, lstatus = 0;
+ int s2;
+ HPOLYGON hS2p;
+ int i;
+ HPOLYGON hNpoly;
+ HPOLYGON hPath;
+ int znode;
+ int nx, ny;
+ int sx, sy;
+ HPOLYGON hEb;
+
+ int ss1, ss2;
+ HPOLYGON shS2p;
+ PMACTOR collisionActor;
+#if 1
+ int sTargetX, sTargetY;
+#endif
+
+ /*
+ * Desired destination (Itarget) is already set
+ */
+ x = pActor->objx; // Current position
+ y = pActor->objy;
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+
+ /*
+ * If we're where we're headed, end it all (the moving).
+ */
+// if (x == targetX && y == targetY)
+ if (ABS(x - targetX) < XMDIST && ABS(y - targetY) < YMDIST) {
+ if (targetX == pActor->UtargetX && targetY == pActor->UtargetY) {
+ // Desired position
+ GotThere(pActor);
+ return;
+ } else {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ }
+ }
+
+ if (pActor->bNoPath || pActor->bIgPath) {
+ /* Can get there directly. */
+ SetMoverDest(pActor, targetX, targetY);
+ pActor->over = false;
+ return;
+ }
+
+ /*----------------------------------------------------------------------
+ | Some work to do here if we're in a follow nodes polygon - basically
+ | head for the next node.
+ ----------------------------------------------------------------------*/
+ hNpoly = pActor->hFnpath; // The node path we're in (if any)
+ switch (pActor->npstatus) {
+ case NOT_IN:
+ break;
+
+ case ENTERING:
+ znode = NearestEndNode(hNpoly, x, y);
+ /* Hang on, we're probably here already! */
+ if (znode) {
+ pActor->npstatus = GOING_DOWN;
+ pActor->line = znode-1;
+ getNpathNode(hNpoly, znode - 1, &nx, &ny);
+ } else {
+ pActor->npstatus = GOING_UP;
+ pActor->line = znode;
+ getNpathNode(hNpoly, 1, &nx, &ny);
+ }
+ SetMoverDest(pActor, nx, ny);
+
+ // Test for pseudo-one-node npaths
+ if (numNodes(hNpoly) == 2 &&
+ ABS(pActor->objx - pActor->targetX) < XMDIST &&
+ ABS(pActor->objy - pActor->targetY) < YMDIST) {
+ // That's enough, we're leaving
+ pActor->npstatus = LEAVING;
+ } else {
+ // Normal situation
+ pActor->over = true;
+ return;
+ }
+ // Fall through for LEAVING
+
+ case LEAVING:
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5002
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+
+ case GOING_UP:
+ i = pActor->line; // The line we're on
+
+ // Is this the final target line?
+ if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) {
+ // The final leg of the journey
+ pActor->line = i+1;
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->over = false;
+ return;
+ } else {
+ // Go to the next node unless we're at the last one
+ i++; // The node we're at
+ if (++i < numNodes(hNpoly)) {
+ getNpathNode(hNpoly, i, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->line = i-1;
+ if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST &&
+ ABS(pActor->UtargetY - pActor->targetY) < YMDIST)
+ pActor->over = false;
+ else
+ pActor->over = true;
+ return;
+ } else {
+ // Last node - we're off
+ pActor->npstatus = LEAVING;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5003
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+ }
+ }
+
+ case GOING_DOWN:
+ i = pActor->line; // The line we're on and the node we're at
+
+ // Is this the final target line?
+ if (i - 1 == pActor->Tline && hNpoly == pActor->hUpath) {
+ // The final leg of the journey
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->line = i-1;
+ pActor->over = false;
+ return;
+ } else {
+ // Go to the next node unless we're at the last one
+ if (--i >= 0) {
+ getNpathNode(hNpoly, i, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->line--; /* The next node to head for */
+ if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST &&
+ ABS(pActor->UtargetY - pActor->targetY) < YMDIST)
+ pActor->over = false;
+ else
+ pActor->over = true;
+ return;
+ } else {
+ // Last node - we're off
+ pActor->npstatus = LEAVING;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5004
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+ }
+ }
+ }
+
+
+
+
+ /*------------------------------------------------------
+ | See if it can get there directly. There may be an
+ | intermediate destination to head for.
+ ------------------------------------------------------*/
+
+ while (targetX != -1 || targetY != -1) {
+#if 1
+ // 'push' the target
+ sTargetX = targetX;
+ sTargetY = targetY;
+#endif
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor);
+
+ if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) {
+ ss1 = s1;
+ ss2 = s2;
+ shS2p = hS2p;
+#if 1
+ // 'pop' the target
+ targetX = sTargetX;
+ targetY = sTargetY;
+#endif
+ // Note: this aint right - targetX/Y (may) have been
+ // nobbled by that last call to NewCoOrdinates()
+ // Re-instating them (can) leads to oscillation
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, true, pActor, &collisionActor);
+
+ if (x == nextx && y == nexty) {
+ s1 = ss1;
+ s2 = ss2;
+ hS2p = shS2p;
+ }
+ }
+
+ if (s1 == (XTHERE | YTHERE)) {
+ /* Can get there directly. */
+ SetMoverDest(pActor, nextx, nexty);
+ pActor->over = false;
+ break;
+ } else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT)
+ || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) {
+ /*-------------------------------------------------
+ Can't go any further in this direction. |
+ If it's because of a blocking polygon, try to do |
+ something about it. |
+ -------------------------------------------------*/
+ if (s2 & ENTERING_BLOCK) {
+ x = pActor->objx; // Current position
+ y = pActor->objy;
+ // Go to the nearest corner of the blocking polygon concerned
+ BlockingCorner(hS2p, &x, &y, pActor->ItargetX, pActor->ItargetY);
+ SetMoverDest(pActor, x, y);
+ pActor->over = false;
+ } else if (s2 & ENTERING_MBLOCK) {
+ if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) {
+ // The best we're going to achieve
+ SetMoverUltDest(pActor, x, y);
+ SetMoverDest(pActor, x, y);
+ } else {
+ sx = pActor->objx;
+ sy = pActor->objy;
+// pActor->objx = x;
+// pActor->objy = y;
+
+ hEb = InitExtraBlock(pActor, collisionActor);
+ x = pActor->objx;
+ y = pActor->objy;
+ BlockingCorner(hEb, &x, &y, pActor->ItargetX, pActor->ItargetY);
+
+ pActor->objx = sx;
+ pActor->objy = sy;
+ SetMoverDest(pActor, x, y);
+ pActor->over = false;
+ }
+ } else {
+ /*----------------------------------------
+ Currently, this is as far as we can go. |
+ Definitely room for improvement here! |
+ ----------------------------------------*/
+ hPath = InPolygon(pActor->ItargetX, pActor->ItargetY, PATH);
+ if (hPath != pActor->hIpath) {
+ if (IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hIpath))
+ hPath = pActor->hIpath;
+ }
+ assert(hPath == pActor->hIpath);
+
+ if (pActor->InDifficulty == NO_PROB) {
+ x = PolyCentreX(hPath);
+ y = PolyCentreY(hPath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_CENTRE;
+ pActor->over = false;
+ } else if (pActor->InDifficulty == TRY_CENTRE) {
+ NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_CORNER;
+ pActor->over = false;
+ } else if (pActor->InDifficulty == TRY_CORNER) {
+ NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_NEXTCORNER;
+ pActor->over = false;
+ }
+ }
+ break;
+ }
+ else if (((lstatus & YRESTRICT) && !(s1 & YRESTRICT))
+ || ((lstatus & XRESTRICT) && !(s1 & XRESTRICT))) {
+ /*-----------------------------------------------
+ A restriction in a direction has been removed. |
+ Use this as an intermediate destination. |
+ -----------------------------------------------*/
+ SetMoverDest(pActor, nextx, nexty);
+ pActor->over = false;
+ break;
+ }
+
+ x = nextx;
+ y = nexty;
+
+ /*-------------------------
+ Change of path polygon? |
+ -------------------------*/
+ hPath = InPolygon(x, y, PATH);
+ if (pActor->hCpath != hPath &&
+ !IsInPolygon(x, y, pActor->hCpath) &&
+ !IsAdjacentPath(pActor->hCpath, pActor->hIpath)) {
+ /*----------------------------------------------------------
+ If just entering a follow nodes polygon, go to first node.|
+ Else if just going to pass through, go to pseudo-centre. |
+ ----------------------------------------------------------*/
+ if (PolySubtype(hPath) == NODE && pActor->hFnpath != hPath && pActor->npstatus != LEAVING) {
+ int node = NearestEndNode(hPath, x, y);
+ getNpathNode(hPath, node, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->over = true;
+ } else if (!IsInPolygon(pActor->ItargetX, pActor->ItargetY, hPath) &&
+ !IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hCpath)) {
+ SetMoverDest(pActor, PolyCentreX(hPath), PolyCentreY(hPath));
+ pActor->over = true;
+ } else {
+ SetMoverDest(pActor, pActor->ItargetX, pActor->ItargetY);
+ }
+ break;
+ }
+
+ lstatus = s1;
+ }
+}
+
+/**
+ * Work out where the next position should be.
+ * Check that it's in a path and not in a blocking polygon.
+ */
+static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY,
+ int *newx, int *newy, int *s1, int *s2,
+ HPOLYGON *hS2p, bool bOver, bool bBodge,
+ PMACTOR pActor, PMACTOR *collisionActor) {
+ HPOLYGON hPoly;
+ int sidem, depthm;
+ int sidesteps, depthsteps;
+ PMACTOR ma;
+
+ *s1 = *s2 = 0;
+
+ /*------------------------------------------------
+ Don't overrun if this is the final destination. |
+ ------------------------------------------------*/
+ if (*targetX == pActor->UtargetX && (*targetY == -1 || *targetY == pActor->UtargetY) ||
+ *targetY == pActor->UtargetY && (*targetX == -1 || *targetX == pActor->UtargetX))
+ bOver = false;
+
+ /*----------------------------------------------------
+ Decide how big a step to attempt in each direction. |
+ ----------------------------------------------------*/
+ sidesteps = *targetX == -1 ? 0 : *targetX - fromx;
+ sidesteps = ABS(sidesteps);
+
+ depthsteps = *targetY == -1 ? 0 : *targetY - fromy;
+ depthsteps = ABS(depthsteps);
+
+ if (sidesteps && depthsteps > sidesteps) {
+ depthm = YMDIST;
+ sidem = depthm * sidesteps/depthsteps;
+
+ if (!sidem)
+ sidem = 1;
+ } else if (depthsteps && sidesteps > depthsteps) {
+ sidem = XMDIST;
+ depthm = sidem * depthsteps/sidesteps;
+
+ if (!depthm) {
+ if (bBodge)
+ depthm = 1;
+ } else if (depthm > YMDIST)
+ depthm = YMDIST;
+ } else {
+ sidem = sidesteps ? XMDIST : 0;
+ depthm = depthsteps ? YMDIST : 0;
+ }
+
+ *newx = fromx;
+ *newy = fromy;
+
+ /*------------------------------------------------------------
+ If Left-Right movement is required - then make the move, |
+ but don't overshoot, and do notice when we're already there |
+ ------------------------------------------------------------*/
+ if (*targetX == -1)
+ *s1 |= XTHERE;
+ else {
+ if (*targetX > fromx) { /* To the right? */
+ *newx += sidem; // Move to the right...
+ if (*newx == *targetX)
+ *s1 |= XTHERE;
+ else if (*newx > *targetX) { // ...but don't overshoot
+ if (!bOver)
+ *newx = *targetX;
+ else
+ *targetX = *newx;
+ *s1 |= XTHERE;
+ }
+ } else if (*targetX < fromx) { /* To the left? */
+ *newx -= sidem; // Move to the left...
+ if (*newx == *targetX)
+ *s1 |= XTHERE;
+ else if (*newx < *targetX) { // ...but don't overshoot
+ if (!bOver)
+ *newx = *targetX;
+ else
+ *targetX = *newx;
+ *s1 |= XTHERE;
+ }
+ } else {
+ *targetX = -1; // We're already there!
+ *s1 |= XTHERE;
+ }
+ }
+
+ /*--------------------------------------------------------------
+ If Up-Down movement is required - then make the move,
+ but don't overshoot, and do notice when we're already there
+ --------------------------------------------------------------*/
+ if (*targetY == -1)
+ *s1 |= YTHERE;
+ else {
+ if (*targetY > fromy) { /* Downwards? */
+ *newy += depthm; // Move down...
+ if (*newy == *targetY) // ...but don't overshoot
+ *s1 |= YTHERE;
+ else if (*newy > *targetY) { // ...but don't overshoot
+ if (!bOver)
+ *newy = *targetY;
+ else
+ *targetY = *newy;
+ *s1 |= YTHERE;
+ }
+ } else if (*targetY < fromy) { /* Upwards? */
+ *newy -= depthm; // Move up...
+ if (*newy == *targetY) // ...but don't overshoot
+ *s1 |= YTHERE;
+ else if (*newy < *targetY) { // ...but don't overshoot
+ if (!bOver)
+ *newy = *targetY;
+ else
+ *targetY = *newy;
+ *s1 |= YTHERE;
+ }
+ } else {
+ *targetY = -1; // We're already there!
+ *s1 |= YTHERE;
+ }
+ }
+
+ /* Give over if this is it */
+ if (*s1 == (XTHERE | YTHERE))
+ return;
+
+ /*------------------------------------------------------
+ Have worked out where an optimum step would take us.
+ Must now check if it's in a legal spot.
+ ------------------------------------------------------*/
+
+ if (!pActor->bNoPath && !pActor->bIgPath) {
+ /*------------------------------
+ Must stay in a path polygon.
+ -------------------------------*/
+ hPoly = InPolygon(*newx, *newy, PATH);
+ if (hPoly == NOPOLY) {
+ *s2 = LEAVING_PATH; // Trying to leave the path polygons
+
+ if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCKING) == NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ return;
+ }
+ }
+
+ /*--------------------------------------
+ Must stay out of blocking polygons.
+ --------------------------------------*/
+ hPoly = InPolygon(*newx, *newy, BLOCKING);
+ if (hPoly != NOPOLY) {
+ *s2 = ENTERING_BLOCK; // Trying to enter a blocking poly
+ *hS2p = hPoly;
+
+ if (*newx != fromx && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ }
+ }
+ /*------------------------------------------------------
+ Must stay out of moving actors' blocking polygons.
+ ------------------------------------------------------*/
+ ma = InMActorBlock(pActor, *newx, *newy);
+ if (ma != NULL) {
+ // Ignore if already in it (it may have just appeared)
+ if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) {
+ *s2 = ENTERING_MBLOCK; // Trying to walk through an actor
+
+ *hS2p = -1;
+ if (collisionActor)
+ *collisionActor = ma;
+
+ if (*newx != fromx && InMActorBlock(pActor, *newx, fromy) == NULL
+ && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InMActorBlock(pActor, fromx, *newy) == NULL
+ && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * SetOffWithinNodePath
+ */
+static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath,
+ int targetX, int targetY) {
+ int endnode;
+ HPOLYGON hIpath;
+ int nx, ny;
+ int x, y;
+
+ if (StartPath == DestPath) {
+ if (pActor->line == pActor->Tline) {
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->over = false;
+ } else if (pActor->line < pActor->Tline) {
+ getNpathNode(StartPath, pActor->line+1, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_UP;
+ } else if (pActor->line > pActor->Tline) {
+ getNpathNode(StartPath, pActor->line, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_DOWN;
+ }
+ } else {
+ /*
+ * Leaving this path - work out
+ * which end of this path to head for.
+ */
+ assert(DestPath != NOPOLY); // Error 702
+ if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) {
+ // This should never happen!
+ // It's the old code that didn't always work.
+ endnode = NearestEndNode(StartPath, targetX, targetY);
+ } else {
+ if (PolySubtype(hIpath) != NODE) {
+ x = PolyCentreX(hIpath);
+ y = PolyCentreY(hIpath);
+ endnode = NearestEndNode(StartPath, x, y);
+ } else {
+ endnode = NearEndNode(StartPath, hIpath);
+ }
+ }
+
+#if 1
+ if ((pActor->npstatus == LEAVING) &&
+ endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) {
+ // Leave it be
+ } else
+#endif
+ {
+ if (endnode) {
+ getNpathNode(StartPath, pActor->line+1, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_UP;
+ } else {
+ getNpathNode(StartPath, pActor->line, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_DOWN;
+ }
+ }
+ }
+}
+
+/**
+ * Restore a movement, called from restoreMovement() in ACTORS.CPP
+ */
+void SSetActorDest(PMACTOR pActor) {
+ if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
+ stand(pActor->actorID, pActor->objx, pActor->objy, 0);
+
+ if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
+ SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY,
+ pActor->bIgPath, 0);
+ }
+ } else {
+ stand(pActor->actorID, pActor->objx, pActor->objy, 0);
+ }
+}
+
+/**
+ * Initiate a movement, called from WalkTo_Event()
+ */
+void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE film) {
+ HPOLYGON StartPath, DestPath = 0;
+ int targetX, targetY;
+
+ if (pActor->actorID == LeadId()) // Now only for lead actor
+ UnTagActor(pActor->actorID); // Tag not allowed while moving
+ pActor->ticket++;
+ pActor->stop = false;
+ pActor->over = false;
+ pActor->fromx = pActor->objx;
+ pActor->fromy = pActor->objy;
+ pActor->bMoving = true;
+ pActor->bIgPath = igPath;
+
+ // Use the supplied reel or restore the normal actor.
+ if (film != 0)
+ AlterMActor(pActor, film, AR_WALKREEL);
+ else
+ AlterMActor(pActor, 0, AR_NORMAL);
+
+ if (igPath) {
+ targetX = clickX;
+ targetY = clickY;
+ } else {
+ if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) {
+ GotThere(pActor);
+ return;
+ }
+ assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination!
+ assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination!
+ }
+
+
+ /***** Now have a destination to aim for. *****/
+
+ /*----------------------------------
+ | Don't move if it's not worth it.
+ ----------------------------------*/
+ if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) {
+ GotThere(pActor);
+ return;
+ }
+
+ /*------------------------------------------------------
+ | If the destiation is within a follow nodes polygon,
+ | set destination as the nearest node.
+ ------------------------------------------------------*/
+ if (!igPath) {
+ DestPath = InPolygon(targetX, targetY, PATH);
+ if (PolySubtype(DestPath) == NODE) {
+ // Find the nearest point on a line, or nearest node
+ FindBestPoint(DestPath, &targetX, &targetY, &pActor->Tline);
+ }
+ }
+
+ assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005
+ SetMoverUltDest(pActor, targetX, targetY);
+ SetMoverIntDest(pActor, targetX, targetY);
+
+ /*-------------------------------------------------------------------
+ | If in a follow nodes path, need to set off in the right direction! |
+ -------------------------------------------------------------------*/
+ if ((StartPath = pActor->hFnpath) != NOPOLY && !igPath) {
+ SetOffWithinNodePath(pActor, StartPath, DestPath, targetX, targetY);
+ } else {
+ // Set off!
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Change scale if appropriate.
+ */
+static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) {
+ int scale;
+
+ scale = GetScale(hPath, ypos);
+ if (scale != pActor->scale) {
+ SetMActorWalkReel(pActor, pActor->dirn, scale, false);
+ }
+}
+
+/**
+ * Not going anywhere - Kick off again if not at final destination.
+ */
+static void NotMoving(PMACTOR pActor, int x, int y) {
+ pActor->targetX = pActor->targetY = -1;
+
+// if (x == pActor->UtargetX && y == pActor->UtargetY)
+ if (ABS(x - pActor->UtargetX) < XMDIST && ABS(y - pActor->UtargetY) < YMDIST) {
+ GotThere(pActor);
+ return;
+ }
+
+ if (pActor->ItargetX != -1 || pActor->ItargetY != -1) {
+ SetNextDest(pActor);
+ } else if (pActor->UtargetX != -1 || pActor->UtargetY != -1) {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5006
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Does the necessary business when entering a different path polygon.
+ */
+static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) {
+ int firstnode; // First node to go to
+ int lastnode; // Last node to go to
+ HPOLYGON hIpath;
+ int nx, ny;
+ int nxl, nyl;
+
+ pActor->hCpath = hPath; // current path
+
+ if (hPath == NOPOLY) {
+ // Not proved this ever happens, but just in case
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ return;
+ }
+
+ // Is new path a node path?
+ if (PolySubtype(hPath) == NODE) {
+ // Node path - usually go to nearest end node
+ firstnode = NearestEndNode(hPath, x, y);
+ lastnode = -1;
+
+ // If this is not the destination path,
+ // find which end nodfe we wish to leave via
+ if (hPath != pActor->hUpath) {
+ if (pActor->bIgPath) {
+ lastnode = NearestEndNode(hPath, pActor->UtargetX, pActor->UtargetY);
+ } else {
+ assert(pActor->hUpath != NOPOLY); // Error 703
+ hIpath = getPathOnTheWay(hPath, pActor->hUpath);
+ assert(hIpath != NOPOLY); // No path on the way
+
+ if (PolySubtype(hIpath) != NODE) {
+ lastnode = NearestEndNode(hPath, PolyCentreX(hIpath), PolyCentreY(hIpath));
+ } else {
+ lastnode = NearEndNode(hPath, hIpath);
+ }
+ }
+ }
+ // Test for pseudo-one-node npaths
+ if (lastnode != -1 && numNodes(hPath) == 2) {
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ getNpathNode(hPath, lastnode, &nxl, &nyl);
+ if (nxl == nx && nyl == ny)
+ firstnode = lastnode;
+ }
+
+ // If leaving by same node as entering, don't bother.
+ if (lastnode == firstnode) {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5007
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ } else {
+ // Head for first node
+ pActor->over = true;
+ pActor->npstatus = ENTERING;
+ pActor->hFnpath = hPath;
+ pActor->line = firstnode ? firstnode - 1 : firstnode;
+ if (pActor->line == pActor->Tline && hPath == pActor->hUpath) {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5008
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ } else {
+ // This doesn't seem right
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ if (ABS(pActor->objx - nx) < XMDIST
+ && ABS(pActor->objy - ny) < YMDIST) {
+ pActor->npstatus = ENTERING;
+ pActor->hFnpath = hPath;
+ SetNextDest(pActor);
+ } else {
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ }
+ }
+ }
+ return;
+ } else {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5009
+// Added 26/01/95
+ if (IsPolyCorner(hPath, pActor->ItargetX, pActor->ItargetY))
+ return;
+
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Move
+ */
+void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) {
+ MultiSetAniXY(pActor->actorObj, newx, newy);
+ MAsetZPos(pActor, newy, getPolyZfactor(hPath));
+ if (StepAnimScript(&pActor->actorAnim) == ScriptFinished) {
+ // The end of a scale-change reel
+ // Revert to normal walking reel
+ pActor->walkReel = false;
+ pActor->scount = 0;
+ SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true);
+ }
+ pActor->objx = newx;
+ pActor->objy = newy;
+
+ // Synchronised walking reels
+ if (++pActor->scount >= 6)
+ pActor->scount = 0;
+}
+
+/**
+ * Called from MActorProcess() on every tick.
+ *
+ * Moves the actor as appropriate.
+ */
+void MoveActor(PMACTOR pActor) {
+ int newx, newy;
+ HPOLYGON hPath;
+ int status, s2; // s2 not used here!
+ HPOLYGON hS2p; // nor is s2p!
+ HPOLYGON hEb;
+ PMACTOR ma;
+ int sTargetX, sTargetY;
+ bool bNewPath = false;
+
+ // Only do anything if the actor needs to move!
+ if (pActor->targetX == -1 && pActor->targetY == -1)
+ return;
+
+ if (pActor->stop) {
+ GotThere(pActor);
+ pActor->stop = false;
+ SetMActorStanding(pActor);
+ return;
+ }
+
+#if SLOW_RINCE_DOWN
+/**/ if (BogusVar++ < Interlude) // Temporary slow-down-the-action code
+/**/ return; //
+/**/ BogusVar = 0; //
+#endif
+
+ // During swalk()s, movement while hidden may be slowed down.
+ if (pActor->aHidden) {
+ if (++hSlowVar < pActor->SlowFactor)
+ return;
+ hSlowVar = 0;
+ }
+
+ // 'push' the target
+ sTargetX = pActor->targetX;
+ sTargetY = pActor->targetY;
+
+ NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY,
+ &newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor);
+
+ if (newx == pActor->objx && newy == pActor->objy) {
+ // 'pop' the target
+ pActor->targetX = sTargetX;
+ pActor->targetY = sTargetY;
+
+ NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy,
+ &status, &s2, &hS2p, pActor->over, true, pActor);
+ if (newx == pActor->objx && newy == pActor->objy) {
+ NotMoving(pActor, newx, newy);
+ return;
+ }
+ }
+
+ // Find out which path we're in now
+ hPath = InPolygon(newx, newy, PATH);
+ if (hPath == NOPOLY) {
+ if (pActor->bNoPath) {
+ Move(pActor, newx, newy, pActor->hCpath);
+ return;
+ } else {
+ // May be marginally outside!
+ // OR bIgPath may be set.
+ hPath = pActor->hCpath;
+ }
+ } else if (pActor->bNoPath) {
+ pActor->bNoPath = false;
+ bNewPath = true;
+ } else if (hPath != pActor->hCpath) {
+ if (IsInPolygon(newx, newy, pActor->hCpath))
+ hPath = pActor->hCpath;
+ }
+
+ CheckScale(pActor, hPath, newy);
+
+ /*
+ * Must stay out of moving actors' blocking polygons.
+ */
+ ma = InMActorBlock(pActor, newx, newy);
+ if (ma != NULL) {
+ // Stop if there's no chance of arriving
+ if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) {
+ GotThere(pActor);
+ return;
+ }
+
+ if (InMActorBlock(pActor, pActor->objx, pActor->objy))
+ ;
+ else {
+ hEb = InitExtraBlock(pActor, ma);
+ newx = pActor->objx;
+ newy = pActor->objy;
+ BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY);
+ SetMoverDest(pActor, newx, newy);
+ return;
+ }
+ }
+
+ /*--------------------------------------
+ This is where it actually gets moved.
+ --------------------------------------*/
+ Move(pActor, newx, newy, hPath);
+
+ // Entering a new path polygon?
+ if (hPath != pActor->hCpath || bNewPath)
+ EnteringNewPath(pActor, hPath, newx, newy);
+}
+
+/**
+ * Store the default refer type for the current scene.
+ */
+void SetDefaultRefer(int32 defRefer) {
+ DefaultRefer = defRefer;
+}
+
+/**
+ * DoMoveActor
+ */
+void DoMoveActor(PMACTOR pActor) {
+ int wasx, wasy;
+ int i;
+
+#define NUMBER 1
+
+ wasx = pActor->objx;
+ wasy = pActor->objy;
+
+ MoveActor(pActor);
+
+ if ((pActor->targetX != -1 || pActor->targetY != -1)
+ && (wasx == pActor->objx && wasy == pActor->objy)) {
+ for (i=0; i < NUMBER; i++) {
+ MoveActor(pActor);
+ if (wasx != pActor->objx || wasy != pActor->objy)
+ break;
+ }
+// assert(i<NUMBER);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/move.h b/engines/tinsel/move.h
new file mode 100644
index 0000000000..2c5f2cfe73
--- /dev/null
+++ b/engines/tinsel/move.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_MOVE_H // prevent multiple includes
+#define TINSEL_MOVE_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+struct MACTOR;
+
+void SetActorDest(MACTOR *pActor, int x, int y, bool igPath, SCNHANDLE film);
+void SSetActorDest(MACTOR *pActor);
+void DoMoveActor(MACTOR *pActor);
+
+void SetDefaultRefer(int32 defRefer);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_MOVE_H */
diff --git a/engines/tinsel/multiobj.cpp b/engines/tinsel/multiobj.cpp
new file mode 100644
index 0000000000..c60592069f
--- /dev/null
+++ b/engines/tinsel/multiobj.cpp
@@ -0,0 +1,533 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains utilities to handle multi-part objects.
+ */
+
+#include "tinsel/multiobj.h"
+#include "tinsel/handle.h"
+#include "tinsel/object.h"
+
+namespace Tinsel {
+
+// from object.c
+extern OBJECT *objectList;
+
+/**
+ * Initialise a multi-part object using a list of images to init
+ * each object piece. One object is created for each image in the list.
+ * All objects are given the same palette as the first image. A pointer
+ * to the first (master) object created is returned.
+ * @param pInitTbl Pointer to multi-object initialisation table
+ */
+OBJECT *MultiInitObject(const MULTI_INIT *pInitTbl) {
+ OBJ_INIT obj_init; // object init table
+ OBJECT *pFirst, *pObj; // object pointers
+ FRAME *pFrame; // list of images for the multi-part object
+
+ if (pInitTbl->hMulFrame) {
+ // we have a frame handle
+ pFrame = (FRAME *)LockMem(FROM_LE_32(pInitTbl->hMulFrame));
+
+ obj_init.hObjImg = READ_LE_UINT32(pFrame); // first objects shape
+ } else { // this must be a animation list for a NULL object
+ pFrame = NULL;
+ obj_init.hObjImg = 0; // first objects shape
+ }
+
+ // init the object init table
+ obj_init.objFlags = (int)FROM_LE_32(pInitTbl->mulFlags); // all objects have same flags
+ obj_init.objID = (int)FROM_LE_32(pInitTbl->mulID); // all objects have same ID
+ obj_init.objX = (int)FROM_LE_32(pInitTbl->mulX); // all objects have same X ani pos
+ obj_init.objY = (int)FROM_LE_32(pInitTbl->mulY); // all objects have same Y ani pos
+ obj_init.objZ = (int)FROM_LE_32(pInitTbl->mulZ); // all objects have same Z pos
+
+ // create and init the first object
+ pObj = pFirst = InitObject(&obj_init);
+
+ if (pFrame) {
+ // if we have any animation frames
+
+ pFrame++;
+
+ while (READ_LE_UINT32(pFrame) != 0) {
+ // set next objects shape
+ obj_init.hObjImg = READ_LE_UINT32(pFrame);
+
+ // create next object and link to previous
+ pObj = pObj->pSlave = InitObject(&obj_init);
+
+ pFrame++;
+ }
+ }
+
+ // null end of list for final object
+ pObj->pSlave = NULL;
+
+ // return master object
+ return pFirst;
+}
+
+/**
+ * Inserts the multi-part object onto the specified object list.
+ * @param pObjList List to insert multi-part object onto
+* @param pInsObj Head of multi-part object to insert
+
+ */
+
+void MultiInsertObject(OBJECT *pObjList, OBJECT *pInsObj) {
+ // validate object pointer
+ assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // add next part to the specified list
+ InsertObject(pObjList, pInsObj);
+
+ // next obj in list
+ pInsObj = pInsObj->pSlave;
+ } while (pInsObj != NULL);
+}
+
+/**
+ * Deletes all the pieces of a multi-part object from the
+ * specified object list.
+ * @param pObjList List to delete multi-part object from
+ * @param pMultiObj Multi-part object to be deleted
+ */
+
+void MultiDeleteObject(OBJECT *pObjList, OBJECT *pMultiObj) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // delete object
+ DelObject(pObjList, pMultiObj);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+ }
+ while (pMultiObj != NULL);
+}
+
+/**
+ * Hides a multi-part object by giving each object a "NullImage"
+ * image pointer.
+ * @param pMultiObj Multi-part object to be hidden
+ */
+
+void MultiHideObject(OBJECT *pMultiObj) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // set master shape to null animation frame
+ pMultiObj->hShape = 0;
+
+ // change all objects
+ MultiReshape(pMultiObj);
+}
+
+/**
+ * Horizontally flip a multi-part object.
+ * @param pFlipObj Head of multi-part object to flip
+ */
+
+void MultiHorizontalFlip(OBJECT *pFlipObj) {
+ // validate object pointer
+ assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // horizontally flip the next part
+ AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPH,
+ pFlipObj->hImg);
+
+ // next obj in list
+ pFlipObj = pFlipObj->pSlave;
+ } while (pFlipObj != NULL);
+}
+
+/**
+ * Vertically flip a multi-part object.
+ * @param pFlipObj Head of multi-part object to flip
+ */
+
+void MultiVerticalFlip(OBJECT *pFlipObj) {
+ // validate object pointer
+ assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // vertically flip the next part
+ AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPV,
+ pFlipObj->hImg);
+
+ // next obj in list
+ pFlipObj = pFlipObj->pSlave;
+ }
+ while (pFlipObj != NULL);
+}
+
+/**
+ * Adjusts the coordinates of a multi-part object. The adjustments
+ * take into account the orientation of the object.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param deltaX X adjustment
+ * @param deltaY Y adjustment
+ */
+
+void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ if (deltaX == 0 && deltaY == 0)
+ return; // ignore no change
+
+ if (pMultiObj->flags & DMA_FLIPH) {
+ // image is flipped horizontally - flip the x direction
+ deltaX = -deltaX;
+ }
+
+ if (pMultiObj->flags & DMA_FLIPV) {
+ // image is flipped vertically - flip the y direction
+ deltaY = -deltaY;
+ }
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // adjust the x position
+ pMultiObj->xPos += intToFrac(deltaX);
+
+ // adjust the y position
+ pMultiObj->yPos += intToFrac(deltaY);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+
+ } while (pMultiObj != NULL);
+}
+
+/**
+ * Moves all the pieces of a multi-part object by the specified
+ * amount. Does not take into account the objects orientation.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param deltaX X movement
+ * @param deltaY Y movement
+ */
+
+void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ if (deltaX == 0 && deltaY == 0)
+ return; // ignore no change
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // adjust the x position
+ pMultiObj->xPos += intToFrac(deltaX);
+
+ // adjust the y position
+ pMultiObj->yPos += intToFrac(deltaY);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+
+ } while (pMultiObj != NULL);
+}
+
+/**
+ * Sets the x & y anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose position is to be changed
+ * @param newAniX New x animation position
+ * @param newAniY New y animation position
+ */
+
+void MultiSetAniXY(OBJECT *pMultiObj, int newAniX, int newAniY) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc difference between current and new positions
+ newAniX -= curAniX;
+ newAniY -= curAniY;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, newAniX, newAniY);
+}
+
+/**
+ * Sets the x anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose x position is to be changed
+ * @param newAniX New x animation position
+ */
+
+void MultiSetAniX(OBJECT *pMultiObj, int newAniX) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc x difference between current and new positions
+ newAniX -= curAniX;
+ curAniY = 0;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, newAniX, curAniY);
+}
+
+/**
+ * Sets the y anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose x position is to be changed
+ * @param newAniX New y animation position
+ */
+
+void MultiSetAniY(OBJECT *pMultiObj, int newAniY) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc y difference between current and new positions
+ curAniX = 0;
+ newAniY -= curAniY;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, curAniX, newAniY);
+}
+
+/**
+ * Sets the Z position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param newZ New Z order
+ */
+
+void MultiSetZPosition(OBJECT *pMultiObj, int newZ) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // set the new z position
+ pMultiObj->zPos = newZ;
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+ }
+ while (pMultiObj != NULL);
+}
+
+/**
+ * Reshape a multi-part object.
+ * @param pMultiObj Multi-part object to re-shape
+ */
+
+void MultiReshape(OBJECT *pMultiObj) {
+ SCNHANDLE hFrame;
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get objects current anim frame
+ hFrame = pMultiObj->hShape;
+
+ if (hFrame != 0 && hFrame != pMultiObj->hMirror) {
+ // a valid shape frame which is different from previous
+
+ // get pointer to frame
+ const FRAME *pFrame = (const FRAME *)LockMem(hFrame);
+
+ // update previous
+ pMultiObj->hMirror = hFrame;
+
+ while (READ_LE_UINT32(pFrame) != 0 && pMultiObj != NULL) {
+ // a normal image - update the current object with this image
+ AnimateObject(pMultiObj, READ_LE_UINT32(pFrame));
+
+ // move to next image for this frame
+ pFrame++;
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+
+ // null the remaining object parts
+ while (pMultiObj != NULL) {
+ // set a null image for this object part
+ AnimateObject(pMultiObj, 0);
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+ } else if (hFrame == 0) {
+ // update previous
+ pMultiObj->hMirror = hFrame;
+
+ // null all the object parts
+ while (pMultiObj != NULL) {
+ // set a null image for this object part
+ AnimateObject(pMultiObj, 0);
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+ }
+}
+
+/**
+ * Returns the left-most point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiLeftmost(OBJECT *pMulti) {
+ int left;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init leftmost point to first object
+ left = fracToInt(pMulti->xPos);
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->xPos) < left)
+ // this object is further left
+ left = fracToInt(pMulti->xPos);
+ }
+ }
+
+ // return left-most point
+ return left;
+}
+
+/**
+ * Returns the right-most point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiRightmost(OBJECT *pMulti) {
+ int right;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init right-most point to first object
+ right = fracToInt(pMulti->xPos) + pMulti->width;
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->xPos) + pMulti->width > right)
+ // this object is further right
+ right = fracToInt(pMulti->xPos) + pMulti->width;
+ }
+ }
+
+ // return right-most point
+ return right - 1;
+}
+
+/**
+ * Returns the highest point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiHighest(OBJECT *pMulti) {
+ int highest;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init highest point to first object
+ highest = fracToInt(pMulti->yPos);
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->yPos) < highest)
+ // this object is higher
+ highest = fracToInt(pMulti->yPos);
+ }
+ }
+
+ // return highest point
+ return highest;
+}
+
+/**
+ * Returns the lowest point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiLowest(OBJECT *pMulti) {
+ int lowest;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init lowest point to first object
+ lowest = fracToInt(pMulti->yPos) + pMulti->height;
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->yPos) + pMulti->height > lowest)
+ // this object is lower
+ lowest = fracToInt(pMulti->yPos) + pMulti->height;
+ }
+ }
+
+ // return lowest point
+ return lowest - 1;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/multiobj.h b/engines/tinsel/multiobj.h
new file mode 100644
index 0000000000..6d25600ea2
--- /dev/null
+++ b/engines/tinsel/multiobj.h
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Multi-part object definitions
+ */
+
+#ifndef TINSEL_MULTIOBJ_H // prevent multiple includes
+#define TINSEL_MULTIOBJ_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/**
+ * multi-object initialisation structure (parallels OBJ_INIT struct)
+ */
+struct MULTI_INIT {
+ SCNHANDLE hMulFrame; //!< multi-objects shape - NULL terminated list of IMAGE structures
+ int32 mulFlags; //!< multi-objects flags
+ int32 mulID; //!< multi-objects id
+ int32 mulX; //!< multi-objects initial x ani position
+ int32 mulY; //!< multi-objects initial y ani position
+ int32 mulZ; //!< multi-objects initial z position
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+/*----------------------------------------------------------------------*\
+|* Multi Object Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+OBJECT *MultiInitObject( // Initialise a multi-part object
+ const MULTI_INIT *pInitTbl); // pointer to multi-object initialisation table
+
+void MultiInsertObject( // Insert a multi-part object onto a object list
+ OBJECT *pObjList, // list to insert multi-part object onto
+ OBJECT *pInsObj); // head of multi-part object to insert
+
+void MultiDeleteObject( // Delete all the pieces of a multi-part object
+ OBJECT *pObjList, // list to delete multi-part object from
+ OBJECT *pMultiObj); // multi-part object to be deleted
+
+void MultiHideObject( // Hide a multi-part object
+ OBJECT *pMultiObj); // multi-part object to be hidden
+
+void MultiHorizontalFlip( // Hortizontally flip a multi-part object
+ OBJECT *pFlipObj); // head of multi-part object to flip
+
+void MultiVerticalFlip( // Vertically flip a multi-part object
+ OBJECT *pFlipObj); // head of multi-part object to flip
+
+void MultiAdjustXY( // Adjust coords of a multi-part object. Takes into account the orientation
+ OBJECT *pMultiObj, // multi-part object to be adjusted
+ int deltaX, // x adjustment
+ int deltaY); // y adjustment
+
+void MultiMoveRelXY( // Move multi-part object relative. Does not take into account the orientation
+ OBJECT *pMultiObj, // multi-part object to be moved
+ int deltaX, // x movement
+ int deltaY); // y movement
+
+void MultiSetAniXY( // Set the x & y anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose position is to be changed
+ int newAniX, // new x animation position
+ int newAniY); // new y animation position
+
+void MultiSetAniX( // Set the x anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose x position is to be changed
+ int newAniX); // new x animation position
+
+void MultiSetAniY( // Set the y anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose y position is to be adjusted
+ int newAniY); // new y animation position
+
+void MultiSetZPosition( // Sets the z position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object to be adjusted
+ int newZ); // new Z order
+
+void MultiMatchAniPoints( // Matches a multi-parts pos and orientation to be the same as a reference object
+ OBJECT *pMoveObj, // multi-part object to be moved
+ OBJECT *pRefObj); // multi-part object to match with
+
+void MultiReshape( // Reshape a multi-part object
+ OBJECT *pMultiObj); // multi-part object to re-shape
+
+int MultiLeftmost( // Returns the left-most point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiRightmost( // Returns the right-most point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiHighest( // Returns the highest point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiLowest( // Returns the lowest point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_MULTIOBJ_H
diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp
new file mode 100644
index 0000000000..8af905b0c8
--- /dev/null
+++ b/engines/tinsel/music.cpp
@@ -0,0 +1,554 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// FIXME: This code is taken from MADE and may need more work (e.g. setVolume).
+
+// MIDI and digital music class
+
+#include "sound/audiostream.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "sound/audiocd.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+
+#include "tinsel/config.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+
+namespace Tinsel {
+
+//--------------------------- Midi data -------------------------------------
+
+// sound buffer structure used for MIDI data and samples
+struct SOUND_BUFFER {
+ uint8 *pDat; // pointer to actual buffer
+ uint32 size; // size of the buffer
+};
+
+// get set when music driver is installed
+//static MDI_DRIVER *mDriver;
+//static HSEQUENCE mSeqHandle;
+
+// if non-zero this is the index position of the next MIDI sequence to play
+static uint32 dwMidiIndex = 0;
+
+// MIDI buffer
+static SOUND_BUFFER midiBuffer = { 0, 0 };
+
+static SCNHANDLE currentMidi;
+static bool currentLoop;
+
+const SCNHANDLE midiOffsetsGRAVersion[] = {
+ 4, 4534, 14298, 18828, 23358, 38888, 54418, 57172, 59926, 62450,
+ 62952, 67482, 72258, 74538, 79314, 87722, 103252, 115176, 127100, 127898,
+ 130256, 132614, 134972, 137330, 139688, 150196, 152554, 154912, 167422, 174762,
+ 182102, 194612, 198880, 199536, 206128, 206380, 216372, 226364, 235676, 244988,
+ 249098, 249606, 251160, 252714, 263116, 268706, 274296, 283562, 297986, 304566,
+ 312028, 313524, 319192, 324860, 331772, 336548, 336838, 339950, 343062, 346174,
+ 349286, 356246, 359358, 360434, 361510, 369966, 374366, 382822, 384202, 394946,
+ 396022, 396730, 399524, 401020, 403814, 418364, 419466, 420568, 425132, 433540,
+ 434384, 441504, 452132, 462760, 472804, 486772, 491302, 497722, 501260, 507680,
+ 509726, 521858, 524136, 525452, 533480, 538236, 549018, 559870, 564626, 565306,
+ 566734, 567616, 570144, 574102, 574900, 582518, 586350, 600736, 604734, 613812,
+ 616566, 619626, 623460, 627294, 631128, 634188, 648738, 663288, 667864, 681832,
+ 682048, 683014, 688908, 689124, 698888, 708652, 718416, 728180, 737944, 747708,
+ 752238, 765522, 766554, 772944, 774546, 776148, 776994, 781698, 786262, 789016,
+ 794630, 796422, 798998
+};
+
+const SCNHANDLE midiOffsetsSCNVersion[] = {
+ 4, 4504, 11762, 21532, 26070, 28754, 33254, 40512, 56310, 72108,
+ 74864, 77620, 80152, 80662, 85200, 89982, 92268, 97050, 105466, 121264,
+ 133194, 145124, 145928, 148294, 150660, 153026, 155392, 157758, 168272, 170638,
+ 173004, 185522, 192866, 200210, 212728, 217000, 217662, 224254, 224756, 234754,
+ 244752, 245256, 245950, 255256, 264562, 268678, 269192, 270752, 272312, 282712,
+ 288312, 293912, 303186, 317624, 324210, 331680, 333208, 338884, 344560, 351478,
+ 356262, 356552, 359670, 362788, 365906, 369024, 376014, 379132, 380214, 381296,
+ 389758, 394164, 402626, 404012, 414762, 415844, 416552, 419352, 420880, 423680,
+ 438236, 439338, 440440, 445010, 453426, 454276, 461398, 472032, 482666, 492716,
+ 506690, 511226, 517654, 521198, 527626, 529676, 541814, 546210, 547532, 555562,
+ 560316, 571104, 581962, 586716, 587402, 588836, 589718, 592246, 596212, 597016,
+ 604636, 608474, 622862, 626860, 635944, 638700, 641456, 645298, 649140, 652982,
+ 655738, 670294, 684850, 689432, 703628, 703850, 704816, 706350, 706572, 716342,
+ 726112, 735882, 745652, 755422, 765192, 774962, 784732, 794502, 804272, 814042,
+ 823812, 832996, 846286, 847324, 853714, 855324, 856934, 857786, 862496, 867066,
+ 869822, 875436, 877234, 879818
+};
+
+// TODO: finish this (currently unmapped tracks are 0)
+const int enhancedAudioSCNVersion[] = {
+ 0, 0, 2, 0, 0, 0, 0, 3, 3, 4,
+ 4, 0, 0, 0, 0, 0, 0, 10, 3, 11,
+ 11, 0, 13, 13, 13, 13, 13, 0, 13, 13,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 24, 0, 0, 27, 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, 55, 56, 56, 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, 4, 4, 83, 83, 83, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 52, 4,
+ 0, 0, 0, 0
+};
+
+int GetTrackNumber(SCNHANDLE hMidi) {
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ for (int i = 0; i < ARRAYSIZE(midiOffsetsSCNVersion); i++) {
+ if (midiOffsetsSCNVersion[i] == hMidi)
+ return i;
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(midiOffsetsGRAVersion); i++) {
+ if (midiOffsetsGRAVersion[i] == hMidi)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+SCNHANDLE GetTrackOffset(int trackNumber) {
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ assert(trackNumber < ARRAYSIZE(midiOffsetsSCNVersion));
+ return midiOffsetsSCNVersion[trackNumber];
+ } else {
+ assert(trackNumber < ARRAYSIZE(midiOffsetsGRAVersion));
+ return midiOffsetsGRAVersion[trackNumber];
+ }
+}
+
+/**
+ * Plays the specified MIDI sequence through the sound driver.
+ * @param dwFileOffset File offset of MIDI sequence data
+ * @param bLoop Whether to loop the sequence
+ */
+
+bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) {
+ currentMidi = dwFileOffset;
+ currentLoop = bLoop;
+
+ if (volMidi != 0) {
+ SetMidiVolume(volMidi);
+ // Support for compressed music from the music enhancement project
+ AudioCD.stop();
+
+ int trackNumber = GetTrackNumber(dwFileOffset);
+ if (trackNumber >= 0) {
+#if 0
+ // TODO: GRA version
+ int track = enhancedAudioSCNVersion[trackNumber];
+ if (track > 0)
+ AudioCD.play(track, -1, 0, 0);
+#endif
+ } else {
+ warning("Unknown MIDI offset %d", dwFileOffset);
+ }
+
+ if (AudioCD.isPlaying())
+ return true;
+ }
+
+ // set file offset for this sequence
+ dwMidiIndex = dwFileOffset;
+
+ // the index and length of the last tune loaded
+ static uint32 dwLastMidiIndex;
+ static uint32 dwLastSeqLen;
+
+ uint32 dwSeqLen = 0; // length of the sequence
+
+ if (dwMidiIndex == 0)
+ return true;
+
+ if (dwMidiIndex != dwLastMidiIndex) {
+ Common::File midiStream;
+
+ // open MIDI sequence file in binary mode
+ if (!midiStream.open(MIDI_FILE))
+ error("Cannot find file %s", MIDI_FILE);
+
+ // update index of last tune loaded
+ dwLastMidiIndex = dwMidiIndex;
+
+ // move to correct position in the file
+ midiStream.seek(dwMidiIndex, SEEK_SET);
+
+ // read the length of the sequence
+ dwSeqLen = midiStream.readUint32LE();
+
+ // make sure buffer is large enough for this sequence
+ assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size);
+
+ // stop any currently playing tune
+ _vm->_music->stop();
+
+ // read the sequence
+ if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen)
+ error("File %s is corrupt", MIDI_FILE);
+
+ midiStream.close();
+
+ _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop);
+
+ // Store the length
+ dwLastSeqLen = dwSeqLen;
+ } else {
+ // dwMidiIndex == dwLastMidiIndex
+ _vm->_music->stop();
+ _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop);
+ }
+
+ // allow another sequence to play
+ dwMidiIndex = 0;
+
+ return true;
+}
+
+/**
+ * Returns TRUE if a Midi tune is currently playing.
+ */
+
+bool MidiPlaying(void) {
+ if (AudioCD.isPlaying()) return true;
+ return _vm->_music->isPlaying();
+}
+
+/**
+ * Stops any currently playing midi.
+ */
+
+bool StopMidi(void) {
+ currentMidi = 0;
+ currentLoop = false;
+
+ AudioCD.stop();
+ _vm->_music->stop();
+ return true;
+}
+
+
+/**
+ * Gets the volume of the MIDI music.
+ */
+int GetMidiVolume() {
+ return volMidi;
+}
+
+/**
+ * Sets the volume of the MIDI music.
+ * @param vol New volume - 0..MAXMIDIVOL
+ */
+void SetMidiVolume(int vol) {
+ assert(vol >= 0 && vol <= MAXMIDIVOL);
+
+ if (vol == 0 && volMidi == 0) {
+ // Nothing to do
+ } else if (vol == 0 && volMidi != 0) {
+ // Stop current midi sequence
+ AudioCD.stop();
+ StopMidi();
+ } else if (vol != 0 && volMidi == 0) {
+ // Perhaps restart last midi sequence
+ if (currentLoop) {
+ PlayMidiSequence(currentMidi, true);
+ _vm->_music->setVolume(vol);
+ }
+ } else if (vol != 0 && volMidi != 0) {
+ // Alter current volume
+ _vm->_music->setVolume(vol);
+ }
+
+ volMidi = vol;
+}
+
+/**
+ * Opens and inits all MIDI sequence files.
+ */
+void OpenMidiFiles(void) {
+ Common::File midiStream;
+
+ // Demo version has no midi file
+ if (_vm->getFeatures() & GF_DEMO)
+ return;
+
+ if (midiBuffer.pDat)
+ // already allocated
+ return;
+
+ // open MIDI sequence file in binary mode
+ if (!midiStream.open(MIDI_FILE))
+ error("Cannot find file %s", MIDI_FILE);
+
+ // gen length of the largest sequence
+ midiBuffer.size = midiStream.readUint32LE();
+ if (midiStream.ioFailed())
+ error("File %s is corrupt", MIDI_FILE);
+
+ if (midiBuffer.size) {
+ // allocate a buffer big enough for the largest MIDI sequence
+ if ((midiBuffer.pDat = (uint8 *)malloc(midiBuffer.size)) != NULL) {
+ // clear out the buffer
+ memset(midiBuffer.pDat, 0, midiBuffer.size);
+// VMM_lock(midiBuffer.pDat, midiBuffer.size);
+ } else {
+ //mSeqHandle = NULL;
+ }
+ }
+
+ midiStream.close();
+}
+
+void DeleteMidiBuffer() {
+ free(midiBuffer.pDat);
+ midiBuffer.pDat = NULL;
+}
+
+MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) {
+ memset(_channel, 0, sizeof(_channel));
+ _masterVolume = 0;
+ this->open();
+ _xmidiParser = MidiParser::createParser_XMIDI();
+}
+
+MusicPlayer::~MusicPlayer() {
+ _driver->setTimerCallback(NULL, NULL);
+ stop();
+ this->close();
+ _xmidiParser->setMidiDriver(NULL);
+ delete _xmidiParser;
+}
+
+void MusicPlayer::setVolume(int volume) {
+ Common::StackLock lock(_mutex);
+
+ // FIXME: Could we simply change MAXMIDIVOL to match ScummVM's range?
+ volume = CLIP((255 * volume) / MAXMIDIVOL, 0, 255);
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+
+ 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() {
+ stop();
+ if (_driver)
+ _driver->close();
+ _driver = 0;
+}
+
+void MusicPlayer::send(uint32 b) {
+ 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 & 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);
+
+ if ((b & 0xFFF0) == 0x0079B0) {
+ // We've just Reset All Controllers, so we need to
+ // re-adjust the volume. Otherwise, volume is reset to
+ // default whenever the music changes.
+ _channel[channel]->send(0x000007B0 | (((_channelVolume[channel] * _masterVolume) / 255) << 16) | channel);
+ }
+ }
+}
+
+void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
+ switch (type) {
+ case 0x2F: // End of Track
+ if (_looping)
+ _parser->jumpToTick(0);
+ else
+ stop();
+ break;
+ default:
+ //warning("Unhandled meta event: %02x", type);
+ break;
+ }
+}
+
+void MusicPlayer::onTimer(void *refCon) {
+ MusicPlayer *music = (MusicPlayer *)refCon;
+ Common::StackLock lock(music->_mutex);
+
+ if (music->_isPlaying)
+ music->_parser->onTimer();
+}
+
+void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) {
+ if (_isPlaying)
+ return;
+
+ stop();
+
+ // It seems like not all music (the main menu music, for instance) set
+ // all the instruments explicitly. That means the music will sound
+ // different, depending on which music played before it. This appears
+ // to be a genuine glitch in the original. For consistency, reset all
+ // instruments to the default one (piano).
+
+ for (int i = 0; i < 16; i++) {
+ _driver->send(0xC0 | i, 0, 0);
+ }
+
+ // Load XMID resource data
+
+ if (_xmidiParser->loadMusic(midiData, size)) {
+ MidiParser *parser = _xmidiParser;
+ parser->setTrack(0);
+ parser->setMidiDriver(this);
+ parser->setTimerRate(getBaseTempo());
+ parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+
+ _parser = parser;
+
+ _looping = loop;
+ _isPlaying = true;
+ }
+}
+
+void MusicPlayer::stop() {
+ Common::StackLock lock(_mutex);
+
+ _isPlaying = false;
+ if (_parser) {
+ _parser->unloadMusic();
+ _parser = NULL;
+ }
+}
+
+void MusicPlayer::pause() {
+ setVolume(-1);
+ _isPlaying = false;
+}
+
+void MusicPlayer::resume() {
+ setVolume(GetMidiVolume());
+ _isPlaying = true;
+}
+
+void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) {
+ *pMidi = currentMidi;
+ *pLoop = currentLoop;
+}
+
+void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) {
+ AudioCD.stop();
+ StopMidi();
+
+ currentMidi = Midi;
+ currentLoop = Loop;
+
+ if (volMidi != 0 && Loop) {
+ PlayMidiSequence(currentMidi, true);
+ SetMidiVolume(volMidi);
+ }
+}
+
+#if 0
+// Dumps all of the game's music in external XMIDI *.xmi files
+void dumpMusic() {
+ Common::File midiFile;
+ Common::File outFile;
+ char outName[20];
+ midiFile.open(MIDI_FILE);
+ int outFileSize = 0;
+ char buffer[20000];
+
+ int total = (_vm->getFeatures() & GF_SCNFILES) ?
+ ARRAYSIZE(midiOffsetsSCNVersion) :
+ ARRAYSIZE(midiOffsetsGRAVersion);
+
+ for (int i = 0; i < total; i++) {
+ sprintf(outName, "track%03d.xmi", i + 1);
+ outFile.open(outName, Common::File::kFileWriteMode);
+
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ if (i < total - 1)
+ outFileSize = midiOffsetsSCNVersion[i + 1] - midiOffsetsSCNVersion[i] - 4;
+ else
+ outFileSize = midiFile.size() - midiOffsetsSCNVersion[i] - 4;
+
+ midiFile.seek(midiOffsetsSCNVersion[i] + 4, SEEK_SET);
+ } else {
+ if (i < total - 1)
+ outFileSize = midiOffsetsGRAVersion[i + 1] - midiOffsetsGRAVersion[i] - 4;
+ else
+ outFileSize = midiFile.size() - midiOffsetsGRAVersion[i] - 4;
+
+ midiFile.seek(midiOffsetsGRAVersion[i] + 4, SEEK_SET);
+ }
+
+ assert(outFileSize < 20000);
+ midiFile.read(buffer, outFileSize);
+ outFile.write(buffer, outFileSize);
+
+ outFile.close();
+ }
+
+ midiFile.close();
+}
+#endif
+
+} // End of namespace Made
diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h
new file mode 100644
index 0000000000..80456e2a76
--- /dev/null
+++ b/engines/tinsel/music.h
@@ -0,0 +1,118 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Music class
+
+#ifndef TINSEL_MUSIC_H
+#define TINSEL_MUSIC_H
+
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "common/mutex.h"
+
+namespace Tinsel {
+
+#define MAXMIDIVOL 127
+
+bool PlayMidiSequence( // Plays the specified MIDI sequence through the sound driver
+ uint32 dwFileOffset, // handle of MIDI sequence data
+ bool bLoop); // Whether to loop the sequence
+
+bool MidiPlaying(void); // Returns TRUE if a Midi tune is currently playing
+
+bool StopMidi(void); // Stops any currently playing midi
+
+void SetMidiVolume( // Sets the volume of the MIDI music. Returns the old volume
+ int vol); // new volume - 0..MAXMIDIVOL
+
+int GetMidiVolume();
+
+void OpenMidiFiles();
+void DeleteMidiBuffer();
+
+void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop);
+void RestoreMidiFacts(SCNHANDLE Midi, bool Loop);
+
+int GetTrackNumber(SCNHANDLE hMidi);
+SCNHANDLE GetTrackOffset(int trackNumber);
+
+void dumpMusic();
+
+
+class MusicPlayer : public MidiDriver {
+public:
+ MusicPlayer(MidiDriver *driver);
+ ~MusicPlayer();
+
+ bool isPlaying() { return _isPlaying; }
+ void setPlaying(bool playing) { _isPlaying = playing; }
+
+ void setVolume(int volume);
+ int getVolume() { return _masterVolume; }
+
+ void playXMIDI(byte *midiData, uint32 size, bool loop);
+ void stop();
+ void pause();
+ void resume();
+ void setLoop(bool loop) { _looping = loop; }
+
+ //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 *)) { }
+
+ // The original sets the "sequence timing" to 109 Hz, whatever that
+ // means. The default is 120.
+
+ uint32 getBaseTempo(void) { return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; }
+
+ //Channel allocation functions
+ MidiChannel *allocateChannel() { return 0; }
+ MidiChannel *getPercussionChannel() { return 0; }
+
+ MidiParser *_parser;
+ Common::Mutex _mutex;
+
+protected:
+
+ static void onTimer(void *data);
+
+ MidiChannel *_channel[16];
+ MidiDriver *_driver;
+ MidiParser *_xmidiParser;
+ byte _channelVolume[16];
+
+ bool _isPlaying;
+ bool _looping;
+ byte _masterVolume;
+};
+
+} // End of namespace Made
+
+#endif
diff --git a/engines/tinsel/object.cpp b/engines/tinsel/object.cpp
new file mode 100644
index 0000000000..5ec0ec46e6
--- /dev/null
+++ b/engines/tinsel/object.cpp
@@ -0,0 +1,527 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the Object Manager code.
+ */
+
+#include "tinsel/object.h"
+#include "tinsel/background.h" // for rcScreen definition
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h" // low level interface
+#include "tinsel/handle.h"
+
+#define OID_EFFECTS 0x2000 // generic special effects object id
+
+namespace Tinsel {
+
+// list of all objects
+OBJECT *objectList = 0;
+
+// pointer to free object list
+static OBJECT *pFreeObjects = 0;
+
+#ifdef DEBUG
+// diagnostic object counters
+static int numObj = 0;
+static int maxObj = 0;
+#endif
+
+void FreeObjectList(void) {
+ if (objectList) {
+ free(objectList);
+ objectList = NULL;
+ }
+}
+
+/**
+ * Kills all objects and places them on the free list.
+ */
+
+void KillAllObjects(void) {
+ int i;
+
+#ifdef DEBUG
+ // clear number of objects in use
+ numObj = 0;
+#endif
+
+ if (objectList == NULL) {
+ // first time - allocate memory for object list
+ objectList = (OBJECT *)calloc(NUM_OBJECTS, sizeof(OBJECT));
+
+ // make sure memory allocated
+ if (objectList == NULL) {
+ error("Cannot allocate memory for object data");
+ }
+ }
+
+ // place first object on free list
+ pFreeObjects = objectList;
+
+ // link all other objects after first
+ for (i = 1; i < NUM_OBJECTS; i++) {
+ objectList[i - 1].pNext = objectList + i;
+ }
+
+ // null the last object
+ objectList[NUM_OBJECTS - 1].pNext = NULL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of objects used at once.
+ */
+
+void ObjectStats(void) {
+ printf("%i objects of %i used.\n", maxObj, NUM_OBJECTS);
+}
+#endif
+
+/**
+ * Allocate a object from the free list.
+ */
+OBJECT *AllocObject(void) {
+ OBJECT *pObj = pFreeObjects; // get a free object
+
+ // check for no free objects
+ assert(pObj != NULL);
+
+ // a free object exists
+
+ // get link to next free object
+ pFreeObjects = pObj->pNext;
+
+ // clear out object
+ memset(pObj, 0, sizeof(OBJECT));
+
+ // set default drawing mode and set changed bit
+ pObj->flags = DMA_WNZ | DMA_CHANGED;
+
+#ifdef DEBUG
+ // one more object in use
+ if (++numObj > maxObj)
+ maxObj = numObj;
+#endif
+
+ // return new object
+ return pObj;
+}
+
+/**
+ * Copy one object to another.
+ * @param pDest Destination object
+ * @param pSrc Source object
+ */
+void CopyObject(OBJECT *pDest, OBJECT *pSrc) {
+ // save previous dimensions etc.
+ Common::Rect rcSave = pDest->rcPrev;
+
+ // make a copy
+ memcpy(pDest, pSrc, sizeof(OBJECT));
+
+ // restore previous dimensions etc.
+ pDest->rcPrev = rcSave;
+
+ // set changed flag in destination
+ pDest->flags |= DMA_CHANGED;
+
+ // null the links
+ pDest->pNext = pDest->pSlave = NULL;
+}
+
+/**
+ * Inserts an object onto the specified object list. The object
+ * lists are sorted in Z Y order.
+ * @param pObjList List to insert object onto
+ * @param pInsObj Object to insert
+ */
+
+void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+
+ // validate object pointer
+ assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1);
+
+ for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ // check Z order
+ if (pInsObj->zPos < pObj->zPos) {
+ // object Z is lower than list Z - insert here
+ break;
+ } else if (pInsObj->zPos == pObj->zPos) {
+ // Z values are the same - sort on Y
+ if (fracToDouble(pInsObj->yPos) <= fracToDouble(pObj->yPos)) {
+ // object Y is lower than or same as list Y - insert here
+ break;
+ }
+ }
+ }
+
+ // insert obj between pPrev and pObj
+ pInsObj->pNext = pObj;
+ pPrev->pNext = pInsObj;
+}
+
+
+/**
+ * Deletes an object from the specified object list and places it
+ * on the free list.
+ * @param pObjList List to delete object from
+ * @param pDelObj Object to delete
+ */
+void DelObject(OBJECT *pObjList, OBJECT *pDelObj) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+
+ // validate object pointer
+ assert(pDelObj >= objectList && pDelObj <= objectList + NUM_OBJECTS - 1);
+
+#ifdef DEBUG
+ // one less object in use
+ --numObj;
+ assert(numObj >= 0);
+#endif
+
+ for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ if (pObj == pDelObj) {
+ // found object to delete
+
+ if (IntersectRectangle(pDelObj->rcPrev, pDelObj->rcPrev, rcScreen)) {
+ // allocate a clipping rect for objects previous pos
+ AddClipRect(pDelObj->rcPrev);
+ }
+
+ // make PREV next = OBJ next - removes OBJ from list
+ pPrev->pNext = pObj->pNext;
+
+ // place free list in OBJ next
+ pObj->pNext = pFreeObjects;
+
+ // add OBJ to top of free list
+ pFreeObjects = pObj;
+
+ // delete objects palette
+ if (pObj->pPal)
+ FreePalette(pObj->pPal);
+
+ // quit
+ return;
+ }
+ }
+
+ // if we get to here - object has not been found on the list
+ error("DelObject(): formally 'assert(0)!'");
+}
+
+
+/**
+ * Sort the specified object list in Z Y order.
+ * @param pObjList List to sort
+ */
+void SortObjectList(OBJECT *pObjList) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+ OBJECT head; // temporary head of list - because pObjList is not usually a OBJECT
+
+ // put at head of list
+ head.pNext = pObjList->pNext;
+
+ // set head of list dummy OBJ Z Y values to lowest possible
+ head.yPos = intToFrac(MIN_INT16);
+ head.zPos = MIN_INT;
+
+ for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ // check Z order
+ if (pObj->zPos < pPrev->zPos) {
+ // object Z is lower than previous Z
+
+ // remove object from list
+ pPrev->pNext = pObj->pNext;
+
+ // re-insert object on list
+ InsertObject(pObjList, pObj);
+
+ // back to beginning of list
+ pPrev = &head;
+ pObj = head.pNext;
+ } else if (pObj->zPos == pPrev->zPos) {
+ // Z values are the same - sort on Y
+ if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) {
+ // object Y is lower than previous Y
+
+ // remove object from list
+ pPrev->pNext = pObj->pNext;
+
+ // re-insert object on list
+ InsertObject(pObjList, pObj);
+
+ // back to beginning of list
+ pPrev = &head;
+ pObj = head.pNext;
+ }
+ }
+ }
+}
+
+/**
+ * Returns the animation offsets of a image, dependent on the
+ * images orientation flags.
+ * @param hImg Iimage to get animation offset of
+ * @param flags Images current flags
+ * @param pAniX Gets set to new X animation offset
+ * @param pAniY Gets set to new Y animation offset
+ */
+void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) {
+ if (hImg) {
+ const IMAGE *pImg = (const IMAGE *)LockMem(hImg);
+
+ // set ani X
+ *pAniX = FROM_LE_16(pImg->anioffX);
+
+ // set ani Y
+ *pAniY = FROM_LE_16(pImg->anioffY);
+
+ if (flags & DMA_FLIPH) {
+ // we are flipped horizontally
+
+ // set ani X = -ani X + width - 1
+ *pAniX = -*pAniX + FROM_LE_16(pImg->imgWidth) - 1;
+ }
+
+ if (flags & DMA_FLIPV) {
+ // we are flipped vertically
+
+ // set ani Y = -ani Y + height - 1
+ *pAniY = -*pAniY + FROM_LE_16(pImg->imgHeight) - 1;
+ }
+ } else
+ // null image
+ *pAniX = *pAniY = 0;
+}
+
+
+/**
+ * Returns the x,y position of an objects animation point.
+ * @param pObj Pointer to object
+ * @param pPosX Gets set to objects X animation position
+ * @param pPosY Gets set to objects Y animation position
+ */
+void GetAniPosition(OBJECT *pObj, int *pPosX, int *pPosY) {
+ // validate object pointer
+ assert(pObj >= objectList && pObj <= objectList + NUM_OBJECTS - 1);
+
+ // get the animation offset of the object
+ GetAniOffset(pObj->hImg, pObj->flags, pPosX, pPosY);
+
+ // from animation offset and objects position - determine objects animation point
+ *pPosX += fracToInt(pObj->xPos);
+ *pPosY += fracToInt(pObj->yPos);
+}
+
+/**
+ * Initialise a object using a OBJ_INIT structure to supply parameters.
+ * @param pInitTbl Pointer to object initialisation table
+ */
+OBJECT *InitObject(const OBJ_INIT *pInitTbl) {
+ // allocate a new object
+ OBJECT *pObj = AllocObject();
+
+ // make sure object created
+ assert(pObj != NULL);
+
+ // set objects shape
+ pObj->hImg = pInitTbl->hObjImg;
+
+ // set objects ID
+ pObj->oid = pInitTbl->objID;
+
+ // set objects flags
+ pObj->flags = DMA_CHANGED | pInitTbl->objFlags;
+
+ // set objects Z position
+ pObj->zPos = pInitTbl->objZ;
+
+ // get pointer to image
+ if (pInitTbl->hObjImg) {
+ int aniX, aniY; // objects animation offsets
+ PPALQ pPalQ; // palette queue pointer
+ const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg); // handle to image
+
+ // allocate a palette for this object
+ pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal));
+
+ // make sure palette allocated
+ assert(pPalQ != NULL);
+
+ // assign palette to object
+ pObj->pPal = pPalQ;
+
+ // set objects size
+ pObj->width = FROM_LE_16(pImg->imgWidth);
+ pObj->height = FROM_LE_16(pImg->imgHeight);
+
+ // set objects bitmap definition
+ pObj->hBits = FROM_LE_32(pImg->hImgBits);
+
+ // get animation offset of object
+ GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY);
+
+ // set objects X position - subtract ani offset
+ pObj->xPos = intToFrac(pInitTbl->objX - aniX);
+
+ // set objects Y position - subtract ani offset
+ pObj->yPos = intToFrac(pInitTbl->objY - aniY);
+ } else { // no image handle - null image
+
+ // set objects X position
+ pObj->xPos = intToFrac(pInitTbl->objX);
+
+ // set objects Y position
+ pObj->yPos = intToFrac(pInitTbl->objY);
+ }
+
+ // return new object
+ return pObj;
+}
+
+/**
+ * Give a object a new image and new orientation flags.
+ * @param pAniObj Object to be updated
+ * @param newflags Objects new flags
+ * @param hNewImg Objects new image
+ */
+void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) {
+ // validate object pointer
+ assert(pAniObj >= objectList && pAniObj <= objectList + NUM_OBJECTS - 1);
+
+ if (pAniObj->hImg != hNewImg
+ || (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) {
+ // something has changed
+
+ int oldAniX, oldAniY; // objects old animation offsets
+ int newAniX, newAniY; // objects new animation offsets
+
+ // get objects old animation offsets
+ GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY);
+
+ // get objects new animation offsets
+ GetAniOffset(hNewImg, newflags, &newAniX, &newAniY);
+
+ if (hNewImg) {
+ // get pointer to image
+ const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg);
+
+ // setup new shape
+ pAniObj->width = FROM_LE_16(pNewImg->imgWidth);
+ pAniObj->height = FROM_LE_16(pNewImg->imgHeight);
+
+ // set objects bitmap definition
+ pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits);
+ } else { // null image
+ pAniObj->width = 0;
+ pAniObj->height = 0;
+ pAniObj->hBits = 0;
+ }
+
+ // set objects flags and signal a change
+ pAniObj->flags = newflags | DMA_CHANGED;
+
+ // set objects image
+ pAniObj->hImg = hNewImg;
+
+ // adjust objects position - subtract new from old for difference
+ pAniObj->xPos += intToFrac(oldAniX - newAniX);
+ pAniObj->yPos += intToFrac(oldAniY - newAniY);
+ }
+}
+
+/**
+ * Give an object a new image.
+ * @param pAniObj Object to animate
+ * @param hNewImg Objects new image
+ */
+void AnimateObject(OBJECT *pAniObj, SCNHANDLE hNewImg) {
+ // dont change the objects flags
+ AnimateObjectFlags(pAniObj, pAniObj->flags, hNewImg);
+}
+
+/**
+ * Creates a rectangle object of the given dimensions and returns
+ * a pointer to the object.
+ * @param hPal Palette for the rectangle object
+ * @param colour Which colour offset from the above palette
+ * @param width Width of rectangle
+ * @param height Height of rectangle
+ */
+OBJECT *RectangleObject(SCNHANDLE hPal, int colour, int width, int height) {
+ // template for initialising the rectangle object
+ static const OBJ_INIT rectObj = {0, DMA_CONST, OID_EFFECTS, 0, 0, 0};
+ PPALQ pPalQ; // palette queue pointer
+
+ // allocate and init a new object
+ OBJECT *pRect = InitObject(&rectObj);
+
+ // allocate a palette for this object
+ pPalQ = AllocPalette(hPal);
+
+ // make sure palette allocated
+ assert(pPalQ != NULL);
+
+ // assign palette to object
+ pRect->pPal = pPalQ;
+
+ // set colour in the palette
+ pRect->constant = colour;
+
+ // set rectangle width
+ pRect->width = width;
+
+ // set rectangle height
+ pRect->height = height;
+
+ // return pointer to rectangle object
+ return pRect;
+}
+
+/**
+ * Creates a translucent rectangle object of the given dimensions
+ * and returns a pointer to the object.
+ * @param width Width of rectangle
+ * @param height Height of rectangle
+ */
+OBJECT *TranslucentObject(int width, int height) {
+ // template for initialising the rectangle object
+ static const OBJ_INIT rectObj = {0, DMA_TRANS, OID_EFFECTS, 0, 0, 0};
+
+ // allocate and init a new object
+ OBJECT *pRect = InitObject(&rectObj);
+
+ // set rectangle width
+ pRect->width = width;
+
+ // set rectangle height
+ pRect->height = height;
+
+ // return pointer to rectangle object
+ return pRect;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h
new file mode 100644
index 0000000000..01c56737e4
--- /dev/null
+++ b/engines/tinsel/object.h
@@ -0,0 +1,207 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Object Manager data structures
+ */
+
+#ifndef TINSEL_OBJECT_H // prevent multiple includes
+#define TINSEL_OBJECT_H
+
+#include "tinsel/dw.h"
+#include "common/frac.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct PALQ;
+
+enum {
+ /** the maximum number of objects */
+ NUM_OBJECTS = 256,
+
+ // object flags
+ DMA_WNZ = 0x0001, //!< write non-zero data
+ DMA_CNZ = 0x0002, //!< write constant on non-zero data
+ DMA_CONST = 0x0004, //!< write constant on both zero & non-zero data
+ DMA_WA = 0x0008, //!< write all data
+ DMA_FLIPH = 0x0010, //!< flip object horizontally
+ DMA_FLIPV = 0x0020, //!< flip object vertically
+ DMA_CLIP = 0x0040, //!< clip object
+ DMA_TRANS = 0x0084, //!< translucent rectangle object
+ DMA_ABS = 0x0100, //!< position of object is absolute
+ DMA_CHANGED = 0x0200, //!< object has changed in some way since the last frame
+ DMA_USERDEF = 0x0400, //!< user defined flags start here
+
+ /** flags that effect an objects appearance */
+ DMA_HARDFLAGS = (DMA_WNZ | DMA_CNZ | DMA_CONST | DMA_WA | DMA_FLIPH | DMA_FLIPV | DMA_TRANS)
+};
+
+/** structure for image */
+struct IMAGE {
+ short imgWidth; //!< image width
+ short imgHeight; //!< image height
+ short anioffX; //!< image x animation offset
+ short anioffY; //!< image y animation offset
+ SCNHANDLE hImgBits; //!< image bitmap handle
+ SCNHANDLE hImgPal; //!< image palette handle
+};
+typedef IMAGE *PIMAGE;
+
+
+/** a multi-object animation frame is a list of multi-image handles */
+typedef uint32 FRAME;
+
+
+// object structure
+struct OBJECT {
+ OBJECT *pNext; //!< pointer to next object in list
+ OBJECT *pSlave; //!< pointer to slave object (multi-part objects)
+// char *pOnDispList; //!< pointer to display list byte for background objects
+// frac_t xVel; //!< x velocity of object
+// frac_t yVel; //!< y velocity of object
+ frac_t xPos; //!< x position of object
+ frac_t yPos; //!< y position of object
+ int zPos; //!< z position of object
+ Common::Rect rcPrev; //!< previous screen coordinates of object bounding rectangle
+ int flags; //!< object flags - see above for list
+ PALQ *pPal; //!< objects palette Q position
+ int constant; //!< which colour in palette for monochrome objects
+ int width; //!< width of object
+ int height; //!< height of object
+ SCNHANDLE hBits; //!< image bitmap handle
+ SCNHANDLE hImg; //!< handle to object image definition
+ SCNHANDLE hShape; //!< objects current animation frame
+ SCNHANDLE hMirror; //!< objects previous animation frame
+ int oid; //!< object identifier
+};
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+// object initialisation structure
+struct OBJ_INIT {
+ SCNHANDLE hObjImg; // objects shape - handle to IMAGE structure
+ int32 objFlags; // objects flags
+ int32 objID; // objects id
+ int32 objX; // objects initial x position
+ int32 objY; // objects initial y position
+ int32 objZ; // objects initial z position
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/*----------------------------------------------------------------------*\
+|* Object Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void KillAllObjects(void); // kill all objects and place them on free list
+
+void FreeObjectList(void); // free the object list
+
+#ifdef DEBUG
+void ObjectStats(void); // Shows the maximum number of objects used at once
+#endif
+
+OBJECT *AllocObject(void); // allocate a object from the free list
+
+void FreeObject( // place a object back on the free list
+ OBJECT *pFreeObj); // object to free
+
+void CopyObject( // copy one object to another
+ OBJECT *pDest, // destination object
+ OBJECT *pSrc); // source object
+
+void InsertObject( // insert a object onto a sorted object list
+ OBJECT *pObjList, // list to insert object onto
+ OBJECT *pInsObj); // object to insert
+
+void DelObject( // delete a object from a object list and add to free list
+ OBJECT *pObjList, // list to delete object from
+ OBJECT *pDelObj); // object to delete
+
+void SortObjectList( // re-sort an object list
+ OBJECT *pObjList); // list to sort
+
+OBJECT *GetNextObject( // object list iterator - returns next obj in list
+ OBJECT *pObjList, // which object list
+ OBJECT *pStrtObj); // object to start from - when NULL will start from beginning of list
+
+OBJECT *FindObject( // Searches the specified obj list for a object matching the specified OID
+ OBJECT *pObjList, // object list to search
+ int oidDesired, // object identifer of object to find
+ int oidMask); // mask to apply to object identifiers before comparison
+
+void GetAniOffset( // returns the anim offsets of a image, takes into account orientation
+ SCNHANDLE hImg, // image to get animation offset of
+ int flags, // images current flags
+ int *pAniX, // gets set to new X animation offset
+ int *pAniY); // gets set to new Y animation offset
+
+void GetAniPosition( // Returns a objects x,y animation point
+ OBJECT *pObj, // pointer to object
+ int *pPosX, // gets set to objects X animation position
+ int *pPosY); // gets set to objects Y animation position
+
+OBJECT *InitObject( // Init a object using a OBJ_INIT struct
+ const OBJ_INIT *pInitTbl); // pointer to object initialisation table
+
+void AnimateObjectFlags( // Give a object a new image and new orientation flags
+ OBJECT *pAniObj, // object to be updated
+ int newflags, // objects new flags
+ SCNHANDLE hNewImg); // objects new image
+
+void AnimateObject( // give a object a new image
+ OBJECT *pAniObj, // object to animate
+ SCNHANDLE hNewImg); // objects new image
+
+void HideObject( // Hides a object by giving it a "NullImage" image pointer
+ OBJECT *pObj); // object to be hidden
+
+OBJECT *RectangleObject( // create a rectangle object of the given dimensions
+ SCNHANDLE hPal, // palette for the rectangle object
+ int colour, // which colour offset from the above palette
+ int width, // width of rectangle
+ int height); // height of rectangle
+
+OBJECT *TranslucentObject( // create a translucent rectangle object of the given dimensions
+ int width, // width of rectangle
+ int height); // height of rectangle
+
+void ResizeRectangle( // resizes a rectangle object
+ OBJECT *pRect, // rectangle object pointer
+ int width, // new width of rectangle
+ int height); // new height of rectangle
+
+
+// FIXME: This does not belong here
+struct FILM;
+struct FREEL;
+struct MULTI_INIT;
+IMAGE *GetImageFromReel(const FREEL *pfreel, const MULTI_INIT **ppmi = 0);
+IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr = 0,
+ const MULTI_INIT **ppmi = 0, const FILM **ppfilm = 0);
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_OBJECT_H
diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp
new file mode 100644
index 0000000000..50a908afe3
--- /dev/null
+++ b/engines/tinsel/palette.cpp
@@ -0,0 +1,424 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Allocator for IBM PC.
+ */
+
+#include "tinsel/dw.h" // TBLUE1 definition
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h" // LockMem definition
+#include "tinsel/palette.h" // palette allocator structures etc.
+#include "tinsel/tinsel.h"
+
+#include "common/system.h"
+
+namespace Tinsel {
+
+/** background colour */
+static COLORREF bgndColour = BLACK;
+
+/** palette allocator data */
+static PALQ palAllocData[NUM_PALETTES];
+
+
+/** video DAC transfer Q length */
+#define VDACQLENGTH (NUM_PALETTES+2)
+
+/** video DAC transfer Q */
+static VIDEO_DAC_Q vidDACdata[VDACQLENGTH];
+
+/** video DAC transfer Q head pointer */
+static VIDEO_DAC_Q *pDAChead;
+
+/** colour index of the 4 colours used for the translucent palette */
+#define COL_HILIGHT TBLUE1
+
+/** the translucent palette lookup table */
+uint8 transPalette[MAX_COLOURS]; // used in graphics.cpp
+
+#ifdef DEBUG
+// diagnostic palette counters
+static int numPals = 0;
+static int maxPals = 0;
+static int maxDACQ = 0;
+#endif
+
+/**
+ * Transfer palettes in the palette Q to Video DAC.
+ */
+void PalettesToVideoDAC(void) {
+ PPALQ pPalQ; // palette Q iterator
+ PVIDEO_DAC_Q pDACtail = vidDACdata; // set tail pointer
+ bool needUpdate = false;
+
+ // while Q is not empty
+ while (pDAChead != pDACtail) {
+ PALETTE *pPalette; // pointer to hardware palette
+ COLORREF *pColours; // pointer to list of RGB triples
+
+#ifdef DEBUG
+ // make sure palette does not overlap
+ assert(pDACtail->destDACindex + pDACtail->numColours <= MAX_COLOURS);
+#else
+ // make sure palette does not overlap
+ if (pDACtail->destDACindex + pDACtail->numColours > MAX_COLOURS)
+ pDACtail->numColours = MAX_COLOURS - pDACtail->destDACindex;
+#endif
+
+ if (pDACtail->bHandle) {
+ // we are using a palette handle
+
+ // get hardware palette pointer
+ pPalette = (PALETTE *)LockMem(pDACtail->pal.hRGBarray);
+
+ // get RGB pointer
+ pColours = pPalette->palRGB;
+ } else {
+ // we are using a palette pointer
+ pColours = pDACtail->pal.pRGBarray;
+ }
+
+ if (pDACtail->numColours > 0)
+ needUpdate = true;
+
+ // update the system palette
+ g_system->setPalette((byte *)pColours, pDACtail->destDACindex, pDACtail->numColours);
+
+ // update tail pointer
+ pDACtail++;
+
+ }
+
+ // reset video DAC transfer Q head pointer
+ pDAChead = vidDACdata;
+
+ // clear all palette moved bits
+ for (pPalQ = palAllocData; pPalQ < palAllocData + NUM_PALETTES; pPalQ++)
+ pPalQ->posInDAC &= ~PALETTE_MOVED;
+
+ if (needUpdate)
+ _vm->screen().update();
+}
+
+/**
+ * Commpletely reset the palette allocator.
+ */
+void ResetPalAllocator(void) {
+#ifdef DEBUG
+ // clear number of palettes in use
+ numPals = 0;
+#endif
+
+ // wipe out the palette allocator data
+ memset(palAllocData, 0, sizeof(palAllocData));
+
+ // reset video DAC transfer Q head pointer
+ pDAChead = vidDACdata;
+}
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of palettes used at once.
+ */
+void PaletteStats(void) {
+ printf("%i palettes of %i used.\n", maxPals, NUM_PALETTES);
+ printf("%i DAC queue entries of %i used.\n", maxDACQ, VDACQLENGTH);
+}
+#endif
+
+/**
+ * Places a palette in the video DAC queue.
+ * @param posInDAC Position in video DAC
+ * @param numColours Number of colours in palette
+ * @param hPalette Handle to palette
+ */
+void UpdateDACqueueHandle(int posInDAC, int numColours, SCNHANDLE hPalette) {
+ // check Q overflow
+ assert(pDAChead < vidDACdata + VDACQLENGTH);
+
+ pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC
+ pDAChead->numColours = numColours; // set number of colours
+ pDAChead->pal.hRGBarray = hPalette; // set handle of palette
+ pDAChead->bHandle = true; // we are using a palette handle
+
+ // update head pointer
+ ++pDAChead;
+
+#ifdef DEBUG
+ if ((pDAChead-vidDACdata) > maxDACQ)
+ maxDACQ = pDAChead-vidDACdata;
+#endif
+}
+
+/**
+ * Places a palette in the video DAC queue.
+ * @param posInDAC Position in video DAC
+ * @param numColours, Number of colours in palette
+ * @param pColours List of RGB triples
+ */
+void UpdateDACqueue(int posInDAC, int numColours, COLORREF *pColours) {
+ // check Q overflow
+ assert(pDAChead < vidDACdata + NUM_PALETTES);
+
+ pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC
+ pDAChead->numColours = numColours; // set number of colours
+ pDAChead->pal.pRGBarray = pColours; // set addr of palette
+ pDAChead->bHandle = false; // we are not using a palette handle
+
+ // update head pointer
+ ++pDAChead;
+
+#ifdef DEBUG
+ if ((pDAChead-vidDACdata) > maxDACQ)
+ maxDACQ = pDAChead-vidDACdata;
+#endif
+}
+
+/**
+ * Allocate a palette.
+ * @param hNewPal Palette to allocate
+ */
+PPALQ AllocPalette(SCNHANDLE hNewPal) {
+ PPALQ pPrev, p; // walks palAllocData
+ int iDAC; // colour index in video DAC
+ PPALQ pNxtPal; // next PALQ struct in palette allocator
+ PALETTE *pNewPal;
+
+ // get pointer to new palette
+ pNewPal = (PALETTE *)LockMem(hNewPal);
+
+ // search all structs in palette allocator - see if palette already allocated
+ for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) {
+ if (p->hPal == hNewPal) {
+ // found the desired palette in palette allocator
+ p->objCount++; // update number of objects using palette
+ return p; // return palette queue position
+ }
+ }
+
+ // search all structs in palette allocator - find a free slot
+ iDAC = FGND_DAC_INDEX; // init DAC index to first available foreground colour
+
+ for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) {
+ if (p->hPal == 0) {
+ // found a free slot in palette allocator
+ p->objCount = 1; // init number of objects using palette
+ p->posInDAC = iDAC; // set palettes start pos in video DAC
+ p->hPal = hNewPal; // set hardware palette data
+ p->numColours = FROM_LE_32(pNewPal->numColours); // set number of colours in palette
+
+#ifdef DEBUG
+ // one more palette in use
+ if (++numPals > maxPals)
+ maxPals = numPals;
+#endif
+
+ // Q the change to the video DAC
+ UpdateDACqueueHandle(p->posInDAC, p->numColours, p->hPal);
+
+ // move all palettes after this one down (if necessary)
+ for (pPrev = p, pNxtPal = pPrev + 1; pNxtPal < palAllocData + NUM_PALETTES; pNxtPal++) {
+ if (pNxtPal->hPal != 0) {
+ // palette slot is in use
+ if (pNxtPal->posInDAC >= pPrev->posInDAC + pPrev->numColours)
+ // no need to move palettes down
+ break;
+
+ // move palette down - indicate change
+ pNxtPal->posInDAC = pPrev->posInDAC
+ + pPrev->numColours | PALETTE_MOVED;
+
+ // Q the palette change in position to the video DAC
+ UpdateDACqueueHandle(pNxtPal->posInDAC,
+ pNxtPal->numColours,
+ pNxtPal->hPal);
+
+ // update previous palette to current palette
+ pPrev = pNxtPal;
+ }
+ }
+
+ // return palette pointer
+ return p;
+ }
+
+ // set new DAC index
+ iDAC = p->posInDAC + p->numColours;
+ }
+
+ // no free palettes
+ error("AllocPalette(): formally 'assert(0)!'");
+}
+
+/**
+ * Free a palette allocated with "AllocPalette".
+ * @param pFreePal Palette queue entry to free
+ */
+void FreePalette(PPALQ pFreePal) {
+ // validate palette Q pointer
+ assert(pFreePal >= palAllocData && pFreePal <= palAllocData + NUM_PALETTES - 1);
+
+ // reduce the palettes object reference count
+ pFreePal->objCount--;
+
+ // make sure palette has not been deallocated too many times
+ assert(pFreePal->objCount >= 0);
+
+ if (pFreePal->objCount == 0) {
+ pFreePal->hPal = 0; // palette is no longer in use
+
+#ifdef DEBUG
+ // one less palette in use
+ --numPals;
+ assert(numPals >= 0);
+#endif
+ }
+}
+
+/**
+ * Find the specified palette.
+ * @param hSrchPal Hardware palette to search for
+ */
+PPALQ FindPalette(SCNHANDLE hSrchPal) {
+ PPALQ pPal; // palette allocator iterator
+
+ // search all structs in palette allocator
+ for (pPal = palAllocData; pPal < palAllocData + NUM_PALETTES; pPal++) {
+ if (pPal->hPal == hSrchPal)
+ // found palette in palette allocator
+ return pPal;
+ }
+
+ // palette not found
+ return NULL;
+}
+
+/**
+ * Swaps the palettes at the specified palette queue position.
+ * @param pPalQ Palette queue position
+ * @param hNewPal New palette
+ */
+void SwapPalette(PPALQ pPalQ, SCNHANDLE hNewPal) {
+ // convert handle to palette pointer
+ PALETTE *pNewPal = (PALETTE *)LockMem(hNewPal);
+
+ // validate palette Q pointer
+ assert(pPalQ >= palAllocData && pPalQ <= palAllocData + NUM_PALETTES - 1);
+
+ if (pPalQ->numColours >= (int)FROM_LE_32(pNewPal->numColours)) {
+ // new palette will fit the slot
+
+ // install new palette
+ pPalQ->hPal = hNewPal;
+
+ // Q the change to the video DAC
+ UpdateDACqueueHandle(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), hNewPal);
+ } else {
+ // # colours are different - will have to update all following palette entries
+
+ PPALQ pNxtPalQ; // next palette queue position
+
+ for (pNxtPalQ = pPalQ + 1; pNxtPalQ < palAllocData + NUM_PALETTES; pNxtPalQ++) {
+ if (pNxtPalQ->posInDAC >= pPalQ->posInDAC + pPalQ->numColours)
+ // no need to move palettes down
+ break;
+
+ // move palette down
+ pNxtPalQ->posInDAC = pPalQ->posInDAC
+ + pPalQ->numColours | PALETTE_MOVED;
+
+ // Q the palette change in position to the video DAC
+ UpdateDACqueueHandle(pNxtPalQ->posInDAC,
+ pNxtPalQ->numColours,
+ pNxtPalQ->hPal);
+
+ // update previous palette to current palette
+ pPalQ = pNxtPalQ;
+ }
+ }
+}
+
+/**
+ * Statless palette iterator. Returns the next palette in the list
+ * @param pStrtPal Palette to start from - when NULL will start from beginning of list
+ */
+PPALQ GetNextPalette(PPALQ pStrtPal) {
+ if (pStrtPal == NULL) {
+ // start of palette iteration - return 1st palette
+ return (palAllocData[0].objCount) ? palAllocData : NULL;
+ }
+
+ // validate palette Q pointer
+ assert(pStrtPal >= palAllocData && pStrtPal <= palAllocData + NUM_PALETTES - 1);
+
+ // return next active palette in list
+ while (++pStrtPal < palAllocData + NUM_PALETTES) {
+ if (pStrtPal->objCount)
+ // active palette found
+ return pStrtPal;
+ }
+
+ // non found
+ return NULL;
+}
+
+/**
+ * Sets the current background colour.
+ * @param colour Colour to set the background to
+ */
+void SetBgndColour(COLORREF colour) {
+ // update background colour struct
+ bgndColour = colour;
+
+ // Q the change to the video DAC
+ UpdateDACqueue(BGND_DAC_INDEX, 1, &bgndColour);
+}
+
+/**
+ * Builds the translucent palette from the current backgrounds palette.
+ * @param hPalette Handle to current background palette
+ */
+void CreateTranslucentPalette(SCNHANDLE hPalette) {
+ // get a pointer to the palette
+ PALETTE *pPal = (PALETTE *)LockMem(hPalette);
+
+ // leave background colour alone
+ transPalette[0] = 0;
+
+ for (uint i = 0; i < FROM_LE_32(pPal->numColours); i++) {
+ // get the RGB colour model values
+ uint8 red = GetRValue(pPal->palRGB[i]);
+ uint8 green = GetGValue(pPal->palRGB[i]);
+ uint8 blue = GetBValue(pPal->palRGB[i]);
+
+ // calculate the Value field of the HSV colour model
+ unsigned val = (red > green) ? red : green;
+ val = (val > blue) ? val : blue;
+
+ // map the Value field to one of the 4 colours reserved for the translucent palette
+ val /= 63;
+ transPalette[i + 1] = (uint8)((val == 0) ? 0 : val + COL_HILIGHT - 1);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/palette.h b/engines/tinsel/palette.h
new file mode 100644
index 0000000000..ed648950fd
--- /dev/null
+++ b/engines/tinsel/palette.h
@@ -0,0 +1,157 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Allocator Definitions
+ */
+
+#ifndef TINSEL_PALETTE_H // prevent multiple includes
+#define TINSEL_PALETTE_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+typedef uint32 COLORREF;
+
+#define RGB(r,g,b) ((COLORREF)TO_LE_32(((uint8)(r)|((uint16)(g)<<8))|(((uint32)(uint8)(b))<<16)))
+
+#define GetRValue(rgb) ((uint8)(FROM_LE_32(rgb)))
+#define GetGValue(rgb) ((uint8)(((uint16)(FROM_LE_32(rgb))) >> 8))
+#define GetBValue(rgb) ((uint8)((FROM_LE_32(rgb))>>16))
+
+enum {
+ MAX_COLOURS = 256, //!< maximum number of colours - for VGA 256
+ BITS_PER_PIXEL = 8, //!< number of bits per pixel for VGA 256
+ MAX_INTENSITY = 255, //!< the biggest value R, G or B can have
+ NUM_PALETTES = 3, //!< number of palettes
+
+ // Discworld has some fixed apportioned bits in the palette.
+ BGND_DAC_INDEX = 0, //!< index of background colour in Video DAC
+ FGND_DAC_INDEX = 1, //!< index of first foreground colour in Video DAC
+ TBLUE1 = 228, //!< Blue used in translucent rectangles
+ TBLUE2 = 229, //!< Blue used in translucent rectangles
+ TBLUE3 = 230, //!< Blue used in translucent rectangles
+ TBLUE4 = 231, //!< Blue used in translucent rectangles
+ TALKFONT_COL = 233
+};
+
+// some common colours
+
+#define BLACK (RGB(0, 0, 0))
+#define WHITE (RGB(MAX_INTENSITY, MAX_INTENSITY, MAX_INTENSITY))
+#define RED (RGB(MAX_INTENSITY, 0, 0))
+#define GREEN (RGB(0, MAX_INTENSITY, 0))
+#define BLUE (RGB(0, 0, MAX_INTENSITY))
+#define YELLOW (RGB(MAX_INTENSITY, MAX_INTENSITY, 0))
+#define MAGENTA (RGB(MAX_INTENSITY, 0, MAX_INTENSITY))
+#define CYAN (RGB(0, MAX_INTENSITY, MAX_INTENSITY))
+
+
+/** video DAC transfer Q structure */
+struct VIDEO_DAC_Q {
+ union {
+ SCNHANDLE hRGBarray; //!< handle of palette or
+ COLORREF *pRGBarray; //!< list of palette colours
+ } pal;
+ bool bHandle; //!< when set - use handle of palette
+ int destDACindex; //!< start index of palette in video DAC
+ int numColours; //!< number of colours in "hRGBarray"
+};
+typedef VIDEO_DAC_Q *PVIDEO_DAC_Q;
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** hardware palette structure */
+struct PALETTE {
+ int32 numColours; //!< number of colours in the palette
+ COLORREF palRGB[MAX_COLOURS]; //!< actual palette colours
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/** palette queue structure */
+struct PALQ {
+ SCNHANDLE hPal; //!< handle to palette data struct
+ int objCount; //!< number of objects using this palette
+ int posInDAC; //!< palette position in the video DAC
+ int numColours; //!< number of colours in the palette
+};
+typedef PALQ *PPALQ;
+
+
+#define PALETTE_MOVED 0x8000 // when this bit is set in the "posInDAC"
+ // field - the palette entry has moved
+
+// Translucent objects have NULL pPal
+#define HasPalMoved(pPal) (((pPal) != NULL) && ((pPal)->posInDAC & PALETTE_MOVED))
+
+
+/*----------------------------------------------------------------------*\
+|* Palette Manager Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ResetPalAllocator(void); // wipe out all palettes
+
+#ifdef DEBUG
+void PaletteStats(void); // Shows the maximum number of palettes used at once
+#endif
+
+void PalettesToVideoDAC(void); // Update the video DAC with palettes currently the the DAC queue
+
+void UpdateDACqueueHandle(
+ int posInDAC, // position in video DAC
+ int numColours, // number of colours in palette
+ SCNHANDLE hPalette); // handle to palette
+
+void UpdateDACqueue( // places a palette in the video DAC queue
+ int posInDAC, // position in video DAC
+ int numColours, // number of colours in palette
+ COLORREF *pColours); // list of RGB tripples
+
+PPALQ AllocPalette( // allocate a new palette
+ SCNHANDLE hNewPal); // palette to allocate
+
+void FreePalette( // free a palette allocated with "AllocPalette"
+ PPALQ pFreePal); // palette queue entry to free
+
+PPALQ FindPalette( // find a palette in the palette queue
+ SCNHANDLE hSrchPal); // palette to search for
+
+void SwapPalette( // swaps palettes at the specified palette queue position
+ PPALQ pPalQ, // palette queue position
+ SCNHANDLE hNewPal); // new palette
+
+PPALQ GetNextPalette( // returns the next palette in the queue
+ PPALQ pStrtPal); // queue position to start from - when NULL will start from beginning of queue
+
+COLORREF GetBgndColour(void); // returns current background colour
+
+void SetBgndColour( // sets current background colour
+ COLORREF colour); // colour to set the background to
+
+void CreateTranslucentPalette(SCNHANDLE BackPal);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PALETTE_H
diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp
new file mode 100644
index 0000000000..22b7ca8275
--- /dev/null
+++ b/engines/tinsel/pcode.cpp
@@ -0,0 +1,597 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Virtual processor.
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // 'POINTED' etc.
+#include "tinsel/handle.h" // LockMem()
+#include "tinsel/inventory.h" // for inventory id's
+#include "tinsel/pcode.h" // opcodes etc.
+#include "tinsel/scn.h" // FindChunk()
+#include "tinsel/serializer.h"
+#include "tinsel/tinlib.h" // Library routines
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const PINT_CONTEXT pic, RESUME_STATE *pResumeState);
+
+//----------------- LOCAL DEFINES --------------------
+
+/** list of all opcodes */
+enum OPCODE {
+ OP_HALT = 0, //!< end of program
+ OP_IMM = 1, //!< loads signed immediate onto stack
+ OP_ZERO = 2, //!< loads zero onto stack
+ OP_ONE = 3, //!< loads one onto stack
+ OP_MINUSONE = 4, //!< loads minus one onto stack
+ OP_STR = 5, //!< loads string offset onto stack
+ OP_FILM = 6, //!< loads film offset onto stack
+ OP_FONT = 7, //!< loads font offset onto stack
+ OP_PAL = 8, //!< loads palette offset onto stack
+ OP_LOAD = 9, //!< loads local variable onto stack
+ OP_GLOAD = 10, //!< loads global variable onto stack - long offset to variable
+ OP_STORE = 11, //!< pops stack and stores in local variable - long offset to variable
+ OP_GSTORE = 12, //!< pops stack and stores in global variable - long offset to variable
+ OP_CALL = 13, //!< procedure call
+ OP_LIBCALL = 14, //!< library procedure call - long offset to procedure
+ OP_RET = 15, //!< procedure return
+ OP_ALLOC = 16, //!< allocate storage on stack
+ OP_JUMP = 17, //!< unconditional jump - signed word offset
+ OP_JMPFALSE = 18, //!< conditional jump - signed word offset
+ OP_JMPTRUE = 19, //!< conditional jump - signed word offset
+ OP_EQUAL = 20, //!< tests top two items on stack for equality
+ OP_LESS, //!< tests top two items on stack
+ OP_LEQUAL, //!< tests top two items on stack
+ OP_NEQUAL, //!< tests top two items on stack
+ OP_GEQUAL, //!< tests top two items on stack
+ OP_GREAT = 25, //!< tests top two items on stack
+ OP_PLUS, //!< adds top two items on stack and replaces with result
+ OP_MINUS, //!< subs top two items on stack and replaces with result
+ OP_LOR, //!< logical or of top two items on stack and replaces with result
+ OP_MULT, //!< multiplies top two items on stack and replaces with result
+ OP_DIV = 30, //!< divides top two items on stack and replaces with result
+ OP_MOD, //!< divides top two items on stack and replaces with modulus
+ OP_AND, //!< bitwise ands top two items on stack and replaces with result
+ OP_OR, //!< bitwise ors top two items on stack and replaces with result
+ OP_EOR, //!< bitwise exclusive ors top two items on stack and replaces with result
+ OP_LAND = 35, //!< logical ands top two items on stack and replaces with result
+ OP_NOT, //!< logical nots top item on stack
+ OP_COMP, //!< complements top item on stack
+ OP_NEG, //!< negates top item on stack
+ OP_DUP, //!< duplicates top item on stack
+ OP_ESCON = 40, //!< start of escapable sequence
+ OP_ESCOFF = 41, //!< end of escapable sequence
+ OP_CIMM, //!< loads signed immediate onto stack (special to case statements)
+ OP_CDFILM //!< loads film offset onto stack but not in current scene
+};
+
+// modifiers for the above opcodes
+#define OPSIZE8 0x40 //!< when this bit is set - the operand size is 8 bits
+#define OPSIZE16 0x80 //!< when this bit is set - the operand size is 16 bits
+
+#define OPMASK 0x3F //!< mask to isolate the opcode
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int32 *pGlobals = 0; // global vars
+
+static int numGlobals = 0; // How many global variables to save/restore
+
+static PINT_CONTEXT icList = 0;
+
+/**
+ * Keeps the code array pointer up to date.
+ */
+void LockCode(PINT_CONTEXT ic) {
+ if (ic->GSort == GS_MASTER)
+ ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE);
+ else
+ ic->code = (byte *)LockMem(ic->hCode);
+}
+
+/**
+ * Find a free interpret context and allocate it to the calling process.
+ */
+static PINT_CONTEXT AllocateInterpretContext(GSORT gsort) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_NONE) {
+ pic->pProc = CurrentProcess();
+ pic->GSort = gsort;
+ return pic;
+ }
+#ifdef DEBUG
+ else {
+ if (pic->pProc == CurrentProcess())
+ error("Found unreleased interpret context");
+ }
+#endif
+ }
+
+ error("Out of interpret contexts");
+}
+
+/**
+ * Normal release of an interpret context.
+ * Called from the end of Interpret().
+ */
+static void FreeInterpretContextPi(PINT_CONTEXT pic) {
+ pic->GSort = GS_NONE;
+}
+
+/**
+ * Free interpret context owned by a dying process.
+ * Ensures that interpret contexts don't get lost when an Interpret()
+ * call doesn't complete.
+ */
+void FreeInterpretContextPr(PROCESS *pProc) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_NONE && pic->pProc == pProc) {
+ pic->GSort = GS_NONE;
+ break;
+ }
+ }
+}
+
+/**
+ * Free all interpret contexts except for the master script's
+ */
+void FreeMostInterpretContexts(void) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_MASTER) {
+ pic->GSort = GS_NONE;
+ }
+ }
+}
+
+/**
+ * Free the master script's interpret context.
+ */
+void FreeMasterInterpretContext(void) {
+ PINT_CONTEXT pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_MASTER) {
+ pic->GSort = GS_NONE;
+ return;
+ }
+ }
+}
+
+/**
+ * Allocate and initialise an interpret context.
+ * Called from a process prior to Interpret().
+ * @param gsort which sort of code
+ * @param hCode Handle to code to execute
+ * @param event Causal event
+ * @param hpoly Associated polygon (if any)
+ * @param actorId Associated actor (if any)
+ * @param pinvo Associated inventory object
+ */
+PINT_CONTEXT InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event,
+ HPOLYGON hpoly, int actorid, PINV_OBJECT pinvo) {
+ PINT_CONTEXT ic;
+
+ ic = AllocateInterpretContext(gsort);
+
+ // Previously parameters to Interpret()
+ ic->hCode = hCode;
+ LockCode(ic);
+ ic->event = event;
+ ic->hpoly = hpoly;
+ ic->actorid = actorid;
+ ic->pinvo = pinvo;
+
+ // Previously local variables in Interpret()
+ ic->bHalt = false; // set to exit interpeter
+ ic->escOn = false;
+ ic->myescEvent = 0; // only initialised to prevent compiler warning!
+ ic->sp = 0;
+ ic->bp = ic->sp + 1;
+ ic->ip = 0; // start of code
+
+ ic->resumeState = RES_NOT;
+
+ return ic;
+}
+
+/**
+ * Allocate and initialise an interpret context with restored data.
+ */
+PINT_CONTEXT RestoreInterpretContext(PINT_CONTEXT ric) {
+ PINT_CONTEXT ic;
+
+ ic = AllocateInterpretContext(GS_NONE); // Sort will soon be overridden
+
+ memcpy(ic, ric, sizeof(INT_CONTEXT));
+ ic->pProc = CurrentProcess();
+ ic->resumeState = RES_1;
+
+ LockCode(ic);
+
+ return ic;
+}
+
+/**
+ * Allocates enough RAM to hold the global Glitter variables.
+ */
+void RegisterGlobals(int num) {
+ if (pGlobals == NULL) {
+ numGlobals = num;
+
+ // Allocate RAM for pGlobals and make sure it's allocated
+ pGlobals = (int32 *)calloc(numGlobals, sizeof(int32));
+ if (pGlobals == NULL) {
+ error("Cannot allocate memory for global data");
+ }
+
+ // Allocate RAM for interpret contexts and make sure it's allocated
+ icList = (PINT_CONTEXT)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT));
+ if (icList == NULL) {
+ error("Cannot allocate memory for interpret contexts");
+ }
+
+ SetResourceCallback(FreeInterpretContextPr);
+ } else {
+ // Check size is still the same
+ assert(numGlobals == num);
+
+ memset(pGlobals, 0, numGlobals * sizeof(int32));
+ memset(icList, 0, MAX_INTERPRET * sizeof(INT_CONTEXT));
+ }
+}
+
+void FreeGlobals(void) {
+ if (pGlobals) {
+ free(pGlobals);
+ pGlobals = NULL;
+ }
+
+ if (icList) {
+ free(icList);
+ icList = NULL;
+ }
+}
+
+/**
+ * (Un)serialize the global data for save/restore game.
+ */
+void syncGlobInfo(Serializer &s) {
+ for (int i = 0; i < numGlobals; i++) {
+ s.syncAsSint32LE(pGlobals[i]);
+ }
+}
+
+/**
+ * (Un)serialize an interpreter context for save/restore game.
+ */
+void INT_CONTEXT::syncWithSerializer(Serializer &s) {
+ if (s.isLoading()) {
+ // Null out the pointer fields
+ pProc = NULL;
+ code = NULL;
+ pinvo = NULL;
+ }
+ // Write out used fields
+ s.syncAsUint32LE(GSort);
+ s.syncAsUint32LE(hCode);
+ s.syncAsUint32LE(event);
+ s.syncAsSint32LE(hpoly);
+ s.syncAsSint32LE(actorid);
+
+ for (int i = 0; i < PCODE_STACK_SIZE; ++i)
+ s.syncAsSint32LE(stack[i]);
+
+ s.syncAsSint32LE(sp);
+ s.syncAsSint32LE(bp);
+ s.syncAsSint32LE(ip);
+ s.syncAsUint32LE(bHalt);
+ s.syncAsUint32LE(escOn);
+ s.syncAsSint32LE(myescEvent);
+}
+
+/**
+ * Return pointer to and size of global data for save/restore game.
+ */
+void SaveInterpretContexts(PINT_CONTEXT sICInfo) {
+ memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT));
+}
+
+/**
+ * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code
+ * stream and advance the instruction pointer accordingly.
+ */
+static int32 Fetch(byte opcode, byte *code, int &ip) {
+ int32 tmp;
+ if (opcode & OPSIZE8) {
+ // Fetch and sign extend a 8 bit value to 32 bits.
+ tmp = *(int8 *)(code + ip);
+ ip += 1;
+ } else if (opcode & OPSIZE16) {
+ // Fetch and sign extend a 16 bit value to 32 bits.
+ tmp = (int16)READ_LE_UINT16(code + ip);
+ ip += 2;
+ } else {
+ // Fetch a 32 bit value.
+ tmp = (int32)READ_LE_UINT32(code + ip);
+ ip += 4;
+ }
+ return tmp;
+}
+
+/**
+ * Interprets the PCODE instructions in the code array.
+ */
+void Interpret(CORO_PARAM, PINT_CONTEXT ic) {
+ do {
+ int tmp, tmp2;
+ int ip = ic->ip;
+ byte opcode = ic->code[ip++];
+ debug(7, " Opcode %d (-> %d)", opcode, opcode & OPMASK);
+ switch (opcode & OPMASK) {
+ case OP_HALT: // end of program
+
+ ic->bHalt = true;
+ break;
+
+ case OP_IMM: // loads immediate data onto stack
+ case OP_STR: // loads string handle onto stack
+ case OP_FILM: // loads film handle onto stack
+ case OP_CDFILM: // loads film handle onto stack
+ case OP_FONT: // loads font handle onto stack
+ case OP_PAL: // loads palette handle onto stack
+
+ ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_ZERO: // loads zero onto stack
+ ic->stack[++ic->sp] = 0;
+ break;
+
+ case OP_ONE: // loads one onto stack
+ ic->stack[++ic->sp] = 1;
+ break;
+
+ case OP_MINUSONE: // loads minus one onto stack
+ ic->stack[++ic->sp] = -1;
+ break;
+
+ case OP_LOAD: // loads local variable onto stack
+
+ ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)];
+ break;
+
+ case OP_GLOAD: // loads global variable onto stack
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ ic->stack[++ic->sp] = pGlobals[tmp];
+ break;
+
+ case OP_STORE: // pops stack and stores in local variable
+
+ ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--];
+ break;
+
+ case OP_GSTORE: // pops stack and stores in global variable
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ pGlobals[tmp] = ic->stack[ic->sp--];
+ break;
+
+ case OP_CALL: // procedure call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ //assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds
+ ic->stack[ic->sp + 1] = 0; // static link
+ ic->stack[ic->sp + 2] = ic->bp; // dynamic link
+ ic->stack[ic->sp + 3] = ip; // return address
+ ic->bp = ic->sp + 1; // set new base pointer
+ ip = tmp; // set ip to procedure address
+ break;
+
+ case OP_LIBCALL: // library procedure or function call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ // NOTE: Interpret() itself is not using the coroutine facilities,
+ // but still accepts a CORO_PARAM, so from the outside it looks
+ // like a coroutine. In fact it may still acts as a kind of "proxy"
+ // for some underlying coroutine. To enable this, we just pass on
+ // 'coroParam' to CallLibraryRoutine(). If we then detect that
+ // coroParam was set to a non-zero value, this means that some
+ // coroutine code did run at some point, and we are now supposed
+ // to sleep or die -- hence, we 'return' if coroParam != 0.
+ //
+ // This works because Interpret() is fully re-entrant: If we return
+ // now and are later called again, then we will end up in the very
+ // same spot (i.e. here).
+ //
+ // The reasons we do it this way, instead of turning Interpret into
+ // a 'proper' coroutine are (1) we avoid implementation problems
+ // (CORO_INVOKE involves adding 'case' statements, but Interpret
+ // already has a huge switch/case, so that would not work out of the
+ // box), (2) we incurr less overhead, (3) it's easier to debug,
+ // (4) it's simply cool ;).
+ tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState);
+ if (coroParam)
+ return;
+ ic->sp += tmp2;
+ LockCode(ic);
+ break;
+
+ case OP_RET: // procedure return
+
+ ic->sp = ic->bp - 1; // restore stack
+ ip = ic->stack[ic->sp + 3]; // return address
+ ic->bp = ic->stack[ic->sp + 2]; // restore previous base pointer
+ break;
+
+ case OP_ALLOC: // allocate storage on stack
+
+ ic->sp += Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JUMP: // unconditional jump
+
+ ip = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JMPFALSE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] == 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_JMPTRUE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] != 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_EQUAL: // tests top two items on stack for equality
+ case OP_LESS: // tests top two items on stack
+ case OP_LEQUAL: // tests top two items on stack
+ case OP_NEQUAL: // tests top two items on stack
+ case OP_GEQUAL: // tests top two items on stack
+ case OP_GREAT: // tests top two items on stack
+ case OP_LOR: // logical or of top two items on stack and replaces with result
+ case OP_LAND: // logical ands top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_EQUAL: tmp = (tmp == tmp2); break;
+ case OP_LESS: tmp = (tmp < tmp2); break;
+ case OP_LEQUAL: tmp = (tmp <= tmp2); break;
+ case OP_NEQUAL: tmp = (tmp != tmp2); break;
+ case OP_GEQUAL: tmp = (tmp >= tmp2); break;
+ case OP_GREAT: tmp = (tmp > tmp2); break;
+
+ case OP_LOR: tmp = (tmp || tmp2); break;
+ case OP_LAND: tmp = (tmp && tmp2); break;
+ }
+
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_PLUS: // adds top two items on stack and replaces with result
+ case OP_MINUS: // subs top two items on stack and replaces with result
+ case OP_MULT: // multiplies top two items on stack and replaces with result
+ case OP_DIV: // divides top two items on stack and replaces with result
+ case OP_MOD: // divides top two items on stack and replaces with modulus
+ case OP_AND: // bitwise ands top two items on stack and replaces with result
+ case OP_OR: // bitwise ors top two items on stack and replaces with result
+ case OP_EOR: // bitwise exclusive ors top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_PLUS: tmp += tmp2; break;
+ case OP_MINUS: tmp -= tmp2; break;
+ case OP_MULT: tmp *= tmp2; break;
+ case OP_DIV: tmp /= tmp2; break;
+ case OP_MOD: tmp %= tmp2; break;
+ case OP_AND: tmp &= tmp2; break;
+ case OP_OR: tmp |= tmp2; break;
+ case OP_EOR: tmp ^= tmp2; break;
+ }
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_NOT: // logical nots top item on stack
+
+ ic->stack[ic->sp] = !ic->stack[ic->sp];
+ break;
+
+ case OP_COMP: // complements top item on stack
+ ic->stack[ic->sp] = ~ic->stack[ic->sp];
+ break;
+
+ case OP_NEG: // negates top item on stack
+ ic->stack[ic->sp] = -ic->stack[ic->sp];
+ break;
+
+ case OP_DUP: // duplicates top item on stack
+ ic->stack[ic->sp + 1] = ic->stack[ic->sp];
+ ic->sp++;
+ break;
+
+ case OP_ESCON:
+ ic->escOn = true;
+ ic->myescEvent = GetEscEvents();
+ break;
+
+ case OP_ESCOFF:
+ ic->escOn = false;
+ break;
+
+ default:
+ error("Interpret() - Unknown opcode");
+ }
+
+ // check for stack under-overflow
+ assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE);
+ ic->ip = ip;
+ } while (!ic->bHalt);
+
+ // make sure stack is unwound
+ assert(ic->sp == 0);
+
+ FreeInterpretContextPi(ic);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h
new file mode 100644
index 0000000000..1fc87e6e50
--- /dev/null
+++ b/engines/tinsel/pcode.h
@@ -0,0 +1,158 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Virtual processor definitions
+ */
+
+#ifndef TINSEL_PCODE_H // prevent multiple includes
+#define TINSEL_PCODE_H
+
+#include "tinsel/events.h" // for USER_EVENT
+//#include "tinsel/inventory.h" // for PINV_OBJECT
+#include "tinsel/polygons.h" // for PPOLYGON
+#include "tinsel/sched.h" // for PROCESS
+
+namespace Tinsel {
+
+// forward declaration
+class Serializer;
+struct INV_OBJECT;
+
+enum RESUME_STATE {
+ RES_NOT, RES_1, RES_2
+};
+
+enum {
+ PCODE_STACK_SIZE = 128 //!< interpeters stack size
+};
+
+enum GSORT {
+ GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE
+};
+
+struct INT_CONTEXT {
+
+ // Elements for interpret context management
+ PROCESS *pProc; //!< processes owning this context
+ GSORT GSort; //!< sort of this context
+
+ // Previously parameters to Interpret()
+ SCNHANDLE hCode; //!< scene handle of the code to execute
+ byte *code; //!< pointer to the code to execute
+ USER_EVENT event; //!< causal event
+ HPOLYGON hpoly; //!< associated polygon (if any)
+ int actorid; //!< associated actor (if any)
+ INV_OBJECT *pinvo; //!< associated inventory object
+
+ // Previously local variables in Interpret()
+ int32 stack[PCODE_STACK_SIZE]; //!< interpeters run time stack
+ int sp; //!< stack pointer
+ int bp; //!< base pointer
+ int ip; //!< instruction pointer
+ bool bHalt; //!< set to exit interpeter
+ bool escOn;
+ int myescEvent; //!< only initialised to prevent compiler warning!
+
+ RESUME_STATE resumeState;
+
+ void syncWithSerializer(Serializer &s);
+};
+typedef INT_CONTEXT *PINT_CONTEXT;
+
+
+/*----------------------------------------------------------------------*\
+|* Interpreter Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void Interpret(CORO_PARAM, INT_CONTEXT *ic); // Interprets the PCODE instructions in the code array
+
+PINT_CONTEXT InitInterpretContext(
+ GSORT gsort,
+ SCNHANDLE hCode, // code to execute
+ USER_EVENT event, // causal event
+ HPOLYGON hpoly, // associated polygon (if any)
+ int actorid, // associated actor (if any)
+ INV_OBJECT *pinvo); // associated inventory object
+
+INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric);
+
+void FreeMostInterpretContexts(void);
+void FreeMasterInterpretContext(void);
+
+void SaveInterpretContexts(INT_CONTEXT *sICInfo);
+
+void RegisterGlobals(int num);
+void FreeGlobals(void);
+
+
+#define MAX_INTERPRET (NUM_PROCESS - 20)
+
+/*----------------------------------------------------------------------*\
+|* Library Procedure and Function codes parameter enums *|
+\*----------------------------------------------------------------------*/
+
+#define TAG_DEF 0 // For tagactor()
+#define TAG_Q1TO3 1 // tag types
+#define TAG_Q1TO4 2 // tag types
+
+#define CONV_DEF 0 //
+#define CONV_BOTTOM 1 // conversation() parameter
+#define CONV_END 2 //
+
+#define CONTROL_OFF 0 // control()
+#define CONTROL_ON 1 // parameter
+#define CONTROL_OFFV 2 //
+#define CONTROL_OFFV2 3 //
+#define CONTROL_STARTOFF 4 //
+
+#define NULL_ACTOR (-1) // For actor parameters
+#define LEAD_ACTOR (-2) //
+
+#define RAND_NORM 0 // For random() frills
+#define RAND_NORPT 1 //
+
+#define D_UP 1
+#define D_DOWN 0
+
+#define TW_START 1 // topwindow() parameter
+#define TW_END 2 //
+
+#define MIDI_DEF 0
+#define MIDI_LOOP 1
+
+#define TRANS_DEF 0
+#define TRANS_CUT 1
+#define TRANS_FADE 2
+
+#define FM_IN 0 //
+#define FM_OUT 1 // fademidi()
+
+#define FG_ON 0 //
+#define FG_OFF 1 // FrameGrab()
+
+#define ST_ON 0 //
+#define ST_OFF 1 // SubTitles()
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PCODE_H
diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp
new file mode 100644
index 0000000000..a51f13e62e
--- /dev/null
+++ b/engines/tinsel/pdisplay.cpp
@@ -0,0 +1,649 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * CursorPositionProcess()
+ * TagProcess()
+ * PointProcess()
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+//extern int Overrun; // The overrun counter, in DOS_DW.C
+
+extern int newestString; // The overrun counter, in STRRES.C
+#endif
+
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define LPOSX 295 // X-co-ord of lead actor's position display
+#define CPOSX 24 // X-co-ord of cursor's position display
+#define OPOSX SCRN_CENTRE_X // X-co-ord of overrun counter's display
+#define SPOSX SCRN_CENTRE_X // X-co-ord of string numbner's display
+
+#define POSY 0 // Y-co-ord of these position displays
+
+#define ACTOR_TAG 0xffffffff
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static bool DispPath = false;
+static bool bShowString = false;
+
+static int TaggedActor = 0;
+static HPOLYGON hTaggedPolygon = NOPOLY;
+
+static enum { TAGS_OFF, TAGS_ON } TagsActive = TAGS_ON;
+
+
+#ifdef DEBUG
+/**
+ * Displays the cursor and lead actor's co-ordinates and the overrun
+ * counter. Also which path polygon the cursor is in, if required.
+ *
+ * This process is only started up if a Glitter showpos() call is made.
+ * Obviously, this is for testing purposes only...
+ */
+void CursorPositionProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ int prevsX, prevsY; // Last screen top left
+ int prevcX, prevcY; // Last displayed cursor position
+ int prevlX, prevlY; // Last displayed lead actor position
+// int prevOver; // Last displayed overrun
+ int prevString; // Last displayed string number
+
+ OBJECT *cpText; // cursor position text object pointer
+ OBJECT *cpathText; // cursor path text object pointer
+ OBJECT *rpText; // text object pointer
+// OBJECT *opText; // text object pointer
+ OBJECT *spText; // string number text object pointer
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->prevsX = -1;
+ _ctx->prevsY = -1;
+ _ctx->prevcX = -1;
+ _ctx->prevcY = -1;
+ _ctx->prevlX = -1;
+ _ctx->prevlY = -1;
+// _ctx->prevOver = -1;
+ _ctx->prevString = -1;
+
+ _ctx->cpText = NULL;
+ _ctx->cpathText = NULL;
+ _ctx->rpText = NULL;
+// _ctx->opText = NULL;
+ _ctx->spText = NULL;
+
+
+ int aniX, aniY; // cursor/lead actor position
+ int Loffset, Toffset; // Screen top left
+
+ char PositionString[64]; // sprintf() things into here
+
+ PMACTOR pActor; // Lead actor
+
+ while (1) {
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*-----------------------------------*\
+ | Cursor's position and path display. |
+ \*-----------------------------------*/
+ GetCursorXY(&aniX, &aniY, false);
+
+ // Change in cursor position?
+ if (aniX != _ctx->prevcX || aniY != _ctx->prevcY ||
+ Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
+ // kill current text objects
+ if (_ctx->cpText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpText);
+ }
+ if (_ctx->cpathText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpathText);
+ _ctx->cpathText = NULL;
+ }
+
+ // New text objects
+ sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset);
+ _ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, CPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+ if (DispPath) {
+ HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH);
+ if (hp == NOPOLY)
+ sprintf(PositionString, "No path");
+ else
+ sprintf(PositionString, "%d,%d %d,%d %d,%d %d,%d",
+ PolyCornerX(hp, 0), PolyCornerY(hp, 0),
+ PolyCornerX(hp, 1), PolyCornerY(hp, 1),
+ PolyCornerX(hp, 2), PolyCornerY(hp, 2),
+ PolyCornerX(hp, 3), PolyCornerY(hp, 3));
+ _ctx->cpathText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, 4, POSY+ 10, hTagFontHandle(), 0);
+ }
+
+ // update previous position
+ _ctx->prevcX = aniX;
+ _ctx->prevcY = aniY;
+ }
+
+#if 0
+ /*------------------------*\
+ | Overrun counter display. |
+ \*------------------------*/
+ if (Overrun != _ctx->prevOver) {
+ // kill current text objects
+ if (_ctx->opText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->opText);
+ }
+
+ sprintf(PositionString, "%d", Overrun);
+ _ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, OPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+
+ // update previous value
+ _ctx->prevOver = Overrun;
+ }
+#endif
+
+ /*----------------------*\
+ | Lead actor's position. |
+ \*----------------------*/
+ pActor = GetMover(LEAD_ACTOR);
+ if (pActor && pActor->MActorState == NORM_MACTOR) {
+ // get lead's animation position
+ GetActorPos(LEAD_ACTOR, &aniX, &aniY);
+
+ // Change in position?
+ if (aniX != _ctx->prevlX || aniY != _ctx->prevlY ||
+ Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
+ // Kill current text objects
+ if (_ctx->rpText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->rpText);
+ }
+
+ // create new text object list
+ sprintf(PositionString, "%d %d", aniX, aniY);
+ _ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, LPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+
+ // update previous position
+ _ctx->prevlX = aniX;
+ _ctx->prevlY = aniY;
+ }
+ }
+
+ /*-------------*\
+ | String number |
+ \*-------------*/
+ if (bShowString && newestString != _ctx->prevString) {
+ // kill current text objects
+ if (_ctx->spText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->spText);
+ }
+
+ sprintf(PositionString, "String: %d", newestString);
+ _ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, SPOSX, POSY+10, hTalkFontHandle(), TXT_CENTRE);
+
+ // update previous value
+ _ctx->prevString = newestString;
+ }
+
+ // update previous playfield position
+ _ctx->prevsX = Loffset;
+ _ctx->prevsY = Toffset;
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+ CORO_END_CODE;
+}
+#endif
+
+/**
+ * Tag process keeps us updated as to which tagged actor is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+static void SaveTaggedActor(int ano) {
+ TaggedActor = ano;
+}
+
+/**
+ * Tag process keeps us updated as to which tagged actor is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+int GetTaggedActor(void) {
+ return TaggedActor;
+}
+
+/**
+ * Tag process keeps us updated as to which polygon is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+static void SaveTaggedPoly(HPOLYGON hp) {
+ hTaggedPolygon = hp;
+}
+
+HPOLYGON GetTaggedPoly(void) {
+ return hTaggedPolygon;
+}
+
+/**
+ * Given cursor position and an actor number, ascertains whether the
+ * cursor is within the actor's tag area.
+ * Returns TRUE for a positive result, FALSE for negative.
+ * If TRUE, the mid-top co-ordinates of the actor's tag area are also
+ * returned.
+ */
+static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) {
+ int Top, Bot; // Top and bottom limits of active area
+ int left, right; // left and right of active area
+ int qrt = 0; // 1/4 of height (sometimes 1/2)
+
+ // First check if within x-range
+ if (aniX > (left = GetActorLeft(ano)) && aniX < (right = GetActorRight(ano))) {
+ Top = GetActorTop(ano);
+ Bot = GetActorBottom(ano);
+
+ // y-range varies according to tag-type
+ switch (TagType(ano)) {
+ case TAG_DEF:
+ // Next to bottom 1/4 of the actor's area
+ qrt = (Bot - Top) >> 1; // Half actor's height
+ Top += qrt; // Top = mid-height
+
+ qrt = qrt >> 1; // Quarter height
+ Bot -= qrt; // Bot = 1/4 way up
+ break;
+
+ case TAG_Q1TO3:
+ // Top 3/4 of the actor's area
+ qrt = (Bot - Top) >> 2; // 1/4 actor's height
+ Bot -= qrt; // Bot = 1/4 way up
+ break;
+
+ case TAG_Q1TO4:
+ // All the actor's area
+ break;
+
+ default:
+ error("illegal tag area type");
+ }
+
+ // Now check if within y-range
+ if (aniY >= Top && aniY <= Bot) {
+ if (TagType(ano) == TAG_Q1TO3)
+ *pytext = Top + qrt;
+ else
+ *pytext = Top;
+ *pxtext = (left + right) / 2;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * See if the cursor is over a tagged actor's hot-spot. If so, display
+ * the tag or, if tag already displayed, maintain the tag's position on
+ * the screen.
+ */
+static bool ActorTag(int curX, int curY, SCNHANDLE *pTag, OBJECT **ppText) {
+ static int Loffset = 0, Toffset = 0; // Values when tag was displayed
+ int nLoff, nToff; // new values, to keep tag in place
+ int ano;
+ int xtext, ytext;
+ bool newActor;
+
+ // For each actor with a tag....
+ FirstTaggedActor();
+ while ((ano = NextTaggedActor()) != 0) {
+ if (InHotSpot(ano, curX, curY, &xtext, &ytext)) {
+ // Put up or maintain actor tag
+ if (*pTag != ACTOR_TAG)
+ newActor = true;
+ else if (ano != GetTaggedActor())
+ newActor = true; // Different actor
+ else
+ newActor = false; // Same actor
+
+ if (newActor) {
+ // Display actor's tag
+
+ if (*ppText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+
+ *pTag = ACTOR_TAG;
+ SaveTaggedActor(ano); // This actor tagged
+ SaveTaggedPoly(NOPOLY); // No tagged polygon
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ LoadStringRes(GetActorTag(ano), tBufferAddr(), TBUFSZ);
+ *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, xtext - Loffset, ytext - Toffset, hTagFontHandle(), TXT_CENTRE);
+ assert(*ppText); // Actor tag string produced NULL text
+ MultiSetZPosition(*ppText, Z_TAG_TEXT);
+ } else {
+ // Maintain actor tag's position
+
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != Loffset || nToff != Toffset) {
+ MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
+ Loffset = nLoff;
+ Toffset = nToff;
+ }
+ }
+ return true;
+ }
+ }
+
+ // No tagged actor
+ if (*pTag == ACTOR_TAG) {
+ *pTag = 0;
+ SaveTaggedActor(0);
+ }
+ return false;
+}
+
+/**
+ * Perhaps some comment in due course.
+ *
+ * Under control of PointProcess(), when the cursor is over a TAG or
+ * EXIT polygon, its pointState flag is set to POINTING. If its Glitter
+ * code contains a printtag() call, its tagState flag gets set to TAG_ON.
+ */
+static bool PolyTag(SCNHANDLE *pTag, OBJECT **ppText) {
+ static int Loffset = 0, Toffset = 0; // Values when tag was displayed
+ int nLoff, nToff; // new values, to keep tag in place
+ HPOLYGON hp;
+ bool newPoly;
+ int shift;
+
+ int tagx, tagy; // Tag display co-ordinates
+ SCNHANDLE hTagtext; // Tag text
+
+ // For each polgon with a tag....
+ for (int i = 0; i < MAX_POLY; i++) {
+ hp = GetPolyHandle(i);
+
+ // Added code for un-tagged tags
+ if (hp != NOPOLY && PolyPointState(hp) == POINTING && PolyTagState(hp) != TAG_ON) {
+ // This poly is entitled to be tagged
+ if (hp != GetTaggedPoly()) {
+ if (*ppText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+ *ppText = NULL;
+ }
+ *pTag = POLY_TAG;
+ SaveTaggedActor(0); // No tagged actor
+ SaveTaggedPoly(hp); // This polygon tagged
+ }
+ return true;
+ } else if (hp != NOPOLY && PolyTagState(hp) == TAG_ON) {
+ // Put up or maintain polygon tag
+ if (*pTag != POLY_TAG)
+ newPoly = true; // A new polygon (no current)
+ else if (hp != GetTaggedPoly())
+ newPoly = true; // Different polygon
+ else
+ newPoly = false; // Same polygon
+
+ if (newPoly) {
+ if (*ppText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+
+ *pTag = POLY_TAG;
+ SaveTaggedActor(0); // No tagged actor
+ SaveTaggedPoly(hp); // This polygon tagged
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ getPolyTagInfo(hp, &hTagtext, &tagx, &tagy);
+
+ int strLen;
+ if (PolyTagHandle(hp) != 0)
+ strLen = LoadStringRes(PolyTagHandle(hp), tBufferAddr(), TBUFSZ);
+ else
+ strLen = LoadStringRes(hTagtext, tBufferAddr(), TBUFSZ);
+
+ if (strLen == 0)
+ // No valid string returned, so leave ppText as NULL
+ ppText = NULL;
+ else {
+ // Handle displaying the tag text on-screen
+ *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, tagx - Loffset, tagy - Toffset,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*ppText); // Polygon tag string produced NULL text
+ MultiSetZPosition(*ppText, Z_TAG_TEXT);
+
+
+ /*
+ * New feature: Don't go off the side of the background
+ */
+ shift = MultiRightmost(*ppText) + Loffset + 2;
+ if (shift >= BackgroundWidth()) // Not off right
+ MultiMoveRelXY(*ppText, BackgroundWidth() - shift, 0);
+ shift = MultiLeftmost(*ppText) + Loffset - 1;
+ if (shift <= 0) // Not off left
+ MultiMoveRelXY(*ppText, -shift, 0);
+ shift = MultiLowest(*ppText) + Toffset;
+ if (shift > BackgroundHeight()) // Not off bottom
+ MultiMoveRelXY(*ppText, 0, BackgroundHeight() - shift);
+ }
+ } else {
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != Loffset || nToff != Toffset) {
+ MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
+ Loffset = nLoff;
+ Toffset = nToff;
+ }
+ }
+ return true;
+ }
+ }
+
+ // No tagged polygon
+ if (*pTag == POLY_TAG) {
+ *pTag = 0;
+ SaveTaggedPoly(NOPOLY);
+ }
+ return false;
+}
+
+/**
+ * Handle display of tagged actor and polygon tags.
+ * Tagged actor's get priority over polygons.
+ */
+void TagProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ SCNHANDLE Tag;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pText = NULL;
+ _ctx->Tag = 0;
+
+ SaveTaggedActor(0); // No tagged actor yet
+ SaveTaggedPoly(NOPOLY); // No tagged polygon yet
+
+ while (1) {
+ if (TagsActive == TAGS_ON) {
+ int curX, curY; // cursor position
+ while (!GetCursorXYNoWait(&curX, &curY, true))
+ CORO_SLEEP(1);
+
+ if (!ActorTag(curX, curY, &_ctx->Tag, &_ctx->pText)
+ && !PolyTag(&_ctx->Tag, &_ctx->pText)) {
+ // Nothing tagged. Remove tag, if there is one
+ if (_ctx->pText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _ctx->pText = NULL;
+ }
+ }
+ } else {
+ SaveTaggedActor(0);
+ SaveTaggedPoly(NOPOLY);
+
+ // Remove tag, if there is one
+ if (_ctx->pText) {
+ // kill current text objects
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _ctx->pText = NULL;
+ _ctx->Tag = 0;
+ }
+ }
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Called from PointProcess() as appropriate.
+ */
+static void enteringpoly(HPOLYGON hp) {
+ SetPolyPointState(hp, POINTING);
+
+ RunPolyTinselCode(hp, POINTED, BE_NONE, false);
+}
+
+/**
+ * Called from PointProcess() as appropriate.
+ */
+static void leavingpoly(HPOLYGON hp) {
+ SetPolyPointState(hp, NOT_POINTING);
+
+ if (PolyTagState(hp) == TAG_ON) {
+ // Delete this tag entry
+ SetPolyTagState(hp, TAG_OFF);
+ }
+}
+
+/**
+ * For TAG and EXIT polygons, monitor cursor entering and leaving.
+ * Maintain the polygons' pointState and tagState flags accordingly.
+ * Also run the polygon's Glitter code when the cursor enters.
+ */
+void PointProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ int aniX, aniY; // cursor/tagged actor position
+ while (!GetCursorXYNoWait(&aniX, &aniY, true))
+ CORO_SLEEP(1);
+
+ /*----------------------------------*\
+ | For polygons of type TAG and EXIT. |
+ \*----------------------------------*/
+ for (int i = 0; i < MAX_POLY; i++) {
+ HPOLYGON hp = GetPolyHandle(i);
+
+ if (hp != NOPOLY && (PolyType(hp) == TAG || PolyType(hp) == EXIT)) {
+ if (PolyPointState(hp) == NOT_POINTING) {
+ if (IsInPolygon(aniX, aniY, hp)) {
+ enteringpoly(hp);
+ }
+ } else if (PolyPointState(hp) == POINTING) {
+ if (!IsInPolygon(aniX, aniY, hp)) {
+ leavingpoly(hp);
+ }
+ }
+ }
+ }
+
+ // allow re-scheduling
+ CORO_SLEEP(1);
+ }
+
+ CORO_END_CODE;
+}
+
+void DisableTags(void) {
+ TagsActive = TAGS_OFF;
+}
+
+void EnableTags(void) {
+ TagsActive = TAGS_ON;
+}
+
+bool DisableTagsIfEnabled(void) {
+ if (TagsActive == TAGS_OFF)
+ return false;
+ else {
+ TagsActive = TAGS_OFF;
+ return true;
+ }
+}
+
+/**
+ * For testing purposes only.
+ * Causes CursorPositionProcess() to display, or not, the path that the
+ * cursor is in.
+ */
+void TogglePathDisplay(void) {
+ DispPath ^= 1; // Toggle path display (XOR with true)
+}
+
+
+void setshowstring(void) {
+ bShowString = true;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/pid.h b/engines/tinsel/pid.h
new file mode 100644
index 0000000000..c2af1a5fcb
--- /dev/null
+++ b/engines/tinsel/pid.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * List of all process identifiers
+ */
+
+#ifndef TINSEL_PID_H // prevent multiple includes
+#define TINSEL_PID_H
+
+namespace Tinsel {
+
+#define PID_DESTROY 0x8000 // process id of any process that is to be destroyed between scenes
+
+#define PID_EFFECTS (0x0010 | PID_DESTROY) // generic special effects process id
+#define PID_FLASH (PID_EFFECTS + 1) // flash colour process
+#define PID_CYCLE (PID_EFFECTS + 2) // cycle colour range process
+#define PID_MORPH (PID_EFFECTS + 3) // morph process
+#define PID_FADER (PID_EFFECTS + 4) // fader process
+#define PID_FADE_BGND (PID_EFFECTS + 5) // fade background colour process
+
+#define PID_BACKGND (0x0020 | PID_DESTROY) // background update process id
+
+#define PID_MOUSE 0x0030 // mouse button checking process id
+
+#define PID_JOYSTICK 0x0040 // joystick button checking process id
+
+#define PID_KEYBOARD 0x0050 // keyboard scanning process
+
+#define PID_CURSOR 0x0060 // cursor process
+#define PID_CUR_TRAIL (PID_CURSOR + 1) // cursor trail process
+
+#define PID_SCROLL (0x0070 | PID_DESTROY) // scroll process
+
+#define PID_INVENTORY 0x0080 // inventory process
+
+#define PID_POSITION (0x0090 | PID_DESTROY) // cursor position process
+
+#define PID_TAG (0x00A0 | PID_DESTROY) // tag process
+
+#define PID_TCODE (0x00B0 | PID_DESTROY) // tinsel code process
+
+#define PID_MASTER_SCR 0x00C0 // tinsel master script process
+
+#define PID_MACTOR (0x00D0 | PID_DESTROY) // moving actor process
+
+#define PID_REEL (0x00E0 | PID_DESTROY) // process for each film reel
+
+#define PID_MIDI (0x00F0 | PID_DESTROY) // process to poll MIDI sound driver
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PID_H
diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp
new file mode 100644
index 0000000000..6daf851fd2
--- /dev/null
+++ b/engines/tinsel/play.cpp
@@ -0,0 +1,507 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Plays films within a scene, takes into account the actor in each 'column'. |
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinlib.h" // stand()
+
+namespace Tinsel {
+
+/**
+ * Poke the background palette into an image.
+ */
+static void PokeInPalette(SCNHANDLE hMulFrame) {
+ const FRAME *pFrame; // Pointer to frame
+ PIMAGE pim; // Pointer to image
+
+ // Could be an empty column
+ if (hMulFrame) {
+ pFrame = (const FRAME *)LockMem(hMulFrame);
+
+ // get pointer to image
+ pim = (PIMAGE)LockMem(READ_LE_UINT32(pFrame)); // handle to image
+
+ pim->hImgPal = TO_LE_32(BackPal());
+ }
+}
+
+
+int32 NoNameFunc(int actorID, bool bNewMover) {
+ PMACTOR pActor;
+ int32 retval;
+
+ pActor = GetMover(actorID);
+
+ if (pActor != NULL && !bNewMover) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath == NOPOLY)
+ retval = getPolyZfactor(FirstPathPoly());
+ else
+ retval = getPolyZfactor(pActor->hCpath);
+ } else {
+ switch (actorMaskType(actorID)) {
+ case ACT_DEFAULT:
+ retval = 0;
+ break;
+ case ACT_MASK:
+ retval = 0;
+ break;
+ case ACT_ALWAYS:
+ retval = 10;
+ break;
+ default:
+ retval = actorMaskType(actorID);
+ break;
+ }
+ }
+
+ return retval;
+}
+
+struct PPINIT {
+ SCNHANDLE hFilm; // The 'film'
+ short x; // } Co-ordinates from the play()
+ short y; // } - set to (-1, -1) if none.
+ short z; // normally 0, set if from restore
+ short speed; // Film speed
+ short actorid; // Set if called from an actor code block
+ bool splay; // Set if called from splay()
+ bool bTop; // Set if called from topplay()
+ short sf; // SlowFactor - only used for moving actors
+ short column; // Column number, first column = 0
+
+ bool escOn;
+ int myescEvent;
+};
+
+
+/**
+ * - Don't bother if this reel is already playing for this actor.
+ * - If explicit co-ordinates, use these, If embedded co-ordinates,
+ * leave alone, otherwise use actor's current position.
+ * - Moving actors get hidden during this play, other actors get
+ * _ctx->replaced by this play.
+ * - Column 0 of a film gets its appropriate Z-position, slave columns
+ * get slightly bigger Z-positions, in column order.
+ * - Play proceeds until the script finishes, another reel starts up for
+ * this actor, or the actor gets killed.
+ * - If called from an splay(), moving actor's co-ordinates are updated
+ * after the play, any walk still in progress will go on from there.
+ */
+void PlayReel(CORO_PARAM, const PPINIT *ppi) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pPlayObj; // Object
+ ANIM thisAnim; // Animation structure
+
+ bool mActor; // Gets set if this is a moving actor
+ bool lifeNoMatter;
+ bool replaced;
+
+ const FREEL *pfreel; // The 'column' to play
+ int stepCount;
+ int frameCount;
+ int reelActor;
+ CORO_END_CONTEXT(_ctx);
+
+ static int firstColZ = 0; // Z-position of column zero
+ static int32 fColZfactor = 0; // Z-factor of column zero's actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+ PMACTOR pActor;
+ bool bNewMover; // Gets set if a moving actor that isn't in scene yet
+
+ const FILM *pfilm;
+
+ _ctx->lifeNoMatter = false;
+ _ctx->replaced = false;
+ pActor = NULL;
+ bNewMover = false;
+
+ pfilm = (const FILM *)LockMem(ppi->hFilm);
+ _ctx->pfreel = &pfilm->reels[ppi->column];
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(_ctx->pfreel->mobj));
+
+ // Save actor's ID
+ _ctx->reelActor = (int32)FROM_LE_32(pmi->mulID);
+
+ /**** New (experimental? bit 5/1/95 ****/
+ if (!actorAlive(_ctx->reelActor))
+ return;
+ /**** Delete a bit down there if this stays ****/
+
+ updateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent);
+
+ // To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios
+ if (ppi->hFilm != getActorLatestFilm(_ctx->reelActor)) {
+ // This in not the last film scheduled for this actor
+
+ // It may be the last non-talk film though
+ if (isActorTalking(_ctx->reelActor))
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk
+
+ return;
+ }
+ if (isActorTalking(_ctx->reelActor)) {
+ // Note: will delete this and there'll be no need to store the talk film!
+ if (ppi->hFilm != getActorTalkFilm(_ctx->reelActor)) {
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk
+ return;
+ }
+ } else {
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm);
+ }
+
+ // If this reel is already playing for this actor, just forget it.
+ if (actorReel(_ctx->reelActor) == _ctx->pfreel)
+ return;
+
+ // Poke in the background palette
+ PokeInPalette(FROM_LE_32(pmi->hMulFrame));
+
+ // Set up and insert the multi-object
+ _ctx->pPlayObj = MultiInitObject(pmi);
+ if (!ppi->bTop)
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
+ else
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
+
+ // If co-ordinates are specified, use specified.
+ // Otherwise, use actor's position if there are not embedded co-ords.
+ // Add this first test for nth columns with offsets
+ // in plays with (x,y)
+ int tmpX, tmpY;
+ tmpX = ppi->x;
+ tmpY = ppi->y;
+ if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) {
+ } else if (tmpX != -1 || tmpY != -1) {
+ MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY);
+ } else if (!pmi->mulX && !pmi->mulY) {
+ GetActorPos(_ctx->reelActor, &tmpX, &tmpY);
+ MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY);
+ }
+
+ // If it's a moving actor, this hides the moving actor
+ // used to do this only if (actorid == 0) - I don't know why
+ _ctx->mActor = HideMovingActor(_ctx->reelActor, ppi->sf);
+
+ // If it's a moving actor, get its MACTOR structure.
+ // If it isn't in the scene yet, get its task running - using
+ // stand() - to prevent a glitch at the end of the play.
+ if (_ctx->mActor) {
+ pActor = GetMover(_ctx->reelActor);
+ if (getMActorState(pActor) == NO_MACTOR) {
+ stand(_ctx->reelActor, MAGICX, MAGICY, 0);
+ bNewMover = true;
+ }
+ }
+
+ // Register the fact that we're playing this for this actor
+ storeActorReel(_ctx->reelActor, _ctx->pfreel, ppi->hFilm, _ctx->pPlayObj, ppi->column, tmpX, tmpY);
+
+ /**** Will get rid of this if the above is kept ****/
+ // We may be temporarily resuscitating a dead actor
+ if (ppi->actorid == 0 && !actorAlive(_ctx->reelActor))
+ _ctx->lifeNoMatter = true;
+
+ InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj, FROM_LE_32(_ctx->pfreel->script), ppi->speed);
+
+ // If first column, set Z position as per
+ // Otherwise, column 0's + column number
+ // N.B. It HAS been ensured that the first column gets here first
+ if (ppi->z != 0) {
+ MultiSetZPosition(_ctx->pPlayObj, ppi->z);
+ storeActorZpos(_ctx->reelActor, ppi->z);
+ } else if (ppi->bTop) {
+ if (ppi->column == 0) {
+ firstColZ = Z_TOPPLAY + actorMaskType(_ctx->reelActor);
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ storeActorZpos(_ctx->reelActor, firstColZ);
+ } else {
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
+ storeActorZpos(_ctx->reelActor, firstColZ + ppi->column);
+ }
+ } else if (ppi->column == 0) {
+ if (_ctx->mActor && !bNewMover) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath == NOPOLY)
+ fColZfactor = getPolyZfactor(FirstPathPoly());
+ else
+ fColZfactor = getPolyZfactor(pActor->hCpath);
+ firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
+ } else {
+ switch (actorMaskType(_ctx->reelActor)) {
+ case ACT_DEFAULT:
+ fColZfactor = 0;
+ firstColZ = 2;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ case ACT_MASK:
+ fColZfactor = 0;
+ firstColZ = MultiLowest(_ctx->pPlayObj);
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ case ACT_ALWAYS:
+ fColZfactor = 10;
+ firstColZ = 10000;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ default:
+ fColZfactor = actorMaskType(_ctx->reelActor);
+ firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
+ if (firstColZ < 2) {
+ // This is an experiment!
+ firstColZ = 2;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ }
+ break;
+ }
+ }
+ storeActorZpos(_ctx->reelActor, firstColZ);
+ } else {
+ if (NoNameFunc(_ctx->reelActor, bNewMover) > fColZfactor) {
+ fColZfactor = NoNameFunc(_ctx->reelActor, bNewMover);
+ firstColZ = fColZfactor << 10;
+ }
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
+ storeActorZpos(_ctx->reelActor, firstColZ + ppi->column);
+ }
+
+ /*
+ * Play until the script finishes,
+ * another reel starts up for this actor,
+ * or the actor gets killed.
+ */
+ _ctx->stepCount = 0;
+ _ctx->frameCount = 0;
+ do {
+ if (_ctx->stepCount++ == 0) {
+ _ctx->frameCount++;
+ storeActorSteps(_ctx->reelActor, _ctx->frameCount);
+ }
+ if (_ctx->stepCount == ppi->speed)
+ _ctx->stepCount = 0;
+
+ if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished)
+ break;
+
+ int x, y;
+ GetAniPosition(_ctx->pPlayObj, &x, &y);
+ storeActorPos(_ctx->reelActor, x, y);
+
+ CORO_SLEEP(1);
+
+ if (actorReel(_ctx->reelActor) != _ctx->pfreel) {
+ _ctx->replaced = true;
+ break;
+ }
+
+ if (actorEsc(_ctx->reelActor) && actorEev(_ctx->reelActor) != GetEscEvents())
+ break;
+
+ } while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor));
+
+ // Register the fact that we're NOT playing this for this actor
+ if (actorReel(_ctx->reelActor) == _ctx->pfreel)
+ storeActorReel(_ctx->reelActor, NULL, 0, NULL, 0, 0, 0);
+
+ // Ditch the object
+ if (!ppi->bTop)
+ MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
+ else
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
+
+ if (_ctx->mActor) {
+ if (!_ctx->replaced)
+ unHideMovingActor(_ctx->reelActor); // Restore moving actor
+
+ // Update it's co-ordinates if this is an splay()
+ if (ppi->splay)
+ restoreMovement(_ctx->reelActor);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Run all animations that comprise the play film.
+ */
+static void playProcess(CORO_PARAM) {
+ // get the stuff copied to process when it was created
+ PPINIT *ppi = (PPINIT *)ProcessGetParamsSelf();
+
+ PlayReel(coroParam, ppi);
+}
+
+// *******************************************************
+
+
+// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios
+void newestFilm(SCNHANDLE film, const FREEL *reel) {
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(reel->mobj));
+
+ setActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film);
+}
+
+// *******************************************************
+
+/**
+ * Start up a play process for each column in a film.
+ *
+ * NOTE: The processes are started in reverse order so that the first
+ * column's process kicks in first.
+ */
+void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn,
+ int myescEvent, bool bTop) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ PPINIT ppi;
+
+ assert(film != 0); // Trying to play NULL film
+
+ // Now allowed empty films!
+ if (pfilm->numreels == 0)
+ return; // Nothing to do!
+
+ ppi.hFilm = film;
+ ppi.x = x;
+ ppi.y = y;
+ ppi.z = 0;
+ ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ ppi.actorid = actorid;
+ ppi.splay = splay;
+ ppi.bTop = bTop;
+ ppi.sf = sfact;
+ ppi.escOn = escOn;
+ ppi.myescEvent = myescEvent;
+
+ // Start display process for each reel in the film
+ for (int i = FROM_LE_32(pfilm->numreels) - 1; i >= 0; i--) {
+ newestFilm(film, &pfilm->reels[i]);
+
+ ppi.column = i;
+ CoroutineInstall(PID_REEL, playProcess, &ppi, sizeof(ppi));
+ }
+}
+
+/**
+ * Start up a play process for each slave column in a film.
+ * Play the first column directly from the parent process.
+ */
+void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact,
+ bool escOn, int myescEvent, bool bTop) {
+ CORO_BEGIN_CONTEXT;
+ PPINIT ppi;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(film != 0); // Trying to play NULL film
+ const FILM *pfilm;
+
+ pfilm = (const FILM *)LockMem(film);
+
+ // Now allowed empty films!
+ if (pfilm->numreels == 0)
+ return; // Already played to completion!
+
+ _ctx->ppi.hFilm = film;
+ _ctx->ppi.x = x;
+ _ctx->ppi.y = y;
+ _ctx->ppi.z = 0;
+ _ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ _ctx->ppi.actorid = actorid;
+ _ctx->ppi.splay = splay;
+ _ctx->ppi.bTop = bTop;
+ _ctx->ppi.sf = sfact;
+ _ctx->ppi.escOn = escOn;
+ _ctx->ppi.myescEvent = myescEvent;
+
+ // Start display process for each secondary reel in the film
+ for (int i = FROM_LE_32(pfilm->numreels) - 1; i > 0; i--) {
+ newestFilm(film, &pfilm->reels[i]);
+
+ _ctx->ppi.column = i;
+ CoroutineInstall(PID_REEL, playProcess, &_ctx->ppi, sizeof(PPINIT));
+ }
+
+ newestFilm(film, &pfilm->reels[0]);
+
+ _ctx->ppi.column = 0;
+ CORO_INVOKE_1(PlayReel, &_ctx->ppi);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Start up a play process for a particular column in a film.
+ *
+ * NOTE: This is specifically for actors during a restore scene.
+ */
+void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ PPINIT ppi;
+
+ ppi.hFilm = film;
+ ppi.x = x;
+ ppi.y = y;
+ ppi.z = z;
+ ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ ppi.actorid = 0;
+ ppi.splay = false;
+ ppi.bTop = false;
+ ppi.sf = 0;
+ ppi.column = reelnum;
+
+ // FIXME: The PlayReel play loop was previously breaking out, and then deleting objects, when
+ // returning to a scene because escOn and myescEvent were undefined. Need to make sure whether
+ // restored objects should have any particular combination of these two values
+ ppi.escOn = false;
+ ppi.myescEvent = GetEscEvents();
+
+ assert(pfilm->numreels);
+
+ newestFilm(film, &pfilm->reels[reelnum]);
+
+ // Start display process for the reel
+ CoroutineInstall(PID_REEL, playProcess, &ppi, sizeof(ppi));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp
new file mode 100644
index 0000000000..21047afcf5
--- /dev/null
+++ b/engines/tinsel/polygons.cpp
@@ -0,0 +1,1805 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/serializer.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define MAXONROUTE 40
+
+typedef POLYGON *PPOLYGON;
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** lineinfo struct - one per (node-1) in a node path */
+struct LINEINFO {
+
+ int32 a;
+ int32 b;
+ int32 c;
+
+ int32 a2; //!< a squared
+ int32 b2; //!< b squared
+ int32 a2pb2; //!< a squared + b squared
+ int32 ra2pb2; //!< root(a squared + b squared)
+
+ int32 ab;
+ int32 ac;
+ int32 bc;
+} PACKED_STRUCT;
+
+/** polygon struct - one per polygon */
+struct POLY {
+ int32 type; //!< type of polygon
+ int32 x[4], y[4]; // Polygon definition
+
+ int32 tagx, tagy; // } For tagged polygons
+ SCNHANDLE hTagtext; // } i.e. EXIT, TAG, EFFECT
+
+ int32 nodex, nodey; // EXIT, TAG, REFER
+ SCNHANDLE hFilm; //!< film reel handle for EXIT, TAG
+
+ int32 reftype; //!< Type of REFER
+
+ int32 id; // } EXIT and TAG
+
+ int32 scale1, scale2; // }
+ int32 reel; // } PATH and NPATH
+ int32 zFactor; // }
+
+ //The arrays now stored externally
+ int32 nodecount; //!<The number of nodes in this polygon
+ int32 pnodelistx,pnodelisty; //!<offset in chunk to this array if present
+ int32 plinelist;
+
+ SCNHANDLE hScript; //!< handle of code segment for polygon events
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int MaxPolys = MAX_POLY;
+
+static POLYGON *Polys[MAX_POLY+1];
+
+static PPOLYGON Polygons = 0;
+
+static SCNHANDLE pHandle = 0; // } Set at start of each scene
+static int noofPolys = 0; // }
+
+static POLYGON extraBlock; // Used for dynamic blocking
+
+static int pathsOnRoute = 0;
+static PPOLYGON RoutePaths[MAXONROUTE];
+
+static PPOLYGON RouteEnd = 0;
+
+#ifdef DEBUG
+int highestYet = 0;
+#endif
+
+
+
+//----------------- LOCAL MACROS --------------------
+
+// The str parameter is no longer used
+#define CHECK_HP_OR(mvar, str) assert((mvar >= 0 && mvar <= noofPolys) || mvar == MAX_POLY);
+#define CHECK_HP(mvar, str) assert(mvar >= 0 && mvar <= noofPolys);
+
+static HPOLYGON PolyIndex(PPOLYGON pp) {
+ for (int j = 0; j <= MAX_POLY; j++) {
+ if (Polys[j] == pp)
+ return j;
+ }
+
+ error("PolyIndex(): polygon not found");
+ return NOPOLY;
+}
+
+/**
+ * Returns TRUE if the point is within the polygon supplied.
+ *
+ * Firstly, the point must be within the smallest imaginary rectangle
+ * which encloses the polygon.
+ *
+ * Then, from each corner of the polygon, if the point is within an
+ * imaginary rectangle enclosing the clockwise-going side from that
+ * corner, the gradient of a line from the corner to the point must be
+ * less than (or more negative than) the gradient of that side:
+ *
+ * If the corners' coordinates are designated (x1, y1) and (x2, y2), and
+ * the point in question's (xt, yt), then:
+ * gradient (x1,y1)->(x2,y2) > gradient (x1,y1)->(xt,yt)
+ * (y1-y2)/(x2-x1) > (y1-yt)/(xt-x1)
+ * (y1-y2)*(xt-x1) > (y1-yt)*(x2-x1)
+ * xt(y1-y2) -x1y1 + x1y2 > -yt(x2-x1) + y1x2 - x1y1
+ * xt(y1-y2) + yt(x2-x1) > y1x2 - x1y2
+ *
+ * If the point passed one of the four 'side tests', and failed none,
+ * then it must be within the polygon. If the point was not tested, it
+ * may be within the internal rectangle not covered by the above tests.
+ *
+ * Most polygons contain an internal rectangle which does not fall into
+ * any of the above side-related tests. Such a rectangle will always
+ * have two polygon corners above it and two corners to the left of it.
+ */
+bool IsInPolygon(int xt, int yt, HPOLYGON hp) {
+ PPOLYGON pp;
+ int i;
+ bool BeenTested = false;
+ int pl = 0, pa = 0;
+
+ CHECK_HP_OR(hp, "Out of range polygon handle (1)");
+ pp = Polys[hp];
+ assert(pp != NULL); // Testing whether in a NULL polygon
+
+ /* Is point within the external rectangle? */
+ if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom)
+ return false;
+
+ // For each corner/side
+ for (i = 0; i < 4; i++) {
+ // If within this side's 'testable' area
+ // i.e. within the width of the line in y direction of end of line
+ // or within the height of the line in x direction of end of line
+ if ((xt >= pp->lleft[i] && xt <= pp->lright[i] && ((yt > pp->cy[i]) == (pp->cy[(i+1)%4] > pp->cy[i])))
+ || (yt >= pp->ltop[i] && yt <= pp->lbottom[i] && ((xt > pp->cx[i]) == (pp->cx[(i+1)%4] > pp->cx[i])))) {
+ if (((long)xt*pp->a[i] + (long)yt*pp->b[i]) < pp->c[i])
+ return false;
+ else
+ BeenTested = true;
+ }
+ }
+
+ if (BeenTested) {
+ // New dodgy code 29/12/94
+ if (pp->polytype == BLOCKING) {
+ // For each corner/side
+ for (i = 0; i < 4; i++) {
+ // Pretend the corners of blocking polys are not in the poly.
+ if (xt == pp->cx[i] && yt == pp->cy[i])
+ return false;
+ }
+ }
+ return true;
+ } else {
+ // Is point within the internal rectangle?
+ for (i = 0; i < 4; i++) {
+ if (pp->cx[i] < xt)
+ pl++;
+ if (pp->cy[i] < yt)
+ pa++;
+ }
+
+ if (pa == 2 && pl == 2)
+ return true;
+ else
+ return false;
+ }
+}
+
+/**
+ * Finds a polygon of the specified type containing the supplied point.
+ */
+
+HPOLYGON InPolygon(int xt, int yt, PTYPE type) {
+ for (int j = 0; j <= MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == type) {
+ if (IsInPolygon(xt, yt, j))
+ return j;
+ }
+ }
+ return NOPOLY;
+}
+
+/**
+ * Given a blocking polygon, current co-ordinates of an actor, and the
+ * co-ordinates of where the actor is heading, works out which corner of
+ * the blocking polygon to head around.
+ */
+
+void BlockingCorner(HPOLYGON hp, int *x, int *y, int tarx, int tary) {
+ PPOLYGON pp;
+ int i;
+ int xd, yd; // distance per axis
+ int ThisD, SmallestD = 1000;
+ int D1, D2;
+ int NearestToHere = 1000, NearestToTarget;
+ unsigned At = 10; // Corner already at
+
+ int bcx[4], bcy[4]; // Bogus corners
+
+ CHECK_HP_OR(hp, "Out of range polygon handle (2)");
+ pp = Polys[hp];
+
+ // Work out a point outside each corner
+ for (i = 0; i < 4; i++) {
+ int next, prev;
+
+ // X-direction
+ next = pp->cx[i] - pp->cx[(i+1)%4];
+ prev = pp->cx[i] - pp->cx[(i+3)%4];
+ if (next <= 0 && prev <= 0)
+ bcx[i] = pp->cx[i] - 4; // Both points to the right
+ else if (next >= 0 && prev >= 0)
+ bcx[i] = pp->cx[i] + 4; // Both points to the left
+ else
+ bcx[i] = pp->cx[i];
+
+ // Y-direction
+ next = pp->cy[i] - pp->cy[(i+1)%4];
+ prev = pp->cy[i] - pp->cy[(i+3)%4];
+ if (next <= 0 && prev <= 0)
+ bcy[i] = pp->cy[i] - 4; // Both points below
+ else if (next >= 0 && prev >= 0)
+ bcy[i] = pp->cy[i] + 4; // Both points above
+ else
+ bcy[i] = pp->cy[i];
+ }
+
+ // Find nearest corner to where we are,
+ // but not the one we're stood at.
+
+ for (i = 0; i < 4; i++) { // For 4 corners
+// ThisD = ABS(*x - pp->cx[i]) + ABS(*y - pp->cy[i]);
+ ThisD = ABS(*x - bcx[i]) + ABS(*y - bcy[i]);
+ if (ThisD < SmallestD) {
+ // Ignore this corner if it's not in a path
+ if (InPolygon(pp->cx[i], pp->cy[i], PATH) == NOPOLY ||
+ InPolygon(bcx[i], bcy[i], PATH) == NOPOLY)
+ continue;
+
+ // Are we stood at this corner?
+ if (ThisD > 4) {
+ // No - it's the nearest we've found yet.
+ NearestToHere = i;
+ SmallestD = ThisD;
+ } else {
+ // Stood at/next to this corner
+ At = i;
+ }
+ }
+ }
+
+ // If we're not already at a corner, go to the nearest corner
+
+ if (At == 10) {
+ // Not stood at a corner
+// assert(NearestToHere != 1000); // At blocking corner, not found near corner!
+ // Better to give up than to assert fail!
+ if (NearestToHere == 1000) {
+ // Send it to where it is now
+ // i.e. leave x and y alone
+ } else {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ }
+ } else {
+ // Already at a corner. Go to an adjacent corner.
+ // First, find out which adjacent corner is nearest the target.
+ xd = ABS(tarx - pp->cx[(At + 1) % 4]);
+ yd = ABS(tary - pp->cy[(At + 1) % 4]);
+ D1 = xd + yd;
+ xd = ABS(tarx - pp->cx[(At + 3) % 4]);
+ yd = ABS(tary - pp->cy[(At + 3) % 4]);
+ D2 = xd + yd;
+ NearestToTarget = (D2 > D1) ? (At + 1) % 4 : (At + 3) % 4;
+ if (NearestToTarget == NearestToHere) {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ } else {
+ // Need to decide whether it's better to go to the nearest to
+ // here and then on to the target, or to the nearest to the
+ // target and on from there.
+ xd = ABS(pp->cx[At] - pp->cx[NearestToHere]);
+ D1 = xd;
+ xd = ABS(pp->cx[NearestToHere] - tarx);
+ D1 += xd;
+
+ yd = ABS(pp->cy[At] - pp->cy[NearestToHere]);
+ D1 += yd;
+ yd = ABS(pp->cy[NearestToHere] - tary);
+ D1 += yd;
+
+ xd = ABS(pp->cx[At] - pp->cx[NearestToTarget]);
+ D2 = xd;
+ xd = ABS(pp->cx[NearestToTarget] - tarx);
+ D2 += xd;
+
+ yd = ABS(pp->cy[At] - pp->cy[NearestToTarget]);
+ D2 += yd;
+ yd = ABS(pp->cy[NearestToTarget] - tary);
+ D2 += yd;
+
+ if (D2 > D1) {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ } else {
+ *x = bcx[NearestToTarget];
+ *y = bcy[NearestToTarget];
+ }
+ }
+ }
+}
+
+
+/**
+ * Try do drop a perpendicular to each inter-node line from the point
+ * and remember the shortest (if any).
+ * Find which node is nearest to the point.
+ * The shortest of these gives the best point in the node path.
+*/
+void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) {
+ PPOLYGON pp;
+
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Compiled polygon data
+ int dropD; // length of perpendicular (i.e. distance of point from line)
+ int dropX, dropY; // (X, Y) where dropped perpendicular intersects the line
+ int d1, d2; // distance from perpendicular intersect to line's end nodes
+ int32 *nlistx, *nlisty;
+
+ int shortestD = 10000; // Shortest distance found
+ int nearestL = -1; // Nearest line
+ int nearestN; // Nearest Node
+
+ int h = *x; // For readability/conveniance
+ int k = *y; // - why aren't these #defines?
+ LINEINFO *llist; // Inter-node line structure
+
+ CHECK_HP(hp, "Out of range polygon handle (3)");
+ pp = Polys[hp];
+
+ // Pointer to polygon data
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + pp->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+ llist = (LINEINFO *)(pps + (int)FROM_LE_32(ptp->plinelist));
+
+ // Look for fit of perpendicular to lines between nodes
+ for (int i = 0; i < (int)FROM_LE_32(ptp->nodecount) - 1; i++) {
+ const int32 a = (int)FROM_LE_32(llist[i].a);
+ const int32 b = (int)FROM_LE_32(llist[i].b);
+ const int32 c = (int)FROM_LE_32(llist[i].c);
+
+#if 1
+ if (true) {
+ //printf("a %d, b %d, c %d, a^2+b^2 = %d\n", a, b, c, a*a+b*b);
+
+ // TODO: If the comments of the LINEINFO struct are correct, then it contains mostly
+ // duplicate data, probably in an effort to safe CPU cycles. Even on the slowest devices
+ // we support, calculatin a product of two ints is not an issue.
+ // So we can just load & endian convert a,b,c, then replace stuff like
+ // (int)FROM_LE_32(line->ab)
+ // by simply a*b, which makes it easier to understand what the code does, too.
+ // Just in case there is some bugged data, I leave this code here for verifying it.
+ // Let's leave it in for some time.
+ //
+ // One bad thing: We use sqrt to compute a square root. Might not be a good idea,
+ // speed wise. Maybe we should take Vicent's fp_sqroot. But that's a problem for later.
+
+ LINEINFO *line = &llist[i];
+ int32 a2 = (int)FROM_LE_32(line->a2); //!< a squared
+ int32 b2 = (int)FROM_LE_32(line->b2); //!< b squared
+ int32 a2pb2 = (int)FROM_LE_32(line->a2pb2); //!< a squared + b squared
+ int32 ra2pb2 = (int)FROM_LE_32(line->ra2pb2); //!< root(a squared + b squared)
+
+ int32 ab = (int)FROM_LE_32(line->ab);
+ int32 ac = (int)FROM_LE_32(line->ac);
+ int32 bc = (int)FROM_LE_32(line->bc);
+
+ assert(a*a == a2);
+ assert(b*b == b2);
+ assert(a*b == ab);
+ assert(a*c == ac);
+ assert(b*c == bc);
+
+ assert(a2pb2 == a*a + b*b);
+ assert(ra2pb2 == (int)sqrt((float)a*a + (float)b*b));
+ }
+#endif
+
+
+ if (a == 0 && b == 0)
+ continue; // Line is just a point!
+
+ // X position of dropped perpendicular intersection with line
+ dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
+
+ // X distances from intersection to end nodes
+ d1 = dropX - (int)FROM_LE_32(nlistx[i]);
+ d2 = dropX - (int)FROM_LE_32(nlistx[i+1]);
+
+ // if both -ve or both +ve, no fit
+ if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
+ continue;
+//#if 0
+ // Y position of sidweays perpendicular intersection with line
+ dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
+
+ // Y distances from intersection to end nodes
+ d1 = dropY - (int)FROM_LE_32(nlisty[i]);
+ d2 = dropY - (int)FROM_LE_32(nlisty[i+1]);
+
+ // if both -ve or both +ve, no fit
+ if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
+ continue;
+//#endif
+ dropD = ((a * h) + (b * k) + c) / (int)sqrt((float)a*a + (float)b*b);
+ dropD = ABS(dropD);
+ if (dropD < shortestD) {
+ shortestD = dropD;
+ nearestL = i;
+ }
+ }
+
+ // Distance to nearest node
+ nearestN = NearestNodeWithin(hp, h, k);
+ dropD = ABS(h - (int)FROM_LE_32(nlistx[nearestN])) + ABS(k - (int)FROM_LE_32(nlisty[nearestN]));
+
+ // Go to a node or a point on a line
+ if (dropD < shortestD) {
+ // A node is nearest
+ *x = (int)FROM_LE_32(nlistx[nearestN]);
+ *y = (int)FROM_LE_32(nlisty[nearestN]);
+ *pline = nearestN;
+ } else {
+ assert(nearestL != -1);
+
+ // A point on a line is nearest
+ const int32 a = (int)FROM_LE_32(llist[nearestL].a);
+ const int32 b = (int)FROM_LE_32(llist[nearestL].b);
+ const int32 c = (int)FROM_LE_32(llist[nearestL].c);
+ dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
+ dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
+ *x = dropX;
+ *y = dropY;
+ *pline = nearestL;
+ }
+
+ assert(IsInPolygon(*x, *y, hp)); // Nearest point is not in polygon(!)
+}
+
+/**
+ * Returns TRUE if two paths are asdjacent.
+ */
+bool IsAdjacentPath(HPOLYGON hPath1, HPOLYGON hPath2) {
+ PPOLYGON pp1, pp2;
+
+ CHECK_HP(hPath1, "Out of range polygon handle (4)");
+ CHECK_HP(hPath2, "Out of range polygon handle (500)");
+
+ if (hPath1 == hPath2)
+ return true;
+
+ pp1 = Polys[hPath1];
+ pp2 = Polys[hPath2];
+
+ for (int j = 0; j < MAXADJ; j++)
+ if (pp1->adjpaths[j] == pp2)
+ return true;
+
+ return false;
+}
+
+static POLYGON *TryPath(POLYGON *last, POLYGON *whereto, POLYGON *current) {
+ PPOLYGON x;
+
+ // For each path adjacent to this one
+ for (int j = 0; j < MAXADJ; j++) {
+ x = current->adjpaths[j]; // call the adj. path x
+ if (x == whereto) {
+ RoutePaths[pathsOnRoute++] = x;
+ return x; // Got there!
+ }
+
+ if (x == NULL)
+ break; // no more adj. paths to look at
+
+ if (x->tried)
+ continue; // don't double back
+
+ if (x == last)
+ continue; // don't double back
+
+ x->tried = true;
+ if (TryPath(current, whereto, x) != NULL) {
+ RoutePaths[pathsOnRoute++] = x;
+ assert(pathsOnRoute < MAXONROUTE);
+ return x; // Got there in this direction
+ } else
+ x->tried = false;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Sort out the first path to head to for the imminent leg of a walk.
+ */
+static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) {
+ // TODO: Fingolfin says: This code currently uses DFS (depth first search),
+ // in the TryPath function, to compute a path between 'from' and 'to'.
+ // However, a BFS (breadth first search) might yield more natural results,
+ // at least in cases where there are multiple possible paths.
+ // There is a small risk of regressions caused by such a change, though.
+ //
+ // Also, the overhead of computing a DFS again and again could be avoided
+ // by computing a path matrix (like we do in the SCUMM engine).
+ int i;
+ PPOLYGON p;
+
+ CHECK_HP(from, "Out of range polygon handle (501a)");
+ CHECK_HP(to, "Out of range polygon handle (501b)");
+
+ if (IsAdjacentPath(from, to))
+ return to;
+
+ for (i = 0; i < MAX_POLY; i++) { // For each polygon..
+ p = Polys[i];
+ if (p && p->polytype == PATH) //...if it's a path
+ p->tried = false;
+ }
+ Polys[from]->tried = true;
+ pathsOnRoute = 0;
+
+ p = TryPath(Polys[from], Polys[to], Polys[from]);
+
+ assert(p != NULL); // Trying to find route between unconnected paths
+
+ // Don't go a roundabout way to an adjacent path.
+ for (i = 0; i < pathsOnRoute; i++) {
+ CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (502)");
+ if (IsAdjacentPath(from, PolyIndex(RoutePaths[i])))
+ return PolyIndex(RoutePaths[i]);
+ }
+ return PolyIndex(p);
+}
+
+/**
+ * Indirect method of calling PathOnTheWay(), to put the burden of
+ * recursion onto the main stack.
+ */
+HPOLYGON getPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) {
+ CHECK_HP(hFrom, "Out of range polygon handle (6)");
+ CHECK_HP(hTo, "Out of range polygon handle (7)");
+
+ // Reuse already computed result
+ if (RouteEnd == Polys[hTo]) {
+ for (int i = 0; i < pathsOnRoute; i++) {
+ CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (503)");
+ if (IsAdjacentPath(hFrom, PolyIndex(RoutePaths[i]))) {
+ return PolyIndex(RoutePaths[i]);
+ }
+ }
+ }
+
+ RouteEnd = Polys[hTo];
+ return PathOnTheWay(hFrom, hTo);
+}
+
+
+/**
+ * Given a node path, work out which end node is nearest the given point.
+ */
+
+int NearestEndNode(HPOLYGON hPath, int x, int y) {
+ PPOLYGON pp;
+
+ int d1, d2;
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hPath, "Out of range polygon handle (8)");
+ pp = Polys[hPath];
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + pp->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ const int nodecount = (int)FROM_LE_32(ptp->nodecount);
+
+ d1 = ABS(x - (int)FROM_LE_32(nlistx[0])) + ABS(y - (int)FROM_LE_32(nlisty[0]));
+ d2 = ABS(x - (int)FROM_LE_32(nlistx[nodecount - 1])) + ABS(y - (int)FROM_LE_32(nlisty[nodecount - 1]));
+
+ return (d2 > d1) ? 0 : nodecount - 1;
+}
+
+
+/**
+ * Given a start path and a destination path, find which pair of end
+ * nodes is nearest together.
+ * Return which node in the start path is part of the closest pair.
+ */
+
+int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) {
+ PPOLYGON pSpath, pDpath;
+
+ int ns, nd; // 'top' nodes in each path
+ int dist, NearDist;
+ int NearNode;
+ uint8 *pps; // Compiled polygon data
+ const POLY *ps, *pd; // Pointer to compiled polygon data
+ int32 *snlistx, *snlisty;
+ int32 *dnlistx, *dnlisty;
+
+ CHECK_HP(hSpath, "Out of range polygon handle (9)");
+ CHECK_HP(hDpath, "Out of range polygon handle (10)");
+ pSpath = Polys[hSpath];
+ pDpath = Polys[hDpath];
+
+ pps = LockMem(pHandle); // All polygons
+ ps = (const POLY *)pps + pSpath->pIndex; // Start polygon
+ pd = (const POLY *)pps + pDpath->pIndex; // Dest polygon
+
+ ns = (int)FROM_LE_32(ps->nodecount) - 1;
+ nd = (int)FROM_LE_32(pd->nodecount) - 1;
+
+ snlistx = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelistx));
+ snlisty = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelisty));
+ dnlistx = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelistx));
+ dnlisty = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelisty));
+
+ // start[0] to dest[0]
+ NearDist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[0]));
+ NearNode = 0;
+
+ // start[0] to dest[top]
+ dist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[nd]));
+ if (dist < NearDist)
+ NearDist = dist;
+
+ // start[top] to dest[0]
+ dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[0]));
+ if (dist < NearDist) {
+ NearDist = dist;
+ NearNode = ns;
+ }
+
+ // start[top] to dest[top]
+ dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[nd]));
+ if (dist < NearDist) {
+ NearNode = ns;
+ }
+
+ return NearNode;
+}
+
+/**
+ * Given a follow nodes path and a co-ordinate, finds which node in the
+ * path is nearest to the co-ordinate.
+ */
+int NearestNodeWithin(HPOLYGON hNpath, int x, int y) {
+ int ThisDistance, SmallestDistance = 1000;
+ int NumNodes; // Number of nodes in this follow nodes path
+ int NearestYet = 0; // Number of nearest node
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hNpath, "Out of range polygon handle (11)");
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ NumNodes = (int)FROM_LE_32(ptp->nodecount);
+
+ for (int i = 0; i < NumNodes; i++) {
+ ThisDistance = ABS(x - (int)FROM_LE_32(nlistx[i])) + ABS(y - (int)FROM_LE_32(nlisty[i]));
+
+ if (ThisDistance < SmallestDistance) {
+ NearestYet = i;
+ SmallestDistance = ThisDistance;
+ }
+ }
+
+ return NearestYet;
+}
+
+/**
+ * Given a point and start and destination paths, find the nearest
+ * corner (if any) of the start path which is within the destination
+ * path. If there is no such corner, find the nearest corner of the
+ * destination path which falls within the source path.
+ */
+void NearestCorner(int *x, int *y, HPOLYGON hStartPoly, HPOLYGON hDestPoly) {
+ PPOLYGON psp, pdp;
+ int j;
+ int ncorn = 0; // nearest corner
+ HPOLYGON hNpath = NOPOLY; // path containing nearest corner
+ int ThisD, SmallestD = 1000;
+
+ CHECK_HP(hStartPoly, "Out of range polygon handle (12)");
+ CHECK_HP(hDestPoly, "Out of range polygon handle (13)");
+
+ psp = Polys[hStartPoly];
+ pdp = Polys[hDestPoly];
+
+ // Nearest corner of start path in destination path.
+
+ for (j = 0; j < 4; j++) {
+ if (IsInPolygon(psp->cx[j], psp->cy[j], hDestPoly)) {
+ ThisD = ABS(*x - psp->cx[j]) + ABS(*y - psp->cy[j]);
+ if (ThisD < SmallestD) {
+ hNpath = hStartPoly;
+ ncorn = j;
+ // Try to ignore it if virtually stood on it
+ if (ThisD > 4)
+ SmallestD = ThisD;
+ }
+ }
+ }
+ if (SmallestD == 1000) {
+ // Nearest corner of destination path in start path.
+ for (j = 0; j < 4; j++) {
+ if (IsInPolygon(pdp->cx[j], pdp->cy[j], hStartPoly)) {
+ ThisD = ABS(*x - pdp->cx[j]) + ABS(*y - pdp->cy[j]);
+ if (ThisD < SmallestD) {
+ hNpath = hDestPoly;
+ ncorn = j;
+ // Try to ignore it if virtually stood on it
+ if (ThisD > 4)
+ SmallestD = ThisD;
+ }
+ }
+ }
+ }
+
+ if (hNpath != NOPOLY) {
+ *x = Polys[hNpath]->cx[ncorn];
+ *y = Polys[hNpath]->cy[ncorn];
+ } else
+ error("NearestCorner() failure");
+}
+
+bool IsPolyCorner(HPOLYGON hPath, int x, int y) {
+ CHECK_HP(hPath, "Out of range polygon handle (37)");
+
+ for (int i = 0; i < 4; i++) {
+ if (Polys[hPath]->cx[i] == x && Polys[hPath]->cy[i] == y)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Given a path polygon and a Y co-ordinate, return a scale value.
+ */
+int GetScale(HPOLYGON hPath, int y) {
+ const POLY *ptp; // Pointer to compiled polygon data
+ int zones; // Number of different scales
+ int zlen; // Depth of each scale zone
+ int scale;
+ int top;
+
+ // To try and fix some unknown potential bug
+ if (hPath == NOPOLY)
+ return SCALE_LARGE;
+
+ CHECK_HP(hPath, "Out of range polygon handle (14)");
+
+ ptp = (const POLY *)LockMem(pHandle) + Polys[hPath]->pIndex;
+
+ // Path is of a constant scale?
+ if (FROM_LE_32(ptp->scale2) == 0)
+ return FROM_LE_32(ptp->scale1);
+
+ assert(FROM_LE_32(ptp->scale1) >= FROM_LE_32(ptp->scale2));
+
+ zones = FROM_LE_32(ptp->scale1) - FROM_LE_32(ptp->scale2) + 1;
+ zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones;
+
+ scale = FROM_LE_32(ptp->scale1);
+ top = Polys[hPath]->ptop;
+
+ do {
+ top += zlen;
+ if (y < top)
+ return scale;
+ } while (--scale);
+
+ return FROM_LE_32(ptp->scale2);
+}
+
+/**
+ * Give the co-ordinates of a node in a node path.
+ */
+void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) {
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hNpath, "Out of range polygon handle (15)");
+ assert(Polys[hNpath] != NULL && Polys[hNpath]->polytype == PATH && Polys[hNpath]->subtype == NODE); // must be given a node path!
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ // Might have just walked to the node from above.
+ if (node == (int)FROM_LE_32(ptp->nodecount))
+ node -= 1;
+
+ *px = (int)FROM_LE_32(nlistx[node]);
+ *py = (int)FROM_LE_32(nlisty[node]);
+}
+
+/**
+ * Get tag text handle and tag co-ordinates of a polygon.
+ */
+
+void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (16)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ *tagx = (int)FROM_LE_32(pp->tagx);
+ *tagy = (int)FROM_LE_32(pp->tagy);
+ *hTagText = FROM_LE_32(pp->hTagtext);
+}
+
+/**
+ * Get polygon's film reel handle.
+ */
+
+SCNHANDLE getPolyFilm(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (17)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return FROM_LE_32(pp->hFilm);
+}
+
+/**
+ * Get polygon's associated node.
+ */
+
+void getPolyNode(HPOLYGON hp, int *px, int *py) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (18)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ *px = (int)FROM_LE_32(pp->nodex);
+ *py = (int)FROM_LE_32(pp->nodey);
+}
+
+/**
+ * Get handle to polygon's glitter code.
+ */
+
+SCNHANDLE getPolyScript(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (19)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return FROM_LE_32(pp->hScript);
+}
+
+REEL getPolyReelType(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ // To try and fix some unknown potential bug (toyshop entrance)
+ if (hp == NOPOLY)
+ return REEL_ALL;
+
+ CHECK_HP(hp, "Out of range polygon handle (20)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (REEL)FROM_LE_32(pp->reel);
+}
+
+int32 getPolyZfactor(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (21)");
+ assert(Polys[hp] != NULL);
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (int)FROM_LE_32(pp->zFactor);
+}
+
+int numNodes(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (22)");
+ assert(Polys[hp] != NULL);
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (int)FROM_LE_32(pp->nodecount);
+}
+
+// *************************************************************************
+//
+// Code concerned with killing and reviving TAG and EXIT polygons.
+// And code to enable this information to be saved and restored.
+//
+// *************************************************************************
+
+struct TAGSTATE {
+ int tid;
+ bool enabled;
+};
+typedef TAGSTATE *PTAGSTATE;
+
+#define MAX_SCENES 256
+#define MAX_TAGS 2048
+#define MAX_EXITS 512
+
+static struct {
+ SCNHANDLE sid;
+ int nooftags;
+ int offset;
+} SceneTags[MAX_SCENES], SceneExits[MAX_SCENES];
+
+static TAGSTATE TagStates[MAX_TAGS];
+static TAGSTATE ExitStates[MAX_EXITS];
+
+static int nextfreeT = 0, numScenesT = 0;
+static int nextfreeE = 0, numScenesE = 0;
+
+static int currentTScene = 0;
+static int currentEScene = 0;
+
+bool deadPolys[MAX_POLY]; // Currently just for dead blocks
+
+void RebootDeadTags(void) {
+ nextfreeT = numScenesT = 0;
+ nextfreeE = numScenesE = 0;
+
+ memset(SceneTags, 0, sizeof(SceneTags));
+ memset(SceneExits, 0, sizeof(SceneExits));
+ memset(TagStates, 0, sizeof(TagStates));
+ memset(ExitStates, 0, sizeof(ExitStates));
+ memset(deadPolys, 0, sizeof(deadPolys));
+}
+
+/**
+ * (Un)serialize the dead tag and exit data for save/restore game.
+ */
+void syncPolyInfo(Serializer &s) {
+ int i;
+
+ for (i = 0; i < MAX_SCENES; i++) {
+ s.syncAsUint32LE(SceneTags[i].sid);
+ s.syncAsSint32LE(SceneTags[i].nooftags);
+ s.syncAsSint32LE(SceneTags[i].offset);
+ }
+
+ for (i = 0; i < MAX_SCENES; i++) {
+ s.syncAsUint32LE(SceneExits[i].sid);
+ s.syncAsSint32LE(SceneExits[i].nooftags);
+ s.syncAsSint32LE(SceneExits[i].offset);
+ }
+
+ for (i = 0; i < MAX_TAGS; i++) {
+ s.syncAsUint32LE(TagStates[i].tid);
+ s.syncAsSint32LE(TagStates[i].enabled);
+ }
+
+ for (i = 0; i < MAX_EXITS; i++) {
+ s.syncAsUint32LE(ExitStates[i].tid);
+ s.syncAsSint32LE(ExitStates[i].enabled);
+ }
+
+ s.syncAsSint32LE(nextfreeT);
+ s.syncAsSint32LE(numScenesT);
+ s.syncAsSint32LE(nextfreeE);
+ s.syncAsSint32LE(numScenesE);
+}
+
+/**
+ * This is all totally different to the way the rest of the way polygon
+ * data is stored and restored, more specifically, different to how dead
+ * tags and exits are handled, because of the piecemeal design-by-just-
+ * thought-of-this approach employed.
+ */
+
+void SaveDeadPolys(bool *sdp) {
+ memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool));
+}
+
+void RestoreDeadPolys(bool *sdp) {
+ memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool));
+}
+
+/**
+ * Convert a BLOCKING to an EX_BLOCK poly.
+ */
+void DisableBlock(int blockno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == BLOCKING && Polys[i]->polyID == blockno) {
+ Polys[i]->polytype = EX_BLOCK;
+ deadPolys[i] = true;
+ }
+ }
+}
+
+/**
+ * Convert an EX_BLOCK to a BLOCKING poly.
+ */
+void EnableBlock(int blockno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_BLOCK && Polys[i]->polyID == blockno) {
+ Polys[i]->polytype = BLOCKING;
+ deadPolys[i] = false;
+ }
+ }
+}
+
+/**
+ * Convert a TAG to an EX_TAG poly.
+ */
+void DisableTag(int tagno) {
+ PTAGSTATE pts;
+
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == TAG && Polys[i]->polyID == tagno) {
+ Polys[i]->polytype = EX_TAG;
+ Polys[i]->tagState = TAG_OFF;
+ Polys[i]->pointState = NOT_POINTING;
+ }
+ }
+
+ pts = &TagStates[SceneTags[currentTScene].offset];
+ for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
+ if (pts->tid == tagno) {
+ pts->enabled = false;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert an EX_TAG to a TAG poly.
+ */
+void EnableTag(int tagno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_TAG && Polys[i]->polyID == tagno) {
+ Polys[i]->polytype = TAG;
+ }
+ }
+
+ PTAGSTATE pts;
+ pts = &TagStates[SceneTags[currentTScene].offset];
+ for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
+ if (pts->tid == tagno) {
+ pts->enabled = true;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert an EX_EXIT to a EXIT poly.
+ */
+void EnableExit(int exitno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_EXIT && Polys[i]->polyID == exitno) {
+ Polys[i]->polytype = EXIT;
+ }
+ }
+
+ PTAGSTATE pts;
+ pts = &ExitStates[SceneExits[currentEScene].offset];
+ for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
+ if (pts->tid == exitno) {
+ pts->enabled = true;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert a EXIT to an EX_EXIT poly.
+ */
+void DisableExit(int exitno) {
+ PTAGSTATE pts;
+
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EXIT && Polys[i]->polyID == exitno) {
+ Polys[i]->polytype = EX_EXIT;
+ Polys[i]->tagState = TAG_OFF;
+ Polys[i]->pointState = NOT_POINTING;
+ }
+ }
+
+ pts = &ExitStates[SceneExits[currentEScene].offset];
+ for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
+ if (pts->tid == exitno) {
+ pts->enabled = false;
+ break;
+ }
+ }
+}
+
+HPOLYGON FirstPathPoly(void) {
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]->polytype == PATH)
+ return i;
+ }
+ error("FirstPathPoly() - no PATH polygons!");
+ return NOPOLY;
+}
+
+HPOLYGON GetPolyHandle(int i) {
+ assert(i >= 0 && i <= MAX_POLY);
+
+ return (Polys[i] != NULL) ? i : NOPOLY;
+}
+
+// **************************************************************************
+//
+// Code called to initialise or wrap up a scene:
+//
+// **************************************************************************
+
+/**
+ * Called at the start of a scene, when all polygons have been
+ * initialised, to work out which paths are adjacent to which.
+ */
+static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) {
+ PPOLYGON pp1, pp2;
+ int i, j;
+ int retval = 0;
+
+ CHECK_HP(hp1, "Out of range polygon handle (23)");
+ CHECK_HP(hp2, "Out of range polygon handle (24)");
+ pp1 = Polys[hp1];
+ pp2 = Polys[hp2];
+
+ // Work out (how many of p1's corners is in p2) + (how many of p2's corners is in p1)
+ for (i = 0; i < 4; i++) {
+ if (IsInPolygon(pp1->cx[i], pp1->cy[i], hp2))
+ retval++;
+ if (IsInPolygon(pp2->cx[i], pp2->cy[i], hp1))
+ retval++;
+ }
+
+ // Common corners only count once
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ if (pp1->cx[i] == pp2->cx[j] && pp1->cy[i] == pp2->cy[j])
+ retval--;
+ }
+ }
+ return retval;
+}
+
+static void SetPathAdjacencies() {
+ PPOLYGON p1, p2; // Polygon pointers
+
+ // For each polygon..
+ for (int i1 = 0; i1 < MAX_POLY-1; i1++) {
+ // Get polygon, but only carry on if it's a path
+ p1 = Polys[i1];
+ if (!p1 || p1->polytype != PATH)
+ continue;
+
+ // For each subsequent polygon..
+ for (int i2 = i1 + 1; i2 < MAX_POLY; i2++) {
+ // Get polygon, but only carry on if it's a path
+ p2 = Polys[i2];
+ if (!p2 || p2->polytype != PATH)
+ continue;
+
+ int j = DistinctCorners(i1, i2);
+
+ if (j >= 2) {
+ // Paths are adjacent
+ for (j = 0; j < MAXADJ; j++)
+ if (p1->adjpaths[j] == NULL) {
+ p1->adjpaths[j] = p2;
+ break;
+ }
+#ifdef DEBUG
+ if (j > highestYet)
+ highestYet = j;
+#endif
+ assert(j < MAXADJ); // Number of adjacent paths limit
+ for (j = 0; j < MAXADJ; j++) {
+ if (p2->adjpaths[j] == NULL) {
+ p2->adjpaths[j] = p1;
+ break;
+ }
+ }
+#ifdef DEBUG
+ if (j > highestYet)
+ highestYet = j;
+#endif
+ assert(j < MAXADJ); // Number of adjacent paths limit
+ }
+ }
+ }
+}
+
+/**
+ * Ensure NPATH nodes are not inside another PATH/NPATH polygon.
+ * Only bother with end nodes for now.
+ */
+#ifdef DEBUG
+void CheckNPathIntegrity() {
+ uint8 *pps; // Compiled polygon data
+ PPOLYGON rp; // Run-time polygon structure
+ HPOLYGON hp;
+ const POLY *cp; // Compiled polygon structure
+ int i, j; // Loop counters
+ int n; // Last node in current path
+ int32 *nlistx, *nlisty;
+
+ pps = LockMem(pHandle); // All polygons
+
+ for (i = 0; i < MAX_POLY; i++) { // For each polygon..
+ rp = Polys[i];
+ if (rp && rp->polytype == PATH && rp->subtype == NODE) { //...if it's a node path
+ // Get compiled polygon structure
+ cp = (const POLY *)pps + rp->pIndex; // This polygon
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelisty));
+
+ n = (int)FROM_LE_32(cp->nodecount) - 1; // Last node
+ assert(n >= 1); // Node paths must have at least 2 nodes
+
+ hp = PolyIndex(rp);
+ for (j = 0; j <= n; j++) {
+ if (!IsInPolygon((int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), hp)) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), rp->cx[0], rp->cy[0]);
+ error(tBufferAddr());
+ }
+ }
+
+ // Check end nodes are not in adjacent path
+ for (j = 0; j < MAXADJ; j++) { // For each adjacent path
+ if (rp->adjpaths[j] == NULL)
+ break;
+
+ if (IsInPolygon((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolyIndex(rp->adjpaths[j]))) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
+ error(tBufferAddr())
+ }
+ if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolyIndex(rp->adjpaths[j]))) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
+ error(tBufferAddr())
+ }
+ }
+ }
+ }
+}
+#endif
+
+/**
+ * Called at the start of a scene, nobbles TAG polygons which should be dead.
+ */
+static void SetExBlocks() {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (deadPolys[i]) {
+ if (Polys[i] && Polys[i]->polytype == BLOCKING)
+ Polys[i]->polytype = EX_BLOCK;
+#ifdef DEBUG
+ else
+ error("Impossible message!");
+#endif
+ }
+ }
+}
+
+/**
+ * Called at the start of a scene, nobbles TAG polygons which should be dead.
+ */
+static void SetExTags(SCNHANDLE ph) {
+ PTAGSTATE pts;
+ int i, j;
+
+ for (i = 0; i < numScenesT; i++) {
+ if (SceneTags[i].sid == ph) {
+ currentTScene = i;
+
+ pts = &TagStates[SceneTags[i].offset];
+ for (j = 0; j < SceneTags[i].nooftags; j++, pts++) {
+ if (!pts->enabled)
+ DisableTag(pts->tid);
+ }
+ return;
+ }
+ }
+ i = numScenesT++;
+ currentTScene = i;
+ assert(numScenesT < MAX_SCENES); // Dead tag remembering: scene limit
+
+ SceneTags[i].sid = ph;
+ SceneTags[i].offset = nextfreeT;
+ SceneTags[i].nooftags = 0;
+
+ for (j = 0; j < MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == TAG) {
+ TagStates[nextfreeT].tid = Polys[j]->polyID;
+ TagStates[nextfreeT].enabled = true;
+ nextfreeT++;
+ assert(nextfreeT < MAX_TAGS); // Dead tag remembering: tag limit
+ SceneTags[i].nooftags++;
+ }
+ }
+}
+
+/**
+ * Called at the start of a scene, nobbles EXIT polygons which should be dead.
+ */
+#ifdef DEBUG
+void SetExExits(SCNHANDLE ph) {
+#else
+static void SetExExits(SCNHANDLE ph) {
+#endif
+ PTAGSTATE pts;
+ int i, j;
+
+ for (i = 0; i < numScenesE; i++) {
+ if (SceneExits[i].sid == ph) {
+ currentEScene = i;
+
+ pts = &ExitStates[SceneExits[i].offset];
+ for (j = 0; j < SceneExits[i].nooftags; j++, pts++) {
+ if (!pts->enabled)
+ DisableExit(pts->tid);
+ }
+ return;
+ }
+ }
+
+ i = numScenesE++;
+ currentEScene = i;
+ assert(numScenesE < MAX_SCENES); // Dead exit remembering: scene limit
+
+ SceneExits[i].sid = ph;
+ SceneExits[i].offset = nextfreeE;
+ SceneExits[i].nooftags = 0;
+
+ for (j = 0; j < MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == EXIT) {
+ ExitStates[nextfreeE].tid = Polys[j]->polyID;
+ ExitStates[nextfreeE].enabled = true;
+ nextfreeE++;
+ assert(nextfreeE < MAX_EXITS); // Dead exit remembering: exit limit
+ SceneExits[i].nooftags++;
+ }
+ }
+}
+
+/**
+ * Works out some fixed numbers for a polygon.
+ */
+static void FiddlyBit(POLYGON *p) {
+ int t1, t2; // General purpose temp. variables
+
+ // Enclosing external rectangle
+ t1 = p->cx[0] > p->cx[1] ? p->cx[0] : p->cx[1];
+ t2 = p->cx[2] > p->cx[3] ? p->cx[2] : p->cx[3];
+ p->pright = (short)(t1 > t2 ? t1 : t2);
+
+ t1 = p->cx[0] < p->cx[1] ? p->cx[0] : p->cx[1];
+ t2 = p->cx[2] < p->cx[3] ? p->cx[2] : p->cx[3];
+ p->pleft = (short)(t1 < t2 ? t1 : t2);
+
+ t1 = p->cy[0] > p->cy[1] ? p->cy[0] : p->cy[1];
+ t2 = p->cy[2] > p->cy[3] ? p->cy[2] : p->cy[3];
+ p->pbottom = (short)(t1 > t2 ? t1 : t2);
+
+ t1 = p->cy[0] < p->cy[1] ? p->cy[0] : p->cy[1];
+ t2 = p->cy[2] < p->cy[3] ? p->cy[2] : p->cy[3];
+ p->ptop = (short)(t1 < t2 ? t1 : t2);
+
+ // Rectangles enclosing each side and each side's magic numbers
+ for (t1 = 0; t1 < 4; t1++) {
+ p->lright[t1] = p->cx[t1] > p->cx[(t1+1)%4] ? p->cx[t1] : p->cx[(t1+1)%4];
+ p->lleft[t1] = p->cx[t1] < p->cx[(t1+1)%4] ? p->cx[t1] : p->cx[(t1+1)%4];
+
+ p->ltop[t1] = p->cy[t1] < p->cy[(t1+1)%4] ? p->cy[t1] : p->cy[(t1+1)%4];
+ p->lbottom[t1] = p->cy[t1] > p->cy[(t1+1)%4] ? p->cy[t1] : p->cy[(t1+1)%4];
+
+ p->a[t1] = p->cy[t1] - p->cy[(t1+1)%4];
+ p->b[t1] = p->cx[(t1+1)%4] - p->cx[t1];
+ p->c[t1] = (long)p->cy[t1]*p->cx[(t1+1)%4] - (long)p->cx[t1]*p->cy[(t1+1)%4];
+ }
+}
+
+/**
+ * Calculate a point approximating to the centre of a polygon.
+ * Not very sophisticated.
+ */
+#ifdef DEBUG
+void PseudoCentre(POLYGON *p) {
+#else
+static void PseudoCentre(POLYGON *p) {
+#endif
+ p->pcentrex = (p->cx[0] + p->cx[1] + p->cx[2] + p->cx[3])/4;
+ p->pcentrey = (p->cy[0] + p->cy[1] + p->cy[2] + p->cy[3])/4;
+
+ if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) {
+ int i, top = 0, bot = 0;
+
+ for (i = p->ptop; i <= p->pbottom; i++) {
+ if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) {
+ top = i;
+ break;
+ }
+ }
+ for (i = p->pbottom; i >= p->ptop; i--) {
+ if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) {
+ bot = i;
+ break;
+ }
+ }
+ p->pcentrex = (top+bot)/2;
+ }
+#ifdef DEBUG
+ // assert(IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))); // Pseudo-centre is not in path
+ if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) {
+ sprintf(tBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?",
+ p->cx[0], p->cy[0]);
+ error(tBufferAddr());
+ }
+#endif
+}
+
+/**
+ * Allocate a POLYGON structure.
+ */
+static POLYGON *GetPolyEntry(PTYPE type, const POLY *pp, int pno) {
+ for (int i = 0; i < MaxPolys; i++) {
+ if (!Polys[i]) {
+ POLYGON *p = Polys[i] = &Polygons[i];
+ memset(p, 0, sizeof(POLYGON));
+
+ p->polytype = type; // Polygon type
+ p->pIndex = pno;
+ p->tagState = TAG_OFF;
+ p->pointState = NOT_POINTING;
+ p->polyID = FROM_LE_32(pp->id); // Identifier
+
+ for (int j = 0; j < 4; j++) { // Polygon definition
+ p->cx[j] = (short)FROM_LE_32(pp->x[j]);
+ p->cy[j] = (short)FROM_LE_32(pp->y[j]);
+ }
+
+ return p;
+ }
+ }
+
+ error("Exceeded MaxPolys");
+ return NULL;
+}
+
+/**
+ * Initialise an EXIT polygon.
+ */
+static void InitExit(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(EXIT, pp, pno));
+}
+
+/**
+ * Initialise a PATH or NPATH polygon.
+ */
+static void InitPath(const POLY *pp, bool NodePath, int pno) {
+ POLYGON *p;
+
+ p = GetPolyEntry(PATH, pp, pno); // Obtain a slot
+
+ if (NodePath) {
+ p->subtype = NODE;
+ } else {
+ p->subtype = NORMAL;
+ }
+
+ // Clear out ajacent path pointers
+ memset(p->adjpaths, 0, MAXADJ*sizeof(PPOLYGON));
+
+ FiddlyBit(p);
+ PseudoCentre(p);
+}
+
+
+/**
+ * Initialise a BLOCKING polygon.
+ */
+static void InitBlock(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(BLOCKING, pp, pno));
+}
+
+/**
+ * Initialise an extra BLOCKING polygon related to a moving actor.
+ * The width of the polygon depends on the width of the actor which is
+ * trying to walk through the actor you first thought of.
+ * This is for dynamic blocking.
+ */
+HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) {
+ int caX, caY; // Calling actor co-ords
+ int taX, taY; // Test actor co-ords
+ int left, right;
+
+ GetMActorPosition(ca, &caX, &caY); // Calling actor co-ords
+ GetMActorPosition(ta, &taX, &taY); // Test actor co-ords
+
+ left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX);
+ right = GetMActorRight(ta) + (caX - GetMActorLeft(ca));
+
+ memset(&extraBlock, 0, sizeof(extraBlock));
+
+ // The 3s on the y co-ordinates used to be 10s
+ extraBlock.cx[0] = (short)(left - 2);
+ extraBlock.cy[0] = (short)(taY - 3);
+ extraBlock.cx[1] = (short)(right + 2);
+ extraBlock.cy[1] = (short)(taY - 3);
+ extraBlock.cx[2] = (short)(right + 2);
+ extraBlock.cy[2] = (short)(taY + 3);
+ extraBlock.cx[3] = (short)(left - 2);
+ extraBlock.cy[3] = (short)(taY + 3);
+
+ FiddlyBit(&extraBlock); // Is this necessary?
+
+ Polys[MAX_POLY] = &extraBlock;
+ return MAX_POLY;
+}
+
+/**
+ * Initialise an EFFECT polygon.
+ */
+static void InitEffect(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(EFFECT, pp, pno));
+}
+
+
+/**
+ * Initialise a REFER polygon.
+ */
+static void InitRefer(const POLY *pp, int pno) {
+ POLYGON *p = GetPolyEntry(REFER, pp, pno); // Obtain a slot
+
+ p->subtype = FROM_LE_32(pp->reftype); // Refer type
+
+ FiddlyBit(p);
+}
+
+
+/**
+ * Initialise a TAG polygon.
+ */
+static void InitTag(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(TAG, pp, pno));
+}
+
+
+/**
+ * Called at the start of a scene to initialise the polys in that scene.
+ */
+void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) {
+ const POLY *pp; // Pointer to compiled data polygon structure
+
+ pHandle = ph;
+ noofPolys = numPoly;
+
+ if (Polygons == NULL) {
+ // first time - allocate memory for process list
+ Polygons = (PPOLYGON)calloc(MaxPolys, sizeof(POLYGON));
+
+ // make sure memory allocated
+ if (Polygons == NULL) {
+ error("Cannot allocate memory for polygon data");
+ }
+ }
+
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]) {
+ Polys[i]->pointState = NOT_POINTING;
+ Polys[i] = NULL;
+ }
+ }
+
+ memset(RoutePaths, 0, sizeof(RoutePaths));
+
+ if (!bRestart)
+ memset(deadPolys, 0, sizeof(deadPolys));
+
+ pp = (const POLY *)LockMem(ph);
+ for (int i = 0; i < numPoly; i++, pp++) {
+ switch (FROM_LE_32(pp->type)) {
+ case POLY_PATH:
+ InitPath(pp, false, i);
+ break;
+
+ case POLY_NPATH:
+ InitPath(pp, true, i);
+ break;
+
+ case POLY_BLOCK:
+ InitBlock(pp, i);
+ break;
+
+ case POLY_REFER:
+ InitRefer(pp, i);
+ break;
+
+ case POLY_EFFECT:
+ InitEffect(pp, i);
+ break;
+
+ case POLY_EXIT:
+ InitExit(pp, i);
+ break;
+
+ case POLY_TAG:
+ InitTag(pp, i);
+ break;
+
+ default:
+ error("Unknown polygon type");
+ }
+ }
+ SetPathAdjacencies(); // Paths need to know the facts
+#ifdef DEBUG
+ CheckNPathIntegrity();
+#endif
+ SetExTags(ph); // Some tags may have been killed
+ SetExExits(ph); // Some exits may have been killed
+
+ if (bRestart)
+ SetExBlocks(); // Some blocks may have been killed
+}
+
+/**
+ * Called at the end of a scene to ditch all polygons.
+ */
+void DropPolygons() {
+ pathsOnRoute = 0;
+ memset(RoutePaths, 0, sizeof(RoutePaths));
+ RouteEnd = NULL;
+
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]) {
+ Polys[i]->pointState = NOT_POINTING;
+ Polys[i] = NULL;
+ }
+ }
+ noofPolys = 0;
+ free(Polygons);
+ Polygons = NULL;
+}
+
+
+
+PTYPE PolyType(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (25)");
+
+ return Polys[hp]->polytype;
+}
+
+int PolySubtype(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (26)");
+
+ return Polys[hp]->subtype;
+}
+
+int PolyCentreX(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (27)");
+
+ return Polys[hp]->pcentrex;
+}
+
+int PolyCentreY(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (28)");
+
+ return Polys[hp]->pcentrey;
+}
+
+int PolyCornerX(HPOLYGON hp, int n) {
+ CHECK_HP(hp, "Out of range polygon handle (29)");
+
+ return Polys[hp]->cx[n];
+}
+
+int PolyCornerY(HPOLYGON hp, int n) {
+ CHECK_HP(hp, "Out of range polygon handle (30)");
+
+ return Polys[hp]->cy[n];
+}
+
+PSTATE PolyPointState(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (31)");
+
+ return Polys[hp]->pointState;
+}
+
+TSTATE PolyTagState(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (32)");
+
+ return Polys[hp]->tagState;
+}
+
+SCNHANDLE PolyTagHandle(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (33)");
+
+ return Polys[hp]->oTagHandle;
+}
+
+void SetPolyPointState(HPOLYGON hp, PSTATE ps) {
+ CHECK_HP(hp, "Out of range polygon handle (34)");
+
+ Polys[hp]->pointState = ps;
+}
+
+void SetPolyTagState(HPOLYGON hp, TSTATE ts) {
+ CHECK_HP(hp, "Out of range polygon handle (35)");
+
+ Polys[hp]->tagState = ts;
+}
+
+void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) {
+ CHECK_HP(hp, "Out of range polygon handle (36)");
+
+ Polys[hp]->oTagHandle = th;
+}
+
+void MaxPolygons(int numPolys) {
+ assert(numPolys <= MAX_POLY);
+
+ MaxPolys = numPolys;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/polygons.h b/engines/tinsel/polygons.h
new file mode 100644
index 0000000000..464aa4e124
--- /dev/null
+++ b/engines/tinsel/polygons.h
@@ -0,0 +1,186 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Definition of POLYGON structure and functions in POLYGONS.C
+ */
+
+#ifndef TINSEL_POLYGONS_H // prevent multiple includes
+#define TINSEL_POLYGONS_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/scene.h" // for PPOLY and REEL
+
+namespace Tinsel {
+
+// Note 7/10/94, with adjacency reduction ANKHMAP max is 3, UNSEEN max is 4
+// so reduced this back to 6 (from 12) for now.
+#define MAXADJ 6 // Max number of known adjacent paths
+
+
+// Polygon Types
+enum PTYPE {
+ TEST, PATH, EXIT, BLOCKING,
+ EFFECT, REFER, TAG, EX_TAG, EX_EXIT, EX_BLOCK
+};
+
+// subtype
+#define NORMAL 0
+#define NODE 1 // For paths
+
+// tagState
+enum TSTATE {
+ NO_TAG, TAG_OFF, TAG_ON
+};
+
+// pointState
+enum PSTATE {
+ NO_POINT, NOT_POINTING, POINTING
+};
+
+
+
+struct POLYGON {
+
+ PTYPE polytype; // Polygon type
+
+ int subtype; // refer type in REFER polygons
+ // NODE/NORMAL in PATH polygons
+
+ int pIndex; // Index into compiled polygon data
+
+ /*
+ * Data duplicated from compiled polygon data
+ */
+ short cx[4]; // Corners (clockwise direction)
+ short cy[4];
+ int polyID;
+
+ /* For TAG and EXIT (and EFFECT in future?) polygons only */
+ TSTATE tagState;
+ PSTATE pointState;
+ SCNHANDLE oTagHandle; // Override tag.
+
+ /* For Path polygons only */
+ bool tried;
+
+ /*
+ * Internal derived data for speed and conveniance
+ * set up by FiddlyBit()
+ */
+ short ptop; //
+ short pbottom; // Enclosing external rectangle
+ short pleft; //
+ short pright; //
+
+ short ltop[4]; //
+ short lbottom[4]; // Rectangles enclosing each side
+ short lleft[4]; //
+ short lright[4]; //
+
+ int a[4]; // y1-y2 }
+ int b[4]; // x2-x1 } See IsInPolygon()
+ long c[4]; // y1x2 - x1y2 }
+
+ /*
+ * Internal derived data for speed and conveniance
+ * set up by PseudoCentre()
+ */
+ int pcentrex; // Pseudo-centre
+ int pcentrey; //
+
+ /**
+ * List of adjacent polygons. For Path polygons only.
+ * set up by SetPathAdjacencies()
+ */
+ POLYGON *adjpaths[MAXADJ];
+
+};
+
+
+enum {
+ NOPOLY = -1
+};
+
+
+
+/*-------------------------------------------------------------------------*/
+
+bool IsInPolygon(int xt, int yt, HPOLYGON p);
+HPOLYGON InPolygon(int xt, int yt, PTYPE type);
+void BlockingCorner(HPOLYGON poly, int *x, int *y, int tarx, int tary);
+void FindBestPoint(HPOLYGON path, int *x, int *y, int *line);
+bool IsAdjacentPath(HPOLYGON path1, HPOLYGON path2);
+HPOLYGON getPathOnTheWay(HPOLYGON from, HPOLYGON to);
+int NearestEndNode(HPOLYGON path, int x, int y);
+int NearEndNode(HPOLYGON spath, HPOLYGON dpath);
+int NearestNodeWithin(HPOLYGON npath, int x, int y);
+void NearestCorner(int *x, int *y, HPOLYGON spath, HPOLYGON dpath);
+bool IsPolyCorner(HPOLYGON hPath, int x, int y);
+int GetScale(HPOLYGON path, int y);
+void getNpathNode(HPOLYGON npath, int node, int *px, int *py);
+void getPolyTagInfo(HPOLYGON p, SCNHANDLE *hTagText, int *tagx, int *tagy);
+SCNHANDLE getPolyFilm(HPOLYGON p);
+void getPolyNode(HPOLYGON p, int *px, int *py);
+SCNHANDLE getPolyScript(HPOLYGON p);
+REEL getPolyReelType(HPOLYGON p);
+int32 getPolyZfactor(HPOLYGON p);
+int numNodes(HPOLYGON pp);
+void RebootDeadTags(void);
+void DisableBlock(int blockno);
+void EnableBlock(int blockno);
+void DisableTag(int tagno);
+void EnableTag(int tagno);
+void DisableExit(int exitno);
+void EnableExit(int exitno);
+HPOLYGON FirstPathPoly(void);
+HPOLYGON GetPolyHandle(int i);
+void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart);
+void DropPolygons(void);
+
+
+void SaveDeadPolys(bool *sdp);
+void RestoreDeadPolys(bool *sdp);
+
+/*-------------------------------------------------------------------------*/
+
+PTYPE PolyType(HPOLYGON hp); // ->type
+int PolySubtype(HPOLYGON hp); // ->subtype
+int PolyCentreX(HPOLYGON hp); // ->pcentrex
+int PolyCentreY(HPOLYGON hp); // ->pcentrey
+int PolyCornerX(HPOLYGON hp, int n); // ->cx[n]
+int PolyCornerY(HPOLYGON hp, int n); // ->cy[n]
+PSTATE PolyPointState(HPOLYGON hp); // ->pointState
+TSTATE PolyTagState(HPOLYGON hp); // ->tagState
+SCNHANDLE PolyTagHandle(HPOLYGON hp); // ->oTagHandle
+
+void SetPolyPointState(HPOLYGON hp, PSTATE ps); // ->pointState
+void SetPolyTagState(HPOLYGON hp, TSTATE ts); // ->tagState
+void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th);// ->oTagHandle
+
+void MaxPolygons(int maxPolys);
+
+/*-------------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_POLYGONS_H */
diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp
new file mode 100644
index 0000000000..1ecf43f821
--- /dev/null
+++ b/engines/tinsel/rince.cpp
@@ -0,0 +1,708 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.c"
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static MACTOR Movers[MAX_MOVERS];
+
+
+/**
+ * RebootMovers
+ */
+void RebootMovers(void) {
+ memset(Movers, 0, sizeof(Movers));
+}
+
+/**
+ * Given an actor number, return pointer to its moving actor structure,
+ * if it is a moving actor.
+ */
+PMACTOR GetMover(int ano) {
+ int i;
+
+ // Slot 0 is reserved for lead actor
+ if (ano == LeadId() || ano == LEAD_ACTOR)
+ return &Movers[0];
+
+ for (i = 1; i < MAX_MOVERS; i++)
+ if (Movers[i].actorID == ano)
+ return &Movers[i];
+
+ return NULL;
+}
+
+/**
+ * Register an actor as being a moving one.
+ */
+PMACTOR SetMover(int ano) {
+ int i;
+
+ // Slot 0 is reserved for lead actor
+ if (ano == LeadId() || ano == LEAD_ACTOR) {
+ Movers[0].actorToken = TOKEN_LEAD;
+ Movers[0].actorID = LeadId();
+ return &Movers[0];
+ }
+
+ // Check it hasn't already been declared
+ for (i = 1; i < MAX_MOVERS; i++) {
+ if (Movers[i].actorID == ano) {
+ // Actor is already a moving actor
+ return &Movers[i];
+ }
+ }
+
+ // Find an empty slot
+ for (i = 1; i < MAX_MOVERS; i++)
+ if (!Movers[i].actorID) {
+ Movers[i].actorToken = TOKEN_LEAD + i;
+ Movers[i].actorID = ano;
+ return &Movers[i];
+ }
+
+ error("Too many moving actors");
+}
+
+/**
+ * Given an index, returns the associated moving actor.
+ *
+ * At the time of writing, used by the effect process.
+ */
+PMACTOR GetLiveMover(int index) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ if (Movers[index].MActorState == NORM_MACTOR)
+ return &Movers[index];
+ else
+ return NULL;
+}
+
+bool IsMAinEffectPoly(int index) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ return Movers[index].InEffect;
+}
+
+void SetMAinEffectPoly(int index, bool tf) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ Movers[index].InEffect = tf;
+}
+
+/**
+ * Remove a moving actor from the current scene.
+ */
+void KillMActor(PMACTOR pActor) {
+ if (pActor->MActorState == NORM_MACTOR) {
+ pActor->MActorState = NO_MACTOR;
+ MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj);
+ pActor->actorObj = NULL;
+ assert(CurrentProcess() != pActor->pProc);
+ ProcessKill(pActor->pProc);
+ }
+}
+
+/**
+ * getMActorState
+ */
+MAS getMActorState(PMACTOR pActor) {
+ return pActor->MActorState;
+}
+
+/**
+ * If the actor's object exists, move it behind the background.
+ * MultiHideObject() is deliberately not used, as StepAnimScript() calls
+ * cause the object to re-appear.
+ */
+void hideMActor(PMACTOR pActor, int sf) {
+ assert(pActor); // Hiding null moving actor
+
+ pActor->aHidden = true;
+ pActor->SlowFactor = sf;
+
+ if (pActor->actorObj)
+ MultiSetZPosition(pActor->actorObj, -1);
+}
+
+/**
+ * getMActorHideState
+ */
+bool getMActorHideState(PMACTOR pActor) {
+ if (pActor)
+ return pActor->aHidden;
+ else
+ return false;
+}
+
+/**
+ * unhideMActor
+ */
+void unhideMActor(PMACTOR pActor) {
+ assert(pActor); // unHiding null moving actor
+
+ pActor->aHidden = false;
+
+ // Make visible on the screen
+ if (pActor->actorObj) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+ }
+}
+
+/**
+ * Get it into our heads that there's nothing doing.
+ * Called at the end of a scene.
+ */
+void DropMActors(void) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ Movers[i].MActorState = NO_MACTOR;
+ Movers[i].objx = 0;
+ Movers[i].objy = 0;
+ Movers[i].actorObj = NULL; // No moving actor objects
+
+ Movers[i].hCpath = NOPOLY; // No moving actor path
+ }
+}
+
+
+/**
+ * Reposition a moving actor.
+ */
+void MoveMActor(PMACTOR pActor, int x, int y) {
+ int z;
+ int node;
+ HPOLYGON hPath;
+
+ assert(pActor); // Moving null moving actor
+ assert(pActor->actorObj);
+
+ pActor->objx = x;
+ pActor->objy = y;
+ MultiSetAniXY(pActor->actorObj, x, y);
+
+ hPath = InPolygon(x, y, PATH);
+ if (hPath != NOPOLY) {
+ pActor->hCpath = hPath;
+ if (PolySubtype(hPath) == NODE) {
+ node = NearestNodeWithin(hPath, x, y);
+ getNpathNode(hPath, node, &pActor->objx, &pActor->objy);
+ pActor->hFnpath = hPath;
+ pActor->line = node;
+ pActor->npstatus = GOING_UP;
+ } else {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ }
+
+ z = GetScale(hPath, pActor->objy);
+ pActor->scale = z;
+ SetMActorStanding(pActor);
+ } else {
+ pActor->bNoPath = true;
+
+ pActor->hFnpath = NOPOLY; // Ain't in one
+ pActor->npstatus = NOT_IN;
+
+ // Ensure legal reel and scale
+ if (pActor->dirn < 0 || pActor->dirn > 3)
+ pActor->dirn = FORWARD;
+ if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES)
+ pActor->scale = 1;
+ }
+}
+
+/**
+ * Get position of a moving actor.
+ */
+void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) {
+ assert(pActor); // Getting null moving actor's position
+
+ if (pActor->actorObj != NULL)
+ GetAniPosition(pActor->actorObj, paniX, paniY);
+ else {
+ *paniX = 0;
+ *paniY = 0;
+ }
+}
+
+/**
+ * Moving actor's mid-top position.
+ */
+void GetMActorMidTopPosition(PMACTOR pActor, int *aniX, int *aniY) {
+ assert(pActor); // Getting null moving actor's mid-top position
+ assert(pActor->actorObj); // Getting null moving actor's mid-top position
+
+ *aniX = (MultiLeftmost(pActor->actorObj) + MultiRightmost(pActor->actorObj))/2;
+ *aniY = MultiHighest(pActor->actorObj);
+}
+
+/**
+ * Moving actor's left-most co-ordinate.
+ */
+int GetMActorLeft(PMACTOR pActor) {
+ assert(pActor); // Getting null moving actor's leftmost position
+ assert(pActor->actorObj); // Getting null moving actor's leftmost position
+
+ return MultiLeftmost(pActor->actorObj);
+}
+
+/**
+ * Moving actor's right-most co-ordinate.
+ */
+int GetMActorRight(PMACTOR pActor) {
+ assert(pActor); // Getting null moving actor's rightmost position
+ assert(pActor->actorObj); // Getting null moving actor's rightmost position
+
+ return MultiRightmost(pActor->actorObj);
+}
+
+/**
+ * See if moving actor is stood within a polygon.
+ */
+bool MActorIsInPolygon(PMACTOR pActor, HPOLYGON hp) {
+ assert(pActor); // Checking if null moving actor is in polygon
+ assert(pActor->actorObj); // Checking if null moving actor is in polygon
+
+ int aniX, aniY;
+ GetAniPosition(pActor->actorObj, &aniX, &aniY);
+
+ return IsInPolygon(aniX, aniY, hp);
+}
+
+/**
+ * Change which reel is playing for a moving actor.
+ */
+void AlterMActor(PMACTOR pActor, SCNHANDLE film, AR_FUNCTION fn) {
+ const FILM *pfilm;
+
+ assert(pActor->actorObj); // Altering null moving actor's animation script
+
+ if (fn == AR_POPREEL) {
+ film = pActor->pushedfilm; // Use the saved film
+ }
+ if (fn == AR_PUSHREEL) {
+ // Save the one we're replacing
+ pActor->pushedfilm = (pActor->TagReelRunning) ? pActor->lastfilm : 0;
+ }
+
+ if (film == 0) {
+ if (pActor->TagReelRunning) {
+ // Revert to 'normal' actor
+ SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true);
+ pActor->TagReelRunning = false;
+ }
+ } else {
+ pActor->lastfilm = film; // Remember this one
+
+ pfilm = (const FILM *)LockMem(film);
+ assert(pfilm != NULL);
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ pActor->scount = 0;
+
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+
+ if (fn == AR_WALKREEL) {
+ pActor->TagReelRunning = false;
+ pActor->walkReel = true;
+ } else {
+ pActor->TagReelRunning = true;
+ pActor->walkReel = false;
+
+#ifdef DEBUG
+ assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished!
+#else
+ StepAnimScript(&pActor->actorAnim); // 04/01/95
+#endif
+ }
+
+ // Hang on, we may not want him yet! 04/01/95
+ if (pActor->aHidden)
+ MultiSetZPosition(pActor->actorObj, -1);
+ }
+}
+
+/**
+ * Return the actor's direction.
+ */
+DIRREEL GetMActorDirection(PMACTOR pActor) {
+ return pActor->dirn;
+}
+
+/**
+ * Return the actor's scale.
+ */
+int GetMActorScale(PMACTOR pActor) {
+ return pActor->scale;
+}
+
+/**
+ * Point actor in specified derection
+ */
+void SetMActorDirection(PMACTOR pActor, DIRREEL dirn) {
+ pActor->dirn = dirn;
+}
+
+/**
+ * MAmoving
+ */
+bool MAmoving(PMACTOR pActor) {
+ return pActor->bMoving;
+}
+
+/**
+ * Return an actor's walk ticket.
+ */
+int GetActorTicket(PMACTOR pActor) {
+ return pActor->ticket;
+}
+
+/**
+ * Get actor to adopt its appropriate standing reel.
+ */
+void SetMActorStanding(PMACTOR pActor) {
+ assert(pActor->actorObj);
+ AlterMActor(pActor, pActor->StandReels[pActor->scale-1][pActor->dirn], AR_NORMAL);
+}
+
+/**
+ * Get actor to adopt its appropriate walking reel.
+ */
+void SetMActorWalkReel(PMACTOR pActor, DIRREEL reel, int scale, bool force) {
+ SCNHANDLE whichReel;
+ const FILM *pfilm;
+
+ // Kill off any play that may be going on for this actor
+ // and restore the real actor
+ storeActorReel(pActor->actorID, NULL, 0, NULL, 0, 0, 0);
+ unhideMActor(pActor);
+
+ // Don't do it if using a special walk reel
+ if (pActor->walkReel)
+ return;
+
+ if (force || pActor->scale != scale || pActor->dirn != reel) {
+ assert(reel >= 0 && reel <= 3 && scale > 0 && scale <= TOTAL_SCALES); // out of range scale or reel
+
+ // If scale change and both are regular scales
+ // and there's a scaling reel in the right direction
+ if (pActor->scale != scale
+ && scale <= NUM_MAINSCALES && pActor->scale <= NUM_MAINSCALES
+ && (whichReel = ScalingReel(pActor->actorID, pActor->scale, scale, reel)) != 0) {
+// error("Cripes!");
+ ; // Use what is now in 'whichReel'
+ } else {
+ whichReel = pActor->WalkReels[scale-1][reel];
+ assert(whichReel); // no reel
+ }
+
+ pfilm = (const FILM *)LockMem(whichReel);
+ assert(pfilm != NULL); // no film
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), 1);
+
+ // Synchronised walking reels
+ SkipFrames(&pActor->actorAnim, pActor->scount);
+
+ pActor->scale = scale;
+ pActor->dirn = reel;
+ }
+}
+
+/**
+ * Sort some stuff out at actor start-up time.
+ */
+static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) {
+ HPOLYGON hPath;
+ int node;
+ int z;
+
+ pActor->objx = xpos;
+ pActor->objy = ypos;
+
+ /*--------------------------------------
+ | If Actor is in a follow nodes path, |
+ | position it at the nearest node. |
+ --------------------------------------*/
+ hPath = InPolygon(xpos, ypos, PATH);
+
+ if (hPath != NOPOLY) {
+ pActor->hCpath = hPath;
+ if (PolySubtype(hPath) == NODE) {
+ node = NearestNodeWithin(hPath, xpos, ypos);
+ getNpathNode(hPath, node, &pActor->objx, &pActor->objy);
+ pActor->hFnpath = hPath;
+ pActor->line = node;
+ pActor->npstatus = GOING_UP;
+ }
+
+ z = GetScale(hPath, pActor->objy);
+ } else {
+ pActor->bNoPath = true;
+
+ z = GetScale(FirstPathPoly(), pActor->objy);
+ }
+ SetMActorWalkReel(pActor, FORWARD, z, false);
+}
+
+/**
+ * Clear everything out at actor start-up time.
+ */
+static void InitMActor(PMACTOR pActor) {
+
+ pActor->objx = pActor->objy = 0;
+ pActor->targetX = pActor->targetY = -1;
+ pActor->ItargetX = pActor->ItargetY = -1;
+ pActor->hIpath = NOPOLY;
+ pActor->UtargetX = pActor->UtargetY = -1;
+ pActor->hUpath = NOPOLY;
+ pActor->hCpath = NOPOLY;
+
+ pActor->over = false;
+ pActor->InDifficulty = NO_PROB;
+
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ pActor->line = 0;
+
+ pActor->Tline = 0;
+
+ pActor->TagReelRunning = false;
+
+ if (pActor->dirn != FORWARD || pActor->dirn != AWAY
+ || pActor->dirn != LEFTREEL || pActor->dirn != RIGHTREEL)
+ pActor->dirn = FORWARD;
+
+ if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES)
+ pActor->scale = 1;
+
+ pActor->scount = 0;
+
+ pActor->fromx = pActor->fromy = 0;
+
+ pActor->bMoving = false;
+ pActor->bNoPath = false;
+ pActor->bIgPath = false;
+ pActor->walkReel = false;
+
+ pActor->actorObj = NULL;
+
+ pActor->lastfilm = 0;
+ pActor->pushedfilm = 0;
+
+ pActor->InEffect = false;
+ pActor->aHidden = false; // 20/2/95
+}
+
+static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) {
+ const FILM *pfilm;
+ const MULTI_INIT *pmi;
+ const FRAME *pFrame;
+ PIMAGE pim;
+
+
+ assert(BackPal()); // Can't start actor without a background palette
+ assert(pActor->WalkReels[0][FORWARD]); // Starting actor process without walk reels
+
+ InitMActor(pActor);
+ InitialPathChecks(pActor, X, Y);
+
+ pfilm = (const FILM *)LockMem(pActor->WalkReels[0][FORWARD]);
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfilm->reels[0].mobj));
+
+//---
+ pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame));
+
+ // get pointer to image
+ pim = (PIMAGE)LockMem(READ_LE_UINT32(pFrame)); // handle to image
+ pim->hImgPal = TO_LE_32(BackPal());
+//---
+ pActor->actorObj = MultiInitObject(pmi);
+
+/**/ assert(pActor->actorID == id);
+ pActor->actorID = id;
+
+ // add it to display list
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj);
+ storeActorReel(id, NULL, 0, pActor->actorObj, 0, 0, 0);
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ pActor->scount = 0;
+
+ MultiSetAniXY(pActor->actorObj, pActor->objx, pActor->objy);
+
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+
+ // Make him the right size
+ SetMActorStanding(pActor);
+
+//**** if added 18/11/94, am
+ if (X != MAGICX && Y != MAGICY) {
+ hideMActor(pActor, 0); // Allows a play to come in before this appears
+ pActor->aHidden = false; // ...but don't stay hidden
+ }
+
+ pActor->MActorState = NORM_MACTOR;
+}
+
+/**
+ * Moving actor process - 1 per moving actor in current scene.
+ */
+void MActorProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = *(PMACTOR *)ProcessGetParamsSelf();
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ if (pActor->TagReelRunning) {
+ if (!pActor->aHidden)
+#ifdef DEBUG
+ assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished!
+#else
+ StepAnimScript(&pActor->actorAnim);
+#endif
+ } else
+ DoMoveActor(pActor);
+
+ CORO_SLEEP(1); // allow rescheduling
+
+ }
+
+ CORO_END_CODE;
+}
+
+void MActorProcessCreate(int X, int Y, int id, PMACTOR pActor) {
+ MActorProcessHelper(X, Y, id, pActor);
+ pActor->pProc = CoroutineInstall(PID_MACTOR, MActorProcess, &pActor, sizeof(PMACTOR));
+}
+
+
+/**
+ * Check for moving actor collision.
+ */
+PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) {
+ int caX; // Calling actor's pos'n
+ int caL, caR; // Calling actor's left and right
+ int taX, taY; // Test actor's pos'n
+ int taL, taR; // Test actor's left and right
+
+ caX = pActor->objx;
+ if (pActor->hFnpath != NOPOLY || bNoBlocking)
+ return NULL;
+
+ caL = GetMActorLeft(pActor) + x - caX;
+ caR = GetMActorRight(pActor) + x - caX;
+
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ if (pActor == &Movers[i] || Movers[i].MActorState == NO_MACTOR)
+ continue;
+
+ // At around the same height?
+ GetMActorPosition(&Movers[i], &taX, &taY);
+ if (Movers[i].hFnpath != NOPOLY)
+ continue;
+
+ if (ABS(y - taY) > 2) // 2 was 8
+ continue;
+
+ // To the left?
+ taL = GetMActorLeft(&Movers[i]);
+ if (caR <= taL)
+ continue;
+
+ // To the right?
+ taR = GetMActorRight(&Movers[i]);
+ if (caL >= taR)
+ continue;
+
+ return &Movers[i];
+ }
+ return NULL;
+}
+
+/**
+ * Copies key information for savescn.c to store away.
+ */
+void SaveMovers(SAVED_MOVER *sMoverInfo) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ sMoverInfo[i].MActorState= Movers[i].MActorState;
+ sMoverInfo[i].actorID = Movers[i].actorID;
+ sMoverInfo[i].objx = Movers[i].objx;
+ sMoverInfo[i].objy = Movers[i].objy;
+ sMoverInfo[i].lastfilm = Movers[i].lastfilm;
+
+ memcpy(sMoverInfo[i].WalkReels, Movers[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(sMoverInfo[i].StandReels, Movers[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(sMoverInfo[i].TalkReels, Movers[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ }
+}
+
+void RestoreAuxScales(SAVED_MOVER *sMoverInfo) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ memcpy(Movers[i].WalkReels, sMoverInfo[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(Movers[i].StandReels, sMoverInfo[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(Movers[i].TalkReels, sMoverInfo[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h
new file mode 100644
index 0000000000..a3ed5bd845
--- /dev/null
+++ b/engines/tinsel/rince.h
@@ -0,0 +1,209 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.h"
+ */
+
+#ifndef TINSEL_RINCE_H // prevent multiple includes
+#define TINSEL_RINCE_H
+
+#include "tinsel/anim.h" // for ANIM
+#include "tinsel/scene.h" // for TFTYPE
+
+namespace Tinsel {
+
+struct OBJECT;
+struct PROCESS;
+
+enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING};
+
+enum IND {NO_PROB, TRY_CENTRE, TRY_CORNER, TRY_NEXTCORNER};
+
+enum MAS {NO_MACTOR, NORM_MACTOR};
+
+enum DIRREEL{ LEFTREEL, RIGHTREEL, FORWARD, AWAY };
+
+enum {
+ NUM_MAINSCALES = 5,
+ NUM_AUXSCALES = 5,
+ TOTAL_SCALES = NUM_MAINSCALES + NUM_AUXSCALES
+};
+
+struct MACTOR {
+
+ int objx; /* Co-ordinates object */
+ int objy;
+
+ int targetX, targetY;
+ int ItargetX, ItargetY; /* Intermediate destination */
+ HPOLYGON hIpath;
+ int UtargetX, UtargetY; /* Ultimate destination */
+ HPOLYGON hUpath;
+
+ HPOLYGON hCpath;
+
+ bool over;
+ int ticket;
+
+ IND InDifficulty;
+
+/* For use in 'follow nodes' polygons */
+ HPOLYGON hFnpath;
+ NPS npstatus;
+ int line;
+
+ int Tline; // NEW
+
+ bool TagReelRunning;
+
+
+ /* Used internally */
+ DIRREEL dirn; // Current reel
+ int scale; // Current scale
+ int scount; // Step count for walking reel synchronisation
+
+ unsigned fromx;
+ unsigned fromy;
+
+ bool bMoving; // Set this to TRUE during a walk
+
+ bool bNoPath;
+ bool bIgPath;
+ bool walkReel;
+
+ OBJECT *actorObj; // Actor's object
+ ANIM actorAnim; // Actor's animation script
+
+ SCNHANDLE lastfilm; // } Used by AlterActor()
+ SCNHANDLE pushedfilm; // }
+
+ int actorID;
+ int actorToken;
+
+ SCNHANDLE WalkReels[TOTAL_SCALES][4];
+ SCNHANDLE StandReels[TOTAL_SCALES][4];
+ SCNHANDLE TalkReels[TOTAL_SCALES][4];
+
+ MAS MActorState;
+
+ bool aHidden;
+ int SlowFactor; // Slow down movement while hidden
+
+ bool stop;
+
+ /* NOTE: If effect polys can overlap, this needs improving */
+ bool InEffect;
+
+ PROCESS *pProc;
+};
+typedef MACTOR *PMACTOR;
+
+//---------------------------------------------------------------------------
+
+
+void MActorProcessCreate(int X, int Y, int id, MACTOR *pActor);
+
+
+enum AR_FUNCTION { AR_NORMAL, AR_PUSHREEL, AR_POPREEL, AR_WALKREEL };
+
+
+MACTOR *GetMover(int ano);
+MACTOR *SetMover(int ano);
+void KillMActor(MACTOR *pActor);
+MACTOR *GetLiveMover(int index);
+
+MAS getMActorState(MACTOR *psActor);
+
+void hideMActor(MACTOR *pActor, int sf);
+bool getMActorHideState(MACTOR *pActor);
+void unhideMActor(MACTOR *pActor);
+void DropMActors(void);
+void MoveMActor(MACTOR *pActor, int x, int y);
+
+void GetMActorPosition(MACTOR *pActor, int *aniX, int *aniY);
+void GetMActorMidTopPosition(MACTOR *pActor, int *aniX, int *aniY);
+int GetMActorLeft(MACTOR *pActor);
+int GetMActorRight(MACTOR *pActor);
+
+bool MActorIsInPolygon(MACTOR *pActor, HPOLYGON hPoly);
+void AlterMActor(MACTOR *actor, SCNHANDLE film, AR_FUNCTION fn);
+DIRREEL GetMActorDirection(MACTOR *pActor);
+int GetMActorScale(MACTOR *pActor);
+void SetMActorDirection(MACTOR *pActor, DIRREEL dirn);
+void SetMActorStanding(MACTOR *actor);
+void SetMActorWalkReel(MACTOR *actor, DIRREEL reel, int scale, bool force);
+
+MACTOR *InMActorBlock(MACTOR *pActor, int x, int y);
+
+void RebootMovers(void);
+
+bool IsMAinEffectPoly(int index);
+void SetMAinEffectPoly(int index, bool tf);
+
+bool MAmoving(MACTOR *pActor);
+
+int GetActorTicket(MACTOR *pActor);
+
+/*----------------------------------------------------------------------*/
+
+struct SAVED_MOVER {
+
+ MAS MActorState;
+ int actorID;
+ int objx;
+ int objy;
+ SCNHANDLE lastfilm;
+
+ SCNHANDLE WalkReels[TOTAL_SCALES][4];
+ SCNHANDLE StandReels[TOTAL_SCALES][4];
+ SCNHANDLE TalkReels[TOTAL_SCALES][4];
+
+};
+
+void SaveMovers(SAVED_MOVER *sMoverInfo);
+void RestoreAuxScales(SAVED_MOVER *sMoverInfo);
+
+/*----------------------------------------------------------------------*/
+
+/*
+* Dodgy bit...
+* These functions are now in MAREELS.C, but I can't be bothered to
+* create a new header file.
+*/
+SCNHANDLE GetMactorTalkReel(MACTOR *pAactor, TFTYPE dirn);
+
+void setscalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away);
+SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel);
+void RebootScalingReels(void);
+
+enum {
+ MAGICX = -101,
+ MAGICY = -102
+};
+
+/*----------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_RINCE_H */
diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp
new file mode 100644
index 0000000000..5cb149eb37
--- /dev/null
+++ b/engines/tinsel/saveload.cpp
@@ -0,0 +1,472 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Save and restore scene and game.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/dw.h"
+#include "tinsel/inventory.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/serializer.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinlib.h"
+#include "tinsel/tinsel.h"
+
+#include "common/savefile.h"
+
+namespace Tinsel {
+
+
+/**
+ * The current savegame format version.
+ * Our save/load system uses an elaborate scheme to allow us to modify the
+ * savegame while keeping full backward compatibility, in the sense that newer
+ * ScummVM versions always are able to load old savegames.
+ * In order to achieve that, we store a version in the savegame files, and whenever
+ * the savegame layout is modified, the version is incremented.
+ *
+ * This roughly works by marking each savegame entry with a range of versions
+ * for which it is valid; the save/load code iterates over all entries, but
+ * only saves/loads those which are valid for the version of the savegame
+ * which is being loaded/saved currently.
+ */
+#define CURRENT_VER 1
+// TODO: Not yet used
+
+/**
+ * An auxillary macro, used to specify savegame versions. We use this instead
+ * of just writing the raw version, because this way they stand out more to
+ * the reading eye, making it a bit easier to navigate through the code.
+ */
+#define VER(x) x
+
+
+
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+// in DOS_DW.C
+extern void syncSCdata(Serializer &s);
+
+// in DOS_MAIN.C
+//char HardDriveLetter(void);
+
+// in PCODE.C
+extern void syncGlobInfo(Serializer &s);
+
+// in POLYGONS.C
+extern void syncPolyInfo(Serializer &s);
+
+// in SAVESCN.CPP
+extern void RestoreScene(SAVED_DATA *sd, bool bFadeOut);
+
+//----------------- LOCAL DEFINES --------------------
+
+struct SaveGameHeader {
+ uint32 id;
+ uint32 size;
+ uint32 ver;
+ char desc[SG_DESC_LEN];
+ struct tm dateTime;
+};
+
+enum {
+ SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld ScummVM"
+ SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int numSfiles = 0;
+static SFILES savedFiles[MAX_SFILES];
+
+static bool NeedLoad = true;
+
+static SAVED_DATA *srsd = 0;
+static int RestoreGameNumber = 0;
+static char *SaveSceneName = 0;
+static const char *SaveSceneDesc = 0;
+static int *SaveSceneSsCount = 0;
+static char *SaveSceneSsData = 0; // points to 'SAVED_DATA ssdata[MAX_NEST]'
+
+static SRSTATE SRstate = SR_IDLE;
+
+//------------- SAVE/LOAD SUPPORT METHODS ----------------
+
+static void syncTime(Serializer &s, struct tm &t) {
+ s.syncAsUint16LE(t.tm_year);
+ s.syncAsByte(t.tm_mon);
+ s.syncAsByte(t.tm_mday);
+ s.syncAsByte(t.tm_hour);
+ s.syncAsByte(t.tm_min);
+ s.syncAsByte(t.tm_sec);
+ if (s.isLoading()) {
+ t.tm_wday = 0;
+ t.tm_yday = 0;
+ t.tm_isdst = 0;
+ }
+}
+
+static bool syncSaveGameHeader(Serializer &s, SaveGameHeader &hdr) {
+ s.syncAsUint32LE(hdr.id);
+ s.syncAsUint32LE(hdr.size);
+ s.syncAsUint32LE(hdr.ver);
+
+ s.syncBytes((byte *)hdr.desc, SG_DESC_LEN);
+
+ syncTime(s, hdr.dateTime);
+
+ int tmp = hdr.size - s.bytesSynced();
+ // Perform sanity check
+ if (tmp < 0 || hdr.id != SAVEGAME_ID || hdr.ver > CURRENT_VER || hdr.size > 1024)
+ return false;
+ // Skip over any extra bytes
+ while (tmp-- > 0) {
+ byte b = 0;
+ s.syncAsByte(b);
+ }
+ return true;
+}
+
+static void syncSavedMover(Serializer &s, SAVED_MOVER &sm) {
+ SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.WalkReels, (SCNHANDLE *)&sm.StandReels, (SCNHANDLE *)&sm.TalkReels };
+
+ s.syncAsUint32LE(sm.MActorState);
+ s.syncAsSint32LE(sm.actorID);
+ s.syncAsSint32LE(sm.objx);
+ s.syncAsSint32LE(sm.objy);
+ s.syncAsUint32LE(sm.lastfilm);
+
+ for (int pIndex = 0; pIndex < 3; ++pIndex) {
+ SCNHANDLE *p = pList[pIndex];
+ for (int i = 0; i < TOTAL_SCALES * 4; ++i)
+ s.syncAsUint32LE(*p++);
+ }
+}
+
+static void syncSavedActor(Serializer &s, SAVED_ACTOR &sa) {
+ s.syncAsUint16LE(sa.actorID);
+ s.syncAsUint16LE(sa.z);
+ s.syncAsUint32LE(sa.bAlive);
+ s.syncAsUint32LE(sa.presFilm);
+ s.syncAsUint16LE(sa.presRnum);
+ s.syncAsUint16LE(sa.presX);
+ s.syncAsUint16LE(sa.presY);
+}
+
+extern void syncAllActorsAlive(Serializer &s);
+
+static void syncNoScrollB(Serializer &s, NOSCROLLB &ns) {
+ s.syncAsSint32LE(ns.ln);
+ s.syncAsSint32LE(ns.c1);
+ s.syncAsSint32LE(ns.c2);
+}
+
+static void syncSavedData(Serializer &s, SAVED_DATA &sd) {
+ s.syncAsUint32LE(sd.SavedSceneHandle);
+ s.syncAsUint32LE(sd.SavedBgroundHandle);
+ for (int i = 0; i < MAX_MOVERS; ++i)
+ syncSavedMover(s, sd.SavedMoverInfo[i]);
+ for (int i = 0; i < MAX_SAVED_ACTORS; ++i)
+ syncSavedActor(s, sd.SavedActorInfo[i]);
+
+ s.syncAsSint32LE(sd.NumSavedActors);
+ s.syncAsSint32LE(sd.SavedLoffset);
+ s.syncAsSint32LE(sd.SavedToffset);
+ for (int i = 0; i < MAX_INTERPRET; ++i)
+ sd.SavedICInfo[i].syncWithSerializer(s);
+ for (int i = 0; i < MAX_POLY; ++i)
+ s.syncAsUint32LE(sd.SavedDeadPolys[i]);
+ s.syncAsUint32LE(sd.SavedControl);
+ s.syncAsUint32LE(sd.SavedMidi);
+ s.syncAsUint32LE(sd.SavedLoop);
+ s.syncAsUint32LE(sd.SavedNoBlocking);
+
+ // SavedNoScrollData
+ for (int i = 0; i < MAX_VNOSCROLL; ++i)
+ syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]);
+ for (int i = 0; i < MAX_HNOSCROLL; ++i)
+ syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]);
+ s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV);
+ s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH);
+}
+
+
+/**
+ * Called when saving a game to a new file.
+ * Generates a new, unique, filename.
+ */
+static char *NewName(void) {
+ static char result[FNAMELEN];
+ int i;
+ int ano = 1; // Allocated number
+
+ while (1) {
+ Common::String fname = _vm->getSavegameFilename(ano);
+ strcpy(result, fname.c_str());
+
+ for (i = 0; i < numSfiles; i++)
+ if (!strcmp(savedFiles[i].name, result))
+ break;
+
+ if (i == numSfiles)
+ break;
+ ano++;
+ }
+
+ return result;
+}
+
+/**
+ * Interrogate the current DOS directory for saved game files.
+ * Store the file details, ordered by time, in savedFiles[] and return
+ * the number of files found).
+ */
+int getList(void) {
+ // No change since last call?
+ // TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
+ if (!NeedLoad)
+ return numSfiles;
+
+ int i;
+
+ const Common::String pattern = _vm->getSavegamePattern();
+ Common::StringList files = _vm->getSaveFileMan()->listSavefiles(pattern.c_str());
+
+ numSfiles = 0;
+
+ for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) {
+ if (numSfiles >= MAX_SFILES)
+ break;
+
+ const Common::String &fname = *file;
+ Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(fname.c_str());
+ if (f == NULL) {
+ continue;
+ }
+
+ // Try to load save game header
+ Serializer s(f, 0);
+ SaveGameHeader hdr;
+ bool validHeader = syncSaveGameHeader(s, hdr);
+ delete f;
+ if (!validHeader) {
+ continue; // Invalid header, or savegame too new -> skip it
+ // TODO: In SCUMM, we still show an entry for the save, but with description
+ // "incompatible version".
+ }
+
+ i = numSfiles;
+#ifndef DISABLE_SAVEGAME_SORTING
+ for (i = 0; i < numSfiles; i++) {
+ if (difftime(mktime(&hdr.dateTime), mktime(&savedFiles[i].dateTime)) > 0) {
+ Common::copy_backward(&savedFiles[i], &savedFiles[numSfiles], &savedFiles[numSfiles + 1]);
+ break;
+ }
+ }
+#endif
+
+ strncpy(savedFiles[i].name, fname.c_str(), FNAMELEN);
+ strncpy(savedFiles[i].desc, hdr.desc, SG_DESC_LEN);
+ savedFiles[i].dateTime = hdr.dateTime;
+
+ ++numSfiles;
+ }
+
+ // Next getList() needn't do its stuff again
+ NeedLoad = false;
+
+ return numSfiles;
+}
+
+
+char *ListEntry(int i, letype which) {
+ if (i == -1)
+ i = numSfiles;
+
+ assert(i >= 0);
+
+ if (i < numSfiles)
+ return which == LE_NAME ? savedFiles[i].name : savedFiles[i].desc;
+ else
+ return NULL;
+}
+
+static void DoSync(Serializer &s) {
+ int sg;
+
+ syncSavedData(s, *srsd);
+ syncGlobInfo(s); // Glitter globals
+ syncInvInfo(s); // Inventory data
+
+ // Held object
+ if (s.isSaving())
+ sg = WhichItemHeld();
+ s.syncAsSint32LE(sg);
+ if (s.isLoading())
+ HoldItem(sg);
+
+ syncTimerInfo(s); // Timer data
+ syncPolyInfo(s); // Dead polygon data
+ syncSCdata(s); // Hook Scene and delayed scene
+
+ s.syncAsSint32LE(*SaveSceneSsCount);
+
+ if (*SaveSceneSsCount != 0) {
+ SAVED_DATA *sdPtr = (SAVED_DATA *)SaveSceneSsData;
+ for (int i = 0; i < *SaveSceneSsCount; ++i, ++sdPtr)
+ syncSavedData(s, *sdPtr);
+ }
+
+ syncAllActorsAlive(s);
+}
+
+/**
+ * DoRestore
+ */
+static bool DoRestore(void) {
+ Common::InSaveFile *f;
+ uint32 id;
+
+ f = _vm->getSaveFileMan()->openForLoading(savedFiles[RestoreGameNumber].name);
+ if (f == NULL) {
+ return false;
+ }
+
+ Serializer s(f, 0);
+ SaveGameHeader hdr;
+ if (!syncSaveGameHeader(s, hdr)) {
+ delete f; // Invalid header, or savegame too new -> skip it
+ return false;
+ }
+
+ DoSync(s);
+
+ id = f->readSint32LE();
+ if (id != (uint32)0xFEEDFACE)
+ error("Incompatible saved game");
+
+ bool failed = f->ioFailed();
+
+ delete f;
+
+ return !failed;
+}
+
+/**
+ * DoSave
+ */
+static void DoSave(void) {
+ Common::OutSaveFile *f;
+ const char *fname;
+
+ // Next getList() must do its stuff again
+ NeedLoad = true;
+
+ if (SaveSceneName == NULL)
+ SaveSceneName = NewName();
+ if (SaveSceneDesc[0] == 0)
+ SaveSceneDesc = "unnamed";
+
+ fname = SaveSceneName;
+
+ f = _vm->getSaveFileMan()->openForSaving(fname);
+ if (f == NULL)
+ return;
+
+ Serializer s(0, f);
+
+ // Write out a savegame header
+ SaveGameHeader hdr;
+ hdr.id = SAVEGAME_ID;
+ hdr.size = SAVEGAME_HEADER_SIZE;
+ hdr.ver = CURRENT_VER;
+ memcpy(hdr.desc, SaveSceneDesc, SG_DESC_LEN);
+ g_system->getTimeAndDate(hdr.dateTime);
+ if (!syncSaveGameHeader(s, hdr) || f->ioFailed()) {
+ goto save_failure;
+ }
+
+ DoSync(s);
+
+ // Write out the special Id for Discworld savegames
+ f->writeUint32LE(0xFEEDFACE);
+ if (f->ioFailed())
+ goto save_failure;
+
+ f->finalize();
+ delete f;
+ return;
+
+save_failure:
+ delete f;
+ _vm->getSaveFileMan()->removeSavefile(fname);
+}
+
+/**
+ * ProcessSRQueue
+ */
+void ProcessSRQueue(void) {
+ switch (SRstate) {
+ case SR_DORESTORE:
+ if (DoRestore()) {
+ RestoreScene(srsd, false);
+ }
+ SRstate = SR_IDLE;
+ break;
+
+ case SR_DOSAVE:
+ DoSave();
+ SRstate = SR_IDLE;
+ break;
+ default:
+ break;
+ }
+}
+
+
+void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
+ assert(SRstate == SR_IDLE);
+
+ SaveSceneName = name;
+ SaveSceneDesc = desc;
+ SaveSceneSsCount = pSsCount;
+ SaveSceneSsData = (char *)pSsData;
+ srsd = sd;
+ SRstate = SR_DOSAVE;
+}
+
+void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
+ assert(num >= 0);
+
+ RestoreGameNumber = num;
+ SaveSceneSsCount = pSsCount;
+ SaveSceneSsData = (char *)pSsData;
+ srsd = sd;
+ SRstate = SR_DORESTORE;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp
new file mode 100644
index 0000000000..fa6e921a56
--- /dev/null
+++ b/engines/tinsel/savescn.cpp
@@ -0,0 +1,335 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Save and restore scene and game.
+ */
+
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/faders.h" // FadeOutFast()
+#include "tinsel/graphics.h" // ClearScreen()
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/music.h"
+#include "tinsel/pid.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h"
+#include "tinsel/tinlib.h"
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+// in BG.C
+extern void startupBackground(SCNHANDLE bfilm);
+extern SCNHANDLE GetBgroundHandle(void);
+extern void SetDoFadeIn(bool tf);
+
+// In DOS_DW.C
+void RestoreMasterProcess(PINT_CONTEXT pic);
+
+// in EVENTS.C (declared here and not in events.h because of strange goings-on)
+void RestoreProcess(PINT_CONTEXT pic);
+
+// in PLAY.C
+extern void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y);
+
+// in SCENE.C
+extern SCNHANDLE GetSceneHandle(void);
+extern void NewScene(SCNHANDLE scene, int entry);
+
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+enum {
+ RS_COUNT = 5, // Restore scene count
+
+ MAX_NEST = 4
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static bool ASceneIsSaved = false;
+
+static int savedSceneCount = 0;
+
+//static SAVED_DATA s_ssData[MAX_NEST];
+static SAVED_DATA *s_ssData = 0;
+static SAVED_DATA sgData;
+
+static SAVED_DATA *s_rsd = 0;
+
+static int s_restoreSceneCount = 0;
+
+static bool bNoFade = false;
+
+//----------------- FORWARD REFERENCES --------------------
+
+
+
+void InitialiseSs(void) {
+ if (s_ssData == NULL) {
+ s_ssData = (SAVED_DATA *)calloc(MAX_NEST, sizeof(SAVED_DATA));
+ if (s_ssData == NULL) {
+ error("Cannot allocate memory for scene changes");
+ }
+ } else
+ savedSceneCount = 0;
+}
+
+void FreeSs(void) {
+ if (s_ssData) {
+ free(s_ssData);
+ s_ssData = NULL;
+ }
+}
+
+/**
+ * Save current scene.
+ * @param sd Pointer to the scene data
+ */
+void SaveScene(SAVED_DATA *sd) {
+ sd->SavedSceneHandle = GetSceneHandle();
+ sd->SavedBgroundHandle = GetBgroundHandle();
+ SaveMovers(sd->SavedMoverInfo);
+ sd->NumSavedActors = SaveActors(sd->SavedActorInfo);
+ PlayfieldGetPos(FIELD_WORLD, &sd->SavedLoffset, &sd->SavedToffset);
+ SaveInterpretContexts(sd->SavedICInfo);
+ SaveDeadPolys(sd->SavedDeadPolys);
+ sd->SavedControl = TestToken(TOKEN_CONTROL);
+ CurrentMidiFacts(&sd->SavedMidi, &sd->SavedLoop);
+ sd->SavedNoBlocking = bNoBlocking;
+ GetNoScrollData(&sd->SavedNoScrollData);
+
+ ASceneIsSaved = true;
+}
+
+/**
+ * Initiate restoration of the saved scene.
+ * @param sd Pointer to the scene data
+ * @param bFadeOut Flag to perform a fade out
+ */
+void RestoreScene(SAVED_DATA *sd, bool bFadeOut) {
+ s_rsd = sd;
+
+ if (bFadeOut)
+ s_restoreSceneCount = RS_COUNT + COUNTOUT_COUNT; // Set restore scene count
+ else
+ s_restoreSceneCount = RS_COUNT; // Set restore scene count
+}
+
+/**
+ * Checks that all non-moving actors are playing the same reel as when
+ * the scene was saved.
+ * Also 'stand' all the moving actors at their saved positions.
+ */
+void sortActors(SAVED_DATA *rsd) {
+ for (int i = 0; i < rsd->NumSavedActors; i++) {
+ ActorsLife(rsd->SavedActorInfo[i].actorID, rsd->SavedActorInfo[i].bAlive);
+
+ // Should be playing the same reel.
+ if (rsd->SavedActorInfo[i].presFilm != 0) {
+ if (!actorAlive(rsd->SavedActorInfo[i].actorID))
+ continue;
+
+ playThisReel(rsd->SavedActorInfo[i].presFilm, rsd->SavedActorInfo[i].presRnum, rsd->SavedActorInfo[i].z,
+ rsd->SavedActorInfo[i].presX, rsd->SavedActorInfo[i].presY);
+ }
+ }
+
+ RestoreAuxScales(rsd->SavedMoverInfo);
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ if (rsd->SavedMoverInfo[i].MActorState == NORM_MACTOR)
+ stand(rsd->SavedMoverInfo[i].actorID, rsd->SavedMoverInfo[i].objx,
+ rsd->SavedMoverInfo[i].objy, rsd->SavedMoverInfo[i].lastfilm);
+ }
+}
+
+
+//---------------------------------------------------------------------------
+
+void ResumeInterprets(SAVED_DATA *rsd) {
+ // Master script only affected on restore game, not restore scene
+ if (rsd == &sgData) {
+ KillMatchingProcess(PID_MASTER_SCR, -1);
+ FreeMasterInterpretContext();
+ }
+
+ for (int i = 0; i < MAX_INTERPRET; i++) {
+ switch (rsd->SavedICInfo[i].GSort) {
+ case GS_NONE:
+ break;
+
+ case GS_INVENTORY:
+ if (rsd->SavedICInfo[i].event != POINTED) {
+ RestoreProcess(&rsd->SavedICInfo[i]);
+ }
+ break;
+
+ case GS_MASTER:
+ // Master script only affected on restore game, not restore scene
+ if (rsd == &sgData)
+ RestoreMasterProcess(&rsd->SavedICInfo[i]);
+ break;
+
+ case GS_ACTOR:
+ RestoreActorProcess(rsd->SavedICInfo[i].actorid, &rsd->SavedICInfo[i]);
+ break;
+
+ case GS_POLYGON:
+ case GS_SCENE:
+ RestoreProcess(&rsd->SavedICInfo[i]);
+ break;
+ }
+ }
+}
+
+/**
+ * Do restore scene
+ * @param n Id
+ */
+static int DoRestoreScene(SAVED_DATA *rsd, int n) {
+ switch (n) {
+ case RS_COUNT + COUNTOUT_COUNT:
+ // Trigger pre-load and fade and start countdown
+ FadeOutFast(NULL);
+ break;
+
+ case RS_COUNT:
+ _vm->_sound->stopAllSamples();
+ ClearScreen(0L);
+ RestoreDeadPolys(rsd->SavedDeadPolys);
+ NewScene(rsd->SavedSceneHandle, NO_ENTRY_NUM);
+ SetDoFadeIn(!bNoFade);
+ bNoFade = false;
+ startupBackground(rsd->SavedBgroundHandle);
+ KillScroll();
+ PlayfieldSetPos(FIELD_WORLD, rsd->SavedLoffset, rsd->SavedToffset);
+ bNoBlocking = rsd->SavedNoBlocking;
+ RestoreNoScrollData(&rsd->SavedNoScrollData);
+/*
+ break;
+
+ case RS_COUNT - 1:
+*/
+ sortActors(rsd);
+ break;
+
+ case 2:
+ break;
+
+ case 1:
+ RestoreMidiFacts(rsd->SavedMidi, rsd->SavedLoop);
+ if (rsd->SavedControl)
+ control(CONTROL_ON); // TOKEN_CONTROL was free
+ ResumeInterprets(rsd);
+ }
+
+ return n - 1;
+}
+
+/**
+ * Restore game
+ * @param num num
+ */
+void RestoreGame(int num) {
+ KillInventory();
+
+ RequestRestoreGame(num, &sgData, &savedSceneCount, s_ssData);
+
+ // Actual restoring is performed by ProcessSRQueue
+}
+
+/**
+ * Save game
+ * @param name Name of savegame
+ * @param desc Description of savegame
+ */
+void SaveGame(char *name, char *desc) {
+ // Get current scene data
+ SaveScene(&sgData);
+
+ RequestSaveGame(name, desc, &sgData, &savedSceneCount, s_ssData);
+
+ // Actual saving is performed by ProcessSRQueue
+}
+
+
+//---------------------------------------------------------------------------------
+
+bool IsRestoringScene() {
+ if (s_restoreSceneCount) {
+ s_restoreSceneCount = DoRestoreScene(s_rsd, s_restoreSceneCount);
+ }
+
+ return s_restoreSceneCount ? true : false;
+}
+
+/**
+ * Please Restore Scene
+ */
+void PleaseRestoreScene(bool bFade) {
+ // only called by restore_scene PCODE
+ if (s_restoreSceneCount == 0) {
+ assert(savedSceneCount >= 1); // No saved scene to restore
+
+ if (ASceneIsSaved)
+ RestoreScene(&s_ssData[--savedSceneCount], bFade);
+ if (!bFade)
+ bNoFade = true;
+ }
+}
+
+/**
+ * Please Save Scene
+ */
+void PleaseSaveScene(CORO_PARAM) {
+ // only called by save_scene PCODE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(savedSceneCount < MAX_NEST); // nesting limit reached
+
+ // Don't save the same thing multiple times!
+ // FIXME/TODO: Maybe this can be changed to an assert?
+ if (savedSceneCount && s_ssData[savedSceneCount-1].SavedSceneHandle == GetSceneHandle())
+ CORO_KILL_SELF();
+
+ SaveScene(&s_ssData[savedSceneCount++]);
+
+ CORO_END_CODE;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/savescn.h b/engines/tinsel/savescn.h
new file mode 100644
index 0000000000..a999c9bffa
--- /dev/null
+++ b/engines/tinsel/savescn.h
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.h"
+ */
+
+#ifndef TINSEL_SAVESCN_H
+#define TINSEL_SAVESCN_H
+
+#include <time.h> // for time_t struct
+
+#include "tinsel/actors.h" // SAVED_ACTOR
+#include "tinsel/dw.h" // SCNHANDLE
+#include "tinsel/rince.h" // SAVED_MOVER
+#include "tinsel/pcode.h" // INT_CONTEXT
+#include "tinsel/scroll.h" // SCROLLDATA
+
+namespace Tinsel {
+
+enum {
+ SG_DESC_LEN = 40, // Max. saved game description length
+ MAX_SFILES = 30,
+
+ // FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the
+ // name field in savedFiles. Raising it to 256 as a preliminary fix.
+ FNAMELEN = 256 // 8.3
+};
+
+struct SFILES {
+ char name[FNAMELEN];
+ char desc[SG_DESC_LEN + 2];
+ struct tm dateTime;
+};
+
+struct SAVED_DATA {
+ SCNHANDLE SavedSceneHandle; // Scene handle
+ SCNHANDLE SavedBgroundHandle; // Background handle
+ SAVED_MOVER SavedMoverInfo[MAX_MOVERS]; // Moving actors
+ SAVED_ACTOR SavedActorInfo[MAX_SAVED_ACTORS]; // } Actors
+ int NumSavedActors; // }
+ int SavedLoffset, SavedToffset; // Screen offsets
+ INT_CONTEXT SavedICInfo[MAX_INTERPRET]; // Interpret contexts
+ bool SavedDeadPolys[MAX_POLY];
+ bool SavedControl;
+ SCNHANDLE SavedMidi; // }
+ bool SavedLoop; // } Midi
+ bool SavedNoBlocking;
+ SCROLLDATA SavedNoScrollData;
+};
+
+
+enum SRSTATE {
+ SR_IDLE, SR_DORESTORE, SR_DONERESTORE,
+ SR_DOSAVE, SR_DONESAVE, SR_ABORTED
+};
+
+void PleaseRestoreScene(bool bFade);
+void PleaseSaveScene(CORO_PARAM);
+
+bool IsRestoringScene();
+
+
+enum letype{
+ LE_NAME, LE_DESC
+};
+
+char *ListEntry(int i, letype which);
+int getList(void);
+
+void RestoreGame(int num);
+void SaveGame(char *name, char *desc);
+
+void ProcessSRQueue(void);
+
+void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData);
+void RequestRestoreGame(int num, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData);
+
+void InitialiseSs(void);
+void FreeSs(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SAVESCN_H */
diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp
new file mode 100644
index 0000000000..006efd7b25
--- /dev/null
+++ b/engines/tinsel/scene.cpp
@@ -0,0 +1,308 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Starts up new scenes.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/film.h"
+#include "tinsel/move.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/scn.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h" // stopAllSamples()
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h" // process IDs
+#include "tinsel/token.h"
+
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern void DropBackground(void);
+
+// in EFFECT.C
+extern void EffectPolyProcess(CORO_PARAM);
+
+// in PDISPLAY.C
+#ifdef DEBUG
+extern void CursorPositionProcess(CORO_PARAM);
+#endif
+extern void TagProcess(CORO_PARAM);
+extern void PointProcess(CORO_PARAM);
+extern void EnableTags(void);
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** scene structure - one per scene */
+struct SCENE_STRUC {
+ int32 numEntrance; //!< number of entrances in this scene
+ int32 numPoly; //!< number of various polygons in this scene
+ int32 numActor; //!< number of actors in this scene
+ int32 defRefer; //!< Default refer direction
+ SCNHANDLE hSceneScript; //!< handle to scene script
+ SCNHANDLE hEntrance; //!< handle to table of entrances
+ SCNHANDLE hPoly; //!< handle to table of polygons
+ SCNHANDLE hActor; //!< handle to table of actors
+} PACKED_STRUCT;
+
+/** entrance structure - one per entrance */
+struct ENTRANCE_STRUC {
+ int32 eNumber; //!< entrance number
+ SCNHANDLE hScript; //!< handle to entrance script
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+static bool ShowPosition = false; // Set when showpos() has been called
+#endif
+
+static SCNHANDLE SceneHandle = 0; // Current scene handle - stored in case of Save_Scene()
+
+
+/**
+ * Started up for scene script and entrance script.
+ */
+static void SceneTinselProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PINT_CONTEXT pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ SCNHANDLE *ss = (SCNHANDLE *)ProcessGetParamsSelf();
+ assert(*ss); // Must have some code to run
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pic = InitInterpretContext(GS_SCENE, READ_LE_UINT32(ss), NOEVENT, NOPOLY, 0, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Get the SCENE_STRUC
+ * Initialise polygons for the scene
+ * Initialise the actors for this scene
+ * Run the appropriate entrance code (if any)
+ * Get the default refer type
+ */
+static void LoadScene(SCNHANDLE scene, int entry) {
+ const SCENE_STRUC *ss;
+ const ENTRANCE_STRUC *es;
+ uint i;
+
+ // Scene structure
+ SceneHandle = scene; // Save scene handle in case of Save_Scene()
+
+ LockMem(SceneHandle); // Make sure scene is loaded
+ LockScene(SceneHandle); // Prevent current scene from being discarded
+
+ ss = (const SCENE_STRUC *)FindChunk(scene, CHUNK_SCENE);
+ assert(ss != NULL);
+
+ // Initialise all the polygons for this scene
+ InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), (entry == NO_ENTRY_NUM));
+
+ // Initialise the actors for this scene
+ StartActors(FROM_LE_32(ss->hActor), FROM_LE_32(ss->numActor), (entry != NO_ENTRY_NUM));
+
+ if (entry != NO_ENTRY_NUM) {
+
+ // Run the appropriate entrance code (if any)
+ es = (const ENTRANCE_STRUC *)LockMem(FROM_LE_32(ss->hEntrance));
+ for (i = 0; i < FROM_LE_32(ss->numEntrance); i++, es++) {
+ if (FROM_LE_32(es->eNumber) == (uint)entry) {
+ if (es->hScript)
+ CoroutineInstall(PID_TCODE, SceneTinselProcess, &es->hScript, sizeof(es->hScript));
+ break;
+ }
+ }
+
+ if (i == FROM_LE_32(ss->numEntrance))
+ error("Non-existant scene entry number");
+
+ if (ss->hSceneScript)
+ CoroutineInstall(PID_TCODE, SceneTinselProcess, &ss->hSceneScript, sizeof(ss->hSceneScript));
+ }
+
+ // Default refer type
+ SetDefaultRefer(FROM_LE_32(ss->defRefer));
+}
+
+
+/**
+ * Wrap up the last scene.
+ */
+void EndScene(void) {
+ if (SceneHandle != 0) {
+ UnlockScene(SceneHandle);
+ SceneHandle = 0;
+ }
+
+ KillInventory(); // Close down any open inventory
+
+ DropPolygons(); // No polygons
+ DropNoScrolls(); // No no-scrolls
+ DropBackground(); // No background
+ DropMActors(); // No moving actors
+ DropCursor(); // No cursor
+ DropActors(); // No actor reels running
+ FreeAllTokens(); // No-one has tokens
+ FreeMostInterpretContexts(); // Only master script still interpreting
+
+ _vm->_sound->stopAllSamples(); // Kill off any still-running sample
+
+ // init the palette manager
+ ResetPalAllocator();
+
+ // init the object manager
+ KillAllObjects();
+
+ // kill all destructable process
+ KillMatchingProcess(PID_DESTROY, PID_DESTROY);
+}
+
+/**
+ *
+ */
+void PrimeBackground(void)
+{
+ // structure for playfields
+ static PLAYFIELD playfield[] = {
+ { // FIELD WORLD
+ NULL, // no modules
+ NULL, // display list
+ 0, // init field x
+ 0, // init field y
+ 0, // x vel
+ 0, // y vel
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect
+ false // moved flag
+ },
+ { // FIELD STATUS
+ NULL, // no modules
+ NULL, // display list
+ 0, // init field x
+ 0, // init field y
+ 0, // x vel
+ 0, // y vel
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect
+ false // moved flag
+ }
+ };
+
+ // structure for background
+ static BACKGND backgnd = {
+ BLACK, // sky colour
+ Common::Point(0, 0), // initial world pos
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // scroll limits
+ 0, // no background update process
+ NULL, // no x scroll table
+ NULL, // no y scroll table
+ 2, // 2 playfields
+ playfield, // playfield pointer
+ false // no auto-erase
+ };
+
+ InitBackground(&backgnd);
+}
+
+/**
+ * Start up the standard stuff for the next scene.
+ */
+
+void PrimeScene(void) {
+
+ bNoBlocking = false;
+
+ RestartCursor(); // Restart the cursor
+ EnableTags(); // Next scene with tags enabled
+
+ CoroutineInstall(PID_SCROLL, ScrollProcess, NULL, 0);
+ CoroutineInstall(PID_SCROLL, EffectPolyProcess, NULL, 0);
+
+#ifdef DEBUG
+ if (ShowPosition)
+ CoroutineInstall(PID_POSITION, CursorPositionProcess, NULL, 0);
+#endif
+
+ CoroutineInstall(PID_TAG, TagProcess, NULL, 0);
+ CoroutineInstall(PID_TAG, PointProcess, NULL, 0);
+
+ // init the current background
+ PrimeBackground();
+}
+
+/**
+ * Wrap up the last scene and start up the next scene.
+ */
+
+void NewScene(SCNHANDLE scene, int entry) {
+ EndScene(); // Wrap up the last scene.
+
+ PrimeScene(); // Start up the standard stuff for the next scene.
+
+ LoadScene(scene, entry);
+}
+
+#ifdef DEBUG
+/**
+ * Sets the ShowPosition flag, causing the cursor position process to be
+ * created in each scene.
+ */
+
+void setshowpos(void) {
+ ShowPosition = true;
+}
+#endif
+
+/**
+ * Return the current scene handle.
+ */
+
+SCNHANDLE GetSceneHandle(void) {
+ return SceneHandle;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scene.h b/engines/tinsel/scene.h
new file mode 100644
index 0000000000..baecac40b9
--- /dev/null
+++ b/engines/tinsel/scene.h
@@ -0,0 +1,79 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Scene parsing defines
+ */
+
+#ifndef TINSEL_SCENE_H
+#define TINSEL_SCENE_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+enum {
+ MAX_NODES = 32, //!< maximum nodes in a Node Path
+ MAX_NOSCROLL = 16, //!< maximum number of NoScroll commands in a scene
+ MAX_ENTRANCE = 25, //!< maximum number of entrances in a scene
+ MAX_POLY = 256, //!< maximum number of polygons in a scene
+ MAX_ACTOR = 32 //!< maximum number of actors in a scene
+};
+
+/** reference direction */
+enum REFTYPE {
+ REF_DEFAULT, REF_UP, REF_DOWN, REF_LEFT, REF_RIGHT, REF_POINT
+};
+
+enum TFTYPE {
+ TF_NONE, TF_UP, TF_DOWN, TF_LEFT, TF_RIGHT, TF_BOGUS
+};
+
+/** different actor masks */
+enum MASK_TYPE{
+ ACT_DEFAULT,
+ ACT_MASK = -1,
+ ACT_ALWAYS = -2
+};
+
+/** different types of polygon */
+enum POLY_TYPE {
+ POLY_PATH, POLY_NPATH, POLY_BLOCK, POLY_REFER, POLY_EFFECT,
+ POLY_EXIT, POLY_TAG
+};
+
+/** different scales */
+enum SCALE {
+ SCALE_DEFAULT, SCALE_LARGE, SCALE_MEDIUM, SCALE_SMALL,
+ SCALE_COMPACT, SCALE_TINY,
+ SCALE_AUX1, SCALE_AUX2, SCALE_AUX3,
+ SCALE_AUX4, SCALE_AUX5
+};
+
+/** different reels */
+enum REEL {
+ REEL_DEFAULT, REEL_ALL, REEL_HORIZ, REEL_VERT
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SCENE_H
diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp
new file mode 100644
index 0000000000..9391805040
--- /dev/null
+++ b/engines/tinsel/sched.cpp
@@ -0,0 +1,344 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Process scheduler.
+ */
+
+#include "tinsel/sched.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+
+/** list of all processes */
+static PROCESS *processList = 0;
+
+/** active process list - also saves scheduler state */
+static PROCESS active;
+
+/** pointer to free process list */
+static PROCESS *pFreeProcesses = 0;
+
+/** the currently active process */
+static PROCESS *pCurrent = 0;
+
+#ifdef DEBUG
+// diagnostic process counters
+static int numProcs = 0;
+static int maxProcs = 0;
+#endif
+
+/**
+ * Called from ProcessKill() to enable other resources
+ * a process may be allocated to be released.
+ */
+static VFPTRPP pRCfunction = 0;
+
+
+/**
+ * Kills all processes and places them on the free list.
+ */
+void InitScheduler(void) {
+
+#ifdef DEBUG
+ // clear number of process in use
+ numProcs = 0;
+#endif
+
+ if (processList == NULL) {
+ // first time - allocate memory for process list
+ processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS));
+
+ // make sure memory allocated
+ if (processList == NULL) {
+ error("Cannot allocate memory for process data");
+ }
+
+ // fill with garbage
+ memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS));
+ }
+
+ // no active processes
+ pCurrent = active.pNext = NULL;
+
+ // place first process on free list
+ pFreeProcesses = processList;
+
+ // link all other processes after first
+ for (int i = 1; i < NUM_PROCESS; i++) {
+ processList[i - 1].pNext = processList + i;
+ }
+
+ // null the last process
+ processList[NUM_PROCESS - 1].pNext = NULL;
+}
+
+void FreeProcessList(void) {
+ if (processList) {
+ free(processList);
+ processList = NULL;
+ }
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of process used at once.
+ */
+void ProcessStats(void) {
+ printf("%i process of %i used.\n", maxProcs, NUM_PROCESS);
+}
+#endif
+
+
+/**
+ * Give all active processes a chance to run
+ */
+void Scheduler(void) {
+ // start dispatching active process list
+ PROCESS *pPrevProc = &active;
+ PROCESS *pProc = active.pNext;
+ while (pProc != NULL) {
+ if (--pProc->sleepTime <= 0) {
+ // process is ready for dispatch, activate it
+ pCurrent = pProc;
+ pProc->coroAddr(pProc->state);
+ pCurrent = NULL;
+ if (!pProc->state || pProc->state->_sleep <= 0) {
+ // Coroutine finished
+ ProcessKill(pProc);
+ pProc = pPrevProc;
+ } else {
+ pProc->sleepTime = pProc->state->_sleep;
+ }
+ }
+ pPrevProc = pProc;
+ pProc = pProc->pNext;
+ }
+}
+
+
+/**
+ * Creates a new process.
+ *
+ * @param pid process identifier
+ * @param CORO_ADDR coroutine start address
+ * @param pParam process specific info
+ * @param sizeParam size of process specific info
+ */
+PROCESS *CoroutineInstall(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
+ PROCESS *pProc;
+
+ // get a free process
+ pProc = pFreeProcesses;
+
+ // trap no free process
+ assert(pProc != NULL); // Out of processes
+
+#ifdef DEBUG
+ // one more process in use
+ if (++numProcs > maxProcs)
+ maxProcs = numProcs;
+#endif
+
+ // get link to next free process
+ pFreeProcesses = pProc->pNext;
+
+ if (pCurrent != NULL) {
+ // place new process before the next active process
+ pProc->pNext = pCurrent->pNext;
+
+ // make this new process the next active process
+ pCurrent->pNext = pProc;
+ } else { // no active processes, place process at head of list
+ pProc->pNext = active.pNext;
+ active.pNext = pProc;
+ }
+
+ // set coroutine entry point
+ pProc->coroAddr = coroAddr;
+
+ // clear coroutine state
+ pProc->state = 0;
+
+ // wake process up as soon as possible
+ pProc->sleepTime = 1;
+
+ // set new process id
+ pProc->pid = pid;
+
+ // set new process specific info
+ if (sizeParam) {
+ assert(sizeParam > 0 && sizeParam <= PARAM_SIZE);
+
+ // set new process specific info
+ memcpy(pProc->param, pParam, sizeParam);
+ }
+
+
+ // return created process
+ return pProc;
+}
+
+/**
+ * Kills the specified process.
+ *
+ * @param pKillProc which process to kill
+ */
+void ProcessKill(PROCESS *pKillProc) {
+ PROCESS *pProc, *pPrev; // process list pointers
+
+ // make sure a valid process pointer
+ assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1);
+
+ // can not kill the current process using ProcessKill !
+ assert(pCurrent != pKillProc);
+
+#ifdef DEBUG
+ // one less process in use
+ --numProcs;
+ assert(numProcs >= 0);
+#endif
+
+ // search the active list for the process
+ for (pProc = active.pNext, pPrev = &active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
+ if (pProc == pKillProc) {
+ // found process in active list
+
+ // Free process' resources
+ if (pRCfunction != NULL)
+ (pRCfunction)(pProc);
+
+ delete pProc->state;
+
+ // make prev point to next to unlink pProc
+ pPrev->pNext = pProc->pNext;
+
+ // link first free process after pProc
+ pProc->pNext = pFreeProcesses;
+
+ // make pProc the first free process
+ pFreeProcesses = pProc;
+
+ return;
+ }
+ }
+
+ // process not found in active list if we get to here
+ error("ProcessKill(): tried to kill a process not in the list of active processes");
+}
+
+
+
+/**
+ * Returns a pointer to the currently running process.
+ */
+PROCESS *CurrentProcess(void) {
+ return pCurrent;
+}
+
+char *ProcessGetParamsSelf() {
+ PROCESS *pProc = pCurrent;
+
+ // make sure a valid process pointer
+ assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1);
+
+ return pProc->param;
+}
+
+/**
+ * Returns the process identifier of the specified process.
+ *
+ * @param pProc which process
+ */
+int ProcessGetPID(PROCESS *pProc) {
+ // make sure a valid process pointer
+ assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1);
+
+ // return processes PID
+ return pProc->pid;
+}
+
+/**
+ * Kills any process matching the specified PID. The current
+ * process cannot be killed.
+ *
+ * @param pidKill process identifier of process to kill
+ * @param pidMask mask to apply to process identifiers before comparison
+ * @return The number of processes killed is returned.
+ */
+int KillMatchingProcess(int pidKill, int pidMask) {
+ int numKilled = 0;
+ PROCESS *pProc, *pPrev; // process list pointers
+
+ for (pProc = active.pNext, pPrev = &active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
+ if ((pProc->pid & pidMask) == pidKill) {
+ // found a matching process
+
+ // dont kill the current process
+ if (pProc != pCurrent) {
+ // kill this process
+ numKilled++;
+
+ // make prev point to next to unlink pProc
+ pPrev->pNext = pProc->pNext;
+
+ // link first free process after pProc
+ pProc->pNext = pFreeProcesses;
+
+ // make pProc the first free process
+ pFreeProcesses = pProc;
+
+ // set to a process on the active list
+ pProc = pPrev;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ // adjust process in use
+ numProcs -= numKilled;
+ assert(numProcs >= 0);
+#endif
+
+ // return number of processes killed
+ return numKilled;
+}
+
+
+
+/**
+ * Set pointer to a function to be called by ProcessKill().
+ *
+ * May be called by a resource allocator, the function supplied is
+ * called by ProcessKill() to allow the resource allocator to free
+ * resources allocated to the dying process.
+ *
+ * @param pFunc Function to be called by ProcessKill()
+ */
+void SetResourceCallback(VFPTRPP pFunc) {
+ pRCfunction = pFunc;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h
new file mode 100644
index 0000000000..6b89a60891
--- /dev/null
+++ b/engines/tinsel/sched.h
@@ -0,0 +1,100 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used by the process scheduler
+ */
+
+#ifndef TINSEL_SCHED_H // prevent multiple includes
+#define TINSEL_SCHED_H
+
+#include "tinsel/dw.h" // new data types
+#include "tinsel/coroutine.h"
+
+namespace Tinsel {
+
+// the size of process specific info
+#define PARAM_SIZE 32
+
+// the maximum number of processes
+#define NUM_PROCESS 64
+
+typedef void (*CORO_ADDR)(CoroContext &);
+
+
+// process structure
+
+struct PROCESS {
+ PROCESS *pNext; // pointer to next process in active or free list
+
+ CoroContext state; // the state of the coroutine
+ CORO_ADDR coroAddr; // the entry point of the coroutine
+
+ int sleepTime; // number of scheduler cycles to sleep
+ int pid; // process ID
+ char param[PARAM_SIZE]; // process specific info
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Scheduler Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void InitScheduler(void); // called to init scheduler - kills all processes and places them on free list
+
+void FreeProcessList(void);
+
+#ifdef DEBUG
+void ProcessStats(void); // Shows the maximum number of process used at once
+#endif
+
+void Scheduler(void); // called to start process dispatching
+
+PROCESS *CoroutineInstall(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam);
+
+void ProcessKill( // kill a process
+ PROCESS *pKillProc); // which process to kill (must be different from current one)
+
+PROCESS *CurrentProcess(void); // Returns a pointer to the currently running process
+
+int ProcessGetPID( // Returns the process identifier of the specified process
+ PROCESS *pProc); // which process
+
+char *ProcessGetParamsSelf();
+
+int KillMatchingProcess( // kill any process matching the pid parameters
+ int pidKill, // process identifier of process to kill
+ int pidMask); // mask to apply to process identifiers before comparison
+
+
+// Pointer to a function of the form "void function(PPROCESS)"
+typedef void (*VFPTRPP)(PROCESS *);
+
+void SetResourceCallback(VFPTRPP pFunc); // May be called by a resource allocator,
+ // the function supplied is called by ProcessKill()
+ // to allow the resource allocator to free resources
+ // allocated to the dying process.
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SCHED_H
diff --git a/engines/tinsel/scn.cpp b/engines/tinsel/scn.cpp
new file mode 100644
index 0000000000..b14b1c5962
--- /dev/null
+++ b/engines/tinsel/scn.cpp
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * A (some would say very) small collection of utility functions.
+ */
+
+#include "common/endian.h"
+#include "common/util.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/scn.h"
+#include "tinsel/tinsel.h" // for _vm
+
+namespace Tinsel {
+
+/**
+ * Given a scene handle and a chunk id, gets the scene in RAM and
+ * locates the requested chunk.
+ * @param handle Scene handle
+ * @param chunk Chunk Id
+ */
+byte *FindChunk(SCNHANDLE handle, uint32 chunk) {
+ byte *bptr = LockMem(handle);
+ uint32 *lptr = (uint32 *)bptr;
+ uint32 add;
+
+ // V1 chunk types can be found by substracting 2 from the
+ // chunk type. Note that CHUNK_STRING and CHUNK_BITMAP are
+ // the same in V1 and V2
+ if (_vm->getVersion() == TINSEL_V1 &&
+ chunk != CHUNK_STRING && chunk != CHUNK_BITMAP)
+ chunk -= 0x2L;
+
+ while (1) {
+ if (READ_LE_UINT32(lptr) == chunk)
+ return (byte *)(lptr + 2);
+
+ ++lptr;
+ add = READ_LE_UINT32(lptr);
+ if (!add)
+ return NULL;
+
+ lptr = (uint32 *)(bptr + add);
+ }
+}
+
+/**
+ * Get the actor id from a film (column 0)
+ */
+int extractActor(SCNHANDLE film) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ const FREEL *preel = &pfilm->reels[0];
+ const MULTI_INIT *pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(preel->mobj));
+ return (int)FROM_LE_32(pmi->mulID);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scn.h b/engines/tinsel/scn.h
new file mode 100644
index 0000000000..29f3dc51fc
--- /dev/null
+++ b/engines/tinsel/scn.h
@@ -0,0 +1,68 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_SCN_H // prevent multiple includes
+#define TINSEL_SCN_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+
+// chunk identifier numbers
+
+// V2 chunks
+
+#define CHUNK_STRING 0x33340001L // same in V1 and V2
+#define CHUNK_BITMAP 0x33340002L // same in V1 and V2
+#define CHUNK_CHARPTR 0x33340003L // not used!
+#define CHUNK_CHARMATRIX 0x33340004L // not used!
+#define CHUNK_PALETTE 0x33340005L // not used!
+#define CHUNK_IMAGE 0x33340006L // not used!
+#define CHUNK_ANI_FRAME 0x33340007L // not used!
+#define CHUNK_FILM 0x33340008L // not used!
+#define CHUNK_FONT 0x33340009L // not used!
+#define CHUNK_PCODE 0x3334000AL
+#define CHUNK_ENTRANCE 0x3334000BL // not used!
+#define CHUNK_POLYGONS 0x3334000CL // not used!
+#define CHUNK_ACTORS 0x3334000DL // not used!
+#define CHUNK_SCENE 0x3334000EL
+#define CHUNK_TOTAL_ACTORS 0x3334000FL
+#define CHUNK_TOTAL_GLOBALS 0x33340010L
+#define CHUNK_TOTAL_OBJECTS 0x33340011L
+#define CHUNK_OBJECTS 0x33340012L
+#define CHUNK_MIDI 0x33340013L // not used!
+#define CHUNK_SAMPLE 0x33340014L // not used!
+#define CHUNK_TOTAL_POLY 0x33340015L
+#define CHUNK_MBSTRING 0x33340022L // Multi-byte characters
+
+#define INDEX_FILENAME "index" // name of index file
+
+byte *FindChunk(SCNHANDLE handle, uint32 chunk);
+int extractActor(SCNHANDLE film);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SCN_H */
diff --git a/engines/tinsel/scroll.cpp b/engines/tinsel/scroll.cpp
new file mode 100644
index 0000000000..7c0b12730f
--- /dev/null
+++ b/engines/tinsel/scroll.cpp
@@ -0,0 +1,432 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles scrolling
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sched.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define LEFT 'L'
+#define RIGHT 'R'
+#define UP 'U'
+#define DOWN 'D'
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int LeftScroll = 0, DownScroll = 0; // Number of iterations outstanding
+
+static int scrollActor = 0;
+static PMACTOR psActor = 0;
+static int oldx = 0, oldy = 0;
+
+/** Boundaries and numbers of boundaries */
+static SCROLLDATA sd = {
+ {
+ {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}, {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
+ };
+
+static int ImageH = 0, ImageW = 0;
+
+static bool ScrollCursor = 0; // If a TAG or EXIT polygon is clicked on,
+ // the cursor is kept over that polygon
+ // whilst scrolling
+
+static int scrollPixels = SCROLLPIXELS;
+
+
+/**
+ * Reset the ScrollCursor flag
+ */
+void DontScrollCursor(void) {
+ ScrollCursor = false;
+}
+
+/**
+ * Set the ScrollCursor flag
+ */
+void DoScrollCursor(void) {
+ ScrollCursor = true;
+}
+
+/**
+ * Configure a no-scroll boundary for a scene.
+ */
+void SetNoScroll(int x1, int y1, int x2, int y2) {
+ if (x1 == x2) {
+ /* Vertical line */
+ assert(sd.NumNoH < MAX_HNOSCROLL);
+
+ sd.NoHScroll[sd.NumNoH].ln = x1; // X pos of vertical line
+ sd.NoHScroll[sd.NumNoH].c1 = y1;
+ sd.NoHScroll[sd.NumNoH].c2 = y2;
+ sd.NumNoH++;
+ } else if (y1 == y2) {
+ /* Horizontal line */
+ assert(sd.NumNoV < MAX_VNOSCROLL);
+
+ sd.NoVScroll[sd.NumNoV].ln = y1; // Y pos of horizontal line
+ sd.NoVScroll[sd.NumNoV].c1 = x1;
+ sd.NoVScroll[sd.NumNoV].c2 = x2;
+ sd.NumNoV++;
+ } else {
+ /* No-scroll lines must be horizontal or vertical */
+ }
+}
+
+/**
+ * Does the obvious - called at the end of a scene.
+ */
+void DropNoScrolls(void) {
+ sd.NumNoH = sd.NumNoV = 0;
+}
+
+/**
+ * Called from scroll process when it thinks that a scroll is in order.
+ * Checks for no-scroll boundaries and sets off a scroll if allowed.
+ */
+static void NeedScroll(int direction) {
+ uint i;
+ int BottomLine, RightCol;
+ int Loffset, Toffset;
+
+ // get background offsets
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ switch (direction) {
+ case LEFT: /* Picture will go left, 'camera' right */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoH; i++) {
+ if (RightCol >= sd.NoHScroll[i].ln - 1 && RightCol <= sd.NoHScroll[i].ln + 1 &&
+ ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) ||
+ (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) ||
+ (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine)))
+ return;
+ }
+
+ if (LeftScroll <= 0) {
+ scrollPixels = SCROLLPIXELS;
+ LeftScroll = RLSCROLL;
+ }
+ break;
+
+ case RIGHT: /* Picture will go right, 'camera' left */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+
+ for (i = 0; i < sd.NumNoH; i++) {
+ if (Loffset >= sd.NoHScroll[i].ln - 1 && Loffset <= sd.NoHScroll[i].ln + 1 &&
+ ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) ||
+ (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) ||
+ (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine)))
+ return;
+ }
+
+ if (LeftScroll >= 0) {
+ scrollPixels = SCROLLPIXELS;
+ LeftScroll = -RLSCROLL;
+ }
+ break;
+
+ case UP: /* Picture will go upwards, 'camera' downwards */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoV; i++) {
+ if ((BottomLine >= sd.NoVScroll[i].ln - 1 && BottomLine <= sd.NoVScroll[i].ln + 1) &&
+ ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) ||
+ (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) ||
+ (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol)))
+ return;
+ }
+
+ if (DownScroll <= 0) {
+ scrollPixels = SCROLLPIXELS;
+ DownScroll = UDSCROLL;
+ }
+ break;
+
+ case DOWN: /* Picture will go downwards, 'camera' upwards */
+
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoV; i++) {
+ if (Toffset >= sd.NoVScroll[i].ln - 1 && Toffset <= sd.NoVScroll[i].ln + 1 &&
+ ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) ||
+ (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) ||
+ (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol)))
+ return;
+ }
+
+ if (DownScroll >= 0) {
+ scrollPixels = SCROLLPIXELS;
+ DownScroll = -UDSCROLL;
+ }
+ break;
+ }
+}
+
+/**
+ * Called from scroll process - Scrolls the image as appropriate.
+ */
+static void ScrollImage(void) {
+ int OldLoffset = 0, OldToffset = 0; // Used when keeping cursor on a tag
+ int Loffset, Toffset;
+ int curX, curY;
+
+ // get background offsets
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*
+ * Keeping cursor on a tag?
+ */
+ if (ScrollCursor) {
+ GetCursorXY(&curX, &curY, true);
+ if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) {
+ OldLoffset = Loffset;
+ OldToffset = Toffset;
+ } else
+ ScrollCursor = false;
+ }
+
+ /*
+ * Horizontal scrolling
+ */
+ if (LeftScroll > 0) {
+ LeftScroll -= scrollPixels;
+ if (LeftScroll < 0) {
+ Loffset += LeftScroll;
+ LeftScroll = 0;
+ }
+ Loffset += scrollPixels; // Move right
+ if (Loffset > ImageW - SCREEN_WIDTH)
+ Loffset = ImageW - SCREEN_WIDTH;// Now at extreme right
+ } else if (LeftScroll < 0) {
+ LeftScroll += scrollPixels;
+ if (LeftScroll > 0) {
+ Loffset += LeftScroll;
+ LeftScroll = 0;
+ }
+ Loffset -= scrollPixels; // Move left
+ if (Loffset < 0)
+ Loffset = 0; // Now at extreme left
+ }
+
+ /*
+ * Vertical scrolling
+ */
+ if (DownScroll > 0) {
+ DownScroll -= scrollPixels;
+ if (DownScroll < 0) {
+ Toffset += DownScroll;
+ DownScroll = 0;
+ }
+ Toffset += scrollPixels; // Move down
+
+ if (Toffset > ImageH - SCREEN_HEIGHT)
+ Toffset = ImageH - SCREEN_HEIGHT;// Now at extreme bottom
+
+ } else if (DownScroll < 0) {
+ DownScroll += scrollPixels;
+ if (DownScroll > 0) {
+ Toffset += DownScroll;
+ DownScroll = 0;
+ }
+ Toffset -= scrollPixels; // Move up
+
+ if (Toffset < 0)
+ Toffset = 0; // Now at extreme top
+ }
+
+ /*
+ * Move cursor if keeping cursor on a tag.
+ */
+ if (ScrollCursor)
+ AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset);
+
+ PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset);
+}
+
+
+/**
+ * See if the actor on whom the camera is is approaching an edge.
+ * Request a scroll if he is.
+ */
+static void MonitorScroll(void) {
+ int newx, newy;
+ int Loffset, Toffset;
+
+ /*
+ * Only do it if the actor is there and is visible
+ */
+ if (!psActor || getMActorHideState(psActor)
+ || getMActorState(psActor) == NO_MACTOR)
+ return;
+
+ GetActorPos(scrollActor, &newx, &newy);
+
+ if (oldx == newx && oldy == newy)
+ return;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*
+ * Approaching right side or left side of the screen?
+ */
+ if (newx > Loffset+SCREEN_WIDTH-RLDISTANCE && Loffset < ImageW-SCREEN_WIDTH) {
+ if (newx > oldx)
+ NeedScroll(LEFT);
+ } else if (newx < Loffset + RLDISTANCE && Loffset) {
+ if (newx < oldx)
+ NeedScroll(RIGHT);
+ }
+
+ /*
+ * Approaching bottom or top of the screen?
+ */
+ if (newy > Toffset+SCREEN_HEIGHT-UDDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) {
+ if (newy > oldy)
+ NeedScroll(UP);
+ } else if (Toffset && newy < Toffset + UDDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) {
+ if (newy < oldy)
+ NeedScroll(DOWN);
+ }
+
+ oldx = newx;
+ oldy = newy;
+}
+
+/**
+ * Decide when to scroll and scroll when decided to.
+ */
+void ScrollProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ ImageH = BackgroundHeight(); // Dimensions
+ ImageW = BackgroundWidth(); // of this scene.
+
+ // Give up if there'll be no purpose in this process
+ if (ImageW == SCREEN_WIDTH && ImageH == SCREEN_HEIGHT)
+ CORO_KILL_SELF();
+
+ LeftScroll = DownScroll = 0; // No iterations outstanding
+ oldx = oldy = 0;
+ scrollPixels = SCROLLPIXELS;
+
+ if (!scrollActor)
+ scrollActor = LeadId();
+
+ psActor = GetMover(scrollActor);
+
+ while (1) {
+ MonitorScroll(); // Set scroll requirement
+
+ if (LeftScroll || DownScroll) // Scroll if required
+ ScrollImage();
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Change which actor the camera is following.
+ */
+void ScrollFocus(int ano) {
+ if (scrollActor != ano) {
+ oldx = oldy = 0;
+ scrollActor = ano;
+
+ psActor = ano ? GetMover(scrollActor) : NULL;
+ }
+}
+
+/**
+ * Scroll to abslote position.
+ */
+void ScrollTo(int x, int y, int iter) {
+ int Loffset, Toffset; // for background offsets
+
+ scrollPixels = iter != 0 ? iter : SCROLLPIXELS;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); // get background offsets
+
+ LeftScroll = x - Loffset;
+ DownScroll = y - Toffset;
+}
+
+/**
+ * Kill of any current scroll.
+ */
+void KillScroll(void) {
+ LeftScroll = DownScroll = 0;
+}
+
+
+void GetNoScrollData(SCROLLDATA *ssd) {
+ memcpy(ssd, &sd, sizeof(SCROLLDATA));
+}
+
+void RestoreNoScrollData(SCROLLDATA *ssd) {
+ memcpy(&sd, ssd, sizeof(SCROLLDATA));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scroll.h b/engines/tinsel/scroll.h
new file mode 100644
index 0000000000..02ce2afd5b
--- /dev/null
+++ b/engines/tinsel/scroll.h
@@ -0,0 +1,77 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_SCROLL_H // prevent multiple includes
+#define TINSEL_SCROLL_H
+
+namespace Tinsel {
+
+#define SCROLLPIXELS 8 // Number of pixels to scroll per iteration
+
+#define RLDISTANCE 50 // Distance from edge that triggers a scroll
+#define UDDISTANCE 20
+
+// Number of iterations to make
+#define RLSCROLL 160 // 20*8 = 160 = half a screen
+#define UDSCROLL 100 // 12.5*8 = 100 = half a screen
+
+
+// These structures defined here so boundaries can be saved
+struct NOSCROLLB {
+ int ln;
+ int c1;
+ int c2;
+};
+
+#define MAX_HNOSCROLL 10
+#define MAX_VNOSCROLL 10
+
+struct SCROLLDATA{
+ NOSCROLLB NoVScroll[MAX_VNOSCROLL]; // Vertical no-scroll boundaries
+ NOSCROLLB NoHScroll[MAX_HNOSCROLL]; // Horizontal no-scroll boundaries
+ unsigned NumNoV, NumNoH; // Counts of no-scroll boundaries
+};
+
+
+
+void DontScrollCursor(void);
+void DoScrollCursor(void);
+
+void SetNoScroll(int x1, int y1, int x2, int y2);
+void DropNoScrolls(void);
+
+void ScrollProcess(CORO_PARAM);
+
+void ScrollFocus(int actor);
+void ScrollTo(int x, int y, int iter);
+
+void KillScroll(void);
+
+void GetNoScrollData(SCROLLDATA *ssd);
+void RestoreNoScrollData(SCROLLDATA *ssd);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SCROLL_H */
diff --git a/engines/tinsel/serializer.h b/engines/tinsel/serializer.h
new file mode 100644
index 0000000000..98ee398ef8
--- /dev/null
+++ b/engines/tinsel/serializer.h
@@ -0,0 +1,131 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ */
+
+#ifndef TINSEL_SERIALIZER_H
+#define TINSEL_SERIALIZER_H
+
+#include "common/scummsys.h"
+#include "common/savefile.h"
+
+
+namespace Tinsel {
+
+
+#define SYNC_AS(SUFFIX,TYPE,SIZE) \
+ template <class T> \
+ void syncAs ## SUFFIX(T &val) { \
+ if (_loadStream) \
+ val = static_cast<T>(_loadStream->read ## SUFFIX()); \
+ else { \
+ TYPE tmp = val; \
+ _saveStream->write ## SUFFIX(tmp); \
+ } \
+ _bytesSynced += SIZE; \
+ }
+
+
+// TODO: Write comment for this
+// TODO: Inspired by the SCUMM engine -- move to common/ code and use in more engines?
+class Serializer {
+public:
+ Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out)
+ : _loadStream(in), _saveStream(out), _bytesSynced(0) {
+ assert(in || out);
+ }
+
+ bool isSaving() { return (_saveStream != 0); }
+ bool isLoading() { return (_loadStream != 0); }
+
+ uint bytesSynced() const { return _bytesSynced; }
+
+ void syncBytes(byte *buf, uint16 size) {
+ if (_loadStream)
+ _loadStream->read(buf, size);
+ else
+ _saveStream->write(buf, size);
+ _bytesSynced += size;
+ }
+
+ SYNC_AS(Byte, byte, 1)
+
+ SYNC_AS(Uint16LE, uint16, 2)
+ SYNC_AS(Uint16BE, uint16, 2)
+ SYNC_AS(Sint16LE, int16, 2)
+ SYNC_AS(Sint16BE, int16, 2)
+
+ SYNC_AS(Uint32LE, uint32, 4)
+ SYNC_AS(Uint32BE, uint32, 4)
+ SYNC_AS(Sint32LE, int32, 4)
+ SYNC_AS(Sint32BE, int32, 4)
+
+protected:
+ Common::SeekableReadStream *_loadStream;
+ Common::OutSaveFile *_saveStream;
+
+ uint _bytesSynced;
+};
+
+#undef SYNC_AS
+
+// TODO: Make a subclass "VersionedSerializer", which makes it easy to support
+// multiple versions of a savegame format (again inspired by SCUMM).
+/*
+class VersionedSerializer : public Serializer {
+public:
+ // "version" is the version of the savegame we are loading/creating
+ VersionedSerializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, int version)
+ : Serializer(in, out), _version(version) {
+ assert(in || out);
+ }
+
+ void syncBytes(byte *buf, uint16 size, int minVersion = 0, int maxVersion = INF) {
+ if (_version < minVersion || _version > maxVersion)
+ return; // Do nothing if too old or too new
+ if (_loadStream) {
+ _loadStream->read(buf, size);
+ } else {
+ _saveStream->write(buf, size);
+ }
+ }
+ ...
+
+};
+
+*/
+
+// Mixin class / interface
+// TODO Maybe call it ISerializable or SerializableMixin ?
+// TODO: Taken from SCUMM engine -- move to common/ code?
+class Serializable {
+public:
+ virtual ~Serializable() {}
+ virtual void saveLoadWithSerializer(Serializer *ser) = 0;
+};
+
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp
new file mode 100644
index 0000000000..0a3b01089f
--- /dev/null
+++ b/engines/tinsel/sound.cpp
@@ -0,0 +1,257 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * sound functionality
+ */
+
+#include "tinsel/sound.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/config.h"
+#include "tinsel/music.h"
+#include "tinsel/strres.h"
+#include "tinsel/tinsel.h"
+
+#include "common/endian.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "sound/mixer.h"
+#include "sound/audiocd.h"
+
+namespace Tinsel {
+
+//--------------------------- General data ----------------------------------
+
+// get set when music/sample driver is installed
+static bool bInstalled = false;
+
+SoundManager::SoundManager(TinselEngine *vm) :
+ //_vm(vm), // TODO: Enable this once global _vm var is gone
+ _sampleIndex(0), _sampleIndexLen(0) {
+}
+
+SoundManager::~SoundManager() {
+ free(_sampleIndex);
+}
+
+/**
+ * Plays the specified sample through the sound driver.
+ * @param id Identifier of sample to be played
+ * @param type type of sound (voice or sfx)
+ * @param handle sound handle
+ */
+bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) {
+ // Floppy version has no sample file
+ if (_vm->getFeatures() & GF_FLOPPY)
+ return false;
+
+ // no sample driver?
+ if (!_vm->_mixer->isReady())
+ return false;
+
+ // stop any currently playing sample
+ _vm->_mixer->stopHandle(_handle);
+
+ // make sure id is in range
+ assert(id > 0 && id < _sampleIndexLen);
+
+ // get file offset for this sample
+ uint32 dwSampleIndex = _sampleIndex[id];
+
+ // move to correct position in the sample file
+ _sampleStream.seek(dwSampleIndex);
+ if (_sampleStream.ioFailed() || _sampleStream.pos() != dwSampleIndex)
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // read the length of the sample
+ uint32 sampleLen = _sampleStream.readUint32LE();
+ if (_sampleStream.ioFailed())
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // allocate a buffer
+ void *sampleBuf = malloc(sampleLen);
+ assert(sampleBuf);
+
+ // read all of the sample
+ if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen)
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // FIXME: Should set this in a different place ;)
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volSound);
+ //_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volVoice);
+
+
+ // play it
+ _vm->_mixer->playRaw(type, &_handle, sampleBuf, sampleLen, 22050,
+ Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED);
+
+ if (handle)
+ *handle = _handle;
+
+ return true;
+}
+
+/**
+ * Returns TRUE if there is a sample for the specified sample identifier.
+ * @param id Identifier of sample to be checked
+ */
+bool SoundManager::sampleExists(int id) {
+ if (_vm->_mixer->isReady()) {
+ // make sure id is in range
+ if (id > 0 && id < _sampleIndexLen) {
+ // check for a sample index
+ if (_sampleIndex[id])
+ return true;
+ }
+ }
+
+ // no sample driver or no sample
+ return false;
+}
+
+/**
+ * Returns true if a sample is currently playing.
+ */
+bool SoundManager::sampleIsPlaying(void) {
+ return _vm->_mixer->isSoundHandleActive(_handle);
+}
+
+/**
+ * Stops any currently playing sample.
+ */
+void SoundManager::stopAllSamples(void) {
+ // stop currently playing sample
+ _vm->_mixer->stopHandle(_handle);
+}
+
+/**
+ * Opens and inits all sound sample files.
+ */
+void SoundManager::openSampleFiles(void) {
+ // Floppy and demo versions have no sample files
+ if (_vm->getFeatures() & GF_FLOPPY || _vm->getFeatures() & GF_DEMO)
+ return;
+
+ Common::File f;
+
+ if (_sampleIndex)
+ // already allocated
+ return;
+
+ // open sample index file in binary mode
+ if (f.open(SAMPLE_INDEX)) {
+ // get length of index file
+ f.seek(0, SEEK_END); // move to end of file
+ _sampleIndexLen = f.pos(); // get file pointer
+ f.seek(0, SEEK_SET); // back to beginning
+
+ if (_sampleIndex == NULL) {
+ // allocate a buffer for the indices
+ _sampleIndex = (uint32 *)malloc(_sampleIndexLen);
+
+ // make sure memory allocated
+ if (_sampleIndex == NULL) {
+ // disable samples if cannot alloc buffer for indices
+ // TODO: Disabled sound if we can't load the sample index?
+ return;
+ }
+ }
+
+ // load data
+ if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen)
+ // file must be corrupt if we get to here
+ error("File %s is corrupt", SAMPLE_FILE);
+
+#ifdef SCUMM_BIG_ENDIAN
+ // Convert all ids from LE to native format
+ for (uint i = 0; i < _sampleIndexLen / sizeof(uint32); ++i) {
+ _sampleIndex[i] = READ_LE_UINT32(_sampleIndex + i);
+ }
+#endif
+
+ // close the file
+ f.close();
+
+ // convert file size to size in DWORDs
+ _sampleIndexLen /= sizeof(uint32);
+ } else
+ error("Cannot find file %s", SAMPLE_INDEX);
+
+ // open sample file in binary mode
+ if (!_sampleStream.open(SAMPLE_FILE))
+ error("Cannot find file %s", SAMPLE_FILE);
+
+/*
+ // gen length of the largest sample
+ sampleBuffer.size = _sampleStream.readUint32LE();
+ if (_sampleStream.ioFailed())
+ error("File %s is corrupt", SAMPLE_FILE);
+*/
+}
+
+/**
+ * Initialises the sound driver.
+ */
+
+bool SoundInit(void) {
+ if (!bInstalled) {
+// if (mDriver != NULL) {
+ if (true) { // TODO: Check that a MIDI music output device is available
+ // open MIDI files
+ OpenMidiFiles();
+ }
+
+ if (_vm->_mixer->isReady()) {
+ // open sample files
+ _vm->_sound->openSampleFiles();
+ }
+ bInstalled = true;
+ return true;
+ } else {
+ // already installed
+ return false;
+ }
+}
+
+
+/**
+ * De-initialises the sound driver.
+ */
+
+bool SoundDeinit(void) {
+ if (bInstalled) {
+ bInstalled = false;
+ AudioCD.stop();
+ StopMidi();
+ _vm->_sound->stopAllSamples();
+ DeleteMidiBuffer();
+ return true;
+ } else {
+ // not installed
+ return false;
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h
new file mode 100644
index 0000000000..c80e7589ec
--- /dev/null
+++ b/engines/tinsel/sound.h
@@ -0,0 +1,83 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the Sound Driver data structures etc.
+ */
+
+#ifndef TINSEL_SOUND_H
+#define TINSEL_SOUND_H
+
+#include "common/file.h"
+#include "common/file.h"
+
+#include "sound/mixer.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+#define MAXSAMPVOL 127
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+class SoundManager {
+protected:
+
+ //TinselEngine *_vm; // TODO: Enable this once global _vm var is gone
+
+ /** Sample handle */
+ Audio::SoundHandle _handle;
+
+ /** Sample index buffer and number of entries */
+ uint32 *_sampleIndex;
+
+ /** Number of entries in the sample index */
+ long _sampleIndexLen;
+
+ /** file stream for sample file */
+ Common::File _sampleStream;
+
+public:
+
+ SoundManager(TinselEngine *vm);
+ ~SoundManager();
+
+ bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0);
+ void stopAllSamples(void); // Stops any currently playing sample
+
+ bool sampleExists(int id);
+ bool sampleIsPlaying(void);
+
+ // TODO: Internal method, make this protected?
+ void openSampleFiles(void);
+};
+
+bool SoundInit(void); // Initialises the sound driver
+bool SoundDeinit(void); // De-initialises the sound driver
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SOUND_H
diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp
new file mode 100644
index 0000000000..abf5a880f6
--- /dev/null
+++ b/engines/tinsel/strres.cpp
@@ -0,0 +1,209 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * String resource managment routines
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "common/file.h"
+#include "common/endian.h"
+
+namespace Tinsel {
+
+#ifdef DEBUG
+// Diagnostic number
+int newestString;
+#endif
+
+// buffer for resource strings
+static uint8 *textBuffer = 0;
+
+// language resource string filenames
+static const char *languageFiles[] = {
+ "english.txt",
+ "french.txt",
+ "german.txt",
+ "italian.txt",
+ "spanish.txt"
+};
+
+// Set if we're handling 2-byte characters.
+bool bMultiByte = false;
+
+/**
+ * Called to load a resource file for a different language
+ * @param newLang The new language
+ */
+void ChangeLanguage(LANGUAGE newLang) {
+ Common::File f;
+ uint32 textLen = 0; // length of buffer
+
+ if (textBuffer) {
+ // free the previous buffer
+ free(textBuffer);
+ textBuffer = NULL;
+ }
+
+ // Try and open the specified language file. If it fails, and the language
+ // isn't English, try falling back on opening 'english.txt' - some foreign
+ // language versions reused it rather than their proper filename
+ if (!f.open(languageFiles[newLang])) {
+ if ((newLang == TXT_ENGLISH) || !f.open(languageFiles[TXT_ENGLISH]))
+ error("Cannot find file %s", languageFiles[newLang]);
+ }
+
+ // Check whether the file is compressed or not - for compressed files the
+ // first long is the filelength and for uncompressed files it is the chunk
+ // identifier
+ textLen = f.readUint32LE();
+ if (f.ioFailed())
+ error("File %s is corrupt", languageFiles[newLang]);
+
+ if (textLen == CHUNK_STRING || textLen == CHUNK_MBSTRING) {
+ // the file is uncompressed
+
+ bMultiByte = (textLen == CHUNK_MBSTRING);
+
+ // get length of uncompressed file
+ textLen = f.size();
+ f.seek(0, SEEK_SET); // Set to beginning of file
+
+ if (textBuffer == NULL) {
+ // allocate a text buffer for the strings
+ textBuffer = (uint8 *)malloc(textLen);
+
+ // make sure memory allocated
+ assert(textBuffer);
+ }
+
+ // load data
+ if (f.read(textBuffer, textLen) != textLen)
+ // file must be corrupt if we get to here
+ error("File %s is corrupt", languageFiles[newLang]);
+
+ // close the file
+ f.close();
+ } else { // the file must be compressed
+ error("Compression handling has been removed!");
+ }
+}
+
+/**
+ * Loads a string resource identified by id.
+ * @param id identifier of string to be loaded
+ * @param pBuffer points to buffer that receives the string
+ * @param bufferMax maximum number of chars to be copied to the buffer
+ */
+int LoadStringRes(int id, char *pBuffer, int bufferMax) {
+#ifdef DEBUG
+ // For diagnostics
+ newestString = id;
+#endif
+
+ // base of string resource table
+ uint8 *pText = textBuffer;
+
+ // index into text resource file
+ uint32 index = 0;
+
+ // number of chunks to skip
+ int chunkSkip = id / STRINGS_PER_CHUNK;
+
+ // number of strings to skip when in the correct chunk
+ int strSkip = id % STRINGS_PER_CHUNK;
+
+ // length of string
+ int len;
+
+ // skip to the correct chunk
+ while (chunkSkip-- != 0) {
+ // make sure chunk id is correct
+ assert(READ_LE_UINT32(pText + index) == CHUNK_STRING || READ_LE_UINT32(pText + index) == CHUNK_MBSTRING);
+
+ if (READ_LE_UINT32(pText + index + sizeof(uint32)) == 0) {
+ // TEMPORARY DIRTY BODGE
+ strcpy(pBuffer, "!! HIGH STRING !!");
+
+ // string does not exist
+ return 0;
+ }
+
+ // get index to next chunk
+ index = READ_LE_UINT32(pText + index + sizeof(uint32));
+ }
+
+ // skip over chunk id and offset
+ index += (2 * sizeof(uint32));
+
+ // pointer to strings
+ pText = pText + index;
+
+ // skip to the correct string
+ while (strSkip-- != 0) {
+ // skip to next string
+ pText += *pText + 1;
+ }
+
+ // get length of string
+ len = *pText;
+
+ if (len) {
+ // the string exists
+
+ // copy the string to the buffer
+ if (len < bufferMax) {
+ memcpy(pBuffer, pText + 1, len);
+
+ // null terminate
+ pBuffer[len] = 0;
+
+ // number of chars copied
+ return len + 1;
+ } else {
+ memcpy(pBuffer, pText + 1, bufferMax - 1);
+
+ // null terminate
+ pBuffer[bufferMax - 1] = 0;
+
+ // number of chars copied
+ return bufferMax;
+ }
+ }
+
+ // TEMPORARY DIRTY BODGE
+ strcpy(pBuffer, "!! NULL STRING !!");
+
+ // string does not exist
+ return 0;
+}
+
+void FreeTextBuffer() {
+ if (textBuffer) {
+ free(textBuffer);
+ textBuffer = NULL;
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/strres.h b/engines/tinsel/strres.h
new file mode 100644
index 0000000000..fac287492b
--- /dev/null
+++ b/engines/tinsel/strres.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * String resource managment routines
+ */
+
+#ifndef TINSEL_STRRES_H
+#define TINSEL_STRRES_H
+
+#include "common/scummsys.h"
+#include "tinsel/scn.h"
+
+namespace Tinsel {
+
+#define STRINGS_PER_CHUNK 64 // number of strings per chunk in the language text files
+#define FIRST_STR_ID 1 // id number of first string in string table
+#define MAX_STRING_SIZE 255 // maximum size of a string in the resource table
+#define MAX_STRRES_SIZE 300000 // maximum size of string resource file
+
+// Set if we're handling 2-byte characters.
+extern bool bMultiByte;
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+/**
+ * Called to load a resource file for a different language
+ * @param newLang The new language
+ */
+void ChangeLanguage(LANGUAGE newLang);
+
+/**
+ * Loads a string resource identified by id.
+ * @param id identifier of string to be loaded
+ * @param pBuffer points to buffer that receives the string
+ * @param bufferMax maximum number of chars to be copied to the buffer
+ */
+int LoadStringRes(int id, char *pBuffer, int bufferMax);
+
+/**
+ * Frees the text buffer allocated from ChangeLanguage()
+ */
+void FreeTextBuffer();
+
+} // end of namespace Tinsel
+
+#endif
+
diff --git a/engines/tinsel/text.cpp b/engines/tinsel/text.cpp
new file mode 100644
index 0000000000..dab97f7fdf
--- /dev/null
+++ b/engines/tinsel/text.cpp
@@ -0,0 +1,279 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utilities.
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h" // object plotting
+#include "tinsel/handle.h"
+#include "tinsel/sched.h" // process scheduler defines
+#include "tinsel/strres.h" // bMultiByte
+#include "tinsel/text.h" // text defines
+
+namespace Tinsel {
+
+/**
+ * Returns the length of one line of a string in pixels.
+ * @param szStr String
+ * @param pFont Which font to use for dimensions
+ */
+int StringLengthPix(char *szStr, const FONT *pFont) {
+ int strLen; // accumulated length of string
+ byte c;
+ SCNHANDLE hImg;
+
+ // while not end of string or end of line
+ for (strLen = 0; (c = *szStr) != EOS_CHAR && c != LF_CHAR; szStr++) {
+ if (bMultiByte) {
+ if (c & 0x80)
+ c = ((c & ~0x80) << 8) + *++szStr;
+ }
+ hImg = FROM_LE_32(pFont->fontDef[c]);
+
+ if (hImg) {
+ // there is a IMAGE for this character
+ const IMAGE *pChar = (const IMAGE *)LockMem(hImg);
+
+ // add width of font bitmap
+ strLen += FROM_LE_16(pChar->imgWidth);
+ } else
+ // use width of space character
+ strLen += FROM_LE_32(pFont->spaceSize);
+
+ // finally add the inter-character spacing
+ strLen += FROM_LE_32(pFont->xSpacing);
+ }
+
+ // return length of line in pixels - minus inter-char spacing for last character
+ strLen -= FROM_LE_32(pFont->xSpacing);
+ return (strLen > 0) ? strLen : 0;
+}
+
+/**
+ * Returns the justified x start position of a line of text.
+ * @param szStr String to output
+ * @param xPos X position of string
+ * @param pFont Which font to use
+ * @param mode Mode flags for the string
+ */
+int JustifyText(char *szStr, int xPos, const FONT *pFont, int mode) {
+ if (mode & TXT_CENTRE) {
+ // centre justify the text
+
+ // adjust x positioning by half the length of line in pixels
+ xPos -= StringLengthPix(szStr, pFont) / 2;
+ } else if (mode & TXT_RIGHT) {
+ // right justify the text
+
+ // adjust x positioning by length of line in pixels
+ xPos -= StringLengthPix(szStr, pFont);
+ }
+
+ // return text line x start position
+ return xPos;
+}
+
+/**
+ * Main text outputting routine. If a object list is specified a
+ * multi-object is created for the whole text and a pointer to the head
+ * of the list is returned.
+ * @param pList Object list to add text to
+ * @param szStr String to output
+ * @param colour Colour for monochrome text
+ * @param xPos X position of string
+ * @param yPos Y position of string
+ * @param hFont Which font to use
+ * @param mode Mode flags for the string
+ */
+OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos,
+ SCNHANDLE hFont, int mode) {
+ int xJustify; // x position of text after justification
+ int yOffset; // offset to next line of text
+ OBJECT *pFirst; // head of multi-object text list
+ OBJECT *pChar = 0; // object ptr for the character
+ byte c;
+ SCNHANDLE hImg;
+ const IMAGE *pImg;
+
+ // make sure there is a linked list to add text to
+ assert(pList);
+
+ // get font pointer
+ const FONT *pFont = (const FONT *)LockMem(hFont);
+
+ // init head of text list
+ pFirst = NULL;
+
+ // get image for capital W
+ assert(pFont->fontDef[(int)'W']);
+ pImg = (const IMAGE *)LockMem(FROM_LE_32(pFont->fontDef[(int)'W']));
+
+ // get height of capital W for offset to next line
+ yOffset = FROM_LE_16(pImg->imgHeight);
+
+ while (*szStr) {
+ // x justify the text according to the mode flags
+ xJustify = JustifyText(szStr, xPos, pFont, mode);
+
+ // repeat until end of string or end of line
+ while ((c = *szStr) != EOS_CHAR && c != LF_CHAR) {
+ if (bMultiByte) {
+ if (c & 0x80)
+ c = ((c & ~0x80) << 8) + *++szStr;
+ }
+ hImg = FROM_LE_32(pFont->fontDef[c]);
+
+ if (hImg == 0) {
+ // no image for this character
+
+ // add font spacing for a space character
+ xJustify += FROM_LE_32(pFont->spaceSize);
+ } else { // printable character
+
+ int aniX, aniY; // char image animation offsets
+
+ OBJ_INIT oi;
+ oi.hObjImg = FROM_LE_32(pFont->fontInit.hObjImg);
+ oi.objFlags = FROM_LE_32(pFont->fontInit.objFlags);
+ oi.objID = FROM_LE_32(pFont->fontInit.objID);
+ oi.objX = FROM_LE_32(pFont->fontInit.objX);
+ oi.objY = FROM_LE_32(pFont->fontInit.objY);
+ oi.objZ = FROM_LE_32(pFont->fontInit.objZ);
+
+ // allocate and init a character object
+ if (pFirst == NULL)
+ // first time - init head of list
+ pFirst = pChar = InitObject(&oi); // FIXME: endian issue using fontInit!!!
+ else
+ // chain to multi-char list
+ pChar = pChar->pSlave = InitObject(&oi); // FIXME: endian issue using fontInit!!!
+
+ // convert image handle to pointer
+ pImg = (const IMAGE *)LockMem(hImg);
+
+ // fill in character object
+ pChar->hImg = hImg; // image def
+ pChar->width = FROM_LE_16(pImg->imgWidth); // width of chars bitmap
+ pChar->height = FROM_LE_16(pImg->imgHeight); // height of chars bitmap
+ pChar->hBits = FROM_LE_32(pImg->hImgBits); // bitmap
+
+ // check for absolute positioning
+ if (mode & TXT_ABSOLUTE)
+ pChar->flags |= DMA_ABS;
+
+ // set characters colour - only effective for mono fonts
+ pChar->constant = colour;
+
+ // get Y animation offset
+ GetAniOffset(hImg, pChar->flags, &aniX, &aniY);
+
+ // set x position - ignore animation point
+ pChar->xPos = intToFrac(xJustify);
+
+ // set y position - adjust for animation point
+ pChar->yPos = intToFrac(yPos - aniY);
+
+ if (mode & TXT_SHADOW) {
+ // we want to shadow the character
+ OBJECT *pShad;
+
+ // allocate a object for the shadow and chain to multi-char list
+ pShad = pChar->pSlave = AllocObject();
+
+ // copy the character for a shadow
+ CopyObject(pShad, pChar);
+
+ // add shadow offsets to characters position
+ pShad->xPos += intToFrac(FROM_LE_32(pFont->xShadow));
+ pShad->yPos += intToFrac(FROM_LE_32(pFont->yShadow));
+
+ // shadow is behind the character
+ pShad->zPos--;
+
+ // shadow is always mono
+ pShad->flags = DMA_CNZ | DMA_CHANGED;
+
+ // check for absolute positioning
+ if (mode & TXT_ABSOLUTE)
+ pShad->flags |= DMA_ABS;
+
+ // shadow always uses first palette entry
+ // should really alloc a palette here also ????
+ pShad->constant = 1;
+
+ // add shadow to object list
+ InsertObject(pList, pShad);
+ }
+
+ // add character to object list
+ InsertObject(pList, pChar);
+
+ // move to end of list
+ if (pChar->pSlave)
+ pChar = pChar->pSlave;
+
+ // add character spacing
+ xJustify += FROM_LE_16(pImg->imgWidth);
+ }
+
+ // finally add the inter-character spacing
+ xJustify += FROM_LE_32(pFont->xSpacing);
+
+ // next character in string
+ ++szStr;
+ }
+
+ // adjust the text y position and add the inter-line spacing
+ yPos += yOffset + FROM_LE_32(pFont->ySpacing);
+
+ // check for newline
+ if (c == LF_CHAR)
+ // next character in string
+ ++szStr;
+ }
+
+ // return head of list
+ return pFirst;
+}
+
+/**
+ * Is there an image for this character in this font?
+ * @param hFont which font to use
+ * @param c character to test
+ */
+bool IsCharImage(SCNHANDLE hFont, char c) {
+ byte c2 = (byte)c;
+
+ // Inventory save game name editor needs to be more clever for
+ // multi-byte characters. This bodge will stop it erring.
+ if (bMultiByte && (c2 & 0x80))
+ return false;
+
+ // get font pointer
+ const FONT *pFont = (const FONT *)LockMem(hFont);
+
+ return pFont->fontDef[c2] != 0;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h
new file mode 100644
index 0000000000..78998831a1
--- /dev/null
+++ b/engines/tinsel/text.h
@@ -0,0 +1,101 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utility defines
+ */
+
+#ifndef TINSEL_TEXT_H // prevent multiple includes
+#define TINSEL_TEXT_H
+
+#include "tinsel/object.h" // object manager defines
+
+namespace Tinsel {
+
+/** text mode flags - defaults to left justify */
+enum {
+ TXT_CENTRE = 0x0001, //!< centre justify text
+ TXT_RIGHT = 0x0002, //!< right justify text
+ TXT_SHADOW = 0x0004, //!< shadow each character
+ TXT_ABSOLUTE = 0x0008 //!< position of text is absolute (only for object text)
+};
+
+/** maximum number of characters in a font */
+#define MAX_FONT_CHARS 256
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/**
+ * Text font data structure.
+ * @note only the pointer is used so the size of fontDef[] is not important.
+ * It is currently set at 300 because it suited me for debugging.
+ */
+struct FONT {
+ int xSpacing; //!< x spacing between characters
+ int ySpacing; //!< y spacing between characters
+ int xShadow; //!< x shadow offset
+ int yShadow; //!< y shadow offset
+ int spaceSize; //!< x spacing to use for a space character
+ OBJ_INIT fontInit; //!< structure used to init text objects
+ SCNHANDLE fontDef[300]; //!< image handle array for all characters in the font
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/** structure for passing the correct parameters to ObjectTextOut */
+struct TEXTOUT {
+ OBJECT *pList; //!< object list to add text to
+ char *szStr; //!< string to output
+ int colour; //!< colour for monochrome text
+ int xPos; //!< x position of string
+ int yPos; //!< y position of string
+ SCNHANDLE hFont; //!< which font to use
+ int mode; //!< mode flags for the string
+ int sleepTime; //!< sleep time between each character (if non-zero)
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Text Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+OBJECT *ObjectTextOut( // output a string of text
+ OBJECT *pList, // object list to add text to
+ char *szStr, // string to output
+ int colour, // colour for monochrome text
+ int xPos, // x position of string
+ int yPos, // y position of string
+ SCNHANDLE hFont, // which font to use
+ int mode); // mode flags for the string
+
+OBJECT *ObjectTextOutIndirect( // output a string of text
+ TEXTOUT *pText); // pointer to TextOut struct with all parameters
+
+bool IsCharImage( // Is there an image for this character in this font?
+ SCNHANDLE hFont, // which font to use
+ char c); // character to test
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TEXT_H
diff --git a/engines/tinsel/timers.cpp b/engines/tinsel/timers.cpp
new file mode 100644
index 0000000000..c7b9d3708b
--- /dev/null
+++ b/engines/tinsel/timers.cpp
@@ -0,0 +1,192 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ *
+ * Note: As part of the transition to ScummVM, the ticks field of a timer has been changed
+ * to a millisecond value, rather than ticks at 24Hz. Most places should be able to use
+ * the timers without change, since the ONE_SECOND constant has been set to be in MILLISECONDS
+ */
+
+#include "tinsel/timers.h"
+#include "tinsel/dw.h"
+#include "tinsel/serializer.h"
+
+#include "common/system.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+#define MAX_TIMERS 16
+
+struct TIMER {
+ int tno; /**< Timer number */
+ int ticks; /**< Tick count */
+ int secs; /**< Second count */
+ int delta; /**< Increment/decrement value */
+ bool frame; /**< If set, in ticks, otherwise in seconds */
+};
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static TIMER timers[MAX_TIMERS];
+
+
+//--------------------------------------------------------
+
+/**
+ * Gets the current time in number of ticks.
+ *
+ * DOS timer ticks is the number of 54.9254ms since midnight. Converting the
+ * millisecond count won't give the exact same 'since midnight' count, but I
+ * figure that as long as the timing interval is more or less accurate, it
+ * shouldn't be a problem.
+ */
+
+uint32 DwGetCurrentTime() {
+ return g_system->getMillis() * 55 / 1000;
+}
+
+/**
+ * Resets all of the timer slots
+ */
+
+void RebootTimers(void) {
+ memset(timers, 0, sizeof(timers));
+}
+
+/**
+ * (Un)serialize the timer data for save/restore game.
+ */
+void syncTimerInfo(Serializer &s) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ s.syncAsSint32LE(timers[i].tno);
+ s.syncAsSint32LE(timers[i].ticks);
+ s.syncAsSint32LE(timers[i].secs);
+ s.syncAsSint32LE(timers[i].delta);
+ s.syncAsSint32LE(timers[i].frame);
+ }
+}
+
+/**
+ * Find the timer numbered thus, if one is thus numbered.
+ * @param num number of the timer
+ * @return the timer with the specified number, or NULL if there is none
+ */
+static TIMER *findTimer(int num) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (timers[i].tno == num)
+ return &timers[i];
+ }
+ return NULL;
+}
+
+/**
+ * Find an empty timer slot.
+ */
+static TIMER *allocateTimer(int num) {
+ assert(num); // zero is not allowed as a timer number
+ assert(!findTimer(num)); // Allocating already existant timer
+
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (!timers[i].tno) {
+ timers[i].tno = num;
+ return &timers[i];
+ }
+ }
+
+ error("Too many timers");
+}
+
+/**
+ * Update all timers, as appropriate.
+ */
+void FettleTimers(void) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (!timers[i].tno)
+ continue;
+
+ timers[i].ticks += timers[i].delta; // Update tick value
+
+ if (timers[i].frame) {
+ if (timers[i].ticks < 0)
+ timers[i].ticks = 0; // Have reached zero
+ } else {
+ if (timers[i].ticks < 0) {
+ timers[i].ticks = ONE_SECOND;
+ timers[i].secs--;
+ if (timers[i].secs < 0)
+ timers[i].secs = 0; // Have reached zero
+ } else if (timers[i].ticks == ONE_SECOND) {
+ timers[i].ticks = 0;
+ timers[i].secs++; // Another second has passed
+ }
+ }
+ }
+}
+
+/**
+ * Start a timer up.
+ */
+void DwSetTimer(int num, int sval, bool up, bool frame) {
+ TIMER *pt;
+
+ assert(num); // zero is not allowed as a timer number
+
+ pt = findTimer(num);
+ if (pt == NULL)
+ pt = allocateTimer(num);
+
+ pt->delta = up ? 1 : -1; // Increment/decrement value
+ pt->frame = frame;
+
+ if (frame) {
+ pt->secs = 0;
+ pt->ticks = sval;
+ } else {
+ pt->secs = sval;
+ pt->ticks = 0;
+ }
+}
+
+/**
+ * Return the current count of a timer.
+ */
+int Timer(int num) {
+ TIMER *pt;
+
+ pt = findTimer(num);
+
+ if (pt == NULL)
+ return -1;
+
+ if (pt->frame)
+ return pt->ticks;
+ else
+ return pt->secs;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/timers.h b/engines/tinsel/timers.h
new file mode 100644
index 0000000000..75eb87ee2b
--- /dev/null
+++ b/engines/tinsel/timers.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ */
+
+#ifndef TINSEL_TIMERS_H // prevent multiple includes
+#define TINSEL_TIMERS_H
+
+#include "common/scummsys.h"
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+class Serializer;
+
+#define ONE_SECOND 24
+
+uint32 DwGetCurrentTime(void);
+
+void RebootTimers(void);
+
+void syncTimerInfo(Serializer &s);
+
+void FettleTimers(void);
+
+void DwSetTimer(int num, int sval, bool up, bool frame);
+
+int Timer(int num);
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp
new file mode 100644
index 0000000000..a07a16a205
--- /dev/null
+++ b/engines/tinsel/tinlib.cpp
@@ -0,0 +1,2980 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Glitter library functions.
+ *
+ * In the main called only from PCODE.C
+ * Function names are the same as Glitter code function names.
+ *
+ * To ensure exclusive use of resources and exclusive control responsibilities.
+ */
+
+#define BODGE
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/coroutine.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/music.h"
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/scn.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h"
+#include "tinsel/tinsel.h"
+#include "tinsel/token.h"
+
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+// In DOS_DW.C
+extern bool bRestart; // restart flag - set to restart the game
+extern bool bHasRestarted; // Set after a restart
+
+// In DOS_MAIN.C
+// TODO/FIXME: From dos_main.c: "Only used on PSX so far"
+int clRunMode = 0;
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern void startupBackground(SCNHANDLE bfilm);
+extern void ChangePalette(SCNHANDLE hPal);
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+// in DOS_DW.C
+extern void SetHookScene(SCNHANDLE scene, int entrance, int transition);
+extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+extern void UnHookScene(void);
+extern void SuspendHook(void);
+extern void UnSuspendHook(void);
+
+// in PDISPLAY.C
+extern void EnableTags(void);
+extern void DisableTags(void);
+bool DisableTagsIfEnabled(void);
+extern void setshowstring(void);
+
+// in PLAY.C
+extern void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop);
+extern void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop);
+
+// in SCENE.C
+extern void setshowpos(void);
+
+#ifdef BODGE
+// In DOS_HAND.C
+bool ValidHandle(SCNHANDLE offset);
+
+// In SCENE.C
+SCNHANDLE GetSceneHandle(void);
+#endif
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+bool bEnableF1;
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define JAP_TEXT_TIME (2*ONE_SECOND)
+
+/*----------------------------------------------------------------------*\
+|* Library Procedure and Function codes *|
+\*----------------------------------------------------------------------*/
+
+enum LIB_CODE {
+ ACTORATTR = 0, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS = 4,
+ ACTORYPOS, ADDICON, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE = 10,
+ BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION = 15,
+ CONVICON, CURSORXPOS, CURSORYPOS, DEC_CONVW, DEC_CURSOR = 20,
+ DEC_INV1, DEC_INV2, DEC_INVW, DEC_LEAD, DEC_TAGFONT = 25,
+ DEC_TALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT = 31,
+ GETINVLIMIT, HELDOBJECT, HIDE, ININVENTORY, INVDEPICT = 36,
+ INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, LEFTOFFSET = 42,
+ MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE = 48,
+ PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ = 54,
+ PRINTTAG, RANDOM, RESTORE_SCENE, SAVE_SCENE, SCALINGREELS = 59,
+ SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT = 65,
+ SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY = 71,
+ STAND, STANDTAG, STOP, SWALK, TAGACTOR, TALK, TALKATTR, TIMER = 79,
+ TOPOFFSET, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY = 85,
+ WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG = 91,
+ WHICHINVENTORY = 92,
+ ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME, RESETIDLETIME = 97,
+ TALKAT, UNHOOKSCENE, WAITFRAME, DEC_CSTRINGS, STOPMIDI, STOPSAMPLE = 103,
+ TALKATS = 104,
+ DEC_FLAGS, FADEMIDI, CLEARHOOKSCENE, SETINVSIZE, INWHICHINV = 109,
+ NOBLOCKING, SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEF1 = 113,
+ RESTARTGAME, QUITGAME, FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD = 119,
+ HASRESTARTED, RESTORE_CUT, RUNMODE, SUBTITLES, SETLANGUAGE = 124
+};
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+// Saved cursor co-ordinates for control(on) to restore cursor position
+// as it was at control(off).
+// They are global so that movecursor(..) has a net effect if it
+// precedes control(on).
+static int controlX = 0, controlY = 0;
+
+static int offtype = 0; // used by control()
+static uint32 lastValue = 0; // used by dw_random()
+static int scrollCount = 0; // used by scroll()
+
+static bool NotPointedRunning = false; // Used in printobj and printobjPointed
+
+static COLORREF s_talkfontColor = 0;
+
+//----------------- FORWARD REFERENCES --------------------
+
+void resetidletime(void);
+void stopmidi(void);
+void stopsample(void);
+void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescTime);
+
+
+/**
+ * NOT A LIBRARY FUNCTION
+ *
+ * Poke supplied colours into the DAC queue.
+ */
+static void setTextPal(COLORREF col) {
+ s_talkfontColor = col;
+ UpdateDACqueue(TALKFONT_COL, 1, &s_talkfontColor);
+}
+
+
+static int TextTime(char *pTstring) {
+ if (isJapanMode())
+ return JAP_TEXT_TIME;
+ else if (!speedText)
+ return strlen(pTstring) + ONE_SECOND;
+ else
+ return strlen(pTstring) + ONE_SECOND + (speedText * 5 * ONE_SECOND) / 100;
+}
+
+/*--------------------------------------------------------------------------*/
+
+
+/**
+ * Set actor's attributes.
+ * - currently only the text colour.
+ */
+void actorattr(int actor, int r1, int g1, int b1) {
+ storeActorAttr(actor, r1, g1, b1);
+}
+
+/**
+ * Return the actor's direction.
+ */
+int actordirection(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor != NULL); // not a moving actor
+
+ return (int)GetMActorDirection(pActor);
+}
+
+/**
+ * Return the actor's scale.
+ */
+int actorscale(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor != NULL); // not a moving actor
+
+ return (int)GetMActorScale(pActor);
+}
+
+/**
+ * Returns the x or y position of an actor.
+ */
+int actorpos(int xory, int actor) {
+ int x, y;
+
+ GetActorPos(actor, &x, &y);
+ return (xory == ACTORXPOS) ? x : y;
+}
+
+/**
+ * Make all actors alive at the start of each scene.
+ */
+void actorson(void) {
+ setactorson();
+}
+
+/**
+ * Adds an icon to the conversation window.
+ */
+void addicon(int icon) {
+ AddToInventory(INV_CONV, icon, false);
+}
+
+/**
+ * Place the object in inventory 1 or 2.
+ */
+void addinv(int invno, int object) {
+ assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN); // illegal inventory number
+
+ AddToInventory(invno, object, false);
+}
+
+/**
+ * Define an actor's walk and stand reels for an auxilliary scale.
+ */
+void auxscale(int actor, int scale, SCNHANDLE *rp) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor); // Can't set aux scale for a non-moving actor
+
+ int j;
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[scale-1][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[scale-1][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->TalkReels[scale-1][j] = *rp++;
+}
+
+/**
+ * Defines the background image for a scene.
+ */
+void background(SCNHANDLE bfilm) {
+ startupBackground(bfilm);
+}
+
+/**
+ * Sets focus of the scroll process.
+ */
+void camera(int actor) {
+ ScrollFocus(actor);
+}
+
+/**
+ * A CDPLAY() is imminent.
+ */
+void cdload(SCNHANDLE start, SCNHANDLE next) {
+ assert(start && next && start != next); // cdload() fault
+
+// TODO/FIXME
+// LoadExtraGraphData(start, next);
+}
+
+/**
+ * Clear the hooked scene (if any)
+ */
+
+void clearhookscene() {
+ SetHookScene(0, 0, 0);
+}
+
+/**
+ * Guess what.
+ */
+
+void closeinventory(void) {
+ KillInventory();
+}
+
+/**
+ * Turn off cursor and take control from player - and variations on the theme.
+ * OR Restore cursor and return control to the player.
+ */
+
+void control(int param) {
+ bEnableF1 = false;
+
+ switch (param) {
+ case CONTROL_STARTOFF:
+ GetControlToken(); // Take control
+ DisableTags(); // Switch off tags
+ DwHideCursor(); // Blank out cursor
+ offtype = param;
+ break;
+
+ case CONTROL_OFF:
+ case CONTROL_OFFV:
+ case CONTROL_OFFV2:
+ if (TestToken(TOKEN_CONTROL)) {
+ GetControlToken(); // Take control
+
+ DisableTags(); // Switch off tags
+ GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position
+
+ // There may be a button timing out
+ GetToken(TOKEN_LEFT_BUT);
+ FreeToken(TOKEN_LEFT_BUT);
+ }
+
+ if (offtype == CONTROL_STARTOFF)
+ GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position
+
+ offtype = param;
+
+ if (param == CONTROL_OFF)
+ DwHideCursor(); // Blank out cursor
+ else if (param == CONTROL_OFFV) {
+ UnHideCursor();
+ FreezeCursor();
+ } else if (param == CONTROL_OFFV2) {
+ UnHideCursor();
+ }
+ break;
+
+ case CONTROL_ON:
+ if (offtype != CONTROL_OFFV2 && offtype != CONTROL_STARTOFF)
+ SetCursorXY(controlX, controlY);// ... where it was
+
+ FreeControlToken(); // Release control
+
+ if (!InventoryActive())
+ EnableTags(); // Tags back on
+
+ RestoreMainCursor(); // Re-instate cursor...
+ }
+}
+
+/**
+ * Open or close the conversation window.
+ */
+
+void conversation(int fn, HPOLYGON hp, bool escOn, int myescEvent) {
+ assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block
+
+ switch (fn) {
+ case CONV_END: // Close down conversation
+ CloseDownConv();
+ break;
+
+ case CONV_DEF: // Default (i.e. TOP of screen)
+ case CONV_BOTTOM: // BOTTOM of screen
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+
+ if (IsConvWindow())
+ break;
+
+ KillInventory();
+ convPos(fn);
+ ConvPoly(hp);
+ PopUpInventory(INV_CONV); // Conversation window
+ ConvAction(INV_OPENICON); // CONVERSATION event
+ break;
+ }
+}
+
+/**
+ * Add icon to conversation window's permanent default list.
+ */
+
+void convicon(int icon) {
+ AddIconToPermanentDefaultList(icon);
+}
+
+/**
+ * Returns the x or y position of the cursor.
+ */
+
+int cursorpos(int xory) {
+ int x, y;
+
+ GetCursorXY(&x, &y, true);
+ return (xory == CURSORXPOS) ? x : y;
+}
+
+/**
+ * Declare conversation window.
+ */
+
+void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ idec_convw(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare config strings.
+ */
+
+void dec_cstrings(SCNHANDLE *tp) {
+ setConfigStrings(tp);
+}
+
+/**
+ * Declare cursor's reels.
+ */
+
+void dec_cursor(SCNHANDLE bfilm) {
+ DwInitCursor(bfilm);
+}
+
+/**
+ * Declare the language flags.
+ */
+
+void dec_flags(SCNHANDLE hf) {
+ setFlagFilms(hf);
+}
+
+/**
+ * Declare inventory 1's parameters.
+ */
+
+void dec_inv1(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv1(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare inventory 2's parameters.
+ */
+
+void dec_inv2(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv2(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare the bits that the inventory windows are constructed from.
+ */
+
+void dec_invw(SCNHANDLE hf) {
+ setInvWinParts(hf);
+}
+
+/**
+ * Declare lead actor.
+ * - the actor's id, walk and stand reels for all the regular scales,
+ * and the tag text.
+ */
+
+void dec_lead(uint32 id, SCNHANDLE *rp, SCNHANDLE text) {
+ PMACTOR pActor; // Moving actor structure
+
+ Tag_Actor(id, text, TAG_DEF); // The lead actor is automatically tagged
+ setleadid(id); // Establish this as the lead
+ SetMover(id); // Establish as a moving actor
+
+ pActor = GetMover(id); // Get moving actor structure
+ assert(pActor);
+
+ // Store all those reels
+ int i, j;
+ for (i = 0; i < 5; ++i) {
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->TalkReels[i][j] = *rp++;
+ }
+
+
+ for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
+ for (j = 0; j < 4; ++j) {
+ pActor->WalkReels[i][j] = pActor->WalkReels[4][j];
+ pActor->StandReels[i][j] = pActor->StandReels[2][j];
+ pActor->TalkReels[i][j] = pActor->TalkReels[4][j];
+ }
+ }
+}
+
+/**
+ * Declare the text font.
+ */
+
+void dec_tagfont(SCNHANDLE hf) {
+ TagFontHandle(hf); // Store the font handle
+}
+
+/**
+ * Declare the text font.
+ */
+
+void dec_talkfont(SCNHANDLE hf) {
+ TalkFontHandle(hf); // Store the font handle
+}
+
+/**
+ * Remove an icon from the conversation window.
+ */
+
+void delicon(int icon) {
+ RemFromInventory(INV_CONV, icon);
+}
+
+/**
+ * Delete the object from inventory 1 or 2.
+ */
+
+void delinv(int object) {
+ if (!RemFromInventory(INV_1, object)) // Remove from inventory 1...
+ RemFromInventory(INV_2, object); // ...or 2 (whichever)
+
+ DropItem(object); // Stop holding it
+}
+
+/**
+ * enablef1
+ */
+
+void enablef1(void) {
+ bEnableF1 = true;
+}
+
+/**
+ * fademidi(in/out)
+ */
+
+void fademidi(CORO_PARAM, int inout) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ assert(inout == FM_IN || inout == FM_OUT);
+
+ // To prevent compiler complaining
+ if (inout == FM_IN || inout == FM_OUT)
+ CORO_SLEEP(1);
+ CORO_END_CODE;
+}
+
+/**
+ * Guess what.
+ */
+
+int getinvlimit(int invno) {
+ return InvGetLimit(invno);
+}
+
+/**
+ * Returns TRUE if the game has been restarted, FALSE if not.
+ */
+bool hasrestarted(void) {
+ return bHasRestarted;
+}
+
+/**
+ * Returns which object is currently held.
+ */
+
+int heldobject(void) {
+ return WhichItemHeld();
+}
+
+/**
+ * Removes a player from the screen, probably when he's about to be
+ * replaced by an animation.
+ *
+ * Not believed to work anymore! (hide() is not used).
+ */
+
+void hide(int actor) {
+ HideActor(actor);
+}
+
+/**
+ * hookscene(scene, entrance, transition)
+ */
+
+void hookscene(SCNHANDLE scene, int entrance, int transition) {
+ SetHookScene(scene, entrance, transition);
+}
+
+/**
+ * idletime
+ */
+
+int idletime(void) {
+ uint32 x;
+
+ x = getUserEventTime() / ONE_SECOND;
+
+ if (!TestToken(TOKEN_CONTROL))
+ resetidletime();
+
+ return (int)x;
+}
+
+/**
+ * invdepict
+ */
+void invdepict(int object, SCNHANDLE hFilm) {
+ invObjectFilm(object, hFilm);
+}
+
+/**
+ * See if an object is in the inventory.
+ */
+int ininventory(int object) {
+ return (InventoryPos(object) != INV_NOICON);
+}
+
+/**
+ * Open an inventory.
+ */
+void inventory(int invno, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory
+
+ PopUpInventory(invno);
+}
+
+/**
+ * See if an object is in the inventory.
+ */
+int inwhichinv(int object) {
+ if (WhichItemHeld() == object)
+ return 0;
+
+ if (IsInInventory(object, INV_1))
+ return 1;
+
+ if (IsInInventory(object, INV_2))
+ return 2;
+
+ return -1;
+}
+
+/**
+ * Kill an actor.
+ */
+void killactor(int actor) {
+ DisableActor(actor);
+}
+
+/**
+ * Turn a blocking polygon off.
+ */
+void killblock(int block) {
+ DisableBlock(block);
+}
+
+/**
+ * Turn an exit off.
+ */
+void killexit(int exit) {
+ DisableExit(exit);
+}
+
+/**
+ * Turn a tag off.
+ */
+void killtag(int tagno) {
+ DisableTag(tagno);
+}
+
+/**
+ * Returns the left or top offset of the screen.
+ */
+int ltoffset(int lort) {
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ return (lort == LEFTOFFSET) ? Loffset : Toffset;
+}
+
+/**
+ * Set new cursor position.
+ */
+void movecursor(int x, int y) {
+ SetCursorXY(x, y);
+
+ controlX = x; // Save these values so that
+ controlY = y; // control(on) doesn't undo this
+}
+
+/**
+ * Triggers change to a new scene.
+ */
+void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+#ifdef BODGE
+ if (!ValidHandle(scene)) {
+ scene = GetSceneHandle();
+ entrance = 1;
+ }
+ assert(scene); // Non-existant first scene!
+#endif
+
+ SetNewScene(scene, entrance, transition);
+
+#if 1
+ // Prevent tags and cursor re-appearing
+ GetControl(CONTROL_STARTOFF);
+#endif
+
+ // Prevent code subsequent to this call running before scene changes
+ if (ProcessGetPID(CurrentProcess()) != PID_MASTER_SCR)
+ CORO_KILL_SELF();
+ CORO_END_CODE;
+}
+
+/**
+ * Disable dynamic blocking for current scene.
+ */
+void noblocking(void) {
+ bNoBlocking = true;
+}
+
+/**
+ * Define a no-scroll boundary for the current scene.
+ */
+void noscroll(int x1, int y1, int x2, int y2) {
+ SetNoScroll(x1, y1, x2, y2);
+}
+
+/**
+ * Hold the specified object.
+ */
+void objectheld(int object) {
+ HoldItem(object);
+}
+
+/**
+ * Set the top left offset of the screen.
+ */
+void offset(int x, int y) {
+ KillScroll();
+ PlayfieldSetPos(FIELD_WORLD, x, y);
+}
+
+/**
+ * Play a film.
+ */
+void play(CORO_PARAM, SCNHANDLE film, int x, int y, int compit, int actorid, bool splay, int sfact,
+ bool escOn, int myescEvent, bool bTop) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(film != 0); // play(): Trying to play NULL film
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // If this actor is dead, call a stop to the calling process
+ if (actorid && !actorAlive(actorid))
+ CORO_KILL_SELF();
+
+ // 7/4/95
+ if (!escOn)
+ myescEvent = GetEscEvents();
+
+ if (compit == 1) {
+ // Play to completion before returning
+ CORO_INVOKE_ARGS(playFilmc, (CORO_SUBCTX, film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop));
+ } else if (compit == 2) {
+ error("play(): compit == 2 - please advise John");
+ } else {
+ // Kick off the play and return.
+ playFilm(film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a midi file.
+ */
+void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) {
+ // FIXME: This is a workaround for the FIXME below
+ if (GetMidiVolume() == 0)
+ return;
+
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ assert(loop == MIDI_DEF || loop == MIDI_LOOP);
+
+ PlayMidiSequence(hMidi, loop == MIDI_LOOP);
+
+ // FIXME: The following check messes up the script arguments when
+ // entering the secret door in the bookshelf in the library,
+ // leading to a crash, when the music volume is set to 0 (MidiPlaying()
+ // always false then).
+ //
+ // Why exactly this happens is unclear. An analysis of the involved
+ // script(s) might reveal more.
+ //
+ // Note: This check&sleep was added in DW v2. It was most likely added
+ // to ensure that the MIDI song started playing before the next opcode
+ // is executed.
+ if (!MidiPlaying())
+ CORO_SLEEP(1);
+
+ if (complete) {
+ while (MidiPlaying())
+ CORO_SLEEP(1);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a sample.
+ */
+void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ Audio::SoundHandle handle;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't play SFX if voice is already playing
+ if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType))
+ return;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents()) {
+ _vm->_sound->stopAllSamples(); // Stop any currently playing sample
+ return;
+ }
+
+ if (volSound != 0 && _vm->_sound->sampleExists(sample)) {
+ _vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle);
+
+ if (complete) {
+ while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents()) {
+ _vm->_mixer->stopHandle(_ctx->handle);
+ break;
+ }
+
+ CORO_SLEEP(1);
+ }
+ }
+ } else {
+ // Prevent Glitter lock-up
+ CORO_SLEEP(1);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a sample.
+ */
+void tryplaysample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not appropriate
+ if (_vm->_sound->sampleIsPlaying()) {
+ // return, but prevent Glitter lock-up
+ CORO_SLEEP(1);
+ return;
+ }
+
+ CORO_INVOKE_ARGS(playsample, (CORO_SUBCTX, sample, complete, escOn, myescEvent));
+ CORO_END_CODE;
+}
+
+/**
+ * Trigger pre-loading of a scene's data.
+ */
+void preparescene(SCNHANDLE scene) {
+#ifdef BODGE
+ if (!ValidHandle(scene))
+ return;
+#endif
+}
+
+/**
+ * Print the given text at the given place for the given time.
+ *
+ * Print(....., h) -> hold = 1 (not used)
+ * Print(....., s) -> hold = 2 (sustain)
+ */
+void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ int myleftEvent;
+ bool bSample; // Set if a sample is playing
+ Audio::SoundHandle handle;
+ int timeout;
+ int time;
+ CORO_END_CONTEXT(_ctx);
+
+ bool bJapDoPrintText; // Bodge to get-around Japanese bodge
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pText = NULL;
+ _ctx->bSample = false;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // Kick off the voice sample
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ }
+
+ // Calculate display time
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ bJapDoPrintText = false;
+ if (time == 0) {
+ // This is a 'talky' print
+ _ctx->time = TextTime(tBufferAddr());
+
+ // Cut short-able if sustain was not set
+ _ctx->myleftEvent = (hold == 2) ? 0 : GetLeftEvents();
+ } else {
+ _ctx->time = time * ONE_SECOND;
+ _ctx->myleftEvent = 0;
+ if (isJapanMode())
+ bJapDoPrintText = true;
+ }
+
+ // Print the text
+ if (bJapDoPrintText || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) {
+ int Loffset, Toffset; // Screen position
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, x - Loffset, y - Toffset, hTalkFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // string produced NULL text
+ if (IsTopWindow())
+ MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
+
+ /*
+ * New feature: Don't go off the side of the background
+ */
+ int shift;
+ shift = MultiRightmost(_ctx->pText) + 2;
+ if (shift >= BackgroundWidth()) // Not off right
+ MultiMoveRelXY(_ctx->pText, BackgroundWidth() - shift, 0);
+ shift = MultiLeftmost(_ctx->pText) - 1;
+ if (shift <= 0) // Not off left
+ MultiMoveRelXY(_ctx->pText, -shift, 0);
+ shift = MultiLowest(_ctx->pText);
+ if (shift > BackgroundHeight()) // Not off bottom
+ MultiMoveRelXY(_ctx->pText, 0, BackgroundHeight() - shift);
+ }
+
+ // Give up if nothing printed and no sample
+ if (_ctx->pText == NULL && !_ctx->bSample)
+ return;
+
+ // Leave it up until time runs out or whatever
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ // Abort if left click - hardwired feature for talky-print!
+ // Will be ignored if myleftevent happens to be 0!
+ // Abort if sample times out
+ if ((escOn && myescEvent != GetEscEvents())
+ || (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents())
+ || (_ctx->bSample && --_ctx->timeout <= 0))
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Must wait for time
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->time-- <= 0)
+ break;
+ }
+
+ } while (1);
+
+ // Delete the text
+ if (_ctx->pText != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _vm->_mixer->stopHandle(_ctx->handle);
+
+ CORO_END_CODE;
+}
+
+
+static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const PINV_OBJECT pinvo, OBJECT *&pText, const int textx, const int texty, const int item);
+static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText);
+
+/**
+ * Print the given inventory object's name or whatever.
+ */
+void printobj(CORO_PARAM, const SCNHANDLE text, const PINV_OBJECT pinvo, const int event) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ int textx, texty;
+ int item;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(pinvo != 0); // printobj() may only be called from an object code block
+
+ if (text == (SCNHANDLE)-1) { // 'OFF'
+ NotPointedRunning = true;
+ return;
+ }
+ if (text == (SCNHANDLE)-2) { // 'ON'
+ NotPointedRunning = false;
+ return;
+ }
+
+ GetCursorXY(&_ctx->textx, &_ctx->texty, false); // Cursor position..
+ _ctx->item = InvItem(&_ctx->textx, &_ctx->texty, true); // ..to text position
+
+ if (_ctx->item == INV_NOICON)
+ return;
+
+ if (event != POINTED) {
+ NotPointedRunning = true; // Get POINTED text to die
+ CORO_SLEEP(1); // Give it chance to
+ } else
+ NotPointedRunning = false; // There may have been an OFF without an ON
+
+ // Display the text and set it's Z position
+ if (event == POINTED || (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))) {
+ int xshift;
+
+ LoadStringRes(text, tBufferAddr(), TBUFSZ); // The text string
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, _ctx->textx, _ctx->texty, hTagFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // printobj() string produced NULL text
+ MultiSetZPosition(_ctx->pText, Z_INV_ITEXT);
+
+ // Don't go off the side of the screen
+ xshift = MultiLeftmost(_ctx->pText);
+ if (xshift < 0) {
+ MultiMoveRelXY(_ctx->pText, - xshift, 0);
+ _ctx->textx -= xshift;
+ }
+ xshift = MultiRightmost(_ctx->pText);
+ if (xshift > SCREEN_WIDTH) {
+ MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
+ _ctx->textx += SCREEN_WIDTH - xshift;
+ }
+ } else
+ _ctx->pText = NULL;
+
+ if (event == POINTED) {
+ // FIXME: Is there ever an associated sound if in POINTED mode???
+ assert(!_vm->_sound->sampleExists(text));
+ CORO_INVOKE_ARGS(printobjPointed, (CORO_SUBCTX, text, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item));
+ } else {
+ CORO_INVOKE_2(printobjNonPointed, text, _ctx->pText);
+ }
+
+ // Delete the text, if haven't already
+ if (_ctx->pText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+
+ CORO_END_CODE;
+}
+
+static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const PINV_OBJECT pinvo, OBJECT *&pText, const int textx, const int texty, const int item) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Have to give way to non-POINTED-generated text
+ // and go away if the item gets picked up
+ int x, y;
+ do {
+ // Give up if this item gets picked up
+ if (WhichItemHeld() == pinvo->id)
+ break;
+
+ // Give way to non-POINTED-generated text
+ if (NotPointedRunning) {
+ // Delete the text, and wait for the all-clear
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText);
+ pText = NULL;
+ while (NotPointedRunning)
+ CORO_SLEEP(1);
+
+ GetCursorXY(&x, &y, false);
+ if (InvItem(&x, &y, false) != item)
+ break;
+
+ // Re-display in the same place
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, textx, texty, hTagFontHandle(), TXT_CENTRE);
+ assert(pText); // printobj() string produced NULL text
+ MultiSetZPosition(pText, Z_INV_ITEXT);
+ }
+
+ CORO_SLEEP(1);
+
+ // Carry on until the cursor leaves this icon
+ GetCursorXY(&x, &y, false);
+ } while (InvItemId(x, y) == pinvo->id);
+
+ CORO_END_CODE;
+}
+
+static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) {
+ CORO_BEGIN_CONTEXT;
+ bool bSample; // Set if a sample is playing
+ Audio::SoundHandle handle;
+
+ int myleftEvent;
+ bool took_control;
+ int ticks;
+ int timeout;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Kick off the voice sample
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ } else
+ _ctx->bSample = false;
+
+ _ctx->myleftEvent = GetLeftEvents();
+ _ctx->took_control = GetControl(CONTROL_OFF);
+
+ // Display for a time, but abort if conversation gets hidden
+ if (isJapanMode())
+ _ctx->ticks = JAP_TEXT_TIME;
+ else if (pText)
+ _ctx->ticks = TextTime(tBufferAddr());
+ else
+ _ctx->ticks = 0;
+
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ CORO_SLEEP(1);
+ --_ctx->timeout;
+
+ // Abort if left click - hardwired feature for talky-print!
+ // Abort if sample times out
+ // Abort if conversation hidden
+ if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || convHid())
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Must wait for time
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->ticks-- <= 0)
+ break;
+ }
+ } while (1);
+
+ NotPointedRunning = false; // Let POINTED text back in
+
+ if (_ctx->took_control)
+ control(CONTROL_ON); // Free control if we took it
+
+ _vm->_mixer->stopHandle(_ctx->handle);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Register the fact that this poly would like its tag displayed.
+ */
+void printtag(HPOLYGON hp, SCNHANDLE text) {
+ assert(hp != NOPOLY); // printtag() may only be called from a polygon code block
+
+ if (PolyTagState(hp) == TAG_OFF) {
+ SetPolyTagState(hp, TAG_ON);
+ SetPolyTagHandle(hp, text);
+ }
+}
+
+/**
+ * quitgame
+ */
+void quitgame(void) {
+ stopmidi();
+ stopsample();
+ _vm->quitFlag = true;
+}
+
+/**
+ * Return a random number between optional limits.
+ */
+int dw_random(int n1, int n2, int norpt) {
+ int i = 0;
+ uint32 value;
+
+ do {
+ value = n1 + _vm->getRandomNumber(n2 - n1);
+ } while ((lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10));
+
+ lastValue = value;
+ return value;
+}
+
+/**
+ * resetidletime
+ */
+void resetidletime(void) {
+ resetUserEventTime();
+}
+
+/**
+ * restartgame
+ */
+void restartgame(void) {
+ stopmidi();
+ stopsample();
+ bRestart = true;
+}
+
+/**
+ * Restore saved scene.
+ */
+void restore_scene(bool bFade) {
+ UnSuspendHook();
+ PleaseRestoreScene(bFade);
+}
+
+/**
+ * runmode
+ */
+int runmode(void) {
+ return clRunMode;
+}
+
+/**
+ * sampleplaying
+ */
+bool sampleplaying(bool escOn, int myescEvent) {
+ // escape effects introduced 14/12/95 to fix
+ // while (sampleplaying()) pause;
+
+ if (escOn && myescEvent != GetEscEvents())
+ return false;
+
+ return _vm->_sound->sampleIsPlaying();
+}
+
+/**
+ * Save current scene.
+ */
+void save_scene(CORO_PARAM) {
+ PleaseSaveScene(coroParam);
+ SuspendHook();
+}
+
+/**
+ * scalingreels
+ */
+void scalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {
+
+ setscalingreels(actor, scale, direction, left, right, forward, away);
+}
+
+/**
+ * Return the icon that caused the CONVERSE event.
+ */
+
+int scanicon(void) {
+ return convIcon();
+}
+
+/**
+ * Scroll the screen to target co-ordinates.
+ */
+
+void scroll(CORO_PARAM, int x, int y, int iter, bool comp, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int mycount;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Instant completion!
+ offset(x, y);
+ } else {
+ _ctx->mycount = ++scrollCount;
+
+ ScrollTo(x, y, iter);
+
+ if (comp) {
+ int Loffset, Toffset;
+ do {
+ CORO_SLEEP(1);
+
+ // If escapable and ESCAPE is pressed...
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Instant completion!
+ offset(x, y);
+ break;
+ }
+
+ // give up if have been superseded
+ if (_ctx->mycount != scrollCount)
+ CORO_KILL_SELF();
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ } while (Loffset != x || Toffset != y);
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Un-kill an actor.
+ */
+void setactor(int actor) {
+ EnableActor(actor);
+}
+
+/**
+ * Turn a blocking polygon on.
+ */
+
+void setblock(int blockno) {
+ EnableBlock(blockno);
+}
+
+/**
+ * Turn an exit on.
+ */
+
+void setexit(int exitno) {
+ EnableExit(exitno);
+}
+
+/**
+ * Guess what.
+ */
+void setinvlimit(int invno, int n) {
+ InvSetLimit(invno, n);
+}
+
+/**
+ * Guess what.
+ */
+void setinvsize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Guess what.
+ */
+void setlanguage(LANGUAGE lang) {
+ assert(lang == TXT_ENGLISH || lang == TXT_FRENCH
+ || lang == TXT_GERMAN || lang == TXT_ITALIAN
+ || lang == TXT_SPANISH); // ensure language is valid
+
+ ChangeLanguage(lang);
+}
+
+/**
+ * Set palette
+ */
+void setpalette(SCNHANDLE hPal, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ ChangePalette(hPal);
+}
+
+/**
+ * Turn a tag on.
+ */
+void settag(int tagno) {
+ EnableTag(tagno);
+}
+
+/**
+ * Initialise a timer.
+ */
+void settimer(int timerno, int start, bool up, bool frame) {
+ DwSetTimer(timerno, start, up != 0, frame != 0);
+}
+
+#ifdef DEBUG
+/**
+ * Enable display of diagnostic co-ordinates.
+ */
+void showpos(void) {
+ setshowpos();
+}
+
+/**
+ * Enable display of diagnostic co-ordinates.
+ */
+void showstring(void) {
+ setshowstring();
+}
+#endif
+
+/**
+ * Special play - slow down associated actor's movement while the play
+ * is running. After the play, position the actor where the play left
+ * it and continue walking, if the actor still is.
+ */
+
+void splay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myescEvent, false);
+}
+
+/**
+ * (Re)Position an actor.
+ * If moving actor is not around yet in this scene, start it up.
+ */
+
+void stand(int actor, int x, int y, SCNHANDLE film) {
+ PMACTOR pActor; // Moving actor structure
+
+ pActor = GetMover(actor);
+ if (pActor) {
+ if (pActor->MActorState == NO_MACTOR) {
+ // create a moving actor process
+ MActorProcessCreate(x, y, (actor == LEAD_ACTOR) ? LeadId() : actor, pActor);
+
+ if (film == TF_NONE) {
+ SetMActorStanding(pActor);
+ } else {
+ switch (film) {
+ case TF_NONE:
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ AlterMActor(pActor, film, AR_NORMAL);
+ break;
+ }
+ }
+ } else {
+ switch (film) {
+ case TF_NONE:
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ AlterMActor(pActor, film, AR_NORMAL);
+ break;
+ }
+ }
+ } else if (actor == NULL_ACTOR) {
+ //
+ } else {
+ assert(film != 0); // Trying to play NULL film
+
+ // Kick off the play and return.
+ playFilm(film, x, y, actor, false, 0, false, 0, false);
+ }
+}
+
+/**
+ * Position the actor at the polygon's tag node.
+ */
+void standtag(int actor, HPOLYGON hp) {
+ SCNHANDLE film;
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // standtag() may only be called from a polygon code block
+
+ // Lead actor uses tag node film
+ film = getPolyFilm(hp);
+ getPolyNode(hp, &pnodex, &pnodey);
+ if (film && (actor == LEAD_ACTOR || actor == LeadId()))
+ stand(actor, pnodex, pnodey, film);
+ else
+ stand(actor, pnodex, pnodey, 0);
+}
+
+/**
+ * Kill a moving actor's walk.
+ */
+void stop(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor); // Trying to stop a null actor
+
+ GetToken(pActor->actorToken); // Kill the walk process
+ pActor->stop = true; // Cause the actor to stop
+ FreeToken(pActor->actorToken);
+}
+
+void stopmidi(void) {
+ StopMidi(); // Stop any currently playing midi
+}
+
+void stopsample(void) {
+ _vm->_sound->stopAllSamples(); // Stop any currently playing sample
+}
+
+void subtitles(int onoff) {
+ assert (onoff == ST_ON || onoff == ST_OFF);
+
+ if (isJapanMode())
+ return; // Subtitles are always off in JAPAN version (?)
+
+ if (onoff == ST_ON)
+ bSubtitles = true;
+ else
+ bSubtitles = false;
+}
+
+/**
+ * Special walk.
+ * Walk into or out of a legal path.
+ */
+void swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ bool took_control; // Set if this function takes control
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // For lead actor, lock out the user (if not already locked out)
+ if (actor == LeadId() || actor == LEAD_ACTOR)
+ _ctx->took_control = GetControl(CONTROL_OFFV2);
+ else
+ _ctx->took_control = false;
+
+ HPOLYGON hPath;
+
+ hPath = InPolygon(x1, y1, PATH);
+ if (hPath != NOPOLY) {
+ // Walking out of a path
+ stand(actor, x1, y1, 0);
+ } else {
+ hPath = InPolygon(x2, y2, PATH);
+ // One of them has to be in a path
+ assert(hPath != NOPOLY); //one co-ordinate must be in a legal path
+
+ // Walking into a path
+ stand(actor, x2, y2, 0); // Get path's characteristics
+ stand(actor, x1, y1, 0);
+ }
+
+ CORO_INVOKE_ARGS(walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, escOn, myescEvent));
+
+ // Free control if we took it
+ if (_ctx->took_control)
+ control(CONTROL_ON);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Define a tagged actor.
+ */
+
+void tagactor(int actor, SCNHANDLE text, int tp) {
+ Tag_Actor(actor, text, tp);
+}
+
+/**
+ * Text goes over actor's head while actor plays the talk reel.
+ */
+
+void FinishTalkingReel(PMACTOR pActor, int actor) {
+ if (pActor) {
+ SetMActorStanding(pActor);
+ AlterMActor(pActor, 0, AR_POPREEL);
+ } else {
+ setActorTalking(actor, false);
+ playFilm(getActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false);
+ }
+}
+
+void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int Loffset, Toffset; // Top left of display
+ int actor; // The speaking actor
+ PMACTOR pActor; // For moving actors
+ int myleftEvent;
+ int ticks;
+ bool bTookControl; // Set if this function takes control
+ bool bTookTags; // Set if this function disables tags
+ OBJECT *pText; // text object pointer
+ bool bSample; // Set if a sample is playing
+ bool bTalkReel; // Set while talk reel is playing
+ Audio::SoundHandle handle;
+ int timeout;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->Loffset = 0;
+ _ctx->Toffset = 0;
+ _ctx->ticks = 0;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ _ctx->myleftEvent = GetLeftEvents();
+
+ // If this actor is dead, call a stop to the calling process
+ if (actorid && !actorAlive(actorid))
+ CORO_KILL_SELF();
+
+ /*
+ * Find out which actor is talking
+ * and with which direction if no film supplied
+ */
+ TFTYPE direction;
+ switch (film) {
+ case TF_NONE:
+ case TF_UP:
+ case TF_DOWN:
+ case TF_LEFT:
+ case TF_RIGHT:
+ _ctx->actor = LeadId(); // If no film, actor is lead actor
+ direction = (TFTYPE)film;
+ break;
+
+ default:
+ _ctx->actor = extractActor(film);
+ assert(_ctx->actor); // talk() - no actor ID in the reel
+ direction = TF_BOGUS;
+ break;
+ }
+
+ /*
+ * Lock out the user (for lead actor, if not already locked out)
+ * May need to disable tags for other actors
+ */
+ if (_ctx->actor == LeadId())
+ _ctx->bTookControl = GetControl(CONTROL_OFF);
+ else
+ _ctx->bTookControl = false;
+ _ctx->bTookTags = DisableTagsIfEnabled();
+
+ /*
+ * Kick off the voice sample
+ */
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ } else
+ _ctx->bSample = false;
+
+ /*
+ * Replace actor with the talk reel, saving the current one
+ */
+ _ctx->pActor = GetMover(_ctx->actor);
+ if (_ctx->pActor) {
+ if (direction != TF_BOGUS)
+ film = GetMactorTalkReel(_ctx->pActor, direction);
+ AlterMActor(_ctx->pActor, film, AR_PUSHREEL);
+ } else {
+ setActorTalking(_ctx->actor, true);
+ setActorTalkFilm(_ctx->actor, film);
+ playFilm(film, -1, -1, 0, false, 0, escOn, myescEvent, false);
+ }
+ _ctx->bTalkReel = true;
+ CORO_SLEEP(1); // Allow the play to come in
+
+ /*
+ * Display the text.
+ */
+ _ctx->pText = NULL;
+ if (isJapanMode()) {
+ _ctx->ticks = JAP_TEXT_TIME;
+ } else if (bSubtitles || !_ctx->bSample) {
+ int aniX, aniY; // actor position
+ int xshift, yshift;
+ /*
+ * Work out where to display the text
+ */
+ PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset);
+ GetActorMidTop(_ctx->actor, &aniX, &aniY);
+ aniY -= _ctx->Toffset;
+
+ setTextPal(getActorTcol(_ctx->actor));
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, aniX - _ctx->Loffset, aniY, hTalkFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // talk() string produced NULL text;
+ if (IsTopWindow())
+ MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
+
+ /*
+ * Set bottom of text just above the speaker's head
+ * But don't go off the top of the screen
+ */
+ yshift = aniY - MultiLowest(_ctx->pText) - 2; // Just above head
+ MultiMoveRelXY(_ctx->pText, 0, yshift); //
+ yshift = MultiHighest(_ctx->pText);
+ if (yshift < 4)
+ MultiMoveRelXY(_ctx->pText, 0, 4 - yshift); // Not off top
+
+ /*
+ * Don't go off the side of the screen
+ */
+ xshift = MultiRightmost(_ctx->pText) + 2;
+ if (xshift >= SCREEN_WIDTH) // Not off right
+ MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
+ xshift = MultiLeftmost(_ctx->pText) - 1;
+ if (xshift <= 0) // Not off left
+ MultiMoveRelXY(_ctx->pText, -xshift, 0);
+ /*
+ * Work out how long to talk.
+ * During this time, reposition the text if the screen scrolls.
+ */
+ _ctx->ticks = TextTime(tBufferAddr());
+ }
+
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ // Keep text in place if scrolling
+ if (_ctx->pText != NULL) {
+ int nLoff, nToff;
+
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) {
+ MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff);
+ _ctx->Loffset = nLoff;
+ _ctx->Toffset = nToff;
+ }
+ }
+
+ CORO_SLEEP(1);
+ --_ctx->timeout;
+
+ // Abort if escapable and ESCAPE is pressed
+ // Abort if left click - hardwired feature for talk!
+ // Abort if sample times out
+ if ((escOn && myescEvent != GetEscEvents())
+ || (_ctx->myleftEvent != GetLeftEvents())
+ || (_ctx->timeout <= 0))
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Talk reel stops at end of speech
+ FinishTalkingReel(_ctx->pActor, _ctx->actor);
+ _ctx->bTalkReel = false;
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->ticks-- <= 0)
+ break;
+ }
+ } while (1);
+
+ /*
+ * The talk is over now - dump the text
+ * Stop the sample
+ * Restore the actor's film or standing reel
+ */
+ if (_ctx->pText != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _vm->_mixer->stopHandle(_ctx->handle);
+ if (_ctx->bTalkReel)
+ FinishTalkingReel(_ctx->pActor, _ctx->actor);
+
+ /*
+ * Restore user control and tags, as appropriate
+ * And, finally, release the talk token.
+ */
+ if (_ctx->bTookControl)
+ control(CONTROL_ON);
+ if (_ctx->bTookTags)
+ EnableTags();
+
+ CORO_END_CODE;
+}
+
+/**
+ * talkat(actor, x, y, text)
+ */
+void talkat(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myescEvent) {
+ if (!coroParam) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))
+ setTextPal(getActorTcol(actor));
+ }
+
+ print(coroParam, x, y, text, 0, 0, escOn, myescEvent);
+}
+
+/**
+ * talkats(actor, x, y, text, sustain)
+ */
+void talkats(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myescEvent) {
+ if (!coroParam) {
+ assert(sustain == 2);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!isJapanMode())
+ setTextPal(getActorTcol(actor));
+ }
+
+ print(coroParam, x, y, text, 0, sustain, escOn, myescEvent);
+}
+
+/**
+ * Set talk font's palette entry.
+ */
+void talkattr(int r1, int g1, int b1, bool escOn, int myescEvent) {
+ if (isJapanMode())
+ return;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure
+ if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits
+ if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // }
+
+ setTextPal(RGB(r1, g1, b1));
+}
+
+/**
+ * Get a timer's current count.
+ */
+int timer(int timerno) {
+ return Timer(timerno);
+}
+
+/**
+ * topplay(film, x, y, actor, hold, complete)
+ */
+void topplay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) {
+ play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true);
+}
+
+/**
+ * Open or close the 'top window'
+ */
+
+void topwindow(int bpos) {
+ assert(bpos == TW_START || bpos == TW_END);
+
+ switch (bpos) {
+ case TW_END:
+ KillInventory();
+ break;
+
+ case TW_START:
+ KillInventory();
+ PopUpConf(TOPWIN);
+ break;
+ }
+}
+
+/**
+ * unhookscene
+ */
+
+void unhookscene(void) {
+ UnHookScene();
+}
+
+/**
+ * Un-define an actor as tagged.
+ */
+
+void untagactor(int actor) {
+ UnTagActor(actor);
+}
+
+/**
+ * vibrate
+ */
+
+void vibrate(void) {
+}
+
+/**
+ * waitframe(int actor, int frameNumber)
+ */
+
+void waitframe(CORO_PARAM, int actor, int frameNumber, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ while (getActorSteps(actor) < frameNumber) {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Return when a key pressed or button pushed.
+ */
+
+void waitkey(CORO_PARAM, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int startEvent;
+ int startX, startY;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ while (1) {
+ _ctx->startEvent = getUserEvents();
+ // Store cursor position
+ while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false))
+ CORO_SLEEP(1);
+
+ while (_ctx->startEvent == getUserEvents()) {
+ CORO_SLEEP(1);
+
+ // Not necessary to monitor escape as it's an event anyway
+
+ int curX, curY;
+ GetCursorXY(&curX, &curY, false); // Store cursor position
+ if (curX != _ctx->startX || curY != _ctx->startY)
+ break;
+
+ if (IsConfWindow())
+ break;
+ }
+
+ if (!IsConfWindow())
+ return;
+
+ do {
+ CORO_SLEEP(1);
+ } while (IsConfWindow());
+
+ CORO_SLEEP(ONE_SECOND / 2); // Let it die down
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Pause for requested time.
+ */
+
+void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int time;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!frame)
+ time *= ONE_SECOND;
+
+ _ctx->time = time;
+ do {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+ } while (_ctx->time--);
+ CORO_END_CODE;
+}
+
+/**
+ * Set a moving actor off on a walk.
+ */
+void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ return;
+ }
+
+ assert(pActor->hCpath != NOPOLY); // moving actor not in path
+
+ GetToken(pActor->actorToken);
+ SetActorDest(pActor, x, y, igPath, film);
+ DontScrollCursor();
+
+ if (hold == 2) {
+ ;
+ } else {
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+ }
+ }
+ FreeToken(pActor->actorToken);
+ CORO_END_CODE;
+}
+
+/**
+ * Set a moving actor off on a walk.
+ * Wait to see if its aborted or completed.
+ */
+void walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myescEvent, bool &retVal) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ int ticket;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ retVal = true;
+ return;
+ }
+
+ CORO_SLEEP(ONE_SECOND);
+
+ assert(pActor->hCpath != NOPOLY); // moving actor not in path
+
+ // Briefly aquire token to kill off any other normal walk
+ GetToken(pActor->actorToken);
+ FreeToken(pActor->actorToken);
+
+ SetActorDest(pActor, x, y, false, film);
+ DontScrollCursor();
+
+ _ctx->ticket = GetActorTicket(pActor);
+
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ if (_ctx->ticket != GetActorTicket(pActor)) {
+ retVal = false;
+ return;
+ }
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ retVal = true;
+ return;
+ }
+ }
+
+ int endx, endy;
+ GetMActorPosition(pActor, &endx, &endy);
+ retVal = (_ctx->ticket == GetActorTicket(pActor) && endx == x && endy == y);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Declare a moving actor.
+ */
+void walkingactor(uint32 id, SCNHANDLE *rp) {
+ PMACTOR pActor; // Moving actor structure
+
+ SetMover(id); // Establish as a moving actor
+ pActor = GetMover(id);
+ assert(pActor);
+
+ // Store all those reels
+ int i, j;
+ for (i = 0; i < 5; ++i) {
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[i][j] = *rp++;
+ }
+
+
+ for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
+ for (j = 0; j < 4; ++j) {
+ pActor->WalkReels[i][j] = pActor->WalkReels[4][j];
+ pActor->StandReels[i][j] = pActor->StandReels[2][j];
+ }
+ }
+}
+
+/**
+ * Walk a moving actor towards the polygon's tag, but return when the
+ * actor enters the polygon.
+ */
+
+void walkpoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int aniX, aniY; // cursor/actor position
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ standtag(actor, hp);
+ return;
+ }
+
+ GetToken(pActor->actorToken);
+ getPolyNode(hp, &pnodex, &pnodey);
+ SetActorDest(pActor, pnodex, pnodey, false, film);
+ DoScrollCursor();
+
+ do {
+ CORO_SLEEP(1);
+
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Straight there if escaped
+ standtag(actor, hp);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+
+ GetMActorPosition(pActor, &aniX, &aniY);
+ } while (!MActorIsInPolygon(pActor, hp) && MAmoving(pActor));
+
+ FreeToken(pActor->actorToken);
+
+ CORO_END_CODE;
+}
+
+/**
+ * walktag(actor, reel, hold)
+ */
+
+void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ standtag(actor, hp);
+ return;
+ }
+
+ GetToken(pActor->actorToken);
+ getPolyNode(hp, &pnodex, &pnodey);
+ SetActorDest(pActor, pnodex, pnodey, false, film);
+ DoScrollCursor();
+
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Straight there if escaped
+ standtag(actor, hp);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+ }
+
+ // Adopt the tag-related reel
+ SCNHANDLE pfilm = getPolyFilm(hp);
+
+ switch (pfilm) {
+ case TF_NONE:
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ if (actor == LEAD_ACTOR || actor == LeadId())
+ AlterMActor(pActor, pfilm, AR_NORMAL);
+ else
+ SetMActorStanding(pActor);
+ break;
+ }
+
+ FreeToken(pActor->actorToken);
+ CORO_END_CODE;
+}
+
+/**
+ * whichinventory
+ */
+
+int whichinventory(void) {
+ return WhichInventoryOpen();
+}
+
+
+/**
+ * Subtract one less that the number of parameters from pp
+ * pp then points to the first parameter.
+ *
+ * If the library function has no return value:
+ * return -(the number of parameters) to pop them from the stack
+ *
+ * If the library function has a return value:
+ * return -(the number of parameters - 1) to pop most of them from
+ * the stack, and stick the return value in pp[0]
+ * @param operand Library function
+ * @param pp Top of parameter stack
+ */
+int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const PINT_CONTEXT pic, RESUME_STATE *pResumeState) {
+ debug(7, "CallLibraryRoutine op %d (escOn %d, myescEvent %d)", operand, pic->escOn, pic->myescEvent);
+ switch (operand) {
+ case ACTORATTR:
+ pp -= 3; // 4 parameters
+ actorattr(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case ACTORDIRECTION:
+ pp[0] = actordirection(pp[0]);
+ return 0;
+
+ case ACTORREF:
+ error("actorref isn't a real function!");
+
+ case ACTORSCALE:
+ pp[0] = actorscale(pp[0]);
+ return 0;
+
+ case ACTORSON:
+ actorson();
+ return 0;
+
+ case ACTORXPOS:
+ pp[0] = actorpos(ACTORXPOS, pp[0]);
+ return 0;
+
+ case ACTORYPOS:
+ pp[0] = actorpos(ACTORYPOS, pp[0]);
+ return 0;
+
+ case ADDICON:
+ addicon(pp[0]);
+ return -1;
+
+ case ADDINV1:
+ addinv(INV_1, pp[0]);
+ return -1;
+
+ case ADDINV2:
+ addinv(INV_2, pp[0]);
+ return -1;
+
+ case ADDOPENINV:
+ addinv(INV_OPEN, pp[0]);
+ return -1;
+
+ case AUXSCALE:
+ pp -= 13; // 14 parameters
+ auxscale(pp[0], pp[1], (SCNHANDLE *)(pp+2));
+ return -14;
+
+ case BACKGROUND:
+ background(pp[0]);
+ return -1;
+
+ case CAMERA:
+ camera(pp[0]);
+ return -1;
+
+ case CDLOAD:
+ pp -= 1; // 2 parameters
+ cdload(pp[0], pp[1]);
+ return -2;
+
+ case CDPLAY:
+ error("cdplay isn't a real function!");
+
+ case CLEARHOOKSCENE:
+ clearhookscene();
+ return 0;
+
+ case CLOSEINVENTORY:
+ closeinventory();
+ return 0;
+
+ case CONTROL:
+ control(pp[0]);
+ return -1;
+
+ case CONVERSATION:
+ conversation(pp[0], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -1;
+
+ case CONVICON:
+ convicon(pp[0]);
+ return -1;
+
+ case CURSORXPOS:
+ pp[0] = cursorpos(CURSORXPOS);
+ return 0;
+
+ case CURSORYPOS:
+ pp[0] = cursorpos(CURSORYPOS);
+ return 0;
+
+ case CUTSCENE:
+ error("cutscene isn't a real function!");
+
+ case DEC_CONVW:
+ pp -= 7; // 8 parameters
+ dec_convw(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_CSTRINGS:
+ pp -= 19; // 20 parameters
+ dec_cstrings((SCNHANDLE *)pp);
+ return -20;
+
+ case DEC_CURSOR:
+ dec_cursor(pp[0]);
+ return -1;
+
+ case DEC_FLAGS:
+ dec_flags(pp[0]);
+ return -1;
+
+ case DEC_INV1:
+ pp -= 7; // 8 parameters
+ dec_inv1(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_INV2:
+ pp -= 7; // 8 parameters
+ dec_inv2(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_INVW:
+ dec_invw(pp[0]);
+ return -1;
+
+ case DEC_LEAD:
+ pp -= 61; // 62 parameters
+ dec_lead(pp[0], (SCNHANDLE *)&pp[1], pp[61]);
+ return -62;
+
+ case DEC_TAGFONT:
+ dec_tagfont(pp[0]);
+ return -1;
+
+ case DEC_TALKFONT:
+ dec_talkfont(pp[0]);
+ return -1;
+
+ case DELICON:
+ delicon(pp[0]);
+ return -1;
+
+ case DELINV:
+ delinv(pp[0]);
+ return -1;
+
+ case EFFECTACTOR:
+ assert(pic->event == ENTER || pic->event == LEAVE); // effectactor() must be from effect poly code
+
+ pp[0] = pic->actorid;
+ return 0;
+
+ case ENABLEF1:
+ enablef1();
+ return 0;
+
+ case EVENT:
+ pp[0] = pic->event;
+ return 0;
+
+ case FADEMIDI:
+ fademidi(coroParam, pp[0]);
+ return -1;
+
+ case FRAMEGRAB:
+ return -1;
+
+ case GETINVLIMIT:
+ pp[0] = getinvlimit(pp[0]);
+ return 0;
+
+ case HASRESTARTED:
+ pp[0] = hasrestarted();
+ return 0;
+
+ case HELDOBJECT:
+ pp[0] = heldobject();
+ return 0;
+
+ case HIDE:
+ hide(pp[0]);
+ return -1;
+
+ case HOOKSCENE:
+ pp -= 2; // 3 parameters
+ hookscene(pp[0], pp[1], pp[2]);
+ return -3;
+
+ case IDLETIME:
+ pp[0] = idletime();
+ return 0;
+
+ case ININVENTORY:
+ pp[0] = ininventory(pp[0]);
+ return 0; // using return value
+
+ case INVDEPICT:
+ pp -= 1; // 2 parameters
+ invdepict(pp[0], pp[1]);
+ return -2;
+
+ case INVENTORY:
+ inventory(pp[0], pic->escOn, pic->myescEvent);
+ return -1;
+
+ case INWHICHINV:
+ pp[0] = inwhichinv(pp[0]);
+ return 0; // using return value
+
+ case KILLACTOR:
+ killactor(pp[0]);
+ return -1;
+
+ case KILLBLOCK:
+ killblock(pp[0]);
+ return -1;
+
+ case KILLEXIT:
+ killexit(pp[0]);
+ return -1;
+
+ case KILLTAG:
+ killtag(pp[0]);
+ return -1;
+
+ case LEFTOFFSET:
+ pp[0] = ltoffset(LEFTOFFSET);
+ return 0;
+
+ case MOVECURSOR:
+ pp -= 1; // 2 parameters
+ movecursor(pp[0], pp[1]);
+ return -2;
+
+ case NEWSCENE:
+ pp -= 2; // 3 parameters
+ if (*pResumeState == RES_2)
+ *pResumeState = RES_NOT;
+ else
+ newscene(coroParam, pp[0], pp[1], pp[2]);
+ return -3;
+
+ case NOBLOCKING:
+ noblocking();
+ return 0;
+
+ case NOSCROLL:
+ pp -= 3; // 4 parameters
+ noscroll(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case OBJECTHELD:
+ objectheld(pp[0]);
+ return -1;
+
+ case OFFSET:
+ pp -= 1; // 2 parameters
+ offset(pp[0], pp[1]);
+ return -2;
+
+ case PLAY:
+ pp -= 5; // 6 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myescEvent, false);
+ else
+ play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent, false);
+ return -6;
+
+ case PLAYMIDI:
+ pp -= 2; // 3 parameters
+ playmidi(coroParam, pp[0], pp[1], pp[2]);
+ return -3;
+
+ case PLAYRTF:
+ error("playrtf only applies to cdi!");
+
+ case PLAYSAMPLE:
+ pp -= 1; // 2 parameters
+ playsample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case PREPARESCENE:
+ preparescene(pp[0]);
+ return -1;
+
+ case PRINT:
+ pp -= 5; // 6 parameters
+ /* pp[2] was intended to be attribute */
+ print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent);
+ return -6;
+
+ case PRINTOBJ:
+ printobj(coroParam, pp[0], pic->pinvo, pic->event);
+ return -1;
+
+ case PRINTTAG:
+ printtag(pic->hpoly, pp[0]);
+ return -1;
+
+ case QUITGAME:
+ quitgame();
+ return 0;
+
+ case RANDOM:
+ pp -= 2; // 3 parameters
+ pp[0] = dw_random(pp[0], pp[1], pp[2]);
+ return -2; // One holds return value
+
+ case RESETIDLETIME:
+ resetidletime();
+ return 0;
+
+ case RESTARTGAME:
+ restartgame();
+ return 0;
+
+ case RESTORE_CUT:
+ restore_scene(false);
+ return 0;
+
+ case RESTORE_SCENE:
+ restore_scene(true);
+ return 0;
+
+ case RUNMODE:
+ pp[0] = runmode();
+ return 0;
+
+ case SAMPLEPLAYING:
+ pp[0] = sampleplaying(pic->escOn, pic->myescEvent);
+ return 0;
+
+ case SAVE_SCENE:
+ if (*pResumeState == RES_1)
+ *pResumeState = RES_2;
+ else
+ save_scene(coroParam);
+ return 0;
+
+ case SCALINGREELS:
+ pp -= 6; // 7 parameters
+ scalingreels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
+ return -7;
+
+ case SCANICON:
+ pp[0] = scanicon();
+ return 0;
+
+ case SCROLL:
+ pp -= 3; // 4 parameters
+ scroll(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent);
+ return -4;
+
+ case SETACTOR:
+ setactor(pp[0]);
+ return -1;
+
+ case SETBLOCK:
+ setblock(pp[0]);
+ return -1;
+
+ case SETEXIT:
+ setexit(pp[0]);
+ return -1;
+
+ case SETINVLIMIT:
+ pp -= 1; // 2 parameters
+ setinvlimit(pp[0], pp[1]);
+ return -2;
+
+ case SETINVSIZE:
+ pp -= 6; // 7 parameters
+ setinvsize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
+ return -7;
+
+ case SETLANGUAGE:
+ setlanguage((LANGUAGE)pp[0]);
+ return -1;
+
+ case SETPALETTE:
+ setpalette(pp[0], pic->escOn, pic->myescEvent);
+ return -1;
+
+ case SETTAG:
+ settag(pp[0]);
+ return -1;
+
+ case SETTIMER:
+ pp -= 3; // 4 parameters
+ settimer(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case SHOWPOS:
+#ifdef DEBUG
+ showpos();
+#endif
+ return 0;
+
+ case SHOWSTRING:
+#ifdef DEBUG
+ showstring();
+#endif
+ return 0;
+
+ case SPLAY:
+ pp -= 6; // 7 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myescEvent);
+ else
+ splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->actorid, pic->escOn, pic->myescEvent);
+ return -7;
+
+ case STAND:
+ pp -= 3; // 4 parameters
+ stand(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case STANDTAG:
+ standtag(pp[0], pic->hpoly);
+ return -1;
+
+ case STOP:
+ stop(pp[0]);
+ return -1;
+
+ case STOPMIDI:
+ stopmidi();
+ return 0;
+
+ case STOPSAMPLE:
+ stopsample();
+ return 0;
+
+ case SUBTITLES:
+ subtitles(pp[0]);
+ return -1;
+
+ case SWALK:
+ pp -= 5; // 6 parameters
+ swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent);
+ return -6;
+
+ case TAGACTOR:
+ pp -= 2; // 3 parameters
+ tagactor(pp[0], pp[1], pp[2]);
+ return -3;
+
+ case TALK:
+ pp -= 1; // 2 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ talk(coroParam, pp[0], pp[1], 0, pic->escOn, pic->myescEvent);
+ else
+ talk(coroParam, pp[0], pp[1], pic->actorid, pic->escOn, pic->myescEvent);
+ return -2;
+
+ case TALKAT:
+ pp -= 3; // 4 parameters
+ talkat(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent);
+ return -4;
+
+ case TALKATS:
+ pp -= 4; // 5 parameters
+ talkats(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myescEvent);
+ return -5;
+
+ case TALKATTR:
+ pp -= 2; // 3 parameters
+ talkattr(pp[0], pp[1], pp[2], pic->escOn, pic->myescEvent);
+ return -3;
+
+ case TIMER:
+ pp[0] = timer(pp[0]);
+ return 0;
+
+ case TOPOFFSET:
+ pp[0] = ltoffset(TOPOFFSET);
+ return 0;
+
+ case TOPPLAY:
+ pp -= 5; // 6 parameters
+ topplay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent);
+ return -6;
+
+ case TOPWINDOW:
+ topwindow(pp[0]);
+ return -1;
+
+ case TRYPLAYSAMPLE:
+ pp -= 1; // 2 parameters
+ tryplaysample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case UNHOOKSCENE:
+ unhookscene();
+ return 0;
+
+ case UNTAGACTOR:
+ untagactor(pp[0]);
+ return -1;
+
+ case VIBRATE:
+ vibrate();
+ return 0;
+
+ case WAITKEY:
+ waitkey(coroParam, pic->escOn, pic->myescEvent);
+ return 0;
+
+ case WAITFRAME:
+ pp -= 1; // 2 parameters
+ waitframe(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case WAITTIME:
+ pp -= 1; // 2 parameters
+ waittime(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case WALK:
+ pp -= 4; // 5 parameters
+ walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, pic->escOn, pic->myescEvent);
+ return -5;
+
+ case WALKED: {
+ pp -= 3; // 4 parameters
+ bool tmp;
+ walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent, tmp);
+ if (!coroParam) {
+ // Only write the result to the stack if walked actually completed running.
+ pp[0] = tmp;
+ }
+ }
+ return -3;
+
+ case WALKINGACTOR:
+ pp -= 40; // 41 parameters
+ walkingactor(pp[0], (SCNHANDLE *)&pp[1]);
+ return -41;
+
+ case WALKPOLY:
+ pp -= 2; // 3 parameters
+ walkpoly(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -3;
+
+ case WALKTAG:
+ pp -= 2; // 3 parameters
+ walktag(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -3;
+
+ case WHICHINVENTORY:
+ pp[0] = whichinventory();
+ return 0;
+
+ default:
+ error("Unsupported library function");
+ }
+
+ error("Can't possibly get here");
+}
+
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/tinlib.h b/engines/tinsel/tinlib.h
new file mode 100644
index 0000000000..001de70896
--- /dev/null
+++ b/engines/tinsel/tinlib.h
@@ -0,0 +1,41 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utility defines
+ */
+
+#ifndef TINSEL_TINLIB_H // prevent multiple includes
+#define TINSEL_TINLIB_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// Library functions in TINLIB.C
+
+void control(int param);
+void stand(int actor, int x, int y, SCNHANDLE film);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TINLIB_H
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
new file mode 100644
index 0000000000..ec16e73294
--- /dev/null
+++ b/engines/tinsel/tinsel.cpp
@@ -0,0 +1,1005 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/endian.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "common/file.h"
+#include "common/savefile.h"
+#include "common/config-manager.h"
+#include "common/stream.h"
+
+#include "graphics/cursorman.h"
+
+#include "base/plugins.h"
+#include "base/version.h"
+
+#include "sound/mididrv.h"
+#include "sound/mixer.h"
+#include "sound/audiocd.h"
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/faders.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/heapmem.h" // MemoryInit
+#include "tinsel/inventory.h"
+#include "tinsel/music.h"
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/savescn.h"
+#include "tinsel/scn.h"
+#include "tinsel/serializer.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// In BG.CPP
+extern void SetDoFadeIn(bool tf);
+extern void DropBackground(void);
+
+// In CURSOR.CPP
+extern void CursorProcess(CORO_PARAM);
+
+// In INVENTORY.CPP
+extern void InventoryProcess(CORO_PARAM);
+
+// In SCENE.CPP
+extern void PrimeBackground( void );
+extern void NewScene(SCNHANDLE scene, int entry);
+extern SCNHANDLE GetSceneHandle(void);
+
+// In TIMER.CPP
+extern void FettleTimers(void);
+extern void RebootTimers(void);
+
+//----------------- FORWARD DECLARATIONS ---------------------
+void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+bool bRestart = false;
+bool bHasRestarted = false;
+
+#ifdef DEBUG
+bool bFast; // set to make it go ludicrously fast
+#endif
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+struct Scene {
+ SCNHANDLE scene; // Memory handle for scene
+ int entry; // Entrance number
+ int trans; // Transition - not yet used
+};
+
+static Scene NextScene = { 0, 0, 0 };
+static Scene HookScene = { 0, 0, 0 };
+static Scene DelayedScene = { 0, 0, 0 };
+
+static bool bHookSuspend = false;
+
+static uint32 lastLeftClick = 0, lastRightClick = 0;
+
+static PROCESS *pMouseProcess = 0;
+static PROCESS *pKeyboardProcess = 0;
+
+// Stack of pending mouse button events
+Common::List<Common::EventType> mouseButtons;
+
+// Stack of pending keypresses
+Common::List<Common::Event> keypresses;
+
+//----------------- LOCAL PROCEDURES --------------------
+
+/**
+ * Process to handle keypresses
+ */
+void KeyboardProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ while (true) {
+ if (keypresses.empty()) {
+ // allow scheduling
+ CORO_SLEEP(1);
+ continue;
+ }
+
+ // Get the next keyboard event off the stack
+ Common::Event evt = *keypresses.begin();
+ keypresses.erase(keypresses.begin());
+
+ // Switch for special keys
+ switch (evt.kbd.keycode) {
+ // Drag action
+ case Common::KEYCODE_LALT:
+ case Common::KEYCODE_RALT:
+ if (evt.type == Common::EVENT_KEYDOWN) {
+ if (!bSwapButtons)
+ ProcessButEvent(BE_RDSTART);
+ else
+ ProcessButEvent(BE_LDSTART);
+ } else {
+ if (!bSwapButtons)
+ ProcessButEvent(BE_LDEND);
+ else
+ ProcessButEvent(BE_RDEND);
+ }
+ continue;
+
+ case Common::KEYCODE_LCTRL:
+ case Common::KEYCODE_RCTRL:
+ if (evt.type == Common::EVENT_KEYDOWN) {
+ ProcessKeyEvent(LOOK_KEY);
+ } else {
+ // Control key release
+ }
+ continue;
+
+ default:
+ break;
+ }
+
+ // At this point only key down events need processing
+ if (evt.type == Common::EVENT_KEYUP)
+ continue;
+
+ if (_vm->_keyHandler != NULL)
+ // Keyboard is hooked, so pass it on to that handler first
+ if (!_vm->_keyHandler(evt.kbd))
+ continue;
+
+ switch (evt.kbd.keycode) {
+ /*** SPACE = WALKTO ***/
+ case Common::KEYCODE_SPACE:
+ ProcessKeyEvent(WALKTO_KEY);
+ continue;
+
+ /*** RETURN = ACTION ***/
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ ProcessKeyEvent(ACTION_KEY);
+ continue;
+
+ /*** l = LOOK ***/
+ case Common::KEYCODE_l: // LOOK
+ ProcessKeyEvent(LOOK_KEY);
+ continue;
+
+ case Common::KEYCODE_ESCAPE:
+ // WORKAROUND: Check if any of the starting logo screens are active, and if so
+ // manually skip to the title screen, allowing them to be bypassed
+ {
+ int sceneOffset = (_vm->getFeatures() & GF_SCNFILES) ? 1 : 0;
+ int sceneNumber = (GetSceneHandle() >> SCNHANDLE_SHIFT) - sceneOffset;
+ if ((language == TXT_GERMAN) &&
+ ((sceneNumber >= 25 && sceneNumber <= 27) || (sceneNumber == 17))) {
+ // Skip to title screen
+ // It seems the German CD version uses scenes 25,26,27,17 for the intro,
+ // instead of 13,14,15,11; also, the title screen is 11 instead of 10
+ SetNewScene((11 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT);
+ } else if ((sceneNumber >= 13) && (sceneNumber <= 15) || (sceneNumber == 11)) {
+ // Skip to title screen
+ SetNewScene((10 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT);
+ } else {
+ // Not on an intro screen, so process the key normally
+ ProcessKeyEvent(ESC_KEY);
+ }
+ }
+ continue;
+
+#ifdef SLOW_RINCE_DOWN
+ case '>':
+ AddInterlude(1);
+ continue;
+ case '<':
+ AddInterlude(-1);
+ continue;
+#endif
+
+ case Common::KEYCODE_F1:
+ // Options dialog
+ ProcessKeyEvent(OPTION_KEY);
+ continue;
+ case Common::KEYCODE_F5:
+ // Save game
+ ProcessKeyEvent(SAVE_KEY);
+ continue;
+ case Common::KEYCODE_F7:
+ // Load game
+ ProcessKeyEvent(LOAD_KEY);
+ continue;
+ case Common::KEYCODE_q:
+ if ((evt.kbd.flags == Common::KBD_CTRL) || (evt.kbd.flags == Common::KBD_ALT))
+ ProcessKeyEvent(QUIT_KEY);
+ continue;
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_KP9:
+ ProcessKeyEvent(PGUP_KEY);
+ continue;
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_KP3:
+ ProcessKeyEvent(PGDN_KEY);
+ continue;
+ case Common::KEYCODE_HOME:
+ case Common::KEYCODE_KP7:
+ ProcessKeyEvent(HOME_KEY);
+ continue;
+ case Common::KEYCODE_END:
+ case Common::KEYCODE_KP1:
+ ProcessKeyEvent(END_KEY);
+ continue;
+ default:
+ ProcessKeyEvent(NOEVENT_KEY);
+ break;
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Process to handle changes in the mouse buttons.
+ */
+void MouseProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ bool lastLWasDouble;
+ bool lastRWasDouble;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->lastLWasDouble = false;
+ _ctx->lastRWasDouble = false;
+
+ while (true) {
+ // FIXME: I'm still keeping the ctrl/Alt handling in the KeyProcess method.
+ // Need to make sure that this works correctly
+ //DragKeys();
+
+ if (mouseButtons.empty()) {
+ // allow scheduling
+ CORO_SLEEP(1);
+ continue;
+ }
+
+ // get next mouse button event
+ Common::EventType type = *mouseButtons.begin();
+ mouseButtons.erase(mouseButtons.begin());
+
+ switch (type) {
+ case Common::EVENT_LBUTTONDOWN:
+ // left button press
+ if (DwGetCurrentTime() - lastLeftClick < (uint32)dclickSpeed) {
+ // signal left drag start
+ ProcessButEvent(BE_LDSTART);
+
+ // signal left double click event
+ ProcessButEvent(BE_DLEFT);
+
+ _ctx->lastLWasDouble = true;
+ } else {
+ // signal left drag start
+ ProcessButEvent(BE_LDSTART);
+
+ // signal left single click event
+ ProcessButEvent(BE_SLEFT);
+
+ _ctx->lastLWasDouble = false;
+ }
+ break;
+
+ case Common::EVENT_LBUTTONUP:
+ // left button release
+
+ // update click timer
+ if (_ctx->lastLWasDouble == false)
+ lastLeftClick = DwGetCurrentTime();
+ else
+ lastLeftClick -= dclickSpeed;
+
+ // signal left drag end
+ ProcessButEvent(BE_LDEND);
+ break;
+
+ case Common::EVENT_RBUTTONDOWN:
+ // right button press
+
+ if (DwGetCurrentTime() - lastRightClick < (uint32)dclickSpeed) {
+ // signal right drag start
+ ProcessButEvent(BE_RDSTART);
+
+ // signal right double click event
+ ProcessButEvent(BE_DRIGHT);
+
+ _ctx->lastRWasDouble = true;
+ } else {
+ // signal right drag start
+ ProcessButEvent(BE_RDSTART);
+
+ // signal right single click event
+ ProcessButEvent(BE_SRIGHT);
+
+ _ctx->lastRWasDouble = false;
+ }
+ break;
+
+ case Common::EVENT_RBUTTONUP:
+ // right button release
+
+ // update click timer
+ if (_ctx->lastRWasDouble == false)
+ lastRightClick = DwGetCurrentTime();
+ else
+ lastRightClick -= dclickSpeed;
+
+ // signal right drag end
+ ProcessButEvent(BE_RDEND);
+ break;
+
+ default:
+ break;
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Installs the event driver processes
+ */
+
+void EventsInstall(void) {
+ lastLeftClick = lastRightClick = DwGetCurrentTime();
+
+ pMouseProcess = CoroutineInstall(PID_MOUSE, MouseProcess, NULL, 0);
+ pKeyboardProcess = CoroutineInstall(PID_KEYBOARD, KeyboardProcess, NULL, 0);
+}
+
+/**
+ * Removes the event driver processes
+ */
+
+void EventsUninstall(void) {
+ ProcessKill(pMouseProcess);
+ ProcessKill(pKeyboardProcess);
+}
+
+/**
+ * Run the master script.
+ * Continues between scenes, or until Interpret() returns.
+ */
+static void MasterScriptProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PINT_CONTEXT pic;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ _ctx->pic = InitInterpretContext(GS_MASTER, 0, NOEVENT, NOPOLY, 0, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+ CORO_END_CODE;
+}
+
+/**
+ * Store the facts pertaining to a scene change.
+ */
+
+void SetNewScene(SCNHANDLE scene, int entrance, int transition) {
+ if (HookScene.scene == 0 || bHookSuspend) {
+ // This scene comes next
+ NextScene.scene = scene;
+ NextScene.entry = entrance;
+ NextScene.trans = transition;
+ } else {
+ // This scene gets delayed
+ DelayedScene.scene = scene;
+ DelayedScene.entry = entrance;
+ DelayedScene.trans = transition;
+
+ // The hooked scene comes next
+ NextScene.scene = HookScene.scene;
+ NextScene.entry = HookScene.entry;
+ NextScene.trans = HookScene.trans;
+
+ HookScene.scene = 0;
+ }
+}
+
+void SetHookScene(SCNHANDLE scene, int entrance, int transition) {
+ assert(HookScene.scene == 0); // scene already hooked
+
+ HookScene.scene = scene;
+ HookScene.entry = entrance;
+ HookScene.trans = transition;
+}
+
+void UnHookScene(void) {
+ assert(DelayedScene.scene != 0); // no scene delayed
+
+ // The delayed scene can go now
+ NextScene.scene = DelayedScene.scene;
+ NextScene.entry = DelayedScene.entry;
+ NextScene.trans = DelayedScene.trans;
+
+ DelayedScene.scene = 0;
+}
+
+void SuspendHook(void) {
+ bHookSuspend = true;
+}
+
+void UnSuspendHook(void) {
+ bHookSuspend = false;
+}
+
+void syncSCdata(Serializer &s) {
+ s.syncAsUint32LE(HookScene.scene);
+ s.syncAsSint32LE(HookScene.entry);
+ s.syncAsSint32LE(HookScene.trans);
+
+ s.syncAsUint32LE(DelayedScene.scene);
+ s.syncAsSint32LE(DelayedScene.entry);
+ s.syncAsSint32LE(DelayedScene.trans);
+}
+
+
+//-----------------------------------------------------------------------
+
+static void RestoredProcess(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PINT_CONTEXT pic;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // get the stuff copied to process when it was created
+ _ctx->pic = *((PINT_CONTEXT *)ProcessGetParamsSelf());
+
+ _ctx->pic = RestoreInterpretContext(_ctx->pic);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ CORO_END_CODE;
+}
+
+void RestoreProcess(PINT_CONTEXT pic) {
+ CoroutineInstall(PID_TCODE, RestoredProcess, &pic, sizeof(pic));
+}
+
+void RestoreMasterProcess(PINT_CONTEXT pic) {
+ CoroutineInstall(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic));
+}
+
+// FIXME: CountOut is used by ChangeScene
+static int CountOut = 1; // == 1 for immediate start of first scene
+
+/**
+ * If a scene restore is going on, just return (we don't update the
+ * screen during this time).
+ * If a scene change is required, 'order' the data for the new scene and
+ * start a fade out and a countdown.
+ * When the count expires, the screen will have faded. Ensure the scene |
+ * is loaded, clear the screen, and start the new scene.
+ */
+void ChangeScene() {
+
+ if (IsRestoringScene())
+ return;
+
+ if (NextScene.scene != 0) {
+ if (!CountOut) {
+ switch (NextScene.trans) {
+ case TRANS_CUT:
+ CountOut = 1;
+ break;
+
+ case TRANS_FADE:
+ default:
+ // Trigger pre-load and fade and start countdown
+ CountOut = COUNTOUT_COUNT;
+ FadeOutFast(NULL);
+ break;
+ }
+ } else if (--CountOut == 0) {
+ ClearScreen(0L);
+
+ NewScene(NextScene.scene, NextScene.entry);
+ NextScene.scene = 0;
+
+ switch (NextScene.trans) {
+ case TRANS_CUT:
+ SetDoFadeIn(false);
+ break;
+
+ case TRANS_FADE:
+ default:
+ SetDoFadeIn(true);
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * LoadBasicChunks
+ */
+
+void LoadBasicChunks(void) {
+ byte *cptr;
+ int numObjects;
+
+ // Allocate RAM for savescene data
+ InitialiseSs();
+
+ // CHUNK_TOTAL_ACTORS seems to be missing in the released version, hard coding a value
+ // TODO: Would be nice to just change 511 to MAX_SAVED_ALIVES
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_ACTORS);
+ RegisterActors((cptr != NULL) ? READ_LE_UINT32(cptr) : 511);
+
+ // CHUNK_TOTAL_GLOBALS seems to be missing in some versions.
+ // So if it is missing, set a reasonably high value for the number of globals.
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_GLOBALS);
+ RegisterGlobals((cptr != NULL) ? READ_LE_UINT32(cptr) : 512);
+
+ cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_TOTAL_OBJECTS);
+ numObjects = (cptr != NULL) ? READ_LE_UINT32(cptr) : 0;
+
+ cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS);
+
+#ifdef SCUMM_BIG_ENDIAN
+ //convert to native endianness
+ INV_OBJECT *io = (INV_OBJECT *)cptr;
+ for (int i = 0; i < numObjects; i++, io++) {
+ io->id = FROM_LE_32(io->id);
+ io->hFilm = FROM_LE_32(io->hFilm);
+ io->hScript = FROM_LE_32(io->hScript);
+ io->attribute = FROM_LE_32(io->attribute);
+ }
+#endif
+
+ RegisterIcons(cptr, numObjects);
+
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY);
+ if (cptr != NULL)
+ MaxPolygons(*cptr);
+}
+
+//----------------- TinselEngine --------------------
+
+// Global pointer to engine
+TinselEngine *_vm;
+
+struct GameSettings {
+ const char *gameid;
+ const char *description;
+ byte id;
+ uint32 features;
+ const char *detectname;
+};
+
+static const GameSettings tinselSettings[] = {
+ {"tinsel", "Tinsel game", 0, 0, 0},
+
+ {NULL, NULL, 0, 0, NULL}
+};
+
+TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) :
+ Engine(syst), _gameDescription(gameDesc), _screenSurface(true) {
+ _vm = this;
+
+ // Setup mixer
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+
+ const GameSettings *g;
+
+ const char *gameid = ConfMan.get("gameid").c_str();
+ for (g = tinselSettings; g->gameid; ++g)
+ if (!scumm_stricmp(g->gameid, gameid))
+ _gameId = g->id;
+
+ int cd_num = ConfMan.getInt("cdrom");
+ if (cd_num >= 0)
+ _system->openCD(cd_num);
+
+ 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 MusicPlayer(driver);
+ //_music->setNativeMT32(native_mt32);
+ //_music->setAdlib(adlib);
+
+ _musicVolume = ConfMan.getInt("music_volume");
+
+ _sound = new SoundManager(this);
+
+ _mousePos.x = 0;
+ _mousePos.y = 0;
+ _keyHandler = NULL;
+ _dosPlayerDir = 0;
+ quitFlag = false;
+}
+
+TinselEngine::~TinselEngine() {
+ delete _sound;
+ delete _music;
+ delete _console;
+ FreeSs();
+ FreeTextBuffer();
+ FreeHandleTable();
+ FreeActors();
+ FreeObjectList();
+ FreeGlobals();
+ FreeProcessList();
+}
+
+int TinselEngine::init() {
+ // Initialize backend
+ _system->beginGFXTransaction();
+ initCommonGFX(false);
+ _system->initSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+ _system->endGFXTransaction();
+
+ _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, 1);
+
+ g_system->getEventManager()->registerRandomSource(_random, "tinsel");
+
+ _console = new Console();
+
+ // init memory manager
+ MemoryInit();
+
+ // load user configuration
+ ReadConfig();
+
+#if 1
+ // FIXME: The following is taken from RestartGame().
+ // It may have to be adjusted a bit
+ RebootCursor();
+ RebootDeadTags();
+ RebootMovers();
+ RebootTimers();
+ RebootScalingReels();
+
+ DelayedScene.scene = HookScene.scene = 0;
+#endif
+
+ // Init palette and object managers, scheduler, keyboard and mouse
+ RestartDrivers();
+
+ // TODO: More stuff from dos_main.c may have to be added here
+
+ // Set language - we'll be clever here and use the ScummVM language setting
+ language = TXT_ENGLISH;
+ switch (getLanguage()) {
+ case Common::FR_FRA:
+ language = TXT_FRENCH;
+ break;
+ case Common::DE_DEU:
+ language = TXT_GERMAN;
+ break;
+ case Common::IT_ITA:
+ language = TXT_ITALIAN;
+ break;
+ case Common::ES_ESP:
+ language = TXT_SPANISH;
+ break;
+ default:
+ language = TXT_ENGLISH;
+ }
+ ChangeLanguage(language);
+
+ // load in graphics info
+ SetupHandleTable();
+
+ // Actors, globals and inventory icons
+ LoadBasicChunks();
+
+ return 0;
+}
+
+Common::String TinselEngine::getSavegamePattern() const {
+ return _targetName + ".???";
+}
+
+Common::String TinselEngine::getSavegameFilename(int16 saveNum) const {
+ char filename[256];
+ snprintf(filename, 256, "%s.%03d", getTargetName().c_str(), saveNum);
+ return filename;
+}
+
+#define GAME_FRAME_DELAY (1000 / ONE_SECOND)
+
+int TinselEngine::go() {
+ uint32 timerVal = 0;
+
+ // Continuous game processes
+ CreateConstProcesses();
+
+ // allow game to run in the background
+ //RestartBackgroundProcess(); // FIXME: is this still needed?
+
+ //dumpMusic(); // dumps all of the game's music in external XMIDI files
+
+#if 0
+ // Load game from specified slot, if any
+ // FIXME: Not working correctly right now
+ if (ConfMan.hasKey("save_slot")) {
+ getList();
+ RestoreGame(ConfMan.getInt("save_slot"));
+ }
+#endif
+
+ // Foreground loop
+
+ while (!quitFlag) {
+ assert(_console);
+ if (_console->isAttached())
+ _console->onFrame();
+
+ // Check for time to do next game cycle
+ if ((g_system->getMillis() > timerVal + GAME_FRAME_DELAY)) {
+ timerVal = g_system->getMillis();
+ AudioCD.updateCD();
+ NextGameCycle();
+ }
+
+ if (bRestart) {
+ RestartGame();
+ bRestart = false;
+ bHasRestarted = true; // Set restarted flag
+ }
+
+ // Save/Restore scene file transfers
+ ProcessSRQueue();
+
+#ifdef DEBUG
+ if (bFast)
+ continue; // run flat-out
+#endif
+ // Loop processing events while there are any pending
+ while (pollEvent());
+
+ g_system->delayMillis(10);
+ }
+
+ // Write configuration
+ WriteConfig();
+
+ return 0;
+}
+
+
+void TinselEngine::NextGameCycle(void) {
+ //
+ ChangeScene();
+
+ // Allow a user event for this schedule
+ ResetEcount();
+
+ // schedule process
+ Scheduler();
+
+ // redraw background
+ DrawBackgnd();
+
+ // Why waste resources on yet another process?
+ FettleTimers();
+}
+
+
+bool TinselEngine::pollEvent() {
+ if (!g_system->getEventManager()->pollEvent(_event))
+ return false;
+
+ // Handle the various kind of events
+ switch (_event.type) {
+ case Common::EVENT_QUIT:
+ quitFlag = true;
+ break;
+
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ // Add button to queue for the mouse process
+ mouseButtons.push_back(_event.type);
+ break;
+
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = _event.mouse;
+ break;
+
+ case Common::EVENT_KEYDOWN:
+ case Common::EVENT_KEYUP:
+ KeyProcess();
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/**
+ * Start the processes that continue between scenes.
+ */
+
+void TinselEngine::CreateConstProcesses(void) {
+ // Process to run the master script
+ CoroutineInstall(PID_MASTER_SCR, MasterScriptProcess, NULL, 0);
+
+ // Processes to run the cursor and inventory,
+ CoroutineInstall(PID_CURSOR, CursorProcess, NULL, 0);
+ CoroutineInstall(PID_INVENTORY, InventoryProcess, NULL, 0);
+}
+
+/**
+ * Restart the game
+ */
+
+void TinselEngine::RestartGame(void) {
+ HoldItem(INV_NOICON); // Holding nothing
+
+ DropBackground(); // No background
+
+ // Ditches existing infrastructure background
+ PrimeBackground();
+
+ // Next scene change won't need to fade out
+ // -> reset the count used by ChangeScene
+ CountOut = 1;
+
+ RebootCursor();
+ RebootDeadTags();
+ RebootMovers();
+ RebootTimers();
+ RebootScalingReels();
+
+ DelayedScene.scene = HookScene.scene = 0;
+
+ // remove keyboard, mouse and joystick drivers
+ ChopDrivers();
+
+ // Init palette and object managers, scheduler, keyboard and mouse
+ RestartDrivers();
+
+ // Actors, globals and inventory icons
+ LoadBasicChunks();
+
+ // Continuous game processes
+ CreateConstProcesses();
+}
+
+/**
+ * Init palette and object managers, scheduler, keyboard and mouse.
+ */
+
+void TinselEngine::RestartDrivers(void) {
+ // init the palette manager
+ ResetPalAllocator();
+
+ // init the object manager
+ KillAllObjects();
+
+ // init the process scheduler
+ InitScheduler();
+
+ // init the event handlers
+ EventsInstall();
+
+ // install sound driver
+ SoundInit();
+
+ // Set midi volume
+ SetMidiVolume(volMidi);
+}
+
+/**
+ * Remove keyboard, mouse and joystick drivers.
+ */
+
+void TinselEngine::ChopDrivers(void) {
+ // remove sound driver
+ SoundDeinit();
+
+ // remove event drivers
+ EventsUninstall();
+}
+
+/**
+ * Process a keyboard event
+ */
+
+void TinselEngine::KeyProcess(void) {
+
+ // Handle any special keys immediately
+ switch (_event.kbd.keycode) {
+ case Common::KEYCODE_d:
+ if ((_event.kbd.flags == Common::KBD_CTRL) && (_event.type == Common::EVENT_KEYDOWN)) {
+ // Activate the debugger
+ assert(_console);
+ _console->attach();
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Check for movement keys
+ int idx = 0;
+ switch (_event.kbd.keycode) {
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ idx = MSK_UP;
+ break;
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ idx = MSK_DOWN;
+ break;
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ idx = MSK_LEFT;
+ break;
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ idx = MSK_RIGHT;
+ break;
+ default:
+ break;
+ }
+ if (idx != 0) {
+ if (_event.type == Common::EVENT_KEYDOWN)
+ _dosPlayerDir |= idx;
+ else
+ _dosPlayerDir &= ~idx;
+ return;
+ }
+
+ // All other keypresses add to the queue for processing in KeyboardProcess
+ keypresses.push_back(_event);
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h
new file mode 100644
index 0000000000..99136e0e7b
--- /dev/null
+++ b/engines/tinsel/tinsel.h
@@ -0,0 +1,141 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_H
+#define TINSEL_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "common/util.h"
+
+#include "sound/mididrv.h"
+#include "sound/mixer.h"
+
+#include "engines/engine.h"
+#include "tinsel/debugger.h"
+#include "tinsel/graphics.h"
+#include "tinsel/sound.h"
+
+namespace Tinsel {
+
+class MusicPlayer;
+class SoundManager;
+
+enum TinselGameID {
+ GID_DW1 = 0,
+ GID_DW2 = 1
+};
+
+enum TinselGameFeatures {
+ GF_DEMO = 1 << 0,
+ GF_CD = 1 << 1,
+ GF_FLOPPY = 1 << 2,
+ GF_SCNFILES = 1 << 3
+};
+
+enum TinselEngineVersion {
+ TINSEL_V1 = 1 << 0,
+ TINSEL_V2 = 1 << 1
+};
+
+struct TinselGameDescription;
+
+enum TinselKeyDirection {
+ MSK_LEFT = 1, MSK_RIGHT = 2, MSK_UP = 4, MSK_DOWN = 8,
+ MSK_DIRECTION = MSK_LEFT | MSK_RIGHT | MSK_UP | MSK_DOWN
+};
+
+typedef bool (*KEYFPTR)(const Common::KeyState &);
+
+class TinselEngine : public ::Engine {
+ int _gameId;
+ Common::KeyState _keyPressed;
+ Common::RandomSource _random;
+ Surface _screenSurface;
+ Common::Event _event;
+ Common::Point _mousePos;
+ uint8 _dosPlayerDir;
+ Console *_console;
+protected:
+
+ int init();
+ int go();
+
+public:
+ TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc);
+ virtual ~TinselEngine();
+ int getGameId() {
+ return _gameId;
+ }
+
+ const TinselGameDescription *_gameDescription;
+ uint32 getGameID() const;
+ uint32 getFeatures() const;
+ Common::Language getLanguage() const;
+ uint16 getVersion() const;
+ Common::Platform getPlatform() const;
+ bool quitFlag;
+
+ SoundManager *_sound;
+ MusicPlayer *_music;
+
+ KEYFPTR _keyHandler;
+private:
+ //MusicPlayer *_music;
+ int _musicVolume;
+
+ void NextGameCycle(void);
+ void CreateConstProcesses(void);
+ void RestartGame(void);
+ void RestartDrivers(void);
+ void ChopDrivers(void);
+ void KeyProcess(void);
+public:
+ const Common::String getTargetName() const { return _targetName; }
+ Common::String getSavegamePattern() const;
+ Common::String getSavegameFilename(int16 saveNum) const;
+ Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; }
+ Surface &screen() { return _screenSurface; }
+
+ bool pollEvent();
+ Common::Event event() { return _event; }
+ Common::Point getMousePosition() const { return _mousePos; }
+ void setMousePosition(const Common::Point &pt) {
+ g_system->warpMouse(pt.x, pt.y);
+ _mousePos = pt;
+ }
+ void divertKeyInput(KEYFPTR fptr) { _keyHandler = fptr; }
+ int getRandomNumber(int maxNumber) { return _random.getRandomNumber(maxNumber); }
+ uint8 getKeyDirection() const { return _dosPlayerDir; }
+};
+
+// Global reference to the TinselEngine object
+extern TinselEngine *_vm;
+
+} // End of namespace Tinsel
+
+#endif /* TINSEL_H */
diff --git a/engines/tinsel/token.cpp b/engines/tinsel/token.cpp
new file mode 100644
index 0000000000..e50290c3f0
--- /dev/null
+++ b/engines/tinsel/token.cpp
@@ -0,0 +1,129 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * To ensure exclusive use of resources and exclusive control responsibilities.
+ */
+
+#include "common/util.h"
+
+#include "tinsel/sched.h"
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+struct Token {
+ PROCESS *proc;
+};
+
+static Token tokens[NUMTOKENS];
+
+
+/**
+ * Release all tokens held by this process, and kill the process.
+ */
+static void TerminateProcess(PROCESS *tProc) {
+
+ // Release tokens held by the process
+ for (int i = 0; i < NUMTOKENS; i++) {
+ if (tokens[i].proc == tProc) {
+ tokens[i].proc = NULL;
+ }
+ }
+
+ // Kill the process
+ ProcessKill(tProc);
+}
+
+/**
+ * Gain control of the CONTROL token if it is free.
+ */
+void GetControlToken() {
+ const int which = TOKEN_CONTROL;
+
+ if (tokens[which].proc == NULL) {
+ tokens[which].proc = CurrentProcess();
+ }
+}
+
+/**
+ * Release control of the CONTROL token.
+ */
+void FreeControlToken() {
+ // Allow anyone to free TOKEN_CONTROL
+ tokens[TOKEN_CONTROL].proc = NULL;
+}
+
+
+/**
+ * Gain control of a token. If the requested token is out of range, or
+ * is already held by the calling process, then the calling process
+ * will be killed off.
+ *
+ * Otherwise, the calling process will gain the token. If the token was
+ * held by another process, then the previous holder is killed off.
+ */
+void GetToken(int which) {
+ assert(TOKEN_LEAD <= which && which < NUMTOKENS);
+
+ if (tokens[which].proc != NULL) {
+ assert(tokens[which].proc != CurrentProcess());
+ TerminateProcess(tokens[which].proc);
+ }
+
+ tokens[which].proc = CurrentProcess();
+}
+
+/**
+ * Release control of a token. If the requested token is not owned by
+ * the calling process, then the calling process will be killed off.
+ */
+void FreeToken(int which) {
+ assert(TOKEN_LEAD <= which && which < NUMTOKENS);
+
+ assert(tokens[which].proc == CurrentProcess()); // we'd have been killed if some other proc had taken this token
+
+ tokens[which].proc = NULL;
+}
+
+/**
+ * If it's a valid token and it's free, returns true.
+ */
+bool TestToken(int which) {
+ if (which < 0 || which >= NUMTOKENS)
+ return false;
+
+ return (tokens[which].proc == NULL);
+}
+
+/**
+ * Call at the start of each scene.
+ */
+void FreeAllTokens(void) {
+ for (int i = 0; i < NUMTOKENS; i++) {
+ tokens[i].proc = NULL;
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/token.h b/engines/tinsel/token.h
new file mode 100644
index 0000000000..4ab4775bfb
--- /dev/null
+++ b/engines/tinsel/token.h
@@ -0,0 +1,57 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_TOKEN_H
+#define TINSEL_TOKEN_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// Fixed tokens
+
+enum {
+ TOKEN_CONTROL = 0,
+ TOKEN_LEAD, // = TOKEN_CONTROL + 1
+ TOKEN_LEFT_BUT = TOKEN_LEAD + MAX_MOVERS,
+
+ NUMTOKENS // = TOKEN_LEFT_BUT + 1
+};
+
+// Token functions
+
+void GetControlToken();
+void FreeControlToken();
+
+void GetToken(int which);
+void FreeToken(int which);
+
+void FreeAllTokens(void);
+bool TestToken(int which);
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TOKEN_H