From af945ac7881ae7e414f004bd0e99e8c3b5d76be9 Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Mon, 1 Dec 2008 20:35:36 +0000 Subject: Merged the tinsel 2 engine with tinsel 1. Both Discworld 1 and Discworld 2 should be completable svn-id: r35196 --- engines/tinsel/actors.cpp | 1135 ++++++-- engines/tinsel/actors.h | 114 +- engines/tinsel/anim.cpp | 104 +- engines/tinsel/anim.h | 28 + engines/tinsel/background.cpp | 29 + engines/tinsel/background.h | 20 +- engines/tinsel/bg.cpp | 170 +- engines/tinsel/bmv.cpp | 1272 +++++++++ engines/tinsel/config.cpp | 19 +- engines/tinsel/config.h | 6 +- engines/tinsel/coroutine.h | 28 +- engines/tinsel/cursor.cpp | 128 +- engines/tinsel/cursor.h | 3 + engines/tinsel/debugger.cpp | 2 +- engines/tinsel/detection.cpp | 147 +- engines/tinsel/dialogs.cpp | 5736 +++++++++++++++++++++++++++++++++++++++++ engines/tinsel/dialogs.h | 168 ++ engines/tinsel/drives.cpp | 129 + engines/tinsel/drives.h | 63 + engines/tinsel/dw.h | 23 +- engines/tinsel/effect.cpp | 31 +- engines/tinsel/events.cpp | 576 +++-- engines/tinsel/events.h | 96 +- engines/tinsel/faders.cpp | 87 +- engines/tinsel/faders.h | 16 +- engines/tinsel/font.cpp | 50 +- engines/tinsel/font.h | 24 +- engines/tinsel/graphics.cpp | 449 ++-- engines/tinsel/graphics.h | 11 +- engines/tinsel/handle.cpp | 213 +- engines/tinsel/handle.h | 17 + engines/tinsel/heapmem.cpp | 15 +- engines/tinsel/inventory.cpp | 4526 -------------------------------- engines/tinsel/inventory.h | 142 - engines/tinsel/mareels.cpp | 81 +- engines/tinsel/mareels.h | 56 + engines/tinsel/module.mk | 5 +- engines/tinsel/move.cpp | 894 ++++--- engines/tinsel/move.h | 15 +- engines/tinsel/multiobj.cpp | 46 +- engines/tinsel/multiobj.h | 9 + engines/tinsel/music.cpp | 481 +++- engines/tinsel/music.h | 99 +- engines/tinsel/object.cpp | 32 +- engines/tinsel/object.h | 10 +- engines/tinsel/palette.cpp | 203 +- engines/tinsel/palette.h | 40 +- engines/tinsel/pcode.cpp | 245 +- engines/tinsel/pcode.h | 38 +- engines/tinsel/pdisplay.cpp | 356 ++- engines/tinsel/pdisplay.h | 37 + engines/tinsel/pid.h | 10 +- engines/tinsel/play.cpp | 853 +++++- engines/tinsel/play.h | 64 + engines/tinsel/polygons.cpp | 1212 ++++++--- engines/tinsel/polygons.h | 68 +- engines/tinsel/rince.cpp | 781 ++++-- engines/tinsel/rince.h | 186 +- engines/tinsel/saveload.cpp | 145 +- engines/tinsel/savescn.cpp | 311 ++- engines/tinsel/savescn.h | 25 +- engines/tinsel/scene.cpp | 261 +- engines/tinsel/scene.h | 26 +- engines/tinsel/sched.cpp | 513 +++- engines/tinsel/sched.h | 48 +- engines/tinsel/scn.cpp | 19 +- engines/tinsel/scn.h | 50 +- engines/tinsel/scroll.cpp | 201 +- engines/tinsel/scroll.h | 25 +- engines/tinsel/sound.cpp | 290 ++- engines/tinsel/sound.h | 52 +- engines/tinsel/strres.cpp | 290 ++- engines/tinsel/strres.h | 33 + engines/tinsel/sysvar.cpp | 218 ++ engines/tinsel/sysvar.h | 149 ++ engines/tinsel/text.cpp | 9 +- engines/tinsel/text.h | 28 +- engines/tinsel/timers.cpp | 2 +- engines/tinsel/timers.h | 2 +- engines/tinsel/tinlib.cpp | 4607 ++++++++++++++++++++++++++------- engines/tinsel/tinlib.h | 31 +- engines/tinsel/tinsel.cpp | 490 +++- engines/tinsel/tinsel.h | 70 +- 83 files changed, 20942 insertions(+), 8351 deletions(-) create mode 100644 engines/tinsel/bmv.cpp create mode 100644 engines/tinsel/dialogs.cpp create mode 100644 engines/tinsel/dialogs.h create mode 100644 engines/tinsel/drives.cpp create mode 100644 engines/tinsel/drives.h delete mode 100644 engines/tinsel/inventory.cpp delete mode 100644 engines/tinsel/inventory.h create mode 100644 engines/tinsel/mareels.h create mode 100644 engines/tinsel/pdisplay.h create mode 100644 engines/tinsel/play.h create mode 100644 engines/tinsel/sysvar.cpp create mode 100644 engines/tinsel/sysvar.h (limited to 'engines/tinsel') diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp index c2f01added..c151bdac9b 100644 --- a/engines/tinsel/actors.cpp +++ b/engines/tinsel/actors.cpp @@ -25,19 +25,23 @@ */ #include "tinsel/actors.h" +#include "tinsel/background.h" #include "tinsel/events.h" #include "tinsel/film.h" // for FREEL #include "tinsel/handle.h" -#include "tinsel/inventory.h" // INV_NOICON +#include "tinsel/dialogs.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/play.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/sched.h" #include "tinsel/serializer.h" +#include "tinsel/sysvar.h" +#include "tinsel/tinsel.h" #include "tinsel/token.h" #include "common/util.h" @@ -51,25 +55,37 @@ namespace Tinsel { #include "common/pack-start.h" // START STRUCT PACKING /** actor struct - one per actor */ -struct ACTOR_STRUC { +struct T1_ACTOR_STRUC { int32 masking; //!< type of actor masking SCNHANDLE hActorId; //!< handle actor ID string index SCNHANDLE hActorCode; //!< handle to actor script } PACKED_STRUCT; +struct T2_ACTOR_STRUC { + SCNHANDLE hActorId; // handle actor ID string index + SCNHANDLE hTagText; // tag + int32 tagPortionV; // defines tag area + int32 tagPortionH; // defines tag area + SCNHANDLE hActorCode; // handle to actor script +} PACKED_STRUCT; + #include "common/pack-end.h" // END STRUCT PACKING +//----------------- LOCAL MACROS ---------------------------- +#define RANGE_CHECK(num) assert(num > 0 && num <= NumActors); //----------------- LOCAL GLOBAL DATA -------------------- +#define MAX_REELS 6 + 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 bAlive; // TRUE == alive + bool bHidden; // TRUE == hidden bool completed; // TRUE == script played out int x, y, z; @@ -81,27 +97,46 @@ struct ACTORINFO { int presRnum; // the present reel number SCNHANDLE presFilm; // the film that reel belongs to OBJECT *presObj; // reference for position information - int presX, presY; + int presPlayX, presPlayY; bool tagged; // actor tagged? SCNHANDLE hTag; // handle to tag text int tType; // e.g. TAG_Q1TO3 - bool escOn; - int escEv; + bool bEscOn; + int escEvent; - COLORREF tColour; // Text colour + COLORREF textColour; // 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; + bool bTalking; int steps; + int loopCount; + + // DW2 new fields and alternates + int presColumns[MAX_REELS]; // the present columns + OBJECT *presObjs[MAX_REELS]; // reference for position information + int filmNum; +}; +struct TAGACTOR { + // Copies of compiled data + int id; + SCNHANDLE hTagText; // handle to tag text + int32 tagPortionV; // which portion is active + int32 tagPortionH; // which portion is active + SCNHANDLE hActorCode; // The actor's script + + int tagFlags; + SCNHANDLE hOverrideTag; // Override tag. }; +typedef TAGACTOR *PTAGACTOR; + -static ACTORINFO *actorInfo = 0; +static ACTORINFO *actorInfo = NULL; static COLORREF defaultColour = 0; // Text colour @@ -109,6 +144,18 @@ static bool bActorsOn = false; static int ti = 0; +#define MAX_TAGACTORS 10 + +static TAGACTOR taggedActors[MAX_TAGACTORS]; + +static int numTaggedActors = 0; + +static uint8 *zFactors = NULL; + +static Z_POSITIONS zPositions[NUM_ZPOSITIONS]; + +//-------------------- METHOD LIST ----------------------- + /** * Called once at start-up time, and again at restart time. * Registers the total number of actors in the game. @@ -122,10 +169,13 @@ void RegisterActors(int num) { // Check we can save so many assert(NumActors <= MAX_SAVED_ALIVES); - // Allocate RAM for actorInfo + // Allocate RAM for actor structures + // 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)); + if (TinselV2) + zFactors = (uint8 *)malloc(MAX_SAVED_ALIVES); // make sure memory allocated if (actorInfo == NULL) { @@ -136,11 +186,13 @@ void RegisterActors(int num) { assert(num == NumActors); memset(actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO)); + if (TinselV2) + memset(zFactors, 0, MAX_SAVED_ALIVES); } // All actors start off alive. while (num--) - actorInfo[num].alive = true; + actorInfo[num].bAlive = true; } void FreeActors() { @@ -154,7 +206,7 @@ void FreeActors() { * Called from dec_lead(), i.e. normally once at start of master script. * @param leadID Lead Id */ -void setleadid(int leadID) { +void SetLeadId(int leadID) { LeadActorId = leadID; actorInfo[leadID-1].mtype = ACT_MASK; } @@ -162,16 +214,36 @@ void setleadid(int leadID) { /** * No comment. */ -int LeadId(void) { +int GetLeadId(void) { return LeadActorId; } +bool ActorIsGhost(int actor) { + return actor == SysVar(ISV_GHOST_ACTOR); +} + struct ATP_INIT { int id; // Actor number - USER_EVENT event; // Event - BUTEVENT bev; // Causal mouse event + TINSEL_EVENT event; // Event + PLR_EVENT bev; // Causal mouse event + + PINT_CONTEXT pic; }; +/** + * Convert actor id to index into TaggedActors[] + */ +static int TaggedActorIndex(int actor) { + int i; + + for (i = 0; i < numTaggedActors; i++) { + if (taggedActors[i].id == actor) + return i; + } + + error("You may say to yourself \"this is not my tagged actor\""); +} + /** * Runs actor's glitter code. */ @@ -179,23 +251,46 @@ static void ActorTinselProcess(CORO_PARAM, const void *param) { // COROUTINE CORO_BEGIN_CONTEXT; INT_CONTEXT *pic; + bool bTookControl; CORO_END_CONTEXT(_ctx); // get the stuff copied to process when it was created - ATP_INIT *atp = (ATP_INIT *)param; + const ATP_INIT *atp = (const ATP_INIT *)param; CORO_BEGIN_CODE(_ctx); - CORO_INVOKE_1(AllowDclick, atp->bev); // May kill us if single click + if (TinselV2) { + // Take control for CONVERSE events + if (atp->event == CONVERSE) { + _ctx->bTookControl = GetControl(); + HideConversation(true); + } else + _ctx->bTookControl = false; - // Run the Glitter code - assert(actorInfo[atp->id - 1].actorCode); // no code to run + // Run the Glitter code + CORO_INVOKE_1(Interpret, atp->pic); - _ctx->pic = InitInterpretContext(GS_ACTOR, actorInfo[atp->id - 1].actorCode, atp->event, NOPOLY, atp->id, NULL); - CORO_INVOKE_1(Interpret, _ctx->pic); + // Restore conv window if applicable + if (atp->event == CONVERSE) { + // Free control if we took it + if (_ctx->bTookControl) + ControlOn(); - // If it gets here, actor's code has run to completion - actorInfo[atp->id - 1].completed = true; + HideConversation(false); + } + } else { + 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; } @@ -215,7 +310,7 @@ static void ActorRestoredProcess(CORO_PARAM, const void *param) { CORO_END_CONTEXT(_ctx); // get the stuff copied to process when it was created - RATP_INIT *r = (RATP_INIT *)param; + const RATP_INIT *r = (const RATP_INIT *)param; CORO_BEGIN_CODE(_ctx); @@ -240,7 +335,7 @@ void RestoreActorProcess(int id, INT_CONTEXT *pic) { * @param event Event structure * @param be ButEvent */ -void actorEvent(int ano, USER_EVENT event, BUTEVENT be) { +void ActorEvent(int ano, TINSEL_EVENT event, PLR_EVENT be) { ATP_INIT atp; // Only if there is Glitter code associated with this actor. @@ -248,20 +343,59 @@ void actorEvent(int ano, USER_EVENT event, BUTEVENT be) { atp.id = ano; atp.event = event; atp.bev = be; + atp.pic = NULL; g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp)); } } +/** + * Starts up process to run actor's glitter code. + */ +void ActorEvent(CORO_PARAM, int ano, TINSEL_EVENT tEvent, bool bWait, int myEscape, bool *result) { + ATP_INIT atp; + int index; + CORO_BEGIN_CONTEXT; + PPROCESS pProc; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + index = TaggedActorIndex(ano); + assert(taggedActors[index].hActorCode); + if (result) *result = false; + + atp.id = 0; + atp.event = tEvent; + atp.pic = InitInterpretContext(GS_ACTOR, + taggedActors[index].hActorCode, + tEvent, + NOPOLY, // No polygon + ano, // Actor + NULL, // No object + myEscape); + + if (atp.pic != NULL) { + _ctx->pProc = g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp)); + AttachInterpret(atp.pic, _ctx->pProc); + + if (bWait) + CORO_INVOKE_2(WaitInterpret,_ctx->pProc, result); + } + + CORO_END_CODE; +} + + /** * 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) { +void StartActor(const T1_ACTOR_STRUC *as, bool bRunScript) { SCNHANDLE hActorId = FROM_LE_32(as->hActorId); // Zero-out many things - actorInfo[hActorId - 1].hidden = false; + actorInfo[hActorId - 1].bHidden = false; actorInfo[hActorId - 1].completed = false; actorInfo[hActorId - 1].x = 0; actorInfo[hActorId - 1].y = 0; @@ -276,10 +410,10 @@ void StartActor(const ACTOR_STRUC *as, bool bRunScript) { // Run actor's script for this scene if (bRunScript) { if (bActorsOn) - actorInfo[hActorId - 1].alive = true; + actorInfo[hActorId - 1].bAlive = true; - if (actorInfo[hActorId - 1].alive && FROM_LE_32(as->hActorCode)) - actorEvent(hActorId, STARTUP, BE_NONE); + if (actorInfo[hActorId - 1].bAlive && FROM_LE_32(as->hActorCode)) + ActorEvent(hActorId, STARTUP, PLR_NOEVENT); } } @@ -289,18 +423,47 @@ void StartActor(const ACTOR_STRUC *as, bool bRunScript) { * @param numActors Number of actors * @param bRunScript Flag for whether to run actor scene scripts */ -void StartActors(SCNHANDLE ah, int numActors, bool bRunScript) { +void StartTaggedActors(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; + if (TinselV2) { + // Clear it all out for a fresh start + memset(taggedActors, 0, sizeof(taggedActors)); + numTaggedActors = numActors; + } else { + // 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); + if (!TinselV2) { + // Tinsel 1 load variation + const T1_ACTOR_STRUC *as = (const T1_ACTOR_STRUC *)LockMem(ah); + for (i = 0; i < numActors; i++, as++) { + StartActor(as, bRunScript); + } + } else if (numActors > 0) { + // Tinsel 2 load variation + const T2_ACTOR_STRUC *as = (T2_ACTOR_STRUC *)LockMem(ah); + for (i = 0; i < numActors; i++, as++) { + assert(as->hActorCode); + + // Store current scene's parameters for this tagged actor + taggedActors[i].id = FROM_LE_32(as->hActorId); + taggedActors[i].hTagText = FROM_LE_32(as->hTagText); + taggedActors[i].tagPortionV = FROM_LE_32(as->tagPortionV); + taggedActors[i].tagPortionH = FROM_LE_32(as->tagPortionH); + taggedActors[i].hActorCode = FROM_LE_32(as->hActorCode); + + // Run actor's script for this scene + if (bRunScript) { + // Send in reverse order - they get swapped round in the scheduler + ActorEvent(nullContext, taggedActors[i].id, SHOWEVENT, false, 0); + ActorEvent(nullContext, taggedActors[i].id, STARTUP, false, 0); + } + } } } @@ -308,18 +471,34 @@ void StartActors(SCNHANDLE ah, int numActors, bool 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; + for (int i = 0; i < NumActors; i++) { + if (TinselV2) { + // Save text colour + COLORREF tColour = actorInfo[i].textColour; + + memset(&actorInfo[i], 0, sizeof(ACTORINFO)); + + // Restor text colour + actorInfo[i].textColour = tColour; + + // Clear extra arrays + memset(zFactors, 0, NumActors); + memset(zPositions, 0, sizeof(zPositions)); + } else { + // In Tinsel v1, only certain fields get reset + 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].bTalking = false; + } } } @@ -328,17 +507,17 @@ void DropActors(void) { * @param ano Actor Id */ void DisableActor(int ano) { - PMACTOR pActor; + PMOVER pActor; assert(ano > 0 && ano <= NumActors); // illegal actor number - actorInfo[ano - 1].alive = false; // Record as dead + actorInfo[ano - 1].bAlive = 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); + KillMover(pActor); } /** @@ -349,14 +528,14 @@ 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; + if (!actorInfo[ano - 1].bAlive || actorInfo[ano - 1].completed) { + actorInfo[ano - 1].bAlive = true; + actorInfo[ano - 1].bHidden = false; actorInfo[ano - 1].completed = false; // Re-run actor's script for this scene if (actorInfo[ano-1].actorCode) - actorEvent(ano, STARTUP, BE_NONE); + ActorEvent(ano, STARTUP, PLR_NOEVENT); } } @@ -367,7 +546,7 @@ void EnableActor(int ano) { bool actorAlive(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - return actorInfo[ano - 1].alive; + return actorInfo[ano - 1].bAlive; } /** @@ -444,16 +623,16 @@ void FirstTaggedActor(void) { * or there are no more tagged actors to look at. */ int NextTaggedActor(void) { - PMACTOR pActor; + PMOVER pActor; bool hid; do { if (actorInfo[ti].tagged) { pActor = GetMover(ti+1); if (pActor) - hid = getMActorHideState(pActor); + hid = MoverHidden(pActor); else - hid = actorInfo[ti].hidden; + hid = actorInfo[ti].bHidden; if (!hid) { return ++ti; @@ -464,6 +643,41 @@ int NextTaggedActor(void) { return 0; } +/** + * Called from TagProcess, NextTaggedActor() is + * called repeatedly until the caller gets fed up or + * there are no more tagged actors to look at. + */ +int NextTaggedActor(int previous) { + PMOVER pMover; + + // Convert actor number to index + if (!previous) + previous = -1; + else + previous = TaggedActorIndex(previous); + + while (++previous < numTaggedActors) { + pMover = GetMover(taggedActors[previous].id); + + // No tag on lead actor while he's moving + if ((taggedActors[previous].id) == GetLeadId() && MoverMoving(pMover)) { + taggedActors[previous].tagFlags &= ~(POINTING | TAGWANTED); + continue; + } + + // Not if the actor doesn't exist at the moment + if (pMover && !MoverIs(pMover)) + continue; + + if (!(pMover ? MoverHidden(pMover) : ActorHidden(taggedActors[previous].id))) { + return taggedActors[previous].id; + } + } + + return 0; +} + /** * Returns the masking type of the actor. * @param ano Actor Id @@ -481,41 +695,88 @@ int32 actorMaskType(int ano) { * @param x X position * @param y Y position */ -void storeActorPos(int ano, int x, int y) { +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) { +void StoreActorSteps(int ano, int steps) { assert(ano > 0 && ano <= NumActors); // illegal actor number actorInfo[ano - 1].steps = steps; } -int getActorSteps(int ano) { +int GetActorSteps(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number return actorInfo[ano - 1].steps; } -void storeActorZpos(int ano, int z) { +void StoreActorZpos(int ano, int z, int column) { assert(ano > 0 && ano <= NumActors); // illegal actor number - actorInfo[ano - 1].z = z; + if (!TinselV2) { + // Prior to Tinsel 2, only a single z value was stored + actorInfo[ano - 1].z = z; + } else { + // Alter existing entry, if there is one + for (int i = 0; i < NUM_ZPOSITIONS; i++) { + if (zPositions[i].actor == ano && zPositions[i].column == column) { + zPositions[i].z = z; + return; + } + } + + // No existing entry found, so find an empty slot + for (int i = 0; i < NUM_ZPOSITIONS; i++) { + if (zPositions[i].actor == 0) { + zPositions[i].actor = (short)ano; + zPositions[i].column = (short)column; + zPositions[i].z = z; + return; + } + } + + error("NUM_ZPOSITIONS exceeded"); + } } +int GetActorZpos(int ano, int column) { + RANGE_CHECK(ano); + + // Find entry, there should be one + for (int i = 0; i < NUM_ZPOSITIONS; i++) { + if (zPositions[i].actor == ano && zPositions[i].column == column) { + return zPositions[i].z; + } + } + + return 1000; // Nominal value +} + +void IncLoopCount(int ano) { + RANGE_CHECK(ano); + + actorInfo[ano - 1].loopCount++; +} + +int GetLoopCount(int ano) { + RANGE_CHECK(ano); + + return actorInfo[ano - 1].loopCount; +} void GetActorPos(int ano, int *x, int *y) { - PMACTOR pActor; + PMOVER pActor; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor pActor = GetMover(ano); if (pActor) - GetMActorPosition(pActor, x, y); + GetMoverPosition(pActor, x, y); else { *x = actorInfo[ano - 1].x; *y = actorInfo[ano - 1].y; @@ -531,15 +792,18 @@ void GetActorPos(int ano, int *x, int *y) { */ void GetActorMidTop(int ano, int *x, int *y) { // Not used in JAPAN version - PMACTOR pActor; + PMOVER 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) { + GetMoverMidTop(pActor, x, y); + else if (TinselV2) { + *x = (GetActorLeft(ano) + GetActorRight(ano)) / 2; + *y = GetActorTop(ano); + } else if (actorInfo[ano - 1].presObj) { *x = (MultiLeftmost(actorInfo[ano - 1].presObj) + MultiRightmost(actorInfo[ano - 1].presObj)) / 2; *y = MultiHighest(actorInfo[ano - 1].presObj); @@ -554,10 +818,39 @@ void GetActorMidTop(int ano, int *x, int *y) { int GetActorLeft(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - if (!actorInfo[ano - 1].presObj) - return 0; + if (!TinselV2) { + // Tinsel 1 version + if (!actorInfo[ano - 1].presObj) + return 0; - return MultiLeftmost(actorInfo[ano - 1].presObj); + return MultiLeftmost(actorInfo[ano - 1].presObj); + } + + // Tinsel 2 version + PMOVER pMover = GetMover(ano); + int i; + bool bIsObj; + int left = 0; + + if (pMover != NULL) { + return GetMoverLeft(pMover); + } else { + for (i = 0, bIsObj = false; i < MAX_REELS; i++) { + // If there's an object + // and it is not a blank frame for it... + if (actorInfo[ano-1].presObjs[i] && MultiHasShape(actorInfo[ano - 1].presObjs[i])) { + if (!bIsObj) { + bIsObj = true; + left = MultiLeftmost(actorInfo[ano - 1].presObjs[i]); + } else { + if (MultiLeftmost(actorInfo[ano - 1].presObjs[i]) < left) + left = MultiLeftmost(actorInfo[ano - 1].presObjs[i]); + } + } + } + + return bIsObj ? left : 0; + } } /** @@ -567,10 +860,38 @@ int GetActorLeft(int ano) { int GetActorRight(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - if (!actorInfo[ano - 1].presObj) - return 0; + if (!TinselV2) { + // Tinsel 1 version + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiRightmost(actorInfo[ano - 1].presObj); + } + + // Tinsel 2 version + PMOVER pMover = GetMover(ano); + int i; + bool bIsObj; + int right = 0; - return MultiRightmost(actorInfo[ano - 1].presObj); + if (pMover != NULL) { + return GetMoverRight(pMover); + } else { + for (i = 0, bIsObj = false; i < MAX_REELS; i++) { + // If there's an object + // and it is not a blank frame for it... + if (actorInfo[ano-1].presObjs[i] && MultiHasShape(actorInfo[ano-1].presObjs[i])) { + if (!bIsObj) { + bIsObj = true; + right = MultiRightmost(actorInfo[ano-1].presObjs[i]); + } else { + if (MultiRightmost(actorInfo[ano-1].presObjs[i]) > right) + right = MultiRightmost(actorInfo[ano-1].presObjs[i]); + } + } + } + return bIsObj ? right : 0; + } } /** @@ -580,10 +901,39 @@ int GetActorRight(int ano) { int GetActorTop(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - if (!actorInfo[ano - 1].presObj) - return 0; + if (!TinselV2) { + // Tinsel 1 version + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiHighest(actorInfo[ano - 1].presObj); + } - return MultiHighest(actorInfo[ano - 1].presObj); + // Tinsel 2 version + PMOVER pMover = GetMover(ano); + int i; + bool bIsObj; + int top = 0; + + if (pMover != NULL) { + return GetMoverTop(pMover); + } else { + for (i = 0, bIsObj = false; i < MAX_REELS; i++) { + // If there's an object + // and it is not a blank frame for it... + if (actorInfo[ano-1].presObjs[i] && MultiHasShape(actorInfo[ano-1].presObjs[i])) { + if (!bIsObj) { + bIsObj = true; + top = MultiHighest(actorInfo[ano-1].presObjs[i]); + } else { + if (MultiHighest(actorInfo[ano-1].presObjs[i]) < top) + top = MultiHighest(actorInfo[ano-1].presObjs[i]); + } + } + } + + return bIsObj ? top : 0; + } } /** @@ -592,10 +942,65 @@ int GetActorTop(int ano) { int GetActorBottom(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - if (!actorInfo[ano - 1].presObj) - return 0; + if (!TinselV2) { + // Tinsel 1 version + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiLowest(actorInfo[ano - 1].presObj); + } + + // Tinsel 2 version + PMOVER pMover = GetMover(ano); + int i; + bool bIsObj; + int bottom = 0; + + if (pMover != NULL) { + return GetMoverBottom(pMover); + } else { + for (i = 0, bIsObj = false; i < MAX_REELS; i++) { + // If there's an object + // and it is not a blank frame for it... + if (actorInfo[ano-1].presObjs[i] && MultiHasShape(actorInfo[ano-1].presObjs[i])) { + if (!bIsObj) { + bIsObj = true; + bottom = MultiLowest(actorInfo[ano-1].presObjs[i]); + } else { + if (MultiLowest(actorInfo[ano-1].presObjs[i]) > bottom) + bottom = MultiLowest(actorInfo[ano-1].presObjs[i]); + } + } + } + return bIsObj ? bottom : 0; + } +} + +/** + * Shows the given actor + */ +void ShowActor(CORO_PARAM, int ano) { + PMOVER pMover; + RANGE_CHECK(ano); + + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); - return MultiLowest(actorInfo[ano - 1].presObj); + CORO_BEGIN_CODE(_ctx); + + // reset hidden flag + actorInfo[ano - 1].bHidden = false; + + // Send event to tagged actors + if (IsTaggedActor(ano)) + CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, ano, SHOWEVENT, true, 0)); + + // If moving actor involved, un-hide it + pMover = GetMover(ano); + if (pMover) + UnHideMover(pMover); + + CORO_END_CODE; } /** @@ -603,18 +1008,47 @@ int GetActorBottom(int ano) { * For a moving actor, actually hide it. * @param ano Actor Id */ -void HideActor(int ano) { - PMACTOR pActor; - +void HideActor(CORO_PARAM, int ano) { + PMOVER pMover; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (TinselV2) { + actorInfo[ano - 1].bHidden = true; + + // Send event to tagged actors + // (this is duplicated in HideMover()) + if (IsTaggedActor(ano)) { + CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, ano, HIDEEVENT, true, 0)); + + // It may be pointed to + SetActorPointedTo(ano, false); + SetActorTagWanted(ano, false, false, 0); + } + } + // Get moving actor involved - pActor = GetMover(ano); + pMover = GetMover(ano); - if (pActor) - hideMActor(pActor, 0); - else - actorInfo[ano - 1].hidden = true; + if (pMover) + HideMover(pMover, 0); + else if (!TinselV2) + actorInfo[ano - 1].bHidden = true; + + CORO_END_CODE; +} + +/** + * Return actor hidden status. + */ +bool ActorHidden(int ano) { + RANGE_CHECK(ano); + + return actorInfo[ano - 1].bHidden; } /** @@ -623,7 +1057,7 @@ void HideActor(int ano) { * @param sf sf */ bool HideMovingActor(int ano, int sf) { - PMACTOR pActor; + PMOVER pActor; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor @@ -631,7 +1065,7 @@ bool HideMovingActor(int ano, int sf) { pActor = GetMover(ano); if (pActor) { - hideMActor(pActor, sf); + HideMover(pActor, sf); return true; } else { if (actorInfo[ano - 1].presObj != NULL) @@ -645,7 +1079,7 @@ bool HideMovingActor(int ano, int sf) { * @param ano Actor Id */ void unHideMovingActor(int ano) { - PMACTOR pActor; + PMOVER pActor; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor @@ -654,7 +1088,7 @@ void unHideMovingActor(int ano) { assert(pActor); // not a moving actor - unhideMActor(pActor); + UnHideMover(pActor); } /** @@ -663,7 +1097,7 @@ void unHideMovingActor(int ano) { * actor's walk (if any) from the new co-ordinates. */ void restoreMovement(int ano) { - PMACTOR pActor; + PMOVER pActor; assert(ano > 0 && ano <= NumActors); // illegal actor number @@ -672,11 +1106,11 @@ void restoreMovement(int ano) { assert(pActor); // not a moving actor - if (pActor->objx == actorInfo[ano - 1].x && pActor->objy == actorInfo[ano - 1].y) + 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; + pActor->objX = actorInfo[ano - 1].x; + pActor->objY = actorInfo[ano - 1].y; if (pActor->actorObj) SSetActorDest(pActor); @@ -686,28 +1120,28 @@ void restoreMovement(int ano) { * 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; +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE hFilm, OBJECT *pobj, int reelnum, int x, int y) { + PMOVER 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) + // Only store the reel and film for a moving actor if NOT called from MoverProcess() + // (MoverProcess() calls with reel=film=NULL, pobj not NULL) if (!pActor - || !(reel == NULL && film == 0 && pobj != NULL)) { + || !(reel == NULL && hFilm == 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; + actorInfo[ano - 1].presFilm = hFilm; // Store film + actorInfo[ano - 1].presPlayX = x; + actorInfo[ano - 1].presPlayY = y; } - // Only store the object for a moving actor if called from MActorProcess() + // Only store the object for a moving actor if called from MoverProcess() if (!pActor) { actorInfo[ano - 1].presObj = pobj; // Store object - } else if (reel == NULL && film == 0 && pobj != NULL) { + } else if (reel == NULL && hFilm == 0 && pobj != NULL) { actorInfo[ano - 1].presObj = pobj; // Store object } } @@ -723,50 +1157,50 @@ const FREEL *actorReel(int ano) { /***************************************************************************/ -void setActorPlayFilm(int ano, SCNHANDLE film) { +void SetActorPlayFilm(int ano, SCNHANDLE hFilm) { assert(ano > 0 && ano <= NumActors); // illegal actor number - actorInfo[ano - 1].playFilm = film; + actorInfo[ano - 1].playFilm = hFilm; } -SCNHANDLE getActorPlayFilm(int ano) { +SCNHANDLE GetActorPlayFilm(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number return actorInfo[ano - 1].playFilm; } -void setActorTalkFilm(int ano, SCNHANDLE film) { +void SetActorTalkFilm(int ano, SCNHANDLE hFilm) { assert(ano > 0 && ano <= NumActors); // illegal actor number - actorInfo[ano - 1].talkFilm = film; + actorInfo[ano - 1].talkFilm = hFilm; } -SCNHANDLE getActorTalkFilm(int ano) { +SCNHANDLE GetActorTalkFilm(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number return actorInfo[ano - 1].talkFilm; } -void setActorTalking(int ano, bool tf) { +void SetActorTalking(int ano, bool tf) { assert(ano > 0 && ano <= NumActors); // illegal actor number - actorInfo[ano - 1].talking = tf;; + actorInfo[ano - 1].bTalking = tf;; } -bool isActorTalking(int ano) { +bool ActorIsTalking(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - return actorInfo[ano - 1].talking; + return actorInfo[ano - 1].bTalking; } -void setActorLatestFilm(int ano, SCNHANDLE film) { +void SetActorLatestFilm(int ano, SCNHANDLE hFilm) { assert(ano > 0 && ano <= NumActors); // illegal actor number - actorInfo[ano - 1].latestFilm = film; + actorInfo[ano - 1].latestFilm = hFilm; actorInfo[ano - 1].steps = 0; } -SCNHANDLE getActorLatestFilm(int ano) { +SCNHANDLE GetActorLatestFilm(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number return actorInfo[ano - 1].latestFilm; @@ -774,23 +1208,36 @@ SCNHANDLE getActorLatestFilm(int ano) { /***************************************************************************/ -void updateActorEsc(int ano, bool escOn, int escEvent) { +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; + actorInfo[ano - 1].bEscOn = escOn; + actorInfo[ano - 1].escEvent = escEvent; } -bool actorEsc(int ano) { +void UpdateActorEsc(int ano, int escEvent) { + RANGE_CHECK(ano); + + if (escEvent) { + actorInfo[ano - 1].bEscOn = true; + actorInfo[ano - 1].escEvent = escEvent; + } else { + actorInfo[ano - 1].bEscOn = false; + actorInfo[ano - 1].escEvent = GetEscEvents(); + } + +} + +bool ActorEsc(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - return actorInfo[ano - 1].escOn; + return actorInfo[ano - 1].bEscOn; } -int actorEev(int ano) { +int ActorEev(int ano) { assert(ano > 0 && ano <= NumActors); // illegal actor number - return actorInfo[ano - 1].escEv; + return actorInfo[ano - 1].escEvent; } /** @@ -801,7 +1248,7 @@ int AsetZPos(OBJECT *pObj, int y, int32 z) { z += z ? -1 : 0; - zPos = y + (z << 10); + zPos = y + (z << ZSHIFT); MultiSetZPosition(pObj, zPos); return zPos; } @@ -809,9 +1256,18 @@ int AsetZPos(OBJECT *pObj, int y, int32 z) { /** * Guess what these do. */ -void MAsetZPos(PMACTOR pActor, int y, int32 zFactor) { - if (!pActor->aHidden) - AsetZPos(pActor->actorObj, y, zFactor); +void SetMoverZ(PMOVER pMover, int y, int32 zFactor) { + if (!pMover->bHidden) { + if (!TinselV2) + AsetZPos(pMover->actorObj, y, zFactor); + else if (MoverIsSWalking(pMover) && pMover->zOverride != -1) { + // Special for SWalk() + MultiSetZPosition(pMover->actorObj, (pMover->zOverride << ZSHIFT) + y); + } else { + // Normal case + MultiSetZPosition(pMover->actorObj, (zFactor << ZSHIFT) + y); + } + } } /** @@ -828,51 +1284,127 @@ void storeActorAttr(int ano, int r1, int g1, int b1) { if (ano == -1) defaultColour = RGB(r1, g1, b1); else - actorInfo[ano - 1].tColour = RGB(r1, g1, b1); + actorInfo[ano - 1].textColour = RGB(r1, g1, b1); +} + +/** + * Called from ActorRGB() - Stores actor's speech colour. + */ + +void SetActorRGB(int ano, COLORREF colour) { + assert(ano >= 0 && ano <= NumActors); + + if (ano) + actorInfo[ano - 1].textColour = colour; + else + defaultColour = colour; } /** * Get the actor's stored speech colour. * @param ano Actor Id */ -COLORREF getActorTcol(int ano) { +COLORREF GetActorRGB(int ano) { // Not used in JAPAN version - assert(ano > 0 && ano <= NumActors); // illegal actor number + assert((ano >= -1) && (ano <= NumActors)); // illegal actor number - if (actorInfo[ano - 1].tColour) - return actorInfo[ano - 1].tColour; - else + if ((ano == -1) || !actorInfo[ano - 1].textColour) return defaultColour; + else + return actorInfo[ano - 1].textColour; +} + +/** + * Set the actor's Z-factor + */ +void SetActorZfactor(int ano, uint32 zFactor) { + RANGE_CHECK(ano); + + zFactors[ano - 1] = (uint8)zFactor; +} + +uint32 GetActorZfactor(int ano) { + RANGE_CHECK(ano); + + return zFactors[ano - 1]; } /** * Store relevant information pertaining to currently existing actors. */ int SaveActors(SAVED_ACTOR *sActorInfo) { - int i, j; + int i, j, k; 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++; + for (k = 0; k < (TinselV2 ? MAX_REELS : 1); ++k) { + bool presFlag = !TinselV2 ? actorInfo[i].presObj != NULL : + (actorInfo[i].presObjs[k] != NULL) && !IsCdPlayHandle(actorInfo[i].presFilm); + if (presFlag) { + + assert(j < MAX_SAVED_ACTORS); // Saving too many actors + + if (!TinselV2) { + sActorInfo[j].bAlive = actorInfo[i].bAlive; + sActorInfo[j].zFactor = (short)actorInfo[i].z; + sActorInfo[j].presRnum = (short)actorInfo[i].presRnum; + } + + sActorInfo[j].actorID = (short)(i+1); + if (TinselV2) + sActorInfo[j].bHidden = actorInfo[i].bHidden; + // sActorInfo[j].x = (short)actorInfo[i].x; + // sActorInfo[j].y = (short)actorInfo[i].y; + // sActorInfo[j].presReel = actorInfo[i].presReel; + sActorInfo[j].presFilm = actorInfo[i].presFilm; + sActorInfo[j].presPlayX = (short)actorInfo[i].presPlayX; + sActorInfo[j].presPlayY = (short)actorInfo[i].presPlayY; + j++; + + break; + } } } return j; } +/** + * Restore actor data + */ +void RestoreActors(int numActors, PSAVED_ACTOR sActorInfo) { + int i, aIndex; + + for (i = 0; i < numActors; i++) { + aIndex = sActorInfo[i].actorID - 1; + + actorInfo[aIndex].bHidden = sActorInfo[i].bHidden; + + // Play the same reel. + if (sActorInfo[i].presFilm != 0) { + RestoreActorReels(sActorInfo[i].presFilm, sActorInfo[i].actorID, + sActorInfo[i].presPlayX, sActorInfo[i].presPlayY); + } + } +} + +void SaveZpositions(void *zpp) { + memcpy(zpp, zPositions, sizeof(zPositions)); +} + +void RestoreZpositions(void *zpp) { + memcpy(zPositions, zpp, sizeof(zPositions)); +} + +void SaveActorZ(byte *saveActorZ) { + assert(NumActors <= MAX_SAVED_ACTOR_Z); + + memcpy(saveActorZ, zFactors, NumActors); +} + +void RestoreActorZ(byte *saveActorZ) { + memcpy(zFactors, saveActorZ, NumActors); +} + void setactorson(void) { bActorsOn = true; } @@ -880,18 +1412,307 @@ void setactorson(void) { void ActorsLife(int ano, bool bAlive) { assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number - actorInfo[ano-1].alive = bAlive; + actorInfo[ano-1].bAlive = bAlive; } void syncAllActorsAlive(Serializer &s) { for (int i = 0; i < MAX_SAVED_ALIVES; i++) { - s.syncAsByte(actorInfo[i].alive); + s.syncAsByte(actorInfo[i].bAlive); s.syncAsByte(actorInfo[i].tagged); s.syncAsByte(actorInfo[i].tType); s.syncAsUint32LE(actorInfo[i].hTag); } } +/** + * Called from EndActor() + */ +void dwEndActor(int ano) { + int i; + + RANGE_CHECK(ano); + + // Make play.c think it's been replaced +// The following line may have been indirectly making text go away! +// actorInfo[ano - 1].presFilm = NULL; +// but things were returning after a cut scene. +// so re-instate it and de-register the object + actorInfo[ano - 1].presFilm = 0; + actorInfo[ano-1].filmNum++; + + for (i = 0; i < MAX_REELS; i++) { + // It may take a frame to remove this, so make it invisible + if (actorInfo[ano-1].presObjs[i] != NULL) { + MultiHideObject(actorInfo[ano-1].presObjs[i]); + actorInfo[ano-1].presObjs[i] = NULL; + } + } +} + + +/** + * Returns a tagged actor's tag portion. + */ +void GetActorTagPortion(int ano, unsigned *top, unsigned *bottom, unsigned *left, unsigned *right) { + // Convert actor number to index + ano = TaggedActorIndex(ano); + + *top = taggedActors[ano].tagPortionV >> 16; + *bottom = taggedActors[ano].tagPortionV & 0xffff; + *left = taggedActors[ano].tagPortionH >> 16; + *right = taggedActors[ano].tagPortionH & 0xffff; + + // ensure validity + assert(*top >= 1 && *top <= 8); + assert(*bottom >= *top && *bottom <= 8); + assert(*left >= 1 && *left <= 8); + assert(*right >= *left && *right <= 8); +} + +/** + * Returns handle to tagged actor's tag text. + */ +SCNHANDLE GetActorTagHandle(int ano) { + // Convert actor number to index + ano = TaggedActorIndex(ano); + + return taggedActors[ano].hOverrideTag ? + taggedActors[ano].hOverrideTag : taggedActors[ano].hTagText; +} + +void SetActorPointedTo(int actor, bool bPointedTo) { + // Convert actor number to index + actor = TaggedActorIndex(actor); + + if (bPointedTo) + taggedActors[actor].tagFlags |= POINTING; + else + taggedActors[actor].tagFlags &= ~POINTING; +} + +bool ActorIsPointedTo(int actor) { + // Convert actor number to index + actor = TaggedActorIndex(actor); + + return (taggedActors[actor].tagFlags & POINTING); +} + +void SetActorTagWanted(int actor, bool bTagWanted, bool bCursor, SCNHANDLE hOverrideTag) { + // Convert actor number to index + actor = TaggedActorIndex(actor); + + if (bTagWanted) { + taggedActors[actor].tagFlags |= TAGWANTED; + taggedActors[actor].hOverrideTag = hOverrideTag; + } else { + taggedActors[actor].tagFlags &= ~TAGWANTED; + taggedActors[actor].hOverrideTag = 0; + } + + if (bCursor) + taggedActors[actor].tagFlags |= FOLLOWCURSOR; + else + taggedActors[actor].tagFlags &= ~FOLLOWCURSOR; +} + +bool ActorTagIsWanted(int actor) { + // Convert actor number to index + actor = TaggedActorIndex(actor); + + return (taggedActors[actor].tagFlags & TAGWANTED); +} + +/** + * 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. + */ +bool InHotSpot(int ano, int curX, int curY) { + int aTop, aBot; // Top and bottom limits } + int aHeight; // Height } of active area + int aLeft, aRight; // Left and right } + int aWidth; // Width } + unsigned topEighth, botEighth, leftEighth, rightEighth; + + // First check if within broad range + if (curX < (aLeft = GetActorLeft(ano)) // too far left + || curX > (aRight = GetActorRight(ano)) // too far right + || curY < (aTop = GetActorTop(ano)) // too high + || curY > (aBot = GetActorBottom(ano)) ) // too low + return false; + + GetActorTagPortion(ano, &topEighth, &botEighth, &leftEighth, &rightEighth); + + aWidth = aRight - aLeft; + aLeft += ((leftEighth - 1)*aWidth)/8; + aRight -= ((8 - rightEighth)*aWidth)/8; + + // check if within x-range + if (curX < aLeft || curX > aRight) + return false; + + aHeight = aBot - aTop; + aTop += ((topEighth - 1)*aHeight)/8; + aBot -= ((8 - botEighth)*aHeight)/8; + + // check if within y-range + if (curY < aTop || curY > aBot) + return false; + + return true; +} + +/** + * Front Tagged Actor + */ +int FrontTaggedActor(void) { + int i; + + for (i = 0; i < numTaggedActors; i++) { + if (taggedActors[i].tagFlags & POINTING) + return taggedActors[i].id; + } + return 0; +} + +/** + * GetActorTagPos + */ +void GetActorTagPos(int actor, int *pTagX, int *pTagY, bool bAbsolute) { + unsigned topEighth, botEighth; + int aTop; // Top and bottom limits } + int aHeight; // Height } of active area + int Loffset, Toffset; + + GetActorTagPortion(actor, &topEighth, &botEighth, (unsigned *)&Loffset, (unsigned *)&Toffset); + + aTop = GetActorTop(actor); + aHeight = GetActorBottom(actor) - aTop; + aTop += ((topEighth - 1) * aHeight) / 8; + + *pTagX = ((GetActorLeft(actor) + GetActorRight(actor)) / 2); + *pTagY = aTop; + + if (!bAbsolute) { + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + *pTagX -= Loffset; + *pTagY -= Toffset; + } +} + +/** + * Is Tagged Actor + */ +bool IsTaggedActor(int actor) { + int i; + + for (i = 0; i < numTaggedActors; i++) { + if (taggedActors[i].id == actor) + return true; + } + return false; +} + +/** + * StoreActorPresFilm + */ +void StoreActorPresFilm(int ano, SCNHANDLE hFilm, int x, int y) { + int i; + + RANGE_CHECK(ano); + + actorInfo[ano-1].presFilm = hFilm; + actorInfo[ano-1].presPlayX = x; + actorInfo[ano-1].presPlayY = y; + actorInfo[ano-1].filmNum++; + + for (i = 0; i < MAX_REELS; i++) { + // It may take a frame to remove this, so make it invisible + if (actorInfo[ano - 1].presObjs[i] != NULL) + MultiHideObject(actorInfo[ano - 1].presObjs[i]); + + actorInfo[ano - 1].presColumns[i] = -1; + actorInfo[ano - 1].presObjs[i] = NULL; + } +} + +/** + * GetActorPresFilm + */ +SCNHANDLE GetActorPresFilm(int ano) { + RANGE_CHECK(ano); + + return actorInfo[ano - 1].presFilm; +} + + +/** + * GetActorFilmNumber + */ +int GetActorFilmNumber(int ano) { + RANGE_CHECK(ano); + + return actorInfo[ano - 1].filmNum; +} + +/** + * More properly should be called: + * 'StoreActorReelAndObject()' + */ +void StoreActorReel(int actor, int column, OBJECT *pObj) { + RANGE_CHECK(actor); + int i; + + for (i = 0; i < MAX_REELS; i++) { + if (actorInfo[actor-1].presColumns[i] == -1) { + // Store reel and object + actorInfo[actor - 1].presColumns[i] = column; + actorInfo[actor - 1].presObjs[i] = pObj; + break; + } + } + + assert(i < MAX_REELS); +} + +/** + * NotPlayingReel + */ +void NotPlayingReel(int actor, int filmNumber, int column) { + int i; + + RANGE_CHECK(actor); + + if (actorInfo[actor-1].filmNum != filmNumber) + return; + + // De-register this reel + for (i = 0; i < MAX_REELS; i++) { + if (actorInfo[actor-1].presColumns[i] == column) { + actorInfo[actor-1].presObjs[i] = NULL; + actorInfo[actor-1].presColumns[i] = -1; + break; + } + } + + // De-register the film if this was the last reel + for (i = 0; i < MAX_REELS; i++) { + if (actorInfo[actor-1].presColumns[i] != -1) + break; + } + if (i == MAX_REELS) + actorInfo[actor-1].presFilm = 0; +} + +bool ActorReelPlaying(int actor, int column) { + RANGE_CHECK(actor); + + for (int i = 0; i < MAX_REELS; i++) { + if (actorInfo[actor - 1].presColumns[i] == column) + return true; + } + return false; +} } // end of namespace Tinsel diff --git a/engines/tinsel/actors.h b/engines/tinsel/actors.h index 91f54519d5..03fd0151b4 100644 --- a/engines/tinsel/actors.h +++ b/engines/tinsel/actors.h @@ -29,24 +29,30 @@ #include "tinsel/dw.h" // for SCNHANDLE -#include "tinsel/events.h" // for USER_EVENT +#include "tinsel/events.h" // for TINSEL_EVENT #include "tinsel/palette.h" // for COLORREF namespace Tinsel { struct FREEL; struct INT_CONTEXT; -struct MACTOR; +struct MOVER; struct OBJECT; +#define ACTORTAG_KEY 0x1000000 + +#define OTH_RELATEDACTOR 0x00000fff +#define OTH_RELATIVE 0x00001000 +#define OTH_ABSOLUTE 0x00002000 /*----------------------------------------------------------------------*/ void RegisterActors(int num); void FreeActors(void); -void setleadid(int rid); -int LeadId(void); -void StartActors(SCNHANDLE ah, int numActors, bool bRunScript); +void SetLeadId(int rid); +int GetLeadId(void); +bool ActorIsGhost(int actor); +void StartTaggedActors(SCNHANDLE ah, int numActors, bool bRunScript); void DropActors(void); // No actor reels running void DisableActor(int actor); void EnableActor(int actor); @@ -63,60 +69,110 @@ int GetActorLeft(int ano); int GetActorRight(int ano); int GetActorTop(int ano); int GetActorBottom(int ano); -void HideActor(int ano); +void ShowActor(CORO_PARAM, int ano); +void HideActor(CORO_PARAM, int ano); +bool ActorHidden(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); +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE hFilm, 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); +void SetActorPlayFilm(int ano, SCNHANDLE hFilm); +SCNHANDLE GetActorPlayFilm(int ano); +void SetActorTalkFilm(int ano, SCNHANDLE hFilm); +SCNHANDLE GetActorTalkFilm(int ano); +void SetActorTalking(int ano, bool tf); +bool ActorIsTalking(int ano); +void SetActorLatestFilm(int ano, SCNHANDLE hFilm); +SCNHANDLE GetActorLatestFilm(int ano); + +void UpdateActorEsc(int ano, bool escOn, int escEvent); +void UpdateActorEsc(int ano, int escEvent); +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, int column = -1); +int GetActorZpos(int ano, int column); +void IncLoopCount(int ano); +int GetLoopCount(int ano); SCNHANDLE GetActorTag(int ano); void FirstTaggedActor(void); int NextTaggedActor(void); +int NextTaggedActor(int previous); 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 SetMoverZ(MOVER *pMover, int y, int32 zFactor); +void ActorEvent(int ano, TINSEL_EVENT event, PLR_EVENT be); void storeActorAttr(int ano, int r1, int g1, int b1); -COLORREF getActorTcol(int ano); +COLORREF GetActorRGB(int ano); +void SetActorRGB(int ano, COLORREF colour); +void SetActorZfactor(int ano, uint32 zFactor); +uint32 GetActorZfactor(int ano); void setactorson(void); void ActorsLife(int id, bool bAlive); +void dwEndActor(int ano); + +void ActorEvent(CORO_PARAM, int ano, TINSEL_EVENT tEvent, bool bWait, int myEscape, bool *result = NULL); + +void GetActorTagPortion(int ano, unsigned *top, unsigned *bottom, unsigned *left, unsigned *right); +SCNHANDLE GetActorTagHandle(int ano); +void SetActorPointedTo(int actor, bool bPointedTo); +bool ActorIsPointedTo(int actor); +void SetActorTagWanted(int actor, bool bTagWanted, bool bCursor, SCNHANDLE hOverrideTag); +bool ActorTagIsWanted(int actor); +bool InHotSpot(int ano, int curX, int curY); +int FrontTaggedActor(void); +void GetActorTagPos(int actor, int *pTagX, int *pTagY, bool bAbsolute); +bool IsTaggedActor(int actor); +void StoreActorPresFilm(int ano, SCNHANDLE hFilm, int x, int y); +SCNHANDLE GetActorPresFilm(int ano); +int GetActorFilmNumber(int ano); +void StoreActorReel(int actor, int column, OBJECT *pObj); +void NotPlayingReel(int actor, int filmNumber, int column); +bool ActorReelPlaying(int actor, int column); +void SetActorPlayFilm(int ano, SCNHANDLE hFilm); +SCNHANDLE GetActorPlayFilm(int ano); + /*----------------------------------------------------------------------*/ struct SAVED_ACTOR { short actorID; - short z; + short zFactor; bool bAlive; + bool bHidden; SCNHANDLE presFilm; //!< the film that reel belongs to short presRnum; //!< the present reel number - short presX, presY; + short presPlayX, presPlayY; +}; +typedef SAVED_ACTOR *PSAVED_ACTOR; + +#define NUM_ZPOSITIONS 200 // Reasonable-sounding number + +struct Z_POSITIONS { + short actor; + short column; + int z; }; int SaveActors(SAVED_ACTOR *sActorInfo); - void RestoreActorProcess(int id, INT_CONTEXT *pic); +int SaveActors(PSAVED_ACTOR sActorInfo); +void RestoreActors(int numActors, PSAVED_ACTOR sActorInfo); + +void SaveZpositions(void *zpp); +void RestoreZpositions(void *zpp); + +void SaveActorZ(byte *saveActorZ); +void RestoreActorZ(byte *saveActorZ); /*----------------------------------------------------------------------*/ diff --git a/engines/tinsel/anim.cpp b/engines/tinsel/anim.cpp index 95d834d88a..055e0bba94 100644 --- a/engines/tinsel/anim.cpp +++ b/engines/tinsel/anim.cpp @@ -29,32 +29,12 @@ #include "tinsel/multiobj.h" // multi-part object defintions etc. #include "tinsel/object.h" #include "tinsel/sched.h" +#include "tinsel/tinsel.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 @@ -64,6 +44,9 @@ SCRIPTSTATE DoNextFrame(ANIM *pAnim) { const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); while (1) { // repeat until a real image + debugC(DEBUG_DETAILED, kTinselDebugAnimations, + "DoNextFrame %ph index=%d, op=%xh", (byte *)pAnim, pAnim->scriptIndex, + FROM_LE_32(pAni[pAnim->scriptIndex].op)); switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) { case ANI_END: // end of animation script @@ -104,7 +87,6 @@ SCRIPTSTATE DoNextFrame(ANIM *pAnim) { // go fetch a real image break; - case ANI_HVFLIP: // flip animated object in both directions // next opcode @@ -230,6 +212,12 @@ SCRIPTSTATE DoNextFrame(ANIM *pAnim) { void InitStepAnimScript(ANIM *pAnim, OBJECT *pAniObj, SCNHANDLE hNewScript, int aniSpeed) { OBJECT *pObj; // multi-object list iterator + debugC(DEBUG_DETAILED, kTinselDebugAnimations, + "InitStepAnimScript Object=(%d,%d,%xh) script=%xh aniSpeed=%d rec=%ph", + !pAniObj ? 0 : fracToInt(pAniObj->xPos), + !pAniObj ? 0 : fracToInt(pAniObj->yPos), + !pAniObj ? 0 : pAniObj->hImg, hNewScript, aniSpeed, (byte *)pAnim); + pAnim->aniDelta = 1; // will animate on next call to NextAnimRate pAnim->pObject = pAniObj; // set object to animate pAnim->hScript = hNewScript; // set animation script @@ -254,9 +242,13 @@ SCRIPTSTATE StepAnimScript(ANIM *pAnim) { // re-init animation delta counter pAnim->aniDelta = pAnim->aniRate; - // move to next frame - while ((state = DoNextFrame(pAnim)) == ScriptNoSleep) - ; + if (TinselV2) + state = DoNextFrame(pAnim); + else { + // move to next frame + while ((state = DoNextFrame(pAnim)) == ScriptNoSleep) + ; + } return state; } @@ -274,7 +266,7 @@ void SkipFrames(ANIM *pAnim, int numFrames) { // get a pointer to the script const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); - if (numFrames <= 0) + if (!TinselV2 && (numFrames <= 0)) // do nothing return; @@ -282,9 +274,10 @@ void SkipFrames(ANIM *pAnim, int numFrames) { 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; + // going off the end is probably a error, but only in Tinsel 1 + if (!TinselV2) + error("SkipFrames(): formally 'assert(0)!'"); + return; case ANI_JUMP: // do animation jump @@ -293,6 +286,10 @@ void SkipFrames(ANIM *pAnim, int numFrames) { // jump to new frame position pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + if (TinselV2) + // Done if skip to jump + return; break; case ANI_HFLIP: // flip animated object horizontally @@ -383,7 +380,10 @@ void SkipFrames(ANIM *pAnim, int numFrames) { default: // must be an actual animation frame handle // one less frame - if (numFrames-- > 0) { + if (numFrames == 0) + return; + + if (numFrames == -1 || numFrames-- > 0) { // next opcode pAnim->scriptIndex++; } else { @@ -401,4 +401,48 @@ void SkipFrames(ANIM *pAnim, int numFrames) { } } +/** + * About to jump or end + * @param pAnim Animation data structure + */ +bool AboutToJumpOrEnd(PANIM pAnim) { + if (pAnim->aniDelta == 1) { + // get a pointer to the script + ANI_SCRIPT *pAni = (ANI_SCRIPT *)LockMem(pAnim->hScript); + int zzz = pAnim->scriptIndex; + + for (;;) { + // repeat until a real image + switch (FROM_LE_32(pAni[zzz].op)) { + case ANI_END: // end of animation script + case ANI_JUMP: // do animation jump + return true; + + case ANI_HFLIP: // flip animated object horizontally + case ANI_VFLIP: // flip animated object vertically + case ANI_HVFLIP: // flip animated object in both directions + zzz++; + break; + + case ANI_ADJUSTX: // adjust animated object x animation point + case ANI_ADJUSTY: // adjust animated object y animation point + zzz += 2; + break; + + case ANI_ADJUSTXY: // adjust animated object x & y animation points + zzz += 3; + break; + + case ANI_HIDE: // hide animated object + default: // must be an actual animation frame handle + return false; + } + } + } + + return false; +} + + + } // end of namespace Tinsel diff --git a/engines/tinsel/anim.h b/engines/tinsel/anim.h index 5b25292a6b..a3a7b4447e 100644 --- a/engines/tinsel/anim.h +++ b/engines/tinsel/anim.h @@ -41,6 +41,32 @@ struct ANIM { uint32 hScript; //!< animation script handle int scriptIndex; //!< current position in animation script }; +typedef ANIM *PANIM; + +typedef void (*PANI_ADDR)(struct ANIM *); + +/** 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 + ANI_STOP = 11 //!< stop sound +}; + +/** animation script command possibilities */ +union ANI_SCRIPT { + int32 op; //!< treat as an opcode or operand + uint32 hFrame; //!< treat as a animation frame handle +// PANI_ADDR pFunc; //!< treat as a animation function call +}; /*----------------------------------------------------------------------*\ @@ -66,6 +92,8 @@ void SkipFrames( // Skip the specified number of frames ANIM *pAnim, // animation data structure int numFrames); // number of frames to skip +bool AboutToJumpOrEnd(PANIM pAnim); + } // end of namespace Tinsel #endif // TINSEL_ANIM_H diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp index 91d21b4e0b..94525e33dd 100644 --- a/engines/tinsel/background.cpp +++ b/engines/tinsel/background.cpp @@ -37,6 +37,9 @@ namespace Tinsel { // current background BACKGND *pCurBgnd = NULL; +// FIXME: Not yet used +static bool bEntireRedraw; + /** * Called to initialise a background. * @param pBgnd Pointer to data struct for current background @@ -124,6 +127,27 @@ void PlayfieldGetPos(int which, int *pXpos, int *pYpos) { *pYpos = fracToInt(pPlayfield->fieldY); } +/** + * Returns the x position of the centre of the specified playfield + * @param which Which playfield + */ + +int PlayfieldGetCentreX(int which) { + PLAYFIELD *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 + return fracToInt(pPlayfield->fieldX) + SCREEN_WIDTH/2; +} + /** * Returns the display list for the specified playfield. * @param which Which playfield @@ -229,4 +253,9 @@ void DrawBackgnd(void) { ResetClipRect(); } +void ForceEntireRedraw(void) { + bEntireRedraw = true; +} + + } // end of namespace Tinsel diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h index 7b8d099446..245507841c 100644 --- a/engines/tinsel/background.h +++ b/engines/tinsel/background.h @@ -27,10 +27,11 @@ #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" +#include "tinsel/coroutine.h" +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/palette.h" // palette definitions namespace Tinsel { @@ -78,6 +79,8 @@ struct BACKGND { void InitBackground( // called to initialise a background BACKGND *pBgnd); // pointer to data struct for current background +void StartupBackground(CORO_PARAM, SCNHANDLE hFilm); + void StopBgndScrolling(void); // Stops all background playfields from scrolling void PlayfieldSetPos( // Sets the xy position of the specified playfield in the current background @@ -90,6 +93,9 @@ void PlayfieldGetPos( // Returns the xy position of the specified playfield in int *pXpos, // returns current x position int *pYpos); // returns current y position +int PlayfieldGetCentreX( // Returns the xy position of the specified playfield in the current background + int which); // which playfield + OBJECT *GetPlayfieldList( // Returns the display list for the specified playfield int which); // which playfield @@ -100,7 +106,15 @@ 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); +OBJECT *GetBgObject(); + +SCNHANDLE BgPal(void); + +void ForceEntireRedraw(void); + +int BgWidth(void); + +int BgHeight(void); } // end of namespace Tinsel diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp index 9c1e5f1540..96b03292cb 100644 --- a/engines/tinsel/bg.cpp +++ b/engines/tinsel/bg.cpp @@ -37,7 +37,8 @@ #include "tinsel/pid.h" #include "tinsel/sched.h" #include "tinsel/timers.h" // For ONE_SECOND constant -#include "tinsel/tinlib.h" // For control() +#include "tinsel/tinlib.h" // For Control() +#include "tinsel/tinsel.h" #include "common/util.h" @@ -45,49 +46,61 @@ namespace Tinsel { //----------------- LOCAL GLOBAL DATA -------------------- -static SCNHANDLE BackPalette = 0; // Background's palette -static OBJECT *pBG = 0; // The main picture's object. +#define MAX_BG 10 + +static SCNHANDLE hBgPal = 0; // Background's palette +static POBJECT pBG[MAX_BG]; +static ANIM thisAnim[MAX_BG]; // used by BGmainProcess() 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() +static SCNHANDLE hBackground = 0; // Current scene handle - stored in case of Save_Scene() +static bool bDoFadeIn = false; +static int bgReels; + +/** + * GetBgObject + */ +OBJECT *GetBgObject() { + return pBG[0]; +} /** * BackPal */ -SCNHANDLE BackPal(void) { - return BackPalette; +SCNHANDLE BgPal(void) { + return hBgPal; } /** * SetDoFadeIn */ void SetDoFadeIn(bool tf) { - DoFadeIn = tf; + bDoFadeIn = tf; } /** * Called before scene change. */ void DropBackground(void) { - pBG = NULL; // No background - BackPalette = 0; // No background palette + pBG[0] = NULL; // No background + + if (!TinselV2) + hBgPal = 0; // No background palette } /** * Return the width of the current background. */ -int BackgroundWidth(void) { - assert(pBG); - return MultiRightmost(pBG) + 1; +int BgWidth(void) { + assert(pBG[0]); + return MultiRightmost(pBG[0]) + 1; } /** * Return the height of the current background. */ -int BackgroundHeight(void) { - assert(pBG); - return MultiLowest(pBG) + 1; +int BgHeight(void) { + assert(pBG[0]); + return MultiLowest(pBG[0]) + 1; } /** @@ -100,90 +113,137 @@ static void BGmainProcess(CORO_PARAM, const void *param) { CORO_BEGIN_CODE(_ctx); - const FREEL *pfr; + const FILM *pFilm; + const FREEL *pReel; const MULTI_INIT *pmi; // get the stuff copied to process when it was created - pfr = (const FREEL *)param; - - if (pBG == NULL) { + if (pBG[0] == 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 (!TinselV2) { + pReel = (const FREEL *)param; + + // Get the MULTI_INIT structure + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pReel->mobj)); + + // Initialise and insert the object, and initialise its script. + pBG[0] = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG[0]); + InitStepAnimScript(&thisAnim[0], pBG[0], FROM_LE_32(pReel->script), BGspeed); + bgReels = 1; + } else { + /*** At start of scene ***/ + pFilm = (const FILM *)LockMem(hBackground); + bgReels = pFilm->numreels; + + int i; + for (i = 0; i < bgReels; i++) { + // Get the MULTI_INIT structure + pmi = (PMULTI_INIT) LockMem(pFilm->reels[i].mobj); + + // Initialise and insert the object, and initialise its script. + pBG[i] = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG[i]); + MultiSetZPosition(pBG[i], 0); + InitStepAnimScript(&thisAnim[i], pBG[i], pFilm->reels[i].script, BGspeed); + + if (i > 0) + pBG[i-1]->pSlave = pBG[i]; + } + } - if (DoFadeIn) { + if (bDoFadeIn) { FadeInFast(NULL); - DoFadeIn = false; - } + bDoFadeIn = false; + } else if (TinselV2) + PokeInTagColour(); - while (StepAnimScript(&thisAnim) != ScriptFinished) - CORO_SLEEP(1); + for (;;) { + for (int i = 0; i < bgReels; i++) { + if (StepAnimScript(&thisAnim[i]) == ScriptFinished) + error("Background animation has finished!"); + } - error("Background animation has finished!"); + CORO_SLEEP(1); + } } else { // New background during scene - - // Just re-initialise the script. - InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed); - StepAnimScript(&thisAnim); + if (!TinselV2) { + pReel = (const FREEL *)param; + InitStepAnimScript(&thisAnim[0], pBG[0], FROM_LE_32(pReel->script), BGspeed); + StepAnimScript(&thisAnim[0]); + } else { + pFilm = (const FILM *)LockMem(hBackground); + assert(bgReels == pFilm->numreels); + + // Just re-initialise the scripts. + for (int i = 0; i < bgReels; i++) { + InitStepAnimScript(&thisAnim[i], pBG[i], pFilm->reels[i].script, BGspeed); + StepAnimScript(&thisAnim[i]); + } + } } CORO_END_CODE; } /** - * setBackPal() + * AetBgPal() */ -void setBackPal(SCNHANDLE hPal) { - BackPalette = hPal; +void SetBackPal(SCNHANDLE hPal) { + hBgPal = hPal; - fettleFontPal(BackPalette); - CreateTranslucentPalette(BackPalette); + FettleFontPal(hBgPal); + CreateTranslucentPalette(hBgPal); } void ChangePalette(SCNHANDLE hPal) { - SwapPalette(FindPalette(BackPalette), hPal); + SwapPalette(FindPalette(hBgPal), hPal); - setBackPal(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 + * @param hFilm Scene background film */ -void startupBackground(SCNHANDLE bfilm) { +void StartupBackground(CORO_PARAM, SCNHANDLE hFilm) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + const FILM *pfilm; IMAGE *pim; - BgroundHandle = bfilm; // Save handle in case of Save_Scene() + hBackground = hFilm; // Save handle in case of Save_Scene() - pim = GetImageFromFilm(bfilm, 0, NULL, NULL, &pfilm); - setBackPal(FROM_LE_32(pim->hImgPal)); + pim = GetImageFromFilm(hFilm, 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 g_scheduler->createProcess(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL)); + + if (pBG[0] == NULL) + ControlStartOff(); + + if (TinselV2 && (coroParam != nullContext)) + CORO_GIVE_WAY; + + CORO_END_CODE; } /** * Return the current scene handle. */ SCNHANDLE GetBgroundHandle(void) { - return BgroundHandle; + return hBackground; } } // end of namespace Tinsel diff --git a/engines/tinsel/bmv.cpp b/engines/tinsel/bmv.cpp new file mode 100644 index 0000000000..613d8c9686 --- /dev/null +++ b/engines/tinsel/bmv.cpp @@ -0,0 +1,1272 @@ +/* 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$ + * + * The movie player. + */ + +#include "common/file.h" +#include "sound/mixer.h" +#include "sound/audiostream.h" +#include "tinsel/tinsel.h" +#include "tinsel/background.h" +#include "tinsel/cliprect.h" +#include "tinsel/coroutine.h" +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/heapmem.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/sched.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" +#include "tinsel/timers.h" +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +//----------------- GLOBAL GLOBAL DATA ------------------------ + +bool bOldAudio; + +//----------------- LOCAL GLOBAL DATA ------------------------ + +//static READREQ rr; + +//----------------- LOCAL DEFINES ---------------------------- + +#define SZ_C_BLOB 65 +#define SZ_U_BLOB 128 + +#define BLANK_SOUND 0x0 // for 16 bit silence + +#define PT_A 20 // Number of times PT_B may be reached +#define PT_B 6 + + +#define SLOT_SIZE (25*1024) +//#define NUM_SLOTS 168 +#define NUM_SLOTS 122 // -> ~ 3MB + + +#define PREFETCH (NUM_SLOTS/2) // For initial test + +#ifndef _Windows +//#define ADVANCE_SOUND 12 // 1 second +#define ADVANCE_SOUND 18 // 1 1/2 second +//#define MAX_ADVANCE_SOUND 36 // 3 seconds +#else +#define ADVANCE_SOUND 18 // 1 1/2 seconds +#endif +#define SUBSEQUENT_SOUND 6 // 1/2 second + + + +// PACKET TYPE IDs & FLAGS + +#define CD_SLOT_NOP 0x00 // Skip to next slot +#define CD_LE_FIN 0x01 // End of movie +#define CD_PDELTA 0x02 // Image compressed to previous one +#define CD_SDELTA 0x03 // Image self-compressed + +#define BIT0 0x01 + +#define CD_XSCR 0x04 // Screen has a scroll offset +#define CD_CMAP 0x08 // Colour map is included +#define CD_CMND 0x10 // Command is included +#define CD_AUDIO 0x20 // Audio data is included +#define CD_EXTEND 0x40 // Extended modes "A"-"z" +#define CD_PRINT 0x80 // goes in conjunction with CD_CMD + +// Data field sizes +#define sz_XSCR_pkt 2 +#define sz_CMAP_pkt 0x300 +#define sz_CMD_TALK_pkt 10 +#define sz_CMD_PRINT_pkt 8 +#define sz_AUDIO_pkt 3675 + + +typedef struct { + + short x; + short y; + short stringId; + unsigned char duration; + char r; // may be b! + char g; + char b; // may be r! + +} TALK_CMD, *PTALK_CMD; + +typedef struct { + + short x; + short y; + short stringId; + unsigned char duration; + unsigned char fontId; + +} PRINT_CMD, *PPRINT_CMD; + + +//----------------- LOCAL GLOBAL DATA ------------------------ + +// Set when a movie is on +static bool bMovieOn; + +// Set to kill one off +static bool bAbort; + +// For escaping out of movies +static int bmvEscape; + +// Movie file pointer +static Common::File stream; + +// Movie file name +static char szMovieFile[14]; + +// Pointers to buffers +static byte *bigBuffer; //, *screenBuffer; + +// Next data to use to extract a frame +static int nextUseOffset; + +// Next data to use to extract sound data +static int nextSoundOffset; + +// When above offset gets to what this is set at, rewind +static int wrapUseOffset; + +// The offset of the most future packet +static int mostFutureOffset; + +// The current frame +static int currentFrame; +static int currentSoundFrame; + +// Number of packets currently in RAM +static int numAdvancePackets; + +// Next slot that will be read from disc +static int nextReadSlot; + +// Set when the whole file has been read +static bool bFileEnd; + +// Palette +static COLORREF moviePal[256]; + +static int blobsInBuffer; + +static struct { + + POBJECT pText; + int dieFrame; + +} texts[2]; + +static COLORREF talkColour; + +static int bigProblemCount; + +static bool bIsText; + +static int movieTick; +static int startTick; +static uint32 nextMovieTime = 0; + +static int nowTick; + +static uint16 Au_Prev1 = 0; +static uint16 Au_Prev2 = 0; +static byte *ScreenBeg; +static byte *screenBuffer; + +static bool audioStarted; + +static Audio::AppendableAudioStream *audioStream = 0; +static Audio::SoundHandle audioHandle; + +const uint16 Au_DecTable[16] = {16512, 8256, 4128, 2064, 1032, 516, 258, 192, + 129, 88, 64, 56, 48, 40, 36, 32}; + +//---------------- DECOMPRESSOR FUNCTIONS -------------------- + +#define SCREEN_WIDE 640 +#define SCREEN_HIGH 429 +#define SAM_P_BLOB (32 * 2) + +#define ROR(x,v) x = ((x >> (v%32)) | (x << (32 - (v%32)))); +#define ROL(x,v) x = ((x << (v%32)) | (x >> (32 - (v%32)))); +#define NEXT_BYTE(v) v = forwardDirection ? v + 1 : v - 1; + +static void PrepBMV(const byte *sourceData, int length, short deltaFetchDisp) { + uint8 NibbleHi = 0; + const byte *saved_esi; + uint32 eax = 0; + uint32 edx = length; + int32 ebx = deltaFetchDisp; + uint32 ecx = 0; + const byte *esi; + byte *edi, *ebp; + + bool forwardDirection = (deltaFetchDisp <= -SCREEN_WIDE) || (deltaFetchDisp >= 0); + if (forwardDirection) { + // Forward decompression + esi = sourceData; + edi = ScreenBeg; + ebp = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH; + } else { + esi = sourceData + length - 1; + edi = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH - 1; + ebp = ScreenBeg - 1; + } + + bool firstLoop, flag; + + int loopCtr = 0; + for (;;) { + flag = false; + + if ((loopCtr == 0) || (edx == 4)) { + // Get the next hi,lo nibble + eax = (eax & 0xffffff00) | *esi; + firstLoop = true; + } else { + // Get the high nibble + eax = eax & 0xffffff00 | (NibbleHi >> 4); + firstLoop = false; + } + + // Is lo nibble '00xx'? + if ((eax & 0xC) == 0) { + for (;;) { +//@_rDN_Lp_1: + // Only execute this bit first the first time into the loop + if (!firstLoop) { + ROR(eax, 2); + ecx += 2; + eax = (eax & 0xffffff00) | *esi; + + if ((eax & 0xC) != 0) + break; + } + firstLoop = false; + +//@_rD2nd_1: + ROR(eax, 2); // Save bi-bit into hi 2 bits + ecx += 2; // and increase bit-shifter + // Shift another 2 bits to get hi nibble + eax = (eax & 0xffffff00) | ((eax & 0xff) >> 2); + NEXT_BYTE(esi); + + if ((eax & 0xC) != 0) { + flag = true; + ROL(eax, ecx); + break; + } + } + } else if (loopCtr != 0) { + flag = edx != 4; + } + + if (flag) { +//@_rdNum__1: + edx = 4; // offset rDNum_Lo ; Next nibble is a 'lo' + } else { +// @_rDNum_1 + NibbleHi = (uint8)eax; + edx = 0; // offset rDNum_Hi ; Next nibble is a 'hi' (reserved) + eax &= 0xffffff0f; + NEXT_BYTE(esi); + ROL(eax, ecx); + } +//rDN_1: +//@_rD_or_R: + bool actionFlag = (eax & 1) != 0; + eax >>= 1; + ecx = eax - 1; + + // Move to next loop index + if (++loopCtr == 4) loopCtr = 1; + + if (actionFlag) { + // Adjust loopCtr to fall into the correct processing case + loopCtr = loopCtr % 3 + 1; + } + + switch (loopCtr) { + case 1: + // @_rDelta: + saved_esi = esi; // Save the source pointer + esi = edi + ebx; // Point it to existing data + + while (ecx > 0) { + *edi = *esi; + NEXT_BYTE(esi); + NEXT_BYTE(edi); + --ecx; + } + + esi = saved_esi; + break; + + case 2: + // @_rRaw + // Copy data from source to dest + while (ecx > 0) { + *edi = *esi; + NEXT_BYTE(esi); + NEXT_BYTE(edi); + --ecx; + } + break; + + case 3: + // @_rRun + // Repeating run of data + eax = forwardDirection ? *(edi - 1) : *(edi + 1); + + while (ecx > 0) { + *edi = (uint8)eax; + NEXT_BYTE(edi); + --ecx; + } + break; + default: + break; + } + + if (edi == ebp) + break; // Exit if complete + + eax = 0; + } +} + +static void InitBMV(byte *memoryBuffer) { + // Clear the two extra 'off-screen' rows + memset(memoryBuffer, 0, SCREEN_WIDE); + memset(memoryBuffer + SCREEN_WIDE * (SCREEN_HIGH + 1), 0, SCREEN_WIDE); + + if (audioStream) { + _vm->_mixer->stopHandle(audioHandle); + + delete audioStream; + audioStream = 0; + } + + // Set the screen beginning to the second line (ie. past the off-screen line) + ScreenBeg = memoryBuffer + SCREEN_WIDTH; + Au_Prev1 = Au_Prev2 = 0; +} + +void PrepAudio(const byte *sourceData, int blobCount, byte *destPtr) { + uint16 dx1 = Au_Prev1; + uint16 dx2 = Au_Prev2; + + uint16 *destP = (uint16 *) destPtr; + int8 *srcP = (int8 *) sourceData; + + // Blob Loop + while (blobCount-- > 0) { + uint32 ebx = (uint8) *srcP++; + uint32 ebp = ebx & 0x1E; + + int blobSize = SAM_P_BLOB / 2; + + ebx = (((ebx & 0x0F) << 4) | ((ebx & 0xF0) >> 4)) & 0x1E; + + ebp = Au_DecTable[ebp >> 1]; + ebx = Au_DecTable[ebx >> 1]; + + // Inner loop + while (blobSize-- > 0) { + uint32 s1 = (((int32) *srcP++) * ((int32) ebp)) >> 5; + uint32 s2 = (((int32) *srcP++) * ((int32) ebx)) >> 5; + + dx1 += s1 & 0xFFFF; + dx2 += s2 & 0xFFFF; + + *destP++ = TO_BE_16(dx1); + *destP++ = TO_BE_16(dx2); + } + } + + Au_Prev1 = dx1; + Au_Prev2 = dx2; +} + +//----------------- BMV FUNCTIONS ---------------------------- + +static bool MaintainBuffer(void); + + +/** + * Called when a packet contains a palette field. + * Build a COLORREF array and queue it to the DAC. + */ +static void MoviePalette(int paletteOffset) { + int i; + byte *r; + + r = bigBuffer + paletteOffset; + + for (i = 0; i < 256; i++, r += 3) { + moviePal[i] = RGB(*r, *(r + 1), *(r + 2)); + } + + UpdateDACqueue(1, 255, &moviePal[1]); + + // Don't clobber talk + if (talkColour != 0) + SetTextPal(talkColour); +} + +static void InitialiseMovieSound() { + audioStream = + Audio::makeAppendableAudioStream(22050, + Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_STEREO); + audioStarted = false; +} + +static void StartMovieSound() { +} + +static void FinishMovieSound() { + if (audioStream) { + _vm->_mixer->stopHandle(audioHandle); + + delete audioStream; + audioStream = 0; + } +} + +/** + * Called when a packet contains an audio field. + */ +static void MovieAudio(int audioOffset, int blobs) { + if (audioOffset == 0 && blobs == 0) + blobs = 57; + + byte *data = new byte[blobs * 128]; + + if (audioOffset != 0) + PrepAudio(bigBuffer+audioOffset, blobs, data); + else + memset(data, 0, blobs * 128); + + audioStream->queueBuffer(data, blobs * 128); + + if (currentSoundFrame == ADVANCE_SOUND) { + if (!audioStarted) { + _vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, + &audioHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume, 0, false); + audioStarted = true; + } + } +} + +/*-----------------------------------------------------*\ +|-------------------------------------------------------| +\*-----------------------------------------------------*/ + +void FettleMovieText(void) { + int i; + + bIsText = false; + + for (i = 0; i < 2; i++) { + if (texts[i].pText) { + if (currentFrame > texts[i].dieFrame) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText); + texts[i].pText = NULL; + } else { + MultiForceRedraw(texts[i].pText); + bIsText = true; + } + } + } +} + +/*-----------------------------------------------------*\ +|-------------------------------------------------------| +\*-----------------------------------------------------*/ + +void BmvDrawText(bool bDraw) { + int w, h, x, y; + + for (int i = 0; i < 2; i++) { + if (texts[i].pText) { + x = MultiLeftmost(texts[i].pText); + y = MultiHighest(texts[i].pText); + w = MIN(MultiRightmost(texts[i].pText) + 1, (int)SCREEN_WIDTH) - x; + h = MIN(MultiLowest(texts[i].pText) + 1, SCREEN_HIGH) - y; + + const byte *src = ScreenBeg + (y * SCREEN_WIDTH) + x; + byte *dest = (byte *)_vm->screen().getBasePtr(x, y); + + for (int j = 0; j < h; j++, dest += SCREEN_WIDTH, src += SCREEN_WIDTH) { + memcpy(dest, src, w); + } + + if (bDraw) { + Common::Point ptWin; + Common::Rect rcPlayClip; + + ptWin.x = ptWin.y = 0; + rcPlayClip.left = x; + rcPlayClip.top = y; + rcPlayClip.right = x+w; + rcPlayClip.bottom = y+h; + UpdateClipRect(GetPlayfieldList(FIELD_STATUS), &ptWin, &rcPlayClip); + } + } + } +} + +/*-----------------------------------------------------*\ +|-------------------------------------------------------| +\*-----------------------------------------------------*/ + +void MovieText(CORO_PARAM, int stringId, int x, int y, int fontId, COLORREF *pTalkColour, int duration) { + SCNHANDLE hFont; + int index; + + if (fontId == 1) { + // It's a 'print' + + hFont = GetTagFontHandle(); + index = 0; + } else { + // It's a 'talk' + + if (pTalkColour != NULL) + SetTextPal(*pTalkColour); + hFont = GetTalkFontHandle(); + index = 1; + } + + if (texts[index].pText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[index].pText); + + LoadSubString(stringId, 0, TextBufferAddr(), TBUFSZ); + + texts[index].dieFrame = currentFrame + duration; + texts[index].pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), + 0, + x, y, + hFont, + TXT_CENTRE, 0); + KeepOnScreen(texts[index].pText, &x, &y); +} + +/** + * Called when a packet contains a command field. + */ +static int MovieCommand(char cmd, int commandOffset) { + if (cmd & CD_PRINT) { + PPRINT_CMD pCmd; + + pCmd = (PPRINT_CMD)(bigBuffer + commandOffset); + + MovieText(nullContext, pCmd->stringId, + pCmd->x, + pCmd->y, + pCmd->fontId, + NULL, + pCmd->duration); + + return sz_CMD_PRINT_pkt; + } else { + if (bSubtitles) { + PTALK_CMD pCmd; + + pCmd = (PTALK_CMD)(bigBuffer + commandOffset); + talkColour = RGB(pCmd->r, pCmd->g, pCmd->b); + + MovieText(nullContext, pCmd->stringId, + pCmd->x, + pCmd->y, + 0, + &talkColour, + pCmd->duration); + } + return sz_CMD_TALK_pkt; + } +} + +/** + * Called from PlayMovie() in tinlib.cpp + * Kicks off the playback of a movie, and waits around + * until it's finished. + */ +void PlayBMV(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(!bMovieOn); + + strcpy(szMovieFile, (char *)LockMem(hFileStem)); + strcat(szMovieFile, BMOVIE_EXTENSION); + + assert(strlen(szMovieFile) <= 12); + + bMovieOn = true; + bAbort = false; + bmvEscape = myEscape; + + do { + CORO_SLEEP(1); + } while (bMovieOn); + + CORO_END_CODE; +} + +/** + * Given a packet offset, calculates the offset of the + * next packet. The packet may not yet exist, and the + *return value may be off the end of bigBuffer. + */ +static int FollowingPacket(int thisPacket, bool bReallyImportant) { + unsigned char *data; + int nextSlot, length; + + // Set pointer to thisPacket's data + data = bigBuffer + thisPacket; + + switch (*data) { + case CD_SLOT_NOP: + nextSlot = thisPacket/SLOT_SIZE; + if (thisPacket%SLOT_SIZE) + nextSlot++; + + return nextSlot * SLOT_SIZE; + + case CD_LE_FIN: + return -1; + + default: + // Following 3 bytes are the length + if (bReallyImportant) { + // wrapped round or at least 3 bytes + assert(((nextReadSlot * SLOT_SIZE) < thisPacket) || + ((thisPacket + 3) < (nextReadSlot * SLOT_SIZE))); + + if ((nextReadSlot * SLOT_SIZE >= thisPacket) && + ((thisPacket + 3) >= nextReadSlot*SLOT_SIZE)) { + // MaintainBuffer calls this back, but with false + MaintainBuffer(); + } + } else { + // not wrapped and not 3 bytes + if (nextReadSlot*SLOT_SIZE >= thisPacket && thisPacket+3 >= nextReadSlot*SLOT_SIZE) + return thisPacket + 3; + } + length = *(int32 *)(bigBuffer + thisPacket + 1); + length &= 0x00ffffff; + return thisPacket + length + 4; + } +} + +/** + * Called from the foreground when starting playback of a movie. + */ +static void LoadSlots(int number) { + int nextOffset; + + assert(number + nextReadSlot < NUM_SLOTS); + + if (stream.read(bigBuffer + nextReadSlot*SLOT_SIZE, number * SLOT_SIZE) != + (uint32)(number * SLOT_SIZE)) { + int possibleSlots; + + // May be a short file + possibleSlots = stream.size() / SLOT_SIZE; + if ((number + nextReadSlot) > possibleSlots) { + bFileEnd = true; + nextReadSlot = possibleSlots; + } else + error(FILE_IS_CORRUPT, szMovieFile); + } + + nextReadSlot += number; + + nextOffset = FollowingPacket(nextUseOffset, true); + while (nextOffset < nextReadSlot*SLOT_SIZE + && nextOffset != -1) { + numAdvancePackets++; + mostFutureOffset = nextOffset; + nextOffset = FollowingPacket(mostFutureOffset, false); + } +} + +/** + * Called from the foreground when starting playback of a movie. + */ +static void InitialiseBMV(void) { + if (!stream.open(szMovieFile)) + error(CANNOT_FIND_FILE, szMovieFile); + + // Grab the data buffer + bigBuffer = (byte *)malloc(NUM_SLOTS * SLOT_SIZE); + if (bigBuffer == NULL) + error(NO_MEM, "FMV data buffer\n"); + + // Screen buffer (2 lines more than screen + screenBuffer = (byte *)malloc(SCREEN_WIDTH * (SCREEN_HIGH + 2)); + if (screenBuffer == NULL) + error(NO_MEM, "FMV screen buffer\n"); + + // Pass the sceen buffer to the decompresser + InitBMV(screenBuffer); + + // Initialise some stuff + nextUseOffset = 0; + nextSoundOffset = 0; + wrapUseOffset = -1; + mostFutureOffset = 0; + currentFrame = 0; + currentSoundFrame = 0; + numAdvancePackets = 0; + nextReadSlot = 0; + bFileEnd = false; + blobsInBuffer = 0; + memset(texts, 0, sizeof(texts)); + talkColour = 0; + bigProblemCount = 0; + + movieTick = 0; + + bIsText = false; + +// memset(&rr, 0, sizeof(rr)); + + // Prefetch data + LoadSlots(PREFETCH); + + while (numAdvancePackets < ADVANCE_SOUND) + LoadSlots(1); + + // Initialise the sound channel + InitialiseMovieSound(); +} + +/** + * Called from the foreground when ending playback of a movie. + */ +void FinishBMV(void) { + int i; + + // Notify the sound channel + FinishMovieSound(); + + // Close the file stream + if (stream.isOpen()) + stream.close(); + + // Release the data buffer + if (bigBuffer != NULL) { + free(bigBuffer); + bigBuffer = NULL; + } + + // Release the screen buffer + if (screenBuffer != NULL) { + free(screenBuffer); + screenBuffer = NULL; + } + + // Ditch any text objects + for (i = 0; i < 2; i++) { + if (texts[i].pText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText); + texts[i].pText = NULL; + } + } + bMovieOn = false; + + nextMovieTime = 0; + + // Test for 'twixt-movie glitch + ClearScreen(); +} + +/** + * MaintainBuffer() + */ +static bool MaintainBuffer(void) { + int nextOffset; + + // No action if the file is all read + if (bFileEnd == true) + return false; + + // See if next complete packet exists + // and if so, if it will fit in the top of the buffer + nextOffset = FollowingPacket(mostFutureOffset, false); + if (nextOffset == -1) { + // No following packets + return false; + } else if (nextOffset > NUM_SLOTS * SLOT_SIZE) { + // The current unfinished packet will not fit + // Copy this slot to slot 0 + + // Not if we're still using it!!! + // Or, indeed, if the player is still lagging + if (nextUseOffset < SLOT_SIZE || nextUseOffset > mostFutureOffset) { + // Slot 0 is still in use, buffer is full! + return false; + } + + // Tell data player where to make the jump + wrapUseOffset = mostFutureOffset; + + // mostFuture Offset is now in slot 0 + mostFutureOffset %= SLOT_SIZE; + + // Copy the data we already have for unfinished packet + memcpy(bigBuffer + mostFutureOffset, + bigBuffer + wrapUseOffset, + SLOT_SIZE - mostFutureOffset); + + // Next read is into slot 1 + nextReadSlot = 1; + } + + if (nextReadSlot == NUM_SLOTS) { + // Want to go to slot zero, wait if still in use + if (nextUseOffset < SLOT_SIZE) { + // Slot 0 is still in use, buffer is full! + return false; + } + + // nextOffset must be the buffer size + assert(nextOffset == NUM_SLOTS*SLOT_SIZE); + + // wrapUseOffset must not be set + assert(wrapUseOffset == -1); + wrapUseOffset = nextOffset; + + nextReadSlot = 0; + mostFutureOffset = 0; + } + + // Don't overwrite unused data + if (nextUseOffset / SLOT_SIZE == nextReadSlot) { + // Buffer is full! + return false; + } + + if (stream.read(bigBuffer + nextReadSlot * SLOT_SIZE, SLOT_SIZE) != SLOT_SIZE) { + bFileEnd = true; + } + + // Read next slot next time + nextReadSlot++; + + // Find new mostFutureOffset + nextOffset = FollowingPacket(mostFutureOffset, false); + while (nextOffset < nextReadSlot*SLOT_SIZE + && nextOffset != -1) { + numAdvancePackets++; + mostFutureOffset = nextOffset; + nextOffset = FollowingPacket(mostFutureOffset, false); + } + + // New test feature for e.g. short files + if (bFileEnd && *(bigBuffer+mostFutureOffset) != CD_LE_FIN) + bAbort = true; + + return true; +} + +/** + * DoBMVFrame + */ +static bool DoBMVFrame(void) { + unsigned char *data; + int graphOffset, length; + signed short xscr; + + if (nextUseOffset == wrapUseOffset) { + nextUseOffset %= SLOT_SIZE; + } + + while (nextUseOffset == mostFutureOffset) { + data = bigBuffer + nextUseOffset; + if (*data != CD_LE_FIN) { + // Don't get stuck in an infinite loop + if (!MaintainBuffer()) { + FinishBMV(); + return false; + } + + if (nextUseOffset == wrapUseOffset) { + nextUseOffset %= SLOT_SIZE; + } + } else + break; + } + + // Set pointer to data + data = bigBuffer + nextUseOffset; + + // If still at most Future, it must be last + if (nextUseOffset == mostFutureOffset) { + assert(*data == CD_LE_FIN); + } + + switch (*data) { + case CD_SLOT_NOP: + nextUseOffset = FollowingPacket(nextUseOffset, true); + if (nextUseOffset == wrapUseOffset) { + nextUseOffset %= SLOT_SIZE; + wrapUseOffset = -1; + } + numAdvancePackets--; + return false; + + case CD_LE_FIN: + FinishBMV(); + numAdvancePackets--; + return true; + + default: + length = *(int *)(data + 1); + length &= 0x00ffffff; + + graphOffset = nextUseOffset + 4; // Skip command byte and length + + if (*data & CD_AUDIO) { + if (bOldAudio) { + graphOffset += sz_AUDIO_pkt; // Skip audio data + length -= sz_AUDIO_pkt; + } else { + int blobs; + + blobs = *(bigBuffer + graphOffset); + blobs *= SZ_C_BLOB; + graphOffset += (blobs + 1); + length -= (blobs + 1); + } + } + + if (*data & CD_CMND) { + int cmdLen; + + // Process command and skip data + cmdLen = MovieCommand(*data, graphOffset); + + graphOffset += cmdLen; + length -= cmdLen; + } + + if (*data & CD_CMAP) { + MoviePalette(graphOffset); + graphOffset += sz_CMAP_pkt; // Skip palette data + length -= sz_CMAP_pkt; + } + + if (*data & CD_XSCR) { + xscr = *(signed short *)(bigBuffer + graphOffset); + graphOffset += sz_XSCR_pkt; // Skip scroll offset + length -= sz_XSCR_pkt; + } else if (*data & BIT0) + xscr = -640; + else + xscr = 0; + + PrepBMV(bigBuffer + graphOffset, length, xscr); + + currentFrame++; + numAdvancePackets--; + + nextUseOffset = FollowingPacket(nextUseOffset, true); + if (nextUseOffset == wrapUseOffset) { + nextUseOffset %= SLOT_SIZE; + wrapUseOffset = -1; + } + return true; + } +} + +/** + * DoSoundFrame + */ +static bool DoSoundFrame(void) { + unsigned char *data; + int graphOffset; + + if (nextSoundOffset == wrapUseOffset) { + nextSoundOffset %= SLOT_SIZE; + } + + // Make sure the full slot is here + while (nextSoundOffset == mostFutureOffset) { + data = bigBuffer + nextSoundOffset; + if (*data != CD_LE_FIN) { + // Don't get stuck in an infinite loop + if (!MaintainBuffer()) { + if (!bOldAudio) + MovieAudio(0, 0); + currentSoundFrame++; + return false; + } + + if (nextSoundOffset == wrapUseOffset) { + nextSoundOffset %= SLOT_SIZE; + } + } else + break; + } + + // Set pointer to data + data = bigBuffer + nextSoundOffset; + + // If still at most Future, it must be last + if (nextSoundOffset == mostFutureOffset) { + assert(*data == CD_LE_FIN); + } + + switch (*data) { + case CD_SLOT_NOP: + nextSoundOffset = FollowingPacket(nextSoundOffset, true); + if (nextSoundOffset == wrapUseOffset) { + nextSoundOffset %= SLOT_SIZE; + } + return false; + + case CD_LE_FIN: + if (!bOldAudio) + MovieAudio(0, 0); + currentSoundFrame++; + return true; + + default: + if (*data & CD_AUDIO) { + graphOffset = nextSoundOffset + 4; // Skip command byte and length + + if (!bOldAudio) { + int blobs = *(bigBuffer + graphOffset); + MovieAudio(graphOffset+1, blobs); + } + } else { + if (!bOldAudio) + MovieAudio(0, 0); + } + + nextSoundOffset = FollowingPacket(nextSoundOffset, false); + if (nextSoundOffset == wrapUseOffset) { + nextSoundOffset %= SLOT_SIZE; + } + currentSoundFrame++; + return true; + } + + return true; +} + +/** + * CopyMovieToScreen + */ +void CopyMovieToScreen(void) { + // Not if not up and running yet! + if (!screenBuffer || (currentFrame == 0)) { + ForceEntireRedraw(); + DrawBackgnd(); + return; + } + + // The movie surface is slightly less high than the output screen (429 rows versus 432). + // Because of this, there's some extra line clearing above and below the displayed area + int yStart = (SCREEN_HEIGHT - SCREEN_HIGH) / 2; + memset(_vm->screen().getBasePtr(0, 0), 0, yStart * SCREEN_WIDTH); + memcpy(_vm->screen().getBasePtr(0, yStart), ScreenBeg, SCREEN_WIDTH * SCREEN_HIGH); + memset(_vm->screen().getBasePtr(0, yStart + SCREEN_HIGH), 0, + (SCREEN_HEIGHT - SCREEN_HIGH - yStart) * SCREEN_WIDTH); + + BmvDrawText(true); + PalettesToVideoDAC(); // Keep palette up-to-date + UpdateScreenRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); + BmvDrawText(false); +} + +/** + * LookAtBuffers + */ +static void LookAtBuffers(void) { + static int junk; + int i; + + if (bigBuffer) { + for (i = 0; i < NUM_SLOTS; i++) + junk += bigBuffer[i*SLOT_SIZE]; + } +} + +/** + * Handles playback of any active movie. Called from the foreground 24 times a second. + */ +void FettleBMV(void) { + static int nextMaintain = 0; + + int refFrame; + // Tick counter needs to be incrementing at a 24Hz rate + int tick = movieTick++; + + if (!bMovieOn) + return; + + // Escape the rest if appropriate + if (bAbort || (bmvEscape && bmvEscape != GetEscEvents())) { + FinishBMV(); + return; + } + + LookAtBuffers(); + + if (!stream.isOpen()) { + int i; + + // First time in with this movie + + InitialiseBMV(); + + for (i = 0; i < ADVANCE_SOUND;) { + if (DoSoundFrame()) + i++; + } + startTick = -ONE_SECOND / 4; // 1/4 second + return; + } + + if (startTick < 0) { + startTick++; + return; + } + if (startTick == 0) { + startTick = tick; + nextMaintain = startTick + 1; + StartMovieSound(); + } + + nextMovieTime = g_system->getMillis() + 41; + + FettleMovieText(); + + if (bigProblemCount < PT_A) { + refFrame = currentSoundFrame; + + while (currentSoundFrame < ((tick+1-startTick)/2 + ADVANCE_SOUND) && bMovieOn) { + if (currentSoundFrame == refFrame+PT_B) + break; + + DoSoundFrame(); + } + } + + // Time to process a frame (or maybe more) + if (bigProblemCount < PT_A) { + refFrame = currentFrame; + + while ((currentFrame < (tick-startTick)/2) && bMovieOn) { + DoBMVFrame(); + + if (currentFrame == refFrame+PT_B) { + bigProblemCount++; + + if (bigProblemCount == PT_A) { + startTick = tick-(2*currentFrame); + bigProblemCount = 0; + } + break; + } + } + if (currentFrame == refFrame || currentFrame <= refFrame+3) { + bigProblemCount = 0; + } + } else { + while (currentFrame < (tick-startTick)/2 && bMovieOn) { + DoBMVFrame(); + } + } + + if (tick >= nextMaintain || numAdvancePackets < SUBSEQUENT_SOUND) { + MaintainBuffer(); + nextMaintain = tick + 2; + } +} + +/** + * Returns true if a movie is playing. + */ +bool MoviePlaying(void) { + return bMovieOn; +} + +/** + * Returns the audio lag in ms + */ +int32 MovieAudioLag(void) { + if (!bMovieOn || !audioStream) + return 0; + + // Calculate lag + int32 playLength = (movieTick - startTick - 1) * ((((uint32) 1000) << 10) / 24); + return (playLength - (audioStream->getTotalPlayTime() << 10)) >> 10; +} + +uint32 NextMovieTime(void) { + return nextMovieTime; +} + +void AbortMovie(void) { + bAbort = true; +} + +void SlowMovieDown(void) { + bigProblemCount = 0; + + if (currentFrame < (nowTick-startTick)/2 && bMovieOn) { + startTick = nowTick - 2*currentFrame; + } else + startTick += 2; +} + +void SpeedMovieUp(void) { + if (!bigProblemCount) { + startTick -= 2; + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp index 803d2231e4..c6d774de7a 100644 --- a/engines/tinsel/config.cpp +++ b/engines/tinsel/config.cpp @@ -24,6 +24,8 @@ * This file contains configuration functionality */ +//#define USE_3FLAGS 1 + #include "tinsel/config.h" #include "tinsel/dw.h" #include "tinsel/sound.h" @@ -39,7 +41,7 @@ namespace Tinsel { //----------------- GLOBAL GLOBAL DATA -------------------- int dclickSpeed = DOUBLE_CLICK_TIME; -int volMidi = Audio::Mixer::kMaxChannelVolume; +int volMusic = Audio::Mixer::kMaxChannelVolume; int volSound = Audio::Mixer::kMaxChannelVolume; int volVoice = Audio::Mixer::kMaxChannelVolume; int speedText = DEFTEXTSPEED; @@ -49,21 +51,18 @@ LANGUAGE g_language = TXT_ENGLISH; int bAmerica = 0; -// Shouldn't really be here, but time is short... -bool bNoBlocking; /** * Write settings to config manager and flush the config file to disk. */ void WriteConfig(void) { ConfMan.setInt("dclick_speed", dclickSpeed); - ConfMan.setInt("music_volume", volMidi); + ConfMan.setInt("music_volume", volMusic); ConfMan.setInt("sfx_volume", volSound); ConfMan.setInt("speech_volume", volVoice); ConfMan.setInt("talkspeed", (speedText * 255) / 100); ConfMan.setBool("subtitles", bSubtitles); //ConfMan.setBool("swap_buttons", bSwapButtons ? 1 : 0); - //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB // Store language for multilingual versions if ((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)) { @@ -92,16 +91,14 @@ void WriteConfig(void) { ConfMan.flushToDisk(); } -/*---------------------------------------------------------------------*\ -| ReadConfig() | -|-----------------------------------------------------------------------| -| -\*---------------------------------------------------------------------*/ +/** + * Read configuration settings from the config file into memory + */ void ReadConfig(void) { if (ConfMan.hasKey("dclick_speed")) dclickSpeed = ConfMan.getInt("dclick_speed"); - volMidi = ConfMan.getInt("music_volume"); + volMusic = ConfMan.getInt("music_volume"); volSound = ConfMan.getInt("sfx_volume"); volVoice = ConfMan.getInt("speech_volume"); diff --git a/engines/tinsel/config.h b/engines/tinsel/config.h index fc85f0abe0..a02b763afb 100644 --- a/engines/tinsel/config.h +++ b/engines/tinsel/config.h @@ -37,7 +37,7 @@ enum { }; extern int dclickSpeed; -extern int volMidi; +extern int volMusic; extern int volSound; extern int volVoice; extern int speedText; @@ -51,10 +51,6 @@ 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 index e0292735bb..8cdbd44b19 100644 --- a/engines/tinsel/coroutine.h +++ b/engines/tinsel/coroutine.h @@ -70,6 +70,7 @@ struct CoroBaseContext { typedef CoroBaseContext *CoroContext; +extern CoroContext nullContext; /** * Wrapper class which holds a pointer to a pointer to a CoroBaseContext. @@ -101,6 +102,7 @@ public: #define CORO_END_CONTEXT(x) } *x = (CoroContextTag *)coroParam #define CORO_BEGIN_CODE(x) \ + if (&coroParam == &nullContext) assert(!nullContext);\ if (!x) {coroParam = x = new CoroContextTag();}\ assert(coroParam);\ assert(coroParam->_sleep >= 0);\ @@ -109,17 +111,22 @@ public: switch(coroParam->_line) { case 0:; #define CORO_END_CODE \ + if (&coroParam == &nullContext) nullContext = NULL; \ } -#define CORO_SLEEP(delay) \ - do {\ +#define CORO_SLEEP(delay) do {\ coroParam->_line = __LINE__;\ coroParam->_sleep = delay;\ + assert(&coroParam != &nullContext);\ return; case __LINE__:;\ } while (0) +#define CORO_GIVE_WAY do { g_scheduler->giveWay(); CORO_SLEEP(1); } while (0) +#define CORO_RESCHEDULE do { g_scheduler->reschedule(); CORO_SLEEP(1); } while (0) + /** Stop the currently running coroutine */ -#define CORO_KILL_SELF() do { coroParam->_sleep = -1; return; } while(0) +#define CORO_KILL_SELF() \ + do { if (&coroParam != &nullContext) { coroParam->_sleep = -1; } return; } while(0) /** Invoke another coroutine */ #define CORO_INVOKE_ARGS(subCoro, ARGS) \ @@ -130,9 +137,22 @@ public: subCoro ARGS;\ if (!coroParam->_subctx) break;\ coroParam->_sleep = coroParam->_subctx->_sleep;\ + assert(&coroParam != &nullContext);\ return; case __LINE__:;\ } while(1);\ } while (0) +#define CORO_INVOKE_ARGS_V(subCoro, RESULT, ARGS) \ + do {\ + coroParam->_line = __LINE__;\ + coroParam->_subctx = 0;\ + do {\ + subCoro ARGS;\ + if (!coroParam->_subctx) break;\ + coroParam->_sleep = coroParam->_subctx->_sleep;\ + assert(&coroParam != &nullContext);\ + return RESULT; case __LINE__:;\ + } while(1);\ + } while (0) #define CORO_INVOKE_0(subCoroutine) \ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX)) @@ -140,6 +160,8 @@ public: 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)) } // end of namespace Tinsel diff --git a/engines/tinsel/cursor.cpp b/engines/tinsel/cursor.cpp index f933b2dd79..719b55d1bb 100644 --- a/engines/tinsel/cursor.cpp +++ b/engines/tinsel/cursor.cpp @@ -34,11 +34,14 @@ #include "tinsel/film.h" #include "tinsel/graphics.h" #include "tinsel/handle.h" -#include "tinsel/inventory.h" +#include "tinsel/dialogs.h" #include "tinsel/multiobj.h" // multi-part object defintions etc. #include "tinsel/object.h" #include "tinsel/pid.h" +#include "tinsel/play.h" #include "tinsel/sched.h" +#include "tinsel/sysvar.h" +#include "tinsel/text.h" #include "tinsel/timers.h" // For ONE_SECOND constant #include "tinsel/tinlib.h" // resetidletime() #include "tinsel/tinsel.h" // For engine access @@ -54,20 +57,21 @@ namespace Tinsel { //----------------- LOCAL GLOBAL DATA -------------------- -static OBJECT *McurObj = 0; // Main cursor object -static OBJECT *AcurObj = 0; // Auxiliary cursor object +static OBJECT *McurObj = NULL; // Main cursor object +static OBJECT *AcurObj = NULL; // 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 bHiddenCursor = false; // Set when cursor is hidden static bool bTempNoTrailers = false; // Set when cursor trails are hidden +static bool bTempHide = false; // Set when cursor is 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 SCNHANDLE hCursorFilm = 0; // Handle to cursor reel data static int numTrails = 0; static int nextTrail = 0; @@ -76,8 +80,11 @@ 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 uint16 restart = 0; // When main cursor has been bWhoa-ed, it waits + // for this to be set to 0x8000. + // Main cursor sets all the bits after a re-start + // - each cursor trail examines it's own bit + // to trigger a trail restart. static short ACoX = 0, ACoY = 0; // Auxillary cursor image's animation offsets @@ -97,7 +104,7 @@ static int lastCursorX = 0, lastCursorY = 0; //----------------- FORWARD REFERENCES -------------------- -static void MoveCursor(void); +static void DoCursorMove(void); /** * Initialise and insert a cursor trail object, set its Z-pos, and hide @@ -117,9 +124,9 @@ static void InitCurTrailObj(int i, int x, int y) { 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()); + pim = GetImageFromFilm(hCursorFilm, i+1, &pfr, &pmi, &pfilm);// Get pointer to image + assert(BgPal()); // No background palette + pim->hImgPal = TO_LE_32(BgPal()); // Initialise and insert the object, set its Z-pos, and hide it ntrailData[i].trailObj = MultiInitObject(pmi); @@ -140,8 +147,8 @@ static bool GetDriverPosition(int *x, int *y) { *x = ptMouse.x; *y = ptMouse.y; - return(*x >= 0 && *x <= SCREEN_WIDTH-1 && - *y >= 0 && *y <= SCREEN_HEIGHT-1); + return(*x >= 0 && *x <= SCREEN_WIDTH - 1 && + *y >= 0 && *y <= SCREEN_HEIGHT - 1); } /** @@ -154,7 +161,7 @@ void AdjustCursorXY(int deltaX, int deltaY) { if (GetDriverPosition(&x, &y)) _vm->setMousePosition(Common::Point(x + deltaX, y + deltaY)); } - MoveCursor(); + DoCursorMove(); } /** @@ -170,7 +177,7 @@ void SetCursorXY(int newx, int newy) { if (GetDriverPosition(&x, &y)) _vm->setMousePosition(Common::Point(newx, newy)); - MoveCursor(); + DoCursorMove(); } /** @@ -181,7 +188,7 @@ void SetCursorScreenXY(int newx, int newy) { if (GetDriverPosition(&x, &y)) _vm->setMousePosition(Common::Point(newx, newy)); - MoveCursor(); + DoCursorMove(); } /** @@ -229,7 +236,7 @@ void RestoreMainCursor(void) { const FILM *pfilm; if (McurObj != NULL) { - pfilm = (const FILM *)LockMem(CursorHandle); + pfilm = (const FILM *)LockMem(hCursorFilm); InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfilm->reels->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); StepAnimScript(&McurAnim); @@ -281,6 +288,13 @@ void FreezeCursor(void) { bFrozenCursor = true; } +/** + * Freeze the cursor, or not. + */ +void DoFreezeCursor(bool bFreeze) { + bFrozenCursor = bFreeze; +} + /** * HideCursorTrails */ @@ -365,11 +379,12 @@ void SetAuxCursor(SCNHANDLE hFilm) { 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 + assert(BgPal()); // no background palette + pim->hImgPal = TO_LE_32(BgPal()); // Poke in the background palette ACoX = (short)(FROM_LE_16(pim->imgWidth)/2 - ((int16) FROM_LE_16(pim->anioffX))); - ACoY = (short)(FROM_LE_16(pim->imgHeight)/2 - ((int16) FROM_LE_16(pim->anioffY))); + ACoY = (short)((FROM_LE_16(pim->imgHeight) & ~C16_FLAG_MASK)/2 - + ((int16) FROM_LE_16(pim->anioffY))); // Initialise and insert the auxillary cursor object AcurObj = MultiInitObject(pmi); @@ -387,7 +402,7 @@ void SetAuxCursor(SCNHANDLE hFilm) { /** * MoveCursor */ -static void MoveCursor(void) { +static void DoCursorMove(void) { int startX, startY; Common::Point ptMouse; frac_t newX, newY; @@ -459,22 +474,30 @@ static void MoveCursor(void) { * Initialise cursor object. */ static void InitCurObj(void) { - const FILM *pfilm; + const FILM *pFilm; const FREEL *pfr; const MULTI_INIT *pmi; IMAGE *pim; - pim = GetImageFromFilm(CursorHandle, 0, &pfr, &pmi, &pfilm);// Get pointer to image - assert(BackPal()); // no background palette - pim->hImgPal = TO_LE_32(BackPal()); -//--- + if (TinselV2) { + pFilm = (const FILM *)LockMem(hCursorFilm); + pfr = (const FREEL *)&pFilm->reels[0]; + pmi = (MULTI_INIT *)LockMem(pfr->mobj); - AcurObj = NULL; // No auxillary cursor + PokeInPalette(pmi); + } else { + assert(BgPal()); // no background palette + + pim = GetImageFromFilm(hCursorFilm, 0, &pfr, &pmi, &pFilm);// Get pointer to image + pim->hImgPal = TO_LE_32(BgPal()); + + 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)); + InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pFilm->frate)); } /** @@ -486,7 +509,7 @@ static void InitCurPos(void) { lastCursorY = ptMouse.y; MultiSetZPosition(McurObj, Z_CURSOR); - MoveCursor(); + DoCursorMove(); MultiHideObject(McurObj); IterationSize = ITERATION_BASE; @@ -505,16 +528,16 @@ static void CursorStoppedCheck(CORO_PARAM) { // If scene is closing down if (bWhoa) { // ...wait for next scene start-up - while (!restart) + while (restart != 0x8000) CORO_SLEEP(1); // Re-initialise InitCurObj(); InitCurPos(); - InventoryIconCursor(); // May be holding something + InventoryIconCursor(false); // May be holding something // Re-start the cursor trails - restart = false; // set all bits + restart = (uint16)-1; // set all bits bWhoa = false; } CORO_END_CODE; @@ -530,15 +553,15 @@ void CursorProcess(CORO_PARAM, const void *) { CORO_BEGIN_CODE(_ctx); - while (!CursorHandle || !BackPal()) + while (!hCursorFilm || !BgPal()) CORO_SLEEP(1); InitCurObj(); InitCurPos(); - InventoryIconCursor(); // May be holding something + InventoryIconCursor(false); // May be holding something bWhoa = false; - restart = false; + restart = 0; while (1) { // allow rescheduling @@ -562,10 +585,10 @@ void CursorProcess(CORO_PARAM, const void *) { // Move the cursor as appropriate if (!bFrozenCursor) - MoveCursor(); + DoCursorMove(); // If the cursor should be hidden... - if (bHiddenCursor) { + if (bHiddenCursor || bTempHide) { // ...hide the cursor object(s) MultiHideObject(McurObj); if (AcurObj) @@ -595,9 +618,9 @@ void CursorProcess(CORO_PARAM, const void *) { void DwInitCursor(SCNHANDLE bfilm) { const FILM *pfilm; - CursorHandle = bfilm; + hCursorFilm = bfilm; - pfilm = (const FILM *)LockMem(CursorHandle); + pfilm = (const FILM *)LockMem(hCursorFilm); numTrails = FROM_LE_32(pfilm->numreels) - 1; assert(numTrails <= MAX_TRAILERS); @@ -607,6 +630,15 @@ void DwInitCursor(SCNHANDLE bfilm) { * DropCursor is called when a scene is closing down. */ void DropCursor(void) { + if (TinselV2) { + if (AcurObj) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), AcurObj); + if (McurObj) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), McurObj); + + restart = 0; + } + AcurObj = NULL; // No auxillary cursor McurObj = NULL; // No cursor object (imminently deleted elsewhere) bHiddenCursor = false; // Not hidden in next scene @@ -625,7 +657,7 @@ void DropCursor(void) { * RestartCursor is called when a new scene is starting up. */ void RestartCursor(void) { - restart = true; // Get the main cursor to re-initialise + restart = 0x8000; // Get the main cursor to re-initialise } /** @@ -639,10 +671,22 @@ void RebootCursor(void) { bHiddenCursor = bTempNoTrailers = bFrozenCursor = false; - CursorHandle = 0; + hCursorFilm = 0; bWhoa = false; - restart = false; + restart = 0; +} + +void StartCursorFollowed(void) { + DelAuxCursor(); + + if (!SysVar(SV_ENABLEPRINTCURSOR)) + bTempHide = true; +} + +void EndCursorFollowed(void) { + InventoryIconCursor(false); // May be holding something + bTempHide = false; } } // end of namespace Tinsel diff --git a/engines/tinsel/cursor.h b/engines/tinsel/cursor.h index 15349dda26..bf1f0d064b 100644 --- a/engines/tinsel/cursor.h +++ b/engines/tinsel/cursor.h @@ -42,6 +42,7 @@ void SetTempCursor(SCNHANDLE pScript); void DwHideCursor(void); void UnHideCursor(void); void FreezeCursor(void); +void DoFreezeCursor(bool bFreeze); void HideCursorTrails(void); void UnHideCursorTrails(void); void DelAuxCursor(void); @@ -50,6 +51,8 @@ void DwInitCursor(SCNHANDLE bfilm); void DropCursor(void); void RestartCursor(void); void RebootCursor(void); +void StartCursorFollowed(void); +void EndCursorFollowed(void); } // end of namespace Tinsel diff --git a/engines/tinsel/debugger.cpp b/engines/tinsel/debugger.cpp index dc37e6a9a1..3ade360445 100644 --- a/engines/tinsel/debugger.cpp +++ b/engines/tinsel/debugger.cpp @@ -25,7 +25,7 @@ #include "tinsel/tinsel.h" #include "tinsel/debugger.h" -#include "tinsel/inventory.h" +#include "tinsel/dialogs.h" #include "tinsel/pcode.h" #include "tinsel/scene.h" #include "tinsel/sound.h" diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp index 45cd903552..e56198c76c 100644 --- a/engines/tinsel/detection.cpp +++ b/engines/tinsel/detection.cpp @@ -31,7 +31,6 @@ #include "tinsel/tinsel.h" #include "tinsel/savescn.h" // needed by TinselMetaEngine::listSaves - namespace Tinsel { struct TinselGameDescription { @@ -68,7 +67,7 @@ uint16 TinselEngine::getVersion() const { static const PlainGameDescriptor tinselGames[] = { {"tinsel", "Tinsel engine game"}, {"dw", "Discworld"}, - {"dw2", "Discworld 2: Mortality Bytes!"}, + {"dw2", "Discworld 2: Missing Presumed ...!?"}, {0, 0} }; @@ -77,25 +76,15 @@ namespace Tinsel { static const TinselGameDescription gameDescriptions[] = { - // The DW1 demo was based on an older revision of the Tinsel engine - // than the one used in the released game. We call it Tinsel v0 as - // opposed to v1 which was used in the full retail version of DW. - - { // 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_V0, - }, + // Note: The following is the (hopefully) definitive list of version details: + // TINSEL_V0: Used only by the Discworld 1 demo - this used a more primitive version + // of the Tinsel engine and graphics compression, and isn't currently supported + // TINSEL_V1: There were two versions of the Discworld 1 game - the first used .GRA + // files, and the second used .SCN files. The second also provided some fixes to + // various script bugs and coding errors, but is still considered TINSEL_V1, + // as both game versions work equally well with the newer code. + // TINSEL_V2: The Discworld 2 game used this updated version of the Tinsel 1 engine, + // and as far as we know there aren't any variations of this engine. { { // This version has *.gra files @@ -112,49 +101,6 @@ static const TinselGameDescription gameDescriptions[] = { TINSEL_V1, }, - { // Multilingual floppy with *.gra files. - // Note: It contains no english subtitles. - // Reported on our forums. - { - "dw", - "Floppy", - { - {"dw.gra", 0, "c8808ccd988d603dd35dff42013ae7fd", 781656}, - {"french.txt", 0, NULL, -1}, - {"german.txt", 0, NULL, -1}, - {"italian.txt", 0, NULL, -1}, - {"spanish.txt", 0, NULL, -1}, - {NULL, 0, NULL, 0} - }, - Common::FR_FRA, - Common::kPlatformPC, - Common::ADGF_DROPLANGUAGE - }, - GID_DW1, - 0, - GF_FLOPPY | GF_USE_4FLAGS, - TINSEL_V1, - }, - - { // English CD. This version has *.gra files - { - "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_V1, - }, - { // Multilingual CD with english speech and *.gra files. // Note: It contains no english subtitles. { @@ -245,7 +191,7 @@ static const TinselGameDescription gameDescriptions[] = { TINSEL_V1, }, - { // English CD with SCN files + { // English CD v2 { "dw", "CD", @@ -265,7 +211,7 @@ static const TinselGameDescription gameDescriptions[] = { }, #if 0 - { // English Saturn CD. Not (yet?) supported + { // English Saturn CD { "dw", "CD", @@ -285,6 +231,25 @@ static const TinselGameDescription gameDescriptions[] = { }, #endif +// Currently disabled since it isn't really supported +#if 0 + { // 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_V0, + }, +#endif + { // German CD re-release "Neon Edition" // Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT { @@ -301,6 +266,44 @@ static const TinselGameDescription gameDescriptions[] = { TINSEL_V1, }, + { // Europen/Australian Discworld 2 release + { + "dw2", + "CD", + { + {"dw2.scn", 0, "c6d15ce9720a9d8fef06e6582dcf3f34", 103593}, + {"english1.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW2, + 0, + GF_CD | GF_SCNFILES, + TINSEL_V2, + }, + + { // German Discworld 2 re-release "Neon Edition" + { + "dw2", + "CD", + { + {"dw2.scn", 0, "c6d15ce9720a9d8fef06e6582dcf3f34", 103593}, + {"german1.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW2, + 0, + GF_CD | GF_SCNFILES, + TINSEL_V2, + }, + { AD_TABLE_END_MARKER, 0, 0, 0, 0 } }; @@ -334,25 +337,23 @@ public: } virtual const char *getCopyright() const { - // FIXME: Bad copyright string. - // Should be something like "Tinsel (C) Psygnosis" or so... ??? - return "Tinsel Engine"; + return "Tinsel (C) Psygnosis"; } virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const; virtual bool hasFeature(MetaEngineFeature f) const; virtual SaveStateList listSaves(const char *target) const; - virtual int getMaximumSaveSlot() const; }; bool TinselMetaEngine::hasFeature(MetaEngineFeature f) const { - return - (f == kSupportsListSaves); + return (f == kSupportsListSaves); } namespace Tinsel { + extern int getList(Common::SaveFileManager *saveFileMan, const Common::String &target); + } SaveStateList TinselMetaEngine::listSaves(const char *target) const { @@ -368,8 +369,6 @@ SaveStateList TinselMetaEngine::listSaves(const char *target) const { return saveList; } -int TinselMetaEngine::getMaximumSaveSlot() const { return 999; } - bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const { const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc; if (gd) { diff --git a/engines/tinsel/dialogs.cpp b/engines/tinsel/dialogs.cpp new file mode 100644 index 0000000000..cc844278fb --- /dev/null +++ b/engines/tinsel/dialogs.cpp @@ -0,0 +1,5736 @@ +/* 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/drives.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/heapmem.h" +#include "tinsel/dialogs.h" +#include "tinsel/multiobj.h" +#include "tinsel/music.h" +#include "tinsel/palette.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.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/sysvar.h" +#include "tinsel/text.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" // For engine access +#include "tinsel/token.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); +extern void DisablePointing(CORO_PARAM); +extern void EnablePointing(void); + +//----------------- LOCAL DEFINES -------------------- + +#define INV_PICKUP PLR_SLEFT // Local names +#define INV_LOOK PLR_SRIGHT // for button events +#define INV_ACTION PLR_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 hWinParts's reels + +enum PARTS_INDEX { + IX_SLIDE = 0, // Slider + IX_V26 = 1, + IX_V52 = 2, + IX_V78 = 3, + IX_V104 = 4, + IX_V130 = 5, + IX_H26 = 6, + IX_H52 = 7, + IX_H78 = 8, + IX_H104 = 9, + IX_H130 = 10, + IX_H156 = 11, + IX_H182 = 12, + IX_H208 = 13, + IX_H234 = 14, + IX_TL = 15, // Top left corner + IX_TR = 16, // Top right corner + IX_BL = 17, // Bottom left corner + IX_BR = 18, // Bottom right corner + + IX1_H25 = 19, + IX1_V11 = 20, + IX1_RTL = 21, // Re-sizing top left corner + IX1_RTR = 22, // Re-sizing top right corner + IX1_RBR = 23, // Re-sizing bottom right corner + IX1_CURLR = 24, // } + IX1_CURUD = 25, // } + IX1_CURDU = 26, // } Custom cursors + IX1_CURDD = 27, // } + IX1_CURUP = 28, // } + IX1_CURDOWN = 29, // } + IX1_MDGROOVE = 30, // 'Mixing desk' slider background + IX1_MDSLIDER= 34, // 'Mixing desk' slider + IX1_BLANK1 = 35, // + IX1_BLANK2 = 36, // + IX1_BLANK3 = 37, // + IX1_CIRCLE1 = 38, // + IX1_CIRCLE2 = 39, // + IX1_CROSS1 = 40, // + IX1_CROSS2 = 41, // + IX1_CROSS3 = 42, // + IX1_QUIT1 = 43, // + IX1_QUIT2 = 44, // + IX1_QUIT3 = 45, // + IX1_TICK1 = 46, // + IX1_TICK2 = 47, // + IX1_TICK3 = 48, // + IX1_NTR = 49, // New top right corner + + IX2_RTL = 19, // Re-sizing top left corner + IX2_RTR = 20, // Re-sizing top right corner + IX2_RBR = 21, // Re-sizing bottom right corner + IX2_CURLR = 22, // } + IX2_CURUD = 23, // } + IX2_CURDU = 24, // } Custom cursors + IX2_CURDD = 25, // } + IX2_MDGROOVE = 26, // 'Mixing desk' slider background + IX2_MDSLIDER = 27, // 'Mixing desk' slider + IX2_CIRCLE1 = 28, // + IX2_CIRCLE2 = 29, // + IX2_CROSS1 = 30, // + IX2_CROSS2 = 31, // + IX2_CROSS3 = 32, // + IX2_TICK1 = 33, // + IX2_TICK2 = 34, // + IX2_TICK3 = 35, // + IX2_NTR = 36, // New top right corner + IX2_TR4 = 37, + IX2_LEFT1 = 38, + IX2_LEFT2 = 39, + IX2_RIGHT1 = 40, + IX2_RIGHT2 = 41, + + T1_HOPEDFORREELS = 50, + T2_HOPEDFORREELS = 42 +}; + +// The following defines select the correct constant depending on Tinsel version +#define IX_CROSS1 (TinselV2 ? IX2_CROSS1 : IX1_CROSS1) +#define IX_CURDD (TinselV2 ? IX2_CURDD : IX1_CURDD) +#define IX_CURDU (TinselV2 ? IX2_CURDU : IX1_CURDU) +#define IX_CURLR (TinselV2 ? IX2_CURLR : IX1_CURLR) +#define IX_CURUD (TinselV2 ? IX2_CURUD : IX1_CURUD) +#define IX_CURUL (TinselV2 ? IX2_CURUL : IX1_CURUL) +#define IX_MDGROOVE (TinselV2 ? IX2_MDGROOVE : IX1_MDGROOVE) +#define IX_MDSLIDER (TinselV2 ? IX2_MDSLIDER : IX1_MDSLIDER) +#define IX_NTR (TinselV2 ? IX2_NTR : IX1_NTR) +#define IX_RBR (TinselV2 ? IX2_RBR : IX1_RBR) +#define IX_RTL (TinselV2 ? IX2_RTL : IX1_RTL) +#define IX_RTR (TinselV2 ? IX2_RTR : IX1_RTR) +#define IX_TICK1 (TinselV2 ? IX2_TICK1 : IX1_TICK1) + + + +#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 (TinselV2 ? 160 : 150) // Max in an inventory +#define MAX_ININV_TOT 160 +#define MAX_PERMICONS 10 // Max permanent conversation icons + +#define MAXHICONS 10 // Max dimensions of +#define MAXVICONS 6 // an inventory window + +#define ITEM_WIDTH (TinselV2 ? 50 : 25) // Dimensions of an icon +#define ITEM_HEIGHT (TinselV2 ? 50 : 25) // +#define I_SEPARATION (TinselV2 ? 2 : 1) // Item separation + +#define NM_TOFF 11 // Title text Y offset from top +#define NM_TBT (TinselV2 ? 4 : 0) // Y, title box top +#define NM_TBB 33 +#define NM_LSX (TinselV2 ? 4 : 0) // X, left side +#define NM_BSY (TinselV2 ? -9 : - M_TH + 1) +#define NM_RSX (TinselV2 ? -9 : - M_SW + 1) +#define NM_SBL (-27) +#define NM_SLH (TinselV2 ? 11 : 5) // Slider height +#define NM_SLX (-11) // Slider X offset (from right) + +#define NM_BG_POS_X (TinselV2 ? 9 : 1) // } +#define NM_BG_POS_Y (TinselV2 ? 9 : 1) // } Offset of translucent rectangle +#define NM_BG_SIZ_X (TinselV2 ? -18 : -3) // } +#define NM_BG_SIZ_Y (TinselV2 ? -18 : -3) // } How much larger it is than edges + +#define NM_RS_T_INSET 3 +#define NM_RS_B_INSET 4 +#define NM_RS_L_INSET 3 +#define NM_RS_R_INSET 4 +#define NM_RS_THICKNESS 5 +#define NM_MOVE_AREA_B_Y 30 +#define NM_SLIDE_INSET (TinselV2 ? 18 : 15) // X offset (from right) of left of scroll region +#define NM_SLIDE_THICKNESS (TinselV2 ? 13 : 4) // thickness of scroll region +#define NM_UP_ARROW_TOP 34 // Y offset of top of up arrow +#define NM_UP_ARROW_BOTTOM 49 // Y offset of bottom of up arrow +#define NM_DN_ARROW_TOP 22 // Y offset (from bottom) of top of down arrow +#define NM_DN_ARROW_BOTTOM 5 // Y offset (from bottom) of bottom of down arrow + +#define MD_YBUTTOP (TinselV2 ? 2 : 9) +#define MD_YBUTBOT (TinselV2 ? 16 : 0) +#define MD_XLBUTL (TinselV2 ? 4 : 1) +#define MD_XLBUTR (TinselV2 ? 26 : 10) +#define MD_XRBUTL (TinselV2 ? 173 : 105) +#define MD_XRBUTR (TinselV2 ? 195 : 114) +#define ROTX1 60 // Rotate button's offsets from the centre + +// 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 + +#define MAX_NAME_RIGHT (TinselV2 ? 417 : 213) + +#define SLIDE_RANGE (TinselV2 ? 120 : 81) +#define SLIDE_MINX (TinselV2 ? 25 : 8) +#define SLIDE_MAXX (TinselV2 ? 25 + 120 : 8 + 81) + +#define MDTEXT_YOFF (TinselV2 ? -1 : 6) +#define MDTEXT_XOFF -4 +#define TOG2_YOFF -22 +#define ROT_YOFF 48 +#define TYOFF (TinselV2 ? 4 : 0) +#define FLAGX (-5) +#define FLAGY 4 + + +//----------------- 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 hWinParts = 0; // Window members and cursors' graphic data +static SCNHANDLE flagFilm = 0; // Window members and cursors' graphic data +static SCNHANDLE configStrings[20]; + +static INV_OBJECT *invObjects = NULL; // Inventory objects' data +static int numObjects = 0; // Number of inventory objects +static SCNHANDLE *invFilms = NULL; +static bool bNoLanguage = false; +static DIRECTION initialDirection; + +//----- 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 + +static SCNHANDLE heldFilm; + +struct INV_DEF { + + int MinHicons; // } + int MinVicons; // } Dimension limits + int MaxHicons; // } + int MaxVicons; // } + + int NoofHicons; // } + int NoofVicons; // } Current dimentsions + + int contents[160]; // 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 bMoveable; // Moveable window? + + int sNoofHicons; // } + int sNoofVicons; // } Current dimensions + + bool bMax; // Maximised last time open? + +}; + +static INV_DEF InvD[NUM_INV]; // Conversation + 2 inventories + ... + + +// Permanent contents of conversation inventory +static int permIcons[MAX_PERMICONS]; // Basic items i.e. permanent contents +static int numPermIcons = 0; // - copy to conv. inventory at pop-up time +static int numEndIcons = 0; + +//----- 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 bReOpenMenu = 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 LANGUAGE displayedLanguage; + +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 sliderYpos = 0; // For positioning the slider +static int sliderYmax = 0, sliderYmin = 0; // + +#define sliderRange (sliderYmax - sliderYmin) + +// Also to do with the slider +static struct { int n; int y; } slideStuff[MAX_ININV_TOT+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; + +// Icon clicked on to cause an event +// - Passed to conversation polygon or actor code via Topic() +// - (sometimes) Passed to inventory icon code via OtherObject() +static int thisIcon = 0; + +static CONV_PARAM thisConvFn; // Top, 'Middle' or Bottom +static HPOLYGON thisConvPoly = 0; // Conversation code is in a polygon code block +static int thisConvActor; // ...or an actor's code block. +static int pointedIcon = INV_NOICON; // used by InvLabels - icon pointed to on last call +static volatile int PointedWaitCount = 0; // used by ObjectProcess - 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 + +static bool bMoveOnUnHide; // Set before start of conversation + // - causes conversation to be started in a sensible place + +//----- 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 + +#define T2_EDIT_BOX1_WIDTH 290 +#define T2_EDIT_BOX2_WIDTH 322 +#define T2_BOX_HEIGHT 26 + +//----- Data pertinant to scene hoppers ------------------------ + +#include "common/pack-start.h" // START STRUCT PACKING + +struct HOPPER { + uint32 hScene; + SCNHANDLE hSceneDesc; + uint32 numEntries; + uint32 entryIndex; +} PACKED_STRUCT; +typedef HOPPER *PHOPPER; + +struct HOPENTRY { + uint32 eNumber; // entrance number + SCNHANDLE hDesc; // handle to entrance description + uint32 flags; +} PACKED_STRUCT; +typedef HOPENTRY *PHOPENTRY; + +#include "common/pack-end.h" // END STRUCT PACKING + +static PHOPPER pHopper; +static PHOPENTRY pEntries; +static int numScenes; + +static int numEntries; + +static PHOPPER pChosenScene = NULL; + +static int lastChosenScene; +static bool bRemember; + +//-------------------------------------------------------------- + + + +enum BTYPE { + 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 + ARSGBUT, + AAGBUT, //!< Action always, graphic button + SLIDER, //!< Not a button at all + TOGGLE, //!< Discworld 1 toggle + TOGGLE1, //!< Discworld 2 toggle type 1 + TOGGLE2, //!< Discworld 2 toggle type 2 + DCTEST, + FLIP, + FRGROUP, + ROTATE, + NOTHING +}; + +enum BFUNC { + NOFUNC, + SAVEGAME, + LOADGAME, + IQUITGAME, + CLOSEWIN, + OPENLOAD, + OPENSAVE, + OPENREST, + OPENSOUND, + OPENCONT, +#ifndef JAPAN + OPENSUBT, +#endif + OPENQUIT, + INITGAME, + MUSICVOL, + + HOPPER2, // Call up Scene Hopper 2 + BF_CHANGESCENE, + + CLANG, + RLANG +#ifdef MAC_OPTIONS + , MASTERVOL, SAMPVOL +#endif +}; + +#define NO_HEADING ((SCNHANDLE)-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 + +enum TM {TM_POINTER, TM_INDEX, TM_STRINGNUM, TM_NONE}; + +struct CONFBOX { + BTYPE boxType; + BFUNC boxFunc; + TM textMethod; + + 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 +}; + +struct CONFINIT { + int h; + int v; + int x; + int y; + bool bExtraWin; + CONFBOX *Box; + int NumBoxes; + uint32 ixHeading; +}; + +#define BW 44 // Width of crosses and ticks etc. buttons +#define BH 41 // Height of crosses and ticks etc. buttons + +/*-------------------------------------------------------------*\ +| 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 + +#define OPTX 33 +#define OPTY 30 +#define BOX_V_SEP 7 + +#define BOXX 56 // X-position of text boxes +#define BOXY 50 // Y-position of text boxes +#define T2_OPTX 33 +#define T2_OPTY 36 +#define T2_BOX_V_SEP 12 +#define T2_BOX_V2_SEP 6 + +CONFBOX t1OptionBox[] = { + + { AATBUT, OPENLOAD, TM_NONE, NULL, SIX_LOAD_OPTION, FBX, FBY, EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSAVE, TM_NONE, NULL, SIX_SAVE_OPTION, FBX, FBY + (BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENREST, TM_NONE, NULL, SIX_RESTART_OPTION, FBX, FBY + 2*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSOUND, TM_NONE, NULL, SIX_SOUND_OPTION, FBX, FBY + 3*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENCONT, TM_NONE, 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, TM_NONE, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENQUIT, TM_NONE, NULL, SIX_QUIT_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, CLOSEWIN, TM_NONE, NULL, SIX_RESUME_OPTION, FBX, FBY + 7*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } +#endif + +}; + +CONFINIT t1ciOption = { 6, 5, 72, 23, false, t1OptionBox, ARRAYSIZE(t1OptionBox), NO_HEADING }; + +CONFBOX t2OptionBox[] = { + + { AATBUT, OPENLOAD, TM_INDEX, NULL, SS_LOAD_OPTION, T2_OPTX, T2_OPTY, T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSAVE, TM_INDEX, NULL, SS_SAVE_OPTION, T2_OPTX, T2_OPTY + (T2_BOX_HEIGHT + T2_BOX_V_SEP), T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENREST, TM_INDEX, NULL, SS_RESTART_OPTION, T2_OPTX, T2_OPTY + 2*(T2_BOX_HEIGHT + T2_BOX_V_SEP), T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSOUND, TM_INDEX, NULL, SS_SOUND_OPTION, T2_OPTX, T2_OPTY + 3*(T2_BOX_HEIGHT + T2_BOX_V_SEP), T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENQUIT, TM_INDEX, NULL, SS_QUIT_OPTION, T2_OPTX, T2_OPTY + 4*(T2_BOX_HEIGHT + T2_BOX_V_SEP), T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 } + +}; + +CONFINIT t2ciOption = { 6, 4, 144, 60, false, t2OptionBox, sizeof(t2OptionBox)/sizeof(CONFBOX), NO_HEADING }; + +#define ciOption (TinselV2 ? t2ciOption : t1ciOption) +#define optionBox (TinselV2 ? t2OptionBox : t1OptionBox) + +/*-------------------------------------------------------------*\ +| These are the load and save game menus. | +\*-------------------------------------------------------------*/ + +#define NUM_RGROUP_BOXES 9 + +#ifdef JAPAN +#define NUM_RGROUP_BOXES 7 // number of visible slots +#define SY 32 // y-position of first slot +#else +#define NUM_RGROUP_BOXES 9 // number of visible slots +#define SY 31 // y-position of first slot +#endif + +CONFBOX t1LoadBox[NUM_RGROUP_BOXES+2] = { + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, LOADGAME, TM_NONE, NULL, USE_POINTER, 230, 44, 23, 19, NULL, IX1_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX1_CROSS1 } +}; + +CONFBOX t2LoadBox[] = { + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY, T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + + { ARSGBUT, LOADGAME, TM_NONE, NULL, 0, 460, 100, BW, BH, NULL, IX2_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 460, 100+100, BW, BH, NULL, IX2_CROSS1 } +}; + +CONFINIT t1ciLoad = { 10, 6, 20, 16, true, t1LoadBox, ARRAYSIZE(t1LoadBox), SIX_LOAD_HEADING }; +CONFINIT t2ciLoad = { 10, 6, 40, 16, true, t2LoadBox, sizeof(t2LoadBox)/sizeof(CONFBOX), SS_LOAD_HEADING }; + + +CONFBOX t1SaveBox[NUM_RGROUP_BOXES+2] = { + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, SAVEGAME, TM_NONE, NULL,USE_POINTER, 230, 44, 23, 19, NULL, IX1_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX1_CROSS1 } +}; + +CONFBOX t2SaveBox[] = { + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY, T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + + { ARSGBUT, SAVEGAME, TM_NONE, NULL, 0, 460, 100, BW, BH, NULL, IX2_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 460, 100+100, BW, BH, NULL, IX2_CROSS1 } +}; + +CONFINIT t1ciSave = { 10, 6, 20, 16, true, t1SaveBox, ARRAYSIZE(t1SaveBox), SIX_SAVE_HEADING }; +CONFINIT t2ciSave = { 10, 6, 40, 16, true, t2SaveBox, sizeof(t2SaveBox)/sizeof(CONFBOX), SS_SAVE_HEADING }; + +#define ciLoad (TinselV2 ? t2ciLoad : t1ciLoad) +#define loadBox (TinselV2 ? t2LoadBox : t1LoadBox) +#define ciSave (TinselV2 ? t2ciSave : t1ciSave) +#define saveBox (TinselV2 ? t2SaveBox : t1SaveBox) + +/*-------------------------------------------------------------*\ +| This is the restart confirmation 'menu'. | +\*-------------------------------------------------------------*/ + +CONFBOX t1RestartBox[] = { +#ifdef JAPAN + { AAGBUT, INITGAME, TM_NONE, NULL, USE_POINTER, 96, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 56, 44, 23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, INITGAME, TM_NONE, NULL, USE_POINTER, 70, 28, 23, 19, NULL, IX1_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX1_CROSS1 } +#endif +}; + +CONFBOX t2RestartBox[] = { + { AAGBUT, INITGAME, TM_NONE, NULL, 0, 140, 78, BW, BH, NULL, IX2_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 60, 78, BW, BH, NULL, IX2_CROSS1 } +}; + +#ifdef JAPAN +CONFINIT t1ciRestart = { 6, 2, 72, 53, false, t1RestartBox, ARRAYSIZE(t1RestartBox), SIX_RESTART_HEADING }; +#else +CONFINIT t1ciRestart = { 4, 2, 98, 53, false, t1RestartBox, ARRAYSIZE(t1RestartBox), SIX_RESTART_HEADING }; +#endif +CONFINIT t2ciRestart = { 4, 2, 196, 53, false, t2RestartBox, sizeof(t2RestartBox)/sizeof(CONFBOX), SS_RESTART_HEADING }; + +#define ciRestart (TinselV2 ? t2ciRestart : t1ciRestart) +#define restartBox (TinselV2 ? t2RestartBox : t1RestartBox) + +/*-------------------------------------------------------------*\ +| This is the sound control 'menu'. In Discworld 2, it also | +| contains the subtitles and language selection. | +\*-------------------------------------------------------------*/ + +CONFBOX t1SoundBox[] = { + { SLIDER, MUSICVOL, TM_NONE, NULL, SIX_MVOL_SLIDER, 142, 25, Audio::Mixer::kMaxChannelVolume, 2, &volMusic, 0 }, + { SLIDER, NOFUNC, TM_NONE, NULL, SIX_SVOL_SLIDER, 142, 25+40, Audio::Mixer::kMaxChannelVolume, 2, &volSound, 0 }, + { SLIDER, NOFUNC, TM_NONE, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, Audio::Mixer::kMaxChannelVolume, 2, &volVoice, 0 } +}; + +CONFBOX t2SoundBox[] = { + { SLIDER, MUSICVOL, TM_INDEX, NULL, SS_MVOL_SLIDER, 280, 50, Audio::Mixer::kMaxChannelVolume, 2, &volMusic, 0 }, + { SLIDER, NOFUNC, TM_INDEX, NULL, SS_SVOL_SLIDER, 280, 50+30, Audio::Mixer::kMaxChannelVolume, 2, &volSound, 0 }, + { SLIDER, NOFUNC, TM_INDEX, NULL, SS_VVOL_SLIDER, 280, 50+2*30, Audio::Mixer::kMaxChannelVolume, 2, &volVoice, 0 }, + + { SLIDER, NOFUNC, TM_INDEX, NULL, SS_TSPEED_SLIDER, 280, 160, 100, 2, &speedText, 0 }, + { TOGGLE2, NOFUNC, TM_INDEX, NULL, SS_STITLE_TOGGLE, 100, 220, BW, BH, &bSubtitles, 0 }, + { ROTATE, NOFUNC, TM_INDEX, NULL, SS_LANGUAGE_SELECT, 320,220, BW, BH, NULL, 0 } +}; + +CONFINIT t1ciSound = { 10, 5, 20, 16, false, t1SoundBox, ARRAYSIZE(t1SoundBox), NO_HEADING }; +CONFINIT t2ciSound = { 10, 5, 40, 16, false, t2SoundBox, sizeof(t2SoundBox)/sizeof(CONFBOX), SS_SOUND_HEADING }; + +#define ciSound (TinselV2 ? t2ciSound : t1ciSound) +#define soundBox (TinselV2 ? t2SoundBox : t1SoundBox) + +/*-------------------------------------------------------------*\ +| This is the (mouse) control 'menu'. | +\*-------------------------------------------------------------*/ + +int bFlipped; // looks like this is just so the code has something to alter! + +CONFBOX controlBox[] = { + { SLIDER, NOFUNC, TM_NONE, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, + { FLIP, NOFUNC, TM_NONE, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX1_CIRCLE1 }, +#ifdef JAPAN + { TOGGLE, NOFUNC, TM_NONE, NULL, SIX_SWAP_TOGGLE, 205, 25+70, 23, 19, &bSwapButtons, 0 } +#else + { TOGGLE, NOFUNC, TM_NONE, NULL, SIX_SWAP_TOGGLE, 155, 25+70, 23, 19, &bSwapButtons, 0 } +#endif +}; + +CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING }; + +/*-------------------------------------------------------------*\ +| This is the subtitles 'menu'. | +\*-------------------------------------------------------------*/ + +CONFBOX subtitlesBox[] = { + { SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, + { TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, +}; + +CONFBOX subtitlesBox3Flags[] = { + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 15, 118, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 85, 118, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 155, 118, 56, 32, NULL, FIX_SP }, + + { SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, + { TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, + + { ARSGBUT, CLANG, TM_NONE, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX1_TICK1 }, + { AAGBUT, RLANG, TM_NONE, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX1_CROSS1 } +}; + +CONFBOX subtitlesBox4Flags[] = { + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 20, 100, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 108, 100, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 64, 137, 56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 152, 137, 56, 32, NULL, FIX_SP }, + + { SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, + { TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, + + { ARSGBUT, CLANG, TM_NONE, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX1_TICK1 }, + { AAGBUT, RLANG, TM_NONE, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX1_CROSS1 } +}; + +CONFBOX subtitlesBox5Flags[] = { + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 15, 100, 56, 32, NULL, FIX_UK }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 85, 100, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 155, 100, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 50, 137, 56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER, 120, 137, 56, 32, NULL, FIX_SP }, + + { SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, + { TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, + + { ARSGBUT, CLANG, TM_NONE, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX1_TICK1 }, + { AAGBUT, RLANG, TM_NONE, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX1_CROSS1 } +}; + + +/*-------------------------------------------------------------*\ +| This is the quit confirmation 'menu'. | +\*-------------------------------------------------------------*/ + +CONFBOX t1QuitBox[] = { +#ifdef JAPAN + { AAGBUT, IQUITGAME, TM_NONE, NULL, USE_POINTER,70, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 30, 44, 23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, IQUITGAME, TM_NONE, NULL, USE_POINTER,70, 28, 23, 19, NULL, IX1_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX1_CROSS1 } +#endif +}; + +CONFBOX t2QuitBox[] = { + { AAGBUT, IQUITGAME, TM_NONE, NULL, 0,140, 78, BW, BH, NULL, IX2_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 60, 78, BW, BH, NULL, IX2_CROSS1 } +}; + +CONFINIT t1ciQuit = { 4, 2, 98, 53, false, t1QuitBox, ARRAYSIZE(t1QuitBox), SIX_QUIT_HEADING }; +CONFINIT t2ciQuit = { 4, 2, 196, 53, false, t2QuitBox, sizeof(t2QuitBox)/sizeof(CONFBOX), SS_QUIT_HEADING }; + +#define quitBox (TinselV2 ? t2QuitBox : t1QuitBox) +#define ciQuit (TinselV2 ? t2ciQuit : t1ciQuit) + +/***************************************************************************\ +|************************ Startup and shutdown ***********************| +\***************************************************************************/ + +CONFBOX hopperBox1[] = { + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY, T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + + { ARSGBUT, HOPPER2, TM_NONE, NULL, 0, 460, 100, BW, BH, NULL, IX2_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 460, 100 + 100, BW, BH, NULL, IX2_CROSS1 } +}; + +CONFINIT ciHopper1 = { 10, 6, 40, 16, true, hopperBox1, sizeof(hopperBox1) / sizeof(CONFBOX), SS_HOPPER1 }; + +CONFBOX hopperBox2[] = { + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY, T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + { RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 }, + + { ARSGBUT, BF_CHANGESCENE, TM_NONE, NULL, 0, 460, 50, BW, BH, NULL, IX2_TICK1 }, + { AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 460, 200, BW, BH, NULL, IX2_CROSS1 } +}; + +CONFINIT ciHopper2 = { 10, 6, 40, 16, true, hopperBox2, sizeof(hopperBox2)/sizeof(CONFBOX), NO_HEADING }; + + +/***************************************************************************\ +|**************************** Top Window *****************************| +\***************************************************************************/ +CONFBOX topwinBox[] = { + { NOTHING, NOFUNC, TM_NONE, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 } +}; + + +CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING }; + +CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING }; + +#define NOBOX (-1) + +// Conf window globals +static struct { + CONFBOX *box; + int NumBoxes; + bool bExtraWin; + uint32 ixHeading; + bool editableRgroup; + + int selBox; + int pointBox; // Box pointed to on last call + int modifier; + int extraBase; + 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; + CONFBOX *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 WhichMenuBox() + 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 WhichMenuBox(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 START_ICONX (TinselV2 ? 12 : (M_SW+1)) // } Relative offset of first icon +#define START_ICONY (TinselV2 ? 40 : (M_TBB+M_TH+1)) // } within the inventory window + +/*-------------------------------------------------------------------------*/ + + +bool LanguageChange(void) { + LANGUAGE nLang = g_language; + + if (_vm->getFeatures() & GF_USE_3FLAGS) { + // VERY quick dodgy bodge + if (cd.selBox == 0) + nLang = TXT_FRENCH; // = 1 + else if (cd.selBox == 1) + nLang = TXT_GERMAN; // = 2 + else + nLang = TXT_SPANISH; // = 4 + } else if (_vm->getFeatures() & GF_USE_4FLAGS) { + nLang = (LANGUAGE)(cd.selBox + 1); + } else if (_vm->getFeatures() & GF_USE_5FLAGS) { + nLang = (LANGUAGE)cd.selBox; + } + + if (nLang != g_language) { + KillInventory(); + ChangeLanguage(nLang); + g_language = nLang; + return true; + } else + return false; +} + +/**************************************************************************/ +/***************************** Scene Hopper ******************************/ +/**************************************************************************/ + +/** + * Read in the scene hopper data file and set the + * pointers to the data and scene count. + */ +static void PrimeSceneHopper(void) { + Common::File f; + char *pBuffer; + uint32 vSize; + + // Open the file (it's on the CD) + CdCD(nullContext); + if (!f.open(HOPPER_FILENAME)) + error(CANNOT_FIND_FILE, HOPPER_FILENAME); + + // Read in header + if (f.readUint32LE() != CHUNK_SCENE_HOPPER) + error(FILE_IS_CORRUPT, HOPPER_FILENAME); + vSize = f.readUint32LE(); + + // allocate a buffer for it all + assert(pHopper == NULL); + uint32 size = f.size() - 8; + + // make sure memory allocated + pBuffer = (char *)malloc(size); + if (pBuffer == NULL) + // cannot alloc buffer for index + error(NO_MEM, "Scene hopper data\n"); + + // load data + if (f.read(pBuffer, size) != size) + // file must be corrupt if we get to here + error(FILE_IS_CORRUPT, HOPPER_FILENAME); + + // Set data pointers + pHopper = (PHOPPER)pBuffer; + pEntries = (PHOPENTRY)(pBuffer + vSize); + numScenes = vSize / sizeof(HOPPER); + + // close the file + f.close(); +} + +/** + * Free the scene hopper data file + */ +static void FreeSceneHopper() { + free(pHopper); + pHopper = NULL; +} + +static void FirstScene(int first) { + int i; + + assert(numScenes && pHopper); + + if (bRemember) { + assert(first == 0); + first = lastChosenScene; + bRemember = false; + } + + // Force it to a sensible value + if (first > numScenes - NUM_RGROUP_BOXES) + first = numScenes - NUM_RGROUP_BOXES; + if (first < 0) + first = 0; + + // Fill in the rest + for (i = 0; i < NUM_RGROUP_BOXES && i + first < numScenes; i++) { + cd.box[i].textMethod = TM_STRINGNUM; + cd.box[i].ixText = FROM_LE_32(pHopper[i + first].hSceneDesc); + } + // Blank out the spare ones (if any) + while (i < NUM_RGROUP_BOXES) { + cd.box[i].textMethod = TM_NONE; + cd.box[i++].ixText = 0; + } + + cd.extraBase = first; +} + +static void RememberChosenScene(void) { + bRemember = true; +} + +static void SetChosenScene(void) { + lastChosenScene = cd.selBox + cd.extraBase; + pChosenScene = &pHopper[cd.selBox + cd.extraBase]; +} + +static void FirstEntry(int first) { + int i; + + InvD[INV_MENU].hInvTitle = FROM_LE_32(pChosenScene->hSceneDesc); + + // get number of entrances + numEntries = FROM_LE_32(pChosenScene->numEntries); + + // Force first to a sensible value + if (first > numEntries-NUM_RGROUP_BOXES) + first = numEntries-NUM_RGROUP_BOXES; + if (first < 0) + first = 0; + + for (i = 0; i < NUM_RGROUP_BOXES && i < numEntries; i++) { + cd.box[i].textMethod = TM_STRINGNUM; + cd.box[i].ixText = FROM_LE_32(pEntries[FROM_LE_32(pChosenScene->entryIndex) + i + first].hDesc); + } + // Blank out the spare ones (if any) + while (i < NUM_RGROUP_BOXES) { + cd.box[i].textMethod = TM_NONE; + cd.box[i++].ixText = 0; + } + + cd.extraBase = first; +} + +void HopAction(void) { + PHOPENTRY pEntry = pEntries + FROM_LE_32(pChosenScene->entryIndex) + cd.selBox + cd.extraBase; + + uint32 hScene = FROM_LE_32(pChosenScene->hScene); + uint32 eNumber = FROM_LE_32(pEntry->eNumber); + debugC(DEBUG_BASIC, kTinselDebugAnimations, "Scene hopper chose scene %xh,%d\n", hScene, eNumber); + + if (FROM_LE_32(pEntry->flags) & fCall) { + SaveScene(nullContext); + NewScene(nullContext, pChosenScene->hScene, pEntry->eNumber, TRANS_FADE); + } + else if (FROM_LE_32(pEntry->flags) & fHook) + HookScene(hScene, eNumber, TRANS_FADE); + else + NewScene(nullContext, hScene, eNumber, TRANS_CUT); +} + +/**************************************************************************/ +/******************** Some miscellaneous functions ************************/ +/**************************************************************************/ + +/** + * Delete all the objects in iconArray[] + */ +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. + */ +INV_OBJECT *GetInvObject(int num) { + INV_OBJECT *retval = invObjects; + + for (int i = 0; i < numObjects; i++, retval++) { + if (retval->id == num) + return retval; + } + + error("Trying to manipulate undefined inventory icon"); +} + +/** + * Convert item ID number to index. + */ +int GetObjectIndex(int id) { + int i; // Loop counter + INV_OBJECT *pObject; + + for (i = 0, pObject = invObjects; i < numObjects; i++, pObject++) { + if (pObject->id == id) + break; + } + + assert(i < numObjects); + return i; +} + +/** + * 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].contents[i] == num) + return i; + + for (i = 0; i < InvD[INV_2].NoofItems; i++) // Second inventory + if (InvD[INV_2].contents[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].contents[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(bool bNewItem) { + + if (HeldItem != INV_NOICON) { + if (TinselV2) { + if (bNewItem) { + int objIndex = GetObjectIndex(HeldItem); + heldFilm = invFilms[objIndex]; + } + SetAuxCursor(heldFilm); + } else { + INV_OBJECT *invObj = GetInvObject(HeldItem); + SetAuxCursor(invObj->hIconFilm); + } + } +} + +/** + * 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 OP_INIT { + INV_OBJECT *pinvo; + TINSEL_EVENT event; + PLR_EVENT bev; + int myEscape; +}; + +/** + * Run inventory item's Glitter code + */ +static void ObjectProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + int ThisPointedWait; // Fix the 'repeated pressing bug' + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + const OP_INIT *to = (const OP_INIT *)param; + + CORO_BEGIN_CODE(_ctx); + + if (!TinselV2) + CORO_INVOKE_1(AllowDclick, to->bev); + + _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo, + to->myEscape); + 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 InvTinselEvent(INV_OBJECT *pinvo, TINSEL_EVENT event, PLR_EVENT be, int index) { + OP_INIT to = { pinvo, event, be, 0 }; + + if (InventoryHidden || (TinselV2 && !pinvo->hScript)) + return; + + GlitterIndex = index; + g_scheduler->createProcess(PID_TCODE, ObjectProcess, &to, sizeof(to)); +} + +void ObjectEvent(CORO_PARAM, int objId, TINSEL_EVENT event, bool bWait, int myEscape, bool *result) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PROCESS *pProc; + INV_OBJECT *pInvo; + OP_INIT op; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (result) *result = false; + _ctx->pInvo = GetInvObject(objId); + if (!_ctx->pInvo->hScript) + return; + + _ctx->op.pinvo = _ctx->pInvo; + _ctx->op.event = event; + _ctx->op.myEscape = myEscape; + + g_scheduler->createProcess(PID_TCODE, ObjectProcess, &_ctx->op, sizeof(_ctx->op)); + + if (bWait) + CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result); + else + // FIXME: Essential to ensure created ObjectProcess coroutine has a chance to start + // up, for the scene restore when returning from looking at the AshkEnte instructions + // to work. Really need to trace operation of original to see at what point the scene + // save is done, and when control was first yielded to the ObjectProcess coroutine + CORO_SLEEP(1); + + CORO_END_CODE; +} + +/**************************************************************************/ +/****************** 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_SAVED_FILES - NUM_RGROUP_BOXES) + first = MAX_SAVED_FILES - NUM_RGROUP_BOXES; + + if (first == 0 && i < MAX_SAVED_FILES && cd.box == saveBox) { + // Blank first entry for new save + cd.box[0].boxText = NULL; + cd.modifier = j = 1; + } else { + cd.modifier = j = 0; + } + + for (i = first; j < NUM_RGROUP_BOXES; j++, i++) { + cd.box[j].boxText = ListEntry(i, LE_DESC); + } + + cd.extraBase = 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.modifier+cd.extraBase, LE_NAME), sedit); + } +} + +/** + * Load the selected saved game. + */ +void InvLoadGame(void) { + int rGame; + + if (cd.selBox != NOBOX && (cd.selBox+cd.extraBase < 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.extraBase); + } +} + +/** + * 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.ascii == 0) + return false; + + 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(GetTagFontHandle(), 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(nullContext, + GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.box[cd.selBox].xpos + 2, + InvD[ino].inventoryY + cd.box[cd.selBox].ypos + TYOFF, + GetTagFontHandle(), 0); + if (MultiRightmost(iconArray[HL3]) > MAX_NAME_RIGHT) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE)); + iconArray[HL3] = ObjectTextOut(nullContext, + GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.box[cd.selBox].xpos + 2, + InvD[ino].inventoryY + cd.box[cd.selBox].ypos + TYOFF, + GetTagFontHandle(), 0); + } + MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); + } +#endif + } + return false; +} + +/** + * 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(BgPal(), + (TinselV2 ? HighlightColour() : 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); + + if (TinselV2) { + assert(cd.box[i].textMethod == TM_POINTER); + } else { + 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(nullContext, + 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 + TYOFF, +#endif + GetTagFontHandle(), 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(BgPal(), 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; + } +} + + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * 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; +} + +/** + * Clears the specified inventory + */ +void ClearInventory(int invno) { + assert(invno == INV_1 || invno == INV_2); + + InvD[invno].NoofItems = 0; + memset(InvD[invno].contents, 0, sizeof(InvD[invno].contents)); +} + +/** + * Stick the item into an inventory list (contents[]), and hold the + * item if requested. + */ +void AddToInventory(int invno, int icon, bool hold) { + int i; + bool bOpen; + INV_OBJECT *invObj; + + // Validate trying to add to a legal inventory + assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV + || invno == INV_OPEN || (invno == INV_DEFAULT && TinselV2)); + + 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; + + if (TinselV2 && invno == INV_DEFAULT) { + invObj = GetInvObject(icon); + if (invObj->attribute & DEFINV2) + invno = INV_2; + else if (invObj->attribute & DEFINV1) + invno = INV_1; + else + invno = SysVar(SV_DEFAULT_INV); + } + } + + 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].contents[i] == icon) + break; + } + + // Add it if it isn't already there + if (i == InvD[invno].NoofItems) { + if (!bOpen) { + if (invno == INV_CONV) { + if (TinselV2) { + int nei; + + // Count how many current contents have end attribute + for (i = 0, nei = 0; i < InvD[INV_CONV].NoofItems; i++) { + invObj = GetInvObject(InvD[INV_CONV].contents[i]); + if (invObj->attribute & CONVENDITEM) + nei++; + } + + // For conversation, insert before end icons + memmove(&InvD[INV_CONV].contents[i-nei+1], + &InvD[INV_CONV].contents[i-nei], nei * sizeof(int)); + InvD[INV_CONV].contents[i - nei] = icon; + InvD[INV_CONV].NoofItems++; + InvD[INV_CONV].NoofHicons = InvD[INV_CONV].NoofItems; + + // Get the window to re-position + bMoveOnUnHide = true; + } else { + // For conversation, insert before last icon + // which will always be the goodbye icon + InvD[invno].contents[InvD[invno].NoofItems] = InvD[invno].contents[InvD[invno].NoofItems-1]; + InvD[invno].contents[InvD[invno].NoofItems-1] = icon; + InvD[invno].NoofItems++; + } + } else { + InvD[invno].contents[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].contents[GlitterIndex + 1], + &InvD[invno].contents[GlitterIndex], + (InvD[invno].NoofItems - GlitterIndex) * sizeof(int)); + InvD[invno].contents[GlitterIndex] = icon; + } else { + InvD[invno].contents[InvD[invno].NoofItems] = icon; + } + InvD[invno].NoofItems++; + } + + // Move here after bug on Japenese DW1 + ItemsChanged = true; + } + + // Hold it if requested + if (hold) + HoldItem(icon); +} + +/** + * Take the item from the inventory list (contents[]). + * 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].contents[i] == icon) + break; + } + + if (i == InvD[invno].NoofItems) + return false; // Item wasn't there + else { + memmove(&InvD[invno].contents[i], &InvD[invno].contents[i+1], (InvD[invno].NoofItems-i)*sizeof(int)); + InvD[invno].NoofItems--; + + if (TinselV2 && invno == INV_CONV) { + InvD[INV_CONV].NoofHicons = InvD[invno].NoofItems; + + // Get the window to re-position + bMoveOnUnHide = true; + } + + ItemsChanged = true; + return true; // Item removed + } +} + +/** + * If the item is not already held, hold it. + */ +void HoldItem(int item, bool bKeepFilm) { + INV_OBJECT *invObj; + + if (HeldItem != item) { + if (TinselV2 && (HeldItem != NOOBJECT)) { + // No longer holding previous item + DelAuxCursor(); // no longer aux cursor + + // If old held object is not in an inventory, and + // has a default, stick it in its default inventory. + if (!IsInInventory(HeldItem, INV_1) && !IsInInventory(HeldItem, INV_2)) { + invObj = GetInvObject(HeldItem); + + if (invObj->attribute & DEFINV1) + AddToInventory(INV_1, HeldItem); + else if (invObj->attribute & DEFINV2) + AddToInventory(INV_2, HeldItem); + else + // Hook for definable default inventory + AddToInventory(INV_1, HeldItem); + } + + } else if (!TinselV2) { + if (item == INV_NOICON && HeldItem != INV_NOICON) + DelAuxCursor(); // no longer aux cursor + + if (item != INV_NOICON) { + invObj = GetInvObject(item); + SetAuxCursor(invObj->hIconFilm); // and is aux. cursor + } + } + + HeldItem = item; // Item held + + if (TinselV2) { + InventoryIconCursor(!bKeepFilm); + + // Redraw contents - held item not displayed as a content. + ItemsChanged = true; + } + } + + if (!TinselV2) + // Redraw contents - held item not displayed as a content. + ItemsChanged = true; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +enum { I_NOTIN, I_HEADER, 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 +/*---------------------------------*/ + +/** + * 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. + */ +int InvArea(int x, int y) { + if (TinselV2) { + int RightX = MultiRightmost(RectObject) - NM_BG_SIZ_X - NM_BG_POS_X - NM_RS_R_INSET; + int BottomY = MultiLowest(RectObject) - NM_BG_SIZ_Y - NM_BG_POS_Y - NM_RS_B_INSET; + + // Outside the whole rectangle? + if (x <= LeftX || x > RightX || y <= TopY || y > BottomY) + return I_NOTIN; + + // The bottom line + if (y > BottomY - NM_RS_THICKNESS) { + // Below top of bottom line? + if (x <= LeftX + NM_RS_THICKNESS) + return I_BLEFT; // Bottom left corner + else if (x > RightX - NM_RS_THICKNESS) + return I_BRIGHT; // Bottom right corner + else + return I_BOTTOM; // Just plain bottom + } + + // The top line + if (y <= TopY + NM_RS_THICKNESS) { + // Above bottom of top line? + if (x <= LeftX + NM_RS_THICKNESS) + return I_TLEFT; // Top left corner + else if (x > RightX - NM_RS_THICKNESS) + return I_TRIGHT; // Top right corner + else + return I_TOP; // Just plain top + } + + // Sides + if (x <= LeftX + NM_RS_THICKNESS) // Left of right of left side? + return I_LEFT; + else if (x > RightX - NM_RS_THICKNESS) // Right of left of right side? + return I_RIGHT; + + // In the move area? + if (y < TopY + NM_MOVE_AREA_B_Y) + return I_HEADER; + + // Scroll bits + if (!(ino == INV_MENU && cd.bExtraWin)) { + if (x > RightX - NM_SLIDE_INSET && x <= RightX - NM_SLIDE_INSET + NM_SLIDE_THICKNESS) { + if (y > TopY + NM_UP_ARROW_TOP && y < TopY + NM_UP_ARROW_BOTTOM) + return I_UP; + if (y > BottomY - NM_DN_ARROW_TOP && y <= BottomY - NM_DN_ARROW_BOTTOM) + return I_DOWN; + + /* '3' is a magic adjustment with no apparent sense */ + + if (y >= TopY + sliderYmin - 3 && y < TopY + sliderYmax + NM_SLH) { + if (y < TopY + sliderYpos - 3) + return I_SLIDE_UP; + if (y < TopY + sliderYpos + NM_SLH - 3) + return I_SLIDE; + else + return I_SLIDE_DOWN; + } + } + } + } else { + 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_HEADER; + + /* + * Scroll bits + */ + if (!(ino == INV_CONF && cd.bExtraWin)) { + if (x > RightX - NM_SLIDE_INSET && x <= RightX - NM_SLIDE_INSET + NM_SLIDE_THICKNESS) { + 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 + sliderYmin && y < TopY + sliderYmax + M_SH) { + if (y < TopY + sliderYpos) + return I_SLIDE_UP; + if (y < TopY + sliderYpos + 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; +} + +int InvItem(Common::Point &coOrds, bool update) { + int x = coOrds.x; + int y = coOrds.y; + return InvItem(&x, &y, update); + coOrds.x = x; + coOrds.y = y; +} + +/** + * 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].contents[item]; + } + + ileft += ITEM_WIDTH + 1; + } + itop += ITEM_HEIGHT + 1; + } + return INV_NOICON; +} + +/** + * Finds which box the cursor is in. + */ +static int WhichMenuBox(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 TOGGLE1: + case TOGGLE2: + 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; + + case ROTATE: + if (bNoLanguage) + break; + + if (curY > cd.box[i].ypos && curY < cd.box[i].ypos + cd.box[i].h) { + // Left one? + if (curX > cd.box[i].xpos-ROTX1 && curX < cd.box[i].xpos-ROTX1 + cd.box[i].w) { + cd.box[i].bi = IX2_LEFT1; + return i; + } + // Right one? + if (curX > cd.box[i].xpos+ROTX1 && curX < cd.box[i].xpos+ROTX1 + cd.box[i].w) { + cd.box[i].bi = IX2_RIGHT1; + 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; + } + } + + // Slider on extra window + if (cd.bExtraWin) { + const Common::Rect &r = TinselV2 ? + Common::Rect(411, 46, 425, 339) : + Common::Rect(20 + 181, 24 + 2, 20 + 181 + 8, 24 + 139 + 5); + + if (r.contains(curX, curY)) { + + if (curY < (r.top + (TinselV2 ? 18 : 5))) + return IB_UP; + else if (curY > (r.bottom - (TinselV2 ? 18 : 5))) + return IB_DOWN; + else if (curY + InvD[ino].inventoryY < sliderYpos) + return IB_SLIDE_UP; + else if (curY + InvD[ino].inventoryY >= sliderYpos + NM_SLH) + return IB_SLIDE_DOWN; + else + return IB_SLIDE; + } + } + + return IB_NONE; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +#define ROTX1 60 // Rotate button's offsets from the centre + +/** + * InvBoxes + */ +void InvBoxes(bool InBody, int curX, int curY) { + static int rotateIndex = -1; + 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 = WhichMenuBox(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(BgPal(), + (TinselV2 ? HighlightColour() : 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 || + cd.box[cd.pointBox].boxType == TOGGLE1 || + cd.box[cd.pointBox].boxType == TOGGLE2) { + pfilm = (const FILM *)LockMem(hWinParts); + + 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); + } + else if (cd.box[cd.pointBox].boxType == ROTATE) { + if (bNoLanguage) + return; + + pfilm = (const FILM *)LockMem(hWinParts); + + rotateIndex = cd.box[cd.pointBox].bi; + if (rotateIndex == IX2_LEFT1) { + iconArray[HL1] = AddObject(&pfilm->reels[IX2_LEFT2], -1 ); + MultiSetAniXY(iconArray[HL1], + InvD[ino].inventoryX + cd.box[cd.pointBox].xpos - ROTX1, + InvD[ino].inventoryY + cd.box[cd.pointBox].ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + } else if (rotateIndex == IX2_RIGHT1) { + iconArray[HL1] = AddObject(&pfilm->reels[IX2_RIGHT2], -1); + MultiSetAniXY(iconArray[HL1], + InvD[ino].inventoryX + cd.box[cd.pointBox].xpos + ROTX1, + InvD[ino].inventoryY + cd.box[cd.pointBox].ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS + 1); + } + } + } +} + +static void ButtonPress(CORO_PARAM, CONFBOX *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(hWinParts); + if (iconArray[HL1] != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + pfilm = (const FILM *)LockMem(hWinParts); + 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(hWinParts); + 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(hWinParts); + 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, CONFBOX *box) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + const FILM *pfilm; + + assert((box->boxType == TOGGLE) || (box->boxType == TOGGLE1) + || (box->boxType == TOGGLE2)); + + // 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(hWinParts); + 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(hWinParts); + 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(hWinParts); + 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 + INV_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].contents[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 = GetInvObject(index); + if (invObj->hScript) + InvTinselEvent(invObj, POINTED, PLR_NOEVENT, 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 nsliderYpos; + 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 = sliderYmax - sliderYmin; + + if (!tMissing) + nsliderYpos = sliderYmin; + else if (!bMissing) + nsliderYpos = sliderYmax; + else { + nsliderYpos = tMissing*slideRange/nMissing; + nsliderYpos += sliderYmin; + } + + 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) + sliderYmin; + } + 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 = sliderYmin; + slideStuff[1].n = -1; + } + + if (nsliderYpos != sliderYpos) { + MultiMoveRelXY(SlideObject, 0, nsliderYpos - sliderYpos); + sliderYpos = nsliderYpos; + } +} + +/** + * Insert an inventory icon object onto the display list. + */ +OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) { + INV_OBJECT *invObj; // Icon data + const MULTI_INIT *pmi; // Its INIT structure - from the reel + IMAGE *pim; // ... you get the picture + OBJECT *pPlayObj; // The object we insert + + invObj = GetInvObject(num); + + // Get pointer to image + pim = GetImageFromFilm(invObj->hIconFilm, 0, pfreel, &pmi, pfilm); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BgPal()); + + // 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 contents[] + 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].contents[Index] != HeldItem) { + // Create a display object and position it + iconArray[n] = AddInvObject(InvD[ino].contents[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 + } + } +} + +enum {FROM_HANDLE, FROM_STRING}; + +/** + * Set up a rectangle as the background to the inventory window. + * Additionally, sticks the window title up. + */ +static void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) { + // Why not 2 ???? + int width = TLwidth + extraH + TRwidth + NM_BG_SIZ_X; + int height = TLheight + extraV + BLheight + NM_BG_SIZ_Y; + + // 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 + NM_BG_POS_X, + InvD[ino].inventoryY + NM_BG_POS_Y); + MultiSetZPosition(*rect, Z_INV_BRECT); + + if (title == NULL) + return; + + // Create text object using title string + CoroContext dummyCoro; + if (textFrom == FROM_HANDLE) { + LoadStringRes(InvD[ino].hInvTitle, TextBufferAddr(), TBUFSZ); + *title = ObjectTextOut(dummyCoro, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0, + InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, + GetTagFontHandle(), 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], TextBufferAddr(), TBUFSZ); + *title = ObjectTextOut(dummyCoro, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0, + InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, + GetTagFontHandle(), TXT_CENTRE); + assert(*title); // Inventory title string produced NULL text + MultiSetZPosition(*title, Z_INV_HTEXT); + } +} + +/** + * Set up a rectangle as the background to the inventory window. + */ +static void AddBackground(OBJECT **rect, int extraH, int extraV) { + AddBackground(rect, NULL, extraH, extraV, 0); +} + +/** + * Adds a title for a dialog + */ +static void AddTitle(POBJECT *title, int extraH) { + int width = TLwidth + extraH + TRwidth + NM_BG_SIZ_X; + + // Create text object using title string + if (InvD[ino].hInvTitle != (SCNHANDLE)NO_HEADING) { + LoadStringRes(InvD[ino].hInvTitle, TextBufferAddr(), TBUFSZ); + *title = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0, + InvD[ino].inventoryX + (width/2)+NM_BG_POS_X, InvD[ino].inventoryY + NM_TOFF, + GetTagFontHandle(), TXT_CENTRE, 0); + assert(*title); + 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 + IMAGE *pim; + OBJECT *pPlayObj; + + // Get pointer to image + pim = GetImageFromReel(pfreel, &pmi); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BgPal()); + + // 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) & ~C16_FLAG_MASK; + } else if (num == TR) { + TRwidth = FROM_LE_16(pim->imgWidth); + } else if (num == BL) { + BLheight = FROM_LE_16(pim->imgHeight) & ~C16_FLAG_MASK; + } + + // 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) + (TinselV2 ? NM_SLX : -M_SXOFF + 2) - 1, + InvD[ino].inventoryY + sliderYpos); + MultiSetZPosition(*slide, Z_INV_MFRAME); +} + +/** + * 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: + // Ignore if it's a blank scene hopper box + if (TinselV2 && (cd.box[i].textMethod == TM_NONE)) + break; + + // Give us a box + iconArray[*pi] = RectangleObject(BgPal(), TinselV2 ? BoxColour() : 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].textMethod == TM_POINTER) || + (!TinselV2 && (cd.box[i].ixText == USE_POINTER))) { + if (cd.box[i].boxText != NULL) { + if (cd.box[i].boxType == RGROUP) { + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), cd.box[i].boxText, 0, +#ifdef JAPAN + x + 2, y+2, GetTagFontHandle(), 0); +#else + x + 2, y + TYOFF, GetTagFontHandle(), 0); +#endif + } else { + iconArray[*pi] = ObjectTextOut(nullContext, 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, GetTagFontHandle(), TXT_CENTRE); +#else + x + cd.box[i].w / 2, y + TYOFF, GetTagFontHandle(), TXT_CENTRE); +#endif + } + + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + } + } else { + if (TinselV2) { + if (cd.box[i].textMethod == TM_INDEX) + LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ); + else { + assert(cd.box[i].textMethod == TM_STRINGNUM); + LoadStringRes(cd.box[i].ixText, TextBufferAddr(), TBUFSZ); + } + } else { + LoadStringRes(configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ); + assert(cd.box[i].boxType != RGROUP); // You'll need to add some code! + } + + if (TinselV2 && (cd.box[i].boxType == RGROUP)) + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), + 0, x + 2, y + TYOFF, GetTagFontHandle(), 0, 0); + else + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, +#ifdef JAPAN + x + cd.box[i].w/2, y+2, GetTagFontHandle(), TXT_CENTRE); +#else + x + cd.box[i].w / 2, y + TYOFF, GetTagFontHandle(), TXT_CENTRE); +#endif + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + } + break; + + case AAGBUT: + case ARSGBUT: + pFilm = (const FILM *)LockMem(hWinParts); + + 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(hWinParts); + + 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 + if (TinselV2) { + assert(cd.box[i].textMethod == TM_INDEX); + LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ); + } else { + assert(cd.box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ); + } + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, x + MDTEXT_XOFF, y + MDTEXT_YOFF, GetTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + + case TOGGLE: + case TOGGLE1: + case TOGGLE2: + pFilm = (const FILM *)LockMem(hWinParts); + + 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 + if (TinselV2) { + assert(cd.box[i].textMethod == TM_INDEX); + LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ); + } else { + assert(cd.box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ); + } + + if (cd.box[i].boxType == TOGGLE2) { + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, x + cd.box[i].w / 2, y + TOG2_YOFF, + GetTagFontHandle(), TXT_CENTRE, 0); + } else { + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, x + MDTEXT_XOFF, y + MDTEXT_YOFF, + GetTagFontHandle(), TXT_RIGHT, 0); + } + + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + + case SLIDER: + pFilm = (const FILM *)LockMem(hWinParts); + 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 + if (TinselV2) { + assert(cd.box[i].textMethod == TM_INDEX); + LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ); + } else { + assert(cd.box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ); + } + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, x+MDTEXT_XOFF, y+MDTEXT_YOFF, GetTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + + case ROTATE: + pFilm = (const FILM *)LockMem(hWinParts); + + // Left one + if (!bNoLanguage) { + iconArray[*pi] = AddObject(&pFilm->reels[IX2_LEFT1], -1); + MultiSetAniXY(iconArray[*pi], x-ROTX1, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT + 1); + *pi += 1; + + // Right one + iconArray[*pi] = AddObject( &pFilm->reels[IX2_RIGHT1], -1); + MultiSetAniXY(iconArray[*pi], x + ROTX1, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT + 1); + *pi += 1; + + // Stick in the text + assert(cd.box[i].textMethod == TM_INDEX); + LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, x + cd.box[i].w / 2, y + TOG2_YOFF, + GetTagFontHandle(), TXT_CENTRE, 0); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + } + + // Current language's text + if (LanguageDesc(displayedLanguage) == 0) + break; + + LoadStringRes(LanguageDesc(displayedLanguage), TextBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0, + x + cd.box[i].w / 2, y + ROT_YOFF, GetTagFontHandle(), TXT_CENTRE, 0); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + + // Current language's flag + pFilm = (const FILM *)LockMem(LanguageFlag(displayedLanguage)); + iconArray[*pi] = AddObject(&pFilm->reels[0], -1); + MultiSetAniXY(iconArray[*pi], x + FLAGX, y + FLAGY); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT + 1); + *pi += 1; + break; + } +} + +/** + * Display some boxes. + */ +static void AddBoxes(bool bPosnSlide) { + int objCount = NUMHL; // Object count - allow for HL1, HL2 etc. + + DumpIconArray(); + numMdSlides = 0; + + for (int i = 0; i < cd.NumBoxes; i++) { + AddBox(&objCount, i); + } + + if (cd.bExtraWin) { + if (bPosnSlide && !TinselV2) + sliderYpos = sliderYmin + (cd.extraBase*(sliderYmax-sliderYmin))/(MAX_SAVED_FILES-NUM_RGROUP_BOXES); + else if (bPosnSlide) { + // Tinsel 2 bPosnSlide code + int lastY = sliderYpos; + + if (cd.box == loadBox || cd.box == saveBox) + sliderYpos = sliderYmin + (cd.extraBase * (sliderRange)) / + (MAX_SAVED_FILES - NUM_RGROUP_BOXES); + else if (cd.box == hopperBox1) { + if (numScenes <= NUM_RGROUP_BOXES) + sliderYpos = sliderYmin; + else + sliderYpos = sliderYmin + (cd.extraBase*(sliderRange))/(numScenes-NUM_RGROUP_BOXES); + } else if (cd.box == hopperBox2) { + if (numEntries <= NUM_RGROUP_BOXES) + sliderYpos = sliderYmin; + else + sliderYpos = sliderYmin + (cd.extraBase * (sliderRange)) / + (numEntries-NUM_RGROUP_BOXES); + } + + MultiMoveRelXY(SlideObject, 0, sliderYpos - lastY); + } + + if (!TinselV2) + MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, sliderYpos); + } + + assert(objCount < 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, sliderYpos); + 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(hWinParts); + + x += TinselV2 ? 30 : 20; + y += TinselV2 ? 38 : 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 + (TinselV2 ? TLwidth + 312 : 152), y); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_BL], -1); // Bottom left + MultiSetAniXY(retObj[n], x, y + (TinselV2 ? TLheight + 208 : 124)); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_BR], -1); // Bottom right + MultiSetAniXY(retObj[n], x + (TinselV2 ? TLwidth + 312 : 152), + y + (TinselV2 ? TLheight + 208 : 124)); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + + // Draw the edges + retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Top + MultiSetAniXY(retObj[n], x + (TinselV2 ? TLwidth : 6), y + NM_TBT); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Bottom + MultiSetAniXY(retObj[n], x + (TinselV2 ? TLwidth : 6), y + + (TinselV2 ? TLheight + 208 + BLheight + NM_BSY : 143)); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Left + MultiSetAniXY(retObj[n], x + NM_LSX, y + (TinselV2 ? TLheight : 20)); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 1 + MultiSetAniXY(retObj[n], x + (TinselV2 ? TLwidth + 312 + TRwidth + NM_RSX : 179), + y + (TinselV2 ? TLheight : 20)); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 2 + MultiSetAniXY(retObj[n], x + (TinselV2 ? TLwidth + 312 + TRwidth + NM_SBL : 188), + y + (TinselV2 ? TLheight : 20)); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + + if (TinselV2) { + sliderYpos = sliderYmin = y + 27; + sliderYmax = y + 273; + + retObj[n++] = SlideObject = AddObject( &pfilm->reels[IX_SLIDE], -1); + MultiSetAniXY(SlideObject, + x + TLwidth + 320 + TRwidth - NM_BG_POS_X + NM_BG_SIZ_X - 2, + sliderYpos); + MultiSetZPosition(SlideObject, Z_INV_MFRAME); + } else { + sliderYpos = sliderYmin = y + 9; + sliderYmax = 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(hWinParts); + + // 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+I_SEPARATION) + SuppH; + eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+I_SEPARATION) + SuppV; + + // Which window frame corners to use + if (TinselV2 && (ino == INV_CONV)) { + TL = IX_TL; + TR = IX2_TR4; + BL = IX_BL; + BR = IX_RBR; + } else 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 + NM_TBT); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Bottom of header box + if (filling == FULL) { + if (TinselV2) { + retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth, invY + NM_TBB); + MultiSetZPosition(retObj[n], zpos); + n++; + } else { + 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 + NM_BSY); + + MultiSetZPosition(retObj[n], zpos); + n++; + } + if (SuppH) { + int offx = TLwidth + eH - (TinselV2 ? ITEM_WIDTH + I_SEPARATION : 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 + NM_TBT); + 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 + NM_BSY); + + 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 + NM_LSX, 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); + if (TinselV2) + MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth + NM_SBL, invY + TLheight); + else + 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 + NM_RSX, invY + TLheight); + MultiSetZPosition(retObj[n], zpos); + n++; + } + if (SuppV) { + int offy = TLheight + eV - (TinselV2 ? ITEM_HEIGHT + I_SEPARATION : 26); + int minAmount = TinselV2 ? 20 : 5; + if (offy < minAmount) + offy = minAmount; + + // Left side extra + retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); + MultiSetAniXY(retObj[n], invX + NM_LSX, 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 + NM_RSX, invY + offy); + MultiSetZPosition(retObj[n], zpos); + n++; + } + + OBJECT **rect, **title; + + // Draw background, slider and icons + if (TinselV2 && (filling != EMPTY)) { + AddBackground(&retObj[n++], eH, eV); + AddTitle(&retObj[n++], eH); + } + + if (filling == FULL) { + if (!TinselV2) { + rect = &retObj[n++]; + title = &retObj[n++]; + + AddBackground(rect, title, eH, eV, FROM_HANDLE); + } + + if (ino == INV_CONV) { + SlideObject = NULL; + + if (TinselV2) { + // !!!!! MAGIC NUMBER ALERT !!!!! + // Make sure it's big enough for the heading + if (MultiLeftmost(retObj[n-1]) < InvD[INV_CONV].inventoryX + 10) { + InvD[INV_CONV].NoofHicons++; + ConstructInventory(FULL); + } + } + } else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) { + sliderYmin = TLheight - (TinselV2 ? 2 : 1); + sliderYmax = TLheight + eV + (TinselV2 ? 12 : 10); + AddSlider(&retObj[n++], pfilm); + } + + FillInInventory(); + } + else if (filling == CONF) { + if (!TinselV2) { + rect = &retObj[n++]; + title = &retObj[n++]; + + AddBackground(rect, title, eH, eV, FROM_STRING); + if (cd.bExtraWin) + n += AddExtraWindow(invX, invY, &retObj[n]); + } else { + if (cd.bExtraWin) + 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].bMoveable && 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; + IMAGE *pim; + + // Get pointer to image + pim = GetImageFromFilm(hWinParts, num, &pfreel); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BgPal()); + + 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_HEADER: + 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! + PMOVER pMover = TinselV2 ? GetMover(GetLeadId()) : NULL; + + switch (index) { + case INV_NOICON: + return; + + case INV_CLOSEICON: + thisIcon = -1; // Postamble + break; + + case INV_OPENICON: + // Store the direction the lead character is facing in when the conversation starts + if (TinselV2) + initialDirection = GetMoverDirection(pMover); + thisIcon = -2; // Preamble + break; + + default: + thisIcon = InvD[ino].contents[index]; + break; + } + + if (!TinselV2) + RunPolyTinselCode(thisConvPoly, CONVERSE, PLR_NOEVENT, true); + else { + // If the lead's direction has changed for any reason (such as having broken the + // fourth wall and talked to the screen), reset back to the original direction + DIRECTION currDirection = GetMoverDirection(pMover); + if (currDirection != initialDirection) { + SetMoverDirection(pMover, initialDirection); + SetMoverStanding(pMover); + } + + if (thisConvPoly != NOPOLY) + PolygonEvent(nullContext, thisConvPoly, CONVERSE, 0, false, 0); + else + ActorEvent(nullContext, thisConvActor, CONVERSE, false, 0); + } + +} + +/** + * Called to specify whether conversation window is going to + * appear at the top or bottom of the screen. + * Also to specify which polygon or actor is opening the conversation. + * + * Note: ano may (will probably) be set when it's a polygon. + */ +void SetConvDetails(CONV_PARAM fn, HPOLYGON hPoly, int ano) { + thisConvFn = fn; + thisConvPoly = hPoly; + thisConvActor = ano; + + bMoveOnUnHide = true; + + // Get the Actor Tag's or Tagged Actor's label for the conversation window title + if (hPoly != NOPOLY) { + int x, y; + GetTagTag(hPoly, &InvD[INV_CONV].hInvTitle, &x, &y); + } else { + InvD[INV_CONV].hInvTitle = GetActorTagHandle(ano); + } +} + +/*-------------------------------------------------------------------------*/ + +/** + * Add an icon to the permanent conversation list. + */ +void PermaConvIcon(int icon, bool bEnd) { + int i; + + // See if it's already there + for (i = 0; i < numPermIcons; i++) { + if (permIcons[i] == icon) + break; + } + + // Add it if it isn't already there + if (i == numPermIcons) { + assert(numPermIcons < MAX_PERMICONS); + + if (bEnd || !numEndIcons) { + // Add it at the end + permIcons[numPermIcons++] = icon; + if (bEnd) + numEndIcons++; + } else { + // Insert before end icons + memmove(&permIcons[numPermIcons-numEndIcons+1], + &permIcons[numPermIcons-numEndIcons], + numEndIcons * sizeof(int)); + permIcons[numPermIcons-numEndIcons] = icon; + numPermIcons++; + } + } +} + +/*-------------------------------------------------------------------------*/ + +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 GetIcon(void) { + return thisIcon; +} + +void CloseDownConv(void) { + if (InventoryState == ACTIVE_INV && ino == INV_CONV) { + KillInventory(); + } +} + +void HideConversation(bool bHide) { + int aniX, aniY; + int i; + + if (InventoryState == ACTIVE_INV && ino == INV_CONV) { + if (bHide) { + // Move all the window and icons off-screen + 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); + } + + // Window is hidden + InventoryHidden = true; + + // Remove any labels + InvLabels(false, 0, 0); + } else { + // Window is not hidden + InventoryHidden = false; + + if (TinselV2 && ItemsChanged) + // Just rebuild the whole thing + ConstructInventory(FULL); + else { + // Move it all back on-screen + 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 (TinselV2 || !ItemsChanged) { + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { + MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0); + } + } + } + + if (TinselV2 && bMoveOnUnHide) { + /* + * First time, position it appropriately + */ + int left, centre; + int x, y, deltay; + + // Only do it once per conversation + bMoveOnUnHide = false; + + // Current centre of the window + left = MultiLeftmost(RectObject); + centre = (MultiRightmost(RectObject) + left) / 2; + + // Get the x-offset for the conversation window + if (thisConvActor) { + int Loffset, Toffset; + + GetActorMidTop(thisConvActor, &x, &y); + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + x -= Loffset; + y -= Toffset; + } else { + x = SCREEN_WIDTH / 2; + y = SCREEN_BOX_HEIGHT2 / 2; + } + + // Save old y-position + deltay = InvD[INV_CONV].inventoryY; + + switch (thisConvFn) { + case CONV_TOP: + InvD[INV_CONV].inventoryY = SysVar(SV_CONV_TOPY); + break; + + case CONV_BOTTOM: + InvD[INV_CONV].inventoryY = SysVar(SV_CONV_BOTY); + break; + + case CONV_DEF: + InvD[INV_CONV].inventoryY = y - SysVar(SV_CONV_ABOVE_Y); + break; + + default: + break; + } + + // Calculate y change + deltay = InvD[INV_CONV].inventoryY - deltay; + + // Move it all + for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { + MultiMoveRelXY(objArray[i], x - centre, deltay); + } + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { + MultiMoveRelXY(iconArray[i], x - centre, deltay); + } + InvD[INV_CONV].inventoryX += x - centre; + + /* + * Now positioned as worked out + * - but it must be in a sensible place + */ + if (MultiLeftmost(RectObject) < SysVar(SV_CONV_MINX)) + x = SysVar(SV_CONV_MINX) - MultiLeftmost(RectObject); + else if (MultiRightmost(RectObject) > SCREEN_WIDTH - SysVar(SV_CONV_MINX)) + x = SCREEN_WIDTH - SysVar(SV_CONV_MINX) - MultiRightmost(RectObject); + else + x = 0; + + if (thisConvFn == CONV_DEF && MultiHighest(RectObject) < SysVar(SV_CONV_MINY) + && thisConvActor) { + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + y = GetActorBottom(thisConvActor) - MultiHighest(RectObject) + + SysVar(SV_CONV_BELOW_Y); + y -= Toffset; + } + else + y = 0; + + if (x || y) { + for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { + MultiMoveRelXY(objArray[i], x, y); + } + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { + MultiMoveRelXY(iconArray[i], x, y); + } + InvD[INV_CONV].inventoryX += x; + InvD[INV_CONV].inventoryY += y; + } + + /* + * Oh shit! We might have gone off the bottom + */ + if (MultiLowest(RectObject) > SCREEN_BOX_HEIGHT2 - SysVar(SV_CONV_MINY)) { + y = (SCREEN_BOX_HEIGHT2 - SysVar(SV_CONV_MINY)) - MultiLowest(RectObject); + for (i = 0; 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); + } + InvD[INV_CONV].inventoryY += y; + } + } + + GetCursorXY(&aniX, &aniY, false); + InvLabels(true, aniX, aniY); + } + } +} + +bool ConvIsHidden(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 || invno == INV_MENU); // Trying to open illegal inventory + + if (InventoryState == IDLE_INV) { + bReOpenMenu = false; // Better safe than sorry... + + DisableTags(); // Tags disabled during inventory + if (TinselV2) + DisablePointing(nullContext); // Pointing disabled during inventory + + if (invno == INV_CONV) { // Conversation window? + if (TinselV2) + // Quiet please.. + _vm->_pcmMusic->dim(false); + + // Start conversation with permanent contents + memset(InvD[INV_CONV].contents, 0, MAX_ININV*sizeof(int)); + memcpy(InvD[INV_CONV].contents, permIcons, numPermIcons*sizeof(int)); + InvD[INV_CONV].NoofItems = numPermIcons; + if (TinselV2) + InvD[INV_CONV].NoofHicons = numPermIcons; + else + thisIcon = 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 SetMenuGlobals(CONFINIT *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; + + if (TinselV2) { + if ((ci->ixHeading != NO_HEADING) && SysString(ci->ixHeading)) + InvD[INV_MENU].hInvTitle = SysString(ci->ixHeading); + else + InvD[INV_MENU].hInvTitle = NO_HEADING; + } +} + +/** + * PopupConf + */ +void OpenMenu(CONFTYPE menuType) { + int curX, curY; + + if (InventoryState != IDLE_INV) + return; + + InvD[INV_CONF].resizable = false; + InvD[INV_CONF].bMoveable = false; + + switch (menuType) { + case MAIN_MENU: + SetMenuGlobals(&ciOption); + break; + + case SAVE_MENU: + if (!TinselV2) + SetCursorScreenXY(262, 91); + SetMenuGlobals(&ciSave); + cd.editableRgroup = true; + FirstFile(0); + break; + + case LOAD_MENU: + SetMenuGlobals(&ciLoad); + cd.editableRgroup = false; + FirstFile(0); + break; + + case RESTART_MENU: + if (TinselV2) + SetCursorScreenXY(360, 153); + else if (_vm->getLanguage() == Common::JA_JPN) + SetCursorScreenXY(180, 106); + else + SetCursorScreenXY(180, 90); + + SetMenuGlobals(&ciRestart); + break; + + case SOUND_MENU: + if (TinselV2) + displayedLanguage = TextLanguage(); + SetMenuGlobals(&ciSound); + break; + + case CONTROLS_MENU: + SetMenuGlobals(&ciControl); + break; + + case QUIT_MENU: + if (TinselV2) + SetCursorScreenXY(360, 153); + else if (_vm->getLanguage() == Common::JA_JPN) + SetCursorScreenXY(180, 106); + else + SetCursorScreenXY(180, 90); + + SetMenuGlobals(&ciQuit); + break; + + case HOPPER_MENU1: + PrimeSceneHopper(); + SetMenuGlobals(&ciHopper1); + cd.editableRgroup = false; + RememberChosenScene(); + FirstScene(0); + break; + + case HOPPER_MENU2: + SetMenuGlobals(&ciHopper2); + cd.editableRgroup = false; + SetChosenScene(); + FirstEntry(0); + break; + + case SUBTITLES_MENU: + if (_vm->getFeatures() & GF_USE_3FLAGS) { + ciSubtitles.v = 6; + ciSubtitles.Box = subtitlesBox3Flags; + ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox3Flags); + } else if (_vm->getFeatures() & GF_USE_4FLAGS) { + ciSubtitles.v = 6; + ciSubtitles.Box = subtitlesBox4Flags; + ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox4Flags); + } else if (_vm->getFeatures() & GF_USE_5FLAGS) { + ciSubtitles.v = 6; + ciSubtitles.Box = subtitlesBox4Flags; + ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox4Flags); + } else { + ciSubtitles.v = 3; + ciSubtitles.Box = subtitlesBox; + ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox); + } + + SetMenuGlobals(&ciSubtitles); + break; + + case TOP_WINDOW: + SetMenuGlobals(&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); + + // Make initial box selections if appropriate + if (menuType == SAVE_MENU || menuType == LOAD_MENU + || menuType == HOPPER_MENU1 || menuType == HOPPER_MENU2) + Select(0, false); + else if (menuType == SUBTITLES_MENU) { + if (_vm->getFeatures() & GF_USE_3FLAGS) { + // VERY quick dirty bodges + if (g_language == TXT_FRENCH) + Select(0, false); + else if (g_language == TXT_GERMAN) + Select(1, false); + else + Select(2, false); + } else if (_vm->getFeatures() & GF_USE_4FLAGS) { + Select(g_language-1, false); + } else if (_vm->getFeatures() & GF_USE_5FLAGS) { + Select(g_language, false); + } + } + + 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(); + if (TinselV2) + EnablePointing(); + + InvD[ino].bMax = InventoryMaximised; + + UnHideCursorTrails(); + _vm->divertKeyInput(NULL); + } + + InventoryState = IDLE_INV; + + if (bReOpenMenu) { + bReOpenMenu = false; + OpenMenu(MAIN_MENU); + + // Write config changes + WriteConfig(); + + } else if (ino == INV_CONF) + InventoryIconCursor(false); + + if (TinselV2) + // Pump up the volume + if (ino == INV_CONV) + _vm->_pcmMusic->unDim(false); + +} + +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, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (NumberOfLanguages() <= 1) + bNoLanguage = true; + + 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->quitGame(); + break; + case CLOSEWIN: + KillInventory(); + if ((cd.box == hopperBox1) || (cd.box == hopperBox2)) + FreeSceneHopper(); + break; + case OPENLOAD: + KillInventory(); + OpenMenu(LOAD_MENU); + break; + case OPENSAVE: + KillInventory(); + OpenMenu(SAVE_MENU); + break; + case OPENREST: + KillInventory(); + OpenMenu(RESTART_MENU); + break; + case OPENSOUND: + KillInventory(); + OpenMenu(SOUND_MENU); + break; + case OPENCONT: + KillInventory(); + OpenMenu(CONTROLS_MENU); + break; + #ifndef JAPAN + case OPENSUBT: + KillInventory(); + OpenMenu(SUBTITLES_MENU); + break; + #endif + case OPENQUIT: + KillInventory(); + OpenMenu(QUIT_MENU); + 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 + case HOPPER2: + KillInventory(); + OpenMenu(HOPPER_MENU2); + break; + case BF_CHANGESCENE: + KillInventory(); + HopAction(); + FreeSceneHopper(); + break; + 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 = sliderYpos; + lasti = NearestSlideY(sliderYpos); + break; + + case S_SLIDE: // Y-movement during drag + newY = newY + y; // New y-position + + if (newY < sliderYmin) + gotoY = sliderYmin; // Above top limit + else if (newY > sliderYmax) + gotoY = sliderYmax; // Below bottom limit + else + gotoY = newY; // Hunky-Dory + + // Move slider to new position + MultiMoveRelXY(SlideObject, 0, gotoY - sliderYpos); + sliderYpos = gotoY; + + // Re-draw icons if necessary + ati = NearestSlideY(sliderYpos); + 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(sliderYpos); + 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 = sliderYpos; + break; + + case S_SLIDE: // Y-movement during drag + newY = newY + y; // New y-position + + if (newY < sliderYmin) + gotoY = sliderYmin; // Above top limit + else if (newY > sliderYmax) + gotoY = sliderYmax; // Below bottom limit + else + gotoY = newY; // Hunky-Dory + + // Move slider to new position + if (TinselV2) + MultiMoveRelXY(SlideObject, 0, gotoY - sliderYpos); + sliderYpos = gotoY; + + fc = cd.extraBase; + + if ((cd.box == saveBox || cd.box == loadBox)) + FirstFile((sliderYpos - sliderYmin) * (MAX_SAVED_FILES - NUM_RGROUP_BOXES) / + (sliderYmax - sliderYmin)); + else if (cd.box == hopperBox1) + FirstScene((sliderYpos - sliderYmin) * (numScenes - NUM_RGROUP_BOXES) / sliderRange); + else if (cd.box == hopperBox2) + FirstEntry((sliderYpos - sliderYmin) * (numEntries - NUM_RGROUP_BOXES) / sliderRange); + + // If extraBase has changed... + if (fc != cd.extraBase) { + AddBoxes(false); + fc -= cd.extraBase; + cd.selBox += fc; + + // Ensure within legal limits + if (cd.selBox < 0) + cd.selBox = 0; + else if (cd.selBox >= NUM_RGROUP_BOXES) + cd.selBox = NUM_RGROUP_BOXES-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 == MUSICVOL) + 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 == MUSICVOL) + 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 + if (ino == INV_CONF && cd.box == subtitlesBox) + Select(g_language, false); + 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 = WhichMenuBox(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_HEADER: + if (InvD[ino].bMoveable) { + 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; + ProcessedProvisional(); + } + + // Cursor could well now be inappropriate + InvCursor(IC_AREA, curX, curY); + + Xchange = Ychange = 0; // Probably no need, but does no harm! +} + +static void MenuPageDown(void) { + if (cd.box == loadBox || cd.box == saveBox) { + if (cd.extraBase < MAX_SAVED_FILES-NUM_RGROUP_BOXES) { + FirstFile(cd.extraBase+(NUM_RGROUP_BOXES - 1)); + AddBoxes(true); + cd.selBox = NUM_RGROUP_BOXES - 1; + Select(cd.selBox, true); + } + } else if (cd.box == hopperBox1) { + if (cd.extraBase < numScenes - NUM_RGROUP_BOXES) + { + FirstScene(cd.extraBase + (NUM_RGROUP_BOXES - 1)); + AddBoxes(true); + if (cd.selBox) + cd.selBox = NUM_RGROUP_BOXES - 1; + Select(cd.selBox, true); + } + } else if (cd.box == hopperBox2) { + if (cd.extraBase < numEntries - NUM_RGROUP_BOXES) + { + FirstEntry(cd.extraBase+(NUM_RGROUP_BOXES - 1)); + AddBoxes(true); + if (cd.selBox) + cd.selBox = NUM_RGROUP_BOXES - 1; + Select(cd.selBox, true); + } + } +} + +static void MenuPageUp(void) { + if (cd.extraBase > 0) { + if (cd.box == loadBox || cd.box == saveBox) + FirstFile(cd.extraBase-(NUM_RGROUP_BOXES - 1)); + else if (cd.box == hopperBox1) + FirstScene(cd.extraBase-(NUM_RGROUP_BOXES - 1)); + else if (cd.box == hopperBox2) + FirstEntry(cd.extraBase-(NUM_RGROUP_BOXES - 1)); + else + return; + + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } +} + +/**************************************************************************/ +/************** Incoming events - further processing **********************/ +/**************************************************************************/ + +/** + * MenuAction + */ +void MenuAction(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: + case TOGGLE1: + case TOGGLE2: + 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; + case HOPPER2: + KillInventory(); + OpenMenu(HOPPER_MENU2); + break; + case BF_CHANGESCENE: + KillInventory(); + HopAction(); + FreeSceneHopper(); + 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.extraBase > 0) { + if ((cd.box == loadBox) || (cd.box == saveBox)) + FirstFile(cd.extraBase - 1); + else if (cd.box == hopperBox1) + FirstScene(cd.extraBase - 1); + else if (cd.box == hopperBox2) + FirstEntry(cd.extraBase - 1); + + AddBoxes(true); + if (cd.selBox < NUM_RGROUP_BOXES - 1) + cd.selBox += 1; + Select(cd.selBox, true); + } + break; + case IB_DOWN: // Scroll down + if ((cd.box == loadBox) || (cd.box == saveBox)) { + if (cd.extraBase < MAX_SAVED_FILES - NUM_RGROUP_BOXES) { + FirstFile(cd.extraBase + 1); + AddBoxes(true); + if (cd.selBox) + cd.selBox -= 1; + Select(cd.selBox, true); + } + } else if (cd.box == hopperBox1) { + if (cd.extraBase < numScenes - NUM_RGROUP_BOXES) { + FirstScene(cd.extraBase + 1); + AddBoxes(true); + if (cd.selBox) + cd.selBox -= 1; + Select(cd.selBox, true); + } + } else if (cd.box == hopperBox2) { + if (cd.extraBase < numEntries - NUM_RGROUP_BOXES) { + FirstEntry(cd.extraBase + 1); + AddBoxes(true); + if (cd.selBox) + cd.selBox -= 1; + Select(cd.selBox, true); + } + } + break; + + case IB_SLIDE_UP: + MenuPageUp(); + break; + + case IB_SLIDE_DOWN: + MenuPageDown(); + 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].contents[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].contents[index + 1], &InvD[ino].contents[index], (hiIndex-index)*sizeof(int)); + InvD[ino].contents[index] = HeldItem; + } else if (index > hiIndex) { + memmove(&InvD[ino].contents[hiIndex], &InvD[ino].contents[hiIndex+1], (index-hiIndex)*sizeof(int)); + InvD[ino].contents[index] = HeldItem; + } else { + InvD[ino].contents[index] = HeldItem; + } + + HeldItem = INV_NOICON; + ItemsChanged = true; + DelAuxCursor(); + RestoreMainCursor(); + GetCursorXY(&aniX, &aniY, false); + InvCursor(IC_DROP, aniX, aniY); +} + +void InvPdProcess(CORO_PARAM, const void *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 + const int *pindex = (const int *)param; + + InvPutDown(*pindex); + + CORO_END_CODE; +} + +void InvPickup(int index) { + INV_OBJECT *invObj; + + // Do nothing if not clicked on anything + if (index == NOOBJECT) + return; + + // If not holding anything + if (HeldItem == INV_NOICON && InvD[ino].contents[index] && + (!TinselV2 || InvD[ino].contents[index] != HeldItem)) { + // Pick-up + invObj = GetInvObject(InvD[ino].contents[index]); + thisIcon = InvD[ino].contents[index]; + if (TinselV2) + InvTinselEvent(invObj, PICKUP, INV_PICKUP, index); + else if (invObj->hScript) + InvTinselEvent(invObj, WALKTO, INV_PICKUP, index); + + } else if (HeldItem != INV_NOICON) { + // Put-down + invObj = GetInvObject(HeldItem); + + // If DROPCODE set, send event, otherwise it's a putdown + if (invObj->attribute & IO_DROPCODE && invObj->hScript) + InvTinselEvent(invObj, PUTDOWN, INV_PICKUP, index); + + else if (!(invObj->attribute & IO_ONLYINV1 && ino != INV_1) + && !(invObj->attribute & IO_ONLYINV2 && ino != INV_2)) { + if (TinselV2) + InvPutDown(index); + else + g_scheduler->createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index)); + } + } +} + +/** + * Handle WALKTO event (Pick up/put down event) + */ +static void InvWalkTo(const Common::Point &coOrds) { + int i; + + switch (InvArea(coOrds.x, coOrds.y)) { + case I_NOTIN: + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + if ((cd.box == hopperBox1) || (cd.box == hopperBox2)) + FreeSceneHopper(); + 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) + MenuAction(WhichMenuBox(coOrds.x, coOrds.y, false), false); + } else { + Common::Point pt = coOrds; + i = InvItem(pt, false); + + // To cater for drop in dead space between icons, + // look 1 pixel right, then 1 down, then 1 right and down. + if (i == INV_NOICON && HeldItem != INV_NOICON && + (ino == INV_1 || ino == INV_2)) { + pt.x += 1; // 1 to the right + i = InvItem(pt, false); + if (i == INV_NOICON) { + pt.x -= 1; // 1 down + pt.y += 1; + i = InvItem(pt, false); + if (i == INV_NOICON) { + pt.x += 1; // 1 down-right + i = InvItem(pt, false); + } + } + } + + if (ino == INV_CONV) { + ConvAction(i); + } else + InvPickup(i); + } + break; + } +} + +void InvAction(void) { + int index; + INV_OBJECT *invObj; + int aniX, aniY; + int i; + + GetCursorXY(&aniX, &aniY, false); + + switch (InvArea(aniX, aniY)) { + case I_BODY: + if (ino == INV_CONF) { + if (!InventoryHidden) + MenuAction(WhichMenuBox(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].contents[index] && InvD[ino].contents[index] != HeldItem) { + invObj = GetInvObject(InvD[ino].contents[index]); + if (TinselV2) + thisIcon = InvD[ino].contents[index]; + if (TinselV2 || (invObj->hScript)) + InvTinselEvent(invObj, ACTION, INV_ACTION, index); + } + } + } + break; + + case I_HEADER: // 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; + } + +} + + +static void InvLook(const Common::Point &coOrds) { + int index; + INV_OBJECT *invObj; + Common::Point pt = coOrds; + + switch (InvArea(pt.x, pt.y)) { + case I_BODY: + index = InvItem(pt, false); + if (index != INV_NOICON) { + if (InvD[ino].contents[index] && InvD[ino].contents[index] != HeldItem) { + invObj = GetInvObject(InvD[ino].contents[index]); + if (invObj->hScript) + InvTinselEvent(invObj, LOOK, INV_LOOK, index); + } + } + break; + + case I_NOTIN: + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + KillInventory(); + break; + } +} + + +/**************************************************************************/ +/********************* Incoming events ************************************/ +/**************************************************************************/ + +void EventToInventory(PLR_EVENT pEvent, const Common::Point &coOrds) { + if (InventoryHidden) + return; + + switch (pEvent) { + case PLR_PROV_WALKTO: + if (MenuActive()) { + ProcessedProvisional(); + InvWalkTo(coOrds); + } + break; + + case PLR_WALKTO: // PLR_SLEFT + InvWalkTo(coOrds); + break; + + case INV_LOOK: // PLR_SRIGHT + if (MenuActive()) + InvWalkTo(coOrds); + else + InvLook(coOrds); + break; + + case PLR_ACTION: // PLR_DLEFT + if (InvDragging != ID_MDCONT) + InvDragEnd(); + InvAction(); + break; + + case PLR_DRAG1_START: // Left drag start + InvDragStart(); + break; + + case PLR_DRAG1_END: // Left drag end + InvDragEnd(); + break; + + case PLR_ESCAPE: + if (MenuActive()) { + if (cd.box != optionBox && cd.box != hopperBox1 && cd.box != hopperBox2) + bReOpenMenu = true; + if ((cd.box == hopperBox1) || (cd.box == hopperBox2)) + FreeSceneHopper(); + } + CloseInventory(); + break; + + case PLR_PGDN: + if (ino == INV_MENU) { + // Only act if load or save screen + MenuPageDown(); + } 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 (int 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 PLR_PGUP: + if (ino == INV_MENU) { + // Only act if load or save screen + MenuPageUp(); + } 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 (int 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 PLR_HOME: + if (ino == INV_MENU) { + // Only act if load or save screen + if (cd.box == loadBox || cd.box == saveBox) + FirstFile(0); + else if (cd.box == hopperBox1) + FirstScene(0); + else if (cd.box == hopperBox2) + FirstEntry(0); + else + break; + + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } else { + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + } + break; + + case PLR_END: + if (ino == INV_MENU) { + if (cd.box == loadBox || cd.box == saveBox) + FirstFile(MAX_SAVED_FILES); // Will get reduced to appropriate value + else if (cd.box == hopperBox1) + FirstScene(numScenes); // Will get reduced to appropriate value + else if (cd.box == hopperBox2) + FirstEntry(numEntries); // Will get reduced to appropriate value + else + break; + + 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: + break; + } +} + +/**************************************************************************/ +/************************* Odds and Ends **********************************/ +/**************************************************************************/ + +/** + * Called from Glitter function invdepict() + * Changes (permanently) the animation film for that object. + */ +void SetObjectFilm(int object, SCNHANDLE hFilm) { + INV_OBJECT *invObj; + + invObj = GetInvObject(object); + invObj->hIconFilm = 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].contents[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].bMoveable); + s.syncAsSint32LE(InvD[i].sNoofHicons); + s.syncAsSint32LE(InvD[i].sNoofVicons); + s.syncAsSint32LE(InvD[i].bMax); + } + + if (TinselV2) { + for (int i = 0; i < numObjects; ++i) + s.syncAsUint32LE(invFilms[i]); + s.syncAsUint32LE(heldFilm); + } +} + +/**************************************************************************/ +/************************ 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; + invObjects = (INV_OBJECT *) cptr; + + if (TinselV2) { + if (invFilms == NULL) + // First time - allocate memory + invFilms = (SCNHANDLE *)MemoryAlloc(DWM_FIXED | DWM_ZEROINIT, numObjects * sizeof(SCNHANDLE)); + + if (invFilms == NULL) + error(NO_MEM, "inventory scripts"); + + // Add defined permanent conversation icons + // and store all the films separately + int i; + INV_OBJECT *pio; + for (i = 0, pio = invObjects; i < numObjects; i++, pio++) { + if (pio->attribute & PERMACONV) + PermaConvIcon(pio->id, pio->attribute & CONVENDITEM); + + invFilms[i] = pio->hIconFilm; + } + } +} + +/** + * 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 + + hWinParts = 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].contents, 0, sizeof(InvD[num].contents)); + 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].bMoveable = 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 MenuActive(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/dialogs.h b/engines/tinsel/dialogs.h new file mode 100644 index 0000000000..faf95c7d1c --- /dev/null +++ b/engines/tinsel/dialogs.h @@ -0,0 +1,168 @@ + +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Inventory related functions + */ + +#ifndef TINSEL_INVENTORY_H // prevent multiple includes +#define TINSEL_INVENTORY_H + +#include "tinsel/dw.h" +#include "tinsel/events.h" // for PLR_EVENT, PLR_EVENT + +namespace Tinsel { + +class Serializer; + +enum { + INV_OPEN = -1, // DW1 only + INV_CONV = 0, + INV_1 = 1, + INV_2 = 2, + INV_CONF = 3, + INV_MENU = 3, // DW2 constant + NUM_INV = 4, + + // Discworld 2 constants + DW2_INV_OPEN = 5, + INV_DEFAULT = 6 +}; + +/** structure of each inventory object */ +struct INV_OBJECT { + int32 id; // inventory objects id + SCNHANDLE hIconFilm; // inventory objects animation film + SCNHANDLE hScript; // inventory objects event handling script + int32 attribute; // inventory object's attribute +}; + +// attribute values - not a bit bit field to prevent portability problems +#define DROPCODE 0x01 +#define ONLYINV1 0x02 +#define ONLYINV2 0x04 +#define DEFINV1 0x08 +#define DEFINV2 0x10 +#define PERMACONV 0x20 +#define CONVENDITEM 0x40 + +void PopUpInventory(int invno); + +enum CONFTYPE { + MAIN_MENU, SAVE_MENU, LOAD_MENU, QUIT_MENU, RESTART_MENU, SOUND_MENU, + CONTROLS_MENU, SUBTITLES_MENU, HOPPER_MENU1, HOPPER_MENU2, TOP_WINDOW +}; + +void OpenMenu(CONFTYPE type); + + +void Xmovement(int x); +void Ymovement(int y); + +void EventToInventory(PLR_EVENT pEvent, const Common::Point &coOrds); +void ButtonToInventory(PLR_EVENT be); +void KeyToInventory(PLR_EVENT ke); + + +int WhichItemHeld(void); + +void HoldItem(int item, bool bKeepFilm = false); +void DropItem(int item); +void ClearInventory(int invno); +void AddToInventory(int invno, int icon, bool hold = false); +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 PermaConvIcon(int icon, bool bEnd = false); + +void convPos(int bpos); +void ConvPoly(HPOLYGON hp); +int GetIcon(void); +void CloseDownConv(void); +void HideConversation(bool hide); +bool ConvIsHidden(void); + +enum { + NOOBJECT = -1, + INV_NOICON = -1, + INV_CLOSEICON = -2, + INV_OPENICON = -3, + INV_HELDNOTIN = -4 +}; + +enum CONV_PARAM { + CONV_DEF, + CONV_BOTTOM, + CONV_END, + CONV_TOP +}; + + +void ConvAction(int index); +void SetConvDetails(CONV_PARAM fn, HPOLYGON hPoly, int ano); +void InventoryIconCursor(bool bNewItem); + +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 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 MenuActive(void); +bool IsConvWindow(void); + +void SetObjectFilm(int object, SCNHANDLE hFilm); + +void ObjectEvent(CORO_PARAM, int objId, TINSEL_EVENT event, bool bWait, int myEscape, bool *result = NULL); + +} // end of namespace Tinsel + +#endif /* TINSEL_INVENTRY_H */ diff --git a/engines/tinsel/drives.cpp b/engines/tinsel/drives.cpp new file mode 100644 index 0000000000..2e87eb1715 --- /dev/null +++ b/engines/tinsel/drives.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$ + * + * CD/drive handling functions + */ + +#include "tinsel/drives.h" +#include "tinsel/scene.h" +#include "tinsel/tinsel.h" +#include "tinsel/sched.h" +#include "tinsel/strres.h" + +namespace Tinsel { + +static char currentCD = '1'; +static uint32 cdFlags[] = { fCd1, fCd2, fCd3, fCd4, fCd5, fCd6, fCd7, fCd8 }; + +static bool bChangingCD = false; +static char nextCD = '\0'; + + +void CdCD(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (bChangingCD) { + if (g_scheduler->getCurrentProcess()) { + // FIXME: CdCD gets passed a nullContext in RegisterGlobals() and + // PrimeSceneHopper(), because I didn't know how to get a proper + // context without converting the whole calling stack to CORO'd + // functions. If these functions really get called while a CD + // change is requested, this needs to be resolved. + if (coroParam == nullContext) + error("CdCD needs context!"); + CORO_SLEEP(1); + } else + error("No current process in CdCD()!"); + } + + CORO_END_CODE; +} + +int GetCurrentCD(void) { + // count from 1 + return (currentCD - '1' + 1); +} + +void SetCD(int flags) { + if (flags & cdFlags[currentCD - '1']) + return; + + error("SetCD() problem"); +} + +int GetCD(int flags) { + int i; + char cd = '\0'; + + if (flags & cdFlags[currentCD - '1']) + return GetCurrentCD(); + + for (i = 0; i < 8; i++) { + if (flags & cdFlags[i]) { + cd = (char)(i + '1'); + break; + } + } + assert(i != 8); + + nextCD = cd; + return cd; +} + +void DoCdChange(void) { + if (bChangingCD) { + _vm->_sound->closeSampleStream(); + _vm->_sound->openSampleFiles(); + ChangeLanguage(TextLanguage()); + bChangingCD = false; + } +} + +void SetNextCD(int cdNumber) { + assert(cdNumber == 1 || cdNumber == 2); + + nextCD = (char)(cdNumber + '1' - 1); +} + +bool GotoCD(void) { + // WORKAROUND: Somehow, CdDoChange() is called twice... Hopefully, this guard helps + if (currentCD == nextCD) + return false; + + currentCD = nextCD; + +/* if (bNoCD) { + strcpy(cdDirectory, hdDirectory); + cdLastBit[3] = currentCD; + strcat(cdDirectory, cdLastBit); + } +*/ + bChangingCD = true; + + return true; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/drives.h b/engines/tinsel/drives.h new file mode 100644 index 0000000000..4441466f82 --- /dev/null +++ b/engines/tinsel/drives.h @@ -0,0 +1,63 @@ +/* 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$ + * + * CD/drive handling functions + */ + +#ifndef TINSEL_DRIVES_H +#define TINSEL_DRIVES_H + +#include "tinsel/dw.h" +#include "tinsel/coroutine.h" + +namespace Tinsel { + +// flags2 +#define fCd1 0x00000001L +#define fCd2 0x00000002L +#define fCd3 0x00000004L +#define fCd4 0x00000008L +#define fCd5 0x00000010L +#define fCd6 0x00000020L +#define fCd7 0x00000040L +#define fCd8 0x00000080L + +#define fAllCds (fCd1|fCd2|fCd3|fCd4|fCd5|fCd6|fCd7|fCd8) + +void DoCdChange(void); + +void CdCD(CORO_PARAM); + +int GetCurrentCD(void); + +void SetCD(int flags); + +int GetCD(int flags); + +void SetNextCD(int cdNumber); + +bool GotoCD(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_DRIVES_H */ diff --git a/engines/tinsel/dw.h b/engines/tinsel/dw.h index 9ceef37858..af5ce99d29 100644 --- a/engines/tinsel/dw.h +++ b/engines/tinsel/dw.h @@ -47,9 +47,8 @@ typedef int HPOLYGON; #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 +#define MASTER_SCNHANDLE 0 // master scene memory handle // the minimum value a integer number can have #define MIN_INT (1 << (8*sizeof(int) - 1)) @@ -70,8 +69,7 @@ typedef int HPOLYGON; #define FIELD_WORLD 0 #define FIELD_STATUS 1 - - +#define ZSHIFT 10 // We don't set the Z position for print and talk text // i.e. it gets a Z position of 0 @@ -101,6 +99,7 @@ typedef int HPOLYGON; #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 +#define MAX_SAVED_ACTOR_Z 512 // Saves actors' Z-ness // Legal non-existant entrance number for LoadScene() #define NO_ENTRY_NUM (-3458) // Magic unlikely number @@ -110,13 +109,19 @@ typedef int HPOLYGON; // Language for the resource strings enum LANGUAGE { - TXT_ENGLISH, - TXT_FRENCH, - TXT_GERMAN, - TXT_ITALIAN, - TXT_SPANISH + TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN, TXT_ITALIAN, TXT_SPANISH, + TXT_HEBREW, TXT_HUNGARIAN, TXT_JAPANESE, TXT_US, + NUM_LANGUAGES }; +#define MAX_READ_RETRIES 5 + +// Definitions used for error messages +#define FILE_IS_CORRUPT "File %s is corrupt" +#define FILE_READ_ERROR "Error reading file %s" +#define CANNOT_FIND_FILE "Cannot find file %s" +#define NO_MEM "Cannot allocate memory for %s!" + } // end of namespace Tinsel #endif // TINSEL_DW_H diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp index 91645da71b..95785e5840 100644 --- a/engines/tinsel/effect.cpp +++ b/engines/tinsel/effect.cpp @@ -41,13 +41,14 @@ #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/sched.h" +#include "tinsel/tinsel.h" namespace Tinsel { struct EP_INIT { HPOLYGON hEpoly; - PMACTOR pActor; + PMOVER pMover; int index; }; @@ -61,24 +62,32 @@ static void EffectProcess(CORO_PARAM, const void *param) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); - EP_INIT *to = (EP_INIT *)param; // get the stuff copied to process when it was created + const EP_INIT *to = (const EP_INIT *)param; // 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); + if (TinselV2) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, to->hEpoly, WALKIN, + GetMoverId(to->pMover), false, 0)); + else + effRunPolyTinselCode(to->hEpoly, WALKIN, to->pMover->actorID); do { CORO_SLEEP(1); - GetMActorPosition(to->pActor, &x, &y); + GetMoverPosition(to->pMover, &x, &y); } while (InPolygon(x, y, EFFECT) == to->hEpoly); // Run effect poly leave script - effRunPolyTinselCode(to->hEpoly, LEAVE, to->pActor->actorID); + if (TinselV2) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, to->hEpoly, WALKOUT, + GetMoverId(to->pMover), false, 0)); + else + effRunPolyTinselCode(to->hEpoly, WALKOUT, to->pMover->actorID); - SetMAinEffectPoly(to->index, false); + SetMoverInEffect(to->index, false); CORO_END_CODE; } @@ -88,7 +97,7 @@ static void EffectProcess(CORO_PARAM, const void *param) { * 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) { +static void FettleEffectPolys(int x, int y, int index, PMOVER pActor) { HPOLYGON hPoly; EP_INIT epi; @@ -97,10 +106,10 @@ static void FettleEffectPolys(int x, int y, int index, PMACTOR pActor) { hPoly = InPolygon(x, y, EFFECT); if (hPoly != NOPOLY) { //Just entered effect polygon - SetMAinEffectPoly(index, true); + SetMoverInEffect(index, true); epi.hEpoly = hPoly; - epi.pActor = pActor; + epi.pMover = pActor; epi.index = index; g_scheduler->createProcess(PID_TCODE, EffectProcess, &epi, sizeof(epi)); } @@ -118,10 +127,10 @@ void EffectPolyProcess(CORO_PARAM, const void *param) { CORO_BEGIN_CODE(_ctx); while (1) { for (int i = 0; i < MAX_MOVERS; i++) { - PMACTOR pActor = GetLiveMover(i); + PMOVER pActor = GetLiveMover(i); if (pActor != NULL) { int x, y; - GetMActorPosition(pActor, &x, &y); + GetMoverPosition(pActor, &x, &y); FettleEffectPolys(x, y, i, pActor); } } diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp index bf9f428fd4..91f75fe71e 100644 --- a/engines/tinsel/events.cpp +++ b/engines/tinsel/events.cpp @@ -26,14 +26,17 @@ */ #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/events.h" #include "tinsel/handle.h" // For LockMem() -#include "tinsel/inventory.h" +#include "tinsel/dialogs.h" #include "tinsel/move.h" // For walking lead actor #include "tinsel/pcode.h" // For Interpret() +#include "tinsel/pdisplay.h" #include "tinsel/pid.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" // For walking lead actor @@ -41,6 +44,7 @@ #include "tinsel/scroll.h" // For DontScrollCursor() #include "tinsel/timers.h" // DwGetCurrentTime() #include "tinsel/tinlib.h" // For control() +#include "tinsel/tinsel.h" #include "tinsel/token.h" namespace Tinsel { @@ -51,22 +55,25 @@ namespace Tinsel { extern int GetTaggedActor(void); extern HPOLYGON GetTaggedPoly(void); - //----------------- EXTERNAL GLOBAL DATA --------------------- -extern bool bEnableF1; - +extern bool bEnableMenu; //----------------- 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 leftEvents = 0; // Single or double, left or right. Or escape key. +static int escEvents = 1; // Escape key +static int userEvents = 0; // Whenever a button or a key comes in static int eCount = 0; +static int controlState; +static bool bStartOff; + +static int controlX, controlY; +static bool bProvNotProcessed = false; + /** * Gets called before each schedule, only 1 user action per schedule * is allowed. @@ -87,12 +94,12 @@ void IncUserEvents(void) { * If this is a double click, the process from the waiting single click * gets killed. */ -void AllowDclick(CORO_PARAM, BUTEVENT be) { +void AllowDclick(CORO_PARAM, PLR_EVENT be) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); - if (be == BE_SLEFT) { + if (be == PLR_SLEFT) { GetToken(TOKEN_LEFT_BUT); CORO_SLEEP(dclickSpeed+1); FreeToken(TOKEN_LEFT_BUT); @@ -103,7 +110,7 @@ void AllowDclick(CORO_PARAM, BUTEVENT be) { break; - } else if (be == BE_DLEFT) { + } else if (be == PLR_DLEFT) { GetToken(TOKEN_LEFT_BUT); FreeToken(TOKEN_LEFT_BUT); } @@ -111,86 +118,112 @@ void AllowDclick(CORO_PARAM, BUTEVENT be) { } /** - * Take control from player, if the player has it. - * Return TRUE if control taken, FALSE if not. + * Re-enables user control */ +void ControlOn(void) { + if (!TinselV2) { + Control(CONTROL_ON); + return; + } -bool GetControl(int param) { - if (TestToken(TOKEN_CONTROL)) { - control(param); - return true; - } else - return false; -} + bEnableMenu = 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; -}; + if (controlState == CONTROL_OFF) { + // Control is on + controlState = CONTROL_ON; + + // Restore cursor to where it was + if (bStartOff == true) + bStartOff = false; + else + SetCursorXY(controlX, controlY); + + // Re-instate cursor + UnHideCursor(); + + // Turn tags back on + if (!InventoryActive()) + EnableTags(); + } +} /** - * Runs glitter code associated with a polygon. + * Takes control from the user */ -static void PolyTinselProcess(CORO_PARAM, const void *param) { - // COROUTINE - CORO_BEGIN_CONTEXT; - INT_CONTEXT *pic; - bool took_control; // Set if this function takes control - CORO_END_CONTEXT(_ctx); +void ControlOff(void) { + if (!TinselV2) { + Control(CONTROL_ON); + return; + } - TP_INIT *to = (TP_INIT *)param; // get the stuff copied to process when it was created + bEnableMenu = false; - CORO_BEGIN_CODE(_ctx); + if (controlState == CONTROL_ON) { + // Control is off + controlState = CONTROL_OFF; - CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click + // Store cursor position + GetCursorXY(&controlX, &controlY, true); - // Control may have gone off during AllowDclick() - if (!TestToken(TOKEN_CONTROL) - && (to->event == WALKTO || to->event == ACTION || to->event == LOOK)) - CORO_KILL_SELF(); + // Blank out cursor + DwHideCursor(); - // Take control, if requested - if (to->take_control) - _ctx->took_control = GetControl(CONTROL_OFF); - else - _ctx->took_control = false; + // Switch off tags + DisableTags(); + } +} - // Hide conversation if appropriate - if (to->event == CONVERSE) - convHide(true); +/** + * Prevent tags and cursor re-appearing + */ +void ControlStartOff(void) { + if (!TinselV2) { + Control(CONTROL_STARTOFF); + return; + } - // Run the code - _ctx->pic = InitInterpretContext(GS_POLYGON, getPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL); - CORO_INVOKE_1(Interpret, _ctx->pic); + bEnableMenu = false; - // Free control if we took it - if (_ctx->took_control) - control(CONTROL_ON); + // Control is off + controlState = CONTROL_OFF; - // Restore conv window if applicable - if (to->event == CONVERSE) - convHide(false); + // Blank out cursor + DwHideCursor(); - CORO_END_CODE; + // Switch off tags + DisableTags(); + + bStartOff = true; } /** - * Runs glitter code associated with a polygon. + * Take control from player, if the player has it. + * Return TRUE if control taken, FALSE if not. */ -void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc) { - TP_INIT to = { hPoly, event, be, tc, 0 }; +bool GetControl(int param) { + if (TinselV2) + return GetControl(); - g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); + else if (TestToken(TOKEN_CONTROL)) { + Control(param); + return true; + } else + return false; +} + +bool GetControl(void) { + if (controlState == CONTROL_ON) { + ControlOff(); + return true; + } else + return false; } -void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor) { - TP_INIT to = { hPoly, event, BE_NONE, false, actor }; +bool ControlIsOn(void) { + if (TinselV2) + return (controlState == CONTROL_ON); - g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); + return TestToken(TOKEN_CONTROL); } //----------------------------------------------------------------------- @@ -206,22 +239,33 @@ struct WP_INIT { static void WalkProcess(CORO_PARAM, const void *param) { // COROUTINE CORO_BEGIN_CONTEXT; - PMACTOR pActor; + PMOVER pMover; + int thisWalk; CORO_END_CONTEXT(_ctx); - WP_INIT *to = (WP_INIT *)param; // get the co-ordinates - copied to process when it was created + const WP_INIT *to = (const WP_INIT *)param; // 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 + _ctx->pMover = GetMover(LEAD_ACTOR); + + if (TinselV2 && MoverIs(_ctx->pMover) && !MoverIsSWalking(_ctx->pMover)) { + assert(_ctx->pMover->hCpath != NOPOLY); // Lead actor is not in a path + + _ctx->thisWalk = SetActorDest(_ctx->pMover, to->x, to->y, false, 0); + DontScrollCursor(); + + while (MoverMoving(_ctx->pMover) && (_ctx->thisWalk == GetWalkNumber(_ctx->pMover))) + CORO_SLEEP(1); + + } else if (!TinselV2 && _ctx->pMover->bActive) { + assert(_ctx->pMover->hCpath != NOPOLY); // Lead actor is not in a path GetToken(TOKEN_LEAD); - SetActorDest(_ctx->pActor, to->x, to->y, false, 0); + SetActorDest(_ctx->pMover, to->x, to->y, false, 0); DontScrollCursor(); - while (MAmoving(_ctx->pActor)) + while (MoverMoving(_ctx->pMover)) CORO_SLEEP(1); FreeToken(TOKEN_LEAD); @@ -230,7 +274,7 @@ static void WalkProcess(CORO_PARAM, const void *param) { CORO_END_CODE; } -void walkto(int x, int y) { +void WalkTo(int x, int y) { WP_INIT to = { x, y }; g_scheduler->createProcess(PID_TCODE, WalkProcess, &to, sizeof(to)); @@ -240,7 +284,7 @@ void walkto(int x, int y) { * 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) { +static void ProcessUserEvent(TINSEL_EVENT uEvent, const Common::Point &coOrds, PLR_EVENT be = PLR_NOEVENT) { int actor; int aniX, aniY; HPOLYGON hPoly; @@ -249,19 +293,34 @@ static void User_Event(USER_EVENT uEvent, BUTEVENT be) { if (++eCount != 1) return; - if ((actor = GetTaggedActor()) != 0) - actorEvent(actor, uEvent, be); - else if ((hPoly = GetTaggedPoly()) != NOPOLY) - RunPolyTinselCode(hPoly, uEvent, be, false); - else { + if ((actor = GetTaggedActor()) != 0) { + // Event for a tagged actor + if (TinselV2) + ActorEvent(nullContext, actor, uEvent, false, 0); + else + ActorEvent(actor, uEvent, be); + } else if ((hPoly = GetTaggedPoly()) != NOPOLY) { + // Event for active tagged polygon + if (!TinselV2) + RunPolyTinselCode(hPoly, uEvent, be, false); + else if (uEvent != PROV_WALKTO) + PolygonEvent(nullContext, hPoly, uEvent, 0, false, 0); + + } 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); + if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY || + (!TinselV2 && ((hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY))) { + if (TinselV2 && (uEvent != PROV_WALKTO)) + PolygonEvent(nullContext, hPoly, uEvent, 0, false, 0); + else if (!TinselV2) + RunPolyTinselCode(hPoly, uEvent, be, false); + } else if ((uEvent == PROV_WALKTO) || (uEvent == WALKTO)) { + if (TinselV2) + ProcessedProvisional(); + WalkTo(aniX, aniY); + } } } @@ -269,137 +328,155 @@ static void User_Event(USER_EVENT uEvent, BUTEVENT be) { /** * ProcessButEvent */ -void ProcessButEvent(BUTEVENT be) { - IncUserEvents(); - +void ProcessButEvent(PLR_EVENT be) { if (bSwapButtons) { switch (be) { - case BE_SLEFT: - be = BE_SRIGHT; + case PLR_SLEFT: + be = PLR_SRIGHT; break; - case BE_DLEFT: - be = BE_DRIGHT; + case PLR_DLEFT: + be = PLR_DRIGHT; break; - case BE_SRIGHT: - be = BE_SLEFT; + case PLR_SRIGHT: + be = PLR_SLEFT; break; - case BE_DRIGHT: - be = BE_DLEFT; + case PLR_DRIGHT: + be = PLR_DLEFT; break; - case BE_LDSTART: - be = BE_RDSTART; + case PLR_DRAG1_START: + be = PLR_DRAG2_START; break; - case BE_LDEND: - be = BE_RDEND; + case PLR_DRAG1_END: + be = PLR_DRAG2_END; break; - case BE_RDSTART: - be = BE_LDSTART; + case PLR_DRAG2_START: + be = PLR_DRAG1_START; break; - case BE_RDEND: - be = BE_LDEND; + case PLR_DRAG2_END: + be = PLR_DRAG1_END; break; default: break; } } -// if (be == BE_SLEFT || be == BE_DLEFT || be == BE_SRIGHT || be == BE_DRIGHT) - if (be == BE_SLEFT || be == BE_SRIGHT) - butEvents++; + PlayerEvent(be, _vm->getMousePosition()); +} - if (!TestToken(TOKEN_CONTROL) && be != BE_LDEND) - return; +/** + * ProcessKeyEvent + */ - 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; - } - } +void ProcessKeyEvent(PLR_EVENT ke) { + // Pass the keyboard event to the player event handler + int xp, yp; + GetCursorXYNoWait(&xp, &yp, true); + const Common::Point mousePos(xp, yp); + + PlayerEvent(ke, mousePos); +} + +#define REAL_ACTION_CHECK if (TinselV2) { \ + if (DwGetCurrentTime() - lastRealAction < 4) return; \ + lastRealAction = DwGetCurrentTime(); \ } /** - * ProcessKeyEvent + * Main interface point for specifying player atcions */ +void PlayerEvent(PLR_EVENT pEvent, const Common::Point &coOrds) { + // Logging of player actions + const char *actionList[] = { + "PLR_PROV_WALKTO", "PLR_WALKTO", "PLR_LOOK", "PLR_ACTION", "PLR_ESCAPE", + "PLR_MENU", "PLR_QUIT", "PLR_PGUP", "PLR_PGDN", "PLR_HOME", "PLR_END", + "PLR_DRAG1_START", "PLR_DRAG1_END", "PLR_DRAG2_START", "PLR_DRAG2_END", + "PLR_JUMP", "PLR_NOEVENT"}; + debugC(DEBUG_BASIC, kTinselDebugActions, "%s - (%d,%d)", + actionList[pEvent], coOrds.x, coOrds.y); + static uint32 lastRealAction = 0; -void ProcessKeyEvent(KEYEVENT ke) { // This stuff to allow F1 key during startup. - if (bEnableF1 && ke == OPTION_KEY) - control(CONTROL_ON); + if (bEnableMenu && pEvent == PLR_MENU) + Control(CONTROL_ON); else IncUserEvents(); - if (ke == ESC_KEY) { - escEvents++; - butEvents++; // Yes, I do mean this + if (pEvent == PLR_ESCAPE) { + ++escEvents; + ++leftEvents; // Yes, I do mean this + } else if ((pEvent == PLR_PROV_WALKTO) + || (pEvent == PLR_WALKTO) + || (pEvent == PLR_LOOK) + || (pEvent == PLR_ACTION)) { + ++leftEvents; } - // FIXME: This comparison is weird - I added (BUTEVENT) cast for now to suppress warning - if (!TestToken(TOKEN_CONTROL) && (BUTEVENT)ke != BE_LDEND) + // Only allow events if player control is on + if (!ControlIsOn() && (pEvent != PLR_DRAG1_END)) return; - switch (ke) { - case QUIT_KEY: - PopUpConf(QUIT); + if (TinselV2 && InventoryActive()) { + int x, y; + PlayfieldGetPos(FIELD_WORLD, &x, &y); + EventToInventory(pEvent, Common::Point(coOrds.x - x, coOrds.y - y)); + return; + } + + switch (pEvent) { + case PLR_QUIT: + OpenMenu(QUIT_MENU); break; - case OPTION_KEY: - PopUpConf(OPTION); + case PLR_MENU: + OpenMenu(MAIN_MENU); break; - case SAVE_KEY: - PopUpConf(SAVE); + case PLR_JUMP: + OpenMenu(HOPPER_MENU1); break; - case LOAD_KEY: - PopUpConf(LOAD); + case PLR_SAVE: + OpenMenu(SAVE_MENU); break; - case WALKTO_KEY: - if (InventoryActive()) - ButtonToInventory(BE_SLEFT); - else - User_Event(WALKTO, BE_NONE); + case PLR_LOAD: + OpenMenu(LOAD_MENU); break; - case ACTION_KEY: - if (InventoryActive()) - ButtonToInventory(BE_DLEFT); + case PLR_PROV_WALKTO: // Provisional WALKTO ! + ProcessUserEvent(PROV_WALKTO, coOrds); + break; + + case PLR_WALKTO: + REAL_ACTION_CHECK; + + if (TinselV2 || !InventoryActive()) + ProcessUserEvent(WALKTO, coOrds, PLR_SLEFT); else - User_Event(ACTION, BE_NONE); + EventToInventory(PLR_SLEFT, coOrds); break; - case LOOK_KEY: - if (InventoryActive()) - ButtonToInventory(BE_SRIGHT); + case PLR_ACTION: + REAL_ACTION_CHECK; + + if (TinselV2 || !InventoryActive()) + ProcessUserEvent(ACTION, coOrds, PLR_DLEFT); else - User_Event(LOOK, BE_NONE); + EventToInventory(PLR_DLEFT, coOrds); break; - case ESC_KEY: - case PGUP_KEY: - case PGDN_KEY: - case HOME_KEY: - case END_KEY: - if (InventoryActive()) - KeyToInventory(ke); + case PLR_LOOK: + REAL_ACTION_CHECK; + + if (TinselV2 || !InventoryActive()) + ProcessUserEvent(LOOK, coOrds, PLR_SRIGHT); + else + EventToInventory(PLR_SRIGHT, coOrds); break; default: + if (InventoryActive()) + EventToInventory(pEvent, coOrds); break; } } @@ -407,7 +484,6 @@ void ProcessKeyEvent(KEYEVENT ke) { /** * For ESCapable Glitter sequences */ - int GetEscEvents(void) { return escEvents; } @@ -415,15 +491,21 @@ int GetEscEvents(void) { /** * For cutting short talk()s etc. */ - int GetLeftEvents(void) { - return butEvents; + return leftEvents; +} + +bool LeftEventChange(int myleftEvent) { + if (leftEvents != myleftEvent) { + ProcessedProvisional(); + return true; + } else + return false; } /** * For waitkey() Glitter function */ - int getUserEvents(void) { return userEvents; } @@ -436,4 +518,154 @@ void resetUserEventTime(void) { lastUserEvent = DwGetCurrentTime(); } +struct PTP_INIT { + HPOLYGON hPoly; // Polygon + TINSEL_EVENT event; // Trigerring event + PLR_EVENT bev; // To allow for double clicks + bool take_control; // Set if control should be taken + // while code is running. + int actor; + + PINT_CONTEXT pic; +}; + +/** + * Runs glitter code associated with a polygon. + */ +void PolyTinselProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + bool bTookControl; // Set if this function takes control + + CORO_END_CONTEXT(_ctx); + + const PTP_INIT *to = (const PTP_INIT *)param; // get the stuff copied to process when it was created + + CORO_BEGIN_CODE(_ctx); + + if (TinselV2) { + + // Take control for CONVERSE events + if (to->event == CONVERSE) { + _ctx->bTookControl = GetControl(); + HideConversation(true); + } else + _ctx->bTookControl = false; + + CORO_INVOKE_1(Interpret, to->pic); + + // Restore conv window if applicable + if (to->event == CONVERSE) { + // Free control if we took it + if (_ctx->bTookControl) + ControlOn(); + + HideConversation(false); + } + + } else { + + 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->bTookControl = GetControl(CONTROL_OFF); + else + _ctx->bTookControl = false; + + // Hide conversation if appropriate + if (to->event == CONVERSE) + HideConversation(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->bTookControl) + Control(CONTROL_ON); + + // Restore conv window if applicable + if (to->event == CONVERSE) + HideConversation(false); + } + + CORO_END_CODE; +} + +/** + * Run the Polygon process with the given event + */ +void PolygonEvent(CORO_PARAM, HPOLYGON hPoly, TINSEL_EVENT tEvent, int actor, bool bWait, + int myEscape, bool *result) { + CORO_BEGIN_CONTEXT; + PTP_INIT to; + PPROCESS pProc; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (result) *result = false; + _ctx->to.hPoly = -1; + _ctx->to.event = tEvent; + _ctx->to.pic = InitInterpretContext(GS_POLYGON, + GetPolyScript(hPoly), + tEvent, + hPoly, // Polygon + actor, // Actor + NULL, // No Object + myEscape); + if (_ctx->to.pic != NULL) { + _ctx->pProc = g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &_ctx->to, sizeof(_ctx->to)); + AttachInterpret(_ctx->to.pic, _ctx->pProc); + + if (bWait) + CORO_INVOKE_2(WaitInterpret,_ctx->pProc, result); + } + + CORO_END_CODE; +} + +/** + * Runs glitter code associated with a polygon. + */ +void RunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, PLR_EVENT be, bool tc) { + PTP_INIT to = { hPoly, event, be, tc, 0, NULL }; + + assert(!TinselV2); + g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +void effRunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, int actor) { + PTP_INIT to = { hPoly, event, PLR_NOEVENT, false, actor, NULL }; + + assert(!TinselV2); + g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +/** + * If provisional event was processed, calling this prevents the + * subsequent 'real' event. + */ +void ProcessedProvisional(void) { + bProvNotProcessed = false; +} + +/** + * Resets the bProvNotProcessed flag + */ +void ProvNotProcessed(void) { + bProvNotProcessed = true; +} + +bool GetProvNotProcessed() { + return bProvNotProcessed; +} + } // end of namespace Tinsel diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h index bc49d68717..b26bb2c5b0 100644 --- a/engines/tinsel/events.h +++ b/engines/tinsel/events.h @@ -29,49 +29,99 @@ #include "tinsel/dw.h" #include "tinsel/coroutine.h" +#include "common/rect.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 + PLR_NOEVENT, PLR_SLEFT, PLR_DLEFT, PLR_SRIGHT, PLR_DRIGHT, + PLR_DRAG1_START, PLR_DRAG1_END, PLR_DRAG2_START, PLR_DRAG2_END, + PLR_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, + PLR_ESCAPE, PLR_QUIT, PLR_SAVE, PLR_LOAD, PLR_MENU, + PLR_PGUP, PLR_PGDN, PLR_HOME, PLR_END, + PLR_WALKTO, PLR_ACTION, PLR_LOOK, NOEVENT_KEY -}; +};*/ + +enum PLR_EVENT { + // action list + PLR_PROV_WALKTO = 0, // Provisional WALKTO ! + PLR_WALKTO = 1, + PLR_LOOK = 2, + PLR_ACTION = 3, + PLR_ESCAPE = 4, + PLR_MENU = 5, + PLR_QUIT = 6, + PLR_PGUP = 7, + PLR_PGDN = 8, + PLR_HOME = 9, + PLR_END = 10, + PLR_DRAG1_START = 11, + PLR_DRAG1_END = 12, + PLR_DRAG2_START = 13, + PLR_DRAG2_END = 14, + PLR_JUMP = 15, // Call up scene hopper + PLR_NOEVENT = 16, + PLR_SAVE = 17, + PLR_LOAD = 18, + + // Aliases used for DW1 actions + PLR_SLEFT = PLR_WALKTO, + PLR_DLEFT = PLR_ACTION, + PLR_SRIGHT = PLR_LOOK, + PLR_DRIGHT = PLR_NOEVENT, + PLR_UNKNOWN = PLR_NOEVENT +} ; + /** * 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. + * + * Note: DW2 renames ENTER & LEAVE to WALKIN & WALKOUT, and has a new LEAVE event */ -enum USER_EVENT { - POINTED, WALKTO, ACTION, LOOK, - ENTER, LEAVE, STARTUP, CONVERSE, - UNPOINT, PUTDOWN, - NOEVENT +enum TINSEL_EVENT { + NOEVENT, STARTUP, CLOSEDOWN, POINTED, UNPOINT, WALKIN, WALKOUT, + PICKUP, PUTDOWN, WALKTO, LOOK, ACTION, CONVERSE, SHOWEVENT, + HIDEEVENT, TALKING, ENDTALK, LEAVE_T2, RESTORE, PROV_WALKTO +}; + +enum TINSEL1_EVENT { + T1_POINTED, T1_WALKTO, T1_ACTION, T1_LOOK, T1_ENTER, T1_LEAVE, T1_STARTUP, T1_CONVERSE, + T1_UNPOINT, T1_PUTDOWN, T1_NOEVENT }; +const TINSEL1_EVENT TINSEL1_EVENT_MAP[] = { + T1_NOEVENT, T1_STARTUP, T1_NOEVENT, T1_POINTED, T1_UNPOINT, T1_ENTER, T1_LEAVE, + T1_NOEVENT, T1_PUTDOWN, T1_WALKTO, T1_LOOK, T1_ACTION, T1_CONVERSE, T1_NOEVENT, + T1_NOEVENT, T1_NOEVENT, T1_NOEVENT, T1_NOEVENT, T1_NOEVENT, T1_NOEVENT +}; -void AllowDclick(CORO_PARAM, BUTEVENT be); +void AllowDclick(CORO_PARAM, PLR_EVENT be); bool GetControl(int param); +bool GetControl(void); +bool ControlIsOn(void); +void ControlOn(void); +void ControlOff(void); +void ControlStartOff(void); -void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc); -void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor); +void RunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, PLR_EVENT be, bool tc); +void effRunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, int actor); -void ProcessButEvent(BUTEVENT be); -void ProcessKeyEvent(KEYEVENT ke); +void ProcessButEvent(PLR_EVENT be); +void ProcessKeyEvent(PLR_EVENT ke); int GetEscEvents(void); int GetLeftEvents(void); +bool LeftEventChange(int myleftEvent); + int getUserEvents(void); uint32 getUserEventTime(void); @@ -79,6 +129,16 @@ void resetUserEventTime(void); void ResetEcount(void); +void PolygonEvent(CORO_PARAM, HPOLYGON hPoly, TINSEL_EVENT tEvent, int actor, bool bWait, + int myEscape, bool *result = NULL); + + +void PlayerEvent(PLR_EVENT pEvent, const Common::Point &coOrds); + +void ProcessedProvisional(void); +void ProvNotProcessed(void); +bool GetProvNotProcessed(); + } // end of namespace Tinsel #endif /* TINSEL_EVENTS_H */ diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp index 0018727ccb..0233dbcf4a 100644 --- a/engines/tinsel/faders.cpp +++ b/engines/tinsel/faders.cpp @@ -24,11 +24,14 @@ * Palette Fader and Flasher processes. */ -#include "tinsel/pid.h" // list of all process IDs -#include "tinsel/sched.h" // scheduler defs +#include "tinsel/actors.h" #include "tinsel/faders.h" // fader defs #include "tinsel/handle.h" #include "tinsel/palette.h" // Palette Manager defs +#include "tinsel/pid.h" // list of all process IDs +#include "tinsel/sched.h" // scheduler defs +#include "tinsel/sysvar.h" +#include "tinsel/tinsel.h" namespace Tinsel { @@ -40,9 +43,7 @@ struct 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' @@ -70,8 +71,18 @@ static COLORREF ScaleColour(COLORREF colour, uint32 colourMult) { */ 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); + if (!TinselV2) + // apply multiplier to RGB components + *pNew = ScaleColour(*pOrig, mult); + else if (i == (TalkColour() - 1)) { + *pNew = GetTalkColourRef(); + *pNew = ScaleColour(*pNew, mult); + } else if (SysVar(SV_TAGCOLOUR) && i == (SysVar(SV_TAGCOLOUR) - 1)) { + *pNew = GetTagColorRef(); + *pNew = ScaleColour(*pNew, mult); + } else { + *pNew = ScaleColour(*pOrig, mult); + } } } @@ -89,10 +100,14 @@ static void FadeProcess(CORO_PARAM, const void *param) { CORO_END_CONTEXT(_ctx); // get the fade data structure - copied to process when it was created - FADE *pFade = (FADE *)param; + const FADE *pFade = (const FADE *)param; CORO_BEGIN_CODE(_ctx); + if (TinselV2) + // Note that this palette is being faded + FadingPalette(pFade->pPalQ, true); + // get pointer to palette - reduce pointer indirection a bit _ctx->pPalette = (PALETTE *)LockMem(pFade->pPalQ->hPal); @@ -100,8 +115,12 @@ static void FadeProcess(CORO_PARAM, const void *param) { // 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); + if (TinselV2) + FadePalette(_ctx->fadeRGB, pFade->pPalQ->palRGB, + FROM_LE_32(pFade->pPalQ->numColours), (uint32) *_ctx->pColMult); + else + 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); @@ -110,6 +129,10 @@ static void FadeProcess(CORO_PARAM, const void *param) { CORO_SLEEP(1); } + if (TinselV2) + // Note that this palette is being faded + FadingPalette(pFade->pPalQ, false); + CORO_END_CODE; } @@ -122,6 +145,13 @@ static void FadeProcess(CORO_PARAM, const void *param) { static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) { PALQ *pPal; // palette manager iterator + if (TinselV2) { + // The is only ever one cuncurrent fade + // But this could be a fade out and the fade in is still going! + g_scheduler->killMatchingProcess(PID_FADER); + NoFadingPalettes(); + } + // create a process for each palette in the palette queue for (pPal = GetNextPalette(NULL); pPal != NULL; pPal = GetNextPalette(pPal)) { bool bFade = true; @@ -154,22 +184,61 @@ static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) { } } +/** + * Fades a list of palettes down to black. + * 'noFadeTable' is a NULL terminated list of palettes not to fade. + */ +void FadeOutMedium(SCNHANDLE noFadeTable[]) { + // Fixed point fade multiplier table + static const long fadeout[] = {0xea00, 0xd000, 0xb600, 0x9c00, + 0x8200, 0x6800, 0x4e00, 0x3400, 0x1a00, 0, -1}; + + // call generic fader + Fader(fadeout, noFadeTable); +} + /** * Fades a list of palettes down to black. * @param noFadeTable A NULL terminated list of palettes not to fade. */ void FadeOutFast(SCNHANDLE noFadeTable[]) { + // Fixed point fade multiplier table + static const long fadeout[] = {0xd000, 0xa000, 0x7000, 0x4000, 0x1000, 0, -1}; + // call generic fader Fader(fadeout, noFadeTable); } +/** + * Fades a list of palettes from black to their current colours. + * 'noFadeTable' is a NULL terminated list of palettes not to fade. + */ +void FadeInMedium(SCNHANDLE noFadeTable[]) { + // Fade multiplier table + static const long fadein[] = {0, 0x1a00, 0x3400, 0x4e00, 0x6800, + 0x8200, 0x9c00, 0xb600, 0xd000, 0xea00, 0x10000L, -1}; + + // call generic fader + Fader(fadein, 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[]) { + // Fade multiplier table + static const long fadein[] = {0, 0x1000, 0x4000, 0x7000, 0xa000, 0xd000, 0x10000L, -1}; + // call generic fader Fader(fadein, noFadeTable); } +void PokeInTagColour(void) { + if (SysVar(SV_TAGCOLOUR)) { + static COLORREF c = GetActorRGB(-1); + UpdateDACqueue(SysVar(SV_TAGCOLOUR), 1, &c); + } +} + } // end of namespace Tinsel diff --git a/engines/tinsel/faders.h b/engines/tinsel/faders.h index 1e9336fae8..6e716c3604 100644 --- a/engines/tinsel/faders.h +++ b/engines/tinsel/faders.h @@ -28,16 +28,15 @@ #define TINSEL_FADERS_H #include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/tinsel.h" namespace Tinsel { -enum { - /** - * Number of iterations in a fade out. - * Must match which FadeOut() is in use. - */ - COUNTOUT_COUNT = 6 -}; +/** + * Number of iterations in a fade out. + */ +// FIXME: There seems to be some confusion in Tinsel 2 whether this should be 9 or 6 +#define COUNTOUT_COUNT 6 /*----------------------------------------------------------------------*\ |* Fader Function Prototypes *| @@ -47,8 +46,11 @@ enum { // should not be faded. This parameter can be // NULL - fade all palettes. +void FadeOutMedium(SCNHANDLE noFadeTable[]); void FadeOutFast(SCNHANDLE noFadeTable[]); +void FadeInMedium(SCNHANDLE noFadeTable[]); void FadeInFast(SCNHANDLE noFadeTable[]); +void PokeInTagColour(void); } // end of namespace Tinsel diff --git a/engines/tinsel/font.cpp b/engines/tinsel/font.cpp index 620298867e..10427b6e3b 100644 --- a/engines/tinsel/font.cpp +++ b/engines/tinsel/font.cpp @@ -22,11 +22,14 @@ * $Id$ */ +#include "tinsel/actors.h" #include "tinsel/dw.h" #include "tinsel/font.h" #include "tinsel/handle.h" #include "tinsel/object.h" +#include "tinsel/sysvar.h" #include "tinsel/text.h" +#include "tinsel/tinsel.h" namespace Tinsel { @@ -35,48 +38,63 @@ namespace Tinsel { static char tBuffer[TBUFSZ]; static SCNHANDLE hTagFont = 0, hTalkFont = 0; +static SCNHANDLE hRegularTalkFont = 0, hRegularTagFont = 0; /** * Return address of tBuffer */ -char *tBufferAddr() { +char *TextBufferAddr() { return tBuffer; } /** * Return hTagFont handle. */ -SCNHANDLE hTagFontHandle() { +SCNHANDLE GetTagFontHandle() { return hTagFont; } /** * Return hTalkFont handle. */ -SCNHANDLE hTalkFontHandle() { +SCNHANDLE GetTalkFontHandle() { return hTalkFont; } /** * Called from dec_tagfont() Glitter function. Store the tag font handle. */ -void TagFontHandle(SCNHANDLE hf) { - hTagFont = hf; // Store the font handle +void SetTagFontHandle(SCNHANDLE hFont) { + hTagFont = hRegularTagFont = hFont; // 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 +void SetTalkFontHandle(SCNHANDLE hFont) { + hTalkFont = hRegularTalkFont = hFont; // Store the font handle } +void SetTempTagFontHandle(SCNHANDLE hFont) { + hTagFont = hFont; +} + +void SetTempTalkFontHandle(SCNHANDLE hFont) { + hTalkFont = hFont; +} + +void ResetFontHandles(void) { + hTagFont = hRegularTagFont; + hTalkFont = hRegularTalkFont; +} + + /** * Poke the background palette into character 0's images. */ -void fettleFontPal(SCNHANDLE fontPal) { +void FettleFontPal(SCNHANDLE fontPal) { const FONT *pFont; IMAGE *pImg; @@ -86,11 +104,23 @@ void fettleFontPal(SCNHANDLE fontPal) { pFont = (const FONT *)LockMem(hTagFont); pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0 - pImg->hImgPal = TO_LE_32(fontPal); + if (!TinselV2) + pImg->hImgPal = TO_LE_32(fontPal); + else + pImg->hImgPal = 0; pFont = (const FONT *)LockMem(hTalkFont); pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0 - pImg->hImgPal = TO_LE_32(fontPal); + if (!TinselV2) + pImg->hImgPal = TO_LE_32(fontPal); + else + pImg->hImgPal = 0; + + if (TinselV2 && SysVar(SV_TAGCOLOUR)) { + static COLORREF c = GetActorRGB(-1); + SetTagColorRef(c); + UpdateDACqueue(SysVar(SV_TAGCOLOUR), 1, &c); + } } } // End of namespace Tinsel diff --git a/engines/tinsel/font.h b/engines/tinsel/font.h index b75c36191c..fe2936eb80 100644 --- a/engines/tinsel/font.h +++ b/engines/tinsel/font.h @@ -31,17 +31,27 @@ namespace Tinsel { // A temporary buffer for extracting text into is defined in font.c -// Accessed using tBufferAddr(), this is how big it is: +// Accessed using TextBufferAddr(), this is how big it is: #define TBUFSZ 512 -char *tBufferAddr(void); -SCNHANDLE hTagFontHandle(void); -SCNHANDLE hTalkFontHandle(void); +char *TextBufferAddr(void); -void TagFontHandle(SCNHANDLE hf); -void TalkFontHandle(SCNHANDLE hf); -void fettleFontPal(SCNHANDLE fontPal); +SCNHANDLE GetTagFontHandle(void); + +SCNHANDLE GetTalkFontHandle(void); + +void SetTagFontHandle(SCNHANDLE hFont); + +void SetTalkFontHandle(SCNHANDLE hFont); + +void SetTempTagFontHandle(SCNHANDLE hFont); + +void SetTempTalkFontHandle(SCNHANDLE hFont); + +void ResetFontHandles(void); + +void FettleFontPal(SCNHANDLE fontPal); } // end of namespace Tinsel diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp index cd0937d944..d1db50bde0 100644 --- a/engines/tinsel/graphics.cpp +++ b/engines/tinsel/graphics.cpp @@ -28,6 +28,7 @@ #include "tinsel/handle.h" // LockMem() #include "tinsel/object.h" #include "tinsel/palette.h" +#include "tinsel/scene.h" #include "tinsel/tinsel.h" namespace Tinsel { @@ -46,7 +47,6 @@ extern uint8 transPalette[MAX_COLOURS]; /** * 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; @@ -156,9 +156,90 @@ static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyCl } /** - * Fill the destination area with a constant colour + * Tinsel 2 Straight rendering with transparency support */ +static void t2WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool horizFlipped) { + // Setup for correct clipping of object edges + int yClip = applyClipping ? pObj->topClip : 0; + if (applyClipping) + pObj->height -= pObj->botClip; + int numBytes; + int clipAmount; + + for (int y = 0; y < pObj->height; ++y) { + // Get the position to start writing out from + uint8 *tempP = !horizFlipped ? destP : + destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1; + int leftClip = applyClipping ? pObj->leftClip : 0; + int rightClip = applyClipping ? pObj->rightClip : 0; + if (horizFlipped) + SWAP(leftClip, rightClip); + + int x = 0; + while (x < pObj->width) { + // Get the next opcode + numBytes = *srcP++; + if (numBytes & 0x80) { + // Run length following + numBytes &= 0x7f; + clipAmount = MIN(numBytes, leftClip); + leftClip -= clipAmount; + x+= clipAmount; + + int runLength = numBytes - clipAmount; + uint8 colour = *srcP++; + + if ((yClip == 0) && (runLength > 0) && (colour != 0)) { + runLength = MIN(runLength, pObj->width - rightClip - x); + + if (runLength > 0) { + // Non-transparent run length + colour += pObj->constant; + if (horizFlipped) + Common::set_to(tempP - runLength + 1, tempP + 1, colour); + else + Common::set_to(tempP, tempP + runLength, colour); + } + } + + if (horizFlipped) + tempP -= runLength; + else + tempP += runLength; + + x += numBytes - clipAmount; + + } else { + // Dump a length of pixels + clipAmount = MIN(numBytes, leftClip); + leftClip -= clipAmount; + srcP += clipAmount; + int runLength = numBytes - clipAmount; + x += numBytes - runLength; + + for (int xp = 0; xp < runLength; ++xp) { + if ((yClip > 0) || (x >= (pObj->width - rightClip))) + ++srcP; + else if (horizFlipped) + *tempP-- = pObj->constant + *srcP++; + else + *tempP++ = pObj->constant + *srcP++; + ++x; + } + } + } + assert(x == pObj->width); + if (yClip > 0) + --yClip; + else + destP += 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; @@ -181,7 +262,6 @@ static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { * 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; @@ -204,165 +284,135 @@ static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { } } - -#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 + * Copies an uncompressed block of data straight to the screen */ +static void WrtAll(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { + int objWidth = pObj->width; -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 + if (applyClipping) { + srcP += (pObj->topClip * pObj->width) + pObj->leftClip; - // Set up the offset between destination lines - pObj->lineoffset = SCREEN_WIDTH - pObj->width - (applyClipping ? pObj->leftClip - pObj->rightClip : 0); + pObj->height -= pObj->topClip + pObj->botClip; + pObj->width -= pObj->leftClip + pObj->rightClip; - // 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; - } - } + if (pObj->width <= 0) + return; + } - --pObj->height; - --pObj->topClip; + for (int y = 0; y < pObj->height; ++y) { + Common::copy(srcP, srcP + pObj->width, destP); + srcP += objWidth; + destP += SCREEN_WIDTH; } +} - // Loop for the required number of rows - while (pObj->height > 0) { +/** + * Renders a packed data stream with a variable sized palette + */ +static void PackedWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, + bool applyClipping, bool horizFlipped, int packingType) { + uint8 numColours = 0; + uint8 *colourTable = NULL; + int topClip = 0; + int xOffset = 0; + int numBytes, colour; + int v; - 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 (applyClipping) { + pObj->height -= pObj->botClip; + topClip = pObj->topClip; + } + + if (packingType == 3) { + // Variable colours + numColours = *srcP++; + colourTable = srcP; + srcP += numColours; + } + + for (int y = 0; y < pObj->height; ++y) { + // Get the position to start writing out from + uint8 *tempP = !horizFlipped ? destP : + destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1; + int leftClip = applyClipping ? pObj->leftClip : 0; + int rightClip = applyClipping ? pObj->rightClip : 0; + if (horizFlipped) + SWAP(leftClip, rightClip); + bool eolFlag = false; + + // Get offset for first pixels in next line + xOffset = *srcP++; + + int x = 0; + while (x < pObj->width) { + // Get next run size and colour to use + for (;;) { + if (xOffset > 0) { + x += xOffset; + + // Reduce offset amount by any remaining left clipping + v = MIN(xOffset, leftClip); + xOffset -= v; + leftClip -= v; + + if (horizFlipped) tempP -= xOffset; else tempP += xOffset; + xOffset = 0; } - - 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; + v = *srcP++; + numBytes = v & 0xf; // No. bytes 1-15 + if (packingType == 3) + colour = colourTable[v >> 4]; + else + colour = pObj->baseCol + (v >> 4); + + if (numBytes != 0) + break; + + numBytes = *srcP++; + if (numBytes >= 16) + break; + + xOffset = numBytes + v; + if (xOffset == 0) { + // End of line encountered + eolFlag = true; + break; } } - 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 (eolFlag) + break; - if (opcodeOrLen >= 0) { - // Copy the specified number of bytes - int remainder = 4 - (opcodeOrLen % 4); + // Apply clipping on byte sequence + v = MIN(numBytes, leftClip); + leftClip -= v; + numBytes -= v; + x += v; - if (width < endWidth) { - // Shorten run length by right clipping - remainder += (pObj->rightClip - width); - opcodeOrLen -= (pObj->rightClip - width); + while (numBytes-- > 0) { + if ((topClip == 0) && (x < (pObj->width - rightClip))) { + *tempP = colour; + if (horizFlipped) --tempP; else ++tempP; } - - 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; + ++x; } } + assert(x <= pObj->width); - // 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; - } + if (!eolFlag) { + // Assert that the next bytes signal a line end + assert((*srcP++ & 0xf) == 0); + assert(*srcP++ == 0); } - --pObj->height; - destP += pObj->lineoffset; + if (topClip > 0) + --topClip; + else + destP += SCREEN_WIDTH; } } -#endif //----------------- MAIN FUNCTIONS --------------------- @@ -380,8 +430,10 @@ void ClearScreen() { * Updates the screen surface within the following rectangle */ void UpdateScreenRect(const Common::Rect &pClip) { - byte *pDest = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top); - g_system->copyRectToScreen(pDest, _vm->screen().pitch, pClip.left, pClip.top, pClip.width(), pClip.height()); + int yOffset = (g_system->getHeight() - SCREEN_HEIGHT) / 2; + byte *pSrc = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top); + g_system->copyRectToScreen(pSrc, _vm->screen().pitch, pClip.left, pClip.top + yOffset, + pClip.width(), pClip.height()); g_system->updateScreen(); } @@ -398,11 +450,17 @@ void DrawObject(DRAWOBJECT *pObj) { // 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); + if (TinselV2) { + srcPtr = (byte *)LockMem(pObj->hBits); + pObj->charBase = NULL; + pObj->transOffset = 0; + } else { + byte *p = (byte *)LockMem(pObj->hBits & HANDLEMASK); + + srcPtr = p + (pObj->hBits & OFFSETMASK); + pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10); + pObj->transOffset = READ_LE_UINT32(p + 0x14); + } } // Get destination starting point @@ -410,30 +468,83 @@ void DrawObject(DRAWOBJECT *pObj) { // 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; + + if (TinselV2) { + // Tinsel v2 decoders + // Initial switch statement for the different bit packing types + int packType = pObj->flags >> 14; + + if (packType == 0) { + // No colour packing + switch (typeId) { + case 0x01: + case 0x11: + case 0x41: + case 0x51: + case 0x81: + case 0xC1: + t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, (typeId & 0x10) != 0); + break; + case 0x02: + case 0x42: + // This renderer called 'RlWrtAll', but is the same as t2WrtNonZero + t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, false); + break; + case 0x04: + case 0x44: + // WrtConst with/without clipping + WrtConst(pObj, destPtr, typeId == 0x44); + break; + case 0x08: + case 0x48: + WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40); + break; + case 0x84: + case 0xC4: + // WrtTrans with/without clipping + WrtTrans(pObj, destPtr, typeId == 0xC4); + break; + default: + error("Unknown drawing type %d", typeId); + } + } else { + // 1 = 16 from 240 + // 2 = 16 from 224 + // 3 = variable colour + if (packType == 1) pObj->baseCol = 0xF0; + else if (packType == 2) pObj->baseCol = 0xE0; + + PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0, + (pObj->flags & DMA_FLIPH), packType); + } + + } else { + // Tinsel v1 decoders + switch (typeId) { + case 0x01: + case 0x08: + case 0x41: + case 0x48: + WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40); + break; + + case 0x04: + case 0x44: + // WrtConst 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; + } } } diff --git a/engines/tinsel/graphics.h b/engines/tinsel/graphics.h index 85299d4873..9a05dbe785 100644 --- a/engines/tinsel/graphics.h +++ b/engines/tinsel/graphics.h @@ -37,12 +37,6 @@ 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 - /** draw object structure - only used when drawing objects */ struct DRAWOBJECT { char *charBase; // character set base address @@ -58,8 +52,9 @@ struct DRAWOBJECT { 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 + short xPos; // x position of object + short yPos; // y position of object + uint32 baseCol; // For 4-bit stuff }; diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp index 11623516ec..41774a9c88 100644 --- a/engines/tinsel/handle.cpp +++ b/engines/tinsel/handle.cpp @@ -28,6 +28,7 @@ #include "common/file.h" +#include "tinsel/drives.h" #include "tinsel/dw.h" #include "tinsel/scn.h" // name of "index" file #include "tinsel/handle.h" @@ -41,6 +42,8 @@ #include "tinsel/object.h" #include "tinsel/palette.h" #include "tinsel/text.h" +#include "tinsel/timers.h" +#include "tinsel/tinsel.h" #include "tinsel/scene.h" namespace Tinsel { @@ -58,6 +61,7 @@ struct MEMHANDLE { char szName[12]; //!< 00 - file name of graphics file int32 filesize; //!< 12 - file size and flags MEM_NODE *pNode; //!< 16 - memory node for the graphics + uint32 flags2; }; @@ -72,7 +76,6 @@ enum { }; #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 -------------------- @@ -83,6 +86,13 @@ static MEMHANDLE *handleTable = 0; // number of handles in the handle table static uint numHandles = 0; +static uint32 cdPlayHandle = (uint32)-1; + +static int cdPlayFileNum, cdPlaySceneNum; +static SCNHANDLE cdBaseHandle = 0, cdTopHandle = 0; +static Common::File cdGraphStream; + +static char szCdPlayFile[100]; //----------------- FORWARD REFERENCES -------------------- @@ -94,7 +104,8 @@ static void LoadFile(MEMHANDLE *pH, bool bWarn); // load a memory block as a fil * permanent graphics etc. */ void SetupHandleTable(void) { - enum { RECORD_SIZE = 20 }; + bool t2Flag = (TinselVersion == TINSEL_V2); + int RECORD_SIZE = t2Flag ? 24 : 20; int len; uint i; @@ -108,7 +119,7 @@ void SetupHandleTable(void) { if (len > 0) { if ((len % RECORD_SIZE) != 0) { // index file is corrupt - error("File %s is corrupt", INDEX_FILENAME); + error(FILE_IS_CORRUPT, INDEX_FILENAME); } // calc number of handles @@ -128,31 +139,36 @@ void SetupHandleTable(void) { // need to read that from the file. handleTable[i].pNode = NULL; f.seek(4, SEEK_CUR); + // For Discworld 2, read in the flags2 field + handleTable[i].flags2 = t2Flag ? f.readUint32LE() : 0; } if (f.ioFailed()) { // index file is corrupt - error("File %s is corrupt", INDEX_FILENAME); + error(FILE_IS_CORRUPT, INDEX_FILENAME); } // close the file f.close(); } else { // index file is corrupt - error("File %s is corrupt", INDEX_FILENAME); + error(FILE_IS_CORRUPT, INDEX_FILENAME); } } else { // cannot find the index file - error("Cannot find file %s", INDEX_FILENAME); + error(CANNOT_FIND_FILE, 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); + pH->pNode = MemoryAlloc(DWM_FIXED, sizeof(MEM_NODE) + (pH->filesize & FSIZE_MASK)); // make sure memory allocated assert(pH->pNode); + // Initialise the MEM_NODE structure + memset(pH->pNode, 0, sizeof(MEM_NODE)); + // load the data LoadFile(pH, true); } @@ -178,8 +194,101 @@ void FreeHandleTable(void) { free(handleTable); handleTable = NULL; } + if (cdGraphStream.isOpen()) + cdGraphStream.close(); +} + +/** + * Loads a memory block as a file. + */ +void OpenCDGraphFile(void) { + if (cdGraphStream.isOpen()) + cdGraphStream.close(); + + // As the theory goes, the right CD will be in there! + + cdGraphStream.clearIOFailed(); + cdGraphStream.open(szCdPlayFile); + if (cdGraphStream.ioFailed()) + error(CANNOT_FIND_FILE, szCdPlayFile); +} + +void LoadCDGraphData(MEMHANDLE *pH) { + // read the data + uint bytes; + byte *addr; + int retries = 0; + + assert(!(pH->filesize & fCompressed)); + + // Can't be preloaded + assert(!(pH->filesize & fPreload)); + + // discardable - lock the memory + addr = (byte *)MemoryLock(pH->pNode); + + // make sure address is valid + assert(addr); + + // Move to correct place in file and load the required data + cdGraphStream.seek(cdBaseHandle & OFFSETMASK, SEEK_SET); + bytes = cdGraphStream.read(addr, (cdTopHandle - cdBaseHandle) & OFFSETMASK); + + // New code to try and handle CD read failures 24/2/97 + while (bytes != ((cdTopHandle - cdBaseHandle) & OFFSETMASK) && retries++ < MAX_READ_RETRIES) { + // Try again + cdGraphStream.seek(cdBaseHandle & OFFSETMASK, SEEK_SET); + bytes = cdGraphStream.read(addr, (cdTopHandle - cdBaseHandle) & OFFSETMASK); + } + + // discardable - unlock the memory + MemoryUnlock(pH->pNode); + + // set the loaded flag + pH->filesize |= fLoaded; + + // clear the loading flag +// pH->filesize &= ~fLoading; + + if (bytes != ((cdTopHandle-cdBaseHandle) & OFFSETMASK)) + // file is corrupt + error(FILE_READ_ERROR, "CD play file"); } +/** + * Called immediatly preceding a CDplay(). + * Prepares the ground so that when LockMem() is called, the + * appropriate section of the extra scene file is loaded. + * @param start Handle of start of range + * @param next Handle of end of range + 1 + */ +void LoadExtraGraphData(SCNHANDLE start, SCNHANDLE next) { + if (cdPlayFileNum == cdPlaySceneNum && start == cdBaseHandle) + return; + + OpenCDGraphFile(); + + if ((handleTable + cdPlayHandle)->pNode->pBaseAddr != NULL) + MemoryDiscard((handleTable + cdPlayHandle)->pNode); // Free it + + // It must always be the same + assert(cdPlayHandle == (start >> SCNHANDLE_SHIFT)); + assert(cdPlayHandle == (next >> SCNHANDLE_SHIFT)); + + cdBaseHandle = start; + cdTopHandle = next; +} + +void SetCdPlaySceneDetails(int fileNum, const char *fileName) { + cdPlaySceneNum = fileNum; + strcpy(szCdPlayFile, fileName); +} + +void SetCdPlayHandle(int fileNum) { + cdPlayHandle = fileNum; +} + + /** * Loads a memory block as a file. * @param pH Memory block pointer @@ -204,7 +313,7 @@ void LoadFile(MEMHANDLE *pH, bool bWarn) { if (pH->filesize & fPreload) // preload - no need to lock the memory - addr = (uint8 *)pH->pNode; + addr = (uint8 *)pH->pNode + sizeof(MEM_NODE); else { // discardable - lock the memory addr = (uint8 *)MemoryLock(pH->pNode); @@ -243,12 +352,12 @@ void LoadFile(MEMHANDLE *pH, bool bWarn) { if (bWarn) // file is corrupt - error("File %s is corrupt", szFilename); + error(FILE_IS_CORRUPT, szFilename); } if (bWarn) // cannot find file - error("Cannot find file %s", szFilename); + error(CANNOT_FIND_FILE, szFilename); } /** @@ -265,8 +374,39 @@ uint8 *LockMem(SCNHANDLE offset) { pH = handleTable + handle; if (pH->filesize & fPreload) { + if (TinselV2) + // update the LRU time (new in this file) + pH->pNode->lruTime = DwGetCurrentTime(); + // permanent files are already loaded - return (uint8 *)pH->pNode + (offset & OFFSETMASK); + return (uint8 *)pH->pNode + sizeof(MEM_NODE) + (offset & OFFSETMASK); + } else if (handle == cdPlayHandle) { + // Must be in currently loaded/loadable range + if(offset < cdBaseHandle || offset >= cdTopHandle) + error("Overlapping (in time) CD-plays!"); + + if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded)) + // already allocated and loaded + return pH->pNode->pBaseAddr + ((offset - cdBaseHandle) & OFFSETMASK); + + if (pH->pNode->pBaseAddr == NULL) + // must have been discarded - reallocate the memory + MemoryReAlloc(pH->pNode, cdTopHandle-cdBaseHandle, + DWM_MOVEABLE | DWM_DISCARDABLE); + + if(pH->pNode->pBaseAddr == NULL) + error("Out of memory"); + + LoadCDGraphData(pH); + + // make sure address is valid + assert(pH->pNode->pBaseAddr); + + // update the LRU time (new in this file) + pH->pNode->lruTime = DwGetCurrentTime(); + + return pH->pNode->pBaseAddr + ((offset - cdBaseHandle) & OFFSETMASK); + } else { if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded)) // already allocated and loaded @@ -312,7 +452,10 @@ void LockScene(SCNHANDLE offset) { if ((pH->filesize & fPreload) == 0) { // change the flags for the node - MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE); + // WORKAROUND: The original didn't include the DWM_LOCKED flag. It's being + // included because the method is 'LockScene' so it's presumed that the + // point of this was that the scene's memory block be locked + MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE | DWM_LOCKED); #ifdef DEBUG bLockedScene = true; #endif @@ -363,4 +506,50 @@ bool ValidHandle(SCNHANDLE offset) { } #endif +/** + * TouchMem + * @param offset Handle and offset to data + */ +void TouchMem(SCNHANDLE offset) { + MEMHANDLE *pH; // points to table entry + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + + if (offset != 0) { + pH = handleTable + handle; + + // update the LRU time whether its loaded or not! + pH->pNode->lruTime = DwGetCurrentTime(); + } +} + +/** + * Returns true if the given handle is into the cd graph data + * @param offset Handle and offset to data + */ +bool IsCdPlayHandle(SCNHANDLE offset) { + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + + // range check the memory handle + assert(handle < numHandles); + + return (handle == cdPlayHandle); +} + +/** + * Returns the CD for a given scene handle + */ +int CdNumber(SCNHANDLE offset) { + uint handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + + // range check the memory handle + assert(handle < numHandles); + + MEMHANDLE *pH = handleTable + handle; + + if (!TinselV2) + return 1; + + return GetCD(pH->flags2 & fAllCds); +} + } // end of namespace Tinsel diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h index 2cb1638d9d..3f29ca712a 100644 --- a/engines/tinsel/handle.h +++ b/engines/tinsel/handle.h @@ -48,6 +48,23 @@ void LockScene( // Called to make the current scene non-discardable void UnlockScene( // Called to make the current scene discardable again SCNHANDLE offset); // handle and offset to data +bool IsCdPlayHandle(SCNHANDLE offset); + +void TouchMem(SCNHANDLE offset); + +void SetCdPlaySceneDetails( // Called at scene startup + int sceneNum, + const char *fileName); + +void SetCdPlayHandle( // Called at game startup + int fileNum); + +void LoadExtraGraphData( + SCNHANDLE start, // Handle of start of range + SCNHANDLE next); // Handle of end of range + 1 + +int CdNumber(SCNHANDLE offset); + } // end of namespace Tinsel #endif // TINSEL_HANDLE_H diff --git a/engines/tinsel/heapmem.cpp b/engines/tinsel/heapmem.cpp index aff085d003..78c020eb74 100644 --- a/engines/tinsel/heapmem.cpp +++ b/engines/tinsel/heapmem.cpp @@ -26,11 +26,13 @@ #include "tinsel/heapmem.h" #include "tinsel/timers.h" // For DwGetCurrentTime +#include "tinsel/tinsel.h" namespace Tinsel { -// minimum memory required for MS-DOS version of game -#define MIN_MEM 2506752L +// Specifies the total amount of memory required for DW1 demo, DW1, or DW2 respectively. +// Currently this is set at 10Mb for all three - this could probably be reduced somewhat +uint32 MemoryPoolSize[3] = {10 * 1024 * 1024, 10 * 1024 * 1024, 10 * 1024 * 1024}; // list of all memory nodes MEM_NODE mnodeList[NUM_MNODES]; @@ -73,8 +75,10 @@ void MemoryInit(void) { // null the last mnode mnodeList[NUM_MNODES - 1].pNext = NULL; - // allocatea big chunk of memory - const uint32 size = 2*MIN_MEM+655360L; + // allocates a big chunk of memory + uint32 size = MemoryPoolSize[0]; + if (TinselVersion == TINSEL_V1) size = MemoryPoolSize[1]; + else if (TinselVersion == TINSEL_V2) size = MemoryPoolSize[2]; uint8 *mem = (uint8 *)malloc(size); assert(mem); @@ -274,8 +278,9 @@ MEM_NODE *MemoryAlloc(int flags, long size) { bool bCompacted = true; // set when heap has been compacted // compact the heap if we are allocating fixed memory - if (flags & DWM_FIXED) + if (flags & DWM_FIXED) { HeapCompact(MAX_INT, false); + } while ((flags & DWM_NOALLOC) == 0 && bCompacted) { // search the heap for a free block diff --git a/engines/tinsel/inventory.cpp b/engines/tinsel/inventory.cpp deleted file mode 100644 index d20ada51ac..0000000000 --- a/engines/tinsel/inventory.cpp +++ /dev/null @@ -1,4526 +0,0 @@ -/* 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. - */ - -#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 INV_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? - -}; - -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 -enum BTYPE { - RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER, - TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING -}; - -enum BFUNC { - NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN, - OPENLOAD, OPENSAVE, OPENREST, - OPENSOUND, OPENCONT, - OPENSUBT, - OPENQUIT, - INITGAME, MIDIVOL, - CLANG, RLANG -#ifdef MAC_OPTIONS - , MASTERVOL, SAMPVOL -#endif -}; - -struct CONFBOX { - 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 -}; - - -#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 -#define SIX_SUBTITLES_OPTION 5 -#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, Audio::Mixer::kMaxChannelVolume, 2, &volMidi, 0 }, - { SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER, 142, 25+40, Audio::Mixer::kMaxChannelVolume, 2, &volSound, 0 }, - { SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, Audio::Mixer::kMaxChannelVolume, 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'. | -\*-------------------------------------------------------------*/ - -CONFBOX subtitlesBox[] = { - - { 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 }, - -}; - -CONFBOX subtitlesBox3Flags[] = { - - { 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 }, - - { 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 }, - - { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, - { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } - -}; - -CONFBOX subtitlesBox4Flags[] = { - - { 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 }, - - { 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 }, - - { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, - { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } - -}; - -CONFBOX subtitlesBox5Flags[] = { - - { 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 }, - - { 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 }, - - { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, - { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } - -}; - - -/*-------------------------------------------------------------*\ -| This is the quit confirmation 'menu'. | -\*-------------------------------------------------------------*/ - -CONFBOX quitBox[] = { -#ifdef g - { 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 } -}; - - - -struct CONFINIT { - int h; - int v; - int x; - int y; - bool bExtraWin; - CONFBOX *Box; - int NumBoxes; - int ixHeading; -}; - -CONFINIT ciOption = { 6, 5, 72, 23, false, optionBox, ARRAYSIZE(optionBox), NO_HEADING }; - -CONFINIT ciLoad = { 10, 6, 20, 16, true, loadBox, ARRAYSIZE(loadBox), SIX_LOAD_HEADING }; -CONFINIT ciSave = { 10, 6, 20, 16, true, saveBox, ARRAYSIZE(saveBox), SIX_SAVE_HEADING }; -#ifdef JAPAN -CONFINIT ciRestart = { 6, 2, 72, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING }; -#else -CONFINIT ciRestart = { 4, 2, 98, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING }; -#endif -CONFINIT ciSound = { 10, 5, 20, 16, false, soundBox, ARRAYSIZE(soundBox), NO_HEADING }; -#ifdef MAC_OPTIONS - CONFINIT ciControl = { 10, 3, 20, 40, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING }; -#else - CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING }; -#endif - -CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING }; - -CONFINIT ciQuit = { 4, 2, 98, 53, false, quitBox, ARRAYSIZE(quitBox), SIX_QUIT_HEADING }; - -CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING }; - -#define NOBOX (-1) - -// Conf window globals -static struct { - CONFBOX *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; - CONFBOX *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 - -/*-------------------------------------------------------------------------*/ - - - -bool LanguageChange(void) { - LANGUAGE nLang = TXT_ENGLISH; - - if (_vm->getFeatures() & GF_USE_3FLAGS) { - // VERY quick dodgy bodge - if (cd.selBox == 0) - nLang = TXT_FRENCH; // = 1 - else if (cd.selBox == 1) - nLang = TXT_GERMAN; // = 2 - else - nLang = TXT_SPANISH; // = 4 - } else if (_vm->getFeatures() & GF_USE_4FLAGS) { - nLang = (LANGUAGE)(cd.selBox + 1); - } else if (_vm->getFeatures() & GF_USE_5FLAGS) { - nLang = (LANGUAGE)cd.selBox; - } - - if (nLang != g_language) { - KillInventory(); - ChangeLanguage(nLang); - g_language = nLang; - return true; - } else - return false; -} - -/**************************************************************************/ -/******************** Some miscellaneous functions ************************/ -/**************************************************************************/ - -/** - * Delete all the objects in iconArray[] - */ -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. - */ -INV_OBJECT *findInvObject(int num) { - INV_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) { - INV_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 { - INV_OBJECT *pinvo; - USER_EVENT event; - BUTEVENT bev; -}; - -/** - * Run inventory item's Glitter code - */ -static void InvTinselProcess(CORO_PARAM, const void *param) { - // COROUTINE - CORO_BEGIN_CONTEXT; - INT_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 *)param; - - 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(INV_OBJECT *pinvo, USER_EVENT event, BUTEVENT be, int index) { - ITP_INIT to = { pinvo, event, be }; - - if (InventoryHidden) - return; - - GlitterIndex = index; - g_scheduler->createProcess(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; - - case FRGROUP: - assert((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)); - 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; - - default: - break; - } -} - - -/**************************************************************************/ -/***/ -/**************************************************************************/ - -/** - * If the item is not already held, hold it. - */ - -void HoldItem(int item) { - INV_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 - INV_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; -} - -/** - * Finds which box the cursor is in. - */ -static int WhichInvBox(int curX, int curY, bool bSlides) { - enum { - MD_YSLIDTOP = 7, - MD_YSLIDBOT = 18, - MD_YBUTTOP = 9, - MD_YBUTBOT = 16, - MD_XLBUTL = 1, - MD_XLBUTR = 10, - MD_XRBUTL = 105, - MD_XRBUTR = 114 - }; - - 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, CONFBOX *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, CONFBOX *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 - INV_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) { - INV_OBJECT *invObj; // Icon data - const MULTI_INIT *pmi; // Its INIT structure - from the reel - IMAGE *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 - IMAGE *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; - - case FRGROUP: - assert((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)); - 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; - - 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; - IMAGE *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(CONFINIT *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; - - case SUBT: - if (_vm->getFeatures() & GF_USE_3FLAGS) { - ciSubtitles.v = 6; - ciSubtitles.Box = subtitlesBox3Flags; - ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox3Flags); - } else if (_vm->getFeatures() & GF_USE_4FLAGS) { - ciSubtitles.v = 6; - ciSubtitles.Box = subtitlesBox4Flags; - ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox4Flags); - } else if (_vm->getFeatures() & GF_USE_5FLAGS) { - ciSubtitles.v = 6; - ciSubtitles.Box = subtitlesBox4Flags; - ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox4Flags); - } else { - ciSubtitles.v = 3; - ciSubtitles.Box = subtitlesBox; - ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox); - } - - SetConfGlobals(&ciSubtitles); - break; - - 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); - else if (type == SUBT) { - if (_vm->getFeatures() & GF_USE_3FLAGS) { - // VERY quick dirty bodges - if (g_language == TXT_FRENCH) - Select(0, false); - else if (g_language == TXT_GERMAN) - Select(1, false); - else - Select(2, false); - } else if (_vm->getFeatures() & GF_USE_4FLAGS) { - Select(g_language-1, false); - } else if (_vm->getFeatures() & GF_USE_5FLAGS) { - Select(g_language, false); - } - } - - 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); - - // Write config changes - WriteConfig(); - } 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, const void *) { - // 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->quitGame(); - 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; - case OPENSUBT: - KillInventory(); - PopUpConf(SUBT); - break; - case OPENQUIT: - KillInventory(); - PopUpConf(QUIT); - break; - case INITGAME: - KillInventory(); - bRestart = true; - break; - case CLANG: - if (!LanguageChange()) - KillInventory(); - break; - case RLANG: - KillInventory(); - break; - 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 - if (ino == INV_CONF && cd.Box == subtitlesBox) - Select(g_language, false); - 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; - - case FRGROUP: - assert((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)); - if (dbl) { - Select(i, false); - LanguageChange(); - } else { - Select(i, false); - } - break; - - 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, const void *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 *)param; - - InvPutDown(*pindex); - - CORO_END_CODE; -} - -void InvPickup(int index) { - INV_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)) - g_scheduler->createProcess(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; - INV_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; - INV_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) { - INV_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 = (INV_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 deleted file mode 100644 index d83439c68f..0000000000 --- a/engines/tinsel/inventory.h +++ /dev/null @@ -1,142 +0,0 @@ - -/* 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 -}; - -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 index 4c64eaf091..8c09787690 100644 --- a/engines/tinsel/mareels.cpp +++ b/engines/tinsel/mareels.cpp @@ -24,6 +24,7 @@ * Functions to set up moving actors' reels. */ +#include "tinsel/handle.h" #include "tinsel/pcode.h" // For D_UP, D_DOWN #include "tinsel/rince.h" @@ -34,7 +35,7 @@ namespace Tinsel { //----------------- LOCAL GLOBAL DATA -------------------- enum { - NUM_INTERVALS = NUM_MAINSCALES - 1, + NUM_INTERVALS = REQ_MAIN_SCALES - 1, // 2 for up and down, 3 allow enough entries for 3 fully subscribed moving actors' worth MAX_SCRENTRIES = NUM_INTERVALS*2*3 @@ -51,36 +52,80 @@ static SCIdataStruct SCIdata[MAX_SCRENTRIES]; static int scrEntries = 0; +/** + * Sets an actor's walk reels + */ + +void SetWalkReels(PMOVER pMover, int scale, + SCNHANDLE al, SCNHANDLE ar, SCNHANDLE af, SCNHANDLE aa) { + assert(scale > 0 && scale <= TOTAL_SCALES); + + pMover->walkReels[scale-1][LEFTREEL] = al; + pMover->walkReels[scale-1][RIGHTREEL] = ar; + pMover->walkReels[scale-1][FORWARD] = af; + pMover->walkReels[scale-1][AWAY] = aa; +} + + +/** + * Sets an actor's stand reels + */ + +void SetStandReels(PMOVER pMover, int scale, + SCNHANDLE al, SCNHANDLE ar, SCNHANDLE af, SCNHANDLE aa) { + assert(scale > 0 && scale <= TOTAL_SCALES); + + pMover->standReels[scale-1][LEFTREEL] = al; + pMover->standReels[scale-1][RIGHTREEL] = ar; + pMover->standReels[scale-1][FORWARD] = af; + pMover->standReels[scale-1][AWAY] = aa; +} + + +/** + * Sets an actor's talk reels + */ + +void SetTalkReels(PMOVER pMover, int scale, + SCNHANDLE al, SCNHANDLE ar, SCNHANDLE af, SCNHANDLE aa) { + assert(scale > 0 && scale <= TOTAL_SCALES); + + pMover->talkReels[scale-1][LEFTREEL] = al; + pMover->talkReels[scale-1][RIGHTREEL] = ar; + pMover->talkReels[scale-1][FORWARD] = af; + pMover->talkReels[scale-1][AWAY] = aa; +} + /** * Return handle to actor's talk reel at present scale and direction. */ -SCNHANDLE GetMactorTalkReel(PMACTOR pActor, TFTYPE dirn) { +SCNHANDLE GetMoverTalkReel(PMOVER pActor, TFTYPE dirn) { assert(1 <= pActor->scale && pActor->scale <= TOTAL_SCALES); switch (dirn) { case TF_NONE: - return pActor->TalkReels[pActor->scale-1][pActor->dirn]; + return pActor->talkReels[pActor->scale-1][pActor->direction]; case TF_UP: - return pActor->TalkReels[pActor->scale-1][AWAY]; + return pActor->talkReels[pActor->scale-1][AWAY]; case TF_DOWN: - return pActor->TalkReels[pActor->scale-1][FORWARD]; + return pActor->talkReels[pActor->scale-1][FORWARD]; case TF_LEFT: - return pActor->TalkReels[pActor->scale-1][LEFTREEL]; + return pActor->talkReels[pActor->scale-1][LEFTREEL]; case TF_RIGHT: - return pActor->TalkReels[pActor->scale-1][RIGHTREEL]; + return pActor->talkReels[pActor->scale-1][RIGHTREEL]; default: - error("GetMactorTalkReel() - illegal direction!"); + error("GetMoverTalkReel() - illegal direction!"); } } /** * scalingreels */ -void setscalingreels(int actor, int scale, int direction, +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) && @@ -101,7 +146,7 @@ void setscalingreels(int actor, int scale, int direction, /** * ScalingReel */ -SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel) { +SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRECTION reel) { int d; // Direction // The smaller the number, the bigger the scale @@ -129,4 +174,20 @@ void RebootScalingReels(void) { memset(SCIdata, 0, sizeof(SCIdata)); } +/** + * Discourage them from being ditched. + */ +void TouchMoverReels(void) { + PMOVER pMover; + int scale; + + pMover = NextMover(NULL); + + do { + for (scale = 0; scale < TOTAL_SCALES; scale++) { + TouchMem(pMover->walkReels[scale][LEFTREEL]); + } + } while ((pMover = NextMover(pMover)) != NULL); +} + } // end of namespace Tinsel diff --git a/engines/tinsel/mareels.h b/engines/tinsel/mareels.h new file mode 100644 index 0000000000..cca406a04b --- /dev/null +++ b/engines/tinsel/mareels.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$ + * + */ + +#ifndef TINSEL_MAREELS_H // prevent multiple includes +#define TINSEL_MAREELS_H + +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/rince.h" + +namespace Tinsel { + +void SetWalkReels(PMOVER pMover, int scale, + SCNHANDLE al, SCNHANDLE ar, SCNHANDLE af, SCNHANDLE aa); + +void SetStandReels(PMOVER pMover, int scale, + SCNHANDLE al, SCNHANDLE ar, SCNHANDLE af, SCNHANDLE aa); + +void SetTalkReels(PMOVER pMover, int scale, + SCNHANDLE al, SCNHANDLE ar, SCNHANDLE af, SCNHANDLE aa); + +SCNHANDLE GetMoverTalkReel(PMOVER pActor, 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, DIRECTION reel); + +void RebootScalingReels(void); + +void TouchMoverReels(void); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk index b00afcddbc..dc89731842 100644 --- a/engines/tinsel/module.mk +++ b/engines/tinsel/module.mk @@ -5,11 +5,14 @@ MODULE_OBJS = \ anim.o \ background.o \ bg.o \ + bmv.o \ cliprect.o \ config.o \ cursor.o \ debugger.o \ detection.o \ + dialogs.o \ + drives.o \ effect.o \ events.o \ faders.o \ @@ -17,7 +20,6 @@ MODULE_OBJS = \ graphics.o \ handle.o \ heapmem.o \ - inventory.o \ mareels.o \ move.o \ multiobj.o \ @@ -37,6 +39,7 @@ MODULE_OBJS = \ scroll.o \ sound.o \ strres.o \ + sysvar.o \ text.o \ timers.o \ tinlib.o \ diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp index 803bc5fd7b..20e4175e01 100644 --- a/engines/tinsel/move.cpp +++ b/engines/tinsel/move.cpp @@ -38,7 +38,8 @@ #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/scroll.h" -#include "tinsel/tinlib.h" // For stand() +#include "tinsel/tinlib.h" // For Stand() +#include "tinsel/tinsel.h" namespace Tinsel { @@ -48,21 +49,16 @@ namespace Tinsel { //----------------- 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); +HPOLYGON InitExtraBlock(PMOVER ca, PMOVER ta); //----------------- LOCAL DEFINES -------------------- -#define XMDIST 4 -#define XHMDIST 2 -#define YMDIST 2 -#define YHMDIST 2 +#define XMDIST (TinselV2 ? 6 : 4) +#define XHMDIST (TinselV2 ? 3 : 2) +#define YMDIST (TinselV2 ? 3 : 2) +#define YHMDIST (TinselV2 ? 3 : 2) #define XTHERE 1 #define XRESTRICT 2 @@ -77,6 +73,7 @@ HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta); #define ALL_SORTED 1 #define NOT_SORTED 0 +#define STEPS_MAX (TinselV2 ? 12 : 6) //----------------- LOCAL GLOBAL DATA -------------------- @@ -86,6 +83,8 @@ static int BogusVar = 0; // For slowing down walking, for testing #endif static int32 DefaultRefer = 0; +static int lastLeadXdest = 0, lastLeadYdest = 0; + static int hSlowVar = 0; // used by MoveActor() @@ -94,7 +93,7 @@ static int hSlowVar = 0; // used by MoveActor() 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); + PMOVER pActor, PMOVER *collisionActor = 0); #if SLOW_RINCE_DOWN @@ -126,7 +125,7 @@ static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) { Clicked within a path, go to where requested unless blocked. --------------------------------------*/ - if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) { + if (InPolygon(clickX, clickY, BLOCK) == NOPOLY) { // Not in a blocking polygon - go to where requested. *ptgtX = clickX; *ptgtY = clickY; @@ -143,7 +142,7 @@ static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) { i = SCREEN_HEIGHT; break; } - if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { + if (InPolygon(clickX, i, BLOCK) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; @@ -156,7 +155,7 @@ static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) { i = -1; break; } - if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { + if (InPolygon(clickX, i, BLOCK) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; @@ -187,15 +186,15 @@ static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, switch (PolySubtype(hRefpoly)) { case REF_POINT: // Go to specified node - getPolyNode(hRefpoly, ptgtX, ptgtY); + GetPolyNode(hRefpoly, ptgtX, ptgtY); assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point break; case REF_DOWN: // Search downwards - end = BackgroundHeight(); + end = BgHeight(); for (i = clickY+1; i < end; i++) if (InPolygon(clickX, i, PATH) != NOPOLY - && InPolygon(clickX, i, BLOCKING) == NOPOLY) { + && InPolygon(clickX, i, BLOCK) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; @@ -205,7 +204,7 @@ static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, case REF_UP: // Search upwards for (i = clickY-1; i >= 0; i--) if (InPolygon(clickX, i, PATH) != NOPOLY - && InPolygon(clickX, i, BLOCKING) == NOPOLY) { + && InPolygon(clickX, i, BLOCK) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; @@ -213,10 +212,10 @@ static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, break; case REF_RIGHT: // Search to the right - end = BackgroundWidth(); + end = BgWidth(); for (i = clickX+1; i < end; i++) if (InPolygon(i, clickY, PATH) != NOPOLY - && InPolygon(i, clickY, BLOCKING) == NOPOLY) { + && InPolygon(i, clickY, BLOCK) == NOPOLY) { *ptgtX = i; *ptgtY = clickY; break; @@ -226,7 +225,7 @@ static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, 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) { + && InPolygon(i, clickY, BLOCK) == NOPOLY) { *ptgtX = i; *ptgtY = clickY; break; @@ -265,7 +264,7 @@ static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); } // Try searching down and up (offscreen). - end = BackgroundHeight(); + end = BgHeight(); for (i = clickY+1; i < end; i++) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); @@ -284,7 +283,7 @@ static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) { break; case REF_DOWN: - end = BackgroundHeight(); + end = BgHeight(); for (i = clickY+1; i < end; i++) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); @@ -299,7 +298,7 @@ static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) { break; case REF_RIGHT: - end = BackgroundWidth(); + end = BgWidth(); for (i = clickX + 1; i < end; i++) if (InPolygon(i, clickY, PATH) != NOPOLY) { return ClickedOnPath(i, clickY, ptgtX, ptgtY); @@ -339,18 +338,19 @@ static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *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) { +DIRECTION GetDirection(int fromx, int fromy, int tox, int toy, DIRECTION lastreel, + HPOLYGON hPath, YBIAS yBias) { 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 + DIRECTION 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) + if (hPath != NOPOLY && (GetPolyReelType(hPath) == REEL_VERT)) xdir = X_NO; else if (tox == -1) xdir = X_NONE; @@ -369,7 +369,7 @@ static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL * Determine size and direction of Y movement. * i.e. up, down, none or not allowed. */ - if (getPolyReelType(hPath) == REEL_HORIZ) + if (hPath != NOPOLY && (GetPolyReelType(hPath) == REEL_HORIZ)) ydir = Y_NO; else if (toy == -1) ydir = Y_NONE; @@ -387,7 +387,15 @@ static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL /* * Some adjustment to allow for different x and y pixell sizes. */ - ychange += ychange; // Double y distance to cover + switch (yBias) { + case YB_X2: + ychange += ychange; // Double y distance to cover + break; + + case YB_X1_5: + ychange += ychange / 2; // Double y distance to cover + break; + } /* * Determine which reel to use. @@ -462,37 +470,60 @@ static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL /** * Haven't moved, look towards the cursor. */ -static void GotThereWithoutMoving(PMACTOR pActor) { +static void GotThereWithoutMoving(PMOVER pActor) { int curX, curY; - DIRREEL reel; + DIRECTION reel; - if (!pActor->TagReelRunning) { + if (!pActor->bSpecReel) { GetCursorXYNoWait(&curX, &curY, true); - reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath); + reel = GetDirection(pActor->objX, pActor->objY, curX, curY, pActor->direction, pActor->hCpath); - if (reel != pActor->dirn) - SetMActorWalkReel(pActor, reel, pActor->scale, false); + if (reel != pActor->direction) + SetMoverWalkReel(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; +static void GotThere(PMOVER pMover) { + pMover->targetX = pMover->targetY = -1; // 4/1/95 + pMover->ItargetX = pMover->ItargetY = -1; + pMover->UtargetX = pMover->UtargetY = -1; // Perhaps we have'nt moved. - if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy) - GotThereWithoutMoving(pActor); + if (pMover->objX == (int)pMover->walkedFromX && pMover->objY == (int)pMover->walkedFromY) { + // Got there without moving + if (!TinselV2) + GotThereWithoutMoving(pMover); + else if (!pMover->bSpecReel) { + // No tag reel, look at cursor + int curX, curY; + DIRECTION direction; + + GetCursorXY(&curX, &curY, true); + direction = GetDirection(pMover->objX, pMover->objY, + curX, curY, + pMover->direction, + pMover->hCpath, + YB_X2); + + if (direction != pMover->direction) + SetMoverWalkReel(pMover, direction, pMover->scale, false); + } + } - ReTagActor(pActor->actorID); // Tag allowed while stationary + if (!TinselV2) + ReTagActor(pMover->actorID); // Tag allowed while stationary - SetMActorStanding(pActor); + SetMoverStanding(pMover); + pMover->bMoving = false; - pActor->bMoving = false; + if (TinselV2 && pMover->bIgPath && pMover->zOverride != -1 + && InPolygon(pMover->objX, pMover->objY, PATH) == NOPOLY) + // New feature for end-of-scene walk-outs + SetMoverZ(pMover, pMover->objY, pMover->zOverride); } enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY }; @@ -500,15 +531,15 @@ 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) { +static cgt CanGetThere(PMOVER 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 + int x = pActor->objX; + int y = pActor->objY; // Present position while (targetX != -1 || targetY != -1) { NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, @@ -536,7 +567,7 @@ static cgt CanGetThere(PMACTOR pActor, int tx, int ty) { /** * Set final destination. */ -static void SetMoverUltDest(PMACTOR pActor, int x, int y) { +static void SetMoverUltDest(PMOVER pActor, int x, int y) { pActor->UtargetX = x; pActor->UtargetY = y; pActor->hUpath = InPolygon(x, y, PATH); @@ -554,89 +585,108 @@ static void SetMoverUltDest(PMACTOR pActor, int x, int y) { * Otherwise, head towards the pseudo-centre or end node of the first * en-route path. */ -static void SetMoverIntDest(PMACTOR pActor, int x, int y) { +static void SetMoverIntDest(PMOVER pMover, int x, int y) { HPOLYGON hIpath, hTpath; int node; hTpath = InPolygon(x, y, PATH); // Target path #ifdef DEBUG - if (!pActor->bIgPath) + if (!pMover->bIgPath) assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path #endif - if (pActor->hCpath == hTpath || pActor->bIgPath - || IsInPolygon(pActor->objx, pActor->objy, hTpath)) { + if (pMover->hCpath == hTpath || pMover->bIgPath + || IsInPolygon(pMover->objX, pMover->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)) { + pMover->ItargetX = x; + pMover->ItargetY = y; + // make damn sure that Itarget is in hIpath + pMover->hIpath = !TinselV2 ? hTpath : InPolygon(x, y, PATH); + } else if (IsAdjacentPath(pMover->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); + if (CanGetThere(pMover, x, y) == GT_NOTL) { + NearestCorner(&x, &y, pMover->hCpath, hTpath); } - pActor->ItargetX = x; - pActor->ItargetY = y; + pMover->ItargetX = x; + pMover->ItargetY = y; + if (TinselV2) + // make damn sure that Itarget is in hIpath + pMover->hIpath = InPolygon(x, y, PATH); } else { // Target path is node - head for end node. - node = NearestEndNode(hTpath, pActor->objx, pActor->objy); - getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY); - + node = NearestEndNode(hTpath, pMover->objX, pMover->objY); + getNpathNode(hTpath, node, &pMover->ItargetX, &pMover->ItargetY); + if (TinselV2) + // make damn sure that Itarget is in hIpath + pMover->hIpath = InPolygon(pMover->ItargetX, pMover->ItargetY, PATH); } - pActor->hIpath = hTpath; + if (!TinselV2) + pMover->hIpath = hTpath; } else { assert(hTpath != NOPOLY); // Error 701 - hIpath = getPathOnTheWay(pActor->hCpath, hTpath); + hIpath = GetPathOnTheWay(pMover->hCpath, hTpath); - if (hIpath != NOPOLY) { + if (TinselV2 && (hIpath == NOPOLY)) { + pMover->hIpath = NOPOLY; + } else 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; + if (CanGetThere(pMover, x, y) == GT_OK) { + pMover->ItargetX = x; + pMover->ItargetY = y; + if (TinselV2) + // make damn sure that Itarget is in hIpath + pMover->hIpath = InPolygon(x, y, PATH); } else { - pActor->ItargetX = PolyCentreX(hIpath); - pActor->ItargetY = PolyCentreY(hIpath); + pMover->ItargetX = PolyCentreX(hIpath); + pMover->ItargetY = PolyCentreY(hIpath); + if (TinselV2) + // make damn sure that Itarget is in hIpath + pMover->hIpath = InPolygon(pMover->ItargetX, pMover->ItargetY, PATH); } } else { /* En-route path is node - head for end node. */ - node = NearestEndNode(hIpath, pActor->objx, pActor->objy); - getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY); + node = NearestEndNode(hIpath, pMover->objX, pMover->objY); + getNpathNode(hIpath, node, &pMover->ItargetX, &pMover->ItargetY); + if (TinselV2) + // make damn sure that Itarget is in hIpath + pMover->hIpath = InPolygon(pMover->ItargetX, pMover->ItargetY, PATH); } - pActor->hIpath = hIpath; + if (!TinselV2) + pMover->hIpath = hIpath; } } - pActor->InDifficulty = NO_PROB; + pMover->InDifficulty = NO_PROB; } /** * Set short-term destination and adopt the appropriate reel. */ -static void SetMoverDest(PMACTOR pActor, int x, int y) { +static void SetMoverDest(PMOVER pActor, int x, int y) { int scale; - DIRREEL reel; + DIRECTION 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); + reel = GetDirection(pActor->objX, pActor->objY, x, y, pActor->direction, pActor->hCpath); + scale = GetScale(pActor->hCpath, pActor->objY); + if (scale != pActor->scale || reel != pActor->direction) { + SetMoverWalkReel(pActor, reel, scale, false); } } /** * SetNextDest */ -static void SetNextDest(PMACTOR pActor) { +static void SetNextDest(PMOVER pMover) { int targetX, targetY; // Ultimate destination int x, y; // Present position int nextx, nexty; @@ -653,7 +703,7 @@ static void SetNextDest(PMACTOR pActor) { int ss1, ss2; HPOLYGON shS2p; - PMACTOR collisionActor; + PMOVER collisionActor; #if 1 int sTargetX, sTargetY; #endif @@ -661,30 +711,30 @@ static void SetNextDest(PMACTOR pActor) { /* * Desired destination (Itarget) is already set */ - x = pActor->objx; // Current position - y = pActor->objy; - targetX = pActor->ItargetX; // Desired position - targetY = pActor->ItargetY; + x = pMover->objX; // Current position + y = pMover->objY; + targetX = pMover->ItargetX; // Desired position + targetY = pMover->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) { + if (targetX == pMover->UtargetX && targetY == pMover->UtargetY) { // Desired position - GotThere(pActor); + GotThere(pMover); return; } else { - assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001 - SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + assert(pMover->bIgPath || InPolygon(pMover->UtargetX, pMover->UtargetY, PATH) != NOPOLY); // Error 5001 + SetMoverIntDest(pMover, pMover->UtargetX, pMover->UtargetY); } } - if (pActor->bNoPath || pActor->bIgPath) { + if (pMover->bNoPath || pMover->bIgPath) { /* Can get there directly. */ - SetMoverDest(pActor, targetX, targetY); - pActor->over = false; + SetMoverDest(pMover, targetX, targetY); + pMover->over = false; return; } @@ -692,8 +742,8 @@ static void SetNextDest(PMACTOR pActor) { | 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) { + hNpoly = pMover->hFnpath; // The node path we're in (if any) + switch (pMover->npstatus) { case NOT_IN: break; @@ -701,99 +751,99 @@ static void SetNextDest(PMACTOR pActor) { znode = NearestEndNode(hNpoly, x, y); /* Hang on, we're probably here already! */ if (znode) { - pActor->npstatus = GOING_DOWN; - pActor->line = znode-1; + pMover->npstatus = GOING_DOWN; + pMover->line = znode-1; getNpathNode(hNpoly, znode - 1, &nx, &ny); } else { - pActor->npstatus = GOING_UP; - pActor->line = znode; + pMover->npstatus = GOING_UP; + pMover->line = znode; getNpathNode(hNpoly, 1, &nx, &ny); } - SetMoverDest(pActor, nx, ny); + SetMoverDest(pMover, nx, ny); // Test for pseudo-one-node npaths if (numNodes(hNpoly) == 2 && - ABS(pActor->objx - pActor->targetX) < XMDIST && - ABS(pActor->objy - pActor->targetY) < YMDIST) { + ABS(pMover->objX - pMover->targetX) < XMDIST && + ABS(pMover->objY - pMover->targetY) < YMDIST) { // That's enough, we're leaving - pActor->npstatus = LEAVING; + pMover->npstatus = LEAVING; } else { // Normal situation - pActor->over = true; + pMover->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; + assert(pMover->bIgPath || InPolygon(pMover->UtargetX, pMover->UtargetY, PATH) != NOPOLY); // Error 5002 + SetMoverIntDest(pMover, pMover->UtargetX, pMover->UtargetY); + targetX = pMover->ItargetX; // Desired position + targetY = pMover->ItargetY; break; case GOING_UP: - i = pActor->line; // The line we're on + i = pMover->line; // The line we're on // Is this the final target line? - if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) { + if (i+1 == pMover->Tline && hNpoly == pMover->hUpath) { // The final leg of the journey - pActor->line = i+1; - SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); - pActor->over = false; + pMover->line = i+1; + SetMoverDest(pMover, pMover->UtargetX, pMover->UtargetY); + pMover->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; + SetMoverDest(pMover, nx, ny); + pMover->line = i-1; + if (ABS(pMover->UtargetX - pMover->targetX) < XMDIST && + ABS(pMover->UtargetY - pMover->targetY) < YMDIST) + pMover->over = false; else - pActor->over = true; + pMover->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; + pMover->npstatus = LEAVING; + assert(pMover->bIgPath || InPolygon(pMover->UtargetX, pMover->UtargetY, PATH) != NOPOLY); // Error 5003 + SetMoverIntDest(pMover, pMover->UtargetX, pMover->UtargetY); + targetX = pMover->ItargetX; // Desired position + targetY = pMover->ItargetY; break; } } case GOING_DOWN: - i = pActor->line; // The line we're on and the node we're at + i = pMover->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) { + if (i - 1 == pMover->Tline && hNpoly == pMover->hUpath) { // The final leg of the journey - SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); - pActor->line = i-1; - pActor->over = false; + SetMoverDest(pMover, pMover->UtargetX, pMover->UtargetY); + pMover->line = i-1; + pMover->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; + SetMoverDest(pMover, nx, ny); + pMover->line--; /* The next node to head for */ + if (ABS(pMover->UtargetX - pMover->targetX) < XMDIST && + ABS(pMover->UtargetY - pMover->targetY) < YMDIST) + pMover->over = false; else - pActor->over = true; + pMover->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; + pMover->npstatus = LEAVING; + assert(pMover->bIgPath || InPolygon(pMover->UtargetX, pMover->UtargetY, PATH) != NOPOLY); // Error 5004 + SetMoverIntDest(pMover, pMover->UtargetX, pMover->UtargetY); + targetX = pMover->ItargetX; // Desired position + targetY = pMover->ItargetY; break; } } @@ -814,7 +864,7 @@ static void SetNextDest(PMACTOR pActor) { sTargetY = targetY; #endif NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, - &s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor); + &s1, &s2, &hS2p, pMover->over, false, pMover, &collisionActor); if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) { ss1 = s1; @@ -829,7 +879,7 @@ static void SetNextDest(PMACTOR pActor) { // 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); + &s1, &s2, &hS2p, pMover->over, true, pMover, &collisionActor); if (x == nextx && y == nexty) { s1 = ss1; @@ -840,8 +890,8 @@ static void SetNextDest(PMACTOR pActor) { if (s1 == (XTHERE | YTHERE)) { /* Can get there directly. */ - SetMoverDest(pActor, nextx, nexty); - pActor->over = false; + SetMoverDest(pMover, nextx, nexty); + pMover->over = false; break; } else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT) || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { @@ -851,61 +901,61 @@ static void SetNextDest(PMACTOR pActor) { something about it. | -------------------------------------------------*/ if (s2 & ENTERING_BLOCK) { - x = pActor->objx; // Current position - y = pActor->objy; + x = pMover->objX; // Current position + y = pMover->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; + BlockingCorner(hS2p, &x, &y, pMover->ItargetX, pMover->ItargetY); + SetMoverDest(pMover, x, y); + pMover->over = false; } else if (s2 & ENTERING_MBLOCK) { - if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { + if (InMoverBlock(pMover, pMover->UtargetX, pMover->UtargetY)) { // The best we're going to achieve - SetMoverUltDest(pActor, x, y); - SetMoverDest(pActor, x, y); + SetMoverUltDest(pMover, x, y); + SetMoverDest(pMover, 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; + sx = pMover->objX; + sy = pMover->objY; +// pMover->objX = x; +// pMover->objY = y; + + hEb = InitExtraBlock(pMover, collisionActor); + x = pMover->objX; + y = pMover->objY; + BlockingCorner(hEb, &x, &y, pMover->ItargetX, pMover->ItargetY); + + pMover->objX = sx; + pMover->objY = sy; + SetMoverDest(pMover, x, y); + pMover->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; + hPath = InPolygon(pMover->ItargetX, pMover->ItargetY, PATH); + if (hPath != pMover->hIpath) { + if (IsInPolygon(pMover->ItargetX, pMover->ItargetY, pMover->hIpath)) + hPath = pMover->hIpath; } - assert(hPath == pActor->hIpath); + assert(hPath == pMover->hIpath); - if (pActor->InDifficulty == NO_PROB) { + if (pMover->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; + SetMoverDest(pMover, x, y); + pMover->InDifficulty = TRY_CENTRE; + pMover->over = false; + } else if (pMover->InDifficulty == TRY_CENTRE) { + NearestCorner(&x, &y, pMover->hCpath, pMover->hIpath); + SetMoverDest(pMover, x, y); + pMover->InDifficulty = TRY_CORNER; + pMover->over = false; + } else if (pMover->InDifficulty == TRY_CORNER) { + NearestCorner(&x, &y, pMover->hCpath, pMover->hIpath); + SetMoverDest(pMover, x, y); + pMover->InDifficulty = TRY_NEXTCORNER; + pMover->over = false; } } break; @@ -916,41 +966,43 @@ static void SetNextDest(PMACTOR pActor) { A restriction in a direction has been removed. | Use this as an intermediate destination. | -----------------------------------------------*/ - SetMoverDest(pActor, nextx, nexty); - pActor->over = false; + SetMoverDest(pMover, nextx, nexty); + pMover->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); + if (!TinselV2) { + /*------------------------- + Change of path polygon? | + -------------------------*/ + hPath = InPolygon(x, y, PATH); + if (pMover->hCpath != hPath && + !IsInPolygon(x, y, pMover->hCpath) && + !IsAdjacentPath(pMover->hCpath, pMover->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 && pMover->hFnpath != hPath && pMover->npstatus != LEAVING) { + int node = NearestEndNode(hPath, x, y); + getNpathNode(hPath, node, &nx, &ny); + SetMoverDest(pMover, nx, ny); + pMover->over = true; + } else if (!IsInPolygon(pMover->ItargetX, pMover->ItargetY, hPath) && + !IsInPolygon(pMover->ItargetX, pMover->ItargetY, pMover->hCpath)) { + SetMoverDest(pMover, PolyCentreX(hPath), PolyCentreY(hPath)); + pMover->over = true; + } else { + SetMoverDest(pMover, pMover->ItargetX, pMover->ItargetY); + } + break; } - break; - } - lstatus = s1; + lstatus = s1; + } } } @@ -961,19 +1013,19 @@ static void SetNextDest(PMACTOR pActor) { 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) { + PMOVER pMover, PMOVER *collisionActor) { HPOLYGON hPoly; int sidem, depthm; int sidesteps, depthsteps; - PMACTOR ma; + PMOVER 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)) + if (*targetX == pMover->UtargetX && (*targetY == -1 || *targetY == pMover->UtargetY) || + *targetY == pMover->UtargetY && (*targetX == -1 || *targetX == pMover->UtargetX)) bOver = false; /*---------------------------------------------------- @@ -1087,7 +1139,7 @@ static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, Must now check if it's in a legal spot. ------------------------------------------------------*/ - if (!pActor->bNoPath && !pActor->bIgPath) { + if (!pMover->bNoPath && !pMover->bIgPath) { /*------------------------------ Must stay in a path polygon. -------------------------------*/ @@ -1095,10 +1147,10 @@ static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, 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) { + if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCK) == NOPOLY) { *newy = fromy; *s1 |= YRESTRICT; - } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) { + } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCK) == NOPOLY) { *newx = fromx; *s1 |= XRESTRICT; } else { @@ -1115,15 +1167,15 @@ static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, /*-------------------------------------- Must stay out of blocking polygons. --------------------------------------*/ - hPoly = InPolygon(*newx, *newy, BLOCKING); + hPoly = InPolygon(*newx, *newy, BLOCK); 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) { + if (*newx != fromx && InPolygon(*newx, fromy, BLOCK) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { *newy = fromy; *s1 |= YRESTRICT; - } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { + } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCK) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { *newx = fromx; *s1 |= XRESTRICT; } else { @@ -1138,22 +1190,22 @@ static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, /*------------------------------------------------------ Must stay out of moving actors' blocking polygons. ------------------------------------------------------*/ - ma = InMActorBlock(pActor, *newx, *newy); + ma = InMoverBlock(pMover, *newx, *newy); if (ma != NULL) { // Ignore if already in it (it may have just appeared) - if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) { + if (!InMoverBlock(pMover, pMover->objX, pMover->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) { + if (*newx != fromx && InMoverBlock(pMover, *newx, fromy) == NULL + && InPolygon(*newx, fromy, BLOCK) == 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) { + } else if (*newy != fromy && InMoverBlock(pMover, fromx, *newy) == NULL + && InPolygon(fromx, *newy, BLOCK) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { *newx = fromx; *s1 |= XRESTRICT; } else { @@ -1172,7 +1224,7 @@ static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, /** * SetOffWithinNodePath */ -static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath, +static void SetOffWithinNodePath(PMOVER pMover, HPOLYGON StartPath, HPOLYGON DestPath, int targetX, int targetY) { int endnode; HPOLYGON hIpath; @@ -1180,17 +1232,17 @@ static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON De 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; + if (pMover->line == pMover->Tline) { + SetMoverDest(pMover, pMover->UtargetX, pMover->UtargetY); + pMover->over = false; + } else if (pMover->line < pMover->Tline) { + getNpathNode(StartPath, pMover->line+1, &nx, &ny); + SetMoverDest(pMover, nx, ny); + pMover->npstatus = GOING_UP; + } else if (pMover->line > pMover->Tline) { + getNpathNode(StartPath, pMover->line, &nx, &ny); + SetMoverDest(pMover, nx, ny); + pMover->npstatus = GOING_DOWN; } } else { /* @@ -1198,7 +1250,7 @@ static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON De * which end of this path to head for. */ assert(DestPath != NOPOLY); // Error 702 - if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) { + 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); @@ -1213,20 +1265,31 @@ static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON De } #if 1 - if ((pActor->npstatus == LEAVING) && - endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) { + if ((pMover->npstatus == LEAVING) && + endnode == NearestEndNode(StartPath, pMover->objX, pMover->objY)) { // Leave it be + + if (TinselV2) { + // Yeah, but we need a destination + // It's release night and there's this problem in the bar... + if (hIpath) // must be, but... + { + // could go for its end node if it's an NPATH + // but we probably will when we hit it anyway! + SetMoverDest(pMover, PolyCentreX(hIpath), PolyCentreY(hIpath)); + } + } } else #endif { if (endnode) { - getNpathNode(StartPath, pActor->line+1, &nx, &ny); - SetMoverDest(pActor, nx, ny); - pActor->npstatus = GOING_UP; + getNpathNode(StartPath, pMover->line+1, &nx, &ny); + SetMoverDest(pMover, nx, ny); + pMover->npstatus = GOING_UP; } else { - getNpathNode(StartPath, pActor->line, &nx, &ny); - SetMoverDest(pActor, nx, ny); - pActor->npstatus = GOING_DOWN; + getNpathNode(StartPath, pMover->line, &nx, &ny); + SetMoverDest(pMover, nx, ny); + pMover->npstatus = GOING_DOWN; } } } @@ -1235,52 +1298,79 @@ static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON De /** * Restore a movement, called from restoreMovement() in ACTORS.CPP */ -void SSetActorDest(PMACTOR pActor) { +void SSetActorDest(PMOVER pActor) { if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { - stand(pActor->actorID, pActor->objx, pActor->objy, 0); + Stand(nullContext, 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); + Stand(nullContext, 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) { +int SetActorDest(PMOVER pMover, int clickX, int clickY, bool igPath, SCNHANDLE hFilm) { 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 (TinselV2) { + // No need to synchronise if not moving! + // Hopefully will stop luggage flip in shades. + if (!MoverMoving(pMover)) + pMover->stepCount = 0; + + // Fix interrupted-walking-to-wardrobe bug in mortuary + StopMover(pMover); + } else { + if (pMover->actorID == GetLeadId()) // Now only for lead actor + UnTagActor(pMover->actorID); // Tag not allowed while moving + } + + pMover->walkNumber++; + pMover->bStop = false; + pMover->over = false; + pMover->walkedFromX = pMover->objX; + pMover->walkedFromY = pMover->objY; + pMover->bMoving = true; + pMover->bIgPath = igPath; + pMover->zOverride = -1; + pMover->hRpath = NOPOLY; + + if (!TinselV2) { + // Use the supplied reel or restore the normal actor. + if (hFilm != 0) + AlterMover(pMover, hFilm, AR_WALKREEL); + else + AlterMover(pMover, 0, AR_NORMAL); + } if (igPath) { targetX = clickX; targetY = clickY; + + if (pMover->actorID == GetLeadId()) { + lastLeadXdest = targetX; + lastLeadYdest = targetY; + } } else { - if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) { - GotThere(pActor); - return; + int wodResult = WorkOutDestination(clickX, clickY, &targetX, &targetY); + + if (pMover->actorID == GetLeadId()) { + lastLeadXdest = targetX; + lastLeadYdest = targetY; + } + + if (wodResult == ALL_SORTED) { + GotThere(pMover); + return 0; } assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination! - assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination! + assert(InPolygon(targetX, targetY, BLOCK) == NOPOLY); // illegal destination! } @@ -1289,9 +1379,9 @@ void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE /*---------------------------------- | Don't move if it's not worth it. ----------------------------------*/ - if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) { - GotThere(pActor); - return; + if (ABS(targetX - pMover->objX) < XMDIST && ABS(targetY - pMover->objY) < YMDIST) { + GotThere(pMover); + return 0; } /*------------------------------------------------------ @@ -1302,41 +1392,57 @@ void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE 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); + FindBestPoint(DestPath, &targetX, &targetY, &pMover->Tline); } } - assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005 - SetMoverUltDest(pActor, targetX, targetY); - SetMoverIntDest(pActor, targetX, targetY); + assert(pMover->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005 + SetMoverUltDest(pMover, targetX, targetY); + SetMoverIntDest(pMover, targetX, targetY); + + if (TinselV2) { + // No movement for unconnected paths + if (pMover->hIpath == NOPOLY && !igPath) { + GotThere(pMover); + return 0; + } + + // Use the supplied reel or restore the normal actor. + if (hFilm != 0) + AlterMover(pMover, hFilm, AR_WALKREEL); + else + AlterMover(pMover, 0, AR_NORMAL); + } /*------------------------------------------------------------------- | 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); + if ((StartPath = pMover->hFnpath) != NOPOLY && !igPath) { + SetOffWithinNodePath(pMover, StartPath, DestPath, targetX, targetY); } else { // Set off! - SetNextDest(pActor); + SetNextDest(pMover); } + + return pMover->walkNumber; } /** * Change scale if appropriate. */ -static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) { +static void CheckScale(PMOVER pActor, HPOLYGON hPath, int ypos) { int scale; scale = GetScale(hPath, ypos); if (scale != pActor->scale) { - SetMActorWalkReel(pActor, pActor->dirn, scale, false); + SetMoverWalkReel(pActor, pActor->direction, scale, false); } } /** * Not going anywhere - Kick off again if not at final destination. */ -static void NotMoving(PMACTOR pActor, int x, int y) { +static void NotMoving(PMOVER pActor, int x, int y) { pActor->targetX = pActor->targetY = -1; // if (x == pActor->UtargetX && y == pActor->UtargetY) @@ -1357,19 +1463,21 @@ static void NotMoving(PMACTOR pActor, int x, int y) { /** * Does the necessary business when entering a different path polygon. */ -static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) { +static void EnteringNewPath(PMOVER pMover, HPOLYGON hPath, int x, int y) { int firstnode; // First node to go to int lastnode; // Last node to go to HPOLYGON hIpath; + HPOLYGON hLpath; // one we're leaving int nx, ny; int nxl, nyl; - pActor->hCpath = hPath; // current path + hLpath = pMover->hCpath; + pMover->hCpath = hPath; // current path if (hPath == NOPOLY) { // Not proved this ever happens, but just in case - pActor->hFnpath = NOPOLY; - pActor->npstatus = NOT_IN; + pMover->hFnpath = NOPOLY; + pMover->npstatus = NOT_IN; return; } @@ -1381,12 +1489,12 @@ static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) { // 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); + if (hPath != pMover->hUpath) { + if (pMover->bIgPath) { + lastnode = NearestEndNode(hPath, pMover->UtargetX, pMover->UtargetY); } else { - assert(pActor->hUpath != NOPOLY); // Error 703 - hIpath = getPathOnTheWay(hPath, pActor->hUpath); + assert(pMover->hUpath != NOPOLY); // Error 703 + hIpath = GetPathOnTheWay(hPath, pMover->hUpath); assert(hIpath != NOPOLY); // No path on the way if (PolySubtype(hIpath) != NODE) { @@ -1406,68 +1514,76 @@ static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) { // 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); + pMover->hFnpath = NOPOLY; + pMover->npstatus = NOT_IN; + assert(pMover->bIgPath || InPolygon(pMover->UtargetX, pMover->UtargetY, PATH) != NOPOLY); // Error 5007 + SetMoverIntDest(pMover, pMover->UtargetX, pMover->UtargetY); + SetNextDest(pMover); } 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); + pMover->over = true; + pMover->npstatus = ENTERING; + pMover->hFnpath = hPath; + pMover->line = firstnode ? firstnode - 1 : firstnode; + if (pMover->line == pMover->Tline && hPath == pMover->hUpath) { + assert(pMover->bIgPath || InPolygon(pMover->UtargetX, pMover->UtargetY, PATH) != NOPOLY); // Error 5008 + SetMoverIntDest(pMover, pMover->UtargetX, pMover->UtargetY); + SetMoverDest(pMover, pMover->UtargetX, pMover->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); + if (ABS(pMover->objX - nx) < XMDIST + && ABS(pMover->objY - ny) < YMDIST) { + pMover->npstatus = ENTERING; + pMover->hFnpath = hPath; + SetNextDest(pMover); } else { getNpathNode(hPath, firstnode, &nx, &ny); - SetMoverDest(pActor, nx, ny); + SetMoverDest(pMover, 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)) + pMover->hFnpath = NOPOLY; + pMover->npstatus = NOT_IN; + assert(pMover->bIgPath || InPolygon(pMover->UtargetX, pMover->UtargetY, PATH) != NOPOLY); // Error 5009 + + // Added 26/01/95 + if (IsPolyCorner(hPath, pMover->ItargetX, pMover->ItargetY)) return; - SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); - SetNextDest(pActor); + // Added 23/10/96 + if (TinselV2 && (pMover->hRpath == hPath)) + return; + + if (TinselV2) + pMover->hRpath = hLpath; + SetMoverIntDest(pMover, pMover->UtargetX, pMover->UtargetY); + SetNextDest(pMover); } } /** * 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) { +void Move(PMOVER pMover, int newx, int newy, HPOLYGON hPath) { + pMover->objX = newx; + pMover->objY = newy; + + MultiSetAniXY(pMover->actorObj, newx, newy); + SetMoverZ(pMover, newy, GetPolyZfactor(hPath)); + if (StepAnimScript(&pMover->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); + pMover->bWalkReel = false; + pMover->stepCount = 0; + SetMoverWalkReel(pMover, pMover->direction, pMover->scale, true); } - pActor->objx = newx; - pActor->objy = newy; // Synchronised walking reels - if (++pActor->scount >= 6) - pActor->scount = 0; + if (++pMover->stepCount >= STEPS_MAX) + pMover->stepCount = 0; } /** @@ -1475,24 +1591,25 @@ void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) { * * Moves the actor as appropriate. */ -void MoveActor(PMACTOR pActor) { +void MoveActor(PMOVER pMover) { int newx, newy; HPOLYGON hPath; int status, s2; // s2 not used here! HPOLYGON hS2p; // nor is s2p! HPOLYGON hEb; - PMACTOR ma; + PMOVER ma; int sTargetX, sTargetY; bool bNewPath = false; // Only do anything if the actor needs to move! - if (pActor->targetX == -1 && pActor->targetY == -1) + if (pMover->targetX == -1 && pMover->targetY == -1) return; - if (pActor->stop) { - GotThere(pActor); - pActor->stop = false; - SetMActorStanding(pActor); + if (pMover->bStop) { + GotThere(pMover); + pMover->bStop = false; + pMover->walkNumber++; + SetMoverStanding(pMover); return; } @@ -1502,29 +1619,31 @@ void MoveActor(PMACTOR pActor) { /**/ BogusVar = 0; // #endif - // During swalk()s, movement while hidden may be slowed down. - if (pActor->aHidden) { - if (++hSlowVar < pActor->SlowFactor) - return; - hSlowVar = 0; + if (!TinselV2) { + // During swalk()s, movement while hidden may be slowed down. + if (pMover->bHidden) { + if (++hSlowVar < pMover->SlowFactor) + return; + hSlowVar = 0; + } } // 'push' the target - sTargetX = pActor->targetX; - sTargetY = pActor->targetY; + sTargetX = pMover->targetX; + sTargetY = pMover->targetY; - NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, - &newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor); + NewCoOrdinates(pMover->objX, pMover->objY, &pMover->targetX, &pMover->targetY, + &newx, &newy, &status, &s2, &hS2p, pMover->over, false, pMover); - if (newx == pActor->objx && newy == pActor->objy) { + if (newx == pMover->objX && newy == pMover->objY) { // 'pop' the target - pActor->targetX = sTargetX; - pActor->targetY = sTargetY; + pMover->targetX = sTargetX; + pMover->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); + NewCoOrdinates(pMover->objX, pMover->objY, &pMover->targetX, &pMover->targetY, &newx, &newy, + &status, &s2, &hS2p, pMover->over, true, pMover); + if (newx == pMover->objX && newy == pMover->objY) { + NotMoving(pMover, newx, newy); return; } } @@ -1532,43 +1651,43 @@ void MoveActor(PMACTOR pActor) { // 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); + if (pMover->bNoPath) { + Move(pMover, newx, newy, pMover->hCpath); return; } else { // May be marginally outside! // OR bIgPath may be set. - hPath = pActor->hCpath; + hPath = pMover->hCpath; } - } else if (pActor->bNoPath) { - pActor->bNoPath = false; + } else if (pMover->bNoPath) { + pMover->bNoPath = false; bNewPath = true; - } else if (hPath != pActor->hCpath) { - if (IsInPolygon(newx, newy, pActor->hCpath)) - hPath = pActor->hCpath; + } else if (hPath != pMover->hCpath) { + if (IsInPolygon(newx, newy, pMover->hCpath)) + hPath = pMover->hCpath; } - CheckScale(pActor, hPath, newy); + CheckScale(pMover, hPath, newy); /* * Must stay out of moving actors' blocking polygons. */ - ma = InMActorBlock(pActor, newx, newy); + ma = InMoverBlock(pMover, newx, newy); if (ma != NULL) { // Stop if there's no chance of arriving - if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { - GotThere(pActor); + if (InMoverBlock(pMover, pMover->UtargetX, pMover->UtargetY)) { + GotThere(pMover); return; } - if (InMActorBlock(pActor, pActor->objx, pActor->objy)) + if (InMoverBlock(pMover, pMover->objX, pMover->objY)) ; else { - hEb = InitExtraBlock(pActor, ma); - newx = pActor->objx; - newy = pActor->objy; - BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY); - SetMoverDest(pActor, newx, newy); + hEb = InitExtraBlock(pMover, ma); + newx = pMover->objX; + newy = pMover->objY; + BlockingCorner(hEb, &newx, &newy, pMover->ItargetX, pMover->ItargetY); + SetMoverDest(pMover, newx, newy); return; } } @@ -1576,11 +1695,11 @@ void MoveActor(PMACTOR pActor) { /*-------------------------------------- This is where it actually gets moved. --------------------------------------*/ - Move(pActor, newx, newy, hPath); + Move(pMover, newx, newy, hPath); // Entering a new path polygon? - if (hPath != pActor->hCpath || bNewPath) - EnteringNewPath(pActor, hPath, newx, newy); + if (hPath != pMover->hCpath || bNewPath) + EnteringNewPath(pMover, hPath, newx, newy); } /** @@ -1590,25 +1709,36 @@ void SetDefaultRefer(int32 defRefer) { DefaultRefer = defRefer; } +int GetLastLeadXdest(void) { + return lastLeadXdest; +} + +int GetLastLeadYdest(void) { + return lastLeadYdest; +} + + + + /** * DoMoveActor */ -void DoMoveActor(PMACTOR pActor) { +void DoMoveActor(PMOVER pActor) { int wasx, wasy; int i; #define NUMBER 1 - wasx = pActor->objx; - wasy = pActor->objy; + wasx = pActor->objX; + wasy = pActor->objY; MoveActor(pActor); if ((pActor->targetX != -1 || pActor->targetY != -1) - && (wasx == pActor->objx && wasy == pActor->objy)) { + && (wasx == pActor->objX && wasy == pActor->objY)) { for (i=0; i < NUMBER; i++) { MoveActor(pActor); - if (wasx != pActor->objx || wasy != pActor->objy) + if (wasx != pActor->objX || wasy != pActor->objY) break; } // assert(iflags & DMA_FLIPH) { - // image is flipped horizontally - flip the x direction - deltaX = -deltaX; - } + if (!TinselV2) { + // *** This may be wrong!!! + 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; + 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 @@ -530,4 +534,32 @@ int MultiLowest(OBJECT *pMulti) { return lowest - 1; } +/** + * Returns TRUE if the object currently has an image. + * @param pMulti Multi-part object + */ + +bool MultiHasShape(POBJECT pMulti) { + return (pMulti->hShape != 0); +} + +/** + * Bodge for text on movies. Makes sure it appears for it's lifetime. + * @param pMultiObj Multi-part object to be adjusted + */ + +void MultiForceRedraw(POBJECT pMultiObj) { + // 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; + + // next obj in list + pMultiObj = pMultiObj->pSlave; + } while (pMultiObj != NULL); +} + } // end of namespace Tinsel diff --git a/engines/tinsel/multiobj.h b/engines/tinsel/multiobj.h index 6d25600ea2..9bf1579363 100644 --- a/engines/tinsel/multiobj.h +++ b/engines/tinsel/multiobj.h @@ -28,6 +28,7 @@ #define TINSEL_MULTIOBJ_H #include "tinsel/dw.h" +#include "tinsel/object.h" namespace Tinsel { @@ -45,7 +46,9 @@ struct MULTI_INIT { int32 mulX; //!< multi-objects initial x ani position int32 mulY; //!< multi-objects initial y ani position int32 mulZ; //!< multi-objects initial z position + uint32 otherFlags; //!< multi-objects Tinsel 2 - other flags } PACKED_STRUCT; +typedef MULTI_INIT *PMULTI_INIT; #include "common/pack-end.h" // END STRUCT PACKING @@ -119,6 +122,12 @@ int MultiHighest( // Returns the highest point of a multi-part object int MultiLowest( // Returns the lowest point of a multi-part object OBJECT *pMulti); // multi-part object +bool MultiHasShape( // Returns TRUE if the object currently has an image + POBJECT pMulti); // multi-part object + +void MultiForceRedraw( + POBJECT pMultiObj); // multi-part object to be forced + } // end of namespace Tinsel #endif // TINSEL_MULTIOBJ_H diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp index d165ba0a10..979fc61270 100644 --- a/engines/tinsel/music.cpp +++ b/engines/tinsel/music.cpp @@ -31,12 +31,21 @@ #include "sound/mididrv.h" #include "sound/midiparser.h" #include "sound/audiocd.h" +#include "sound/adpcm.h" #include "common/config-manager.h" #include "common/file.h" #include "tinsel/config.h" #include "tinsel/sound.h" #include "tinsel/music.h" +#include "tinsel/handle.h" +#include "tinsel/sysvar.h" + +#define MUSIC_JUMP (-1) +#define MUSIC_END (-2) +#define BLMAGIC (-3458) + +#define DIM_SPEED 8 namespace Tinsel { @@ -153,8 +162,8 @@ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { currentMidi = dwFileOffset; currentLoop = bLoop; - if (volMidi != 0) { - SetMidiVolume(volMidi); + if (volMusic != 0) { + SetMidiVolume(volMusic); // Support for compressed music from the music enhancement project AudioCD.stop(); @@ -191,7 +200,7 @@ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { // open MIDI sequence file in binary mode if (!midiStream.open(MIDI_FILE)) - error("Cannot find file %s", MIDI_FILE); + error(CANNOT_FIND_FILE, MIDI_FILE); // update index of last tune loaded dwLastMidiIndex = dwMidiIndex; @@ -206,22 +215,22 @@ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size); // stop any currently playing tune - _vm->_music->stop(); + _vm->_midiMusic->stop(); // read the sequence if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen) - error("File %s is corrupt", MIDI_FILE); + error(FILE_IS_CORRUPT, MIDI_FILE); midiStream.close(); - _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + _vm->_midiMusic->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); // Store the length dwLastSeqLen = dwSeqLen; } else { // dwMidiIndex == dwLastMidiIndex - _vm->_music->stop(); - _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + _vm->_midiMusic->stop(); + _vm->_midiMusic->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); } // allow another sequence to play @@ -235,7 +244,7 @@ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { */ bool MidiPlaying(void) { if (AudioCD.isPlaying()) return true; - return _vm->_music->isPlaying(); + return _vm->_midiMusic->isPlaying(); } /** @@ -246,7 +255,7 @@ bool StopMidi(void) { currentLoop = false; AudioCD.stop(); - _vm->_music->stop(); + _vm->_midiMusic->stop(); return true; } @@ -255,7 +264,7 @@ bool StopMidi(void) { * Gets the volume of the MIDI music. */ int GetMidiVolume() { - return volMidi; + return volMusic; } /** @@ -265,24 +274,24 @@ int GetMidiVolume() { void SetMidiVolume(int vol) { assert(vol >= 0 && vol <= Audio::Mixer::kMaxChannelVolume); - if (vol == 0 && volMidi == 0) { + if (vol == 0 && volMusic == 0) { // Nothing to do - } else if (vol == 0 && volMidi != 0) { + } else if (vol == 0 && volMusic != 0) { // Stop current midi sequence AudioCD.stop(); StopMidi(); - } else if (vol != 0 && volMidi == 0) { + } else if (vol != 0 && volMusic == 0) { // Perhaps restart last midi sequence if (currentLoop) { PlayMidiSequence(currentMidi, true); - _vm->_music->setVolume(vol); + _vm->_midiMusic->setVolume(vol); } - } else if (vol != 0 && volMidi != 0) { + } else if (vol != 0 && volMusic != 0) { // Alter current volume - _vm->_music->setVolume(vol); + _vm->_midiMusic->setVolume(vol); } - volMidi = vol; + volMusic = vol; } /** @@ -292,7 +301,7 @@ void OpenMidiFiles(void) { Common::File midiStream; // Demo version has no midi file - if (_vm->getFeatures() & GF_DEMO) + if ((_vm->getFeatures() & GF_DEMO) || (TinselVersion == TINSEL_V2)) return; if (midiBuffer.pDat) @@ -301,12 +310,12 @@ void OpenMidiFiles(void) { // open MIDI sequence file in binary mode if (!midiStream.open(MIDI_FILE)) - error("Cannot find file %s", MIDI_FILE); + error(CANNOT_FIND_FILE, MIDI_FILE); // gen length of the largest sequence midiBuffer.size = midiStream.readUint32LE(); if (midiStream.ioFailed()) - error("File %s is corrupt", MIDI_FILE); + error(FILE_IS_CORRUPT, MIDI_FILE); if (midiBuffer.size) { // allocate a buffer big enough for the largest MIDI sequence @@ -327,14 +336,14 @@ void DeleteMidiBuffer() { midiBuffer.pDat = NULL; } -MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) { +MidiMusicPlayer::MidiMusicPlayer(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() { +MidiMusicPlayer::~MidiMusicPlayer() { _driver->setTimerCallback(NULL, NULL); stop(); this->close(); @@ -342,7 +351,7 @@ MusicPlayer::~MusicPlayer() { delete _xmidiParser; } -void MusicPlayer::setVolume(int volume) { +void MidiMusicPlayer::setVolume(int volume) { _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); if (_masterVolume == volume) @@ -359,7 +368,7 @@ void MusicPlayer::setVolume(int volume) { } } -int MusicPlayer::open() { +int MidiMusicPlayer::open() { // Don't ever call open without first setting the output driver! if (!_driver) return 255; @@ -372,14 +381,14 @@ int MusicPlayer::open() { return 0; } -void MusicPlayer::close() { +void MidiMusicPlayer::close() { stop(); if (_driver) _driver->close(); _driver = 0; } -void MusicPlayer::send(uint32 b) { +void MidiMusicPlayer::send(uint32 b) { byte channel = (byte)(b & 0x0F); if ((b & 0xFFF0) == 0x07B0) { // Adjust volume changes by master volume @@ -409,7 +418,7 @@ void MusicPlayer::send(uint32 b) { } } -void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { +void MidiMusicPlayer::metaEvent(byte type, byte *data, uint16 length) { switch (type) { case 0x2F: // End of Track if (_looping) @@ -423,15 +432,15 @@ void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { } } -void MusicPlayer::onTimer(void *refCon) { - MusicPlayer *music = (MusicPlayer *)refCon; +void MidiMusicPlayer::onTimer(void *refCon) { + MidiMusicPlayer *music = (MidiMusicPlayer *)refCon; Common::StackLock lock(music->_mutex); if (music->_isPlaying) music->_parser->onTimer(); } -void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) { +void MidiMusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) { if (_isPlaying) return; @@ -463,7 +472,7 @@ void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) { } } -void MusicPlayer::stop() { +void MidiMusicPlayer::stop() { Common::StackLock lock(_mutex); _isPlaying = false; @@ -473,16 +482,414 @@ void MusicPlayer::stop() { } } -void MusicPlayer::pause() { +void MidiMusicPlayer::pause() { setVolume(-1); _isPlaying = false; } -void MusicPlayer::resume() { +void MidiMusicPlayer::resume() { setVolume(GetMidiVolume()); _isPlaying = true; } +PCMMusicPlayer::PCMMusicPlayer() { + _silenceSamples = 0; + + _curChunk = 0; + _fileName = 0; + _state = S_IDLE; + _mState = S_IDLE; + _scriptNum = -1; + _scriptIndex = 0; + _forcePlay = false; + + _volume = 255; + _dimmed = false; + _dimmedTinsel = false; + _dimIteration = 0; + + _fadeOutVolume = 0; + _fadeOutIteration = 0; + + _end = true; + + _vm->_mixer->playInputStream(Audio::Mixer::kMusicSoundType, + &_handle, this, -1, _volume, 0, false, true); +} + +PCMMusicPlayer::~PCMMusicPlayer() { + _vm->_mixer->stopHandle(_handle); + + delete[] _fileName; +} + +void PCMMusicPlayer::startPlay(int id) { + if (!_fileName) + return; + + debugC(DEBUG_DETAILED, kTinselDebugMusic, "Playing PCM music %s, index %d", _fileName, id); + + Common::StackLock slock(_mutex); + + stop(); + + _scriptNum = id; + _scriptIndex = 1; + _state = S_NEW; + + play(); +} + +void PCMMusicPlayer::stopPlay() { + Common::StackLock slock(_mutex); + + stop(); +} + +int PCMMusicPlayer::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock slock(_mutex); + + if (!_curChunk && ((_state == S_IDLE) || (_state == S_STOP))) + return 0; + + int samplesLeft = numSamples; + + while (samplesLeft > 0) { + if (_silenceSamples > 0) { + int n = MIN(_silenceSamples, samplesLeft); + + memset(buffer, 0, n); + + buffer += n; + _silenceSamples -= n; + samplesLeft -= n; + + } else if (_curChunk && + ((_state == S_MID) || (_state == S_NEXT) || (_state == S_NEW))) { + int n = _curChunk->readBuffer(buffer, samplesLeft); + + buffer += n; + samplesLeft -= n; + + if (_curChunk->endOfData()) { + _state = S_END1; + + delete _curChunk; + _curChunk = 0; + } + } else { + + if (!getNextChunk()) + break; + } + } + + return (numSamples - samplesLeft); +} + +bool PCMMusicPlayer::isPlaying() const { + return ((_state != S_IDLE) && (_state != S_STOP)); +} + +bool PCMMusicPlayer::isDimmed() const { + return _dimmed; +} + +void PCMMusicPlayer::getTunePlaying(void *voidPtr, int length) { + Common::StackLock lock(_mutex); + + debugC(DEBUG_DETAILED, kTinselDebugMusic, "getTunePlaying"); + + assert(length == (3 * sizeof(int32))); + + int32 *p = (int32 *) voidPtr; + + _mState = _state; + + p[0] = (int32) _mState; + p[1] = _scriptNum; + p[2] = _scriptIndex; +} + +void PCMMusicPlayer::restoreThatTune(void *voidPtr) { + Common::StackLock lock(_mutex); + + debugC(DEBUG_DETAILED, kTinselDebugMusic, "restoreThatTune"); + + int32 *p = (int32 *) voidPtr; + + _mState = (State) p[0]; + _scriptNum = p[1]; + _scriptIndex = p[2]; + + if (_mState != S_IDLE) + _state = S_NEW; + + delete _curChunk; + _curChunk = 0; + + _end = false; +} + +void PCMMusicPlayer::setMusicSceneDetails(SCNHANDLE hScript, + SCNHANDLE hSegment, const char *fileName) { + + Common::StackLock lock(_mutex); + + stop(); + + debugC(DEBUG_INTERMEDIATE, kTinselDebugMusic, "Setting music scene details: %s", fileName); + + _hScript = hScript; + _hSegment = hSegment; + _fileName = new char[strlen(fileName) + 1]; + strcpy(_fileName, fileName); + + // Start scene with music not dimmed + _dimmed = false; + _dimmedTinsel = false; + _dimIteration = 0; + setVol(255); +} + +void PCMMusicPlayer::setVolume(int volume) { + assert((volume >= 0) && (volume <= 100)); + + _dimmed = false; + setVol((volume * 255) / 100); +} + +void PCMMusicPlayer::setVol(uint8 volume) { + _volume = volume; + + _vm->_mixer->setChannelVolume(_handle, _volume); +} + +bool PCMMusicPlayer::getMusicTinselDimmed() const { + return _dimmedTinsel; +} + +void PCMMusicPlayer::dim(bool bTinselDim) { + if (_dimmed || (_volume == 0) || + (_state == S_IDLE) || !_curChunk || (SysVar(SV_MUSICDIMFACTOR) == 0)) + return; + + _dimmed = true; + if (bTinselDim) + _dimmedTinsel = true; + + _dimmedVolume = _volume - (_volume / SysVar(SV_MUSICDIMFACTOR)); + + // Iterate down, negative iteration + if (!_dimIteration) + _dimPosition = _volume; + _dimIteration = (_dimmedVolume - _volume)/DIM_SPEED; + + debugC(DEBUG_DETAILED, kTinselDebugMusic, "Dimming music from %d to %d, steps %d", _dimPosition, _dimmedVolume, _dimIteration); + + // And SFX + if (SysVar(SYS_SceneFxDimFactor)) + _vm->_sound->setSFXVolumes(255 - 255/SysVar(SYS_SceneFxDimFactor)); +} + +void PCMMusicPlayer::unDim(bool bTinselUnDim) { + if (!_dimmed || (_dimmedTinsel && !bTinselUnDim)) + return; // not dimmed + + _dimmed = _dimmedTinsel = false; + + if ((_volume == 0) || (_state == S_IDLE) || !_curChunk) + return; + + // Iterate up, positive iteration + if (!_dimIteration) + _dimPosition = _dimmedVolume; + _dimIteration = (_volume - _dimmedVolume)/DIM_SPEED; + + debugC(DEBUG_DETAILED, kTinselDebugMusic, "UnDimming music from %d to %d, steps %d", _dimPosition, _volume, _dimIteration); + + // And SFX + _vm->_sound->setSFXVolumes(255); +} + +void PCMMusicPlayer::dimIteration() { + if (_dimIteration != 0) + { + _dimPosition += _dimIteration; + if (_dimPosition >= _volume) + { + _dimPosition = _volume; + _dimIteration = 0; + } + else if (_dimPosition <= _dimmedVolume) + { + _dimPosition = _dimmedVolume; + _dimIteration = 0; + } + + _vm->_mixer->setChannelVolume(_handle, _dimPosition); + } +} + +void PCMMusicPlayer::startFadeOut(int ticks) { + if ((_volume == 0) || (_state == S_IDLE) || !_curChunk) + return; + + debugC(DEBUG_INTERMEDIATE, kTinselDebugMusic, "Fading out music..."); + + if (_dimmed) { + // Start from dimmed volume and go from there + _dimmed = false; + _fadeOutVolume = _volume - _volume/SysVar(SV_MUSICDIMFACTOR); + } else + _fadeOutVolume = _volume; + + assert(ticks != 0); + _fadeOutIteration = _fadeOutVolume / ticks; + + fadeOutIteration(); +} + +void PCMMusicPlayer::fadeOutIteration() { + if ((_volume == 0) || (_state == S_IDLE) || !_curChunk) + return; + + _fadeOutVolume = CLIP(_fadeOutVolume -= _fadeOutIteration, 0, 255); + + _vm->_mixer->setChannelVolume(_handle, _fadeOutVolume); +} + +bool PCMMusicPlayer::getNextChunk() { + MusicSegment *musicSegments; + int32 *script, *scriptBuffer; + int id; + int snum; + uint32 sampleOffset, sampleLength, sampleCLength; + Common::File f; + byte *buffer; + Common::MemoryReadStream *sampleStream; + + switch (_state) { + case S_NEW: + case S_NEXT: + _forcePlay = false; + + script = scriptBuffer = (int32 *) LockMem(_hScript); + + // Set parameters for this chunk of music + id = _scriptNum; + while(id--) + script = scriptBuffer + FROM_LE_32(*script); + snum = FROM_LE_32(script[_scriptIndex++]); + + if (snum == MUSIC_JUMP || snum == MUSIC_END) + { + // Let usual code sort it out! + _scriptIndex--; // Undo increment + _forcePlay = true; // Force a Play + _state = S_END1; // 'Goto' S_END1 + break; + } + + musicSegments = (MusicSegment *) LockMem(_hSegment); + + assert(FROM_LE_32(musicSegments[snum].numChannels) == 1); + assert(FROM_LE_32(musicSegments[snum].bitsPerSample) == 16); + + sampleOffset = FROM_LE_32(musicSegments[snum].sampleOffset); + sampleLength = FROM_LE_32(musicSegments[snum].sampleLength); + sampleCLength = (((sampleLength + 63) & ~63)*33)/64; + + if (!f.open(_fileName)) + error(CANNOT_FIND_FILE, _fileName); + + f.seek(sampleOffset); + if (f.ioFailed() || (uint32)f.pos() != sampleOffset) + error(FILE_IS_CORRUPT, _fileName); + + buffer = (byte *) malloc(sampleCLength); + assert(buffer); + + // read all of the sample + if (f.read(buffer, sampleCLength) != sampleCLength) + error(FILE_IS_CORRUPT, _fileName); + + sampleStream = new Common::MemoryReadStream(buffer, sampleCLength, true); + + delete _curChunk; + _curChunk = makeADPCMStream(sampleStream, true, sampleCLength, + Audio::kADPCMTinsel8, 22050, 1, 32); + + _state = S_MID; + return true; + + case S_END1: + script = scriptBuffer = (int32 *) LockMem(_hScript); + + id = _scriptNum; + while(id--) + script = scriptBuffer + FROM_LE_32(*script); + + switch (script[_scriptIndex]) { + + case MUSIC_END: + _state = S_END2; + break; + + case MUSIC_JUMP: + _scriptIndex = script[++_scriptIndex]; + // Fall through + default: + if (_forcePlay) + _state = S_NEW; + else + _state = S_NEXT; + _forcePlay = false; + break; + } + + return true; + + case S_END2: + _silenceSamples = 11025; // Half a second of silence + return true; + + case S_END3: + stop(); + _state = S_IDLE; + return false; + + case S_IDLE: + return false; + + default: + break; + } + + return true; +} + +void PCMMusicPlayer::play() { + if (_curChunk) + return; + if (_scriptNum == -1) + return; + + _end = false; + + getNextChunk(); +} + +void PCMMusicPlayer::stop() { + delete _curChunk; + _curChunk = 0; + _scriptNum = -1; + _state = S_IDLE; + _mState = S_IDLE; + + _end = true; +} + void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) { *pMidi = currentMidi; *pLoop = currentLoop; @@ -495,9 +902,9 @@ void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) { currentMidi = Midi; currentLoop = Loop; - if (volMidi != 0 && Loop) { + if (volMusic != 0 && Loop) { PlayMidiSequence(currentMidi, true); - SetMidiVolume(volMidi); + SetMidiVolume(volMusic); } } @@ -546,4 +953,4 @@ void dumpMusic() { } #endif -} // End of namespace Made +} // End of namespace Tinsel diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h index 3d647f95bf..1909430c32 100644 --- a/engines/tinsel/music.h +++ b/engines/tinsel/music.h @@ -30,6 +30,8 @@ #include "sound/mididrv.h" #include "sound/midiparser.h" +#include "sound/audiostream.h" +#include "sound/mixer.h" #include "common/mutex.h" namespace Tinsel { @@ -58,11 +60,10 @@ SCNHANDLE GetTrackOffset(int trackNumber); void dumpMusic(); - -class MusicPlayer : public MidiDriver { +class MidiMusicPlayer : public MidiDriver { public: - MusicPlayer(MidiDriver *driver); - ~MusicPlayer(); + MidiMusicPlayer(MidiDriver *driver); + ~MidiMusicPlayer(); bool isPlaying() { return _isPlaying; } void setPlaying(bool playing) { _isPlaying = playing; } @@ -71,6 +72,7 @@ public: int getVolume() { return _masterVolume; } void playXMIDI(byte *midiData, uint32 size, bool loop); + void stop(); void pause(); void resume(); @@ -111,6 +113,93 @@ protected: byte _masterVolume; }; -} // End of namespace Made +class PCMMusicPlayer : public Audio::AudioStream { +public: + PCMMusicPlayer(); + ~PCMMusicPlayer(); + + bool isPlaying() const; + + bool isDimmed() const; + + void getTunePlaying(void *voidPtr, int length); + void restoreThatTune(void *voidPtr); + + void setMusicSceneDetails(SCNHANDLE hScript, SCNHANDLE hSegment, const char *fileName); + + void setVolume(int volume); + + void startPlay(int id); + void stopPlay(); + + bool getMusicTinselDimmed() const; + void dim(bool bTinselDim); + void unDim(bool bTinselUnDim); + void dimIteration(); + + void startFadeOut(int ticks); + void fadeOutIteration(); + + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return false; } + bool endOfData() const { return _end; } + bool endOfStream() const { return false; } + int getRate() const { return 22050; } + +protected: + enum State { + S_IDLE, + S_NEW, + S_MID, + S_END1, + S_END2, + S_END3, + S_NEXT, + S_STOP + }; + + struct MusicSegment { + uint32 numChannels; + uint32 bitsPerSec; + uint32 bitsPerSample; + uint32 sampleLength; + uint32 sampleOffset; + }; + + Audio::SoundHandle _handle; + Audio::AudioStream *_curChunk; + Common::Mutex _mutex; + + bool _end; + + int _silenceSamples; + + State _state, _mState; + bool _forcePlay; + int32 _scriptNum; + int32 _scriptIndex; + SCNHANDLE _hScript; + SCNHANDLE _hSegment; + char *_fileName; + + uint8 _volume; + + bool _dimmed; + bool _dimmedTinsel; + uint8 _dimmedVolume; + int _dimIteration; + int _dimPosition; + + uint8 _fadeOutVolume; + int _fadeOutIteration; + + void play(); + void stop(); + void setVol(uint8 volume); + + bool getNextChunk(); +}; + +} // End of namespace Tinsel #endif diff --git a/engines/tinsel/object.cpp b/engines/tinsel/object.cpp index 709fa4fad9..c9766631f9 100644 --- a/engines/tinsel/object.cpp +++ b/engines/tinsel/object.cpp @@ -29,14 +29,13 @@ #include "tinsel/cliprect.h" // object clip rect defs #include "tinsel/graphics.h" // low level interface #include "tinsel/handle.h" +#include "tinsel/text.h" +#include "tinsel/tinsel.h" #define OID_EFFECTS 0x2000 // generic special effects object id namespace Tinsel { -/** screen clipping rectangle */ -static const Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - // list of all objects OBJECT *objectList = 0; @@ -194,6 +193,7 @@ void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) { */ void DelObject(OBJECT *pObjList, OBJECT *pDelObj) { OBJECT *pPrev, *pObj; // object list traversal pointers + const Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // validate object pointer assert(pDelObj >= objectList && pDelObj <= objectList + NUM_OBJECTS - 1); @@ -232,7 +232,9 @@ void DelObject(OBJECT *pObjList, OBJECT *pDelObj) { } // if we get to here - object has not been found on the list - error("DelObject(): formally 'assert(0)!'"); + // This can be triggered in Act 3 in DW1 while talking to the guard, + // so this has been turned to a warning instead of an error + warning("DelObject(): formally 'assert(0)!'"); } @@ -313,7 +315,7 @@ void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) { // we are flipped vertically // set ani Y = -ani Y + height - 1 - *pAniY = -*pAniY + FROM_LE_16(pImg->imgHeight) - 1; + *pAniY = -*pAniY + (FROM_LE_16(pImg->imgHeight) & ~C16_FLAG_MASK) - 1; } } else // null image @@ -365,21 +367,25 @@ OBJECT *InitObject(const OBJ_INIT *pInitTbl) { // get pointer to image if (pInitTbl->hObjImg) { int aniX, aniY; // objects animation offsets - PALQ *pPalQ; // palette queue pointer + PALQ *pPalQ = NULL; // 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)); + if (pImg->hImgPal) { + // allocate a palette for this object + pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal)); - // make sure palette allocated - assert(pPalQ != NULL); + // 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); + pObj->height = FROM_LE_16(pImg->imgHeight) & ~C16_FLAG_MASK; + pObj->flags &= ~C16_FLAG_MASK; + pObj->flags |= FROM_LE_16(pImg->imgHeight) & C16_FLAG_MASK; // set objects bitmap definition pObj->hBits = FROM_LE_32(pImg->hImgBits); @@ -434,7 +440,9 @@ void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) { // setup new shape pAniObj->width = FROM_LE_16(pNewImg->imgWidth); - pAniObj->height = FROM_LE_16(pNewImg->imgHeight); + pAniObj->height = FROM_LE_16(pNewImg->imgHeight) & ~C16_FLAG_MASK; + newflags &= ~C16_FLAG_MASK; + newflags |= pNewImg->imgHeight & C16_FLAG_MASK; // set objects bitmap definition pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits); diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h index 8b61571a3e..f77c113482 100644 --- a/engines/tinsel/object.h +++ b/engines/tinsel/object.h @@ -51,21 +51,24 @@ enum { 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 + DMA_GHOST = 0x0080, + /** 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 */ +#include "common/pack-start.h" // START STRUCT PACKING struct IMAGE { short imgWidth; //!< image width - short imgHeight; //!< image height + unsigned 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 -}; - +} PACKED_STRUCT; +#include "common/pack-end.h" // END STRUCT PACKING /** a multi-object animation frame is a list of multi-image handles */ typedef uint32 FRAME; @@ -93,6 +96,7 @@ struct OBJECT { SCNHANDLE hMirror; //!< objects previous animation frame int oid; //!< object identifier }; +typedef OBJECT *POBJECT; #include "common/pack-start.h" // START STRUCT PACKING diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp index 3bc2b514b5..afbe6555fc 100644 --- a/engines/tinsel/palette.cpp +++ b/engines/tinsel/palette.cpp @@ -28,6 +28,7 @@ #include "tinsel/graphics.h" #include "tinsel/handle.h" // LockMem definition #include "tinsel/palette.h" // palette allocator structures etc. +#include "tinsel/sysvar.h" #include "tinsel/tinsel.h" #include "common/system.h" @@ -72,6 +73,17 @@ static VIDEO_DAC_Q *pDAChead; /** the translucent palette lookup table */ uint8 transPalette[MAX_COLOURS]; // used in graphics.cpp +uint8 ghostPalette[MAX_COLOURS]; + +static int translucentIndex = 228; + +static int talkIndex = 233; + +static COLORREF talkColRef; + +static COLORREF tagColRef; + + #ifdef DEBUG // diagnostic palette counters static int numPals = 0; @@ -243,6 +255,10 @@ PALQ *AllocPalette(SCNHANDLE hNewPal) { p->hPal = hNewPal; // set hardware palette data p->numColours = FROM_LE_32(pNewPal->numColours); // set number of colours in palette + if (TinselV2) + // Copy all the colours + memcpy(p->palRGB, pNewPal->palRGB, pNewPal->numColours * sizeof(COLORREF)); + #ifdef DEBUG // one more palette in use if (++numPals > maxPals) @@ -250,7 +266,10 @@ PALQ *AllocPalette(SCNHANDLE hNewPal) { #endif // Q the change to the video DAC - UpdateDACqueueHandle(p->posInDAC, p->numColours, p->hPal); + if (TinselV2) + UpdateDACqueue(p->posInDAC, pNewPal->numColours, p->palRGB); + else + 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++) { @@ -265,9 +284,14 @@ PALQ *AllocPalette(SCNHANDLE hNewPal) { + pPrev->numColours | PALETTE_MOVED; // Q the palette change in position to the video DAC - UpdateDACqueueHandle(pNxtPal->posInDAC, - pNxtPal->numColours, - pNxtPal->hPal); + if (!TinselV2) + UpdateDACqueueHandle(pNxtPal->posInDAC, + pNxtPal->numColours, + pNxtPal->hPal); + else if (!pNxtPal->bFading) + UpdateDACqueue(pNxtPal->posInDAC, + pNxtPal->numColours, + pNxtPal->palRGB); // update previous palette to current palette pPrev = pNxtPal; @@ -347,10 +371,22 @@ void SwapPalette(PALQ *pPalQ, SCNHANDLE hNewPal) { // install new palette pPalQ->hPal = hNewPal; - // Q the change to the video DAC - UpdateDACqueueHandle(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), hNewPal); + if (TinselV2) { + pPalQ->numColours = pNewPal->numColours; + + // Copy all the colours + memcpy(pPalQ->palRGB, pNewPal->palRGB, pNewPal->numColours * sizeof(COLORREF)); + + if (!pPalQ->bFading) + // Q the change to the video DAC + UpdateDACqueue(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), pPalQ->palRGB); + } else { + // 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 + assert(!TinselV2); // Fatal error for Tinsel 2 PALQ *pNxtPalQ; // next palette queue position @@ -410,6 +446,33 @@ void SetBgndColour(COLORREF colour) { UpdateDACqueue(BGND_DAC_INDEX, 1, &bgndColour); } +/** + * Note whether a palette is being faded. + * @param pPalQ Palette queue position + * @param bFading Whether it is fading + */ +void FadingPalette(PPALQ pPalQ, bool bFading) { + // validate palette Q pointer + assert(pPalQ >= palAllocData && pPalQ <= palAllocData + NUM_PALETTES - 1); + + // validate that this is a change + assert(pPalQ->bFading != bFading); + + pPalQ->bFading = bFading; +} + +/** + * All fading processes have just been killed, so none of the + * palettes are fading. + */ +void NoFadingPalettes(void) { + PPALQ pPalQ; + + for (pPalQ = palAllocData; pPalQ <= palAllocData + NUM_PALETTES - 1; pPalQ++) { + pPalQ->bFading = false; + } +} + /** * Builds the translucent palette from the current backgrounds palette. * @param hPalette Handle to current background palette @@ -433,8 +496,134 @@ void CreateTranslucentPalette(SCNHANDLE hPalette) { // 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); + transPalette[i + 1] = (uint8)((val == 0) ? 0 : val + + (TinselV2 ? TranslucentColour() : COL_HILIGHT) - 1); + } +} + +/** + * Creates the ghost palette + */ +void CreateGhostPalette(SCNHANDLE hPalette) { + // get a pointer to the palette + PALETTE *pPal = (PALETTE *)LockMem(hPalette); + int i; + + // leave background colour alone + ghostPalette[0] = 0; + + for (i = 0; i < 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 /= 64; + assert(/*val >= 0 &&*/ val <= 3); + ghostPalette[i + 1] = (uint8)(val + SysVar(ISV_GHOST_BASE)); + } +} + + +/** + * Returns an adjusted colour RGB + * @param colour Colour to scale + */ +static COLORREF DimColour(COLORREF colour, int factor) { + uint32 red, green, blue; + + if (factor == 10) { + // No change + return colour; + } else if (factor == 0) { + // No brightness + return 0; + } else { + // apply multiplier to RGB components + red = GetRValue(colour) * factor / 10; + green = GetGValue(colour) * factor / 10; + blue = GetBValue(colour) * factor / 10; + + // return new colour + return RGB(red, green, blue); + } +} + +/** + * DimPartPalette + */ +void DimPartPalette(SCNHANDLE hDimPal, int startColour, int length, int brightness) { + PALQ *pPalQ; + PALETTE *pDimPal; + int iColour; + + pPalQ = FindPalette(hDimPal); + assert(pPalQ); + + // get pointer to dim palette + pDimPal = (PALETTE *)LockMem(hDimPal); + + // Adjust for the fact that palettes don't contain colour 0 + startColour -= 1; + + // Check some other things + if (startColour + length > pPalQ->numColours) + error("DimPartPalette(): colour overrun"); + + for (iColour = startColour ; iColour < startColour + length; iColour++) { + pPalQ->palRGB[iColour] = DimColour(pDimPal->palRGB[iColour], brightness); + } + + if (!pPalQ->bFading) { + // Q the change to the video DAC + UpdateDACqueue(pPalQ->posInDAC + startColour, length, &pPalQ->palRGB[startColour]); } } +int TranslucentColour(void) { + return translucentIndex; +} + +int HighlightColour(void) { + static COLORREF cRef; + + cRef = (COLORREF)SysVar(SYS_HighlightRGB); + UpdateDACqueue(talkIndex, 1, &cRef); + + return talkIndex; +} + +int TalkColour(void) { + return TinselV2 ? talkIndex : TALKFONT_COL; +} + +void SetTalkColourRef(COLORREF colRef) { + talkColRef = colRef; +} + +COLORREF GetTalkColourRef(void) { + return talkColRef; +} + +void SetTagColorRef(COLORREF colRef) { + tagColRef = colRef; +} + +COLORREF GetTagColorRef(void) { + return tagColRef; +} + +void SetTranslucencyOffset(int offset) { + translucentIndex = offset; +} + +void SetTalkTextOffset(int offset) { + talkIndex = offset; +} + } // end of namespace Tinsel diff --git a/engines/tinsel/palette.h b/engines/tinsel/palette.h index fdc4826dbd..1d43e6d31f 100644 --- a/engines/tinsel/palette.h +++ b/engines/tinsel/palette.h @@ -43,7 +43,7 @@ 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 + NUM_PALETTES = 32, //!< number of palettes // Discworld has some fixed apportioned bits in the palette. BGND_DAC_INDEX = 0, //!< index of background colour in Video DAC @@ -84,8 +84,11 @@ struct PALQ { int objCount; //!< number of objects using this palette int posInDAC; //!< palette position in the video DAC int numColours; //!< number of colours in the palette + // Discworld 2 fields + bool bFading; // Whether or not fading + COLORREF palRGB[MAX_COLOURS]; // actual palette colours }; - +typedef PALQ *PPALQ; #define PALETTE_MOVED 0x8000 // when this bit is set in the "posInDAC" // field - the palette entry has moved @@ -137,8 +140,41 @@ COLORREF GetBgndColour(void); // returns current background colour void SetBgndColour( // sets current background colour COLORREF colour); // colour to set the background to +void FadingPalette(PPALQ pPalQ, bool bFading); + void CreateTranslucentPalette(SCNHANDLE BackPal); +void CreateGhostPalette(SCNHANDLE hPalette); + +void NoFadingPalettes(void); // All fading processes have just been killed + +void DimPartPalette( + SCNHANDLE hPal, + int startColour, + int length, + int brightness); // 0 = black, 10 == 100% + + +int TranslucentColour(void); + +#define BoxColour TranslucentColour + +int HighlightColour(void); + +int TalkColour(void); + +void SetTalkColourRef(COLORREF colRef); + +COLORREF GetTalkColourRef(void); + +void SetTagColorRef(COLORREF colRef); + +COLORREF GetTagColorRef(void); + +void SetTalkTextOffset(int offset); + +void SetTranslucencyOffset(int offset); + } // end of namespace Tinsel #endif // TINSEL_PALETTE_H diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp index 023417fe3c..815ce2cd2f 100644 --- a/engines/tinsel/pcode.cpp +++ b/engines/tinsel/pcode.cpp @@ -25,13 +25,16 @@ */ #include "tinsel/dw.h" +#include "tinsel/drives.h" #include "tinsel/events.h" // 'POINTED' etc. #include "tinsel/handle.h" // LockMem() -#include "tinsel/inventory.h" // for inventory id's +#include "tinsel/dialogs.h" // for inventory id's #include "tinsel/pcode.h" // opcodes etc. #include "tinsel/scn.h" // FindChunk() #include "tinsel/serializer.h" +#include "tinsel/timers.h" #include "tinsel/tinlib.h" // Library routines +#include "tinsel/tinsel.h" #include "common/util.h" @@ -97,7 +100,7 @@ enum OPCODE { #define OPMASK 0x3F //!< mask to isolate the opcode - +bool bNoPause = false; //----------------- LOCAL GLOBAL DATA -------------------- @@ -107,13 +110,19 @@ static int numGlobals = 0; // How many global variables to save/restore static INT_CONTEXT *icList = 0; +static uint32 hMasterScript; + /** * Keeps the code array pointer up to date. */ void LockCode(INT_CONTEXT *ic) { - if (ic->GSort == GS_MASTER) - ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE); - else + if (ic->GSort == GS_MASTER) { + if (TinselV2) + // Get the srcipt handle from a specific global chunk + ic->code = (byte *)LockMem(hMasterScript); + else + ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE); + } else ic->code = (byte *)LockMem(ic->hCode); } @@ -124,7 +133,7 @@ static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) { INT_CONTEXT *pic; int i; - for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) { if (pic->GSort == GS_NONE) { pic->pProc = g_scheduler->getCurrentProcess(); pic->GSort = gsort; @@ -141,11 +150,41 @@ static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) { error("Out of interpret contexts"); } +static void FreeWaitCheck(PINT_CONTEXT pic, bool bVoluntary) { + int i; + + // Is this waiting for something? + if (pic->waitNumber1) { + for (i = 0; i < NUM_INTERPRET; i++) { + if ((icList + i)->waitNumber2 == pic->waitNumber1) { + (icList + i)->waitNumber2 = 0; + break; + } + } + } + + // Is someone waiting for this? + if (pic->waitNumber2) { + for (i = 0; i < NUM_INTERPRET; i++) { + if ((icList + i)->waitNumber1 == pic->waitNumber2) { + (icList + i)->waitNumber1 = 0; + (icList + i)->resumeCode = bVoluntary ? RES_FINISHED : RES_CUTSHORT; + g_scheduler->reschedule((icList + i)->pProc); + break; + } + } + assert(i < NUM_INTERPRET); + } +} + /** * Normal release of an interpret context. * Called from the end of Interpret(). */ static void FreeInterpretContextPi(INT_CONTEXT *pic) { + FreeWaitCheck(pic, true); + if (TinselV2) + memset(pic, 0, sizeof(INT_CONTEXT)); pic->GSort = GS_NONE; } @@ -158,8 +197,11 @@ void FreeInterpretContextPr(PROCESS *pProc) { INT_CONTEXT *pic; int i; - for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) { if (pic->GSort != GS_NONE && pic->pProc == pProc) { + FreeWaitCheck(pic, false); + if (TinselV2) + memset(pic, 0, sizeof(INT_CONTEXT)); pic->GSort = GS_NONE; break; } @@ -173,8 +215,9 @@ void FreeMostInterpretContexts(void) { INT_CONTEXT *pic; int i; - for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { - if (pic->GSort != GS_MASTER) { + for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) { + if ((pic->GSort != GS_MASTER) && (pic->GSort != GS_GPROCESS)) { + memset(pic, 0, sizeof(INT_CONTEXT)); pic->GSort = GS_NONE; } } @@ -187,8 +230,9 @@ void FreeMasterInterpretContext(void) { INT_CONTEXT *pic; int i; - for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { - if (pic->GSort == GS_MASTER) { + for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) { + if ((pic->GSort == GS_MASTER) || (pic->GSort == GS_GPROCESS)) { + memset(pic, 0, sizeof(INT_CONTEXT)); pic->GSort = GS_NONE; return; } @@ -205,8 +249,8 @@ void FreeMasterInterpretContext(void) { * @param actorId Associated actor (if any) * @param pinvo Associated inventory object */ -INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event, - HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo) { +INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode, TINSEL_EVENT event, + HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo, int myEscape) { INT_CONTEXT *ic; ic = AllocateInterpretContext(gsort); @@ -215,14 +259,14 @@ INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event ic->hCode = hCode; LockCode(ic); ic->event = event; - ic->hpoly = hpoly; - ic->actorid = actorid; + ic->hPoly = hpoly; + ic->idActor = 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->escOn = myEscape > 0; + ic->myEscape = myEscape; ic->sp = 0; ic->bp = ic->sp + 1; ic->ip = 0; // start of code @@ -256,6 +300,9 @@ void RegisterGlobals(int num) { if (pGlobals == NULL) { numGlobals = num; + hMasterScript = !TinselV2 ? 0 : + READ_LE_UINT32(FindChunk(MASTER_SCNHANDLE, CHUNK_MASTER_SCRIPT)); + // Allocate RAM for pGlobals and make sure it's allocated pGlobals = (int32 *)calloc(numGlobals, sizeof(int32)); if (pGlobals == NULL) { @@ -263,18 +310,38 @@ void RegisterGlobals(int num) { } // Allocate RAM for interpret contexts and make sure it's allocated - icList = (INT_CONTEXT *)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT)); + icList = (INT_CONTEXT *)calloc(NUM_INTERPRET, sizeof(INT_CONTEXT)); if (icList == NULL) { error("Cannot allocate memory for interpret contexts"); } - g_scheduler->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)); + memset(icList, 0, NUM_INTERPRET * sizeof(INT_CONTEXT)); + } + + if (TinselV2) { + // read initial values + CdCD(nullContext); + + Common::File f; + if (!f.open(GLOBALS_FILENAME)) + error(CANNOT_FIND_FILE, GLOBALS_FILENAME); + + int32 length = f.readSint32LE(); + if (length != num) + error(FILE_IS_CORRUPT, GLOBALS_FILENAME); + + for (int i = 0; i < length; ++i) + pGlobals[i] = f.readSint32LE(); + + if (f.ioFailed()) + error(FILE_IS_CORRUPT, GLOBALS_FILENAME); + + f.close(); } } @@ -309,8 +376,8 @@ void INT_CONTEXT::syncWithSerializer(Serializer &s) { s.syncAsUint32LE(GSort); s.syncAsUint32LE(hCode); s.syncAsUint32LE(event); - s.syncAsSint32LE(hpoly); - s.syncAsSint32LE(actorid); + s.syncAsSint32LE(hPoly); + s.syncAsSint32LE(idActor); for (int i = 0; i < PCODE_STACK_SIZE; ++i) s.syncAsSint32LE(stack[i]); @@ -320,14 +387,14 @@ void INT_CONTEXT::syncWithSerializer(Serializer &s) { s.syncAsSint32LE(ip); s.syncAsUint32LE(bHalt); s.syncAsUint32LE(escOn); - s.syncAsSint32LE(myescEvent); + s.syncAsSint32LE(myEscape); } /** * Return pointer to and size of global data for save/restore game. */ void SaveInterpretContexts(INT_CONTEXT *sICInfo) { - memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT)); + memcpy(sICInfo, icList, NUM_INTERPRET * sizeof(INT_CONTEXT)); } /** @@ -451,6 +518,8 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { return; ic->sp += tmp2; LockCode(ic); + if (TinselV2 && (ic->resumeState == RES_1)) + ic->resumeState = RES_NOT; break; case OP_RET: // procedure return @@ -567,12 +636,14 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { break; case OP_ESCON: + bNoPause = true; ic->escOn = true; - ic->myescEvent = GetEscEvents(); + ic->myEscape = GetEscEvents(); break; case OP_ESCOFF: ic->escOn = false; + ic->myEscape = 0; break; default: @@ -590,4 +661,128 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { FreeInterpretContextPi(ic); } +/** + * Associates an interpret context with the + * process that will run it. + */ +void AttachInterpret(INT_CONTEXT *pic, PROCESS *pProc) { + // Attach the process which is using this context + pic->pProc = pProc; +} + +/** + * Generate a number that isn't being used. + */ +static uint32 UniqueWaitNumber(void) { + uint32 retval; + int i; + + for (retval = DwGetCurrentTime(); 1; retval--) { + if (retval == 0) + retval = (uint32)-1; + + for (i = 0; i < NUM_INTERPRET; i++) { + if ((icList+i)->waitNumber1 == retval + || (icList+i)->waitNumber2 == retval) + break; + } + + if (i == NUM_INTERPRET) + return retval; + } +} + +/** + * WaitInterpret + */ +void WaitInterpret(CORO_PARAM, PPROCESS pWaitProc, bool *result) { + int i; + PPROCESS currentProcess = g_scheduler->getCurrentProcess(); + assert(currentProcess); + assert(currentProcess != pWaitProc); + if (result) *result = false; + + /* + * Calling process is the waiter, find its interpret context. + */ + + CORO_BEGIN_CONTEXT; + PINT_CONTEXT picWaiter, picWaitee; + CORO_END_CONTEXT(_ctx); + + + CORO_BEGIN_CODE(_ctx); + + for (i = 0, _ctx->picWaiter = icList; i < NUM_INTERPRET; i++, _ctx->picWaiter++) { + if (_ctx->picWaiter->GSort != GS_NONE && _ctx->picWaiter->pProc == currentProcess) { + break; + } + } + + /* + * Find the interpret context of the process we're waiting for + */ + for (i = 0, _ctx->picWaitee = icList; i < NUM_INTERPRET; i++, _ctx->picWaitee++) { + if (_ctx->picWaitee->GSort != GS_NONE && _ctx->picWaitee->pProc == pWaitProc) { + break; + } + } + + /* + * Set the first as waiting for the second + */ + assert(_ctx->picWaitee->waitNumber2 == 0); + _ctx->picWaiter->waitNumber1 = _ctx->picWaitee->waitNumber2 = UniqueWaitNumber(); + _ctx->picWaiter->resumeCode = RES_WAITING; + + /* + * Wait for it + */ + CORO_GIVE_WAY; + while (_ctx->picWaiter->resumeCode == RES_WAITING) { + CORO_SLEEP(1); + } + + if (result) + *result = (_ctx->picWaiter->resumeCode == RES_FINISHED); + CORO_END_CODE; +} + +/** + * CheckOutWaiters + */ +void CheckOutWaiters(void) { + int i, j; + + // Check all waited for have someone waiting + for (i = 0; i < NUM_INTERPRET; i++) { + // If someone is supposedly waiting for this one + if ((icList + i)->GSort != GS_NONE && (icList + i)->waitNumber2) { + // Someone really must be waiting for this one + for (j = 0; j < NUM_INTERPRET; j++) { + if ((icList + j)->GSort != GS_NONE + && (icList + j)->waitNumber1 == (icList + i)->waitNumber2) { + break; + } + } + assert(j < NUM_INTERPRET); + } + } + + // Check waiting for someone to wait for + for (i = 0; i < NUM_INTERPRET; i++) { + // If someone is supposedly waiting for this one + if ((icList + i)->GSort != GS_NONE && (icList + i)->waitNumber1) { + // Someone really must be waiting for this one + for (j = 0; j < NUM_INTERPRET; j++) { + if ((icList + j)->GSort != GS_NONE + && (icList + j)->waitNumber2 == (icList + i)->waitNumber1) { + break; + } + } + assert(j < NUM_INTERPRET); + } + } +} + } // end of namespace Tinsel diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h index 1c7e0a942c..fef45d5e03 100644 --- a/engines/tinsel/pcode.h +++ b/engines/tinsel/pcode.h @@ -27,7 +27,7 @@ #ifndef TINSEL_PCODE_H // prevent multiple includes #define TINSEL_PCODE_H -#include "tinsel/events.h" // for USER_EVENT +#include "tinsel/events.h" // for TINSEL_EVENT #include "tinsel/sched.h" // for PROCESS namespace Tinsel { @@ -45,9 +45,12 @@ enum { }; enum GSORT { - GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE + GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE, + GS_PROCESS, GS_GPROCESS }; +enum RESCODE {RES_WAITING, RES_FINISHED, RES_CUTSHORT}; + struct INT_CONTEXT { // Elements for interpret context management @@ -57,9 +60,9 @@ struct INT_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) + TINSEL_EVENT event; //!< causal event + HPOLYGON hPoly; //!< associated polygon (if any) + int idActor; //!< associated actor (if any) INV_OBJECT *pinvo; //!< associated inventory object // Previously local variables in Interpret() @@ -69,27 +72,32 @@ struct INT_CONTEXT { int ip; //!< instruction pointer bool bHalt; //!< set to exit interpeter bool escOn; - int myescEvent; //!< only initialised to prevent compiler warning! + int myEscape; //!< only initialised to prevent compiler warning! + uint32 waitNumber1; // The waiting numbert + uint32 waitNumber2; // The wait for number + RESCODE resumeCode; 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 +// Interprets the PCODE instructions in the code array +void Interpret(CORO_PARAM, INT_CONTEXT *ic); INT_CONTEXT *InitInterpretContext( GSORT gsort, SCNHANDLE hCode, // code to execute - USER_EVENT event, // causal event + TINSEL_EVENT event, // causal event HPOLYGON hpoly, // associated polygon (if any) int actorid, // associated actor (if any) - INV_OBJECT *pinvo); // associated inventory object + INV_OBJECT *pinvo, + int myEscape = -1); // associated inventory object INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric); @@ -101,8 +109,12 @@ void SaveInterpretContexts(INT_CONTEXT *sICInfo); void RegisterGlobals(int num); void FreeGlobals(void); +void AttachInterpret(INT_CONTEXT *pic, PROCESS *pProc); -#define MAX_INTERPRET (NUM_PROCESS - 20) +void WaitInterpret(CORO_PARAM, PPROCESS pWaitProc, bool *result); + +#define NUM_INTERPRET (NUM_PROCESS - 20) +#define MAX_INTERPRET (MAX_PROCESSES - 20) /*----------------------------------------------------------------------*\ |* Library Procedure and Function codes parameter enums *| @@ -137,10 +149,6 @@ void FreeGlobals(void); #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() diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp index b5488da3e8..8cb6fcde0e 100644 --- a/engines/tinsel/pdisplay.cpp +++ b/engines/tinsel/pdisplay.cpp @@ -28,6 +28,7 @@ #include "tinsel/actors.h" #include "tinsel/background.h" +#include "tinsel/coroutine.h" #include "tinsel/cursor.h" #include "tinsel/dw.h" #include "tinsel/events.h" @@ -41,6 +42,7 @@ #include "tinsel/sched.h" #include "tinsel/strres.h" #include "tinsel/text.h" +#include "tinsel/tinsel.h" namespace Tinsel { @@ -56,8 +58,8 @@ extern int newestString; // The overrun counter, in STRRES.C //----------------- EXTERNAL FUNCTIONS --------------------- // in BG.C -extern int BackgroundWidth(void); -extern int BackgroundHeight(void); +extern int BgWidth(void); +extern int BgHeight(void); @@ -84,8 +86,11 @@ static bool bShowString = false; static int TaggedActor = 0; static HPOLYGON hTaggedPolygon = NOPOLY; -static enum { TAGS_OFF, TAGS_ON } TagsActive = TAGS_ON; +static bool bTagsActive = true; +static bool bPointingActive = true; + +static char tagBuffer[64]; #ifdef DEBUG /** @@ -134,7 +139,7 @@ void CursorPositionProcess(CORO_PARAM, const void *) { char PositionString[64]; // sprintf() things into here - PMACTOR pActor; // Lead actor + PMOVER pActor; // Lead actor while (1) { PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); @@ -159,7 +164,7 @@ void CursorPositionProcess(CORO_PARAM, const void *) { // New text objects sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset); _ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, - 0, CPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + 0, CPOSX, POSY, GetTagFontHandle(), TXT_CENTRE); if (DispPath) { HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH); if (hp == NOPOLY) @@ -171,7 +176,7 @@ void CursorPositionProcess(CORO_PARAM, const void *) { 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); + 0, 4, POSY+ 10, GetTagFontHandle(), 0); } // update previous position @@ -191,7 +196,7 @@ void CursorPositionProcess(CORO_PARAM, const void *) { sprintf(PositionString, "%d", Overrun); _ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, - 0, OPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + 0, OPOSX, POSY, GetTagFontHandle(), TXT_CENTRE); // update previous value _ctx->prevOver = Overrun; @@ -202,7 +207,7 @@ void CursorPositionProcess(CORO_PARAM, const void *) { | Lead actor's position. | \*----------------------*/ pActor = GetMover(LEAD_ACTOR); - if (pActor && pActor->MActorState == NORM_MACTOR) { + if (pActor && pActor->MActorState == NORM_MOVER) { // get lead's animation position GetActorPos(LEAD_ACTOR, &aniX, &aniY); @@ -217,7 +222,7 @@ void CursorPositionProcess(CORO_PARAM, const void *) { // create new text object list sprintf(PositionString, "%d %d", aniX, aniY); _ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, - 0, LPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + 0, LPOSX, POSY, GetTagFontHandle(), TXT_CENTRE); // update previous position _ctx->prevlX = aniX; @@ -236,7 +241,7 @@ void CursorPositionProcess(CORO_PARAM, const void *) { sprintf(PositionString, "String: %d", newestString); _ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, - 0, SPOSX, POSY+10, hTalkFontHandle(), TXT_CENTRE); + 0, SPOSX, POSY+10, GetTalkFontHandle(), TXT_CENTRE); // update previous value _ctx->prevString = newestString; @@ -252,9 +257,52 @@ void CursorPositionProcess(CORO_PARAM, const void *) { } #endif +/** + * While inventory/menu is open. + */ +void DisablePointing(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + int i; + HPOLYGON hPoly; // Polygon handle + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + bPointingActive = false; + + for (_ctx->i = 0; _ctx->i < MAX_POLY; _ctx->i++) { + _ctx->hPoly = GetPolyHandle(_ctx->i); + + if (_ctx->hPoly != NOPOLY && PolyType(_ctx->hPoly) == TAG && PolyIsPointedTo(_ctx->hPoly)) { + SetPolyPointedTo(_ctx->hPoly, false); + SetPolyTagWanted(_ctx->hPoly, false, false, 0); + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->hPoly, UNPOINT, 0, false, 0)); + } + } + + // For each tagged actor + for (_ctx->i = 0; (_ctx->i = NextTaggedActor(_ctx->i)) != 0; ) { + if (ActorIsPointedTo(_ctx->i)) { + SetActorPointedTo(_ctx->i, false); + SetActorTagWanted(_ctx->i, false, false, 0); + + CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->i, UNPOINT, false, 0)); + } + } + + CORO_END_CODE; +} + +/** + * EnablePointing() + */ +void EnablePointing(void) { + bPointingActive = true; +} + /** * 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(). + * (if one is). Tag process asks us for this information, as does ProcessUserEvent(). */ static void SaveTaggedActor(int ano) { TaggedActor = ano; @@ -262,7 +310,7 @@ static void SaveTaggedActor(int 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(). + * (if one is). Tag process asks us for this information, as does ProcessUserEvent(). */ int GetTaggedActor(void) { return TaggedActor; @@ -270,7 +318,7 @@ int GetTaggedActor(void) { /** * 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(). + * (if one is). Tag process asks us for this information, as does ProcessUserEvent(). */ static void SaveTaggedPoly(HPOLYGON hp) { hTaggedPolygon = hp; @@ -341,12 +389,59 @@ static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) { * the screen. */ static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) { - static int Loffset = 0, Toffset = 0; // Values when tag was displayed - int nLoff, nToff; // new values, to keep tag in place + static int tagX = 0, tagY = 0; // Values when tag was displayed + int newX, newY; // new values, to keep tag in place int ano; int xtext, ytext; bool newActor; + if (TinselV2) { + // Tinsel 2 version + // Get the foremost pointed to actor + int actor = FrontTaggedActor(); + + if (actor == 0) { + SaveTaggedActor(0); + return false; + } + + // If new actor + // or actor has suddenly decided it wants tagging... + if (actor != GetTaggedActor() || (ActorTagIsWanted(actor) && !*ppText)) { + // Put up actor tag + SaveTaggedActor(actor); // This actor tagged + SaveTaggedPoly(NOPOLY); // No tagged polygon + + if (*ppText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + + if (ActorTagIsWanted(actor)) { + GetActorTagPos(actor, &tagX, &tagY, false); + LoadStringRes(GetActorTagHandle(actor), tagBuffer, sizeof(tagBuffer)); + + // May have buggered cursor + EndCursorFollowed(); + *ppText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), tagBuffer, + 0, tagX, tagY, GetTagFontHandle(), TXT_CENTRE, 0); + assert(*ppText); + MultiSetZPosition(*ppText, Z_TAG_TEXT); + } else + *ppText = NULL; + } else if (*ppText) { + // Same actor, maintain tag position + GetActorTagPos(actor, &newX, &newY, false); + + if (newX != tagX || newY != tagY) { + MultiMoveRelXY(*ppText, newX - tagX, newY - tagY); + tagX = newX; + tagY = newY; + } + } + + return true; + } + + // Tinsel 1 version // For each actor with a tag.... FirstTaggedActor(); while ((ano = NextTaggedActor()) != 0) { @@ -369,20 +464,20 @@ static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) { 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); + PlayfieldGetPos(FIELD_WORLD, &tagX, &tagY); + LoadStringRes(GetActorTag(ano), TextBufferAddr(), TBUFSZ); + *ppText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), + 0, xtext - tagX, ytext - tagY, GetTagFontHandle(), 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; + PlayfieldGetPos(FIELD_WORLD, &newX, &newY); + if (newX != tagX || newY != tagY) { + MultiMoveRelXY(*ppText, tagX - newX, tagY - newY); + tagX = newX; + tagY = newY; } } return true; @@ -406,6 +501,7 @@ static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) { */ static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) { static int Loffset = 0, Toffset = 0; // Values when tag was displayed + static int curX = 0, curY = 0; int nLoff, nToff; // new values, to keep tag in place HPOLYGON hp; bool newPoly; @@ -418,8 +514,11 @@ static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) { for (int i = 0; i < MAX_POLY; i++) { hp = GetPolyHandle(i); + if (TinselV2 && (hp == NOPOLY)) + continue; + // Added code for un-tagged tags - if (hp != NOPOLY && PolyPointState(hp) == POINTING && PolyTagState(hp) != TAG_ON) { + if ((hp != NOPOLY) && (PolyPointState(hp) == PS_POINTING) && (PolyTagState(hp) != TAG_ON)) { // This poly is entitled to be tagged if (hp != GetTaggedPoly()) { if (*ppText) { @@ -431,58 +530,99 @@ static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) { SaveTaggedPoly(hp); // This polygon tagged } return true; - } else if (hp != NOPOLY && PolyTagState(hp) == TAG_ON) { + } else if ((TinselV2 && PolyTagIsWanted(hp)) || + (!TinselV2 && hp != NOPOLY && PolyTagState(hp) == TAG_ON)) { // Put up or maintain polygon tag - if (*pTag != POLY_HOTSPOT_TAG) - newPoly = true; // A new polygon (no current) - else if (hp != GetTaggedPoly()) - newPoly = true; // Different polygon - else - newPoly = false; // Same polygon + newPoly = false; + if (TinselV2) { + if (hp != GetTaggedPoly()) + newPoly = true; // Different polygon + } else { + if (*pTag != POLY_HOTSPOT_TAG) + newPoly = true; // A new polygon (no current) + else if (hp != GetTaggedPoly()) + newPoly = true; // Different polygon + } if (newPoly) { if (*ppText) MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); - *pTag = POLY_HOTSPOT_TAG; + if (!TinselV2) + *pTag = POLY_HOTSPOT_TAG; SaveTaggedActor(0); // No tagged actor SaveTaggedPoly(hp); // This polygon tagged PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); - getPolyTagInfo(hp, &hTagtext, &tagx, &tagy); + GetTagTag(hp, &hTagtext, &tagx, &tagy); int strLen; - if (PolyTagHandle(hp) != 0) - strLen = LoadStringRes(PolyTagHandle(hp), tBufferAddr(), TBUFSZ); + if (GetPolyTagHandle(hp) != 0) + strLen = LoadStringRes(GetPolyTagHandle(hp), TextBufferAddr(), TBUFSZ); else - strLen = LoadStringRes(hTagtext, tBufferAddr(), TBUFSZ); + strLen = LoadStringRes(hTagtext, TextBufferAddr(), TBUFSZ); if (strLen == 0) // No valid string returned, so leave ppText as NULL ppText = NULL; - else { + else if (TinselV2 && !PolyTagFollowsCursor(hp)) { + // May have buggered cursor + EndCursorFollowed(); + + *ppText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, tagx - Loffset, tagy - Toffset, + GetTagFontHandle(), TXT_CENTRE, 0); + } else if (TinselV2) { + // Bugger cursor + const char *tagPtr = TextBufferAddr(); + if (tagPtr[0] < ' ' && tagPtr[1] == EOS_CHAR) + StartCursorFollowed(); + + GetCursorXYNoWait(&curX, &curY, false); + *ppText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), + 0, curX, curY, GetTagFontHandle(), TXT_CENTRE, 0); + } else { // Handle displaying the tag text on-screen - *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + *ppText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0, tagx - Loffset, tagy - Toffset, - hTagFontHandle(), TXT_CENTRE); + GetTagFontHandle(), TXT_CENTRE); assert(*ppText); // Polygon tag string produced NULL text + } + + // DW1 has some tags without text, e.g. the "equals" button when talking to the guard in act 3 + if (ppText) { 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); + if (shift >= BgWidth()) // Not off right + MultiMoveRelXY(*ppText, BgWidth() - 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); + if (shift > BgHeight()) // Not off bottom + MultiMoveRelXY(*ppText, 0, BgHeight() - shift); } - } else { + } else if (TinselV2 && (*ppText)) { + if (!PolyTagFollowsCursor(hp)) { + PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); + if (nLoff != Loffset || nToff != Toffset) { + MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); + Loffset = nLoff; + Toffset = nToff; + } + } else { + GetCursorXY(&tagx, &tagy, false); + if (tagx != curX || tagy != curY) { + MultiMoveRelXY(*ppText, tagx - curX, tagy - curY); + curX = tagx; + curY = tagy; + } + } + } else if (!TinselV2) { PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); if (nLoff != Loffset || nToff != Toffset) { MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); @@ -495,7 +635,9 @@ static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) { } // No tagged polygon - if (*pTag == POLY_HOTSPOT_TAG) { + if (TinselV2) + SaveTaggedPoly(NOPOLY); + else if (*pTag == POLY_HOTSPOT_TAG) { *pTag = NO_HOTSPOT_TAG; SaveTaggedPoly(NOPOLY); } @@ -522,7 +664,7 @@ void TagProcess(CORO_PARAM, const void *) { SaveTaggedPoly(NOPOLY); // No tagged polygon yet while (1) { - if (TagsActive == TAGS_ON) { + if (bTagsActive) { int curX, curY; // cursor position while (!GetCursorXYNoWait(&curX, &curY, true)) CORO_SLEEP(1); @@ -533,6 +675,10 @@ void TagProcess(CORO_PARAM, const void *) { if (_ctx->pText) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); _ctx->pText = NULL; + + if (TinselV2) + // May have buggered cursor + EndCursorFollowed(); } } } else { @@ -557,22 +703,43 @@ void TagProcess(CORO_PARAM, const void *) { /** * Called from PointProcess() as appropriate. */ -static void enteringpoly(HPOLYGON hp) { - SetPolyPointState(hp, POINTING); +static void enteringpoly(CORO_PARAM, HPOLYGON hp) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + SetPolyPointState(hp, PS_POINTING); - RunPolyTinselCode(hp, POINTED, BE_NONE, false); + if (TinselV2) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, hp, POINTED, 0, false, 0)); + else + RunPolyTinselCode(hp, POINTED, PLR_NOEVENT, false); + + CORO_END_CODE; } /** * Called from PointProcess() as appropriate. */ -static void leavingpoly(HPOLYGON hp) { - SetPolyPointState(hp, NOT_POINTING); +static void leavingpoly(CORO_PARAM, HPOLYGON hp) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + SetPolyPointState(hp, PS_NOT_POINTING); + + if (TinselV2) { + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, hp, UNPOINT, 0, false, 0)); + SetPolyTagWanted(hp, false, false, 0); - if (PolyTagState(hp) == TAG_ON) { + } else if (PolyTagState(hp) == TAG_ON) { // Delete this tag entry SetPolyTagState(hp, TAG_OFF); } + + CORO_END_CODE; } /** @@ -583,56 +750,95 @@ static void leavingpoly(HPOLYGON hp) { void PointProcess(CORO_PARAM, const void *) { // COROUTINE CORO_BEGIN_CONTEXT; + HPOLYGON hPoly; + int i; + int curX, curY; // cursor/tagged actor position CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); + if (TinselV2) + EnablePointing(); + while (1) { - int aniX, aniY; // cursor/tagged actor position - while (!GetCursorXYNoWait(&aniX, &aniY, true)) + while (!GetCursorXYNoWait(&_ctx->curX, &_ctx->curY, 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); + for (_ctx->i = 0; _ctx->i < MAX_POLY; _ctx->i++) { + _ctx->hPoly = GetPolyHandle(_ctx->i); + if ((_ctx->hPoly == NOPOLY) || ((PolyType(_ctx->hPoly) != TAG) && + (PolyType(_ctx->hPoly) != EXIT))) + continue; + + if (!PolyIsPointedTo(_ctx->hPoly)) { + if (IsInPolygon(_ctx->curX, _ctx->curY, _ctx->hPoly)) { + if (TinselV2) { + SetPolyPointedTo(_ctx->hPoly, true); + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->hPoly, POINTED, 0, false, 0)); + } else { + CORO_INVOKE_1(enteringpoly, _ctx->hPoly); } - } else if (PolyPointState(hp) == POINTING) { - if (!IsInPolygon(aniX, aniY, hp)) { - leavingpoly(hp); + } + } else { + if (!IsInPolygon(_ctx->curX, _ctx->curY, _ctx->hPoly)) { + if (TinselV2) { + SetPolyPointedTo(_ctx->hPoly, false); + SetPolyTagWanted(_ctx->hPoly, false, false, 0); + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->hPoly, UNPOINT, 0, false, 0)); + } else { + CORO_INVOKE_1(leavingpoly, _ctx->hPoly); } } } } - // allow re-scheduling - CORO_SLEEP(1); + if (TinselV2) { + // For each tagged actor + for (_ctx->i = 0; (_ctx->i = NextTaggedActor(_ctx->i)) != 0; ) { + if (!ActorIsPointedTo(_ctx->i)) { + if (InHotSpot(_ctx->i, _ctx->curX, _ctx->curY)) { + SetActorPointedTo(_ctx->i, true); + CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->i, POINTED, false, 0)); + } + } else { + if (!InHotSpot(_ctx->i, _ctx->curX, _ctx->curY)) { + SetActorPointedTo(_ctx->i, false); + SetActorTagWanted(_ctx->i, false, false, 0); + CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->i, UNPOINT, false, 0)); + } + } + } + + // allow re-scheduling + do { + CORO_SLEEP(1); + } while (!bPointingActive); + } else { + // allow re-scheduling + CORO_SLEEP(1); + } } CORO_END_CODE; } void DisableTags(void) { - TagsActive = TAGS_OFF; + bTagsActive = false; } void EnableTags(void) { - TagsActive = TAGS_ON; + bTagsActive = true; } bool DisableTagsIfEnabled(void) { - if (TagsActive == TAGS_OFF) - return false; - else { - TagsActive = TAGS_OFF; + if (bTagsActive) { + DisableTags(); return true; - } + } else + return false; } /** diff --git a/engines/tinsel/pdisplay.h b/engines/tinsel/pdisplay.h new file mode 100644 index 0000000000..7a67f3541c --- /dev/null +++ b/engines/tinsel/pdisplay.h @@ -0,0 +1,37 @@ +/* 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$ + * + * Tag related methods + */ + +#ifndef TINSEL_PDISPLAY_H // prevent multiple includes +#define TINSEL_PDISPLAY_H + +namespace Tinsel { + +void EnableTags(void); +void DisableTags(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_PDISPLAY_H */ diff --git a/engines/tinsel/pid.h b/engines/tinsel/pid.h index c2af1a5fcb..c7872d6432 100644 --- a/engines/tinsel/pid.h +++ b/engines/tinsel/pid.h @@ -61,12 +61,20 @@ namespace Tinsel { #define PID_MASTER_SCR 0x00C0 // tinsel master script process -#define PID_MACTOR (0x00D0 | PID_DESTROY) // moving actor process +#define PID_MOVER (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 +#define PID_BMV 0x0100 // Movie player process + +#define PID_BTN_CLICK 0x110 // process to handle mouse button clicks + +#define PID_PROCESS (0x0110 | PID_DESTROY) // Scene process base + +#define PID_GPROCESS 0x0120 // Global process base + } // end of namespace Tinsel #endif // TINSEL_PID_H diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp index e32fc88d3d..27b2ea1bf5 100644 --- a/engines/tinsel/play.cpp +++ b/engines/tinsel/play.cpp @@ -26,20 +26,50 @@ #include "tinsel/actors.h" #include "tinsel/background.h" +#include "tinsel/coroutine.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/play.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/sched.h" +#include "tinsel/scn.h" +#include "tinsel/sound.h" #include "tinsel/timers.h" -#include "tinsel/tinlib.h" // stand() +#include "tinsel/tinlib.h" // Stand() namespace Tinsel { +struct PPINIT { + SCNHANDLE hFilm; // The 'film' + int16 x; // } Co-ordinates from the play() + int16 y; // } - set to (-1, -1) if none. + int16 z; // normally 0, set if from restore + int16 speed; // Film speed + int16 actorid; // Set if called from an actor code block + bool splay; // Set if called from splay() + bool bTop; // Set if called from topplay() + bool bRestore; + int16 sf; // SlowFactor - only used for moving actors + int16 column; // Column number, first column = 0 + + uint8 escOn; + int32 myescEvent; +}; + +//----------------- LOCAL GLOBAL DATA -------------------- + +static SOUNDREELS soundReels[MAX_SOUNDREELS]; +static int soundReelNumbers[MAX_SOUNDREELS]; + +static int soundReelWait; + +//-------------------- METHODS ---------------------- + /** * Poke the background palette into an image. */ @@ -54,13 +84,30 @@ static void PokeInPalette(SCNHANDLE hMulFrame) { // get pointer to image pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image - pim->hImgPal = TO_LE_32(BackPal()); + pim->hImgPal = TO_LE_32(BgPal()); } } +/** + * Poke the background palette into an image. + */ +void PokeInPalette(const MULTI_INIT *pmi) { + FRAME *pFrame; // Pointer to frame + IMAGE *pim; // Pointer to image + + // Could be an empty column + if (pmi->hMulFrame) { + pFrame = (FRAME *)LockMem(pmi->hMulFrame); + + // get pointer to image + pim = (IMAGE *)LockMem(*pFrame); // handle to image + + pim->hImgPal = BgPal(); + } +} int32 NoNameFunc(int actorID, bool bNewMover) { - PMACTOR pActor; + PMOVER pActor; int32 retval; pActor = GetMover(actorID); @@ -68,9 +115,9 @@ int32 NoNameFunc(int actorID, bool bNewMover) { if (pActor != NULL && !bNewMover) { // If no path, just use first path in the scene if (pActor->hCpath == NOPOLY) - retval = getPolyZfactor(FirstPathPoly()); + retval = GetPolyZfactor(FirstPathPoly()); else - retval = getPolyZfactor(pActor->hCpath); + retval = GetPolyZfactor(pActor->hCpath); } else { switch (actorMaskType(actorID)) { case ACT_DEFAULT: @@ -91,22 +138,272 @@ int32 NoNameFunc(int actorID, bool bNewMover) { return retval; } -struct PPINIT { - SCNHANDLE hFilm; // The 'film' - int16 x; // } Co-ordinates from the play() - int16 y; // } - set to (-1, -1) if none. - int16 z; // normally 0, set if from restore - int16 speed; // Film speed - int16 actorid; // Set if called from an actor code block - uint8 splay; // Set if called from splay() - uint8 bTop; // Set if called from topplay() - int16 sf; // SlowFactor - only used for moving actors - int16 column; // Column number, first column = 0 +static FREEL *GetReel(SCNHANDLE hFilm, int column) { + FILM *pFilm = (FILM *)LockMem(hFilm); - uint8 escOn; - int32 myescEvent; -}; + return &pFilm->reels[column]; +} + +static int RegisterSoundReel(SCNHANDLE hFilm, int column, int actorCol) { + int i; + + for (i = 0; i < MAX_SOUNDREELS; i++) { + // Should assert this doesn't happen, but let's be tolerant + if (soundReels[i].hFilm == hFilm && soundReels[i].column == column) + break; + if (!soundReels[i].hFilm) + { + soundReels[i].hFilm = hFilm; + soundReels[i].column = column; + soundReels[i].actorCol = actorCol; + break; + } + } + + soundReelNumbers[i]++; + return i; +} + +void NoSoundReels(void) { + memset(soundReels, 0, sizeof(soundReels)); + soundReelWait = 0; +} + +static void DeRegisterSoundReel(SCNHANDLE hFilm, int column) { + for (int i = 0; i < MAX_SOUNDREELS; i++) { + // Should assert this doesn't happen, but let's be tolerant + if (soundReels[i].hFilm == hFilm && soundReels[i].column == column) { + soundReels[i].hFilm = 0; + break; + } + } +} + +void SaveSoundReels(PSOUNDREELS psr) { + for (int i = 0; i < MAX_SOUNDREELS; i++) + { + if (IsCdPlayHandle(soundReels[i].hFilm)) + soundReels[i].hFilm = 0; + } + + memcpy(psr, soundReels, sizeof(soundReels)); +} + +void RestoreSoundReels(PSOUNDREELS psr) { + memcpy(soundReels, psr, sizeof(soundReels)); +} + +static uint32 GetZfactor(int actorID, PMOVER pMover, bool bNewMover) { + if (pMover != NULL && bNewMover == false) { + // If no path, just use first path in the scene + if (pMover->hCpath == NOPOLY) + return GetPolyZfactor(FirstPathPoly()); + else + return GetPolyZfactor(pMover->hCpath); + } else { + return GetActorZfactor(actorID); + } +} + +/** + * Handles reels with sound id. + * @param hFilm The 'film' + * @param column Column number, first column = 0 + * @param speed Film speed + */ +static void SoundReel(CORO_PARAM, SCNHANDLE hFilm, int column, int speed, + int myescEvent, int actorCol) { + FILM *pFilm; + FREEL *pReel; + ANI_SCRIPT *pAni; + + short x, y; + + CORO_BEGIN_CONTEXT; + int myId; + int myNum; + int frameNumber; + int speed; + int sampleNumber; + bool bFinished; + bool bLooped; + int reelActor; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (actorCol) { + PMULTI_INIT pmi; // MULTI_INIT structure + + pReel = GetReel(hFilm, actorCol - 1); + pmi = (PMULTI_INIT) LockMem(pReel->mobj); + _ctx->reelActor = pmi->mulID; + } else + _ctx->reelActor = 0; + + _ctx->frameNumber = 0; + _ctx->speed = speed; + _ctx->sampleNumber = 0; + _ctx->bFinished = false; + _ctx->bLooped = false; + _ctx->myId = RegisterSoundReel(hFilm, column, actorCol); + _ctx->myNum = soundReelNumbers[_ctx->myId]; + + do { + pFilm = (FILM *)LockMem(hFilm); + pReel = &pFilm->reels[column]; + + pAni = (ANI_SCRIPT *)LockMem(pReel->script); + + if (_ctx->speed == -1) { + _ctx->speed = (ONE_SECOND/pFilm->frate); + + // Restored reel + for (;;) { + if (pAni[_ctx->frameNumber].op == ANI_END) + break; + else if (pAni[_ctx->frameNumber].op == ANI_JUMP) { + _ctx->frameNumber++; + _ctx->frameNumber += pAni[_ctx->frameNumber].op; + break; + } + // Could check for the other stuff here + // but they really dont happen + // OH YES THEY DO + else if (pAni[_ctx->frameNumber].op == ANI_ADJUSTX + || pAni[_ctx->frameNumber].op == ANI_ADJUSTY) { + _ctx->frameNumber += 2; + } else if (pAni[_ctx->frameNumber].op == ANI_ADJUSTXY) { + _ctx->frameNumber += 3; + } else { + // ANI_STOP, ANI_HIDE, ANI_HFLIP, + // ANI_VFLIP, ANI_HVFLIP, default + _ctx->frameNumber++; + } + } + } + + switch (pAni[_ctx->frameNumber].op) { + case ANI_END: + // Stop this sample if repeating + if (_ctx->sampleNumber && _ctx->bLooped) + _vm->_sound->stopSpecSample(_ctx->sampleNumber, 0); + _ctx->bFinished = true; + break; + + case ANI_JUMP: + _ctx->frameNumber++; + + assert(pAni[_ctx->frameNumber].op < 0); + + _ctx->frameNumber += pAni[_ctx->frameNumber].op; + + assert(_ctx->frameNumber >= 0); + continue; + + case ANI_STOP: + // Stop this sample + if (_ctx->sampleNumber) + _vm->_sound->stopSpecSample(_ctx->sampleNumber, 0); + break; + + case ANI_HIDE: + // No op + break; + + case ANI_HFLIP: + case ANI_VFLIP: + case ANI_HVFLIP: + _ctx->frameNumber++; + continue; + + case ANI_ADJUSTX: + case ANI_ADJUSTY: + _ctx->frameNumber += 2; + continue; + + case ANI_ADJUSTXY: + _ctx->frameNumber += 3; + continue; + + default: + // Stop this sample + if (_ctx->sampleNumber) + _vm->_sound->stopSpecSample(_ctx->sampleNumber, 0); + + _ctx->sampleNumber = pAni[_ctx->frameNumber++].op; + if (_ctx->sampleNumber > 0) + _ctx->bLooped = false; + else { + _ctx->sampleNumber = ~_ctx->sampleNumber; + _ctx->bLooped = true; + } + x = (short)(pAni[_ctx->frameNumber].op >> 16); + y = (short)(pAni[_ctx->frameNumber].op & 0xffff); + + if (x == 0) + x = -1; + + _vm->_sound->playSample(_ctx->sampleNumber, 0, _ctx->bLooped, x, y, PRIORITY_SCRIPT, + Audio::Mixer::kSFXSoundType); + + break; + } + + CORO_SLEEP(_ctx->speed); + _ctx->frameNumber++; + + if (_ctx->reelActor && GetActorPresFilm(_ctx->reelActor) != hFilm) { + // Stop this sample if repeating + if (_ctx->sampleNumber && _ctx->bLooped) + _vm->_sound->stopSpecSample(_ctx->sampleNumber, 0); + + _ctx->bFinished = true; + } + + if (myescEvent && myescEvent != GetEscEvents()) { + // Stop this sample + if (_ctx->sampleNumber) + _vm->_sound->stopSpecSample(_ctx->sampleNumber, 0); + + _ctx->bFinished = true; + } + } while (!_ctx->bFinished && _ctx->myNum == soundReelNumbers[_ctx->myId]); + + // De-register - if not been replaced + if (_ctx->myNum == soundReelNumbers[_ctx->myId]) + DeRegisterSoundReel(hFilm, column); + + CORO_END_CODE; +} + +static void ResSoundReel(CORO_PARAM, const void *) { + // get the stuff copied to process when it was created + PPROCESS pProc = g_scheduler->getCurrentProcess(); + int i = *(int *)pProc->param; + + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_ARGS(SoundReel, (CORO_SUBCTX, soundReels[i].hFilm, soundReels[i].column, + -1, 0, soundReels[i].actorCol)); + + CORO_KILL_SELF(); + CORO_END_CODE; +} + +static void SoundReelWaitCheck(void) { + if (--soundReelWait == 0) { + for (int i = 0; i < MAX_SOUNDREELS; i++) { + if (soundReels[i].hFilm) { + g_scheduler->createProcess(PID_REEL, ResSoundReel, &i, sizeof(i)); + } + } + } +} /** * - Don't bother if this reel is already playing for this actor. @@ -121,7 +418,7 @@ struct PPINIT { * - 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) { +static void t1PlayReel(CORO_PARAM, const PPINIT *ppi) { CORO_BEGIN_CONTEXT; OBJECT *pPlayObj; // Object ANIM thisAnim; // Animation structure @@ -134,6 +431,8 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { int stepCount; int frameCount; int reelActor; + PMOVER pActor; + int tmpX, tmpY; CORO_END_CONTEXT(_ctx); static int firstColZ = 0; // Z-position of column zero @@ -142,14 +441,13 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { 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; + _ctx->pActor = NULL; bNewMover = false; pfilm = (const FILM *)LockMem(ppi->hFilm); @@ -166,26 +464,26 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { return; /**** Delete a bit down there if this stays ****/ - updateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent); + 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)) { + 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 + if (ActorIsTalking(_ctx->reelActor)) + SetActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk return; } - if (isActorTalking(_ctx->reelActor)) { + if (ActorIsTalking(_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 + if (ppi->hFilm != GetActorTalkFilm(_ctx->reelActor)) { + SetActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk return; } } else { - setActorPlayFilm(_ctx->reelActor, ppi->hFilm); + SetActorPlayFilm(_ctx->reelActor, ppi->hFilm); } // If this reel is already playing for this actor, just forget it. @@ -206,34 +504,33 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { // 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; + _ctx->tmpX = ppi->x; + _ctx->tmpY = ppi->y; if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) { - } else if (tmpX != -1 || tmpY != -1) { - MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); + } else if (_ctx->tmpX != -1 || _ctx->tmpY != -1) { + MultiSetAniXY(_ctx->pPlayObj, _ctx->tmpX, _ctx->tmpY); } else if (!pmi->mulX && !pmi->mulY) { - GetActorPos(_ctx->reelActor, &tmpX, &tmpY); - MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); + GetActorPos(_ctx->reelActor, &_ctx->tmpX, &_ctx->tmpY); + MultiSetAniXY(_ctx->pPlayObj, _ctx->tmpX, _ctx->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's a moving actor, get its MOVER 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. + // 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); + _ctx->pActor = GetMover(_ctx->reelActor); + if (!getMActorState(_ctx->pActor)) { + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, _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); + storeActorReel(_ctx->reelActor, _ctx->pfreel, ppi->hFilm, _ctx->pPlayObj, ppi->column, _ctx->tmpX, _ctx->tmpY); /**** Will get rid of this if the above is kept ****/ // We may be temporarily resuscitating a dead actor @@ -247,23 +544,23 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { // 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); + 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); + StoreActorZpos(_ctx->reelActor, firstColZ); } else { MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column); - storeActorZpos(_ctx->reelActor, 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()); + if (_ctx->pActor->hCpath == NOPOLY) + fColZfactor = GetPolyZfactor(FirstPathPoly()); else - fColZfactor = getPolyZfactor(pActor->hCpath); + fColZfactor = GetPolyZfactor(_ctx->pActor->hCpath); firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor); } else { switch (actorMaskType(_ctx->reelActor)) { @@ -293,14 +590,14 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { break; } } - storeActorZpos(_ctx->reelActor, firstColZ); + 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); + StoreActorZpos(_ctx->reelActor, firstColZ + ppi->column); } /* @@ -313,7 +610,7 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { do { if (_ctx->stepCount++ == 0) { _ctx->frameCount++; - storeActorSteps(_ctx->reelActor, _ctx->frameCount); + StoreActorSteps(_ctx->reelActor, _ctx->frameCount); } if (_ctx->stepCount == ppi->speed) _ctx->stepCount = 0; @@ -323,7 +620,7 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { int x, y; GetAniPosition(_ctx->pPlayObj, &x, &y); - storeActorPos(_ctx->reelActor, x, y); + StoreActorPos(_ctx->reelActor, x, y); CORO_SLEEP(1); @@ -332,7 +629,7 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { break; } - if (actorEsc(_ctx->reelActor) && actorEev(_ctx->reelActor) != GetEscEvents()) + if (ActorEsc(_ctx->reelActor) && ActorEev(_ctx->reelActor) != GetEscEvents()) break; } while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor)); @@ -358,27 +655,309 @@ void PlayReel(CORO_PARAM, const PPINIT *ppi) { CORO_END_CODE; } +/** + * - 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 + * 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. + * @param x Co-ordinates from the play(), set to (-1, -1) if none + * @param y Co-ordinates from the play(), set to (-1, -1) if none + * @param bRestore Normally False, set if from restore + * @param speed Film speed + * @param hFilm The 'film' + * @param column Column number, first column = 0 + */ +static void t2PlayReel(CORO_PARAM, int x, int y, bool bRestore, int speed, SCNHANDLE hFilm, + int column, int myescEvent, bool bTop) { + CORO_BEGIN_CONTEXT; + bool bReplaced; + bool bGotHidden; + int stepCount; + int frameCount; + bool bEscapedAlready; + bool bPrinciple; // true if this is the first column in a film for one actor + bool bRelative; // true if relative specified in script + + FREEL *pFreel; + MULTI_INIT *pmi; // MULTI_INIT structure + POBJECT pPlayObj; // Object + ANIM thisAnim; // Animation structure + + int reelActor; // Which actor this reel belongs to + PMOVER pMover; // set if it's a moving actor + bool bNewMover; // Gets set if a moving actor that isn't in scene yet + + int filmNumber; + int myZ; // Remember for hide/unhide + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->bReplaced = false; + _ctx->bGotHidden = false; + _ctx->stepCount = 0; + _ctx->frameCount = 0; + + _ctx->bEscapedAlready = false; + + // Get the reel and MULTI_INIT structure + _ctx->pFreel = GetReel(hFilm, column); + _ctx->pmi = (MULTI_INIT *)LockMem(_ctx->pFreel->mobj); + + if (_ctx->pmi->mulID == -2) { + CORO_INVOKE_ARGS(SoundReel, (CORO_SUBCTX, hFilm, column, speed, myescEvent, + _ctx->pmi->otherFlags & OTH_RELATEDACTOR)); + return; + } + + // Save actor's ID + _ctx->reelActor = _ctx->pmi->mulID; + + UpdateActorEsc(_ctx->reelActor, myescEvent); + + // To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios + if (hFilm != GetActorLatestFilm(_ctx->reelActor)) { + // This in not the last film scheduled for this actor + + // It may be the last non-talk film though + if (ActorIsTalking(_ctx->reelActor)) + SetActorPlayFilm(_ctx->reelActor, hFilm); // Revert to this film after talk + + return; + } + if (ActorIsTalking(_ctx->reelActor)) { + // Note: will delete this and there'll be no need to store the talk film! + if (hFilm != GetActorTalkFilm(_ctx->reelActor)) { + SetActorPlayFilm(_ctx->reelActor, hFilm); // Revert to this film after talk + return; + } + } else { + SetActorPlayFilm(_ctx->reelActor, hFilm); + } + + // Register the film for this actor + if (hFilm != GetActorPresFilm(_ctx->reelActor)) { + _ctx->bPrinciple = true; + StoreActorPresFilm(_ctx->reelActor, hFilm, x, y); + } else { + _ctx->bPrinciple = false; + + // If this reel is already playing for this actor, just forget it. + if (ActorReelPlaying(_ctx->reelActor, column)) + return; + } + + /* + * Insert the object + */ + // Poke in the background palette + PokeInPalette(_ctx->pmi); + + // Set ghost bit if wanted + if (ActorIsGhost(_ctx->reelActor)) { + assert(_ctx->pmi->mulFlags == DMA_WNZ || _ctx->pmi->mulFlags == (DMA_WNZ | DMA_GHOST)); + _ctx->pmi->mulFlags |= DMA_GHOST; + } + + // Set up and insert the multi-object + _ctx->pPlayObj = MultiInitObject(_ctx->pmi); + if (!bTop) + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); + else + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + + /* + * More action for moving actors + */ + _ctx->pMover = GetMover(_ctx->reelActor); + if (_ctx->pMover != NULL) { + HideMover(_ctx->pMover); + + if (!MoverIs(_ctx->pMover)) { + // Used to do a Stand here to prevent glitches + + _ctx->bNewMover = true; + } else + _ctx->bNewMover = false; + } + + // Register the reel for this actor + StoreActorReel(_ctx->reelActor, column, _ctx->pPlayObj); + + _ctx->filmNumber = GetActorFilmNumber(_ctx->reelActor); + + /* + * Sort out x and y + */ + assert( ((_ctx->pmi->otherFlags & OTH_RELATIVE) && !(_ctx->pmi->otherFlags & OTH_ABSOLUTE)) + || ((_ctx->pmi->otherFlags & OTH_ABSOLUTE) && !(_ctx->pmi->otherFlags & OTH_RELATIVE)) ); + + _ctx->bRelative = _ctx->pmi->otherFlags & OTH_RELATIVE; + + if (_ctx->bRelative) { + // Use actor's position. If (x, y) specified, move the actor. + if (x == -1 && y == -1) + GetActorPos(_ctx->reelActor, &x, &y); + else + StoreActorPos(_ctx->reelActor, x, y); + } else if (x == -1 && y == -1) + x = y = 0; // Use (0,0) if no specified + + // Add embedded co-ords + MultiSetAniXY(_ctx->pPlayObj, x + _ctx->pmi->mulX, y + _ctx->pmi->mulY); + + /* + * Sort out z + */ + if (bRestore) { + _ctx->myZ = GetActorZpos(_ctx->reelActor, column); + + SoundReelWaitCheck(); + } else { + static int baseZposn; // Z-position of column zero + static uint32 baseZfact; // Z-factor of column zero's actor + + // N.B. It HAS been ensured that the first column gets here first + + if (_ctx->pmi->mulZ != -1) { + // Z override in script + + baseZfact = _ctx->pmi->mulZ; + baseZposn = (baseZfact << ZSHIFT) + MultiLowest(_ctx->pPlayObj); + if (bTop) + baseZposn += Z_TOPPLAY; + } else if (column == 0 + || GetZfactor(_ctx->reelActor, _ctx->pMover, _ctx->bNewMover) > baseZfact) { + // Subsequent columns are based on this one + + baseZfact = GetZfactor(_ctx->reelActor, _ctx->pMover, _ctx->bNewMover); + baseZposn = (baseZfact << ZSHIFT) + MultiLowest(_ctx->pPlayObj); + if (bTop) + baseZposn += Z_TOPPLAY; + } + _ctx->myZ = baseZposn + column; + } + MultiSetZPosition(_ctx->pPlayObj, _ctx->myZ); + StoreActorZpos(_ctx->reelActor, _ctx->myZ, column); + + /* + * Play until the script finishes, + * another reel starts up for this actor, + * or the actor gets killed. + */ + InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj, _ctx->pFreel->script, speed); + + if (bRestore || (ActorEsc(_ctx->reelActor) == true && + ActorEev(_ctx->reelActor) != GetEscEvents())) { + // From restore, step to jump or end + SkipFrames(&_ctx->thisAnim, -1); + } + + for (;;) { + if (_ctx->stepCount++ == 0) { + _ctx->frameCount++; + StoreActorSteps(_ctx->reelActor, _ctx->frameCount); + } + if (_ctx->stepCount == speed) + _ctx->stepCount = 0; + + if (_ctx->bPrinciple && AboutToJumpOrEnd(&_ctx->thisAnim)) + IncLoopCount(_ctx->reelActor); + + if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished) + break; + + if (_ctx->bRelative) { + GetAniPosition(_ctx->pPlayObj, &x, &y); + StoreActorPos(_ctx->reelActor, x, y); + } + + if (_ctx->bGotHidden) { + if (!ActorHidden(_ctx->reelActor)) { + MultiSetZPosition(_ctx->pPlayObj, _ctx->myZ); + _ctx->bGotHidden = false; + } + } else { + if (ActorHidden(_ctx->reelActor)) { + MultiSetZPosition(_ctx->pPlayObj, -1); + _ctx->bGotHidden = true; + } + } + + CORO_SLEEP(1); + + if (GetActorFilmNumber(_ctx->reelActor) != _ctx->filmNumber) { + _ctx->bReplaced = true; + break; + } + + if (ActorEsc(_ctx->reelActor) == true && ActorEev(_ctx->reelActor) != GetEscEvents()) { + if (!_ctx->bEscapedAlready) { + SkipFrames(&_ctx->thisAnim, -1); + _ctx->bEscapedAlready = true; + } + +//WHY??? UpdateActorEsc(reelActor, GetEscEvents()); +// The above line of code, not commented out would fix the coffee pot flash +// but why was it commented out? +// The extra boolean is used instead, 'cos it's release week and I want to play it safe! + } + } + + // Register the fact that we're NOT playing this for this actor + NotPlayingReel(_ctx->reelActor, _ctx->filmNumber, column); + + // Ditch the object + if (!bTop) + MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); + else + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + + // Restore moving actor is nessesary + if (_ctx->pMover != NULL && _ctx->bPrinciple && !_ctx->bReplaced) + UnHideMover(_ctx->pMover); + + CORO_END_CODE; +} + /** * Run all animations that comprise the play film. */ -static void playProcess(CORO_PARAM, const void *param) { - // get the stuff copied to process when it was created - PPINIT *ppi = (PPINIT *)param; +static void PlayProcess(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); - PlayReel(coroParam, ppi); + const PPINIT *ppi = (const PPINIT *)param; + CORO_BEGIN_CODE(_ctx); + + if (TinselV2) + CORO_INVOKE_ARGS(t2PlayReel, (CORO_SUBCTX, ppi->x, ppi->y, ppi->bRestore, ppi->speed, + ppi->hFilm, ppi->column, ppi->myescEvent, ppi->bTop)); + else + CORO_INVOKE_1(t1PlayReel, ppi); + + CORO_END_CODE; } // ******************************************************* // To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios -void newestFilm(SCNHANDLE film, const FREEL *reel) { +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); + if (!TinselV2 || ((int32)FROM_LE_32(pmi->mulID) != -2)) + SetActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film); } // ******************************************************* @@ -389,64 +968,90 @@ void newestFilm(SCNHANDLE film, const FREEL *reel) { * 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, +void PlayFilm(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool splay, bool sfact, bool escOn, int myescEvent, bool bTop) { - const FILM *pfilm = (const FILM *)LockMem(film); - PPINIT ppi; + assert(hFilm != 0); // Trying to play NULL film + const FILM *pFilm; + + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); - assert(film != 0); // Trying to play NULL film + CORO_BEGIN_CODE(_ctx); + + pFilm = (const FILM *)LockMem(hFilm); + PPINIT ppi; // Now allowed empty films! - if (pfilm->numreels == 0) + if (pFilm->numreels == 0) return; // Nothing to do! - ppi.hFilm = film; + ppi.hFilm = hFilm; ppi.x = x; ppi.y = y; ppi.z = 0; - ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + ppi.bRestore = false; + 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; + 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]); + for (int i = FROM_LE_32(pFilm->numreels) - 1; i >= 0; i--) { + NewestFilm(hFilm, &pFilm->reels[i]); ppi.column = i; - g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(PPINIT)); + g_scheduler->createProcess(PID_REEL, PlayProcess, &ppi, sizeof(PPINIT)); } + + if (TinselV2) { + // Let it all kick in and position this process + // down the process list from the playing process(es) + // This ensures something + CORO_GIVE_WAY; + + if (myescEvent && myescEvent != GetEscEvents()) + g_scheduler->rescheduleAll(); + } + + CORO_END_CODE; +} + +void PlayFilm(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int myescEvent, bool bTop) { + PlayFilm(coroParam, hFilm, x, y, 0, false, false, false, myescEvent, bTop); } /** * 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, +void PlayFilmc(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool splay, bool sfact, bool escOn, int myescEvent, bool bTop) { CORO_BEGIN_CONTEXT; PPINIT ppi; + int i; + int loopCount; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); - assert(film != 0); // Trying to play NULL film - const FILM *pfilm; + assert(hFilm != 0); // Trying to play NULL film + const FILM *pFilm; - pfilm = (const FILM *)LockMem(film); + pFilm = (const FILM *)LockMem(hFilm); // Now allowed empty films! - if (pfilm->numreels == 0) + if (pFilm->numreels == 0) return; // Already played to completion! - _ctx->ppi.hFilm = film; + _ctx->ppi.hFilm = hFilm; _ctx->ppi.x = x; _ctx->ppi.y = y; _ctx->ppi.z = 0; - _ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + _ctx->ppi.bRestore = false; + _ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pFilm->frate)); _ctx->ppi.actorid = actorid; _ctx->ppi.splay = splay; _ctx->ppi.bTop = bTop; @@ -454,18 +1059,40 @@ void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay _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]); + // Start display process for each secondary reel in the film in Tinsel 1, + // or all of them in Tinsel 2 + for (int i = FROM_LE_32(pFilm->numreels) - 1; i >= (TinselV2 ? 0 : 1); i--) { + NewestFilm(hFilm, &pFilm->reels[i]); _ctx->ppi.column = i; - g_scheduler->createProcess(PID_REEL, playProcess, &_ctx->ppi, sizeof(PPINIT)); + g_scheduler->createProcess(PID_REEL, PlayProcess, &_ctx->ppi, sizeof(PPINIT)); } - newestFilm(film, &pfilm->reels[0]); + if (TinselV2) { + // Let it all kick in and position this 'waiting' process + // down the process list from the playing process(es) + // This ensures immediate return when the reel finishes + CORO_GIVE_WAY; - _ctx->ppi.column = 0; - CORO_INVOKE_1(PlayReel, &_ctx->ppi); + _ctx->i = ExtractActor(hFilm); + _ctx->loopCount = GetLoopCount(_ctx->i); + + // Wait until film changes or loop count increases + while (GetActorPresFilm(_ctx->i) == hFilm && GetLoopCount(_ctx->i) == _ctx->loopCount) { + if (myescEvent && myescEvent != GetEscEvents()) { + g_scheduler->rescheduleAll(); + break; + } + + CORO_SLEEP(1); + } + } else { + // For Tinsel 1, launch the primary reel + NewestFilm(hFilm, &pFilm->reels[0]); + + _ctx->ppi.column = 0; + CORO_INVOKE_1(t1PlayReel, &_ctx->ppi); + } CORO_END_CODE; } @@ -473,13 +1100,14 @@ void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay /** * Start up a play process for a particular column in a film. * - * NOTE: This is specifically for actors during a restore scene. + * NOTE: This is specifically for actors during a Tinsel 1 restore scene. */ -void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) { - const FILM *pfilm = (const FILM *)LockMem(film); +void RestoreActorReels(SCNHANDLE hFilm, short reelnum, short z, int x, int y) { + assert(!TinselV2); + const FILM *pfilm = (const FILM *)LockMem(hFilm); PPINIT ppi; - ppi.hFilm = film; + ppi.hFilm = hFilm; ppi.x = x; ppi.y = y; ppi.z = z; @@ -487,21 +1115,58 @@ void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) { ppi.actorid = 0; ppi.splay = false; ppi.bTop = false; + ppi.bRestore = true; ppi.sf = 0; ppi.column = reelnum; + ppi.myescEvent = 0; - // 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]); + NewestFilm(hFilm, &pfilm->reels[reelnum]); // Start display process for the reel - g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(ppi)); + g_scheduler->createProcess(PID_REEL, PlayProcess, &ppi, sizeof(ppi)); +} + +/** + * Start up a play process for a particular column in a film. + * + * NOTE: This is specifically for actors during a Tinsel 2 restore scene. + */ +void RestoreActorReels(SCNHANDLE hFilm, int actor, int x, int y) { + assert(TinselV2); + FILM *pFilm = (FILM *)LockMem(hFilm); + PPINIT ppi; + + int i; + FREEL *pFreel; + PMULTI_INIT pmi; // MULTI_INIT structure + + ppi.hFilm = hFilm; + ppi.x = (short)x; + ppi.y = (short)y; + ppi.bRestore = true; + ppi.speed = (short)(ONE_SECOND/pFilm->frate); + ppi.bTop = false; + ppi.myescEvent = 0; + + // Search backwards for now as later column will be the one + for (i = pFilm->numreels - 1; i >= 0; i--) { + pFreel = &pFilm->reels[i]; + pmi = (PMULTI_INIT) LockMem(pFreel->mobj); + if (pmi->mulID == actor) { + ppi.column = (short)i; + NewestFilm(hFilm, &pFilm->reels[i]); + + // Start display process for the reel + g_scheduler->createProcess(PID_REEL, PlayProcess, &ppi, sizeof(ppi)); + + soundReelWait++; + } + } } } // end of namespace Tinsel diff --git a/engines/tinsel/play.h b/engines/tinsel/play.h new file mode 100644 index 0000000000..5cdca25faa --- /dev/null +++ b/engines/tinsel/play.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Plays films within a scene, takes into account the actor in each 'column'. + */ + +#ifndef TINSEL_PLAY_H // prevent multiple includes +#define TINSEL_PLAY_H + +#include "tinsel/coroutine.h" +#include "tinsel/dw.h" +#include "tinsel/multiobj.h" + +namespace Tinsel { + +#define MAX_SOUNDREELS 5 + +struct SOUNDREELS { + SCNHANDLE hFilm; // The 'film' + int column; // Column number + int actorCol; +}; +typedef SOUNDREELS *PSOUNDREELS; + +void PlayFilm(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, bool sfact, bool escOn, + int myescEvent, bool bTop); + +void PlayFilm(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int myescEvent, bool bTop); + +void PlayFilmc(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool splay, bool sfact, + bool escOn, int myescEvent, bool bTop); + +void RestoreActorReels(SCNHANDLE hFilm, short reelnum, short z, int x, int y); +void RestoreActorReels(SCNHANDLE hFilm, int actor, int x, int y); + +void PokeInPalette(const MULTI_INIT *pmi); + +void NoSoundReels(void); +void SaveSoundReels(PSOUNDREELS psr); +void RestoreSoundReels(PSOUNDREELS psr); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp index d73e290277..30d3565c00 100644 --- a/engines/tinsel/polygons.cpp +++ b/engines/tinsel/polygons.cpp @@ -25,9 +25,13 @@ #include "tinsel/actors.h" #include "tinsel/font.h" #include "tinsel/handle.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" +#include "tinsel/sched.h" #include "tinsel/serializer.h" +#include "tinsel/tinsel.h" #include "tinsel/token.h" #include "common/util.h" @@ -43,14 +47,13 @@ enum POLY_TYPE { POLY_EXIT, POLY_TAG }; - // 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 struct POLYGON { - PTYPE polytype; // Polygon type + PTYPE polyType; // Polygon type int subtype; // refer type in REFER polygons // NODE/NORMAL in PATH polygons @@ -64,10 +67,13 @@ struct POLYGON { short cy[4]; int polyID; + /* For TAG polygons only */ + int tagFlags; + SCNHANDLE hOverrideTag; + /* For TAG and EXIT (and EFFECT in future?) polygons only */ TSTATE tagState; PSTATE pointState; - SCNHANDLE oTagHandle; // Override tag. /* For Path polygons only */ bool tried; @@ -104,7 +110,7 @@ struct POLYGON { POLYGON *adjpaths[MAXADJ]; }; - +typedef POLYGON *PPOLYGON; #define MAXONROUTE 40 @@ -127,10 +133,25 @@ struct LINEINFO { int32 bc; } PACKED_STRUCT; -/** polygon struct - one per polygon */ -struct POLY { - int32 type; //!< type of polygon +#include "common/pack-end.h" // END STRUCT PACKING + +// POLY structure class. This is implemented as a class, because the structure +// of POLY's changed between TINSEL v1 and v2 +class Poly { +private: + const byte *_pStart; + const byte *_pData; + int _recordSize; + void nextPoly(); +public: + Poly(const byte *pSrc); + Poly(const byte *pSrc, int startIndex); + void operator++(); + void setIndex(int index); + + POLY_TYPE type; //!< type of polygon int32 x[4], y[4]; // Polygon definition + uint32 xoff, yoff; // DW2 - polygon offset int32 tagx, tagy; // } For tagged polygons SCNHANDLE hTagtext; // } i.e. EXIT, TAG, EFFECT @@ -143,6 +164,8 @@ struct POLY { int32 id; // } EXIT and TAG int32 scale1, scale2; // } + int32 level1, level2; // DW2 fields + int32 bright1, bright2; // DW2 fields int32 reel; // } PATH and NPATH int32 zFactor; // } @@ -152,10 +175,86 @@ struct POLY { int32 plinelist; SCNHANDLE hScript; //!< handle of code segment for polygon events -} PACKED_STRUCT; +}; -#include "common/pack-end.h" // END STRUCT PACKING +Poly::Poly(const byte *pSrc) { + _pStart = pSrc; + _pData = pSrc; + nextPoly(); + _recordSize = _pData - pSrc; +} + +Poly::Poly(const byte *pSrc, int startIndex) { + _pStart = pSrc; + _pData = pSrc; + nextPoly(); + _recordSize = _pData - pSrc; + setIndex(startIndex); +} +void Poly::operator++() { + nextPoly(); +} + +void Poly::setIndex(int index) { + _pData = _pStart + index * _recordSize; + nextPoly(); +} + +uint32 nextLong(const byte *&p) { + uint32 result = *((const uint32 *)p); + p += 4; + return result; +} + +void Poly::nextPoly() { + // Note: Originally I used a Serialiser, but dropped because for now we want + // the endian to remain as it is. It may be cleaner to later on switch to using + // it, and removing all endian conversions from the code that uses POLY's + int typeVal = nextLong(_pData); + if ((typeVal == 5) && TinselV2) ++typeVal; + type = (POLY_TYPE)typeVal; + + for (int i = 0; i < 4; ++i) x[i] = nextLong(_pData); + for (int i = 0; i < 4; ++i) y[i] = nextLong(_pData); + + if (TinselVersion == TINSEL_V2) { + xoff = nextLong(_pData); + yoff = nextLong(_pData); + id = nextLong(_pData); + reftype = nextLong(_pData); + } + + tagx = nextLong(_pData); + tagy = nextLong(_pData); + hTagtext = nextLong(_pData); + nodex = nextLong(_pData); + nodey = nextLong(_pData); + hFilm = nextLong(_pData); + + if (TinselVersion != TINSEL_V2) { + reftype = nextLong(_pData); + id = nextLong(_pData); + } + + scale1 = nextLong(_pData); + scale2 = nextLong(_pData); + + if (TinselVersion == TINSEL_V2) { + level1 = nextLong(_pData); + level2 = nextLong(_pData); + bright1 = nextLong(_pData); + bright2 = nextLong(_pData); + } + + reel = nextLong(_pData); + zFactor = nextLong(_pData); + nodecount = nextLong(_pData); + pnodelistx = nextLong(_pData); + pnodelisty = nextLong(_pData); + plinelist = nextLong(_pData); + hScript = nextLong(_pData); +} //----------------- LOCAL GLOBAL DATA -------------------- @@ -179,7 +278,8 @@ static POLYGON *RouteEnd = 0; int highestYet = 0; #endif - +// dead/alive, offsets +static POLY_VOLATILE volatileStuff[MAX_POLY]; //----------------- LOCAL MACROS -------------------- @@ -187,14 +287,15 @@ int highestYet = 0; #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(const POLYGON *pp) { - for (int j = 0; j <= MAX_POLY; j++) { - if (Polys[j] == pp) - return j; +//-------------------- METHODS ---------------------- + +static HPOLYGON PolygonIndex(const POLYGON *pp) { + for (int i = 0; i <= MAX_POLY; ++i) { + if (Polys[i] == pp) + return i; } - error("PolyIndex(): polygon not found"); - return NOPOLY; + error("PolygonIndex(): polygon not found"); } /** @@ -234,6 +335,12 @@ bool IsInPolygon(int xt, int yt, HPOLYGON hp) { pp = Polys[hp]; assert(pp != NULL); // Testing whether in a NULL polygon + // Shift cursor for relative polygons + if (TinselV2) { + xt -= volatileStuff[hp].xoff; + yt -= volatileStuff[hp].yoff; + } + /* Is point within the external rectangle? */ if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom) return false; @@ -254,7 +361,7 @@ bool IsInPolygon(int xt, int yt, HPOLYGON hp) { if (BeenTested) { // New dodgy code 29/12/94 - if (pp->polytype == BLOCKING) { + if (pp->polyType == BLOCK) { // For each corner/side for (i = 0; i < 4; i++) { // Pretend the corners of blocking polys are not in the poly. @@ -285,7 +392,7 @@ bool IsInPolygon(int xt, int yt, HPOLYGON hp) { HPOLYGON InPolygon(int xt, int yt, PTYPE type) { for (int j = 0; j <= MAX_POLY; j++) { - if (Polys[j] && Polys[j]->polytype == type) { + if (Polys[j] && Polys[j]->polyType == type) { if (IsInPolygon(xt, yt, j)) return j; } @@ -434,7 +541,6 @@ void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) { const POLYGON *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 @@ -453,14 +559,14 @@ void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) { // Pointer to polygon data pps = LockMem(pHandle); // All polygons - ptp = (const POLY *)pps + pp->pIndex; // This polygon + Poly ptp(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)); + 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++) { + 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); @@ -640,7 +746,7 @@ static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) { for (i = 0; i < MAX_POLY; i++) { // For each polygon.. POLYGON *p = Polys[i]; - if (p && p->polytype == PATH) //...if it's a path + if (p && p->polyType == PATH) //...if it's a path p->tried = false; } Polys[from]->tried = true; @@ -648,31 +754,34 @@ static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) { const POLYGON *p = TryPath(Polys[from], Polys[to], Polys[from]); + if (TinselV2 && !p) + return NOPOLY; + 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]); + CHECK_HP(PolygonIndex(RoutePaths[i]), "Out of range polygon handle (502)"); + if (IsAdjacentPath(from, PolygonIndex(RoutePaths[i]))) + return PolygonIndex(RoutePaths[i]); } - return PolyIndex(p); + return PolygonIndex(p); } /** * Indirect method of calling PathOnTheWay(), to put the burden of * recursion onto the main stack. */ -HPOLYGON getPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) { +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]); + CHECK_HP(PolygonIndex(RoutePaths[i]), "Out of range polygon handle (503)"); + if (IsAdjacentPath(hFrom, PolygonIndex(RoutePaths[i]))) { + return PolygonIndex(RoutePaths[i]); } } } @@ -691,19 +800,18 @@ int NearestEndNode(HPOLYGON hPath, int x, int y) { 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 + Poly ptp(pps, pp->pIndex); // This polygon - nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); - nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + 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); + 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])); @@ -725,7 +833,6 @@ int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) { 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; @@ -735,16 +842,16 @@ int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) { pDpath = Polys[hDpath]; pps = LockMem(pHandle); // All polygons - ps = (const POLY *)pps + pSpath->pIndex; // Start polygon - pd = (const POLY *)pps + pDpath->pIndex; // Dest polygon + Poly ps(pps, pSpath->pIndex); // Start polygon + Poly pd(pps, pDpath->pIndex); // Dest polygon - ns = (int)FROM_LE_32(ps->nodecount) - 1; - nd = (int)FROM_LE_32(pd->nodecount) - 1; + 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)); + 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])); @@ -780,18 +887,17 @@ int NearestNodeWithin(HPOLYGON hNpath, int x, int y) { 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 + Poly ptp(pps, Polys[hNpath]->pIndex); // This polygon - nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); - nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + 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); + 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])); @@ -875,7 +981,6 @@ bool IsPolyCorner(HPOLYGON hPath, int x, int y) { * 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; @@ -887,18 +992,18 @@ int GetScale(HPOLYGON hPath, int y) { CHECK_HP(hPath, "Out of range polygon handle (14)"); - ptp = (const POLY *)LockMem(pHandle) + Polys[hPath]->pIndex; + Poly ptp(LockMem(pHandle), Polys[hPath]->pIndex); // Path is of a constant scale? - if (FROM_LE_32(ptp->scale2) == 0) - return FROM_LE_32(ptp->scale1); + if (FROM_LE_32(ptp.scale2) == 0) + return FROM_LE_32(ptp.scale1); - assert(FROM_LE_32(ptp->scale1) >= FROM_LE_32(ptp->scale2)); + assert(FROM_LE_32(ptp.scale1) >= FROM_LE_32(ptp.scale2)); - zones = FROM_LE_32(ptp->scale1) - FROM_LE_32(ptp->scale2) + 1; + 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); + scale = FROM_LE_32(ptp.scale1); top = Polys[hPath]->ptop; do { @@ -907,28 +1012,67 @@ int GetScale(HPOLYGON hPath, int y) { return scale; } while (--scale); - return FROM_LE_32(ptp->scale2); + return FROM_LE_32(ptp.scale2); } +/** + * Given a path polygon and a Y co-ordinate, return a brightness value. + */ + +int GetBrightness(HPOLYGON hPath, int y) { + int zones; // Number of different brightnesses + int zlen; // Depth of each brightness zone + int brightness; + int top; + + // To try and fix some unknown potential bug + if (hPath == NOPOLY) + return 10; + + CHECK_HP(hPath, "Out of range polygon handle (38)"); + + Poly pp(LockMem(pHandle), Polys[hPath]->pIndex); + + // Path is of a constant brightness? + if (pp.bright1 == pp.bright2) + return pp.bright1; + + assert(FROM_LE_32(pp.bright1) >= FROM_LE_32(pp.bright2)); + + zones = FROM_LE_32(pp.bright1) - FROM_LE_32(pp.bright2) + 1; + zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones; + + brightness = FROM_LE_32(pp.bright1); + top = Polys[hPath]->ptop; + + do { + top += zlen; + if (y < top) + return brightness; + } while(--brightness); + + return FROM_LE_32(pp.bright2); +} + + /** * 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! + 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 + Poly ptp(pps, Polys[hNpath]->pIndex); // This polygon - nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); - nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + 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)) + if (node == (int)FROM_LE_32(ptp.nodecount)) node -= 1; *px = (int)FROM_LE_32(nlistx[node]); @@ -936,98 +1080,68 @@ void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) { } /** - * Get tag text handle and tag co-ordinates of a polygon. + * Get compiled tag text handle and tag co-ordinates of a tag polygon. */ - -void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) { - const POLY *pp; // Pointer to compiled polygon data - +void GetTagTag(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) { CHECK_HP(hp, "Out of range polygon handle (16)"); - pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + Poly pp(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); + *tagx = (int)FROM_LE_32(pp.tagx) + (TinselV2 ? volatileStuff[hp].xoff : 0); + *tagy = (int)FROM_LE_32(pp.tagy) + (TinselV2 ? volatileStuff[hp].yoff : 0); + *hTagText = FROM_LE_32(pp.hTagtext); } /** * Get polygon's film reel handle. */ - -SCNHANDLE getPolyFilm(HPOLYGON hp) { - const POLY *pp; // Pointer to compiled polygon data - +SCNHANDLE GetPolyFilm(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (17)"); - pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + Poly pp(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); + return FROM_LE_32(pp.hFilm); } /** * Get handle to polygon's glitter code. */ - -SCNHANDLE getPolyScript(HPOLYGON hp) { - const POLY *pp; // Pointer to compiled polygon data - +SCNHANDLE GetPolyScript(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (19)"); - pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + Poly pp(LockMem(pHandle), Polys[hp]->pIndex); - return FROM_LE_32(pp->hScript); + return FROM_LE_32(pp.hScript); } -REEL getPolyReelType(HPOLYGON hp) { - const POLY *pp; // Pointer to compiled polygon data - +REEL GetPolyReelType(HPOLYGON hp) { // 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; + Poly pp(LockMem(pHandle), Polys[hp]->pIndex); - return (REEL)FROM_LE_32(pp->reel); + return (REEL)FROM_LE_32(pp.reel); } -int32 getPolyZfactor(HPOLYGON hp) { - const POLY *pp; // Pointer to compiled polygon data - +int32 GetPolyZfactor(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (21)"); assert(Polys[hp] != NULL); - pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + Poly pp(LockMem(pHandle), Polys[hp]->pIndex); - return (int)FROM_LE_32(pp->zFactor); + 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; + Poly pp(LockMem(pHandle), Polys[hp]->pIndex); - return (int)FROM_LE_32(pp->nodecount); + return (int)FROM_LE_32(pp.nodecount); } // ************************************************************************* @@ -1116,126 +1230,45 @@ void syncPolyInfo(Serializer &s) { */ void SaveDeadPolys(bool *sdp) { + assert(!TinselV2); memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool)); } void RestoreDeadPolys(bool *sdp) { + assert(!TinselV2); 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 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; - } - } - - TAGSTATE *pts; - pts = &TagStates[SceneTags[currentTScene].offset]; - for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { - if (pts->tid == tagno) { - pts->enabled = true; - break; - } - } +void SavePolygonStuff(POLY_VOLATILE *sps) { + assert(TinselV2); + memcpy(sps, volatileStuff, MAX_POLY*sizeof(POLY_VOLATILE)); } -/** - * 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; - } - } - - TAGSTATE *pts; - pts = &ExitStates[SceneExits[currentEScene].offset]; - for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { - if (pts->tid == exitno) { - pts->enabled = true; - break; - } - } +void RestorePolygonStuff(POLY_VOLATILE *sps) { + assert(TinselV2); + memcpy(volatileStuff, sps, MAX_POLY*sizeof(POLY_VOLATILE)); } -/** - * Convert a TAG to an EX_TAG poly. - */ -void DisableTag(int tagno) { - TAGSTATE *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 a EXIT to an EX_EXIT poly. + * Scan for a given polygon */ -void DisableExit(int exitno) { - TAGSTATE *pts; +static HPOLYGON FindPolygon(PTYPE type, int id) { - 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; + for (int i = 0; i <= MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polyType == type && Polys[i]->polyID == id) { + // Found it + return i; } } - pts = &ExitStates[SceneExits[currentEScene].offset]; - for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { - if (pts->tid == exitno) { - pts->enabled = false; - break; - } - } + // Not found + return NOPOLY; } HPOLYGON FirstPathPoly(void) { for (int i = 0; i < noofPolys; i++) { - if (Polys[i]->polytype == PATH) + if (Polys[i]->polyType == PATH) return i; } error("FirstPathPoly() - no PATH polygons!"); @@ -1286,21 +1319,51 @@ static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) { return retval; } +/** + * Returns true if the two paths are on the same level + */ +static bool MatchingLevels(PPOLYGON p1, PPOLYGON p2) { + byte *pps; // Compiled polygon data + + pps = LockMem(pHandle); // All polygons + Poly pp1(pps, p1->pIndex); // This polygon 1 + Poly pp2(pps, p2->pIndex); // This polygon 2 + + assert(pp1.level1 <= pp1.level2); + assert(pp2.level1 <= pp2.level2); + + for (int pl = pp1.level1; pl <= pp1.level2; pl++) { + if (pl >= pp2.level1 && pl <= pp2.level2) + return true; + } + + return false; +} + static void SetPathAdjacencies() { POLYGON *p1, *p2; // Polygon pointers + int i1, i2; + + // Reset them all + for (i1 = 0; i1 < noofPolys; i1++) + memset(Polys[i1]->adjpaths, 0, MAXADJ * sizeof(PPOLYGON)); // For each polygon.. - for (int i1 = 0; i1 < MAX_POLY-1; i1++) { + for (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) + if (!p1 || p1->polyType != PATH) continue; // For each subsequent polygon.. - for (int i2 = i1 + 1; i2 < MAX_POLY; i2++) { + for (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) + if (!p2 || p2->polyType != PATH) + continue; + + // Must be on the same level + if (TinselV2 && !MatchingLevels(p1, p2)) continue; int j = DistinctCorners(i1, i2); @@ -1351,21 +1414,21 @@ void CheckNPathIntegrity() { 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 + 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)); + 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 + n = (int)FROM_LE_32(cp.nodecount) - 1; // Last node assert(n >= 1); // Node paths must have at least 2 nodes - hp = PolyIndex(rp); + hp = PolygonIndex(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))", + sprintf(TextBufferAddr(), "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()); + error(TextBufferAddr()); } } @@ -1374,15 +1437,15 @@ void CheckNPathIntegrity() { 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))", + if (IsInPolygon((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolygonIndex(rp->adjpaths[j]))) { + sprintf(TextBufferAddr(), "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()) + error(TextBufferAddr()) } - 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))", + if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolygonIndex(rp->adjpaths[j]))) { + sprintf(TextBufferAddr(), "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()) + error(TextBufferAddr()) } } } @@ -1396,8 +1459,8 @@ void CheckNPathIntegrity() { 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; + if (Polys[i] && Polys[i]->polyType == BLOCK) + Polys[i]->polyType = EX_BLOCK; #ifdef DEBUG else error("Impossible message!"); @@ -1410,8 +1473,8 @@ static void SetExBlocks() { * Called at the start of a scene, nobbles TAG polygons which should be dead. */ static void SetExTags(SCNHANDLE ph) { - TAGSTATE *pts; int i, j; + TAGSTATE *pts; for (i = 0; i < numScenesT; i++) { if (SceneTags[i].sid == ph) { @@ -1420,7 +1483,7 @@ static void SetExTags(SCNHANDLE ph) { pts = &TagStates[SceneTags[i].offset]; for (j = 0; j < SceneTags[i].nooftags; j++, pts++) { if (!pts->enabled) - DisableTag(pts->tid); + DisableTag(nullContext, pts->tid); } return; } @@ -1435,7 +1498,7 @@ static void SetExTags(SCNHANDLE ph) { SceneTags[i].nooftags = 0; for (j = 0; j < MAX_POLY; j++) { - if (Polys[j] && Polys[j]->polytype == TAG) { + if (Polys[j] && Polys[j]->polyType == TAG) { TagStates[nextfreeT].tid = Polys[j]->polyID; TagStates[nextfreeT].enabled = true; nextfreeT++; @@ -1474,7 +1537,7 @@ static void SetExExits(SCNHANDLE ph) { SceneExits[i].nooftags = 0; for (j = 0; j < MAX_POLY; j++) { - if (Polys[j] && Polys[j]->polytype == EXIT) { + if (Polys[j] && Polys[j]->polyType == EXIT) { ExitStates[nextfreeE].tid = Polys[j]->polyID; ExitStates[nextfreeE].enabled = true; nextfreeE++; @@ -1521,6 +1584,57 @@ static void FiddlyBit(POLYGON *p) { } } +/** + * Allocate a POLYGON structure and reset it to default values + */ +static PPOLYGON GetPolyEntry() { + int i; // Loop counter + PPOLYGON p; + + for (i = 0; i < MaxPolys; i++) { + if (!Polys[i]) { + p = Polys[i] = &Polygons[i]; + + // What the hell, just clear it all out - it's safer + memset(p, 0, sizeof(POLYGON)); + + return p; + } + } + + error("Exceeded MaxPolys"); +} + +/** + * Variation of GetPolyEntry from Tinsel 1 that splits up getting a new + * polygon structure from initialising it + */ +static PPOLYGON CommonInits(PTYPE polyType, int pno, const Poly &pp, bool bRestart) { + int i; + HPOLYGON hp; + PPOLYGON p = GetPolyEntry(); // Obtain a slot + + p->polyType = polyType; // Polygon type + p->pIndex = pno; + + for (i = 0; i < 4; i++) { // Polygon definition + p->cx[i] = (short)pp.x[i]; + p->cy[i] = (short)pp.y[i]; + } + + if (!bRestart) { + hp = PolygonIndex(p); + volatileStuff[hp].xoff = (short)pp.xoff; + volatileStuff[hp].yoff = (short)pp.yoff; + } + + p->polyID = pp.id; // Identifier + + FiddlyBit(p); + + return p; +} + /** * Calculate a point approximating to the centre of a polygon. * Not very sophisticated. @@ -1529,17 +1643,17 @@ static void PseudoCentre(POLYGON *p) { 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))) { + if (!IsInPolygon(p->pcentrex, p->pcentrey, PolygonIndex(p))) { int i, top = 0, bot = 0; for (i = p->ptop; i <= p->pbottom; i++) { - if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + if (IsInPolygon(p->pcentrex, i, PolygonIndex(p))) { top = i; break; } } for (i = p->pbottom; i >= p->ptop; i--) { - if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + if (IsInPolygon(p->pcentrex, i, PolygonIndex(p))) { bot = i; break; } @@ -1547,68 +1661,30 @@ static void PseudoCentre(POLYGON *p) { 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?", + // assert(IsInPolygon(p->pcentrex, p->pcentrey, PolygonIndex(p))); // Pseudo-centre is not in path + if (!IsInPolygon(p->pcentrex, p->pcentrey, PolygonIndex(p))) { + sprintf(TextBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?", p->cx[0], p->cy[0]); - error(tBufferAddr()); + error(TextBufferAddr()); } #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)); +static void InitExit(const Poly &pp, int pno, bool bRestart) { + CommonInits(EXIT, pno, pp, bRestart); } /** * 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; - } +static void InitPath(const Poly &pp, bool NodePath, int pno, bool bRestart) { + PPOLYGON p = CommonInits(PATH, pno, pp, bRestart); - // Clear out ajacent path pointers - memset(p->adjpaths, 0, MAXADJ*sizeof(POLYGON *)); + p->subtype = NodePath ? NODE : NORMAL; - FiddlyBit(p); PseudoCentre(p); } @@ -1616,8 +1692,8 @@ static void InitPath(const POLY *pp, bool NodePath, int pno) { /** * Initialise a BLOCKING polygon. */ -static void InitBlock(const POLY *pp, int pno) { - FiddlyBit(GetPolyEntry(BLOCKING, pp, pno)); +static void InitBlock(const Poly &pp, int pno, bool bRestart) { + CommonInits(BLOCK, pno, pp, bRestart); } /** @@ -1626,16 +1702,16 @@ static void InitBlock(const POLY *pp, int pno) { * trying to walk through the actor you first thought of. * This is for dynamic blocking. */ -HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) { +HPOLYGON InitExtraBlock(PMOVER ca, PMOVER 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 + GetMoverPosition(ca, &caX, &caY); // Calling actor co-ords + GetMoverPosition(ta, &taX, &taY); // Test actor co-ords - left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX); - right = GetMActorRight(ta) + (caX - GetMActorLeft(ca)); + left = GetMoverLeft(ta) - (GetMoverRight(ca) - caX); + right = GetMoverRight(ta) + (caX - GetMoverLeft(ca)); memset(&extraBlock, 0, sizeof(extraBlock)); @@ -1658,37 +1734,70 @@ HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) { /** * Initialise an EFFECT polygon. */ -static void InitEffect(const POLY *pp, int pno) { - FiddlyBit(GetPolyEntry(EFFECT, pp, pno)); +static void InitEffect(const Poly &pp, int pno, bool bRestart) { + CommonInits(EFFECT, pno, pp, bRestart); } /** * 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 +static void InitRefer(const Poly &pp, int pno, bool bRestart) { + PPOLYGON p = CommonInits(REFER, pno, pp, bRestart); - FiddlyBit(p); + p->subtype = FROM_LE_32(pp.reftype); // Refer type } /** * Initialise a TAG polygon. */ -static void InitTag(const POLY *pp, int pno) { - FiddlyBit(GetPolyEntry(TAG, pp, pno)); +static void InitTag(const Poly &pp, int pno, bool bRestart) { + CommonInits(TAG, pno, pp, bRestart); } +/** + * Called at the restart of a scene, nobbles polygons which are dead. + */ +static void KillDeadPolygons(void) { + int i; + + for (i = 0; i < MAX_POLY; i++) { + if (volatileStuff[i].bDead) { + assert(Polys[i]); + + switch (Polys[i]->polyType) { + case BLOCK: + Polys[i]->polyType = EX_BLOCK; + break; + + case EFFECT: + Polys[i]->polyType = EX_EFFECT; + break; + + case REFER: + Polys[i]->polyType = EX_REFER; + break; + + case PATH: + Polys[i]->polyType = EX_PATH; + break; + + case TAG: + Polys[i]->polyType = EX_TAG; + break; + + default: + error("Impossible message!"); + } + } + } +} /** * 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; @@ -1702,62 +1811,90 @@ void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) { } } + if (numPoly == 0) + return; + for (int i = 0; i < noofPolys; i++) { if (Polys[i]) { - Polys[i]->pointState = NOT_POINTING; + Polys[i]->pointState = PS_NOT_POINTING; Polys[i] = NULL; } } memset(RoutePaths, 0, sizeof(RoutePaths)); - if (!bRestart) - memset(deadPolys, 0, sizeof(deadPolys)); + if (!bRestart) { + if (TinselV2) + memset(volatileStuff, 0, sizeof(volatileStuff)); + else + 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; + if (numPoly > 0) { + Poly pp(LockMem(ph)); - case POLY_NPATH: - InitPath(pp, true, i); - break; + for (int i = 0; i < numPoly; ++i, ++pp) { + switch (FROM_LE_32(pp.type)) { + case POLY_PATH: + InitPath(pp, false, i, bRestart); + break; - case POLY_BLOCK: - InitBlock(pp, i); - break; + case POLY_NPATH: + InitPath(pp, true, i, bRestart); + break; - case POLY_REFER: - InitRefer(pp, i); - break; + case POLY_BLOCK: + InitBlock(pp, i, bRestart); + break; - case POLY_EFFECT: - InitEffect(pp, i); - break; + case POLY_REFER: + InitRefer(pp, i, bRestart); + break; - case POLY_EXIT: - InitExit(pp, i); - break; + case POLY_EFFECT: + InitEffect(pp, i, bRestart); + break; - case POLY_TAG: - InitTag(pp, i); - break; + case POLY_EXIT: + InitExit(pp, i, bRestart); + break; - default: - error("Unknown polygon type"); + case POLY_TAG: + InitTag(pp, i, bRestart); + break; + + default: + error("Unknown polygon type"); + } } } - SetPathAdjacencies(); // Paths need to know the facts + + if (!TinselV2) { + SetPathAdjacencies(); // Paths need to know the facts #ifdef DEBUG - CheckNPathIntegrity(); + 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 + 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 + } else { + if (bRestart) { + // Some may have been killed if this is a restore + KillDeadPolygons(); + } else { + for (int i = numPoly - 1; i >= 0; i--) { + if (Polys[i]->polyType == TAG) { + PolygonEvent(nullContext, i, STARTUP, 0, false, 0); + } + } + } + + // Paths need to know the facts + SetPathAdjacencies(); + } } /** @@ -1770,7 +1907,7 @@ void DropPolygons() { for (int i = 0; i < noofPolys; i++) { if (Polys[i]) { - Polys[i]->pointState = NOT_POINTING; + Polys[i]->pointState = PS_NOT_POINTING; Polys[i] = NULL; } } @@ -1784,7 +1921,7 @@ void DropPolygons() { PTYPE PolyType(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (25)"); - return Polys[hp]->polytype; + return Polys[hp]->polyType; } int PolySubtype(HPOLYGON hp) { @@ -1829,12 +1966,6 @@ TSTATE PolyTagState(HPOLYGON hp) { 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)"); @@ -1850,7 +1981,7 @@ void SetPolyTagState(HPOLYGON hp, TSTATE ts) { void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) { CHECK_HP(hp, "Out of range polygon handle (36)"); - Polys[hp]->oTagHandle = th; + Polys[hp]->hOverrideTag = th; } void MaxPolygons(int numPolys) { @@ -1859,4 +1990,387 @@ void MaxPolygons(int numPolys) { MaxPolys = numPolys; } +/** + * Get polygon's associated node. + * The one for WalkTag(), StandTag() etc. + */ +void GetPolyNode(HPOLYGON hp, int *pNodeX, int *pNodeY) { + CHECK_HP(hp, "GetPolyNode(): Out of range polygon handle"); + + Poly pp(LockMem(pHandle), Polys[hp]->pIndex); + + *pNodeX = FROM_LE_32(pp.nodex); + *pNodeY = FROM_LE_32(pp.nodey); + + if (TinselV2) { + *pNodeX += volatileStuff[hp].xoff; + *pNodeY += volatileStuff[hp].yoff; + } +} + +void SetPolyPointedTo(HPOLYGON hp, bool bPointedTo) { + CHECK_HP(hp, "Out of range polygon handle (34)"); + + if (bPointedTo) + Polys[hp]->tagFlags |= POINTING; + else + Polys[hp]->tagFlags &= ~POINTING; +} + +bool PolyIsPointedTo(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (31)"); + + if (TinselV2) + return (Polys[hp]->tagFlags & POINTING); + + return PolyPointState(hp) == PS_POINTING; +} + +void SetPolyTagWanted(HPOLYGON hp, bool bTagWanted, bool bCursor, SCNHANDLE hOverrideTag) { + CHECK_HP(hp, "Out of range polygon handle (35)"); + + if (bTagWanted) { + Polys[hp]->tagFlags |= TAGWANTED; + Polys[hp]->hOverrideTag = hOverrideTag; + } else { + Polys[hp]->tagFlags &= ~TAGWANTED; + Polys[hp]->hOverrideTag = 0; + } + + if (bCursor) + Polys[hp]->tagFlags |= FOLLOWCURSOR; + else + Polys[hp]->tagFlags &= ~FOLLOWCURSOR; +} + +bool PolyTagIsWanted(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (32)"); + + return (Polys[hp]->tagFlags & TAGWANTED); +} + +bool PolyTagFollowsCursor(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (36)"); + + return (Polys[hp]->tagFlags & FOLLOWCURSOR); +} + +SCNHANDLE GetPolyTagHandle(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (33)"); + + return Polys[hp]->hOverrideTag; +} + +bool IsTagPolygon(int tagno) { + return (FindPolygon(TAG, tagno) != NOPOLY || FindPolygon(EX_TAG, tagno) != NOPOLY); +} + +int GetTagPolyId(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (GetTagPolyId()"); + + assert(Polys[hp]->polyType == TAG || Polys[hp]->polyType == EX_TAG); + + return Polys[hp]->polyID; +} + +void GetPolyMidBottom( HPOLYGON hp, int *pX, int *pY) { + CHECK_HP(hp, "Out of range polygon handle (GetPolyMidBottom()"); + + *pY = Polys[hp]->pbottom + volatileStuff[hp].yoff; + *pX = (Polys[hp]->pleft + Polys[hp]->pright)/2 + volatileStuff[hp].xoff; +} + +int PathCount(void) { + int i, count; + + for (i = 0, count = 0; i < noofPolys; i++) { + if (Polys[i]->polyType == PATH) + count++; + } + + return count; +} + +/** + * Convert a BLOCK to an EX_BLOCK poly + */ +void DisableBlock(int block) { + int i = FindPolygon(BLOCK, block); + + if (i != NOPOLY) { + Polys[i]->polyType = EX_BLOCK; + volatileStuff[i].bDead = true; + } +} + +/** + * Convert an EX_BLOCK to a BLOCK poly + */ +void EnableBlock(int block) { + int i = FindPolygon(EX_BLOCK, block); + + if (i != NOPOLY) { + Polys[i]->polyType = BLOCK; + volatileStuff[i].bDead = false; + } +} + +/** + * Convert an EFFECT to an EX_EFFECT poly + */ +void DisableEffect(int effect) { + int i = FindPolygon(EFFECT, effect); + + if (i != NOPOLY) { + Polys[i]->polyType = EX_EFFECT; + volatileStuff[i].bDead = true; + } +} + +/** + * Convert an EX_EFFECT to an EFFECT poly + */ +void EnableEffect(int effect) { + int i = FindPolygon(EX_EFFECT, effect); + + if (i != NOPOLY) { + Polys[i]->polyType = EFFECT; + volatileStuff[i].bDead = false; + } +} + +/** + * Convert a PATH to an EX_PATH poly + */ +void DisablePath(int path) { + int i = FindPolygon(PATH, path); + + if (i != NOPOLY) { + Polys[i]->polyType = EX_PATH; + volatileStuff[i].bDead = true; + + // Paths need to know the new facts + SetPathAdjacencies(); + } +} + +/** + * Convert a PATH to an EX_PATH poly + */ +void EnablePath(int path) { + int i = FindPolygon(EX_PATH, path); + + if (i != NOPOLY) { + Polys[i]->polyType = PATH; + volatileStuff[i].bDead = false; + + // Paths need to know the new facts + SetPathAdjacencies(); + } +} + +/** + * Convert a REFER to an EX_REFER poly + */ +void DisableRefer(int refer) { + int i = FindPolygon(REFER, refer); + + if (i != NOPOLY) { + Polys[i]->polyType = EX_REFER; + volatileStuff[i].bDead = true; + } +} + +/** + * Convert a REFER to an EX_REFER poly + */ +void EnableRefer(int refer) { + int i = FindPolygon(EX_REFER, refer); + + if (i != NOPOLY) { + Polys[i]->polyType = REFER; + volatileStuff[i].bDead = false; + } +} + +/** + * Convert an EX_TAG to a TAG poly. + */ +void EnableTag(CORO_PARAM, int tag) { + CORO_BEGIN_CONTEXT; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if ((_ctx->i = FindPolygon(EX_TAG, tag)) != NOPOLY) { + Polys[_ctx->i]->polyType = TAG; + volatileStuff[_ctx->i].bDead = false; + + if (TinselV2) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, SHOWEVENT, 0, true, 0)); + } else if ((_ctx->i = FindPolygon(TAG, tag)) != NOPOLY) { + if (TinselV2) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, SHOWEVENT, 0, true, 0)); + } + + if (!TinselV2) { + TAGSTATE *pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tag) { + pts->enabled = true; + break; + } + } + } + + CORO_END_CODE; +} + +/** + * 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; + } + } + + TAGSTATE *pts; + pts = &ExitStates[SceneExits[currentEScene].offset]; + for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { + if (pts->tid == exitno) { + pts->enabled = true; + break; + } + } +} + +/** + * Move a polygon relative to current offset. + */ +void MovePolygon(PTYPE ptype, int id, int x, int y) { + int i = FindPolygon(ptype, id); + + // If not found, try its dead equivalent + if (i == NOPOLY) { + switch (ptype) { + case TAG: + ptype = EX_TAG; + break; + default: + break; + } + + i = FindPolygon(ptype, id); + } + + if (i != NOPOLY) { + volatileStuff[i].xoff += (short)x; + volatileStuff[i].yoff += (short)y; + } +} + +/** + * Move a polygon relative to absolute offset. + */ +void MovePolygonTo(PTYPE ptype, int id, int x, int y) { + int i = FindPolygon(ptype, id); + + // If not found, try its dead equivalent + if (i == NOPOLY) { + switch (ptype) { + case TAG: + ptype = EX_TAG; + break; + default: + break; + } + + i = FindPolygon(ptype, id); + } + + if (i != NOPOLY) { + volatileStuff[i].xoff = (short)x; + volatileStuff[i].yoff = (short)y; + } +} + + +/** + * Convert tag number to polygon handle. + */ +HPOLYGON GetTagHandle(int tagno) { + int i = FindPolygon(TAG, tagno); + + if (i == NOPOLY) + i = FindPolygon(EX_TAG, tagno); + + assert(i != NOPOLY); + + return GetPolyHandle(i); +} + +/** + * Convert a TAG to an EX_TAG poly. + */ +void DisableTag(CORO_PARAM, int tag) { + CORO_BEGIN_CONTEXT; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if ((_ctx->i = FindPolygon(TAG, tag)) != NOPOLY) { + Polys[_ctx->i]->polyType = EX_TAG; + Polys[_ctx->i]->tagFlags = 0; + Polys[_ctx->i]->tagState = TAG_OFF; + Polys[_ctx->i]->pointState = PS_NOT_POINTING; + + volatileStuff[_ctx->i].bDead = true; + + if (TinselV2) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, HIDEEVENT, 0, true, 0)); + } else if ((_ctx->i = FindPolygon(EX_TAG, tag)) != NOPOLY) { + if (TinselV2) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, HIDEEVENT, 0, true, 0)); + } + + if (!TinselV2) { + TAGSTATE *pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tag) { + pts->enabled = false; + break; + } + } + } + + CORO_END_CODE; +} + +/** + * Convert a EXIT to an EX_EXIT poly. + */ +void DisableExit(int exitno) { + TAGSTATE *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 = PS_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; + } + } +} + } // end of namespace Tinsel diff --git a/engines/tinsel/polygons.h b/engines/tinsel/polygons.h index 90c57d5f99..62ec0422c6 100644 --- a/engines/tinsel/polygons.h +++ b/engines/tinsel/polygons.h @@ -35,8 +35,12 @@ namespace Tinsel { // Polygon Types enum PTYPE { - TEST, PATH, EXIT, BLOCKING, - EFFECT, REFER, TAG, EX_TAG, EX_EXIT, EX_BLOCK + // Tinsel 2 Polygon type list + TEST, + BLOCK, EFFECT, PATH, REFER, TAG, + EX_BLOCK, EX_EFFECT, EX_PATH, EX_REFER, EX_TAG, + // Extra polygon types from Tinsel v1 + EXIT, EX_EXIT }; // subtype @@ -45,6 +49,13 @@ enum { NODE = 1 // For paths }; +// tagFlags +enum { + POINTING = 0x01, + TAGWANTED = 0x02, + FOLLOWCURSOR = 0x04 +}; + // tagState enum TSTATE { TAG_OFF, TAG_ON @@ -52,7 +63,7 @@ enum TSTATE { // pointState enum PSTATE { - NO_POINT, NOT_POINTING, POINTING + PS_NO_POINT, PS_NOT_POINTING, PS_POINTING }; @@ -60,7 +71,10 @@ enum { NOPOLY = -1 }; - +struct POLY_VOLATILE { + bool bDead; + short xoff, yoff; // Polygon offset +}; /*-------------------------------------------------------------------------*/ @@ -69,26 +83,36 @@ 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); +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); +int GetBrightness(HPOLYGON hPath, 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); +void GetTagTag(HPOLYGON p, SCNHANDLE *hTagText, int *tagx, int *tagy); +SCNHANDLE GetPolyFilm(HPOLYGON p); +void GetPolyNode(HPOLYGON hp, int *pNodeX, int *pNodeY); +SCNHANDLE GetPolyScript(HPOLYGON p); +REEL GetPolyReelType(HPOLYGON p); +int32 GetPolyZfactor(HPOLYGON p); int numNodes(HPOLYGON pp); void RebootDeadTags(void); +void DisableBlock(int block); +void EnableBlock(int block); +void DisableEffect(int effect); +void EnableEffect(int effect); +void DisablePath(int path); +void EnablePath(int path); +void DisableRefer(int refer); +void EnableRefer(int refer); void DisableBlock(int blockno); void EnableBlock(int blockno); -void DisableTag(int tagno); -void EnableTag(int tagno); +HPOLYGON GetTagHandle(int tagno); +void DisableTag(CORO_PARAM, int tag); +void EnableTag(CORO_PARAM, int tag); void DisableExit(int exitno); void EnableExit(int exitno); HPOLYGON FirstPathPoly(void); @@ -99,6 +123,8 @@ void DropPolygons(void); void SaveDeadPolys(bool *sdp); void RestoreDeadPolys(bool *sdp); +void SavePolygonStuff(POLY_VOLATILE *sps); +void RestorePolygonStuff(POLY_VOLATILE *sps); /*-------------------------------------------------------------------------*/ @@ -110,7 +136,6 @@ 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 @@ -118,6 +143,21 @@ void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th);// ->oTagHandle void MaxPolygons(int maxPolys); +int GetTagPolyId(HPOLYGON hp); +void GetTagTag(HPOLYGON hp, SCNHANDLE *hTagText, int *tagX, int *tagY); +void SetPolyPointedTo(HPOLYGON hp, bool bPointedTo); +bool PolyIsPointedTo(HPOLYGON hp); +void SetPolyTagWanted(HPOLYGON hp, bool bTagWanted, bool bCursor, SCNHANDLE hOverrideTag); +bool PolyTagIsWanted(HPOLYGON hp); +bool PolyTagFollowsCursor(HPOLYGON hp); +SCNHANDLE GetPolyTagHandle(HPOLYGON hp); +bool IsTagPolygon(int tagno); +int GetTagPolyId(HPOLYGON hp); +void GetPolyMidBottom( HPOLYGON hp, int *pX, int *pY); +int PathCount(void); +void MovePolygon(PTYPE ptype, int id, int x, int y); +void MovePolygonTo(PTYPE ptype, int id, int x, int y); + /*-------------------------------------------------------------------------*/ } // end of namespace Tinsel diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp index a9b24bcac9..6590aed749 100644 --- a/engines/tinsel/rince.cpp +++ b/engines/tinsel/rince.cpp @@ -31,16 +31,20 @@ #include "tinsel/dw.h" #include "tinsel/film.h" #include "tinsel/handle.h" -#include "tinsel/inventory.h" +#include "tinsel/dialogs.h" +#include "tinsel/mareels.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/play.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/sched.h" +#include "tinsel/sysvar.h" #include "tinsel/timers.h" +#include "tinsel/tinsel.h" #include "tinsel/token.h" #include "common/util.h" @@ -49,8 +53,65 @@ namespace Tinsel { //----------------- LOCAL GLOBAL DATA -------------------- -static MACTOR Movers[MAX_MOVERS]; +static MOVER Movers[MAX_MOVERS]; +//----------------- FUNCTIONS ---------------------------- + +/** + * Called from ActorPalette(), normally once just after the beginning of time. + */ +void StoreMoverPalette(PMOVER pMover, int startColour, int length) { + pMover->startColour = startColour; + pMover->paletteLength = length; +} + +/** + * Called from the moving actor's main loop. + */ +static void CheckBrightness(PMOVER pMover) { + int brightness; + + if (pMover->hCpath == NOPOLY || pMover->bHidden) + return; + + brightness = GetBrightness(pMover->hCpath, pMover->objY); + + if (brightness != pMover->brightness) { + // Do it all immediately on first appearance, + // otherwise do it iteratively + + if (pMover->brightness == BOGUS_BRIGHTNESS) + pMover->brightness = brightness; // all the way + else if (brightness > pMover->brightness) + pMover->brightness++; // ramp up + else + pMover->brightness--; // ramp down + + DimPartPalette(BgPal(), + pMover->startColour, + pMover->paletteLength, + pMover->brightness); + } +} + +/** + * Called from ActorBrightness() Glitter call. + * Typically called before the moving actor is created + * at the start of a scene to cover a walk-in Play(). + */ +void MoverBrightness(PMOVER pMover, int brightness) { + // Note: Like with some of the Tinsel1 code, this routine original had a process yield + // if BgPal is NULL, and has been changed for ScummVM to a simple assert + + // This is changed from a ProcessGiveWay in DW2 to an assert in ScummVM + assert(BgPal()); + + // Do it all immediately + DimPartPalette(BgPal(), pMover->startColour, pMover->paletteLength, brightness); + + // The actor is probably hidden at this point, + pMover->brightness = brightness; +} /** * RebootMovers @@ -63,11 +124,11 @@ void RebootMovers(void) { * Given an actor number, return pointer to its moving actor structure, * if it is a moving actor. */ -PMACTOR GetMover(int ano) { +PMOVER GetMover(int ano) { int i; // Slot 0 is reserved for lead actor - if (ano == LeadId() || ano == LEAD_ACTOR) + if (ano == GetLeadId() || ano == LEAD_ACTOR) return &Movers[0]; for (i = 1; i < MAX_MOVERS; i++) @@ -80,13 +141,13 @@ PMACTOR GetMover(int ano) { /** * Register an actor as being a moving one. */ -PMACTOR SetMover(int ano) { +PMOVER RegisterMover(int ano) { int i; // Slot 0 is reserved for lead actor - if (ano == LeadId() || ano == LEAD_ACTOR) { + if (ano == GetLeadId() || ano == LEAD_ACTOR) { Movers[0].actorToken = TOKEN_LEAD; - Movers[0].actorID = LeadId(); + Movers[0].actorID = GetLeadId(); return &Movers[0]; } @@ -114,10 +175,10 @@ PMACTOR SetMover(int ano) { * * At the time of writing, used by the effect process. */ -PMACTOR GetLiveMover(int index) { +PMOVER GetLiveMover(int index) { assert(index >= 0 && index < MAX_MOVERS); // out of range - if (Movers[index].MActorState == NORM_MACTOR) + if (Movers[index].bActive) return &Movers[index]; else return NULL; @@ -126,33 +187,33 @@ PMACTOR GetLiveMover(int index) { bool IsMAinEffectPoly(int index) { assert(index >= 0 && index < MAX_MOVERS); // out of range - return Movers[index].InEffect; + return Movers[index].bInEffect; } -void SetMAinEffectPoly(int index, bool tf) { +void SetMoverInEffect(int index, bool tf) { assert(index >= 0 && index < MAX_MOVERS); // out of range - Movers[index].InEffect = tf; + Movers[index].bInEffect = 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(g_scheduler->getCurrentProcess() != pActor->pProc); - g_scheduler->killProcess(pActor->pProc); +void KillMover(PMOVER pMover) { + if (pMover->bActive) { + pMover->bActive = false; + MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pMover->actorObj); + pMover->actorObj = NULL; + assert(g_scheduler->getCurrentProcess() != pMover->pProc); + g_scheduler->killProcess(pMover->pProc); } } /** * getMActorState */ -MAS getMActorState(PMACTOR pActor) { - return pActor->MActorState; +bool getMActorState(PMOVER pActor) { + return pActor->bActive; } /** @@ -160,114 +221,237 @@ MAS getMActorState(PMACTOR pActor) { * 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 +void HideMover(PMOVER pMover, int sf) { + assert(pMover); // Hiding null moving actor + + pMover->bHidden = true; + + if (!TinselV2) { + // sf is only passed in Tinsel v1 + pMover->SlowFactor = sf; + } else { + // Tinsel 2 specific code + if (IsTaggedActor(pMover->actorID)) { + // It may be pointed to + SetActorPointedTo(pMover->actorID, false); + SetActorTagWanted(pMover->actorID, false, false, 0); + } + } - pActor->aHidden = true; - pActor->SlowFactor = sf; + if (pMover->actorObj) + MultiSetZPosition(pMover->actorObj, -1); +} - if (pActor->actorObj) - MultiSetZPosition(pActor->actorObj, -1); +/** + * MoverHidden + */ +bool MoverHidden(PMOVER pMover) { + if (pMover) + return pMover->bHidden; + else + return false; } /** - * getMActorHideState + * To be or not to be? If it be, then it is. */ -bool getMActorHideState(PMACTOR pActor) { - if (pActor) - return pActor->aHidden; +bool MoverIs(PMOVER pMover) { + if (TinselV2) + return pMover->actorObj ? true : false; else + return getMActorState(pMover); +} + +/** + * To be SWalk()ing or not to be SWalk()ing? + */ +bool MoverIsSWalking(PMOVER pMover) { + return (MoverMoving(pMover) && pMover->bIgPath); +} + +/** + * MoverMoving() + */ +bool MoverMoving(PMOVER pMover) { + if (!TinselV2) + return pMover->bMoving; + + if (pMover->UtargetX == -1 && pMover->UtargetY == -1) return false; + else + return true; } /** - * unhideMActor + * Return an actor's walk ticket. */ -void unhideMActor(PMACTOR pActor) { - assert(pActor); // unHiding null moving actor +int GetWalkNumber(PMOVER pMover) { + return pMover->walkNumber; +} - pActor->aHidden = false; +/** + * GetMoverId + */ +int GetMoverId(PMOVER pMover) { + return pMover->actorID; +} - // 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())); +/** + * Sets the mover Z position + */ +void SetMoverZ(PMOVER pMover, int y, uint32 zFactor) { + if (!pMover->bHidden) { + if (MoverIsSWalking(pMover) && pMover->zOverride != -1) { + // Special for SWalk() + MultiSetZPosition(pMover->actorObj, (pMover->zOverride << ZSHIFT) + y); + } else { + // Normal case + MultiSetZPosition(pMover->actorObj, (zFactor << ZSHIFT) + y); + } } } +void SetMoverZoverride(PMOVER pMover, uint32 zFactor) { + pMover->zOverride = zFactor; +} + +/** + * UnHideMover + */ +void UnHideMover(PMOVER pMover) { + assert(pMover); // unHiding null moving actor + + if (!TinselV2 || pMover->bHidden) { + pMover->bHidden = false; + + // Make visible on the screen + if (pMover->actorObj) { + // If no path, just use first path in the scene + if (pMover->hCpath != NOPOLY) + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath)); + else + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly())); + } + } +} + +/** + * Clear everything out at actor start-up time. + */ +static void InitMover(PMOVER pMover) { + pMover->bActive = false; + pMover->actorObj = NULL; + pMover->objX = pMover->objY = 0; + + pMover->hRpath = NOPOLY; + + pMover->targetX = pMover->targetY = -1; + pMover->ItargetX = pMover->ItargetY = -1; + pMover->hIpath = NOPOLY; + pMover->UtargetX = pMover->UtargetY = -1; + pMover->hUpath = NOPOLY; + pMover->hCpath = NOPOLY; + + pMover->over = false; + pMover->InDifficulty = NO_PROB; + + pMover->hFnpath = NOPOLY; + pMover->npstatus = NOT_IN; + pMover->line = 0; + + pMover->Tline = 0; + + if(pMover->direction != FORWARD || pMover->direction != AWAY + || pMover->direction != LEFTREEL || pMover->direction != RIGHTREEL) + pMover->direction = FORWARD; + + if(pMover->scale < 0 || pMover->scale > TOTAL_SCALES) + pMover->scale = 1; + + pMover->brightness = BOGUS_BRIGHTNESS; // Force initial setup + + pMover->bNoPath = false; + pMover->bIgPath = false; + pMover->bHidden = false; // 20/2/95 + pMover->bStop = false; + + pMover->walkNumber= 0; + pMover->stepCount = 0; + + pMover->bWalkReel = false; + pMover->bSpecReel = false; + pMover->hLastFilm = 0; + pMover->hPushedFilm = 0; + + pMover->bInEffect = false; + + pMover->walkedFromX = pMover->walkedFromY = 0; +} + /** * 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 - } +void DropMovers(void) { + for (int i = 0; i < MAX_MOVERS; i++) + InitMover(&Movers[i]); } /** * Reposition a moving actor. */ -void MoveMActor(PMACTOR pActor, int x, int y) { +void PositionMover(PMOVER pMover, int x, int y) { int z; int node; HPOLYGON hPath; - assert(pActor); // Moving null moving actor - assert(pActor->actorObj); + assert(pMover); // Moving null moving actor + assert(pMover->actorObj); - pActor->objx = x; - pActor->objy = y; - MultiSetAniXY(pActor->actorObj, x, y); + pMover->objX = x; + pMover->objY = y; + MultiSetAniXY(pMover->actorObj, x, y); hPath = InPolygon(x, y, PATH); if (hPath != NOPOLY) { - pActor->hCpath = hPath; + pMover->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; + getNpathNode(hPath, node, &pMover->objX, &pMover->objY); + pMover->hFnpath = hPath; + pMover->line = node; + pMover->npstatus = GOING_UP; } else { - pActor->hFnpath = NOPOLY; - pActor->npstatus = NOT_IN; + pMover->hFnpath = NOPOLY; + pMover->npstatus = NOT_IN; } - z = GetScale(hPath, pActor->objy); - pActor->scale = z; - SetMActorStanding(pActor); + z = GetScale(hPath, pMover->objY); + pMover->scale = z; + SetMoverStanding(pMover); } else { - pActor->bNoPath = true; + pMover->bNoPath = true; - pActor->hFnpath = NOPOLY; // Ain't in one - pActor->npstatus = NOT_IN; + pMover->hFnpath = NOPOLY; // Ain't in one + pMover->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; + if (pMover->direction < 0 || pMover->direction > 3) + pMover->direction = FORWARD; + if (pMover->scale < 0 || pMover->scale > TOTAL_SCALES) + pMover->scale = 1; } } /** * Get position of a moving actor. */ -void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) { - assert(pActor); // Getting null moving actor's position +void GetMoverPosition(PMOVER pMover, int *paniX, int *paniY) { + assert(pMover); // Getting null moving actor's position - if (pActor->actorObj != NULL) - GetAniPosition(pActor->actorObj, paniX, paniY); + if (pMover->actorObj != NULL) + GetAniPosition(pMover->actorObj, paniX, paniY); else { *paniX = 0; *paniY = 0; @@ -277,43 +461,63 @@ void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) { /** * 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 +void GetMoverMidTop(PMOVER pMover, int *aniX, int *aniY) { + assert(pMover); // Getting null moving actor's mid-top position + assert(pMover->actorObj); // Getting null moving actor's mid-top position - *aniX = (MultiLeftmost(pActor->actorObj) + MultiRightmost(pActor->actorObj))/2; - *aniY = MultiHighest(pActor->actorObj); + *aniX = (MultiLeftmost(pMover->actorObj) + MultiRightmost(pMover->actorObj)) / 2; + *aniY = MultiHighest(pMover->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 +int GetMoverLeft(PMOVER pMover) { + assert(pMover); // Getting null moving actor's leftmost position + assert(pMover->actorObj); // Getting null moving actor's leftmost position - return MultiLeftmost(pActor->actorObj); + return MultiLeftmost(pMover->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 +int GetMoverRight(PMOVER pMover) { + assert(pMover); // Getting null moving actor's rightmost position + assert(pMover->actorObj); // Getting null moving actor's rightmost position + + return MultiRightmost(pMover->actorObj); +} + +/** + * Moving actor's top co-ordinate. + */ +int GetMoverTop(PMOVER pMover) { + assert(pMover); // Getting null moving actor's topmost position + assert(pMover->actorObj); // Getting null moving actor's topmost position - return MultiRightmost(pActor->actorObj); + return MultiHighest(pMover->actorObj); +} + +/** + * Moving actor's bottom co-ordinate. + */ +int GetMoverBottom(PMOVER pMover) { + assert(pMover); // Getting null moving actor's bottommost position + assert(pMover->actorObj); // Getting null moving actor's bottommost position + + return MultiLowest(pMover->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 +bool MoverIsInPolygon(PMOVER pMover, HPOLYGON hp) { + assert(pMover); // Checking if null moving actor is in polygon + assert(pMover->actorObj); // Checking if null moving actor is in polygon int aniX, aniY; - GetAniPosition(pActor->actorObj, &aniX, &aniY); + GetAniPosition(pMover->actorObj, &aniX, &aniY); return IsInPolygon(aniX, aniY, hp); } @@ -321,157 +525,147 @@ bool MActorIsInPolygon(PMACTOR pActor, HPOLYGON hp) { /** * Change which reel is playing for a moving actor. */ -void AlterMActor(PMACTOR pActor, SCNHANDLE film, AR_FUNCTION fn) { +void AlterMover(PMOVER pMover, SCNHANDLE film, AR_FUNCTION fn) { const FILM *pfilm; - assert(pActor->actorObj); // Altering null moving actor's animation script + assert(pMover->actorObj); // Altering null moving actor's animation script if (fn == AR_POPREEL) { - film = pActor->pushedfilm; // Use the saved film + // Use the saved film + film = pMover->hPushedFilm; } if (fn == AR_PUSHREEL) { // Save the one we're replacing - pActor->pushedfilm = (pActor->TagReelRunning) ? pActor->lastfilm : 0; + pMover->hPushedFilm = (pMover->bSpecReel) ? pMover->hLastFilm : 0; } if (film == 0) { - if (pActor->TagReelRunning) { + if (pMover->bSpecReel) { // Revert to 'normal' actor - SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); - pActor->TagReelRunning = false; + SetMoverWalkReel(pMover, pMover->direction, pMover->scale, true); + pMover->bSpecReel = false; } } else { - pActor->lastfilm = film; // Remember this one + // Remember this one in case the actor talks + pMover->hLastFilm = film; 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; + InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + if (!TinselV2) + pMover->stepCount = 0; // If no path, just use first path in the scene - if (pActor->hCpath != NOPOLY) - MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + if (pMover->hCpath != NOPOLY) + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath)); else - MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly())); if (fn == AR_WALKREEL) { - pActor->TagReelRunning = false; - pActor->walkReel = true; + pMover->bSpecReel = false; + pMover->bWalkReel = true; } else { - pActor->TagReelRunning = true; - pActor->walkReel = false; + pMover->bSpecReel = true; + pMover->bWalkReel = false; #ifdef DEBUG - assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! + assert(StepAnimScript(&pMover->actorAnim) != ScriptFinished); // Actor reel has finished! #else - StepAnimScript(&pActor->actorAnim); // 04/01/95 + StepAnimScript(&pMover->actorAnim); // 04/01/95 #endif } // Hang on, we may not want him yet! 04/01/95 - if (pActor->aHidden) - MultiSetZPosition(pActor->actorObj, -1); + if (pMover->bHidden) + MultiSetZPosition(pMover->actorObj, -1); } } /** * Return the actor's direction. */ -DIRREEL GetMActorDirection(PMACTOR pActor) { - return pActor->dirn; +DIRECTION GetMoverDirection(PMOVER pMover) { + return pMover->direction; } /** * Return the actor's scale. */ -int GetMActorScale(PMACTOR pActor) { - return pActor->scale; +int GetMoverScale(PMOVER pMover) { + return pMover->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; +void SetMoverDirection(PMOVER pMover, DIRECTION dirn) { + pMover->direction = dirn; } /** * 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); +void SetMoverStanding(PMOVER pMover) { + assert(pMover->actorObj); + AlterMover(pMover, pMover->standReels[pMover->scale - 1][pMover->direction], AR_NORMAL); } /** * Get actor to adopt its appropriate walking reel. */ -void SetMActorWalkReel(PMACTOR pActor, DIRREEL reel, int scale, bool force) { +void SetMoverWalkReel(PMOVER pMover, DIRECTION 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); + storeActorReel(pMover->actorID, NULL, 0, NULL, 0, 0, 0); + UnHideMover(pMover); // Don't do it if using a special walk reel - if (pActor->walkReel) + if (pMover->bWalkReel) return; - if (force || pActor->scale != scale || pActor->dirn != reel) { + if (force || pMover->scale != scale || pMover->direction != 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) { + if (pMover->scale != scale + && scale <= NUM_MAINSCALES && pMover->scale <= NUM_MAINSCALES + && (whichReel = ScalingReel(pMover->actorID, pMover->scale, scale, reel)) != 0) { // error("Cripes!"); ; // Use what is now in 'whichReel' } else { - whichReel = pActor->WalkReels[scale-1][reel]; + whichReel = pMover->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); + InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, FROM_LE_32(pfilm->reels[0].script), 1); // Synchronised walking reels - SkipFrames(&pActor->actorAnim, pActor->scount); + assert(pMover->stepCount >= 0); + SkipFrames(&pMover->actorAnim, pMover->stepCount); - pActor->scale = scale; - pActor->dirn = reel; + pMover->scale = scale; + pMover->direction = reel; } } /** * Sort some stuff out at actor start-up time. */ -static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) { +static void InitialPathChecks(PMOVER pMover, int xpos, int ypos) { HPOLYGON hPath; int node; int z; - pActor->objx = xpos; - pActor->objy = ypos; + pMover->objX = xpos; + pMover->objY = ypos; /*-------------------------------------- | If Actor is in a follow nodes path, | @@ -480,87 +674,38 @@ static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) { hPath = InPolygon(xpos, ypos, PATH); if (hPath != NOPOLY) { - pActor->hCpath = hPath; + pMover->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; + getNpathNode(hPath, node, &pMover->objX, &pMover->objY); + pMover->hFnpath = hPath; + pMover->line = node; + pMover->npstatus = GOING_UP; } - z = GetScale(hPath, pActor->objy); + z = GetScale(hPath, pMover->objY); } else { - pActor->bNoPath = true; + pMover->bNoPath = true; - z = GetScale(FirstPathPoly(), pActor->objy); + z = GetScale(FirstPathPoly(), pMover->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 + SetMoverWalkReel(pMover, FORWARD, z, false); } -static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) { +static void MoverProcessHelper(int X, int Y, int id, PMOVER pMover) { const FILM *pfilm; const MULTI_INIT *pmi; const FRAME *pFrame; IMAGE *pim; - assert(BackPal()); // Can't start actor without a background palette - assert(pActor->WalkReels[0][FORWARD]); // Starting actor process without walk reels + assert(BgPal()); // Can't start actor without a background palette + assert(pMover->walkReels[0][FORWARD]); // Starting actor process without walk reels - InitMActor(pActor); - InitialPathChecks(pActor, X, Y); + InitMover(pMover); + InitialPathChecks(pMover, X, Y); - pfilm = (const FILM *)LockMem(pActor->WalkReels[0][FORWARD]); + pfilm = (const FILM *)LockMem(pMover->walkReels[0][FORWARD]); pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfilm->reels[0].mobj)); //--- @@ -568,55 +713,55 @@ static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) { // get pointer to image pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image - pim->hImgPal = TO_LE_32(BackPal()); + pim->hImgPal = TO_LE_32(BgPal()); //--- - pActor->actorObj = MultiInitObject(pmi); + pMover->actorObj = MultiInitObject(pmi); -/**/ assert(pActor->actorID == id); - pActor->actorID = id; +/**/ assert(pMover->actorID == id); + pMover->actorID = id; // add it to display list - MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj); - storeActorReel(id, NULL, 0, pActor->actorObj, 0, 0, 0); + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pMover->actorObj); + storeActorReel(id, NULL, 0, pMover->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; + InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + pMover->stepCount = 0; - MultiSetAniXY(pActor->actorObj, pActor->objx, pActor->objy); + MultiSetAniXY(pMover->actorObj, pMover->objX, pMover->objY); // If no path, just use first path in the scene - if (pActor->hCpath != NOPOLY) - MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + if (pMover->hCpath != NOPOLY) + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath)); else - MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly())); // Make him the right size - SetMActorStanding(pActor); + SetMoverStanding(pMover); //**** 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 + HideMover(pMover, 0); // Allows a play to come in before this appears + pMover->bHidden = false; // ...but don't stay hidden } - pActor->MActorState = NORM_MACTOR; + pMover->bActive = true; } /** * Moving actor process - 1 per moving actor in current scene. */ -void MActorProcess(CORO_PARAM, const void *param) { +void T1MoverProcess(CORO_PARAM, const void *param) { // COROUTINE CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); - PMACTOR pActor = *(PMACTOR *)param; + const PMOVER pActor = *(const PMOVER *)param; CORO_BEGIN_CODE(_ctx); while (1) { - if (pActor->TagReelRunning) { - if (!pActor->aHidden) + if (pActor->bSpecReel) { + if (!pActor->bHidden) #ifdef DEBUG assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! #else @@ -632,34 +777,118 @@ void MActorProcess(CORO_PARAM, const void *param) { CORO_END_CODE; } -void MActorProcessCreate(int X, int Y, int id, PMACTOR pActor) { - MActorProcessHelper(X, Y, id, pActor); - pActor->pProc = g_scheduler->createProcess(PID_MACTOR, MActorProcess, &pActor, sizeof(PMACTOR)); +/** + * Tinsel 2 Moving actor process + * - 1 per moving actor in current scene. + */ +void T2MoverProcess(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + // Get the co-ordinates - copied to process when it was created + const MAINIT *rpos = (const MAINIT *)param; + PMOVER pMover = rpos->pMover; + int i; + FILM *pFilm; + PMULTI_INIT pmi; + + CORO_BEGIN_CODE(_ctx); + + for (i = 0; i < TOTAL_SCALES; i++) { + if (pMover->walkReels[i][FORWARD]) + break; + } + assert(i < TOTAL_SCALES); + + InitMover(pMover); + InitialPathChecks(pMover, rpos->X, rpos->Y); + + pFilm = (FILM *)LockMem(pMover->walkReels[i][FORWARD]); // Any old reel + pmi = (PMULTI_INIT)LockMem(pFilm->reels[0].mobj); + + // Poke in the background palette + PokeInPalette(pmi); + + pMover->actorObj = MultiInitObject(pmi); + pMover->actorID = pMover->actorID; + pMover->bActive = true; + + // add it to display list + MultiInsertObject( GetPlayfieldList(FIELD_WORLD), pMover->actorObj ); + + InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, pFilm->reels[0].script, ONE_SECOND/pFilm->frate); + pMover->stepCount = 0; + + MultiSetAniXY(pMover->actorObj, pMover->objX, pMover->objY); + + // If no path, just use first path in the scene + if (pMover->hCpath != NOPOLY) + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath)); + else + SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly())); + + // Make him the right size + SetMoverStanding(pMover); + + HideMover(pMover); // Allows a play to come in before this appears + pMover->bHidden = false; // ...but don't stay hidden + + for (;;) { + if (pMover->bSpecReel) { + if (!pMover->bHidden) + StepAnimScript(&pMover->actorAnim); + } else + DoMoveActor(pMover); + + CheckBrightness(pMover); + + CORO_SLEEP(1); + } + + CORO_END_CODE; } +/** + * Creates a handling process for a moving actor + */ +void MoverProcessCreate(int X, int Y, int id, PMOVER pMover) { + if (TinselV2) { + static MAINIT iStruct; + iStruct.X = X; + iStruct.Y = Y; + iStruct.pMover = pMover; + + g_scheduler->createProcess(PID_MOVER, T2MoverProcess, &iStruct, sizeof(MAINIT)); + } else { + MoverProcessHelper(X, Y, id, pMover); + pMover->pProc = g_scheduler->createProcess(PID_MOVER, T1MoverProcess, &pMover, sizeof(PMOVER)); + } +} /** * Check for moving actor collision. */ -PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) { +PMOVER InMoverBlock(PMOVER pMover, 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) + caX = pMover->objX; + if (pMover->hFnpath != NOPOLY || GetNoBlocking()) return NULL; - caL = GetMActorLeft(pActor) + x - caX; - caR = GetMActorRight(pActor) + x - caX; + caL = GetMoverLeft(pMover) + x - caX; + caR = GetMoverRight(pMover) + x - caX; for (int i = 0; i < MAX_MOVERS; i++) { - if (pActor == &Movers[i] || Movers[i].MActorState == NO_MACTOR) + if (pMover == &Movers[i] || + (TinselV2 && (Movers[i].actorObj == NULL)) || + (!TinselV2 && !Movers[i].bActive)) continue; // At around the same height? - GetMActorPosition(&Movers[i], &taX, &taY); + GetMoverPosition(&Movers[i], &taX, &taY); if (Movers[i].hFnpath != NOPOLY) continue; @@ -667,12 +896,12 @@ PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) { continue; // To the left? - taL = GetMActorLeft(&Movers[i]); + taL = GetMoverLeft(&Movers[i]); if (caR <= taL) continue; // To the right? - taR = GetMActorRight(&Movers[i]); + taR = GetMoverRight(&Movers[i]); if (caL >= taR) continue; @@ -686,24 +915,54 @@ PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) { */ void SaveMovers(SAVED_MOVER *sMoverInfo) { for (int i = 0; i < MAX_MOVERS; i++) { - sMoverInfo[i].MActorState= Movers[i].MActorState; + sMoverInfo[i].bActive = !TinselV2 ? Movers[i].bActive : Movers[i].actorObj != NULL; sMoverInfo[i].actorID = Movers[i].actorID; - sMoverInfo[i].objx = Movers[i].objx; - sMoverInfo[i].objy = Movers[i].objy; - sMoverInfo[i].lastfilm = Movers[i].lastfilm; + sMoverInfo[i].objX = Movers[i].objX; + sMoverInfo[i].objY = Movers[i].objY; + sMoverInfo[i].hLastfilm = Movers[i].hLastFilm; + + if (TinselV2) { + sMoverInfo[i].bHidden = Movers[i].bHidden; + sMoverInfo[i].brightness = Movers[i].brightness; + sMoverInfo[i].startColour = Movers[i].startColour; + sMoverInfo[i].paletteLength = Movers[i].paletteLength; + } - 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)); + 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)); + if (TinselV2) + Movers[i].actorID = sMoverInfo[i].actorID; + + 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)); } } + +PMOVER NextMover(PMOVER pMover) { + int next; + + if (pMover == NULL) + next = 0; + else + next = pMover - Movers + 1; + + if (Movers[next].actorID) + return &Movers[next]; + else + return NULL; +} + +void StopMover(PMOVER pMover) { + pMover->bStop = true; + DoMoveActor(pMover); +} + } // end of namespace Tinsel diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h index 7ccf017c65..4a60fdca07 100644 --- a/engines/tinsel/rince.h +++ b/engines/tinsel/rince.h @@ -29,6 +29,7 @@ #include "tinsel/anim.h" // for ANIM #include "tinsel/scene.h" // for TFTYPE +#include "tinsel/tinsel.h" namespace Tinsel { @@ -39,31 +40,30 @@ 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 DIRECTION { LEFTREEL, RIGHTREEL, FORWARD, AWAY }; -enum DIRREEL{ LEFTREEL, RIGHTREEL, FORWARD, AWAY }; +#define NUM_MAINSCALES (TinselV2 ? 10 : 5) +#define NUM_AUXSCALES 5 +#define TOTAL_SCALES (NUM_MAINSCALES + NUM_AUXSCALES) +#define REQ_MAIN_SCALES 10 +#define REQ_TOTAL_SCALES 15 -enum { - NUM_MAINSCALES = 5, - NUM_AUXSCALES = 5, - TOTAL_SCALES = NUM_MAINSCALES + NUM_AUXSCALES -}; +#define BOGUS_BRIGHTNESS -1 -struct MACTOR { +struct MOVER { - int objx; /* Co-ordinates object */ - int objy; + int objX, objY; /* Co-ordinates object */ int targetX, targetY; int ItargetX, ItargetY; /* Intermediate destination */ - HPOLYGON hIpath; int UtargetX, UtargetY; /* Ultimate destination */ - HPOLYGON hUpath; + HPOLYGON hIpath; + HPOLYGON hUpath; HPOLYGON hCpath; bool over; - int ticket; + int walkNumber; IND InDifficulty; @@ -74,128 +74,146 @@ struct MACTOR { int Tline; // NEW - bool TagReelRunning; - + // TODO: TagReelRunning may be the same as bSpecReel + bool bSpecReel; /* Used internally */ - DIRREEL dirn; // Current reel - int scale; // Current scale - int scount; // Step count for walking reel synchronisation + DIRECTION direction; // Current reel + int scale; // Current scale + + int stepCount; // Step count for walking reel synchronisation - unsigned fromx; - unsigned fromy; + int walkedFromX, walkedFromY; bool bMoving; // Set this to TRUE during a walk bool bNoPath; bool bIgPath; - bool walkReel; + bool bWalkReel; OBJECT *actorObj; // Actor's object ANIM actorAnim; // Actor's animation script - SCNHANDLE lastfilm; // } Used by AlterActor() - SCNHANDLE pushedfilm; // } + SCNHANDLE hLastFilm; // } Used by AlterMover() + SCNHANDLE hPushedFilm; // } int actorID; int actorToken; - SCNHANDLE WalkReels[TOTAL_SCALES][4]; - SCNHANDLE StandReels[TOTAL_SCALES][4]; - SCNHANDLE TalkReels[TOTAL_SCALES][4]; + SCNHANDLE walkReels[REQ_TOTAL_SCALES][4]; + SCNHANDLE standReels[REQ_TOTAL_SCALES][4]; + SCNHANDLE talkReels[REQ_TOTAL_SCALES][4]; - MAS MActorState; + bool bActive; - bool aHidden; int SlowFactor; // Slow down movement while hidden - bool stop; + bool bStop; /* NOTE: If effect polys can overlap, this needs improving */ - bool InEffect; + bool bInEffect; PROCESS *pProc; + + // Discworld 2 specific fields + int32 zOverride; + bool bHidden; + int brightness; // Current brightness + int startColour; + int paletteLength; + HPOLYGON hRpath; // Recent path +}; +typedef MOVER *PMOVER; + +struct MAINIT { + int X; + int Y; + PMOVER pMover; }; -typedef MACTOR *PMACTOR; +typedef MAINIT *PMAINIT; //--------------------------------------------------------------------------- -void MActorProcessCreate(int X, int Y, int id, MACTOR *pActor); +void MoverProcessCreate(int X, int Y, int id, PMOVER pMover); 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 StoreMoverPalette(PMOVER pMover, int startColour, int length); + +void MoverBrightness(PMOVER pMover, int brightness); + +MOVER *GetMover(int ano); +MOVER *RegisterMover(int ano); +void KillMover(PMOVER pMover); +MOVER *GetLiveMover(int index); + +bool getMActorState(MOVER *psActor); +int GetMoverId(PMOVER pMover); +void SetMoverZ(PMOVER pMover, int y, uint32 zFactor); +void SetMoverZoverride(PMOVER pMover, uint32 zFactor); + +void HideMover(PMOVER pMover, int sf = 0); +bool MoverHidden(PMOVER pMover); +bool MoverIs(PMOVER pMover); +bool MoverIsSWalking(PMOVER pMover); +bool MoverMoving(PMOVER pMover); +int GetWalkNumber(PMOVER pMover); +void UnHideMover(PMOVER pMover); +void DropMovers(void); +void PositionMover(PMOVER pMover, int x, int y); + +void GetMoverPosition(PMOVER pMover, int *aniX, int *aniY); +void GetMoverMidTop(PMOVER pMover, int *aniX, int *aniY); +int GetMoverLeft(PMOVER pMover); +int GetMoverRight(PMOVER pMover); +int GetMoverTop(PMOVER pMover); +int GetMoverBottom(PMOVER pMover); + +bool MoverIsInPolygon(PMOVER pMover, HPOLYGON hPoly); +void AlterMover(PMOVER pMover, SCNHANDLE film, AR_FUNCTION fn); +DIRECTION GetMoverDirection(PMOVER pMover); +int GetMoverScale(PMOVER pMover); +void SetMoverDirection(PMOVER pMover, DIRECTION dirn); +void SetMoverStanding(PMOVER pMover); +void SetMoverWalkReel(PMOVER pMover, DIRECTION reel, int scale, bool force); + +PMOVER InMoverBlock(PMOVER pMover, int x, int y); void RebootMovers(void); bool IsMAinEffectPoly(int index); -void SetMAinEffectPoly(int index, bool tf); - -bool MAmoving(MACTOR *pActor); +void SetMoverInEffect(int index, bool tf); -int GetActorTicket(MACTOR *pActor); +void StopMover(PMOVER pMover); /*----------------------------------------------------------------------*/ struct SAVED_MOVER { - MAS MActorState; - int actorID; - int objx; - int objy; - SCNHANDLE lastfilm; + int actorID; + int objX; + int objY; + SCNHANDLE hLastfilm; - SCNHANDLE WalkReels[TOTAL_SCALES][4]; - SCNHANDLE StandReels[TOTAL_SCALES][4]; - SCNHANDLE TalkReels[TOTAL_SCALES][4]; + SCNHANDLE walkReels[REQ_TOTAL_SCALES][4]; + SCNHANDLE standReels[REQ_TOTAL_SCALES][4]; + SCNHANDLE talkReels[REQ_TOTAL_SCALES][4]; + bool bActive; + bool bHidden; + int brightness; + int startColour; + int paletteLength; }; void SaveMovers(SAVED_MOVER *sMoverInfo); void RestoreAuxScales(SAVED_MOVER *sMoverInfo); -/*----------------------------------------------------------------------*/ +PMOVER NextMover(PMOVER pMover); -/* -* 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, diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp index 73a3faf602..3d532300c2 100644 --- a/engines/tinsel/saveload.cpp +++ b/engines/tinsel/saveload.cpp @@ -25,8 +25,9 @@ */ #include "tinsel/actors.h" +#include "tinsel/dialogs.h" +#include "tinsel/drives.h" #include "tinsel/dw.h" -#include "tinsel/inventory.h" #include "tinsel/rince.h" #include "tinsel/savescn.h" #include "tinsel/serializer.h" @@ -63,7 +64,11 @@ namespace Tinsel { #define VER(x) x +//----------------- GLOBAL GLOBAL DATA -------------------- +int thingHeld = 0; +int restoreCD = 0; +SRSTATE SRstate = SR_IDLE; //----------------- EXTERN FUNCTIONS -------------------- @@ -79,9 +84,6 @@ 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 { @@ -93,15 +95,17 @@ struct SaveGameHeader { }; enum { - SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld ScummVM" + DW1_SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld 1 ScummVM" + DW2_SAVEGAME_ID = 0x44573253, // = 'DW2S' = "DiscWOrld 2 ScummVM" SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7 }; +#define SAVEGAME_ID (TinselV2 ? (uint32)DW2_SAVEGAME_ID : (uint32)DW1_SAVEGAME_ID) //----------------- LOCAL GLOBAL DATA -------------------- static int numSfiles = 0; -static SFILES savedFiles[MAX_SFILES]; +static SFILES savedFiles[MAX_SAVED_FILES]; static bool NeedLoad = true; @@ -112,8 +116,6 @@ 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) { @@ -153,29 +155,38 @@ static bool syncSaveGameHeader(Serializer &s, SaveGameHeader &hdr) { } static void syncSavedMover(Serializer &s, SAVED_MOVER &sm) { - SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.WalkReels, (SCNHANDLE *)&sm.StandReels, (SCNHANDLE *)&sm.TalkReels }; + SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.walkReels, + (SCNHANDLE *)&sm.standReels, (SCNHANDLE *)&sm.talkReels }; - s.syncAsUint32LE(sm.MActorState); + s.syncAsUint32LE(sm.bActive); s.syncAsSint32LE(sm.actorID); - s.syncAsSint32LE(sm.objx); - s.syncAsSint32LE(sm.objy); - s.syncAsUint32LE(sm.lastfilm); + s.syncAsSint32LE(sm.objX); + s.syncAsSint32LE(sm.objY); + s.syncAsUint32LE(sm.hLastfilm); for (int pIndex = 0; pIndex < 3; ++pIndex) { SCNHANDLE *p = pList[pIndex]; for (int i = 0; i < TOTAL_SCALES * 4; ++i) s.syncAsUint32LE(*p++); } + + if (TinselV2) { + s.syncAsByte(sm.bHidden); + + s.syncAsSint32LE(sm.brightness); + s.syncAsSint32LE(sm.startColour); + s.syncAsSint32LE(sm.paletteLength); + } } static void syncSavedActor(Serializer &s, SAVED_ACTOR &sa) { s.syncAsUint16LE(sa.actorID); - s.syncAsUint16LE(sa.z); + s.syncAsUint16LE(sa.zFactor); s.syncAsUint32LE(sa.bAlive); s.syncAsUint32LE(sa.presFilm); s.syncAsUint16LE(sa.presRnum); - s.syncAsUint16LE(sa.presX); - s.syncAsUint16LE(sa.presY); + s.syncAsUint16LE(sa.presPlayX); + s.syncAsUint16LE(sa.presPlayY); } extern void syncAllActorsAlive(Serializer &s); @@ -186,6 +197,24 @@ static void syncNoScrollB(Serializer &s, NOSCROLLB &ns) { s.syncAsSint32LE(ns.c2); } +static void syncZPosition(Serializer &s, Z_POSITIONS &zp) { + s.syncAsSint16LE(zp.actor); + s.syncAsSint16LE(zp.column); + s.syncAsSint32LE(zp.z); +} + +static void syncPolyVolatile(Serializer &s, POLY_VOLATILE &p) { + s.syncAsByte(p.bDead); + s.syncAsSint16LE(p.xoff); + s.syncAsSint16LE(p.yoff); +} + +static void syncSoundReel(Serializer &s, SOUNDREELS &sr) { + s.syncAsUint32LE(sr.hFilm); + s.syncAsSint32LE(sr.column); + s.syncAsSint32LE(sr.actorCol); +} + static void syncSavedData(Serializer &s, SAVED_DATA &sd) { s.syncAsUint32LE(sd.SavedSceneHandle); s.syncAsUint32LE(sd.SavedBgroundHandle); @@ -197,7 +226,7 @@ static void syncSavedData(Serializer &s, SAVED_DATA &sd) { s.syncAsSint32LE(sd.NumSavedActors); s.syncAsSint32LE(sd.SavedLoffset); s.syncAsSint32LE(sd.SavedToffset); - for (int i = 0; i < MAX_INTERPRET; ++i) + for (int i = 0; i < NUM_INTERPRET; ++i) sd.SavedICInfo[i].syncWithSerializer(s); for (int i = 0; i < MAX_POLY; ++i) s.syncAsUint32LE(sd.SavedDeadPolys[i]); @@ -213,6 +242,32 @@ static void syncSavedData(Serializer &s, SAVED_DATA &sd) { syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]); s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV); s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH); + + // Tinsel 2 fields + if (TinselV2) { + // SavedNoScrollData + s.syncAsUint32LE(sd.SavedNoScrollData.xTrigger); + s.syncAsUint32LE(sd.SavedNoScrollData.xDistance); + s.syncAsUint32LE(sd.SavedNoScrollData.xSpeed); + s.syncAsUint32LE(sd.SavedNoScrollData.yTriggerTop); + s.syncAsUint32LE(sd.SavedNoScrollData.yTriggerBottom); + s.syncAsUint32LE(sd.SavedNoScrollData.yDistance); + s.syncAsUint32LE(sd.SavedNoScrollData.ySpeed); + + for (int i = 0; i < NUM_ZPOSITIONS; ++i) + syncZPosition(s, sd.zPositions[i]); + s.syncBytes(sd.savedActorZ, MAX_SAVED_ACTOR_Z); + for (int i = 0; i < MAX_POLY; ++i) + syncPolyVolatile(s, sd.SavedPolygonStuff[i]); + for (int i = 0; i < 3; ++i) + s.syncAsUint32LE(sd.SavedTune[i]); + s.syncAsByte(sd.bTinselDim); + s.syncAsSint32LE(sd.SavedScrollFocus); + for (int i = 0; i < SV_TOPVALID; ++i) + s.syncAsSint32LE(sd.SavedSystemVars[i]); + for (int i = 0; i < MAX_SOUNDREELS; ++i) + syncSoundReel(s, sd.SavedSoundReels[i]); + } } @@ -247,6 +302,11 @@ static char *NewName(void) { * the number of files found). */ int getList(Common::SaveFileManager *saveFileMan, const Common::String &target) { + // 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 = target + ".???"; @@ -255,7 +315,7 @@ int getList(Common::SaveFileManager *saveFileMan, const Common::String &target) numSfiles = 0; for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) { - if (numSfiles >= MAX_SFILES) + if (numSfiles >= MAX_SAVED_FILES) break; const Common::String &fname = *file; @@ -304,11 +364,10 @@ int getList(void) { // TODO/FIXME: Just always reload this data? Be careful about slow downs!!! if (!NeedLoad) return numSfiles; - + return getList(_vm->getSaveFileMan(), _vm->getTargetName()); } - char *ListEntry(int i, letype which) { if (i == -1) i = numSfiles; @@ -322,7 +381,16 @@ char *ListEntry(int i, letype which) { } static void DoSync(Serializer &s) { - int sg = 0; + int sg; + + if (TinselV2) { + if (s.isSaving()) + restoreCD = GetCurrentCD(); + s.syncAsSint16LE(restoreCD); + } + + if (TinselV2 && s.isLoading()) + HoldItem(INV_NOICON); syncSavedData(s, *srsd); syncGlobInfo(s); // Glitter globals @@ -332,11 +400,16 @@ static void DoSync(Serializer &s) { if (s.isSaving()) sg = WhichItemHeld(); s.syncAsSint32LE(sg); - if (s.isLoading()) - HoldItem(sg); + if (s.isLoading()) { + if (TinselV2) + thingHeld = sg; + else + HoldItem(sg); + } syncTimerInfo(s); // Timer data - syncPolyInfo(s); // Dead polygon data + if (!TinselV2) + syncPolyInfo(s); // Dead polygon data syncSCdata(s); // Hook Scene and delayed scene s.syncAsSint32LE(*SaveSceneSsCount); @@ -347,7 +420,8 @@ static void DoSync(Serializer &s) { syncSavedData(s, *sdPtr); } - syncAllActorsAlive(s); + if (!TinselV2) + syncAllActorsAlive(s); } /** @@ -440,7 +514,7 @@ void ProcessSRQueue(void) { switch (SRstate) { case SR_DORESTORE: if (DoRestore()) { - RestoreScene(srsd, false); + DoRestoreScene(srsd, false); } SRstate = SR_IDLE; break; @@ -467,6 +541,15 @@ void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVE } void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) { + if (TinselV2) { + if (num == -1) + return; + else if (num == -2) { + // From CD change for restore + num = RestoreGameNumber; + } + } + assert(num >= 0); RestoreGameNumber = num; @@ -476,4 +559,14 @@ void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsD SRstate = SR_DORESTORE; } +/** + * Returns the index of the most recently saved savegame. This will always be + * the file at the first index, since the list is sorted by date/time + */ +int NewestSavedGame(void) { + int numFiles = getList(); + + return (numFiles == 0) ? -1 : 0; +} + } // end of namespace Tinsel diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp index 9f0d7b9039..418868aa74 100644 --- a/engines/tinsel/savescn.cpp +++ b/engines/tinsel/savescn.cpp @@ -28,19 +28,24 @@ #include "tinsel/actors.h" #include "tinsel/background.h" #include "tinsel/config.h" +#include "tinsel/drives.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/heapmem.h" +#include "tinsel/dialogs.h" #include "tinsel/music.h" #include "tinsel/pid.h" +#include "tinsel/play.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/savescn.h" +#include "tinsel/scene.h" #include "tinsel/sched.h" #include "tinsel/scroll.h" #include "tinsel/sound.h" +#include "tinsel/sysvar.h" #include "tinsel/tinlib.h" #include "tinsel/token.h" @@ -49,7 +54,6 @@ namespace Tinsel { //----------------- EXTERN FUNCTIONS -------------------- // in BG.C -extern void startupBackground(SCNHANDLE bfilm); extern SCNHANDLE GetBgroundHandle(void); extern void SetDoFadeIn(bool tf); @@ -59,14 +63,8 @@ void RestoreMasterProcess(INT_CONTEXT *pic); // in EVENTS.C (declared here and not in events.h because of strange goings-on) void RestoreProcess(INT_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 -------------------- @@ -77,6 +75,11 @@ enum { MAX_NEST = 4 }; +//----------------- EXTERNAL GLOBAL DATA -------------------- + +extern int thingHeld; +extern int restoreCD; +extern SRSTATE SRstate; //----------------- LOCAL GLOBAL DATA -------------------- @@ -84,54 +87,52 @@ static bool ASceneIsSaved = false; static int savedSceneCount = 0; -//static SAVED_DATA s_ssData[MAX_NEST]; -static SAVED_DATA *s_ssData = 0; +static bool bNotDoneYet = false; + +//static SAVED_DATA ssData[MAX_NEST]; +static SAVED_DATA *ssData = NULL; static SAVED_DATA sgData; -static SAVED_DATA *s_rsd = 0; +static SAVED_DATA *rsd = 0; -static int s_restoreSceneCount = 0; +static int 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) { +void DoSaveScene(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; + sd->SavedControl = ControlIsOn(); + sd->SavedNoBlocking = GetNoBlocking(); GetNoScrollData(&sd->SavedNoScrollData); + if (TinselV2) { + // Tinsel 2 specific data save + SaveActorZ(sd->savedActorZ); + SaveZpositions(sd->zPositions); + SavePolygonStuff(sd->SavedPolygonStuff); + _vm->_pcmMusic->getTunePlaying(sd->SavedTune, sizeof(sd->SavedTune)); + sd->bTinselDim = _vm->_pcmMusic->getMusicTinselDimmed(); + sd->SavedScrollFocus = GetScrollFocus(); + SaveSysVars(sd->SavedSystemVars); + SaveSoundReels(sd->SavedSoundReels); + + } else { + // Tinsel 1 specific data save + SaveDeadPolys(sd->SavedDeadPolys); + CurrentMidiFacts(&sd->SavedMidi, &sd->SavedLoop); + } + ASceneIsSaved = true; } @@ -140,13 +141,32 @@ void SaveScene(SAVED_DATA *sd) { * @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; +void DoRestoreScene(SAVED_DATA *sd, bool bFadeOut) { + rsd = sd; if (bFadeOut) - s_restoreSceneCount = RS_COUNT + COUNTOUT_COUNT; // Set restore scene count + RestoreSceneCount = RS_COUNT + COUNTOUT_COUNT; // Set restore scene count else - s_restoreSceneCount = RS_COUNT; // Set restore scene count + RestoreSceneCount = RS_COUNT; // Set restore scene count +} + +void InitialiseSaveScenes(void) { + if (ssData == NULL) { + ssData = (SAVED_DATA *)calloc(MAX_NEST, sizeof(SAVED_DATA)); + if (ssData == NULL) { + error("Cannot allocate memory for scene changes"); + } + } else { + // Re-initialise - no scenes saved + savedSceneCount = 0; + } +} + +void FreeSaveScenes(void) { + if (ssData) { + free(ssData); + ssData = NULL; + } } /** @@ -154,39 +174,84 @@ void RestoreScene(SAVED_DATA *sd, bool bFadeOut) { * 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); +void sortActors(SAVED_DATA *sd) { + assert(!TinselV2); + for (int i = 0; i < sd->NumSavedActors; i++) { + ActorsLife(sd->SavedActorInfo[i].actorID, sd->SavedActorInfo[i].bAlive); // Should be playing the same reel. - if (rsd->SavedActorInfo[i].presFilm != 0) { - if (!actorAlive(rsd->SavedActorInfo[i].actorID)) + if (sd->SavedActorInfo[i].presFilm != 0) { + if (!actorAlive(sd->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); + RestoreActorReels(sd->SavedActorInfo[i].presFilm, sd->SavedActorInfo[i].presRnum, sd->SavedActorInfo[i].zFactor, + sd->SavedActorInfo[i].presPlayX, sd->SavedActorInfo[i].presPlayY); } } - RestoreAuxScales(rsd->SavedMoverInfo); + RestoreAuxScales(sd->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); + if (sd->SavedMoverInfo[i].bActive) + Stand(nullContext, sd->SavedMoverInfo[i].actorID, sd->SavedMoverInfo[i].objX, + sd->SavedMoverInfo[i].objY, sd->SavedMoverInfo[i].hLastfilm); } } +/** + * Stand all the moving actors at their saved positions. + * Not called from the foreground. + */ +static void SortMAProcess(CORO_PARAM, const void *) { + CORO_BEGIN_CONTEXT; + int i; + int viaActor; + CORO_END_CONTEXT(_ctx); + + + CORO_BEGIN_CODE(_ctx); + + // Disable via actor for the stands + _ctx->viaActor = SysVar(ISV_DIVERT_ACTOR); + SetSysVar(ISV_DIVERT_ACTOR, 0); + + RestoreAuxScales(rsd->SavedMoverInfo); + + for (_ctx->i = 0; _ctx->i < MAX_MOVERS; _ctx->i++) { + if (rsd->SavedMoverInfo[_ctx->i].bActive) { + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, rsd->SavedMoverInfo[_ctx->i].actorID, + rsd->SavedMoverInfo[_ctx->i].objX, rsd->SavedMoverInfo[_ctx->i].objY, + rsd->SavedMoverInfo[_ctx->i].hLastfilm)); + + if (rsd->SavedMoverInfo[_ctx->i].bHidden) + HideMover(GetMover(rsd->SavedMoverInfo[_ctx->i].actorID)); + } + + ActorPalette(rsd->SavedMoverInfo[_ctx->i].actorID, + rsd->SavedMoverInfo[_ctx->i].startColour, rsd->SavedMoverInfo[_ctx->i].paletteLength); + + if (rsd->SavedMoverInfo[_ctx->i].brightness != BOGUS_BRIGHTNESS) + ActorBrightness(rsd->SavedMoverInfo[_ctx->i].actorID, rsd->SavedMoverInfo[_ctx->i].brightness); + } + + // Restore via actor + SetSysVar(ISV_DIVERT_ACTOR, _ctx->viaActor); + + bNotDoneYet = false; + + CORO_END_CODE; +} + //--------------------------------------------------------------------------- -void ResumeInterprets(SAVED_DATA *rsd) { +void ResumeInterprets(void) { // Master script only affected on restore game, not restore scene - if (rsd == &sgData) { + if (!TinselV2 && (rsd == &sgData)) { g_scheduler->killMatchingProcess(PID_MASTER_SCR, -1); FreeMasterInterpretContext(); } - for (int i = 0; i < MAX_INTERPRET; i++) { + for (int i = 0; i < NUM_INTERPRET; i++) { switch (rsd->SavedICInfo[i].GSort) { case GS_NONE: break; @@ -203,14 +268,31 @@ void ResumeInterprets(SAVED_DATA *rsd) { RestoreMasterProcess(&rsd->SavedICInfo[i]); break; + case GS_PROCESS: + // Tinsel 2 process + RestoreSceneProcess(&rsd->SavedICInfo[i]); + break; + + case GS_GPROCESS: + // Tinsel 2 Global processes only affected on restore game, not restore scene + if (rsd == &sgData) + RestoreGlobalProcess(&rsd->SavedICInfo[i]); + break; + case GS_ACTOR: - RestoreActorProcess(rsd->SavedICInfo[i].actorid, &rsd->SavedICInfo[i]); + if (TinselV2) + RestoreProcess(&rsd->SavedICInfo[i]); + else + RestoreActorProcess(rsd->SavedICInfo[i].idActor, &rsd->SavedICInfo[i]); break; case GS_POLYGON: case GS_SCENE: RestoreProcess(&rsd->SavedICInfo[i]); break; + + default: + warning("Unhandled GSort in ResumeInterprets"); } } } @@ -219,7 +301,7 @@ void ResumeInterprets(SAVED_DATA *rsd) { * Do restore scene * @param n Id */ -static int DoRestoreScene(SAVED_DATA *rsd, int n) { +static int DoRestoreSceneFrame(SAVED_DATA *sd, int n) { switch (n) { case RS_COUNT + COUNTOUT_COUNT: // Trigger pre-load and fade and start countdown @@ -229,31 +311,90 @@ static int DoRestoreScene(SAVED_DATA *rsd, int n) { case RS_COUNT: _vm->_sound->stopAllSamples(); ClearScreen(); - RestoreDeadPolys(rsd->SavedDeadPolys); - NewScene(rsd->SavedSceneHandle, NO_ENTRY_NUM); + + // Master script only affected on restore game, not restore scene + if (TinselV2 && (sd == &sgData)) { + g_scheduler->killMatchingProcess(PID_MASTER_SCR); + KillGlobalProcesses(); + FreeMasterInterpretContext(); + } + + if (TinselV2) { + RestorePolygonStuff(sd->SavedPolygonStuff); + + // Abandon temporarily if different CD + if (sd == &sgData && restoreCD != GetCurrentCD()) { + SRstate = SR_IDLE; + + EndScene(); + SetNextCD(restoreCD); + CDChangeForRestore(restoreCD); + + return 0; + } + } else { + RestoreDeadPolys(sd->SavedDeadPolys); + } + + // Start up the scene + StartNewScene(sd->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; + StartupBackground(nullContext, sd->SavedBgroundHandle); + + if (TinselV2) { + Offset(EX_USEXY, sd->SavedLoffset, sd->SavedToffset); + } else { + KillScroll(); + PlayfieldSetPos(FIELD_WORLD, sd->SavedLoffset, sd->SavedToffset); + SetNoBlocking(sd->SavedNoBlocking); + } + + RestoreNoScrollData(&sd->SavedNoScrollData); + + if (TinselV2) { + // create process to sort out the moving actors + g_scheduler->createProcess(PID_MOVER, SortMAProcess, NULL, 0); + bNotDoneYet = true; + + RestoreActorZ(sd->savedActorZ); + RestoreZpositions(sd->zPositions); + RestoreSysVars(sd->SavedSystemVars); + CreateGhostPalette(BgPal()); + RestoreActors(sd->NumSavedActors, sd->SavedActorInfo); + RestoreSoundReels(sd->SavedSoundReels); + return 1; + } - case RS_COUNT - 1: -*/ - sortActors(rsd); + sortActors(sd); break; case 2: break; case 1: - RestoreMidiFacts(rsd->SavedMidi, rsd->SavedLoop); - if (rsd->SavedControl) - control(CONTROL_ON); // TOKEN_CONTROL was free - ResumeInterprets(rsd); + if (TinselV2) { + if (bNotDoneYet) + return n; + + if (sd == &sgData) + HoldItem(thingHeld, true); + if (sd->bTinselDim) + _vm->_pcmMusic->dim(true); + _vm->_pcmMusic->restoreThatTune(sd->SavedTune); + ScrollFocus(sd->SavedScrollFocus); + } else { + RestoreMidiFacts(sd->SavedMidi, sd->SavedLoop); + } + + if (sd->SavedControl) + ControlOn(); // Control was on + ResumeInterprets(); + break; + + default: + break; } return n - 1; @@ -266,7 +407,7 @@ static int DoRestoreScene(SAVED_DATA *rsd, int n) { void RestoreGame(int num) { KillInventory(); - RequestRestoreGame(num, &sgData, &savedSceneCount, s_ssData); + RequestRestoreGame(num, &sgData, &savedSceneCount, ssData); // Actual restoring is performed by ProcessSRQueue } @@ -278,9 +419,9 @@ void RestoreGame(int num) { */ void SaveGame(char *name, char *desc) { // Get current scene data - SaveScene(&sgData); + DoSaveScene(&sgData); - RequestSaveGame(name, desc, &sgData, &savedSceneCount, s_ssData); + RequestSaveGame(name, desc, &sgData, &savedSceneCount, ssData); // Actual saving is performed by ProcessSRQueue } @@ -289,23 +430,23 @@ void SaveGame(char *name, char *desc) { //--------------------------------------------------------------------------------- bool IsRestoringScene() { - if (s_restoreSceneCount) { - s_restoreSceneCount = DoRestoreScene(s_rsd, s_restoreSceneCount); + if (RestoreSceneCount) { + RestoreSceneCount = DoRestoreSceneFrame(rsd, RestoreSceneCount); } - return s_restoreSceneCount ? true : false; + return RestoreSceneCount ? true : false; } /** - * Please Restore Scene + * Restores Scene */ -void PleaseRestoreScene(bool bFade) { +void TinselRestoreScene(bool bFade) { // only called by restore_scene PCODE - if (s_restoreSceneCount == 0) { + if (RestoreSceneCount == 0) { assert(savedSceneCount >= 1); // No saved scene to restore if (ASceneIsSaved) - RestoreScene(&s_ssData[--savedSceneCount], bFade); + DoRestoreScene(&ssData[--savedSceneCount], bFade); if (!bFade) bNoFade = true; } @@ -314,7 +455,7 @@ void PleaseRestoreScene(bool bFade) { /** * Please Save Scene */ -void PleaseSaveScene(CORO_PARAM) { +void TinselSaveScene(CORO_PARAM) { // only called by save_scene PCODE CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); @@ -325,10 +466,10 @@ void PleaseSaveScene(CORO_PARAM) { // 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()) + if (savedSceneCount && ssData[savedSceneCount-1].SavedSceneHandle == GetSceneHandle()) CORO_KILL_SELF(); - SaveScene(&s_ssData[savedSceneCount++]); + DoSaveScene(&ssData[savedSceneCount++]); CORO_END_CODE; } diff --git a/engines/tinsel/savescn.h b/engines/tinsel/savescn.h index a999c9bffa..79bf0ca2b2 100644 --- a/engines/tinsel/savescn.h +++ b/engines/tinsel/savescn.h @@ -33,13 +33,16 @@ #include "tinsel/dw.h" // SCNHANDLE #include "tinsel/rince.h" // SAVED_MOVER #include "tinsel/pcode.h" // INT_CONTEXT +#include "tinsel/play.h" +#include "tinsel/polygons.h" #include "tinsel/scroll.h" // SCROLLDATA +#include "tinsel/sysvar.h" namespace Tinsel { enum { SG_DESC_LEN = 40, // Max. saved game description length - MAX_SFILES = 30, + MAX_SAVED_FILES = 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. @@ -66,6 +69,16 @@ struct SAVED_DATA { bool SavedLoop; // } Midi bool SavedNoBlocking; SCROLLDATA SavedNoScrollData; + + // Tinsel 2 fields + Z_POSITIONS zPositions[NUM_ZPOSITIONS]; + byte savedActorZ[MAX_SAVED_ACTOR_Z]; + POLY_VOLATILE SavedPolygonStuff[MAX_POLY]; + uint32 SavedTune[3]; // Music + bool bTinselDim; + int SavedScrollFocus; + int SavedSystemVars[SV_TOPVALID]; + SOUNDREELS SavedSoundReels[MAX_SOUNDREELS]; }; @@ -74,8 +87,10 @@ enum SRSTATE { SR_DOSAVE, SR_DONESAVE, SR_ABORTED }; -void PleaseRestoreScene(bool bFade); -void PleaseSaveScene(CORO_PARAM); +void TinselRestoreScene(bool bFade); +void TinselSaveScene(CORO_PARAM); +void DoRestoreScene(SAVED_DATA *sd, bool bFadeOut); +void DoSaveScene(SAVED_DATA *sd); bool IsRestoringScene(); @@ -95,8 +110,8 @@ 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); +void InitialiseSaveScenes(void); +void FreeSaveScenes(void); } // end of namespace Tinsel diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp index 70700c16a3..8826d97099 100644 --- a/engines/tinsel/scene.cpp +++ b/engines/tinsel/scene.cpp @@ -29,21 +29,26 @@ #include "tinsel/background.h" #include "tinsel/config.h" #include "tinsel/cursor.h" +#include "tinsel/dialogs.h" #include "tinsel/dw.h" #include "tinsel/graphics.h" #include "tinsel/handle.h" -#include "tinsel/inventory.h" #include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/mareels.h" #include "tinsel/move.h" +#include "tinsel/music.h" +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" // process IDs +#include "tinsel/play.h" +#include "tinsel/polygons.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/polygons.h" +#include "tinsel/sysvar.h" #include "tinsel/token.h" @@ -72,20 +77,28 @@ extern void EnableTags(void); /** 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 + int32 defRefer; // Default refer direction (REFTYPE) + SCNHANDLE hSceneScript; // handle to scene script + SCNHANDLE hSceneDesc; // handle to scene description + int32 numEntrance; // number of entrances in this scene + SCNHANDLE hEntrance; // handle to table of entrances + int32 numPoly; // number of various polygons in this scene + SCNHANDLE hPoly; // handle to table of polygons + int32 numTaggedActor; // number of tagged actors in this scene + SCNHANDLE hTaggedActor; // handle to table of tagged actors + int32 numProcess; // number of processes in this scene + SCNHANDLE hProcess; // handle to table of processes + SCNHANDLE hMusicScript; // handle to music script data + SCNHANDLE hMusicSegment;// handle to music segments } PACKED_STRUCT; /** entrance structure - one per entrance */ struct ENTRANCE_STRUC { int32 eNumber; //!< entrance number SCNHANDLE hScript; //!< handle to entrance script + // Tinsel 2 fields + SCNHANDLE hEntDesc; // handle to entrance description + uint32 flags; } PACKED_STRUCT; #include "common/pack-end.h" // END STRUCT PACKING @@ -97,8 +110,39 @@ struct ENTRANCE_STRUC { static bool ShowPosition = false; // Set when showpos() has been called #endif +SCNHANDLE newestScene = 0; + static SCNHANDLE SceneHandle = 0; // Current scene handle - stored in case of Save_Scene() +static bool bWatchingOut = false; + +SCENE_STRUC tempStruc; + +struct TP_INIT { + SCNHANDLE hTinselCode; // Code + TINSEL_EVENT event; // Triggering event +}; + +const SCENE_STRUC *GetSceneStruc(const byte *pStruc) { + if (TinselVersion == TINSEL_V2) + return (const SCENE_STRUC *)pStruc; + + // Copy appropriate fields into tempStruc, and return a pointer to it + const uint32 *p = (const uint32 *)pStruc; + memset(&tempStruc, sizeof(SCENE_STRUC), 0); + + tempStruc.numEntrance = *p++; + tempStruc.numPoly = *p++; + tempStruc.numTaggedActor = *p++; + tempStruc.defRefer = *p++; + tempStruc.hSceneScript = *p++; + tempStruc.hEntrance = *p++; + tempStruc.hPoly = *p++; + tempStruc.hTaggedActor = *p++; + + return &tempStruc; +} + /** * Started up for scene script and entrance script. @@ -107,20 +151,59 @@ static void SceneTinselProcess(CORO_PARAM, const void *param) { // COROUTINE CORO_BEGIN_CONTEXT; INT_CONTEXT *pic; + const TP_INIT *pInit; CORO_END_CONTEXT(_ctx); - // get the stuff copied to process when it was created - SCNHANDLE *ss = (SCNHANDLE *)param; - 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); + // get the stuff copied to process when it was created + _ctx->pInit = (const TP_INIT *)param; + assert(_ctx->pInit); + assert(_ctx->pInit->hTinselCode); // Must have some code to run + + _ctx->pic = InitInterpretContext(GS_SCENE, + READ_LE_UINT32(&_ctx->pInit->hTinselCode), + TinselV2 ? _ctx->pInit->event : NOEVENT, + NOPOLY, // No polygon + 0, // No actor + NULL, // No object + 0); CORO_INVOKE_1(Interpret, _ctx->pic); + if (_ctx->pInit->event == CLOSEDOWN || _ctx->pInit->event == LEAVE_T2) + bWatchingOut = false; + CORO_END_CODE; } +/** + * Start up a SceneTinselProcess() running the scene + * script for various events. + */ +void SendSceneTinselProcess(TINSEL_EVENT event) { + SCENE_STRUC *ss; + + if (event == CLOSEDOWN || event == LEAVE_T2) + bWatchingOut = true; + + if (SceneHandle != (SCNHANDLE)NULL) { + ss = (SCENE_STRUC *) FindChunk(SceneHandle, CHUNK_SCENE); + + if (ss->hSceneScript) { + TP_INIT init; + + init.event = event; + init.hTinselCode = ss->hSceneScript; + + g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init)); + } else if (event == CLOSEDOWN) + bWatchingOut = false; + } + else if (event == CLOSEDOWN) + bWatchingOut = false; +} + + /** * Get the SCENE_STRUC * Initialise polygons for the scene @@ -128,47 +211,103 @@ static void SceneTinselProcess(CORO_PARAM, const void *param) { * Run the appropriate entrance code (if any) * Get the default refer type */ + static void LoadScene(SCNHANDLE scene, int entry) { + uint i; + TP_INIT init; const SCENE_STRUC *ss; const ENTRANCE_STRUC *es; - uint i; - - // Scene structure - SceneHandle = scene; // Save scene handle in case of Save_Scene() + // Scene handle + 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); + if (TinselV2) { + // CdPlay() stuff + byte *cptr = FindChunk(scene, CHUNK_CDPLAY_FILENUM); + assert(cptr); + i = READ_LE_UINT32(cptr); + assert(i < 512); + cptr = FindChunk(scene, CHUNK_CDPLAY_FILENAME); + assert(cptr); + SetCdPlaySceneDetails(i, (const char *)cptr); + } + + // Find scene structure + ss = GetSceneStruc(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)); + if (TinselV2) { + // Handle to scene description + newestScene = FROM_LE_32(ss->hSceneDesc); + + // Music stuff + char *cptr = (char *)FindChunk(scene, CHUNK_MUSIC_FILENAME); + assert(cptr); + _vm->_pcmMusic->setMusicSceneDetails(FROM_LE_32(ss->hMusicScript), + FROM_LE_32(ss->hMusicSegment), cptr); + } + + if (entry == NO_ENTRY_NUM) { + // Restoring scene + + // Initialise all the polygons for this scene + InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), true); + + // Initialise the actors for this scene + StartTaggedActors(FROM_LE_32(ss->hTaggedActor), FROM_LE_32(ss->numTaggedActor), false); + + if (TinselV2) + // Returning from cutscene + SendSceneTinselProcess(RESTORE); - // Initialise the actors for this scene - StartActors(FROM_LE_32(ss->hActor), FROM_LE_32(ss->numActor), (entry != NO_ENTRY_NUM)); + } else { + // Genuine new scene - if (entry != NO_ENTRY_NUM) { + // Initialise all the polygons for this scene + InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), false); + + // Initialise the actors for this scene + StartTaggedActors(FROM_LE_32(ss->hTaggedActor), FROM_LE_32(ss->numTaggedActor), true); // 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++) { + for (i = 0; i < FROM_LE_32(ss->numEntrance); i++) { if (FROM_LE_32(es->eNumber) == (uint)entry) { - if (es->hScript) - g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &es->hScript, sizeof(es->hScript)); + if (es->hScript) { + init.event = STARTUP; + init.hTinselCode = es->hScript; + + g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init)); + } break; } + + // Move to next entrance + if (TinselV2) + ++es; + else + es = (const ENTRANCE_STRUC *)((const byte *)es + 8); + } if (i == FROM_LE_32(ss->numEntrance)) error("Non-existant scene entry number"); - if (ss->hSceneScript) - g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &ss->hSceneScript, sizeof(ss->hSceneScript)); + if (ss->hSceneScript) { + init.event = STARTUP; + init.hTinselCode = ss->hSceneScript; + + g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init)); + } } // Default refer type SetDefaultRefer(FROM_LE_32(ss->defRefer)); + + // Scene's processes + SceneProcesses(FROM_LE_32(ss->numProcess), FROM_LE_32(ss->hProcess)); } @@ -184,15 +323,27 @@ void EndScene(void) { KillInventory(); // Close down any open inventory DropPolygons(); // No polygons - DropNoScrolls(); // No no-scrolls + DropScroll(); // No no-scrolls DropBackground(); // No background - DropMActors(); // No moving actors + DropMovers(); // No moving actors DropCursor(); // No cursor DropActors(); // No actor reels running FreeAllTokens(); // No-one has tokens FreeMostInterpretContexts(); // Only master script still interpreting + if (TinselV2) { + SetSysVar(ISV_DIVERT_ACTOR, 0); + SetSysVar(ISV_GHOST_ACTOR, 0); + SetSysVar(SV_MinimumXoffset, 0); + SetSysVar(SV_MaximumXoffset, 0); + SetSysVar(SV_MinimumYoffset, 0); + SetSysVar(SV_MaximumYoffset, 0); + ResetFontHandles(); + NoSoundReels(); + } + _vm->_sound->stopAllSamples(); // Kill off any still-running sample + _vm->_mixer->stopAll(); // init the palette manager ResetPalAllocator(); @@ -251,11 +402,12 @@ void PrimeBackground(void) { */ void PrimeScene(void) { - - bNoBlocking = false; + SetNoBlocking(false); + SetSysVar(SYS_SceneFxDimFactor, SysVar(SYS_DefaultFxDimFactor)); RestartCursor(); // Restart the cursor - EnableTags(); // Next scene with tags enabled + if (!TinselV2) + EnableTags(); // Next scene with tags enabled g_scheduler->createProcess(PID_SCROLL, ScrollProcess, NULL, 0); g_scheduler->createProcess(PID_SCROLL, EffectPolyProcess, NULL, 0); @@ -276,9 +428,15 @@ void PrimeScene(void) { * Wrap up the last scene and start up the next scene. */ -void NewScene(SCNHANDLE scene, int entry) { +void StartNewScene(SCNHANDLE scene, int entry) { EndScene(); // Wrap up the last scene. + if (TinselV2) { + TouchMoverReels(); + + LockMem(scene); // Do CD change before PrimeScene + } + PrimeScene(); // Start up the standard stuff for the next scene. LoadScene(scene, entry); @@ -303,4 +461,29 @@ SCNHANDLE GetSceneHandle(void) { return SceneHandle; } +/** + * DoHailScene + */ + +void DoHailScene(SCNHANDLE scene) { + // Find scene structure + const SCENE_STRUC *ss = GetSceneStruc(FindChunk(scene, CHUNK_SCENE)); + + if (ss != NULL && ss->hSceneScript) { + TP_INIT init; + + init.event = NOEVENT; + init.hTinselCode = ss->hSceneScript; + + g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init)); + } +} + +/** + * WrapScene + */ +void WrapScene(void) { + SendSceneTinselProcess(CLOSEDOWN); +} + } // end of namespace Tinsel diff --git a/engines/tinsel/scene.h b/engines/tinsel/scene.h index d0fc6e1ae3..e47f0b864c 100644 --- a/engines/tinsel/scene.h +++ b/engines/tinsel/scene.h @@ -28,6 +28,7 @@ #define TINSEL_SCENE_H #include "tinsel/dw.h" +#include "tinsel/events.h" namespace Tinsel { @@ -39,13 +40,19 @@ enum { MAX_ACTOR = 32 //!< maximum number of actors in a scene }; +// ENTRANCE_STRUC bitflags +enum ENTRANCE_FLAGS { + fCall = 0x00000001L, + fHook = 0x00000002L +}; + /** 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 + TF_NONE, TF_UP, TF_DOWN, TF_LEFT, TF_RIGHT, TF_FILM }; /** different actor masks */ @@ -68,6 +75,23 @@ enum REEL { REEL_DEFAULT, REEL_ALL, REEL_HORIZ, REEL_VERT }; +typedef enum { TRANS_DEF, TRANS_CUT, TRANS_FADE } TRANSITS; + +// amount to shift scene handles by +#define SCNHANDLE_SHIFT (TinselV2 ? 25 : 23) +#define OFFSETMASK (TinselV2 ? 0x01ffffffL : 0x007fffffL) +#define HANDLEMASK (TinselV2 ? 0xFE000000L : 0xFF800000L) + +void DoHailScene(SCNHANDLE scene); + +void WrapScene(void); + +void StartNewScene(SCNHANDLE scene, int entry); + +void EndScene(void); + +void SendSceneTinselProcess(TINSEL_EVENT event); + } // end of namespace Tinsel #endif // TINSEL_SCENE_H diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp index 72cfeaf6b0..526ad3fcf6 100644 --- a/engines/tinsel/sched.cpp +++ b/engines/tinsel/sched.cpp @@ -24,6 +24,10 @@ * Process scheduler. */ +#include "tinsel/handle.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" #include "tinsel/sched.h" #include "common/util.h" @@ -32,18 +36,26 @@ namespace Tinsel { Scheduler *g_scheduler = 0; -/** process structure */ -struct PROCESS { - PROCESS *pNext; //!< pointer to next process in active or free list +#include "common/pack-start.h" // START STRUCT PACKING - CoroContext state; //!< the state of the coroutine - CORO_ADDR coroAddr; //!< the entry point of the coroutine +struct PROCESS_STRUC { + uint32 processId; // ID of process + SCNHANDLE hProcessCode; // handle to actor script +} PACKED_STRUCT; - int sleepTime; //!< number of scheduler cycles to sleep - int pid; //!< process ID - char param[PARAM_SIZE]; //!< process specific info -}; +#include "common/pack-end.h" // END STRUCT PACKING +CoroContext nullContext = NULL; + +//----------------- LOCAL GLOBAL DATA -------------------- + +static uint32 numSceneProcess; +static SCNHANDLE hSceneProcess; + +static uint32 numGlobalProcess; +static PROCESS_STRUC *pGlobalProcess; + +//--------------------- FUNCTIONS ------------------------ Scheduler::Scheduler() { processList = 0; @@ -59,6 +71,7 @@ Scheduler::Scheduler() { pRCfunction = 0; active = new PROCESS; + active->pPrevious = NULL; g_scheduler = this; // FIXME HACK } @@ -83,7 +96,7 @@ void Scheduler::reset() { if (processList == NULL) { // first time - allocate memory for process list - processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS)); + processList = (PROCESS *)calloc(MAX_PROCESSES, sizeof(PROCESS)); // make sure memory allocated if (processList == NULL) { @@ -91,7 +104,7 @@ void Scheduler::reset() { } // fill with garbage - memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS)); + memset(processList, 'S', MAX_PROCESSES * sizeof(PROCESS)); } // no active processes @@ -101,12 +114,10 @@ void Scheduler::reset() { pFreeProcesses = processList; // link all other processes after first - for (int i = 1; i < NUM_PROCESS; i++) { - processList[i - 1].pNext = processList + i; + for (int i = 1; i <= NUM_PROCESS; i++) { + processList[i - 1].pNext = (i == NUM_PROCESS) ? NULL : processList + i; + processList[i - 1].pPrevious = (i == 1) ? active : processList + (i - 2); } - - // null the last process - processList[NUM_PROCESS - 1].pNext = NULL; } @@ -119,33 +130,172 @@ void Scheduler::printStats(void) { } #endif +#ifdef DEBUG +/** + * Checks both the active and free process list to insure all the links are valid, + * and that no processes have been lost + */ +void Scheduler::CheckStack() { + Common::List pList; + + // Check both the active and free process lists + for (int i = 0; i < 2; ++i) { + PROCESS *p = (i == 0) ? active : pFreeProcesses; + + if (p != NULL) { + // Make sure the linkages are correct + while (p->pNext != NULL) { + assert(p->pNext->pPrevious == p); + pList.push_back(p); + p = p->pNext; + } + pList.push_back(p); + } + } + + // Make sure all processes are accounted for + for (int idx = 0; idx < NUM_PROCESS; idx++) { + bool found = false; + for (Common::List::iterator i = pList.begin(); i != pList.end(); ++i) { + PROCESS *pTemp = *i; + if (*i == &processList[idx]) { + found = true; + break; + } + } + + assert(found); + } +} +#endif /** * Give all active processes a chance to run */ void Scheduler::schedule(void) { // start dispatching active process list - PROCESS *pPrevProc = active; + PROCESS *pNext; PROCESS *pProc = active->pNext; while (pProc != NULL) { + pNext = pProc->pNext; + if (--pProc->sleepTime <= 0) { // process is ready for dispatch, activate it pCurrent = pProc; pProc->coroAddr(pProc->state, pProc->param); - pCurrent = NULL; + if (!pProc->state || pProc->state->_sleep <= 0) { // Coroutine finished + pCurrent = pCurrent->pPrevious; killProcess(pProc); - pProc = pPrevProc; } else { pProc->sleepTime = pProc->state->_sleep; } + + // pCurrent may have been changed + pNext = pCurrent->pNext; + pCurrent = NULL; } - pPrevProc = pProc; - pProc = pProc->pNext; + + pProc = pNext; } } +/** + * Reschedules all the processes to run again this query + */ +void Scheduler::rescheduleAll() { + assert(pCurrent); + + // Unlink current process + pCurrent->pPrevious->pNext = pCurrent->pNext; + if (pCurrent->pNext) + pCurrent->pNext->pPrevious = pCurrent->pPrevious; + + // Add process to the start of the active list + pCurrent->pNext = active->pNext; + active->pNext->pPrevious = pCurrent; + active->pNext = pCurrent; + pCurrent->pPrevious = active; +} + +/** + * If the specified process has already run on this tick, make it run + * again on the current tick. + */ +void Scheduler::reschedule(PPROCESS pReSchedProc) { + // If not currently processing the schedule list, then no action is needed + if (!pCurrent) + return; + + if (!pReSchedProc) + pReSchedProc = pCurrent; + + PPROCESS pEnd; + + // Find the last process in the list. + // But if the target process is down the list from here, do nothing + for (pEnd = pCurrent; pEnd->pNext != NULL; pEnd = pEnd->pNext) { + if (pEnd->pNext == pReSchedProc) + return; + } + + assert(pEnd->pNext == NULL); + + // Could be in the middle of a KillProc()! + // Dying process was last and this process was penultimate + if (pReSchedProc->pNext == NULL) + return; + + // If we're moving the current process, move it back by one, so that the next + // schedule() iteration moves to the now next one + if (pCurrent == pReSchedProc) + pCurrent = pCurrent->pPrevious; + + // Unlink the process, and add it at the end + pReSchedProc->pPrevious->pNext = pReSchedProc->pNext; + pReSchedProc->pNext->pPrevious = pReSchedProc->pPrevious; + pEnd->pNext = pReSchedProc; + pReSchedProc->pPrevious = pEnd; + pReSchedProc->pNext = NULL; +} + +/** + * Moves the specified process to the end of the dispatch queue + * allowing it to run again within the current game cycle. + * @param pGiveProc Which process + */ +void Scheduler::giveWay(PPROCESS pReSchedProc) { + // If not currently processing the schedule list, then no action is needed + if (!pCurrent) + return; + + if (!pReSchedProc) + pReSchedProc = pCurrent; + + // If the process is already at the end of the queue, nothing has to be done + if (!pReSchedProc->pNext) + return; + + PPROCESS pEnd; + + // Find the last process in the list. + for (pEnd = pCurrent; pEnd->pNext != NULL; pEnd = pEnd->pNext) ; + assert(pEnd->pNext == NULL); + + + // If we're moving the current process, move it back by one, so that the next + // schedule() iteration moves to the now next one + if (pCurrent == pReSchedProc) + pCurrent = pCurrent->pPrevious; + + // Unlink the process, and add it at the end + pReSchedProc->pPrevious->pNext = pReSchedProc->pNext; + pReSchedProc->pNext->pPrevious = pReSchedProc->pPrevious; + pEnd->pNext = pReSchedProc; + pReSchedProc->pPrevious = pEnd; + pReSchedProc->pNext = NULL; +} /** * Creates a new process. @@ -172,16 +322,27 @@ PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pPara // get link to next free process pFreeProcesses = pProc->pNext; + if (pFreeProcesses) + pFreeProcesses->pPrevious = NULL; if (pCurrent != NULL) { // place new process before the next active process pProc->pNext = pCurrent->pNext; + if (pProc->pNext) + pProc->pNext->pPrevious = pProc; // make this new process the next active process pCurrent->pNext = pProc; + pProc->pPrevious = pCurrent; + } else { // no active processes, place process at head of list pProc->pNext = active->pNext; + pProc->pPrevious = active; + + if (pProc->pNext) + pProc->pNext->pPrevious = pProc; active->pNext = pProc; + } // set coroutine entry point @@ -204,7 +365,6 @@ PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pPara memcpy(pProc->param, pParam, sizeParam); } - // return created process return pProc; } @@ -215,8 +375,6 @@ PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pPara * @param pKillProc which process to kill */ void Scheduler::killProcess(PROCESS *pKillProc) { - PROCESS *pProc, *pPrev; // process list pointers - // make sure a valid process pointer assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1); @@ -229,32 +387,25 @@ void Scheduler::killProcess(PROCESS *pKillProc) { 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; + // Free process' resources + if (pRCfunction != NULL) + (pRCfunction)(pKillProc); - // make prev point to next to unlink pProc - pPrev->pNext = pProc->pNext; + delete pKillProc->state; - // link first free process after pProc - pProc->pNext = pFreeProcesses; + // Take the process out of the active chain list + pKillProc->pPrevious->pNext = pKillProc->pNext; + if (pKillProc->pNext) + pKillProc->pNext->pPrevious = pKillProc->pPrevious; - // make pProc the first free process - pFreeProcesses = pProc; - - return; - } - } + // link first free process after pProc + pKillProc->pNext = pFreeProcesses; + if (pFreeProcesses) + pKillProc->pNext->pPrevious = pKillProc; + pKillProc->pPrevious = NULL; - // process not found in active list if we get to here - error("killProcess(): tried to kill a process not in the list of active processes"); + // make pKillProc the first free process + pFreeProcesses = pKillProc; } @@ -302,11 +453,21 @@ int Scheduler::killMatchingProcess(int pidKill, int pidMask) { // kill this process numKilled++; + // Free the process' resources + if (pRCfunction != NULL) + (pRCfunction)(pProc); + + delete pProc->state; + // make prev point to next to unlink pProc pPrev->pNext = pProc->pNext; + if (pProc->pNext) + pPrev->pNext->pPrevious = pPrev; // link first free process after pProc pProc->pNext = pFreeProcesses; + pProc->pPrevious = NULL; + pFreeProcesses->pPrevious = pProc; // make pProc the first free process pFreeProcesses = pProc; @@ -327,8 +488,6 @@ int Scheduler::killMatchingProcess(int pidKill, int pidMask) { return numKilled; } - - /** * Set pointer to a function to be called by killProcess(). * @@ -342,4 +501,262 @@ void Scheduler::setResourceCallback(VFPTRPP pFunc) { pRCfunction = pFunc; } +/**************************************************************************\ +|*********** Stuff to do with scene and global processes ************| +\**************************************************************************/ + +/** + * The code for for restored scene processes. + */ +static void RestoredProcessProcess(CORO_PARAM, const void *) { + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + PROCESS *pProc; // this process pointer + + // get the stuff copied to process when it was created + pProc = g_scheduler->getCurrentProcess(); + _ctx->pic = *((INT_CONTEXT **) pProc->param); + + _ctx->pic = RestoreInterpretContext(_ctx->pic); + AttachInterpret(_ctx->pic, pProc); + + CORO_INVOKE_1(Interpret, _ctx->pic); + + CORO_END_CODE; +} + +/** + * Process Tinsel Process + */ +static void ProcessTinselProcess(CORO_PARAM, const void *) { + PPROCESS pProc = g_scheduler->getCurrentProcess(); + PINT_CONTEXT *pPic = (PINT_CONTEXT *) pProc->param; + + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // get the stuff copied to process when it was created + CORO_INVOKE_1(Interpret, *pPic); + + CORO_KILL_SELF(); + CORO_END_CODE; +} + + +/**************************************************************************\ +|***************** Stuff to do with scene processes *****************| +\**************************************************************************/ + +/** + * Called to restore a scene process. + */ +void RestoreSceneProcess(INT_CONTEXT *pic) { + uint32 i; + PROCESS_STRUC *pStruc; + + pStruc = (PROCESS_STRUC *)LockMem(hSceneProcess); + for (i = 0; i < numSceneProcess; i++) { + if (pStruc[i].hProcessCode == pic->hCode) { + g_scheduler->createProcess(PID_PROCESS + i, RestoredProcessProcess, + &pic, sizeof(pic)); + break; + } + } + + assert(i < numSceneProcess); +} + +/** + * Run a scene process with the given event. + */ +void SceneProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait, int myEscape, + bool *result) { + uint32 i; // Loop counter + if (result) *result = false; + + CORO_BEGIN_CONTEXT; + PROCESS_STRUC *pStruc; + PPROCESS pProc; + PINT_CONTEXT pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->pStruc = (PROCESS_STRUC *)LockMem(hSceneProcess); + for (i = 0; i < numSceneProcess; i++) { + if (_ctx->pStruc[i].processId == procID) { + assert(_ctx->pStruc[i].hProcessCode); // Must have some code to run + + _ctx->pic = InitInterpretContext(GS_PROCESS, + _ctx->pStruc[i].hProcessCode, + event, + NOPOLY, // No polygon + 0, // No actor + NULL, // No object + myEscape); + if (_ctx->pic == NULL) + return; + + _ctx->pProc = g_scheduler->createProcess(PID_PROCESS + i, ProcessTinselProcess, + &_ctx->pic, sizeof(_ctx->pic)); + AttachInterpret(_ctx->pic, _ctx->pProc); + break; + } + } + + if (i == numSceneProcess) + return; + + if (bWait) { + CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result); + } + + CORO_END_CODE; +} + +/** + * Kill all instances of a scene process. + */ +void KillSceneProcess(uint32 procID) { + uint32 i; // Loop counter + PROCESS_STRUC *pStruc; + + pStruc = (PROCESS_STRUC *) LockMem(hSceneProcess); + for (i = 0; i < numSceneProcess; i++) { + if (pStruc[i].processId == procID) { + g_scheduler->killMatchingProcess(PID_PROCESS + i, -1); + break; + } + } +} + +/** + * Register the scene processes in a scene. + */ +void SceneProcesses(uint32 numProcess, SCNHANDLE hProcess) { + numSceneProcess = numProcess; + hSceneProcess = hProcess; +} + + +/**************************************************************************\ +|***************** Stuff to do with global processes ****************| +\**************************************************************************/ + +/** + * Called to restore a global process. + */ +void RestoreGlobalProcess(INT_CONTEXT *pic) { + uint32 i; // Loop counter + + for (i = 0; i < numGlobalProcess; i++) { + if (pGlobalProcess[i].hProcessCode == pic->hCode) { + g_scheduler->createProcess(PID_GPROCESS + i, RestoredProcessProcess, + &pic, sizeof(pic)); + break; + } + } + + assert(i < numGlobalProcess); +} + +/** + * Kill them all (restore game). + */ +void KillGlobalProcesses(void) { + + for (uint32 i = 0; i < numGlobalProcess; ++i) { + g_scheduler->killMatchingProcess(PID_GPROCESS + i, -1); + } +} + +/** + * Run a global process with the given event. + */ +bool GlobalProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait, int myEscape) { + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + PPROCESS pProc; + CORO_END_CONTEXT(_ctx); + + bool result = false; + + CORO_BEGIN_CODE(_ctx); + + uint32 i; // Loop counter + _ctx->pProc = NULL; + + for (i = 0; i < numGlobalProcess; ++i) { + if (pGlobalProcess[i].processId == procID) { + assert(pGlobalProcess[i].hProcessCode); // Must have some code to run + + _ctx->pic = InitInterpretContext(GS_GPROCESS, + pGlobalProcess[i].hProcessCode, + event, + NOPOLY, // No polygon + 0, // No actor + NULL, // No object + myEscape); + + if (_ctx->pic != NULL) { + + _ctx->pProc = g_scheduler->createProcess(PID_GPROCESS + i, ProcessTinselProcess, + &_ctx->pic, sizeof(_ctx->pic)); + AttachInterpret(_ctx->pic, _ctx->pProc); + } + break; + } + } + + if ((i == numGlobalProcess) || (_ctx->pic == NULL)) + result = false; + else if (bWait) + CORO_INVOKE_ARGS_V(WaitInterpret, false, (CORO_SUBCTX, _ctx->pProc, &result)); + + CORO_END_CODE; + return result; +} + +/** + * Kill all instances of a global process. + */ +void xKillGlobalProcess(uint32 procID) { + uint32 i; // Loop counter + + for (i = 0; i < numGlobalProcess; ++i) { + if (pGlobalProcess[i].processId == procID) { + g_scheduler->killMatchingProcess(PID_GPROCESS + i, -1); + break; + } + } +} + +/** + * Register the global processes list + */ +void GlobalProcesses(uint32 numProcess, byte *pProcess) { + pGlobalProcess = new PROCESS_STRUC[numProcess]; + numGlobalProcess = numProcess; + byte *p = pProcess; + + for (uint i = 0; i < numProcess; ++i, p += 8) { + pGlobalProcess[i].processId = READ_LE_UINT32(p); + pGlobalProcess[i].hProcessCode = READ_LE_UINT32(p + 4); + } +} + +/** + * Frees the global processes list + */ +void FreeGlobalProcesses() { + delete[] pGlobalProcess; + numGlobalProcess = 0; +} + } // end of namespace Tinsel diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h index 0d90b3bb9f..1e2d3f796e 100644 --- a/engines/tinsel/sched.h +++ b/engines/tinsel/sched.h @@ -29,6 +29,8 @@ #include "tinsel/dw.h" // new data types #include "tinsel/coroutine.h" +#include "tinsel/events.h" +#include "tinsel/tinsel.h" namespace Tinsel { @@ -36,12 +38,26 @@ namespace Tinsel { #define PARAM_SIZE 32 // the maximum number of processes -#define NUM_PROCESS 64 +#define NUM_PROCESS (TinselV2 ? 70 : 64) +#define MAX_PROCESSES 70 typedef void (*CORO_ADDR)(CoroContext &, const void *); +/** process structure */ +struct PROCESS { + PROCESS *pNext; //!< pointer to next process in active or free list + PROCESS *pPrevious; //!< pointer to previous process in active or free list -struct PROCESS; + 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 +}; +typedef PROCESS *PPROCESS; + +struct INT_CONTEXT; /** * Create and manage "processes" (really coroutines). @@ -64,11 +80,13 @@ private: /** the currently active process */ PROCESS *pCurrent; - + #ifdef DEBUG // diagnostic process counters int numProcs; int maxProcs; + + void CheckStack(); #endif /** @@ -90,13 +108,16 @@ public: #endif void schedule(); - + void rescheduleAll(); + void reschedule(PPROCESS pReSchedProc = NULL); + void giveWay(PPROCESS pReSchedProc = NULL); + PROCESS *createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam); void killProcess(PROCESS *pKillProc); PROCESS *getCurrentProcess(); int getCurrentPID() const; - int killMatchingProcess(int pidKill, int pidMask); + int killMatchingProcess(int pidKill, int pidMask = -1); void setResourceCallback(VFPTRPP pFunc); @@ -105,6 +126,23 @@ public: extern Scheduler *g_scheduler; // FIXME: Temporary global var, to be used until everything has been OOifyied +//----------------- FUNCTION PROTOTYPES -------------------- + +void SceneProcesses(uint32 numProcess, SCNHANDLE hProcess); +void CallSceneProcess(uint32 procID); +void KillSceneProcess(uint32 procID); +void SceneProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait, + int myEscape, bool *result = NULL); +void RestoreSceneProcess(INT_CONTEXT *pic); + +void GlobalProcesses(uint32 numProcess, byte *pProcess); +void xCallGlobalProcess(uint32 procID); +void xKillGlobalProcess(uint32 procID); +bool GlobalProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait, int myEscape); +void RestoreGlobalProcess(INT_CONTEXT *pic); +void KillGlobalProcesses(void); +void FreeGlobalProcesses(); + } // end of namespace Tinsel #endif // TINSEL_SCHED_H diff --git a/engines/tinsel/scn.cpp b/engines/tinsel/scn.cpp index 8639979b41..2825a90fe3 100644 --- a/engines/tinsel/scn.cpp +++ b/engines/tinsel/scn.cpp @@ -47,10 +47,15 @@ byte *FindChunk(SCNHANDLE handle, uint32 chunk) { uint32 *lptr = (uint32 *)bptr; uint32 add; - // V1 chunk types can be found by substracting 2 from the + // Initial adjustmnet for Tinsel 1 chunk types + if ((TinselVersion != TINSEL_V2) && (chunk >= CHUNK_SCENE) && + (chunk != CHUNK_MBSTRING)) + --chunk; + + // V0 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_V0 && + // the same in V0 and V1 + if (TinselVersion == TINSEL_V0 && chunk != CHUNK_STRING && chunk != CHUNK_BITMAP) chunk -= 0x2L; @@ -70,10 +75,10 @@ byte *FindChunk(SCNHANDLE handle, uint32 chunk) { /** * 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)); +int ExtractActor(SCNHANDLE hFilm) { + const FILM *pFilm = (const FILM *)LockMem(hFilm); + 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); } diff --git a/engines/tinsel/scn.h b/engines/tinsel/scn.h index 29f3dc51fc..5b16714b36 100644 --- a/engines/tinsel/scn.h +++ b/engines/tinsel/scn.h @@ -30,6 +30,14 @@ namespace Tinsel { +#define INDEX_FILENAME "index" // name of scene index file +#define INDEXFILE_LENGTH 12 // length of filenames in the MEMHANDLE structure + +#define GLOBALS_FILENAME "gdata" // name of globals file +#define HOPPER_FILENAME "hopper" +#define CD_ID_FILENAME "volume" + +#define BMOVIE_EXTENSION ".bmv" // chunk identifier numbers @@ -48,20 +56,42 @@ namespace Tinsel { #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 CHUNK_PROCESSES 0x3334000EL // Tinsel 2 only + +// Following chunk Ids should be decremented by 1 for Tinsel 1 +#define CHUNK_SCENE 0x3334000FL +#define CHUNK_TOTAL_ACTORS 0x33340010L +#define CHUNK_TOTAL_GLOBALS 0x33340011L +#define CHUNK_TOTAL_OBJECTS 0x33340012L +#define CHUNK_OBJECTS 0x33340013L +#define CHUNK_MIDI 0x33340014L // not used! +#define CHUNK_SAMPLE 0x33340015L // not used! +#define CHUNK_TOTAL_POLY 0x33340016L + +// Following chunks are Tinsel 2 only +#define CHUNK_NUM_PROCESSES 0x33340017L // Master scene only +#define CHUNK_MASTER_SCRIPT 0x33340018L +#define CHUNK_CDPLAY_FILENUM 0x33340019L +#define CHUNK_CDPLAY_HANDLE 0x3334001AL +#define CHUNK_CDPLAY_FILENAME 0x3334001BL +#define CHUNK_MUSIC_FILENAME 0x3334001CL +#define CHUNK_MUSIC_SCRIPT 0x3334001DL +#define CHUNK_MUSIC_SEGMENT 0x3334001EL +#define CHUNK_SCENE_HOPPER 0x3334001FL // Hopper file only +#define CHUNK_SCENE_HOPPER2 0x33340030L // Hopper file only +#define CHUNK_TIME_STAMPS 0x33340020L + +// This single chunk is common to all Tinsel versions +#define CHUNK_MBSTRING 0x33340022L + +// This is a base, subsequent numbers may also get used +#define CHUNK_GRAB_NAME 0x33340100L #define INDEX_FILENAME "index" // name of index file byte *FindChunk(SCNHANDLE handle, uint32 chunk); -int extractActor(SCNHANDLE film); +int ExtractActor(SCNHANDLE hFilm); } // end of namespace Tinsel diff --git a/engines/tinsel/scroll.cpp b/engines/tinsel/scroll.cpp index aa1bc67298..579775c91f 100644 --- a/engines/tinsel/scroll.cpp +++ b/engines/tinsel/scroll.cpp @@ -33,17 +33,11 @@ #include "tinsel/rince.h" #include "tinsel/scroll.h" #include "tinsel/sched.h" +#include "tinsel/sysvar.h" +#include "tinsel/tinsel.h" namespace Tinsel { -//----------------- EXTERNAL FUNCTIONS --------------------- - -// in BG.C -extern int BackgroundWidth(void); -extern int BackgroundHeight(void); - - - //----------------- LOCAL DEFINES -------------------- #define LEFT 'L' @@ -58,7 +52,7 @@ extern int BackgroundHeight(void); static int LeftScroll = 0, DownScroll = 0; // Number of iterations outstanding static int scrollActor = 0; -static PMACTOR psActor = 0; +static PMOVER pScrollMover = 0; static int oldx = 0, oldy = 0; /** Boundaries and numbers of boundaries */ @@ -72,6 +66,14 @@ static SCROLLDATA sd = { {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} }, 0, + 0, + // DW2 fields + 0, + 0, + 0, + 0, + 0, + 0, 0 }; @@ -81,7 +83,8 @@ 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; +static int scrollPixelsX = SCROLLPIXELS; +static int scrollPixelsY = SCROLLPIXELS; /** @@ -123,13 +126,6 @@ void SetNoScroll(int x1, int y1, int x2, int y2) { } } -/** - * 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. @@ -157,8 +153,13 @@ static void NeedScroll(int direction) { } if (LeftScroll <= 0) { - scrollPixels = SCROLLPIXELS; - LeftScroll = RLSCROLL; + if (TinselV2) { + scrollPixelsX = sd.xSpeed; + LeftScroll += sd.xDistance; + } else { + scrollPixelsX = SCROLLPIXELS; + LeftScroll = RLSCROLL; + } } break; @@ -175,8 +176,13 @@ static void NeedScroll(int direction) { } if (LeftScroll >= 0) { - scrollPixels = SCROLLPIXELS; - LeftScroll = -RLSCROLL; + if (TinselV2) { + scrollPixelsX = sd.xSpeed; + LeftScroll -= sd.xDistance; + } else { + scrollPixelsX = SCROLLPIXELS; + LeftScroll = -RLSCROLL; + } } break; @@ -194,8 +200,13 @@ static void NeedScroll(int direction) { } if (DownScroll <= 0) { - scrollPixels = SCROLLPIXELS; - DownScroll = UDSCROLL; + if (TinselV2) { + scrollPixelsY = sd.ySpeed; + DownScroll += sd.yDistance; + } else { + scrollPixelsY = SCROLLPIXELS; + DownScroll = UDSCROLL; + } } break; @@ -212,8 +223,13 @@ static void NeedScroll(int direction) { } if (DownScroll >= 0) { - scrollPixels = SCROLLPIXELS; - DownScroll = -UDSCROLL; + if (TinselV2) { + scrollPixelsY = sd.ySpeed; + DownScroll -= sd.yDistance; + } else { + scrollPixelsY = SCROLLPIXELS; + DownScroll = -UDSCROLL; + } } break; } @@ -234,7 +250,7 @@ static void ScrollImage(void) { * Keeping cursor on a tag? */ if (ScrollCursor) { - GetCursorXY(&curX, &curY, true); + GetCursorXYNoWait(&curX, &curY, true); if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) { OldLoffset = Loffset; OldToffset = Toffset; @@ -246,49 +262,66 @@ static void ScrollImage(void) { * Horizontal scrolling */ if (LeftScroll > 0) { - LeftScroll -= scrollPixels; + LeftScroll -= scrollPixelsX; if (LeftScroll < 0) { Loffset += LeftScroll; LeftScroll = 0; } - Loffset += scrollPixels; // Move right + Loffset += scrollPixelsX; // Move right if (Loffset > ImageW - SCREEN_WIDTH) Loffset = ImageW - SCREEN_WIDTH;// Now at extreme right + + /*** New feature to prop up rickety scroll boundaries ***/ + if (TinselV2 && SysVar(SV_MaximumXoffset) && (Loffset > SysVar(SV_MaximumXoffset))) + Loffset = SysVar(SV_MaximumXoffset); + } else if (LeftScroll < 0) { - LeftScroll += scrollPixels; + LeftScroll += scrollPixelsX; if (LeftScroll > 0) { Loffset += LeftScroll; LeftScroll = 0; } - Loffset -= scrollPixels; // Move left + Loffset -= scrollPixelsX; // Move left if (Loffset < 0) Loffset = 0; // Now at extreme left + + /*** New feature to prop up rickety scroll boundaries ***/ + if (TinselV2 && SysVar(SV_MinimumXoffset) && (Loffset < SysVar(SV_MinimumXoffset))) + Loffset = SysVar(SV_MinimumXoffset); } /* * Vertical scrolling */ if (DownScroll > 0) { - DownScroll -= scrollPixels; + DownScroll -= scrollPixelsY; if (DownScroll < 0) { Toffset += DownScroll; DownScroll = 0; } - Toffset += scrollPixels; // Move down + Toffset += scrollPixelsY; // Move down if (Toffset > ImageH - SCREEN_HEIGHT) Toffset = ImageH - SCREEN_HEIGHT;// Now at extreme bottom + /*** New feature to prop up rickety scroll boundaries ***/ + if (TinselV2 && SysVar(SV_MaximumYoffset) && Toffset > SysVar(SV_MaximumYoffset)) + Toffset = SysVar(SV_MaximumYoffset); + } else if (DownScroll < 0) { - DownScroll += scrollPixels; + DownScroll += scrollPixelsY; if (DownScroll > 0) { Toffset += DownScroll; DownScroll = 0; } - Toffset -= scrollPixels; // Move up + Toffset -= scrollPixelsY; // Move up if (Toffset < 0) Toffset = 0; // Now at extreme top + + /*** New feature to prop up rickety scroll boundaries ***/ + if (TinselV2 && SysVar(SV_MinimumYoffset) && Toffset < SysVar(SV_MinimumYoffset)) + Toffset = SysVar(SV_MinimumYoffset); } /* @@ -312,8 +345,7 @@ static void MonitorScroll(void) { /* * Only do it if the actor is there and is visible */ - if (!psActor || getMActorHideState(psActor) - || getMActorState(psActor) == NO_MACTOR) + if (!pScrollMover || MoverHidden(pScrollMover) || !MoverIs(pScrollMover)) return; GetActorPos(scrollActor, &newx, &newy); @@ -326,7 +358,7 @@ static void MonitorScroll(void) { /* * Approaching right side or left side of the screen? */ - if (newx > Loffset+SCREEN_WIDTH-RLDISTANCE && Loffset < ImageW-SCREEN_WIDTH) { + if (newx > Loffset+SCREEN_WIDTH - RLDISTANCE && Loffset < ImageW - SCREEN_WIDTH) { if (newx > oldx) NeedScroll(LEFT); } else if (newx < Loffset + RLDISTANCE && Loffset) { @@ -337,10 +369,10 @@ static void MonitorScroll(void) { /* * Approaching bottom or top of the screen? */ - if (newy > Toffset+SCREEN_HEIGHT-UDDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) { + if (newy > Toffset+SCREEN_HEIGHT-DDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) { if (newy > oldy) NeedScroll(UP); - } else if (Toffset && newy < Toffset + UDDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) { + } else if (Toffset && newy < Toffset + UDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) { if (newy < oldy) NeedScroll(DOWN); } @@ -349,6 +381,30 @@ static void MonitorScroll(void) { oldy = newy; } +static void RestoreScrollDefaults(void) { + sd.xTrigger = SysVar(SV_SCROLL_XTRIGGER); + sd.xDistance = SysVar(SV_SCROLL_XDISTANCE); + sd.xSpeed = SysVar(SV_SCROLL_XSPEED); + sd.yTriggerTop = SysVar(SV_SCROLL_YTRIGGERTOP); + sd.yTriggerBottom= SysVar(SV_SCROLL_YTRIGGERBOT); + sd.yDistance = SysVar(SV_SCROLL_YDISTANCE); + sd.ySpeed = SysVar(SV_SCROLL_YSPEED); +} + +/** + * Does the obvious - called at the end of a scene. + */ +void DropScroll(void) { + sd.NumNoH = sd.NumNoV = 0; + if (TinselV2) { + LeftScroll = DownScroll = 0; // No iterations outstanding + oldx = oldy = 0; + scrollPixelsX = sd.xSpeed; + scrollPixelsY = sd.ySpeed; + RestoreScrollDefaults(); + } +} + /** * Decide when to scroll and scroll when decided to. */ @@ -359,21 +415,28 @@ void ScrollProcess(CORO_PARAM, const void *) { CORO_BEGIN_CODE(_ctx); - ImageH = BackgroundHeight(); // Dimensions - ImageW = BackgroundWidth(); // of this scene. + // In Tinsel v2, scenes may play movies, so the background may not always + // already be initialised like it is in v1 + while (!GetBgObject()) + CORO_SLEEP(1); + + ImageH = BgHeight(); // Dimensions + ImageW = BgWidth(); // 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 (!TinselV2) { + LeftScroll = DownScroll = 0; // No iterations outstanding + oldx = oldy = 0; + scrollPixelsX = scrollPixelsY = SCROLLPIXELS; + } if (!scrollActor) - scrollActor = LeadId(); + scrollActor = GetLeadId(); - psActor = GetMover(scrollActor); + pScrollMover = GetMover(scrollActor); while (1) { MonitorScroll(); // Set scroll requirement @@ -395,17 +458,26 @@ void ScrollFocus(int ano) { oldx = oldy = 0; scrollActor = ano; - psActor = ano ? GetMover(scrollActor) : NULL; + pScrollMover = ano ? GetMover(scrollActor) : NULL; } } +/** + * Returns the actor which the camera is following + */ +int GetScrollFocus(void) { + return scrollActor; +} + + /** * Scroll to abslote position. */ -void ScrollTo(int x, int y, int iter) { +void ScrollTo(int x, int y, int xIter, int yIter) { int Loffset, Toffset; // for background offsets - scrollPixels = iter != 0 ? iter : SCROLLPIXELS; + scrollPixelsX = xIter != 0 ? xIter : (TinselV2 ? sd.xSpeed : SCROLLPIXELS); + scrollPixelsY = yIter != 0 ? yIter : (TinselV2 ? sd.ySpeed : SCROLLPIXELS); PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); // get background offsets @@ -429,4 +501,35 @@ void RestoreNoScrollData(SCROLLDATA *ssd) { memcpy(&sd, ssd, sizeof(SCROLLDATA)); } +/** + * SetScrollParameters + */ +void SetScrollParameters(int xTrigger, int xDistance, int xSpeed, int yTriggerTop, + int yTriggerBottom, int yDistance, int ySpeed) { + if (xTrigger == 0 && xDistance == 0 && xSpeed == 0 + && yTriggerTop == 0 && yTriggerBottom && yDistance == 0 && ySpeed == 0) { + // Restore defaults + RestoreScrollDefaults(); + } else { + if (xTrigger) + sd.xTrigger = xTrigger; + if (xDistance) + sd.xDistance = xDistance; + if (xSpeed) + sd.xSpeed = xSpeed; + if (yTriggerTop) + sd.yTriggerTop = yTriggerTop; + if (yTriggerBottom) + sd.yTriggerBottom = yTriggerBottom; + if (yDistance) + sd.yDistance = yDistance; + if (ySpeed) + sd.ySpeed = ySpeed; + } +} + +bool IsScrolling(void) { + return (LeftScroll || DownScroll); +} + } // end of namespace Tinsel diff --git a/engines/tinsel/scroll.h b/engines/tinsel/scroll.h index ac903157f2..864fe74beb 100644 --- a/engines/tinsel/scroll.h +++ b/engines/tinsel/scroll.h @@ -30,8 +30,10 @@ 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 +// Distance from edge that triggers a scroll +#define RLDISTANCE (TinselV2 ? sd.xTrigger : 50) +#define UDISTANCE (TinselV2 ? sd.yTriggerTop : 20) +#define DDISTANCE (TinselV2 ? sd.yTriggerBottom : 20) // Number of iterations to make #define RLSCROLL 160 // 20*8 = 160 = half a screen @@ -52,26 +54,39 @@ 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 + // DW2 fields + int xTrigger; + int xDistance; + int xSpeed; + int yTriggerTop; + int yTriggerBottom; + int yDistance; + int ySpeed; }; - void DontScrollCursor(void); void DoScrollCursor(void); void SetNoScroll(int x1, int y1, int x2, int y2); -void DropNoScrolls(void); +void DropScroll(void); void ScrollProcess(CORO_PARAM, const void *); void ScrollFocus(int actor); -void ScrollTo(int x, int y, int iter); +int GetScrollFocus(void); +void ScrollTo(int x, int y, int xIter, int yIter); void KillScroll(void); void GetNoScrollData(SCROLLDATA *ssd); void RestoreNoScrollData(SCROLLDATA *ssd); +void SetScrollParameters(int xTrigger, int xDistance, int xSpeed, int yTriggerTop, + int yTriggerBottom, int yDistance, int ySpeed); + +bool IsScrolling(void); + } // end of namespace Tinsel #endif /* TINSEL_SCROLL_H */ diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp index e37c80ec61..9d1996ca01 100644 --- a/engines/tinsel/sound.cpp +++ b/engines/tinsel/sound.cpp @@ -31,6 +31,8 @@ #include "tinsel/music.h" #include "tinsel/strres.h" #include "tinsel/tinsel.h" +#include "tinsel/sysvar.h" +#include "tinsel/background.h" #include "common/endian.h" #include "common/file.h" @@ -38,14 +40,20 @@ #include "sound/mixer.h" #include "sound/audiocd.h" +#include "sound/adpcm.h" namespace Tinsel { +extern LANGUAGE sampleLanguage; + //--------------------------- General data ---------------------------------- SoundManager::SoundManager(TinselEngine *vm) : //_vm(vm), // TODO: Enable this once global _vm var is gone _sampleIndex(0), _sampleIndexLen(0) { + + for (int i = 0; i < kNumChannels; i++) + _channels[i].sampleNum = _channels[i].subSample = -1; } SoundManager::~SoundManager() { @@ -67,24 +75,29 @@ bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::Sound if (!_vm->_mixer->isReady()) return false; + Channel &curChan = _channels[kChannelTinsel1]; + // stop any currently playing sample - _vm->_mixer->stopHandle(_handle); + _vm->_mixer->stopHandle(curChan.handle); // make sure id is in range assert(id > 0 && id < _sampleIndexLen); + curChan.sampleNum = id; + curChan.subSample = 0; + // get file offset for this sample - int32 dwSampleIndex = _sampleIndex[id]; + 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); + if (_sampleStream.ioFailed() || (uint32)_sampleStream.pos() != dwSampleIndex) + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); // read the length of the sample uint32 sampleLen = _sampleStream.readUint32LE(); if (_sampleStream.ioFailed()) - error("File %s is corrupt", SAMPLE_FILE); + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); // allocate a buffer void *sampleBuf = malloc(sampleLen); @@ -92,7 +105,7 @@ bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::Sound // read all of the sample if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen) - error("File %s is corrupt", SAMPLE_FILE); + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); // FIXME: Should set this in a different place ;) _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volSound); @@ -101,15 +114,211 @@ bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::Sound // play it - _vm->_mixer->playRaw(type, &_handle, sampleBuf, sampleLen, 22050, + _vm->_mixer->playRaw(type, &curChan.handle, sampleBuf, sampleLen, 22050, Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED); if (handle) - *handle = _handle; + *handle = curChan.handle; return true; } +bool SoundManager::playSample(int id, int sub, bool bLooped, int x, int y, int priority, + 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; + + Channel *curChan; + + uint8 sndVol = 255; + + // Sample on screen? + if (!offscreenChecks(x, y)) + return false; + + // If that sample is already playing, stop it + stopSpecSample(id, sub); + + if (type == Audio::Mixer::kSpeechSoundType) { + curChan = &_channels[kChannelTalk]; + } else if (type == Audio::Mixer::kSFXSoundType) { + uint32 oldestTime = g_system->getMillis(); + int oldestChan = kChannelSFX; + + int chan; + for (chan = kChannelSFX; chan < kNumChannels; chan++) { + if (!_vm->_mixer->isSoundHandleActive(_channels[chan].handle)) + break; + + if ((_channels[chan].lastStart < oldestTime) && + (_channels[chan].priority <= priority)) { + + oldestTime = _channels[chan].lastStart; + oldestChan = chan; + } + } + + if (chan == kNumChannels) { + if (_channels[oldestChan].priority > priority) { + warning("playSample: No free channel"); + return false; + } + + chan = oldestChan; + } + + if (_vm->_pcmMusic->isDimmed() && SysVar(SYS_SceneFxDimFactor)) + sndVol = 255 - 255/SysVar(SYS_SceneFxDimFactor); + + curChan = &_channels[chan]; + } else { + warning("playSample: Unknown SoundType"); + return false; + } + + // stop any currently playing sample + _vm->_mixer->stopHandle(curChan->handle); + + // make sure id is in range + assert(id > 0 && id < _sampleIndexLen); + + // get file offset for this sample + uint32 dwSampleIndex = _sampleIndex[id]; + + if (dwSampleIndex == 0) { + warning("Tinsel2 playSample, non-existant sample %d", id); + return false; + } + + // move to correct position in the sample file + _sampleStream.seek(dwSampleIndex); + if (_sampleStream.ioFailed() || (uint32)_sampleStream.pos() != dwSampleIndex) + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); + + // read the length of the sample + uint32 sampleLen = _sampleStream.readUint32LE(); + if (_sampleStream.ioFailed()) + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); + + if (sampleLen & 0x80000000) { + // Has sub samples + + int32 numSubs = sampleLen & ~0x80000000; + + assert(sub >= 0 && sub < numSubs); + + // Skipping + for (int32 i = 0; i < sub; i++) { + sampleLen = _sampleStream.readUint32LE(); + _sampleStream.skip(sampleLen); + if (_sampleStream.ioFailed()) + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); + } + sampleLen = _sampleStream.readUint32LE(); + if (_sampleStream.ioFailed()) + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); + } + + debugC(DEBUG_DETAILED, kTinselDebugSound, "Playing sound %d.%d, %d bytes at %d (pan %d)", id, sub, sampleLen, + _sampleStream.pos(), getPan(x)); + + // allocate a buffer + byte *sampleBuf = (byte *) malloc(sampleLen); + assert(sampleBuf); + + // read all of the sample + if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen) + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); + + Common::MemoryReadStream *sampleStream = + new Common::MemoryReadStream(sampleBuf, sampleLen, true); + Audio::AudioStream *_stream = + makeADPCMStream(sampleStream, true, sampleLen, Audio::kADPCMTinsel6, 22050, 1, 24); + + // 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); + + curChan->sampleNum = id; + curChan->subSample = sub; + curChan->looped = bLooped; + curChan->x = x; + curChan->y = y; + curChan->priority = priority; + curChan->lastStart = g_system->getMillis(); + // /---Compression----\ Milis BytesPerSecond + curChan->timeDuration = (((sampleLen * 64) / 25) * 1000) / (22050 * 2); + + // Play it + _vm->_mixer->playInputStream(type, &curChan->handle, _stream); + _vm->_mixer->setChannelVolume(curChan->handle, sndVol); + _vm->_mixer->setChannelBalance(curChan->handle, getPan(x)); + + if (handle) + *handle = curChan->handle; + + return true; +} + +/** + * Returns FALSE if sample doesn't need playing + */ +bool SoundManager::offscreenChecks(int x, int &y) +{ + // No action if no x specification + if (x == -1) + return true; + + // convert x to offset from screen centre + x -= PlayfieldGetCentreX(FIELD_WORLD); + + if (x < -SCREEN_WIDTH || x > SCREEN_WIDTH) { + // A long way offscreen, ignore it + return false; + } else if (x < -SCREEN_WIDTH/2 || x > SCREEN_WIDTH/2) { + // Off-screen, attennuate it + + y = (y > 0) ? (y / 2) : 50; + + return true; + } else + return true; +} + +int8 SoundManager::getPan(int x) { + + if (x == -1) + return 0; + + x -= PlayfieldGetCentreX(FIELD_WORLD); + + if (x == 0) + return 0; + + if (x < 0) { + if (x < (-SCREEN_WIDTH / 2)) + return -127; + + x = (-x * 127) / (SCREEN_WIDTH / 2); + + return 0 - x; + } + + if (x > (SCREEN_WIDTH / 2)) + return 127; + + x = (x * 127) / (SCREEN_WIDTH / 2); + + return x; +} + /** * Returns TRUE if there is a sample for the specified sample identifier. * @param id Identifier of sample to be checked @@ -131,8 +340,16 @@ bool SoundManager::sampleExists(int id) { /** * Returns true if a sample is currently playing. */ -bool SoundManager::sampleIsPlaying(void) { - return _vm->_mixer->isSoundHandleActive(_handle); +bool SoundManager::sampleIsPlaying(int id) { + if (!TinselV2) + return _vm->_mixer->isSoundHandleActive(_channels[kChannelTinsel1].handle); + + for (int i = 0; i < kNumChannels; i++) + if (_channels[i].sampleNum == id) + if (_vm->_mixer->isSoundHandleActive(_channels[i].handle)) + return true; + + return false; } /** @@ -140,7 +357,37 @@ bool SoundManager::sampleIsPlaying(void) { */ void SoundManager::stopAllSamples(void) { // stop currently playing sample - _vm->_mixer->stopHandle(_handle); + + if (!TinselV2) { + _vm->_mixer->stopHandle(_channels[kChannelTinsel1].handle); + return; + } + + for (int i = 0; i < kNumChannels; i++) + _vm->_mixer->stopHandle(_channels[i].handle); +} + +void SoundManager::stopSpecSample(int id, int sub) { + debugC(DEBUG_DETAILED, kTinselDebugSound, "stopSpecSample(%d, %d)", id, sub); + + if (!TinselV2) { + if (_channels[kChannelTinsel1].sampleNum == id) + _vm->_mixer->stopHandle(_channels[kChannelTinsel1].handle); + return; + } + + for (int i = kChannelTalk; i < kNumChannels; i++) { + if ((_channels[i].sampleNum == id) && (_channels[i].subSample == sub)) + _vm->_mixer->stopHandle(_channels[i].handle); + } +} + +void SoundManager::setSFXVolumes(uint8 volume) { + if (!TinselV2) + return; + + for (int i = kChannelSFX; i < kNumChannels; i++) + _vm->_mixer->setChannelVolume(_channels[i].handle, volume); } /** @@ -158,7 +405,7 @@ void SoundManager::openSampleFiles(void) { return; // open sample index file in binary mode - if (f.open(SAMPLE_INDEX)) { + if (f.open(_vm->getSampleIndex(sampleLanguage))) { // get length of index file f.seek(0, SEEK_END); // move to end of file _sampleIndexLen = f.pos(); // get file pointer @@ -166,7 +413,7 @@ void SoundManager::openSampleFiles(void) { if (_sampleIndex == NULL) { // allocate a buffer for the indices - _sampleIndex = (int32 *)malloc(_sampleIndexLen); + _sampleIndex = (uint32 *)malloc(_sampleIndexLen); // make sure memory allocated if (_sampleIndex == NULL) { @@ -179,7 +426,7 @@ void SoundManager::openSampleFiles(void) { // 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); + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); #ifdef SCUMM_BIG_ENDIAN // Convert all ids from LE to native format @@ -194,18 +441,25 @@ void SoundManager::openSampleFiles(void) { // convert file size to size in DWORDs _sampleIndexLen /= sizeof(uint32); } else - error("Cannot find file %s", SAMPLE_INDEX); + error(CANNOT_FIND_FILE, _vm->getSampleIndex(sampleLanguage)); // open sample file in binary mode - if (!_sampleStream.open(SAMPLE_FILE)) - error("Cannot find file %s", SAMPLE_FILE); + if (!_sampleStream.open(_vm->getSampleFile(sampleLanguage))) + error(CANNOT_FIND_FILE, _vm->getSampleFile(sampleLanguage)); /* // gen length of the largest sample sampleBuffer.size = _sampleStream.readUint32LE(); if (_sampleStream.ioFailed()) - error("File %s is corrupt", SAMPLE_FILE); + error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage)); */ } +void SoundManager::closeSampleStream(void) { + _sampleStream.close(); + free(_sampleIndex); + _sampleIndex = 0; + _sampleIndexLen = 0; +} + } // end of namespace Tinsel diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h index 330409cf59..9b980822dc 100644 --- a/engines/tinsel/sound.h +++ b/engines/tinsel/sound.h @@ -37,6 +37,11 @@ namespace Tinsel { +enum STYPE {FX, VOICE}; + +enum PlayPriority { PRIORITY_SCRIPT, PRIORITY_SPLAY1, PRIORITY_SPLAY2, PRIORITY_TALK }; + +enum SampleFlags {PS_COMPLETE = 0x01, PS_SUSTAIN = 0x02}; /*----------------------------------------------------------------------*\ |* Function Prototypes *| @@ -44,14 +49,39 @@ namespace Tinsel { class SoundManager { protected: + static const int kNumSFX = 3; // Number of SFX channels + enum { + kChannelTalk = 0, + kChannelTinsel1 = 0, // Always using this channel for DW1 + kChannelSFX = 1 + }; + static const int kNumChannels = kChannelSFX + kNumSFX; + + struct Channel { + // Sample handle + Audio::SoundHandle handle; + + // Sample id + int sampleNum; + int subSample; + + // Playing properties + bool looped; + int x, y; + int priority; + + // Time properties + uint32 timeStarted; + uint32 timeDuration; + uint32 lastStart; + }; + + Channel _channels[kNumChannels]; //TinselEngine *_vm; // TODO: Enable this once global _vm var is gone - /** Sample handle */ - Audio::SoundHandle _handle; - /** Sample index buffer and number of entries */ - int32 *_sampleIndex; + uint32 *_sampleIndex; /** Number of entries in the sample index */ long _sampleIndexLen; @@ -59,19 +89,29 @@ protected: /** file stream for sample file */ Common::File _sampleStream; + bool offscreenChecks(int x, int &y); + int8 getPan(int x); + 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 playSample(int id, int sub, bool bLooped, int x, int y, int priority, + Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0); + + void stopAllSamples(void); // Stops any currently playing sample + void stopSpecSample(int id, int sub = 0); // Stops a specific sample + void setSFXVolumes(uint8 volume); + bool sampleExists(int id); - bool sampleIsPlaying(void); + bool sampleIsPlaying(int id = -1); // TODO: Internal method, make this protected? void openSampleFiles(void); + void closeSampleStream(void); }; } // end of namespace Tinsel diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp index abf5a880f6..0997d68fbe 100644 --- a/engines/tinsel/strres.cpp +++ b/engines/tinsel/strres.cpp @@ -40,18 +40,39 @@ int newestString; // 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" +static struct { + bool bPresent; + const char *szStem; + SCNHANDLE hDescription; + SCNHANDLE hFlagFilm; + +} languages[NUM_LANGUAGES] = { + + { false, "English", 0, 0 }, + { false, "French", 0, 0 }, + { false, "German", 0, 0 }, + { false, "Italian", 0, 0 }, + { false, "Spanish", 0, 0 }, + { false, "Hebrew", 0, 0 }, + { false, "Magyar", 0, 0 }, + { false, "Japanese",0, 0 }, + { false, "US", 0, 0 } }; + // Set if we're handling 2-byte characters. bool bMultiByte = false; +LANGUAGE textLanguage, sampleLanguage = TXT_ENGLISH; + +//----------------- LOCAL DEFINES ---------------------------- + +#define languageExtension ".txt" +#define indexExtension ".idx" +#define sampleExtension ".smp" + +//----------------- FUNCTIONS -------------------------------- + /** * Called to load a resource file for a different language * @param newLang The new language @@ -59,6 +80,9 @@ bool bMultiByte = false; void ChangeLanguage(LANGUAGE newLang) { Common::File f; uint32 textLen = 0; // length of buffer + + textLanguage = newLang; + sampleLanguage = newLang; if (textBuffer) { // free the previous buffer @@ -69,9 +93,9 @@ void ChangeLanguage(LANGUAGE newLang) { // 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]); + if (!f.open(_vm->getTextFile(newLang))) { + if ((newLang == TXT_ENGLISH) || !f.open(_vm->getTextFile(TXT_ENGLISH))) + error(CANNOT_FIND_FILE, _vm->getTextFile(newLang)); } // Check whether the file is compressed or not - for compressed files the @@ -79,7 +103,7 @@ void ChangeLanguage(LANGUAGE newLang) { // identifier textLen = f.readUint32LE(); if (f.ioFailed()) - error("File %s is corrupt", languageFiles[newLang]); + error(FILE_IS_CORRUPT, _vm->getTextFile(newLang)); if (textLen == CHUNK_STRING || textLen == CHUNK_MBSTRING) { // the file is uncompressed @@ -101,7 +125,7 @@ void ChangeLanguage(LANGUAGE newLang) { // load data if (f.read(textBuffer, textLen) != textLen) // file must be corrupt if we get to here - error("File %s is corrupt", languageFiles[newLang]); + error(FILE_IS_CORRUPT, _vm->getTextFile(newLang)); // close the file f.close(); @@ -111,19 +135,11 @@ 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 + * FindStringBase */ -int LoadStringRes(int id, char *pBuffer, int bufferMax) { -#ifdef DEBUG - // For diagnostics - newestString = id; -#endif - +static byte *FindStringBase(int id) { // base of string resource table - uint8 *pText = textBuffer; + byte *pText = textBuffer; // index into text resource file uint32 index = 0; @@ -134,20 +150,14 @@ int LoadStringRes(int id, char *pBuffer, int bufferMax) { // 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; + return NULL; } // get index to next chunk @@ -163,17 +173,114 @@ int LoadStringRes(int id, char *pBuffer, int bufferMax) { // skip to the correct string while (strSkip-- != 0) { // skip to next string - pText += *pText + 1; + + if (!TinselV2 || ((*pText & 0x80) == 0)) { + // Tinsel 1, or string of length < 128 + pText += *pText + 1; + } else if (*pText == 0x80) { + // string of length 128 - 255 + pText++; // skip control byte + pText += *pText + 1; + } else if (*pText == 0x90) { + // string of length 256 - 511 + pText++; // skip control byte + pText += *pText + 1 + 256; + } else { // multiple string + int subCount; + + subCount = *pText & ~0x80; + pText++; // skip control byte + + // skip prior sub-strings + while (subCount--) { + // skip control byte, if there is one + if (*pText == 0x80) { + pText++; + pText += *pText + 1; + } else if (*pText == 0x90) { + pText++; + pText += *pText + 1 + 256; + } else + pText += *pText + 1; + } + } } - // get length of string - len = *pText; + return pText; +} - if (len) { + +/** + * 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 LoadStringResource(int id, int sub, char *pBuffer, int bufferMax) { + int len; // length of string + + byte *pText = FindStringBase(id); + + if (pText == NULL) { + strcpy(pBuffer, "!! HIGH STRING !!"); + return 0; + } + + if (!TinselV2 || ((*pText & 0x80) == 0)) { + // get length of string + len = *pText; + } else if (*pText == 0x80) { + // string of length 128 - 255 + pText++; // skip control byte + + // get length of string + len = *pText; + } else if (*pText == 0x90) { + // string of length 128 - 255 + pText++; // skip control byte + + // get length of string + len = *pText + 256; + } else { + // multiple string + pText++; // skip control byte + + // skip prior sub-strings + while (sub--) { + // skip control byte, if there is one + if (*pText == 0x80) { + pText++; + pText += *pText + 1; + } else if (*pText == 0x90) { + pText++; + pText += *pText + 1 + 256; + } else + pText += *pText + 1; + } + // skip control byte, if there is one + if (*pText == 0x80) { + pText++; + + // get length of string + len = *pText; + } else if (*pText == 0x90) { + pText++; + + // get length of string + len = *pText + 256; + } else { + // get length of string + len = *pText; + } + } + + if (len) + { // the string exists // copy the string to the buffer - if (len < bufferMax) { + if (len < bufferMax) + { memcpy(pBuffer, pText + 1, len); // null terminate @@ -199,6 +306,42 @@ int LoadStringRes(int id, char *pBuffer, int bufferMax) { return 0; } +int LoadStringRes(int id, char *pBuffer, int bufferMax) { + return LoadStringResource(id, 0, pBuffer, bufferMax); +} + +/** + * Loads a string resource identified by id + * @param id identifier of string to be loaded + * @param sub sub-string number + * @param pBuffer points to buffer that receives the string + * @param bufferMax maximum number of chars to be copied to the buffer + */ +int LoadSubString(int id, int sub, char *pBuffer, int bufferMax) { + return LoadStringResource(id, sub, pBuffer, bufferMax); +} + +/** + * SubStringCount + * @param id Identifier of string to be tested + */ +int SubStringCount(int id) { + byte *pText; + + pText = FindStringBase(id); + + if (pText == NULL) + return 0; + + if ((*pText & 0x80) == 0 || *pText == 0x80 || *pText == 0x90) { + // string of length < 128 or string of length 128 - 255 + // or of length 256 - 511 + return 1; + } else + return (*pText & ~0x80); +} + + void FreeTextBuffer() { if (textBuffer) { free(textBuffer); @@ -206,4 +349,81 @@ void FreeTextBuffer() { } } +/** + * Called from TINLIB.C from DeclareLanguage(). + */ + +void LanguageFacts(int language, SCNHANDLE hDescription, SCNHANDLE hFlagFilm) { + assert(language >= 0 && language < NUM_LANGUAGES); + + languages[language].hDescription = hDescription; + languages[language].hFlagFilm = hFlagFilm; +} + +/** + * Gets the current subtitles language + */ +LANGUAGE TextLanguage(void) { + return textLanguage; +} + +/** + * Gets the current voice language + */ +LANGUAGE SampleLanguage(void) { + return sampleLanguage; +} + +int NumberOfLanguages(void) { + int i, count; + + for (i = 0, count = 0; i < NUM_LANGUAGES; i++) { + if (languages[i].bPresent) + count++; + } + return count; +} + +LANGUAGE NextLanguage(LANGUAGE thisOne) { + int i; + + for (i = thisOne+1; i < NUM_LANGUAGES; i++) { + if (languages[i].bPresent) + return (LANGUAGE)i; + } + + for (i = 0; i < thisOne; i++) { + if (languages[i].bPresent) + return (LANGUAGE)i; + } + + // No others! + return thisOne; +} + +LANGUAGE PrevLanguage(LANGUAGE thisOne) { + int i; + + for (i = thisOne-1; i >= 0; i--) { + if (languages[i].bPresent) + return (LANGUAGE)i; + } + + for (i = NUM_LANGUAGES-1; i > thisOne; i--) { + if (languages[i].bPresent) + return (LANGUAGE)i; + } + + // No others! + return thisOne; +} + +SCNHANDLE LanguageDesc(LANGUAGE thisOne) { + return languages[thisOne].hDescription; +} + +SCNHANDLE LanguageFlag(LANGUAGE thisOne) { + return languages[thisOne].hFlagFilm; +} + } // end of namespace Tinsel diff --git a/engines/tinsel/strres.h b/engines/tinsel/strres.h index fac287492b..c996317b21 100644 --- a/engines/tinsel/strres.h +++ b/engines/tinsel/strres.h @@ -58,11 +58,44 @@ void ChangeLanguage(LANGUAGE newLang); */ int LoadStringRes(int id, char *pBuffer, int bufferMax); +/** + * Loads a string resource identified by id + * @param id identifier of string to be loaded + * @param sub sub-string number + * @param pBuffer points to buffer that receives the string + * @param bufferMax maximum number of chars to be copied to the buffer + */ +int LoadSubString(int id, int sub, char *pBuffer, int bufferMax); + +int SubStringCount(int id); // identifier of string to be tested + /** * Frees the text buffer allocated from ChangeLanguage() */ void FreeTextBuffer(); +/** + * Called from TINLIB.C from DeclareLanguage(). + */ + +void LanguageFacts(int language, SCNHANDLE hDescription, SCNHANDLE hFlagFilm); + +/** + * Gets the current subtitles language + */ +LANGUAGE TextLanguage(void); + +/** + * Gets the current voice language + */ +LANGUAGE SampleLanguage(void); + +int NumberOfLanguages(void); +LANGUAGE NextLanguage(LANGUAGE thisOne); +LANGUAGE PrevLanguage(LANGUAGE thisOne); +SCNHANDLE LanguageDesc(LANGUAGE thisOne); +SCNHANDLE LanguageFlag(LANGUAGE thisOne); + } // end of namespace Tinsel #endif diff --git a/engines/tinsel/sysvar.cpp b/engines/tinsel/sysvar.cpp new file mode 100644 index 0000000000..7196e7597d --- /dev/null +++ b/engines/tinsel/sysvar.cpp @@ -0,0 +1,218 @@ +/* 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$ + * + * System variable handling. + */ + +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/dialogs.h" +#include "tinsel/strres.h" +#include "tinsel/sysvar.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +// Return for SYS_Platform +typedef enum { DOS_PC, WIN_PC, APPLE_MAC, SONY_PSX, SEGA_SATURN } platform; + +//----------------- GLOBAL GLOBAL DATA -------------------- + +// To prevent assembler from needing to call SysVar() +uint8 ghostColour; + +extern int NewestSavedGame(void); + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int systemVars[SV_TOPVALID] = { + + INV_1, // Default inventory + + 10, // Y-offset of Conversation(TOP) + 320, // Y-offset of Conversation(BOT) + 15, // Minimum distance from side + 10, // Minimum distance from top + 115, // Distance above actor + 10, // Distance below actor + + 0, // Current language **READ ONLY** + 0, // Sample language **READ ONLY** + 0, // Current state **READ ONLY** + 0, // Saved Game Exists **READ ONLY** + + true, // Should Conversation() wait for scroll? [TRUE] + true, // Should Talk()/Say() wait for scroll? [TRUE] + + true, // Enable PointTag() + true, // Enable cursor with PrintCursor() + + 100, // SV_SCROLL_XTRIGGER + 0, // SV_SCROLL_XDISTANCE + 16, // SV_SCROLL_XSPEED + 40, // SV_SCROLL_YTRIGGERTOP + 40, // SV_SCROLL_YTRIGGERBOT + 0, // SV_SCROLL_YDISTANCE + 16, // SV_SCROLL_YSPEED + + 2, // Speech Delay + 2, // Music dim factor + + 0, // if set, default actor's text colour gets poked in here + + 0, // user 1 + 0, // user 2 + 0, // user 3 + 0, // user 4 + 0, // user 5 + 0, // user 6 + + 0, // SYS_MinimumXoffset + 0, // SYS_MaximumXoffset + 0, // SYS_MinimumYoffset + 0, // SYS_MaximumYoffset + + 0, // SYS_DefaultFxDimFactor + 0, // SYS_SceneFxDimFactor + + 0x606060, // SYS_HighlightRGB + WIN_PC, // SYS_Platform, + 0, // SYS_Debug + + 0, // ISV_DIVERT_ACTOR + false, // ISV_NO_BLOCKING + + 0, // ISV_GHOST_ACTOR + 0, // ISV_GHOST_BASE + 0 // ISV_GHOST_COLOUR +}; + +static SCNHANDLE systemStrings[SS_MAX_VALID]; + +//static bool bFlagNoBlocking = false; + +//----------------- FUNCTIONS -------------------------------- + +/** + * Initialises the system variable list + */ + +void InitSysVars() { + systemVars[SV_SCROLL_XDISTANCE] = SCREEN_WIDTH / 2; + systemVars[SV_SCROLL_YDISTANCE] = SCREEN_BOX_HEIGHT1 / 2; +} + +/** + * SetSysVar + */ + +void SetSysVar(int varId, int newValue) { + if (varId < 0 || varId >= SV_TOPVALID) + error("SetSystemVar(): out of range identifier"); + + switch (varId) { + case SV_LANGUAGE: + case SV_SAMPLE_LANGUAGE: + case SV_SUBTITLES: + case SV_SAVED_GAME_EXISTS: + case SYS_Platform: + case SYS_Debug: + error("SetSystemVar(): read only identifier"); + + default: + systemVars[varId] = newValue; + + if (varId == ISV_GHOST_COLOUR) { + ghostColour = (uint8)newValue; + } + } +} + +int SysVar(int varId) { + if (varId < 0 || varId >= SV_TOPVALID) + error("SystemVar(): out of range identifier"); + + switch (varId) { + case SV_LANGUAGE: + return TextLanguage(); + + case SV_SAMPLE_LANGUAGE: + return SampleLanguage(); + + case SV_SUBTITLES: + // FIXME: This isn't currently defined + return false; + //return bSubtitles; + + case SV_SAVED_GAME_EXISTS: + return NewestSavedGame() != -1; + + case SYS_Debug: + // FIXME: This isn't currently defined + return false; + //return bDebuggingAllowed; + + default: + return systemVars[varId]; + } +} + +void SaveSysVars(int *pSv) { + memcpy(pSv, systemVars, sizeof(systemVars)); +} + +void RestoreSysVars(int *pSv) { + memcpy(systemVars, pSv, sizeof(systemVars)); + + ghostColour = (uint8)SysVar(ISV_GHOST_COLOUR); +} + +void SetSysString(int number, SCNHANDLE hString) { + assert(number >= 0 && number < SS_MAX_VALID); + + systemStrings[number] = hString; +} + +SCNHANDLE SysString(int number) { + assert(number >= 0 && number < SS_MAX_VALID); + + return systemStrings[number]; +} + +/** + * Gets the no blocking flag. Note that for convenience, the systemVars arrray entry is + * used even for Tinsel 1, which used a separate boolean variable + */ +bool GetNoBlocking(void) { + return SysVar(ISV_NO_BLOCKING); +} + +/** + * Sets the no blocking flag. Note that for convenience, the systemVars arrray entry is + * used even for Tinsel 1, which used a separate boolean variable + */ +void SetNoBlocking(bool flag) { + SetSysVar(ISV_NO_BLOCKING, flag); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/sysvar.h b/engines/tinsel/sysvar.h new file mode 100644 index 0000000000..869a99caa3 --- /dev/null +++ b/engines/tinsel/sysvar.h @@ -0,0 +1,149 @@ +/* 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$ + * + * System variable handling. + */ + +#ifndef TINSEL_SYSVAR_H // prevent multiple includes +#define TINSEL_SYSVAR_H + +namespace Tinsel { + +typedef enum { SV_DEFAULT_INV, + + SV_CONV_TOPY, // Y-offset of Conversation(TOP) + SV_CONV_BOTY, // Y-offset of Conversation(BOT) + SV_CONV_MINX, // Minimum distance from side + SV_CONV_MINY, // Minimum distance from top + SV_CONV_ABOVE_Y, // Distance above actor + SV_CONV_BELOW_Y, // Distance below actor + + SV_LANGUAGE, + SV_SAMPLE_LANGUAGE, + SV_SUBTITLES, + SV_SAVED_GAME_EXISTS, + + SV_CONVERSATIONWAITS, // } Do they wait for + SV_SPEECHWAITS, // } scrolls to complete? + + SV_ENABLEPOINTTAG, // Enable PointTag() + SV_ENABLEPRINTCURSOR, // Enable cursor with PrintCursor() + + SV_SCROLL_XTRIGGER, // } + SV_SCROLL_XDISTANCE, // } + SV_SCROLL_XSPEED, // } Scroll parameters! + SV_SCROLL_YTRIGGERTOP, // } + SV_SCROLL_YTRIGGERBOT, // } + SV_SCROLL_YDISTANCE, // } + SV_SCROLL_YSPEED, // } + + SV_SPEECHDELAY, // Delay 'twixt text/animation and sample + SV_MUSICDIMFACTOR, // dimVolume = volume - volume/SV_MDF + + SV_TAGCOLOUR, // if set, default actor's text colour gets poked in here + + SV_USER1, + SV_USER2, + SV_USER3, + SV_USER4, + SV_USER5, + SV_USER6, + + SV_MinimumXoffset, + SV_MaximumXoffset, + SV_MinimumYoffset, + SV_MaximumYoffset, + // dimVolume = volume - volume/DF + SYS_DefaultFxDimFactor, // To this at start of scene + SYS_SceneFxDimFactor, // Alter within scene + + SYS_HighlightRGB, + SYS_Platform, // Hardware platform **READ ONLY** + SYS_Debug, // TRUE for debug build/'cheat'**READ ONLY** + + ISV_DIVERT_ACTOR, + ISV_NO_BLOCKING, + ISV_GHOST_ACTOR, + ISV_GHOST_BASE, + ISV_GHOST_COLOUR, + + + SV_TOPVALID } SYSVARS; + +typedef enum { + + // Main Menu + SS_LOAD_OPTION, // + SS_SAVE_OPTION, // + SS_RESTART_OPTION, // + SS_SOUND_OPTION, // + SS_CONTROL_OPTION, // + SS_SUBTITLES_OPTION, // + SS_QUIT_OPTION, // + SS_RESUME_OPTION, // + + SS_LOAD_HEADING, + SS_SAVE_HEADING, + SS_RESTART_HEADING, + SS_QUIT_HEADING, + + SS_MVOL_SLIDER, + SS_SVOL_SLIDER, + SS_VVOL_SLIDER, + + SS_DCLICK_SLIDER, + SS_DCLICK_TEST, + SS_SWAP_TOGGLE, + + SS_TSPEED_SLIDER, + SS_STITLE_TOGGLE, + + SS_HOPPER1, // Hopper scene menu heading + + SS_SOUND_HEADING, + SS_CONTROLS_HEADING, + SS_LANGUAGE_SELECT, + + SS_MAX_VALID +} BOLLOX; + +void InitSysVars(); + +void SetSysVar(int varId, int newValue); + +int SysVar(int varId); + +void SaveSysVars(int *pSv); +void RestoreSysVars(int *pSv); + +void SetSysString(int number, SCNHANDLE hString); + +SCNHANDLE SysString(int number); + +bool GetNoBlocking(void); + +void SetNoBlocking(bool flag); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/text.cpp b/engines/tinsel/text.cpp index dab97f7fdf..7e36e5236f 100644 --- a/engines/tinsel/text.cpp +++ b/engines/tinsel/text.cpp @@ -105,9 +105,10 @@ int JustifyText(char *szStr, int xPos, const FONT *pFont, int mode) { * @param yPos Y position of string * @param hFont Which font to use * @param mode Mode flags for the string + * @param sleepTime Sleep time between each character (if non-zero) */ -OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos, - SCNHANDLE hFont, int mode) { +OBJECT *ObjectTextOut(CORO_PARAM, OBJECT *pList, char *szStr, int colour, + int xPos, int yPos, SCNHANDLE hFont, int mode, int sleepTime) { int xJustify; // x position of text after justification int yOffset; // offset to next line of text OBJECT *pFirst; // head of multi-object text list @@ -130,7 +131,7 @@ OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos 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); + yOffset = FROM_LE_16(pImg->imgHeight) & ~C16_FLAG_MASK; while (*szStr) { // x justify the text according to the mode flags @@ -175,7 +176,7 @@ OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos // 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->height = FROM_LE_16(pImg->imgHeight) & ~C16_FLAG_MASK; // height of chars bitmap pChar->hBits = FROM_LE_32(pImg->hImgBits); // bitmap // check for absolute positioning diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h index 78998831a1..0fcc965ff9 100644 --- a/engines/tinsel/text.h +++ b/engines/tinsel/text.h @@ -27,6 +27,7 @@ #ifndef TINSEL_TEXT_H // prevent multiple includes #define TINSEL_TEXT_H +#include "tinsel/coroutine.h" #include "tinsel/object.h" // object manager defines namespace Tinsel { @@ -42,6 +43,10 @@ enum { /** maximum number of characters in a font */ #define MAX_FONT_CHARS 256 +#define C16_240 0x4000 +#define C16_224 0x8000 +#define C16_MAP 0xC000 +#define C16_FLAG_MASK (C16_240 | C16_224 | C16_MAP) #include "common/pack-start.h" // START STRUCT PACKING @@ -80,14 +85,21 @@ struct TEXTOUT { |* 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 +/** + * 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 + * @param sleepTime Sleep time between each character (if non-zero) + */ +OBJECT *ObjectTextOut(CORO_PARAM, OBJECT *pList, char *szStr, int colour, + int xPos, int yPos, SCNHANDLE hFont, int mode, int sleepTime = 0); OBJECT *ObjectTextOutIndirect( // output a string of text TEXTOUT *pText); // pointer to TextOut struct with all parameters diff --git a/engines/tinsel/timers.cpp b/engines/tinsel/timers.cpp index c7b9d3708b..92abf4c028 100644 --- a/engines/tinsel/timers.cpp +++ b/engines/tinsel/timers.cpp @@ -151,7 +151,7 @@ void FettleTimers(void) { /** * Start a timer up. */ -void DwSetTimer(int num, int sval, bool up, bool frame) { +void StartTimer(int num, int sval, bool up, bool frame) { TIMER *pt; assert(num); // zero is not allowed as a timer number diff --git a/engines/tinsel/timers.h b/engines/tinsel/timers.h index 75eb87ee2b..2260074f1d 100644 --- a/engines/tinsel/timers.h +++ b/engines/tinsel/timers.h @@ -44,7 +44,7 @@ void syncTimerInfo(Serializer &s); void FettleTimers(void); -void DwSetTimer(int num, int sval, bool up, bool frame); +void StartTimer(int num, int sval, bool up, bool frame); int Timer(int num); diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp index 07c1b22b2a..2313f343e8 100644 --- a/engines/tinsel/tinlib.cpp +++ b/engines/tinsel/tinlib.cpp @@ -36,12 +36,16 @@ #include "tinsel/config.h" #include "tinsel/coroutine.h" #include "tinsel/cursor.h" +#include "tinsel/drives.h" #include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/faders.h" #include "tinsel/film.h" #include "tinsel/font.h" #include "tinsel/graphics.h" #include "tinsel/handle.h" -#include "tinsel/inventory.h" +#include "tinsel/dialogs.h" +#include "tinsel/mareels.h" #include "tinsel/move.h" #include "tinsel/multiobj.h" #include "tinsel/music.h" @@ -49,6 +53,7 @@ #include "tinsel/palette.h" #include "tinsel/pcode.h" #include "tinsel/pid.h" +#include "tinsel/play.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/savescn.h" @@ -57,6 +62,7 @@ #include "tinsel/scroll.h" #include "tinsel/sound.h" #include "tinsel/strres.h" +#include "tinsel/sysvar.h" #include "tinsel/text.h" #include "tinsel/timers.h" // For ONE_SECOND constant #include "tinsel/tinlib.h" @@ -72,49 +78,59 @@ namespace Tinsel { extern bool bRestart; // restart flag - set to restart the game extern bool bHasRestarted; // Set after a restart +// In PCODE.CPP +extern bool bNoPause; + // 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); +// in BG.CPP extern void ChangePalette(SCNHANDLE hPal); -extern int BackgroundWidth(void); -extern int BackgroundHeight(void); +extern int BgWidth(void); +extern int BgHeight(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 BMV.CPP +void PlayBMV(CORO_PARAM, SCNHANDLE hFileStem, int myEscape); +bool MoviePlaying(void); +void AbortMovie(void); -// in PDISPLAY.C +// in PDISPLAY.CPP 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 SAVELOAD.CPP +extern int NewestSavedGame(void); -// in SCENE.C +// in SCENE.CPP extern void setshowpos(void); +// in TINSEL.CPP +extern void SetCdChangeScene(SCNHANDLE hScene); +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); + #ifdef BODGE -// In DOS_HAND.C +// In HANDLE.CPP bool ValidHandle(SCNHANDLE offset); -// In SCENE.C +// In SCENE.CPP SCNHANDLE GetSceneHandle(void); #endif //----------------- GLOBAL GLOBAL DATA -------------------- -bool bEnableF1; +bool bEnableMenu; + +static bool bInstantScroll = false; +static bool bEscapedCdPlay = false; //----------------- LOCAL DEFINES -------------------- @@ -125,70 +141,257 @@ bool bEnableF1; |* 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 +enum MASTER_LIB_CODES { + ACTORATTR, ACTORBRIGHTNESS, ACTORDIRECTION, ACTORPALETTE, ACTORPRIORITY, ACTORREF, + ACTORRGB, ACTORSCALE, ACTORSON, ACTORXPOS, ACTORYPOS, ADDHIGHLIGHT, + ADDINV, ADDINV1, ADDINV2, ADDOPENINV, ADDTOPIC, AUXSCALE, BACKGROUND, BLOCKING, + CALLACTOR, CALLGLOBALPROCESS, CALLOBJECT, CALLPROCESS, CALLSCENE, CALLTAG, + CAMERA, CDCHANGESCENE, CDDOCHANGE, CDENDACTOR, CDLOAD, CDPLAY, CLEARHOOKSCENE, + CLOSEINVENTORY, CONTROL, CONVERSATION, CONVTOPIC, CURSOR, CURSORXPOS, CURSORYPOS, + CUTSCENE, DECCONVW, DECCSTRINGS, DECCURSOR, DECFLAGS, DECINV1, DECINV2, DECINVW, + DECLARELANGUAGE, DECLEAD, DECSCALE, DECTAGFONT, DECTALKFONT, DELICON, + DELINV, DELTOPIC, DIMMUSIC, DROP, DROPEVERYTHING, DROPOUT, EFFECTACTOR, ENABLEMENU, + ENDACTOR, ESCAPE, ESCAPEOFF, ESCAPEON, EVENT, FACETAG, FADEIN, FADEMIDI, + FADEOUT, FRAMEGRAB, FREEZECURSOR, GETINVLIMIT, GHOST, GLOBALVAR, GRABMOVIE, HAILSCENE, + HASRESTARTED, HAVE, HELDOBJECT, HIDEACTOR, HIDEBLOCK, HIDEEFFECT, HIDEPATH, + HIDEREFER, HIDETAG, HOLD, HOOKSCENE, IDLETIME, ININVENTORY, INSTANTSCROLL, INVDEPICT, + INVENTORY, INVPLAY, INWHICHINV, KILLACTOR, KILLBLOCK, KILLEXIT, KILLGLOBALPROCESS, + KILLPROCESS, KILLTAG, LOCALVAR, MOVECURSOR, MOVETAG, MOVETAGTO, NEWSCENE, + NOBLOCKING, NOPAUSE, NOSCROLL, OBJECTHELD, OFFSET, OTHEROBJECT, PAUSE, PLAY, PLAYMIDI, + PLAYMOVIE, PLAYMUSIC, PLAYRTF, PLAYSAMPLE, POINTACTOR, POINTTAG, POSTACTOR, POSTGLOBALPROCESS, + POSTOBJECT, POSTPROCESS, POSTTAG, PREPARESCENE, PRINT, PRINTCURSOR, PRINTOBJ, PRINTTAG, + QUITGAME, RANDOM, RESETIDLETIME, RESTARTGAME, RESTORESCENE, RESTORE_CUT, + RESUMELASTGAME, RUNMODE, SAMPLEPLAYING, SAVESCENE, SAY, SAYAT, SCALINGREELS, + SCANICON, SCREENXPOS, SCREENYPOS, SCROLL, SCROLLPARAMETERS, SENDACTOR, SENDGLOBALPROCESS, + SENDOBJECT, SENDPROCESS, SENDTAG, SETACTOR, SETBLOCK, SETBRIGHTNESS, SETEXIT, SETINVLIMIT, + SETINVSIZE, SETLANGUAGE, SETPALETTE, SETSYSTEMREEL, SETSYSTEMSTRING, SETSYSTEMVAR, + SETTAG, SETTIMER, SHELL, SHOWACTOR, SHOWBLOCK, SHOWEFFECT, SHOWMENU, SHOWPATH, + SHOWPOS, SHOWREFER, SHOWSTRING, SHOWTAG, SPLAY, STAND, STANDTAG, STARTGLOBALPROCESS, + STARTPROCESS, STARTTIMER, STOPMIDI, STOPSAMPLE, STOPWALK, SUBTITLES, SWALK, SWALKZ, + SYSTEMVAR, TAGACTOR, TAGTAGXPOS, TAGTAGYPOS, TAGWALKXPOS, TAGWALKYPOS, TALK, TALKAT, + TALKATS, TALKATTR, TALKPALETTEINDEX, TALKRGB, TALKVIA, TEMPTAGFONT, TEMPTALKFONT, + THISOBJECT, THISTAG, TIMER, TOPIC, TOPPLAY, TOPWINDOW, TRANSLUCENTINDEX, + TRYPLAYSAMPLE, UNDIMMUSIC, UNHOOKSCENE, UNTAGACTOR, VIBRATE, WAITFRAME, WAITKEY, + WAITSCROLL, WAITTIME, WALK, WALKED, WALKEDPOLY, WALKEDTAG, WALKINGACTOR, WALKPOLY, + WALKTAG, WALKXPOS, WALKYPOS, WHICHCD, WHICHINVENTORY, ZZZZZZ, + HIGHEST_LIBCODE }; +const MASTER_LIB_CODES DW1_CODES[] = { + ACTORATTR, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS, + ACTORYPOS, ADDTOPIC, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE, + BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION, + CONVTOPIC, CURSORXPOS, CURSORYPOS, DECCONVW, DECCURSOR, + DECINV1, DECINV2, DECINVW, DECLEAD, DECTAGFONT, + DECTALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT, + GETINVLIMIT, HELDOBJECT, HIDEACTOR, ININVENTORY, INVDEPICT, + INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, SCREENXPOS, + MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE, + PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ, + PRINTTAG, RANDOM, RESTORESCENE, SAVESCENE, SCALINGREELS, + SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT, + SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY, + STAND, STANDTAG, STOPWALK, SWALK, TAGACTOR, TALK, TALKATTR, TIMER, + SCREENYPOS, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY, + WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG, + WHICHINVENTORY, ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME, + RESETIDLETIME, TALKAT, UNHOOKSCENE, WAITFRAME, DECCSTRINGS, + STOPMIDI, STOPSAMPLE, TALKATS, DECFLAGS, FADEMIDI, + CLEARHOOKSCENE, SETINVSIZE, INWHICHINV, NOBLOCKING, + SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEMENU, RESTARTGAME, QUITGAME, + FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD, HASRESTARTED, RESTORE_CUT, + RUNMODE, SUBTITLES, SETLANGUAGE, + HIGHEST_LIBCODE +}; +const MASTER_LIB_CODES DW2_CODES[] = { + ACTORBRIGHTNESS, ACTORDIRECTION, ACTORPALETTE, ACTORPRIORITY, + ACTORREF, ACTORRGB, ACTORSCALE, ACTORXPOS, ACTORYPOS, + ADDHIGHLIGHT, ADDINV, ADDINV1, ADDINV2, ADDOPENINV, ADDTOPIC, + BACKGROUND, CALLACTOR, CALLGLOBALPROCESS, CALLOBJECT, + CALLPROCESS, CALLSCENE, CALLTAG, CAMERA, CDCHANGESCENE, + CDDOCHANGE, CDLOAD, CDPLAY, CLEARHOOKSCENE, CLOSEINVENTORY, + CONTROL, CONVERSATION, CURSOR, CURSORXPOS, CURSORYPOS, + DECCONVW, DECCURSOR, DECFLAGS, DECINV1, DECINV2, DECINVW, + DECLEAD, DECSCALE, DECTAGFONT, DECTALKFONT, DELTOPIC, + DIMMUSIC, DROP, DROPOUT, EFFECTACTOR, ENABLEMENU, ENDACTOR, + ESCAPEOFF, ESCAPEON, EVENT, FACETAG, FADEIN, FADEOUT, FRAMEGRAB, + FREEZECURSOR, GETINVLIMIT, GHOST, GLOBALVAR, GRABMOVIE, + HASRESTARTED, HAVE, HELDOBJECT, HIDEACTOR, HIDEBLOCK, HIDEEFFECT, + HIDEPATH, HIDEREFER, HIDETAG, HOLD, HOOKSCENE, IDLETIME, + INSTANTSCROLL, INVENTORY, INVPLAY, INWHICHINV, KILLACTOR, + KILLGLOBALPROCESS, KILLPROCESS, LOCALVAR, MOVECURSOR, MOVETAG, + MOVETAGTO, NEWSCENE, NOBLOCKING, NOPAUSE, NOSCROLL, OFFSET, + OTHEROBJECT, PAUSE, PLAY, PLAYMUSIC, PLAYRTF, PLAYSAMPLE, + POINTACTOR, POINTTAG, POSTACTOR, POSTGLOBALPROCESS, POSTOBJECT, + POSTPROCESS, POSTTAG, PRINT, PRINTCURSOR, PRINTOBJ, PRINTTAG, + QUITGAME, RANDOM, RESETIDLETIME, RESTARTGAME, RESTORESCENE, + RUNMODE, SAVESCENE, SAY, SAYAT, SCALINGREELS, SCREENXPOS, + SCREENYPOS, SCROLL, SCROLLPARAMETERS, SENDACTOR, SENDGLOBALPROCESS, + SENDOBJECT, SENDPROCESS, SENDTAG, SETBRIGHTNESS, SETINVLIMIT, + SETINVSIZE, SETLANGUAGE, SETPALETTE, SETSYSTEMSTRING, SETSYSTEMVAR, + SHELL, SHOWACTOR, SHOWBLOCK, SHOWEFFECT, SHOWPATH, SHOWREFER, + SHOWTAG, STAND, STANDTAG, STARTGLOBALPROCESS, STARTPROCESS, + STARTTIMER, STOPWALK, SUBTITLES, SWALK, SYSTEMVAR, TAGTAGXPOS, + TAGTAGYPOS, TAGWALKXPOS, TAGWALKYPOS, TALK, TALKAT, TALKPALETTEINDEX, + TALKRGB, TALKVIA, THISOBJECT, THISTAG, TIMER, TOPIC, TOPPLAY, + TOPWINDOW, TRANSLUCENTINDEX, UNDIMMUSIC, UNHOOKSCENE, WAITFRAME, + WAITKEY, WAITSCROLL, WAITTIME, WALK, WALKED, WALKEDPOLY, WALKEDTAG, + WALKINGACTOR, WALKPOLY, WALKTAG, WALKXPOS, WALKYPOS, WHICHCD, + WHICHINVENTORY, ZZZZZZ, SWALKZ, DROPEVERYTHING, BLOCKING, STOPSAMPLE, + CDENDACTOR, DECLARELANGUAGE, RESUMELASTGAME, SHOWMENU, TEMPTALKFONT, + TEMPTAGFONT, PLAYMOVIE, HAILSCENE, SETSYSTEMREEL, + HIGHEST_LIBCODE +}; //----------------- 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 +// 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 int offtype = 0; // used by Control() +static uint32 lastValue = 0; // used by RandomFn() +static int scrollNumber = 0; // used by scroll() -static bool NotPointedRunning = false; // Used in printobj and printobjPointed +static bool bNotPointedRunning = 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); +static int HeldObject(void); +void Offset(EXTREME extreme, int x, int y); +static void PostTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape); +void ResetIdleTime(void); +static void SendTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape, bool *result); +static void StandTag(int actor, HPOLYGON hp); +void StopMidiFn(void); +void StopSample(int sample = -1); +static void StopWalk(int actor); +static void WaitScroll(CORO_PARAM, int myescEvent); +void Walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, + int zOverride, bool escOn, int myescTime); + +//----------------- SUPPORT FUNCTIONS -------------------- + +/** + * For Scroll() and Offset(), work out top left for a + * given screen position. + */ +static void DecodeExtreme(EXTREME extreme, int *px, int *py) { + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + switch (extreme) { + case EX_BOTTOM: + *px = Loffset; + *py = BgHeight() - SCREEN_HEIGHT; + break; + case EX_BOTTOMLEFT: + *px = 0; + *py = BgHeight() - SCREEN_HEIGHT; + break; + case EX_BOTTOMRIGHT: + *px = BgWidth() - SCREEN_WIDTH; + *py = BgHeight() - SCREEN_HEIGHT; + break; + case EX_LEFT: + *px = 0; + *py = Toffset; + break; + case EX_RIGHT: + *px = BgWidth() - SCREEN_WIDTH; + *py = Toffset; + break; + case EX_TOP: + *px = Loffset; + *py = 0; + break; + case EX_TOPLEFT: + *px = *py = 0; + break; + case EX_TOPRIGHT: + *px = BgWidth() - SCREEN_WIDTH; + *py = 0; + break; + default: + break; + } +} + +static void KillSelf(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_KILL_SELF(); + + CORO_END_CODE; +} + +struct SCROLL_MONITOR { + int x; + int y; + int thisScroll; + int myEscape; +}; +typedef SCROLL_MONITOR *PSCROLL_MONITOR; + +/** + * Monitor a scrolling, allowing Escape to interrupt it + */ +static void ScrollMonitorProcess(CORO_PARAM, const void *param) { + int Loffset, Toffset; + const SCROLL_MONITOR *psm = (const SCROLL_MONITOR *)param;; + + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + do { + CORO_SLEEP(1); + + // give up if have been superseded + if (psm->thisScroll != scrollNumber) + break; + + // If ESCAPE is pressed... + if (psm->myEscape != GetEscEvents()) + { + // Instant completion! + Offset(EX_USEXY, psm->x, psm->y); + break; + } + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + } while(Loffset != psm->x || Toffset != psm->y); + CORO_END_CODE; +} /** * NOT A LIBRARY FUNCTION * - * Poke supplied colours into the DAC queue. + * Poke supplied colour into the DAC queue. */ -static void setTextPal(COLORREF col) { +void SetTextPal(COLORREF col) { s_talkfontColor = col; - UpdateDACqueue(TALKFONT_COL, 1, &s_talkfontColor); + SetTalkColourRef(col); + UpdateDACqueue(TalkColour(), 1, &s_talkfontColor); } - +/** + * Work out a time depending on length of string and + * subtitle speed modification. + */ static int TextTime(char *pTstring) { if (isJapanMode()) return JAP_TEXT_TIME; @@ -198,45 +401,148 @@ static int TextTime(char *pTstring) { return strlen(pTstring) + ONE_SECOND + (speedText * 5 * ONE_SECOND) / 100; } -/*--------------------------------------------------------------------------*/ +/** + * KeepOnScreen + */ +void KeepOnScreen(POBJECT pText, int *pTextX, int *pTextY) { + int shift; + + // Not off the left + shift = MultiLeftmost(pText); + if (shift < 0) { + MultiMoveRelXY(pText, - shift, 0); + *pTextX -= shift; + } + + // Not off the right + shift = MultiRightmost(pText); + if (shift > SCREEN_WIDTH) { + MultiMoveRelXY(pText, SCREEN_WIDTH - shift, 0); + *pTextX += SCREEN_WIDTH - shift; + } + + // Not off the top + shift = MultiHighest(pText); + if (shift < 0) { + MultiMoveRelXY(pText, 0, - shift); + *pTextY -= shift; + } + + // Not off the bottom + shift = MultiLowest(pText); + if (shift > SCREEN_BOX_HEIGHT2) { + MultiMoveRelXY(pText, 0, SCREEN_BOX_HEIGHT2 - shift); + *pTextX += SCREEN_WIDTH - shift; + } +} + +/** + * Waits until the specified process is finished + */ +static void FinishWaiting(CORO_PARAM, const INT_CONTEXT *pic, bool *result = NULL) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (pic->resumeCode == RES_WAITING) + CORO_SLEEP(1); + + if (result) + *result = pic->resumeCode == RES_FINISHED; + CORO_END_CODE; +} + +void TinGetVersion(WHICH_VER which, char *buffer, int length) { + + if (length > VER_LEN) + length = VER_LEN; + + char *cptr = (char *)FindChunk(MASTER_SCNHANDLE, CHUNK_TIME_STAMPS); + + switch (which) { + case VER_GLITTER: + memcpy(buffer, cptr, length); + break; + + case VER_COMPILE: + memcpy(buffer, cptr + VER_LEN, length); + break; + } +} +/********************************************************************\ +|***** Library functions *****| +\********************************************************************/ /** * Set actor's attributes. * - currently only the text colour. */ -void actorattr(int actor, int r1, int g1, int b1) { +static void ActorAttr(int actor, int r1, int g1, int b1) { storeActorAttr(actor, r1, g1, b1); } /** - * Return the actor's direction. + * Behave as if actor has walked into a polygon with given brughtness. */ -int actordirection(int actor) { - PMACTOR pActor; +void ActorBrightness(int actor, int brightness) { + PMOVER pMover = GetMover(actor); - pActor = GetMover(actor); - assert(pActor != NULL); // not a moving actor + assert(pMover != NULL); + assert(brightness >= 0 && brightness <= 10); - return (int)GetMActorDirection(pActor); + MoverBrightness(pMover, brightness); } /** - * Return the actor's scale. + * Return a moving actor's current direction. + */ +static int ActorDirection(int actor) { + PMOVER pMover = GetMover(actor); + assert(pMover); + + return (int)GetMoverDirection(pMover); +} + +/** + * Set actor's palette details for path brightnesses + */ +void ActorPalette(int actor, int startColour, int length) { + PMOVER pMover = GetMover(actor); + assert(pMover); + + StoreMoverPalette(pMover, startColour, length); +} + +/** + * Set actor's Z-factor. + */ +static void ActorPriority(int actor, int zFactor) { + SetActorZfactor(actor, zFactor); +} + +/** + * Set actor's text colour. */ -int actorscale(int actor) { - PMACTOR pActor; +static void ActorRGB(int actor, COLORREF colour) { + SetActorRGB(actor, colour); +} - pActor = GetMover(actor); - assert(pActor != NULL); // not a moving actor +/** + * Return the actor's scale. + */ +static int ActorScale(int actor) { + PMOVER pMover = GetMover(actor); + assert(pMover); - return (int)GetMActorScale(pActor); + return (int)GetMoverScale(pMover); } /** * Returns the x or y position of an actor. */ -int actorpos(int xory, int actor) { +static int ActorPos(int xory, int actor) { int x, y; GetActorPos(actor, &x, &y); @@ -246,22 +552,23 @@ int actorpos(int xory, int actor) { /** * Make all actors alive at the start of each scene. */ -void actorson(void) { +static void ActorsOn(void) { setactorson(); } /** * Adds an icon to the conversation window. */ -void addicon(int icon) { +static void AddTopic(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 +static void AddInv(int invno, int object) { + // illegal inventory number + assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN || invno == INV_DEFAULT); AddToInventory(invno, object, false); } @@ -269,58 +576,104 @@ void addinv(int invno, int object) { /** * 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 +static void AuxScale(int actor, int scale, SCNHANDLE *rp) { + PMOVER pMover = GetMover(actor); + assert(pMover); int j; for (j = 0; j < 4; ++j) - pActor->WalkReels[scale-1][j] = *rp++; + pMover->walkReels[scale-1][j] = *rp++; for (j = 0; j < 4; ++j) - pActor->StandReels[scale-1][j] = *rp++; + pMover->standReels[scale-1][j] = *rp++; for (j = 0; j < 4; ++j) - pActor->TalkReels[scale-1][j] = *rp++; + pMover->talkReels[scale-1][j] = *rp++; } /** * Defines the background image for a scene. */ -void background(SCNHANDLE bfilm) { - startupBackground(bfilm); +static void Background(CORO_PARAM, SCNHANDLE bfilm) { + StartupBackground(coroParam, bfilm); +} + +/** + * Disable dynamic blocking for current scene. + */ +void Blocking(bool onOrOff) { + SetSysVar(ISV_NO_BLOCKING, !onOrOff); } /** * Sets focus of the scroll process. */ -void camera(int actor) { +static void Camera(int actor) { ScrollFocus(actor); } +/** + * Sets the CD Change Scene + */ + +static void CdChangeScene(SCNHANDLE hScene) { + SetCdChangeScene(hScene); +} + +/** + * CdDoChange + */ +void CdDoChange(CORO_PARAM) { + if (!GotoCD()) + return; + + CdCD(coroParam); + CdHasChanged(); +} + +/** + * CdEndActor("actor") + */ +void CdEndActor(int actor, int myEscape) { + PMOVER pMover; // for if it's a moving actor + + // Only do it if escaped! + if (myEscape && myEscape != GetEscEvents()) { + // End current graphic + dwEndActor(actor); + + // un-hide movers + pMover = GetMover(actor); + if (pMover) + UnHideMover(pMover); + } +} + /** * A CDPLAY() is imminent. */ -void cdload(SCNHANDLE start, SCNHANDLE next) { +static void CDload(SCNHANDLE start, SCNHANDLE next, int myEscape) { assert(start && next && start != next); // cdload() fault -// TODO/FIXME -// LoadExtraGraphData(start, next); + if (TinselV2) { + if (myEscape && myEscape != GetEscEvents()) { + bEscapedCdPlay = true; + return; + } + + LoadExtraGraphData(start, next); + } } /** * Clear the hooked scene (if any) */ - -void clearhookscene() { - SetHookScene(0, 0, 0); +static void ClearHookScene() { + SetHookScene(0, 0, TRANS_DEF); } /** * Guess what. */ - -void closeinventory(void) { +static void CloseInventory(void) { KillInventory(); } @@ -328,9 +681,29 @@ void closeinventory(void) { * 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) { + if (TinselV2) { + if (param) + ControlOn(); + else { + ControlOff(); + + switch (WhichInventoryOpen()) { + case INV_1: + case INV_2: + case INV_MENU: + KillInventory(); + break; + default: + break; + } + } -void control(int param) { - bEnableF1 = false; + return; + } + + // Tinsel 1 handling code + bEnableMenu = false; switch (param) { case CONTROL_STARTOFF: @@ -385,46 +758,82 @@ void control(int param) { /** * Open or close the conversation window. */ - -void conversation(int fn, HPOLYGON hp, bool escOn, int myescEvent) { +static void Conversation(CORO_PARAM, int fn, HPOLYGON hp, int actor, bool escOn, int myEscape) { assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); - switch (fn) { - case CONV_END: // Close down conversation + if (fn == CONV_END) { + // Close down conversation CloseDownConv(); - break; + } else if ((fn == CONV_TOP) || (fn == CONV_DEF) || (fn == CONV_BOTTOM)) { + // TOP of screen, Default (i.e. TOP of screen), or BOTTOM of screen + + // If waiting is enabled, wait for ongoing scroll + if (TinselV2 && SysVar(SV_CONVERSATIONWAITS)) + CORO_INVOKE_1(WaitScroll, myEscape); - 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 (escOn && myEscape != GetEscEvents()) + return; + // Don't do it if already in a conversation if (IsConvWindow()) - break; + return; KillInventory(); - convPos(fn); - ConvPoly(hp); + + if (TinselV2) { + // If this is from a tag polygon, get the associated + // actor (the one the polygon is named after), if any. + if (!actor) { + actor = GetTagPolyId(hp); + if (actor & ACTORTAG_KEY) + actor &= ~ACTORTAG_KEY; + else + actor = 0; + } + + // Top or bottom; tag polygon or tagged actor + SetConvDetails((CONV_PARAM)fn, hp, actor); + } else { + convPos(fn); + ConvPoly(hp); + } + PopUpInventory(INV_CONV); // Conversation window ConvAction(INV_OPENICON); // CONVERSATION event - break; } + + CORO_END_CODE; } /** * Add icon to conversation window's permanent default list. */ +static void ConvTopic(int icon) { + PermaConvIcon(icon); +} -void convicon(int icon) { - AddIconToPermanentDefaultList(icon); +/** + * Cursor(on/off) + */ +void Cursor(int onoff) { + if (onoff) { + // Re-instate cursor + UnHideCursor(); + } else { + // Blank out cursor + DwHideCursor(); + } } /** * Returns the x or y position of the cursor. */ - -int cursorpos(int xory) { +static int CursorPos(int xory) { int x, y; GetCursorXY(&x, &y, true); @@ -434,8 +843,7 @@ int cursorpos(int xory) { /** * Declare conversation window. */ - -void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, +static void DecConvW(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); @@ -444,32 +852,28 @@ void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, /** * Declare config strings. */ - -void dec_cstrings(SCNHANDLE *tp) { +static void DecCStrings(SCNHANDLE *tp) { setConfigStrings(tp); } /** * Declare cursor's reels. */ - -void dec_cursor(SCNHANDLE bfilm) { - DwInitCursor(bfilm); +static void DecCursor(SCNHANDLE hFilm) { + DwInitCursor(hFilm); } /** * Declare the language flags. */ - -void dec_flags(SCNHANDLE hf) { - setFlagFilms(hf); +static void DecFlags(SCNHANDLE hFilm) { + setFlagFilms(hFilm); } /** * Declare inventory 1's parameters. */ - -void dec_inv1(SCNHANDLE text, int MaxContents, +static void DecInv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { @@ -480,8 +884,7 @@ void dec_inv1(SCNHANDLE text, int MaxContents, /** * Declare inventory 2's parameters. */ - -void dec_inv2(SCNHANDLE text, int MaxContents, +static void DecInv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { @@ -492,77 +895,104 @@ void dec_inv2(SCNHANDLE text, int MaxContents, /** * Declare the bits that the inventory windows are constructed from. */ - -void dec_invw(SCNHANDLE hf) { +static void DecInvW(SCNHANDLE hf) { setInvWinParts(hf); } /** - * Declare lead actor. - * - the actor's id, walk and stand reels for all the regular scales, - * and the tag text. + * DeclareLanguage */ +static void DeclareLanguage(int languageId, SCNHANDLE hDescription, SCNHANDLE hFlagFilm) { + LanguageFacts(languageId, hDescription, hFlagFilm); +} -void dec_lead(uint32 id, SCNHANDLE *rp, SCNHANDLE text) { - PMACTOR pActor; // Moving actor structure +/** + * Declare lead actor. + * @param id Actor Id + * @param rp Walk and stand reels for all the regular scales (v1 only) + * @param text Tag text (v1 only) + */ +static void DecLead(uint32 id, SCNHANDLE *rp = 0, SCNHANDLE text = 0) { + PMOVER pMover; // 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 + if (TinselV2) { + // Tinsel 2 only specifies the lead actor Id + SetLeadId(id); + RegisterMover(id); - pActor = GetMover(id); // Get moving actor structure - assert(pActor); + } else { - // 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++; - } + Tag_Actor(id, text, TAG_DEF); // The lead actor is automatically tagged + SetLeadId(id); // Establish this as the lead + RegisterMover(id); // Establish as a moving actor + + pMover = GetMover(id); // Get moving actor structure + assert(pMover); + + // Store all those reels + int i, j; + for (i = 0; i < 5; ++i) { + for (j = 0; j < 4; ++j) + pMover->walkReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pMover->standReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pMover->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]; + for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) { + for (j = 0; j < 4; ++j) { + pMover->walkReels[i][j] = pMover->walkReels[4][j]; + pMover->standReels[i][j] = pMover->standReels[2][j]; + pMover->talkReels[i][j] = pMover->talkReels[4][j]; + } } } } /** - * Declare the text font. + * DecScale("actor", scale, 12*"reel") + * Define an actor's walk and stand reels for a scale. */ - -void dec_tagfont(SCNHANDLE hf) { - TagFontHandle(hf); // Store the font handle +static void DecScale(int actor, int scale, + SCNHANDLE wkl, SCNHANDLE wkr, SCNHANDLE wkf, SCNHANDLE wka, + SCNHANDLE stl, SCNHANDLE str, SCNHANDLE stf, SCNHANDLE sta, + SCNHANDLE tal, SCNHANDLE tar, SCNHANDLE taf, SCNHANDLE taa) +{ + PMOVER pMover = GetMover(actor); + assert(pMover); + + SetWalkReels(pMover, scale, wkl, wkr, wkf, wka); + SetStandReels(pMover, scale, stl, str, stf, sta); + SetTalkReels(pMover, scale, tal, tar, taf, taa); } /** * Declare the text font. */ +static void DecTagFont(SCNHANDLE hf) { + SetTagFontHandle(hf); // Store the font handle +} -void dec_talkfont(SCNHANDLE hf) { - TalkFontHandle(hf); // Store the font handle +/** + * Declare the text font. + */ +static void DecTalkFont(SCNHANDLE hf) { + SetTalkFontHandle(hf); // Store the font handle } /** * Remove an icon from the conversation window. */ - -void delicon(int icon) { +static void DelIcon(int icon) { RemFromInventory(INV_CONV, icon); } /** * Delete the object from inventory 1 or 2. */ - -void delinv(int object) { +static void DelInv(int object) { if (!RemFromInventory(INV_1, object)) // Remove from inventory 1... RemFromInventory(INV_2, object); // ...or 2 (whichever) @@ -570,107 +1000,285 @@ void delinv(int object) { } /** - * enablef1 + * DelTopic */ - -void enablef1(void) { - bEnableF1 = true; +static void DelTopic(int icon) { + RemFromInventory(INV_CONV, icon); } /** - * fademidi(in/out) + * DimMusic */ - -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; +static void DimMusic(void) { + _vm->_pcmMusic->dim(true); } /** - * Guess what. + * Delete the object from inventory 1 or 2. */ +static void Drop(int object) { + if (object == -1) + object = HeldObject(); -int getinvlimit(int invno) { - return InvGetLimit(invno); -} + if (!RemFromInventory(INV_1, object)) // Remove from inventory 1... + RemFromInventory(INV_2, object); // ...or 2 (whichever) -/** - * Returns TRUE if the game has been restarted, FALSE if not. - */ -bool hasrestarted(void) { - return bHasRestarted; + DropItem(object); // Stop holding it } /** - * Returns which object is currently held. + * Delete all objects from inventory 1 and 2. */ +static void DropEverything(void) { + HoldItem(NOOBJECT, false); -int heldobject(void) { - return WhichItemHeld(); + ClearInventory(INV_1); + ClearInventory(INV_2); } /** - * 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). + * EnableMenu */ - -void hide(int actor) { - HideActor(actor); +static void EnableMenu(void) { + bEnableMenu = true; } /** - * hookscene(scene, entrance, transition) + * Kill an actor's current graphics. */ - -void hookscene(SCNHANDLE scene, int entrance, int transition) { - SetHookScene(scene, entrance, transition); +static void EndActor(int actor) { + dwEndActor(actor); } /** - * idletime + * Get the actor to look at the polygon. + * If the actor is at the tag, do a StandTag(). */ +static void FaceTag(int actor, HPOLYGON hp) { + PMOVER pMover; // Moving actor structure + int nowx, nowy; + int nodex, nodey; -int idletime(void) { - uint32 x; + assert(hp != NOPOLY); - x = getUserEventTime() / ONE_SECOND; - - if (!TestToken(TOKEN_CONTROL)) - resetidletime(); + /* + * Get which moving actor it is + */ + pMover = GetMover(actor); + assert(pMover); + if (MoverHidden(pMover)) + return; + + /* + * Stop the actor + */ + StopWalk(actor); + + /* + * Face the tag + */ + // See where node is and where actor is + GetPolyNode(hp, &nodex, &nodey); + GetActorPos(actor, &nowx, &nowy); - return (int)x; + if (nowx == nodex && nowy == nodey) { + // Stood at the tag, don't face in silly direction + StandTag(actor, hp); + } else { + // Look towards polygon + GetPolyMidBottom(hp, &nodex, &nodey); + SetMoverDirection(pMover, GetDirection(nowx, nowy, + nodex, nodey, + GetMoverDirection(pMover), + NOPOLY, YB_X1_5)); + SetMoverStanding(pMover); + } } /** - * invdepict + * FadeIn */ -void invdepict(int object, SCNHANDLE hFilm) { - invObjectFilm(object, hFilm); +static void FadeIn(void) { + FadeInMedium(NULL); } /** - * See if an object is in the inventory. + * FadeOut */ -int ininventory(int object) { +static void FadeOut(void) { + FadeOutMedium(NULL); +} + +/** + * FadeMidi(in/out) + */ +static 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; +} + +/** + * Freeze the cursor, or not. + */ +static void FreezeCursor(bool bFreeze) { + DoFreezeCursor(bFreeze); +} + +/** + * Guess what. + */ +static int GetInvLimit(int invno) { + return InvGetLimit(invno); +} + +/** + * Ghost + */ +static void Ghost(int actor, int tColour, int tPalOffset) { + SetSysVar(ISV_GHOST_ACTOR, actor); + SetSysVar(ISV_GHOST_COLOUR, tColour); + SetSysVar(ISV_GHOST_BASE, tPalOffset); + CreateGhostPalette(BgPal()); +} + +/** + * + */ +static void HailScene(SCNHANDLE scene) { + DoHailScene(scene); +} + +/** + * Returns TRUE if the game has been restarted, FALSE if not. + */ +static bool HasRestarted(void) { + return bHasRestarted; +} + +/** + * See if an object is in the inventory. + */ +int Have(int object) { + return (InventoryPos(object) != NOOBJECT); +} + +/** + * Returns which object is currently held. + */ +static int HeldObject(void) { + return WhichItemHeld(); +} + +/** + * Hides the specified actor + */ +static void HideActorFn(CORO_PARAM, int ano) { + HideActor(coroParam, ano); +} + +/** + * Turn a blocking polygon off. + */ +static void HideBlock(int block) { + DisableBlock(block); +} + +/** + * Turn an effect polygon off. + */ +static void HideEffect(int effect) { + DisableEffect(effect); +} + +/** + * Turn a path polygon off. + */ +static void HidePath(int path) { + DisablePath(path); +} + +/** + * Turn a refer polygon off. + */ +static void HideRefer(int refer) { + DisableRefer(refer); +} + +/** + * Turn a tag polygon off. + */ +static void HideTag(CORO_PARAM, int tag, HPOLYGON hp) { + // Tag could be zero, meaning calling tag + DisableTag(coroParam, tag ? tag : GetTagPolyId(hp)); +} + +/** + * Hold the specified object. + */ +static void Hold(int object) { + HoldItem(object, false); +} + +/** + * HookScene(scene, entrance, transition) + */ +void HookScene(SCNHANDLE scene, int entrance, int transition) { + SetHookScene(scene, entrance, transition); +} + +/** + * IdleTime + */ +static int IdleTime(void) { + // If control is off, system is not idle + if (!ControlIsOn()) { + // Player doesn't currently have control + ResetIdleTime(); + + return 0; + } else { + // Player has control - return time since last event + int x = getUserEventTime() / ONE_SECOND; + + return x; + } +} + +/** + * Set flag if InstantScroll(on), reset if InstantScroll(off) + */ +void InstantScroll(int onoff) { + bInstantScroll = (onoff != 0); +} + +/** + * invdepict + */ +static void InvDepict(int object, SCNHANDLE hFilm) { + SetObjectFilm(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) { +static void Inventory(int invno, bool escOn, int myEscape) { // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory @@ -678,10 +1286,17 @@ void inventory(int invno, bool escOn, int myescEvent) { PopUpInventory(invno); } +/** + * Alter inventory object's icon. + */ +static void InvPlay(int object, SCNHANDLE hFilm) { + SetObjectFilm(object, hFilm); +} + /** * See if an object is in the inventory. */ -int inwhichinv(int object) { +static int InWhichInv(int object) { if (WhichItemHeld() == object) return 0; @@ -697,75 +1312,105 @@ int inwhichinv(int object) { /** * Kill an actor. */ -void killactor(int actor) { +static void KillActor(int actor) { DisableActor(actor); } /** * Turn a blocking polygon off. */ -void killblock(int block) { +static void KillBlock(int block) { DisableBlock(block); } /** * Turn an exit off. */ -void killexit(int exit) { +static void KillExit(int exit) { DisableExit(exit); } /** * Turn a tag off. */ -void killtag(int tagno) { - DisableTag(tagno); +static void KillTag(CORO_PARAM, int tagno) { + DisableTag(coroParam, tagno); +} + +/** + * Kills the specified global process + */ +static void KillGlobalProcess(uint32 procID) { + xKillGlobalProcess(procID); +} + +/** + * Kills the specified process + */ +static void KillProcess(uint32 procID) { + KillSceneProcess(procID); } /** * Returns the left or top offset of the screen. */ -int ltoffset(int lort) { +static int LToffset(int lort) { int Loffset, Toffset; PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); - return (lort == LEFTOFFSET) ? Loffset : Toffset; + return (lort == SCREENXPOS) ? Loffset : Toffset; } /** * Set new cursor position. */ -void movecursor(int x, int y) { +static void MoveCursor(int x, int y) { SetCursorXY(x, y); controlX = x; // Save these values so that controlY = y; // control(on) doesn't undo this } +/** + * MoveTag(tag, x, y) + */ +void MoveTag(int tag, int x, int y, HPOLYGON hp) { + // Tag could be zero, meaning calling tag + MovePolygon(TAG, tag ? tag : GetTagPolyId(hp), x, y); +} + +/** + * MoveTagTo(tag, x, y) + */ +void MoveTagTo(int tag, int x, int y, HPOLYGON hp) { + // Tag could be zero, meaning calling tag + MovePolygonTo(TAG, tag ? tag : GetTagPolyId(hp), x, y); +} + /** * Triggers change to a new scene. */ -void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) { +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; + if (TinselV2) { + if (MoviePlaying()) { + AbortMovie(); + CORO_SLEEP(2); + } } - assert(scene); // Non-existant first scene! -#endif SetNewScene(scene, entrance, transition); -#if 1 // Prevent tags and cursor re-appearing - GetControl(CONTROL_STARTOFF); -#endif + if (TinselV2) + ControlStartOff(); + else + GetControl(CONTROL_STARTOFF); // Prevent code subsequent to this call running before scene changes if (g_scheduler->getCurrentPID() != PID_MASTER_SCR) @@ -776,47 +1421,78 @@ void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) { /** * Disable dynamic blocking for current scene. */ -void noblocking(void) { - bNoBlocking = true; +static void NoBlocking(void) { + SetNoBlocking(true); } /** * Define a no-scroll boundary for the current scene. */ -void noscroll(int x1, int y1, int x2, int y2) { +static void NoScroll(int x1, int y1, int x2, int y2) { SetNoScroll(x1, y1, x2, y2); } /** * Hold the specified object. */ -void objectheld(int object) { +static void ObjectHeld(int object) { HoldItem(object); } /** * Set the top left offset of the screen. */ -void offset(int x, int y) { +void Offset(EXTREME extreme, int x, int y) { KillScroll(); + + if (TinselV2) + DecodeExtreme(extreme, &x, &y); + PlayfieldSetPos(FIELD_WORLD, x, y); } +/** + * OtherObject() + */ +int OtherObject(INV_OBJECT *pinvo) { + assert(pinvo != NULL); + + // return held object or object clicked on - whichever is not the calling object + + // pinvo->id is the calling object + // WhichItemHeld() gives the held object + // GetIcon() gives the object clicked on + + assert(GetIcon() == pinvo->id || WhichItemHeld() == pinvo->id); + + if (GetIcon() == pinvo->id) + return WhichItemHeld(); + else + return GetIcon(); +} + /** * 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) { +static void Play(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int compit, int actorid, bool splay, int sfact, + bool escOn, int myEscape, bool bTop) { + assert(hFilm != 0); // play(): Trying to play NULL film + // COROUTINE CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); - assert(film != 0); // play(): Trying to play NULL film + + // Don't do CDPlay() for now if already escaped + if (bEscapedCdPlay) { + bEscapedCdPlay = false; + return; + } // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; // If this actor is dead, call a stop to the calling process @@ -825,24 +1501,75 @@ void play(CORO_PARAM, SCNHANDLE film, int x, int y, int compit, int actorid, boo // 7/4/95 if (!escOn) - myescEvent = GetEscEvents(); + myEscape = GetEscEvents(); if (compit == 1) { // Play to completion before returning - CORO_INVOKE_ARGS(playFilmc, (CORO_SUBCTX, film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop)); + CORO_INVOKE_ARGS(PlayFilmc, (CORO_SUBCTX, hFilm, x, y, actorid, splay, sfact, escOn, myEscape, 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_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, actorid, splay, sfact, escOn, myEscape, bTop)); } CORO_END_CODE; } +/** + * Play a film + */ +static void Play(CORO_PARAM, SCNHANDLE hFilm, int x, int y, bool bComplete, int myEscape, + bool bTop, TINSEL_EVENT event, HPOLYGON hPoly, int taggedActor) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(hFilm != 0); + + // Don't do CdPlay() for now if already escaped + if (bEscapedCdPlay) { + bEscapedCdPlay = false; + return; + } + + if (event == TALKING) { + int actor; + if (hPoly == NOPOLY) { + // Must be a tagged actor + + assert(taggedActor && IsTaggedActor(taggedActor)); + actor = taggedActor; + } else if (taggedActor == 0) { + // Must be a polygon with an actor ID + actor = GetTagPolyId(hPoly); + assert(actor & ACTORTAG_KEY); + actor &= ~ACTORTAG_KEY; + } + else { + return; + } + + SetActorTalking(actor, true); + SetActorTalkFilm(actor, hFilm); + } + + if (bComplete) { + // Play to completion before returning + CORO_INVOKE_ARGS(PlayFilmc, (CORO_SUBCTX, hFilm, x, y, 0, false, false, myEscape != 0, myEscape, bTop)); + } else { + // Kick off the play and return. + CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, myEscape, bTop)); + } + + CORO_END_CODE; +} + + /** * Play a midi file. */ -void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) { +static void PlayMidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) { // FIXME: This is a workaround for the FIXME below if (GetMidiVolume() == 0) return; @@ -876,10 +1603,49 @@ void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) { CORO_END_CODE; } +/** + * Plays a movie + */ + +static void PlayMovie(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) { + CORO_BEGIN_CONTEXT; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (myEscape && myEscape != GetEscEvents()) + return; + + // Get rid of the cursor + for (_ctx->i = 0; _ctx->i < 3; _ctx->i++) { + DwHideCursor(); + DropCursor(); + CORO_SLEEP(1); + } + + // They claim to be getting "Can't play two movies at once!" error + while (MoviePlaying()) + CORO_SLEEP(1); + + // Play the movie + CORO_INVOKE_2(PlayBMV, hFileStem, myEscape); + + CORO_END_CODE; +} + +/** + * Play some music + */ +static void PlayMusic(int tune) { + _vm->_pcmMusic->startPlay(tune); +} + /** * Play a sample. + * Tinsel 1 version */ -void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { +static void PlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) { CORO_BEGIN_CONTEXT; Audio::SoundHandle handle; CORO_END_CONTEXT(_ctx); @@ -890,7 +1656,7 @@ void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEven return; // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) { + if (escOn && myEscape != GetEscEvents()) { _vm->_sound->stopAllSamples(); // Stop any currently playing sample return; } @@ -898,10 +1664,10 @@ void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEven if (volSound != 0 && _vm->_sound->sampleExists(sample)) { _vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle); - if (complete) { + if (bComplete) { while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) { // Abort if escapable and ESCAPE is pressed - if (escOn && myescEvent != GetEscEvents()) { + if (escOn && myEscape != GetEscEvents()) { _vm->_mixer->stopHandle(_ctx->handle); break; } @@ -917,28 +1683,153 @@ void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEven } /** - * Play a sample. + * Play a sample + * Tinsel 2 version */ -void tryplaysample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { +static void PlaySample(CORO_PARAM, int sample, int x, int y, int flags, int myEscape) { + int priority; CORO_BEGIN_CONTEXT; + Audio::SoundHandle handle; + int myEscape; 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); + + _ctx->myEscape = myEscape; + + // Not escapable if PlaySample(..., s) + if (flags & PS_SUSTAIN) { + _ctx->myEscape = 0; + priority = PRIORITY_SPLAY2; + } else { + priority = PRIORITY_SPLAY1; + } + + // Don't do anything if it's already been escaped + if (_ctx->myEscape && _ctx->myEscape != GetEscEvents()) return; + + if (volSound != 0 && _vm->_sound->sampleExists(sample)) { + if (x == 0) + x = -1; + + _vm->_sound->playSample(sample, 0, false, x, y, priority, Audio::Mixer::kSFXSoundType, + &_ctx->handle); + + if (flags & PS_COMPLETE) { + while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + // Abort if escapable and ESCAPE is pressed + if (_ctx->myEscape && _ctx->myEscape != GetEscEvents()) { + _vm->_mixer->stopHandle(_ctx->handle); + break; + } + + CORO_SLEEP(1); + } + } + } else { + // Prevent Glitter lock-up + CORO_SLEEP(1); } - CORO_INVOKE_ARGS(playsample, (CORO_SUBCTX, sample, complete, escOn, myescEvent)); CORO_END_CODE; } +/** + * Move the cursor to the tagged actor's tag point. + */ +void PointActor(int actor) { + int x, y; + + // Only do this if the function is enabled + if (!SysVar(SV_ENABLEPOINTTAG)) + return; + + assert(IsTaggedActor(actor)); + + GetActorTagPos(actor, &x, &y, true); + + _vm->setMousePosition(Common::Point(x, y)); +} + +/** + * Move the cursor to the tag's tag point. + */ +static void PointTag(int tagno, HPOLYGON hp) { + int x, y; + SCNHANDLE junk; + + // Only do this if the function is enabled + if (!SysVar(SV_ENABLEPOINTTAG)) + return; + + // Tag could be zero, meaning calling tag + if (tagno == 0) + tagno = GetTagPolyId(hp); + + GetTagTag(GetTagHandle(tagno), &junk, &x, &y); + + _vm->setMousePosition(Common::Point(x, y)); +} + +/** + * PostActor("actor", event) + */ +static void PostActor(CORO_PARAM, int actor, TINSEL_EVENT event, HPOLYGON hp, + int taggedActor, int myEscape) { + if (actor == -1) { + actor = taggedActor; + assert(hp == NOPOLY && taggedActor); + assert(IsTaggedActor(actor)); + } + + if (IsTaggedActor(actor)) { + assert(actor); + ActorEvent(coroParam, actor, event, false, myEscape); + } else { + PostTag(coroParam, actor | ACTORTAG_KEY, event, hp, myEscape); + } +} + +/** + * PostGlobalProcess(process#, event) + */ +static void PostGlobalProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) { + GlobalProcessEvent(coroParam, procId, event, false, myEscape); +} + +/** + * PostObject(object, event) + */ +static void PostObject(CORO_PARAM, int object, TINSEL_EVENT event, int myEscape) { + ObjectEvent(coroParam, object, event, false, myEscape); +} + +/** + * PostProcess(process#, event) + */ +static void PostProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) { + SceneProcessEvent(coroParam, procId, event, false, myEscape); +} + +/** + * Posts an event to a specified tag + */ +static void PostTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape) { + // Tag could be zero, meaning calling tag + if (tagno == 0) { + assert(hp != NOPOLY); + PolygonEvent(coroParam, hp, event, 0, false, myEscape); + } else { + assert(IsTagPolygon(tagno)); + PolygonEvent(coroParam, GetTagHandle(tagno), event, 0, false, myEscape); + } +} + /** * Trigger pre-loading of a scene's data. */ -void preparescene(SCNHANDLE scene) { +static void PrepareScene(SCNHANDLE scene) { #ifdef BODGE if (!ValidHandle(scene)) return; @@ -947,11 +1838,11 @@ void preparescene(SCNHANDLE scene) { /** * 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) { +static void Print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, bool bSustain, bool escOn, int myEscape) { + if (TinselV2) + escOn = myEscape != 0; + CORO_BEGIN_CONTEXT; OBJECT *pText; // text object pointer int myleftEvent; @@ -969,37 +1860,55 @@ void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool es _ctx->bSample = false; // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != 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); + if (!TinselV2) { + // 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); + } } + // Get the string + LoadStringRes(text, TextBufferAddr(), TBUFSZ); + // Calculate display time - LoadStringRes(text, tBufferAddr(), TBUFSZ); bJapDoPrintText = false; if (time == 0) { // This is a 'talky' print - _ctx->time = TextTime(tBufferAddr()); + _ctx->time = TextTime(TextBufferAddr()); // Cut short-able if sustain was not set - _ctx->myleftEvent = (hold == 2) ? 0 : GetLeftEvents(); + _ctx->myleftEvent = bSustain ? 0 : GetLeftEvents(); } else { _ctx->time = time * ONE_SECOND; - _ctx->myleftEvent = 0; + _ctx->myleftEvent = (TinselV2 && !bSustain) ? GetLeftEvents() : 0; if (isJapanMode()) bJapDoPrintText = true; } // Print the text - if (bJapDoPrintText || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) { + if (TinselV2) { + int Loffset, Toffset; + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + _ctx->pText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, x - Loffset, y - Toffset, GetTagFontHandle(), + TXT_CENTRE, 0); + assert(_ctx->pText); + + // Adjust x, y, or z if necessary + KeepOnScreen(_ctx->pText, &x, &y); + if (IsTopWindow()) + MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + + } else 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); + _ctx->pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), + 0, x - Loffset, y - Toffset, + TinselV2 ? GetTagFontHandle() : GetTalkFontHandle(), TXT_CENTRE); assert(_ctx->pText); // string produced NULL text if (IsTopWindow()) MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); @@ -1009,14 +1918,14 @@ void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool es */ int shift; shift = MultiRightmost(_ctx->pText) + 2; - if (shift >= BackgroundWidth()) // Not off right - MultiMoveRelXY(_ctx->pText, BackgroundWidth() - shift, 0); + if (shift >= BgWidth()) // Not off right + MultiMoveRelXY(_ctx->pText, BgWidth() - 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); + if (shift > BgHeight()) // Not off bottom + MultiMoveRelXY(_ctx->pText, 0, BgHeight() - shift); } // Give up if nothing printed and no sample @@ -1024,37 +1933,50 @@ void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool es return; // Leave it up until time runs out or whatever - _ctx->timeout = SAMPLETIMEOUT; - do { - CORO_SLEEP(1); + if (TinselV2) { + 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; + // Cancelled? + if ( (myEscape && myEscape != GetEscEvents()) + || (!bSustain && LeftEventChange(_ctx->myleftEvent))) + 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; + } while (_ctx->time-- >= 0); + + } else { + _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 && myEscape != 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; } - } else { - // No sample - just depends on time - if (_ctx->time-- <= 0) - break; - } - } while (1); + } while (1); + } // Delete the text if (_ctx->pText != NULL) @@ -1065,84 +1987,228 @@ void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool es } -static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item); -static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText); +static void PrintObjPointed(CORO_PARAM, const SCNHANDLE text, const INV_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 INV_OBJECT *pinvo, const int event) { +static void PrintObj(CORO_PARAM, const SCNHANDLE hText, const INV_OBJECT *pinvo, const int event, int myEscape) { CORO_BEGIN_CONTEXT; OBJECT *pText; // text object pointer int textx, texty; int item; + bool bSample; + int sub; + Audio::SoundHandle handle; + int ticks; + int timeout; + bool bTookControl; + int myEscape; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); - assert(pinvo != 0); // printobj() may only be called from an object code block + assert(pinvo != 0); // PrintObj() may only be called from an object code block + _ctx->myEscape = myEscape; - if (text == (SCNHANDLE)-1) { // 'OFF' - NotPointedRunning = true; + if (hText == (SCNHANDLE)-1) { // 'OFF' + bNotPointedRunning = true; return; } - if (text == (SCNHANDLE)-2) { // 'ON' - NotPointedRunning = false; + if (hText == (SCNHANDLE)-2) { // 'ON' + bNotPointedRunning = false; return; } + // Don't do it if it's not wanted + if (TinselV2 && (myEscape) && (myEscape != GetEscEvents())) + return; + + /* + * Find out which icon the cursor is over, and where to put the text. + */ 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; + /* + * POINT/other event PrintObj() arbitration... + */ if (event != POINTED) { - NotPointedRunning = true; // Get POINTED text to die + bNotPointedRunning = 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 + } else if (!TinselV2) + bNotPointedRunning = 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; + // Make multi-ones escape + if (TinselV2 && (SubStringCount(hText) > 1) && !_ctx->myEscape) + _ctx->myEscape = GetEscEvents(); - 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); + // Loop once for Tinsel 1 strings, and for Tinsel 2 however many lines are needed + for (_ctx->sub = 0; _ctx->sub < (TinselV2 ? SubStringCount(hText) : 1); _ctx->sub++) { + if (_ctx->myEscape && _ctx->myEscape != GetEscEvents()) + break; - // Don't go off the side of the screen - xshift = MultiLeftmost(_ctx->pText); - if (xshift < 0) { - MultiMoveRelXY(_ctx->pText, - xshift, 0); - _ctx->textx -= xshift; + if (!_vm->_sound->sampleExists(hText)) + _ctx->bSample = false; + else { + // Kick off the voice sample + _vm->_sound->playSample(hText, _ctx->sub, false, -1, -1, PRIORITY_TALK, + Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSample = true; } - xshift = MultiRightmost(_ctx->pText); - if (xshift > SCREEN_WIDTH) { - MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0); - _ctx->textx += SCREEN_WIDTH - xshift; + + // Display the text and set it's Z position + if (event == POINTED || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) { + int xshift; + + // Get the text string + if (TinselV2) + LoadSubString(hText, _ctx->sub, TextBufferAddr(), TBUFSZ); + else + LoadStringRes(hText, TextBufferAddr(), TBUFSZ); + + _ctx->pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), + 0, _ctx->textx, _ctx->texty, GetTagFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // PrintObj() string produced NULL text + + MultiSetZPosition(_ctx->pText, Z_INV_ITEXT); + + if (TinselV2) + KeepOnScreen(_ctx->pText, &_ctx->textx, &_ctx->texty); + else { + // 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 (TinselV2) { + if (event == POINTED) { + /* + * PrintObj() called from POINT event + */ + // 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 (bNotPointedRunning) { + // Delete the text, and wait for the all-clear + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _ctx->pText = NULL; + + while (bNotPointedRunning) + CORO_SLEEP(1); + + GetCursorXY(&x, &y, false); + if (InvItem(&x, &y, false) != _ctx->item) + break; + + // Re-display in the same place + LoadStringRes(hText, TextBufferAddr(), TBUFSZ); + _ctx->pText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, _ctx->textx, _ctx->texty, GetTagFontHandle(), + TXT_CENTRE, 0); + assert(_ctx->pText); + + KeepOnScreen(_ctx->pText, &_ctx->textx, &_ctx->texty); + MultiSetZPosition(_ctx->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); + } else { + /* + * PrintObj() called from other event + */ + _ctx->myEscape = GetLeftEvents(); + _ctx->bTookControl = GetControl(); + + // Display for a time, but abort if conversation gets hidden + if (_ctx->pText) + _ctx->ticks = TextTime(TextBufferAddr()); + _ctx->timeout = SAMPLETIMEOUT; + + for (;;) { + CORO_SLEEP(1); + + // Abort if left click - hardwired feature for talky-print! + // Abort if sample times out + // Abort if conversation hidden + if (LeftEventChange(_ctx->myEscape) + || --_ctx->timeout <= 0 + || ConvIsHidden()) + 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->ticks-- <= 0) + break; + } + } + + if (_ctx->bTookControl) + ControlOn(); // Free control if we took it + } + + } else { + if (event == POINTED) { + // FIXME: Is there ever an associated sound if in POINTED mode??? + assert(!_vm->_sound->sampleExists(hText)); + CORO_INVOKE_ARGS(PrintObjPointed, (CORO_SUBCTX, hText, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item)); + } else { + CORO_INVOKE_2(PrintObjNonPointed, hText, _ctx->pText); + } } - } 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); + + // If it hasn't already finished, stop sample + if (_ctx->bSample) + _vm->_mixer->stopHandle(_ctx->handle); } - // Delete the text, if haven't already - if (_ctx->pText) - MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + // Let POINTED text back in if this is the last + if (event != POINTED) + bNotPointedRunning = false; CORO_END_CODE; } -static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) { +static void PrintObjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); @@ -1156,11 +2222,11 @@ static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT * break; // Give way to non-POINTED-generated text - if (NotPointedRunning) { + if (bNotPointedRunning) { // Delete the text, and wait for the all-clear MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText); pText = NULL; - while (NotPointedRunning) + while (bNotPointedRunning) CORO_SLEEP(1); GetCursorXY(&x, &y, false); @@ -1168,10 +2234,10 @@ static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT * 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 + LoadStringRes(text, TextBufferAddr(), TBUFSZ); + pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), + 0, textx, texty, GetTagFontHandle(), TXT_CENTRE); + assert(pText); // PrintObj() string produced NULL text MultiSetZPosition(pText, Z_INV_ITEXT); } @@ -1184,7 +2250,7 @@ static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT * CORO_END_CODE; } -static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) { +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; @@ -1210,7 +2276,7 @@ static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *p if (isJapanMode()) _ctx->ticks = JAP_TEXT_TIME; else if (pText) - _ctx->ticks = TextTime(tBufferAddr()); + _ctx->ticks = TextTime(TextBufferAddr()); else _ctx->ticks = 0; @@ -1222,7 +2288,7 @@ static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *p // 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()) + if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || ConvIsHidden()) break; if (_ctx->bSample) { @@ -1243,10 +2309,10 @@ static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *p } } while (1); - NotPointedRunning = false; // Let POINTED text back in + bNotPointedRunning = false; // Let POINTED text back in if (_ctx->took_control) - control(CONTROL_ON); // Free control if we took it + Control(CONTROL_ON); // Free control if we took it _vm->_mixer->stopHandle(_ctx->handle); @@ -1256,28 +2322,38 @@ static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *p /** * 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); +static void PrintTag(HPOLYGON hp, SCNHANDLE text, int actor = 0, bool bCursor = false) { + // printtag() may only be called from a polygon code block in Tinsel 1, or + // additionally from a moving actor code block in Tinsel 2 + assert((hp != NOPOLY) || (TinselV2 && (actor != 0))); + + if (hp != NOPOLY) { + // Poly handling + if (TinselV2) + SetPolyTagWanted(hp, true, bCursor, text); + else if (PolyTagState(hp) == TAG_OFF) { + SetPolyTagState(hp, TAG_ON); + SetPolyTagHandle(hp, text); + } + } else { + // Moving actor handling + SetActorTagWanted(actor, true, bCursor, text); } } /** - * quitgame + * Quits the game */ -void quitgame(void) { - stopmidi(); - stopsample(); +static void QuitGame(void) { + StopMidi(); + StopSample(); _vm->quitGame(); } /** * Return a random number between optional limits. */ -int dw_random(int n1, int n2, int norpt) { +static int RandomFn(int n1, int n2, int norpt) { int i = 0; uint32 value; @@ -1290,44 +2366,71 @@ int dw_random(int n1, int n2, int norpt) { } /** - * resetidletime + * ResetIdleTime */ -void resetidletime(void) { +void ResetIdleTime(void) { resetUserEventTime(); } /** * restartgame */ -void restartgame(void) { - stopmidi(); - stopsample(); +static void RestartGame(void) { + // TODO: Tinsel 2 comments out the 2 calls, but I'm not sure that this should be done + StopMidi(); + StopSample(); bRestart = true; } /** * Restore saved scene. */ -void restore_scene(bool bFade) { - UnSuspendHook(); - PleaseRestoreScene(bFade); +static void RestoreScene(CORO_PARAM, TRANSITS transition) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (TinselV2) { + if (MoviePlaying()) { + AbortMovie(); + CORO_SLEEP(2); + } + + CuttingScene(false); + + } else { + UnSuspendHook(); + } + + TinselRestoreScene(transition == TRANS_FADE); + + CORO_END_CODE; +} + +/** + * Resumes the last game + */ +void ResumeLastGame(void) { + RestoreGame(NewestSavedGame()); } /** - * runmode + * Returns the current run mode */ -int runmode(void) { +static int RunMode(void) { return clRunMode; } /** - * sampleplaying + * SamplePlaying */ -bool sampleplaying(bool escOn, int myescEvent) { +static bool SamplePlaying(bool escOn, int myEscape) { // escape effects introduced 14/12/95 to fix // while (sampleplaying()) pause; - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return false; return _vm->_sound->sampleIsPlaying(); @@ -1336,73 +2439,169 @@ bool sampleplaying(bool escOn, int myescEvent) { /** * Save current scene. */ -void save_scene(CORO_PARAM) { - PleaseSaveScene(coroParam); - SuspendHook(); +void SaveScene(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (TinselV2) { + CuttingScene(true); + SendSceneTinselProcess(LEAVE_T2); + CORO_GIVE_WAY; + + CORO_INVOKE_0(TinselSaveScene); + } else { + CORO_INVOKE_0(TinselSaveScene); + SuspendHook(); + } + + CORO_END_CODE; } /** - * scalingreels + * ScalingReels */ -void scalingreels(int actor, int scale, int direction, +static void ScalingReels(int actor, int scale, int direction, SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) { - setscalingreels(actor, scale, direction, left, right, forward, away); + SetScalingReels(actor, scale, direction, left, right, forward, away); } /** * Return the icon that caused the CONVERSE event. */ - -int scanicon(void) { - return convIcon(); +static int ScanIcon(void) { + return GetIcon(); } /** * Scroll the screen to target co-ordinates. */ - -void scroll(CORO_PARAM, int x, int y, int iter, bool comp, bool escOn, int myescEvent) { +static void Scroll(CORO_PARAM, EXTREME extreme, int xp, int yp, int xIter, int yIter, bool bComp, bool escOn, int myEscape) { CORO_BEGIN_CONTEXT; - int mycount; + int thisScroll; + int x, y; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); - if (escOn && myescEvent != GetEscEvents()) { + + ++scrollNumber; + _ctx->x = xp; + _ctx->y = yp; + + if ((TinselV2 && bInstantScroll) || (escOn && myEscape != GetEscEvents())) { // Instant completion! - offset(x, y); + Offset(extreme, _ctx->x, _ctx->y); + } else { + _ctx->thisScroll = scrollNumber; + if (TinselV2) + DecodeExtreme(extreme, &_ctx->x, &_ctx->y); + + ScrollTo(_ctx->x, _ctx->y, xIter, yIter); + + if (bComp) { + int Loffset, Toffset; + do { + CORO_SLEEP(1); + + // If escapable and ESCAPE is pressed... + if (escOn && myEscape != GetEscEvents()) { + // Instant completion! + Offset(extreme, _ctx->x, _ctx->y); + break; + } + + // give up if have been superseded + if (_ctx->thisScroll != scrollNumber) + CORO_KILL_SELF(); + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + } while (Loffset != _ctx->x || Toffset != _ctx->y); + } else if (TinselV2 && myEscape) { + static SCROLL_MONITOR sm; + + // Scroll is escapable even though we're not waiting for it + sm.x = _ctx->x; + sm.y = _ctx->y; + sm.thisScroll = scrollNumber; + sm.myEscape = myEscape; + g_scheduler->createProcess(PID_TCODE, ScrollMonitorProcess, &sm, sizeof(sm)); + } + } + CORO_END_CODE; +} + +/** + * ScrollParameters + */ +static void ScrollParameters(int xTrigger, int xDistance, int xSpeed, int yTriggerTop, + int yTriggerBottom, int yDistance, int ySpeed) { + SetScrollParameters(xTrigger, xDistance, xSpeed, + yTriggerTop, yTriggerBottom, yDistance, ySpeed); +} + +/** + * SendActor("actor", event) + */ +int SendActor(CORO_PARAM, int actor, TINSEL_EVENT event, HPOLYGON hp, int myEscape) { + bool result; + + if (IsTaggedActor(actor)) { + assert(actor); + ActorEvent(coroParam, actor, event, true, myEscape, &result); } else { - _ctx->mycount = ++scrollCount; + SendTag(coroParam, actor | ACTORTAG_KEY, event, hp, myEscape, &result); + } - ScrollTo(x, y, iter); + return result; +} + +/** + * SendGlobalProcess(process#, event) + */ +static int SendGlobalProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) { + return GlobalProcessEvent(coroParam, procId, event, true, myEscape); +} + +/** + * SendObject(object, event) + */ +static int SendObject(CORO_PARAM, int object, TINSEL_EVENT event, int myEscape) { + bool result; + ObjectEvent(coroParam, object, event, true, myEscape, &result); + return result; +} - if (comp) { - int Loffset, Toffset; - do { - CORO_SLEEP(1); +/** + * SendProcess(process#, event) + */ +static int SendProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) { + bool result; + SceneProcessEvent(coroParam, procId, event, true, myEscape, &result); + return result; +} - // If escapable and ESCAPE is pressed... - if (escOn && myescEvent != GetEscEvents()) { - // Instant completion! - offset(x, y); - break; - } +/** + * SendTag(tag#, event) + */ +static void SendTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape, bool *result) { + // Tag could be zero, meaning calling tag + if (tagno == 0) { + assert(hp != NOPOLY); - // give up if have been superseded - if (_ctx->mycount != scrollCount) - CORO_KILL_SELF(); + PolygonEvent(coroParam, hp, event, 0, true, myEscape, result); + } else { + assert(IsTagPolygon(tagno)); - PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); - } while (Loffset != x || Toffset != y); - } + PolygonEvent(coroParam, GetTagHandle(tagno), event, 0, true, myEscape, result); } - CORO_END_CODE; } /** * Un-kill an actor. */ -void setactor(int actor) { +static void SetActor(int actor) { EnableActor(actor); } @@ -1410,7 +2609,7 @@ void setactor(int actor) { * Turn a blocking polygon on. */ -void setblock(int blockno) { +static void SetBlock(int blockno) { EnableBlock(blockno); } @@ -1418,21 +2617,21 @@ void setblock(int blockno) { * Turn an exit on. */ -void setexit(int exitno) { +static void SetExit(int exitno) { EnableExit(exitno); } /** * Guess what. */ -void setinvlimit(int invno, int n) { +static void SetInvLimit(int invno, int n) { InvSetLimit(invno, n); } /** * Guess what. */ -void setinvsize(int invno, int MinWidth, int MinHeight, +static void SetInvSize(int invno, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight); } @@ -1440,7 +2639,7 @@ void setinvsize(int invno, int MinWidth, int MinHeight, /** * Guess what. */ -void setlanguage(LANGUAGE lang) { +static void SetLanguage(LANGUAGE lang) { assert(lang == TXT_ENGLISH || lang == TXT_FRENCH || lang == TXT_GERMAN || lang == TXT_ITALIAN || lang == TXT_SPANISH); // ensure language is valid @@ -1451,191 +2650,332 @@ void setlanguage(LANGUAGE lang) { /** * Set palette */ -void setpalette(SCNHANDLE hPal, bool escOn, int myescEvent) { +static void SetPalette(SCNHANDLE hPal, bool escOn, int myEscape) { // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; ChangePalette(hPal); } +/** + * SetSystemString + */ + +static void SetSystemString(int stringId, SCNHANDLE hString) { + SetSysString(stringId, hString); +} + +/** + * Set a system variable + */ +static void SetSystemVar(int varId, int newValue) { + SetSysVar(varId, newValue); +} + /** * Turn a tag on. */ -void settag(int tagno) { - EnableTag(tagno); +static void SetTag(CORO_PARAM, int tagno) { + EnableTag(coroParam, tagno); } /** * Initialise a timer. */ -void settimer(int timerno, int start, bool up, bool frame) { - DwSetTimer(timerno, start, up != 0, frame != 0); +static void SetTimer(int timerno, int start, bool up, bool frame) { + StartTimer(timerno, start, up != 0, frame != 0); +} + +/** + * Shell("cmdline") + */ +static void Shell(SCNHANDLE commandLine) { + LoadStringRes(commandLine, TextBufferAddr(), TBUFSZ); + error("Tried to execute shell command \"%s\"", TextBufferAddr()); +} + +/** + * Don't hide an actors graphics. + */ +static void ShowActorFn(CORO_PARAM, int actor) { + ShowActor(coroParam, actor); +} + +/** + * Turn a blocking polygon on. + */ +void ShowBlock(int blockno) { + EnableBlock(blockno); +} + +/** + * Turn an effect polygon on. + */ +void ShowEffect(int effect) { + EnableEffect(effect); } #ifdef DEBUG /** * Enable display of diagnostic co-ordinates. */ -void showpos(void) { +static void showpos(void) { setshowpos(); } /** * Enable display of diagnostic co-ordinates. */ -void showstring(void) { +static void showstring(void) { setshowstring(); } #endif +/** + * Shows the main menu + */ +static void ShowMenu(void) { + OpenMenu(MAIN_MENU); +} + +/** + * Turn a path on. + */ +static void ShowPath(int path) { + EnablePath(path); +} + +/** + * Turn a refer on. + */ +void ShowRefer(int refer) { + EnableRefer(refer); +} + +/** + * Turn a tag on. + */ +static void ShowTag(CORO_PARAM, int tag, HPOLYGON hp) { + // Tag could be zero, meaning calling tag + EnableTag(coroParam, tag ? tag : GetTagPolyId(hp)); +} + /** * 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) { +static void SPlay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myEscape) { // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; - play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myescEvent, false); + Play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myEscape, false); } /** * (Re)Position an actor. * If moving actor is not around yet in this scene, start it up. */ +void Stand(CORO_PARAM, int actor, int x, int y, SCNHANDLE hFilm) { + CORO_BEGIN_CONTEXT; + PMOVER pMover; // Moving actor structure + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->pMover = GetMover(actor); + assert(!TinselV2 || (_ctx->pMover != NULL)); + + if (_ctx->pMover) { + if (TinselV2) { + // New special. If no paths, just ignore this + if (PathCount() == 0) + return; -void stand(int actor, int x, int y, SCNHANDLE film) { - PMACTOR pActor; // Moving actor structure + // Another new special. + // If lead actor, and TalkVia, ignore + if ((actor == GetLeadId() || actor == LEAD_ACTOR) && SysVar(ISV_DIVERT_ACTOR)) + return; + } - pActor = GetMover(actor); - if (pActor) { - if (pActor->MActorState == NO_MACTOR) { + if (!MoverIs(_ctx->pMover)) { // create a moving actor process - MActorProcessCreate(x, y, (actor == LEAD_ACTOR) ? LeadId() : actor, pActor); + MoverProcessCreate(x, y, (actor == LEAD_ACTOR) ? GetLeadId() : actor, _ctx->pMover); + + if (hFilm == TF_NONE) { + // Make sure there is an assigned actorObj + while (!_ctx->pMover->actorObj) + CORO_SLEEP(1); - if (film == TF_NONE) { - SetMActorStanding(pActor); + SetMoverStanding(_ctx->pMover); } 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; + // Check hFilm against certain constants. Note that a switch statement isn't + // used here because it would interfere with our co-routine implementation + if (hFilm == TF_UP) { + if (TinselV2) CORO_GIVE_WAY; + SetMoverDirection(_ctx->pMover, AWAY); + SetMoverStanding(_ctx->pMover); + } else if (hFilm == TF_DOWN) { + if (TinselV2) CORO_GIVE_WAY; + SetMoverDirection(_ctx->pMover, FORWARD); + SetMoverStanding(_ctx->pMover); + } else if (hFilm == TF_LEFT) { + if (TinselV2) CORO_GIVE_WAY; + SetMoverDirection(_ctx->pMover, LEFTREEL); + SetMoverStanding(_ctx->pMover); + } else if (hFilm == TF_RIGHT) { + if (TinselV2) CORO_GIVE_WAY; + SetMoverDirection(_ctx->pMover, RIGHTREEL); + SetMoverStanding(_ctx->pMover); + } else if (hFilm != TF_NONE) { + if (TinselV2) CORO_GIVE_WAY; + AlterMover(_ctx->pMover, hFilm, AR_NORMAL); } } } else { - switch (film) { + switch (hFilm) { case TF_NONE: if (x != -1 && y != -1) - MoveMActor(pActor, x, y); + PositionMover(_ctx->pMover, x, y); break; case TF_UP: - SetMActorDirection(pActor, AWAY); + SetMoverDirection(_ctx->pMover, AWAY); if (x != -1 && y != -1) - MoveMActor(pActor, x, y); - SetMActorStanding(pActor); + PositionMover(_ctx->pMover, x, y); + SetMoverStanding(_ctx->pMover); break; case TF_DOWN: - SetMActorDirection(pActor, FORWARD); + SetMoverDirection(_ctx->pMover, FORWARD); if (x != -1 && y != -1) - MoveMActor(pActor, x, y); - SetMActorStanding(pActor); + PositionMover(_ctx->pMover, x, y); + SetMoverStanding(_ctx->pMover); break; case TF_LEFT: - SetMActorDirection(pActor, LEFTREEL); + SetMoverDirection(_ctx->pMover, LEFTREEL); if (x != -1 && y != -1) - MoveMActor(pActor, x, y); - SetMActorStanding(pActor); + PositionMover(_ctx->pMover, x, y); + SetMoverStanding(_ctx->pMover); break; case TF_RIGHT: - SetMActorDirection(pActor, RIGHTREEL); + SetMoverDirection(_ctx->pMover, RIGHTREEL); if (x != -1 && y != -1) - MoveMActor(pActor, x, y); - SetMActorStanding(pActor); + PositionMover(_ctx->pMover, x, y); + SetMoverStanding(_ctx->pMover); break; default: if (x != -1 && y != -1) - MoveMActor(pActor, x, y); - AlterMActor(pActor, film, AR_NORMAL); + PositionMover(_ctx->pMover, x, y); + AlterMover(_ctx->pMover, hFilm, AR_NORMAL); break; } } } else if (actor == NULL_ACTOR) { // } else { - assert(film != 0); // Trying to play NULL film + assert(hFilm != 0); // Trying to play NULL film // Kick off the play and return. - playFilm(film, x, y, actor, false, 0, false, 0, false); + CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, actor, false, 0, false, 0, false)); } + + CORO_END_CODE; } /** * Position the actor at the polygon's tag node. */ -void standtag(int actor, HPOLYGON hp) { - SCNHANDLE film; +static void StandTag(int actor, HPOLYGON hp) { + SCNHANDLE hFilm; int pnodex, pnodey; - assert(hp != NOPOLY); // standtag() may only be called from a polygon code block + assert(hp != NOPOLY); // StandTag() may only be called from a polygon code block + + // Where to stand + GetPolyNode(hp, &pnodex, &pnodey); // 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); + hFilm = GetPolyFilm(hp); + + // other actors can use direction + if (TinselV2) { + if (actor != LEAD_ACTOR && actor != GetLeadId() + && hFilm != TF_UP && hFilm != TF_DOWN + && hFilm != TF_LEFT && hFilm != TF_RIGHT) + hFilm = 0; + + Stand(nullContext, actor, pnodex, pnodey, hFilm); + + } else if (hFilm && (actor == LEAD_ACTOR || actor == GetLeadId())) + Stand(nullContext, actor, pnodex, pnodey, hFilm); else - stand(actor, pnodex, pnodey, 0); + Stand(nullContext, actor, pnodex, pnodey, 0); } + /** - * Kill a moving actor's walk. + * StartGlobalProcess */ -void stop(int actor) { - PMACTOR pActor; +static void StartGlobalProcess(CORO_PARAM, uint32 procID) { + GlobalProcessEvent(coroParam, procID, STARTUP, false, 0); +} - pActor = GetMover(actor); - assert(pActor); // Trying to stop a null actor +/** + * StartProcess + */ +static void StartProcess(CORO_PARAM, uint32 procID) { + SceneProcessEvent(coroParam, procID, STARTUP, false, 0); +} - GetToken(pActor->actorToken); // Kill the walk process - pActor->stop = true; // Cause the actor to stop - FreeToken(pActor->actorToken); +/** + * Initialise a timer. + */ +static void StartTimerFn(int timerno, int start, bool up, int fs) { + StartTimer(timerno, start, up, fs); } -void stopmidi(void) { +void StopMidiFn(void) { StopMidi(); // Stop any currently playing midi } -void stopsample(void) { - _vm->_sound->stopAllSamples(); // Stop any currently playing sample +/** + * Kill a specific sample, or all samples. + */ +void StopSample(int sample) { + if (sample == -1) + _vm->_sound->stopAllSamples(); // Stop any currently playing sample + else + _vm->_sound->stopSpecSample(sample, 0); +} + +/** + * Kill a moving actor's walk. + */ +static void StopWalk(int actor) { + PMOVER pMover; + + pMover = GetMover(actor); + assert(pMover); + + if (TinselV2) { + if (MoverHidden(pMover)) + return; + + StopMover(pMover); // Cause the actor to stop + } else { + GetToken(pMover->actorToken); // Kill the walk process + pMover->bStop = true; // Cause the actor to stop + FreeToken(pMover->actorToken); + } } -void subtitles(int onoff) { +/** + * Subtitles on/off + */ +static void Subtitles(int onoff) { assert (onoff == ST_ON || onoff == ST_OFF); if (isJapanMode()) @@ -1651,271 +2991,468 @@ void subtitles(int onoff) { * 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) { +static void Swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, int zOverride, bool escOn, int myEscape) { CORO_BEGIN_CONTEXT; - bool took_control; // Set if this function takes control + bool bTookControl; // Set if this function takes control CORO_END_CONTEXT(_ctx); + HPOLYGON hPath; + CORO_BEGIN_CODE(_ctx); // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) { + if (TinselV2) { + if (x2 == -1 && y2 == -1) + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0)); + else + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x2, y2, 0)); + } + 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; + if (actor == GetLeadId() || actor == LEAD_ACTOR) { + _ctx->bTookControl = GetControl(CONTROL_OFFV2); + if (TinselV2 && _ctx->bTookControl) + RestoreMainCursor(); + } else { + _ctx->bTookControl = false; + } - hPath = InPolygon(x1, y1, PATH); - if (hPath != NOPOLY) { - // Walking out of a path - stand(actor, x1, y1, 0); + if (TinselV2 && (x2 == -1) && (y2 == -1)) { + // First co-ordinates are the destination + x2 = x1; + y2 = y1; } 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 + // Stand at the start co-ordinates + hPath = InPolygon(x1, y1, PATH); - // Walking into a path - stand(actor, x2, y2, 0); // Get path's characteristics - stand(actor, x1, y1, 0); + if (hPath != NOPOLY) { + // Walking out of a path + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, 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 + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x2, y2, 0)); // Get path's characteristics + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0)); + } + + if (TinselV2 && (zOverride != -1)) { + PMOVER pMover = GetMover(actor); + assert(pMover); + + SetMoverZ(pMover, y1, zOverride); + } } - CORO_INVOKE_ARGS(walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, escOn, myescEvent)); + CORO_INVOKE_ARGS(Walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, zOverride, escOn, myEscape)); // Free control if we took it - if (_ctx->took_control) - control(CONTROL_ON); + if (_ctx->bTookControl) + Control(CONTROL_ON); CORO_END_CODE; } /** - * Define a tagged actor. + * Gets a system variable */ +static int SystemVar(int varId) { + return SysVar(varId); +} -void tagactor(int actor, SCNHANDLE text, int tp) { +/** + * Define a tagged actor. + */ +static void TagActor(int actor, SCNHANDLE text, int tp) { Tag_Actor(actor, text, tp); } +/** + * TagPos([tag #]) + */ +static int TagPos(MASTER_LIB_CODES operand, int tagno, HPOLYGON hp) { + int x, y; + + // Tag could be zero, meaning calling tag + if (tagno == 0) + tagno = GetTagPolyId(hp); + + if (operand == TAGTAGXPOS || operand == TAGTAGYPOS) { + SCNHANDLE junk; + + GetTagTag(GetTagHandle(tagno), &junk, &x, &y); + } else { + GetPolyNode(GetTagHandle(tagno), &x, &y); + } + + if (operand == TAGTAGXPOS || operand == TAGWALKXPOS) + return x; + else + return y; +} + /** * Text goes over actor's head while actor plays the talk reel. */ +static void FinishTalkingReel(CORO_PARAM, PMOVER pMover, int actor) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); -void FinishTalkingReel(PMACTOR pActor, int actor) { - if (pActor) { - SetMActorStanding(pActor); - AlterMActor(pActor, 0, AR_POPREEL); + if (pMover) { + SetMoverStanding(pMover); + AlterMover(pMover, 0, AR_POPREEL); } else { - setActorTalking(actor, false); - playFilm(getActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false); + SetActorTalking(actor, false); + CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, GetActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false)); } + + CORO_END_CODE; } -void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool escOn, int myescEvent) { +static void TalkOrSay(CORO_PARAM, SPEECH_TYPE speechType, SCNHANDLE hText, int x, int y, + SCNHANDLE hFilm, int actorId, bool bSustain, bool escOn, int myEscape) { CORO_BEGIN_CONTEXT; int Loffset, Toffset; // Top left of display int actor; // The speaking actor - PMACTOR pActor; // For moving actors - int myleftEvent; + PMOVER pActor; // For moving actors + int myLeftEvent; + int escEvents; 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 bSamples; bool bTalkReel; // Set while talk reel is playing Audio::SoundHandle handle; int timeout; + + SPEECH_TYPE whatSort; + TFTYPE direction; + int sub; + int x, y; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); + _ctx->whatSort = speechType; + _ctx->escEvents = myEscape; + _ctx->x = x; + _ctx->y = y; _ctx->Loffset = 0; _ctx->Toffset = 0; _ctx->ticks = 0; + _ctx->pText = NULL; + + // If waiting is enabled, wait for ongoing scroll + if (TinselV2 && SysVar(SV_SPEECHWAITS)) + CORO_INVOKE_1(WaitScroll, myEscape); // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; - _ctx->myleftEvent = GetLeftEvents(); + _ctx->myLeftEvent = GetLeftEvents(); // If this actor is dead, call a stop to the calling process - if (actorid && !actorAlive(actorid)) + if (!TinselV2 && (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; + if (!TinselV2 || (speechType == IS_TALK)) { + /* + * Find out which actor is talking + * and with which direction if no film supplied + */ + switch (hFilm) { + case TF_NONE: + case TF_UP: + case TF_DOWN: + case TF_LEFT: + case TF_RIGHT: + _ctx->actor = GetLeadId(); // If no film, actor is lead actor + _ctx->direction = (TFTYPE)hFilm; + break; - default: - _ctx->actor = extractActor(film); - assert(_ctx->actor); // talk() - no actor ID in the reel - direction = TF_BOGUS; - break; - } + default: + _ctx->actor = ExtractActor(hFilm); + assert(_ctx->actor); // talk() - no actor ID in the reel + _ctx->direction = TF_FILM; + break; + } + assert(_ctx->actor); + } else if (TinselV2) + _ctx->actor = actorId; /* * Lock out the user (for lead actor, if not already locked out) * May need to disable tags for other actors */ - if (_ctx->actor == LeadId()) + if (_ctx->actor == GetLeadId() || (TinselV2 && (_ctx->actor == LEAD_ACTOR))) _ctx->bTookControl = GetControl(CONTROL_OFF); else _ctx->bTookControl = false; _ctx->bTookTags = DisableTagsIfEnabled(); + if (TinselV2) { + /* + * Divert stuff + */ + if (SysVar(ISV_DIVERT_ACTOR) && (_ctx->actor == GetLeadId() || _ctx->actor == LEAD_ACTOR)) { + _ctx->actor = SysVar(ISV_DIVERT_ACTOR); + if (_ctx->whatSort == IS_TALK) + _ctx->whatSort = IS_SAY; + else if (_ctx->whatSort == IS_TALKAT) + _ctx->whatSort = IS_SAYAT; + } + } + /* * 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); + if (volVoice != 0 && _vm->_sound->sampleExists(hText)) { + if (!TinselV2) { + _vm->_sound->playSample(hText, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSamples = _vm->_mixer->isSoundHandleActive(_ctx->handle); + } else + _ctx->bSamples = true; } else - _ctx->bSample = false; + _ctx->bSamples = 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); + if (_ctx->whatSort == IS_TALK) { + if (_ctx->pActor) { + if (_ctx->direction != TF_FILM) + hFilm = GetMoverTalkReel(_ctx->pActor, _ctx->direction); + AlterMover(_ctx->pActor, hFilm, AR_PUSHREEL); + } else { + SetActorTalking(_ctx->actor, true); + SetActorTalkFilm(_ctx->actor, hFilm); + CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, -1, -1, 0, false, 0, escOn, myEscape, false)); + } + _ctx->bTalkReel = true; + CORO_SLEEP(1); // Allow the play to come in + + } else if (_ctx->whatSort == IS_TALKAT) { + _ctx->bTalkReel = false; + + } else if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_SAYAT)) { + _ctx->bTalkReel = false; + if (IsTaggedActor(_ctx->actor)) { + CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->actor, TALKING, false, 0)); + } else if (IsTagPolygon(_ctx->actor | ACTORTAG_KEY)) { + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, GetTagHandle(_ctx->actor | ACTORTAG_KEY), + TALKING, 0, false, 0)); + } + + if (TinselV2) + // Let it all kick in and position this 'waiting' process + // down the process list from the playing process(es) + // This ensures immediate return when the reel finishes + CORO_GIVE_WAY; } - _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); + // Make multi-ones escape + if (TinselV2 && (SubStringCount(hText) > 1) && !_ctx->escEvents) + _ctx->escEvents = GetEscEvents(); - /* - * 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 + for (_ctx->sub = 0; _ctx->sub < (TinselV2 ? SubStringCount(hText) : 1); _ctx->sub++) { + if (TinselV2 && _ctx->escEvents && _ctx->escEvents != GetEscEvents()) + break; /* - * Don't go off the side of the screen + * Display the text. */ - 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->bSample = _ctx->bSamples; + _ctx->pText = NULL; - _ctx->timeout = SAMPLETIMEOUT; - do { - // Keep text in place if scrolling - if (_ctx->pText != NULL) { - int nLoff, nToff; + if (isJapanMode()) { + _ctx->ticks = JAP_TEXT_TIME; + } else if (bSubtitles || !_ctx->bSample) { + /* + * Work out where to display the text + */ + int xshift, yshift; + + PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset); + if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_TALK)) + GetActorMidTop(_ctx->actor, &_ctx->x, &_ctx->y); + + SetTextPal(GetActorRGB(_ctx->actor)); + if (TinselV2) + LoadSubString(hText, _ctx->sub, TextBufferAddr(), TBUFSZ); + else { + LoadStringRes(hText, TextBufferAddr(), TBUFSZ); + + _ctx->y -= _ctx->Toffset; + } - 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; + _ctx->pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), + TextBufferAddr(), 0, _ctx->x - _ctx->Loffset, _ctx->y - _ctx->Toffset, + GetTalkFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // talk() string produced NULL text; + + if (IsTopWindow()) + MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + + if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_TALK)) { + /* + * Set bottom of text just above the speaker's head + * But don't go off the top of the screen + */ + if (TinselV2) + MultiMoveRelXY(_ctx->pText, 0, _ctx->y - _ctx->Toffset - MultiLowest(_ctx->pText) - 2); + else { + yshift = _ctx->y - 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); + } } + + if (TinselV2) + // Don't go off the screen + KeepOnScreen(_ctx->pText, &_ctx->x, &_ctx->y); + + /* + * Work out how long to talk. + * During this time, reposition the text if the screen scrolls. + */ + _ctx->ticks = TextTime(TextBufferAddr()); } - CORO_SLEEP(1); - --_ctx->timeout; + if (TinselV2 && _ctx->bSample) { + // Kick off the sample now (perhaps with a delay) + if (bNoPause) + bNoPause = false; + else + CORO_SLEEP(SysVar(SV_SPEECHDELAY)); - // 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; + //SamplePlay(VOICE, hText, _ctx->sub, false, -1, -1, PRIORITY_TALK); + _vm->_sound->playSample(hText, _ctx->sub, false, -1, -1, PRIORITY_TALK, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + } + + _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); + + // Handle timeout decrementing and Escape presses + if (TinselV2) { + if ((_ctx->escEvents && _ctx->escEvents != GetEscEvents()) || + (!bSustain && LeftEventChange(_ctx->myLeftEvent)) || + (--_ctx->timeout <= 0)) { + // Left event only kills current sub-string + _ctx->myLeftEvent = GetLeftEvents(); + break; + } + } else { + --_ctx->timeout; - 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 + // Abort if escapable and ESCAPE is pressed + // Abort if left click - hardwired feature for talk! + // Abort if sample times out + if ((escOn && myEscape != GetEscEvents()) + || (_ctx->myLeftEvent != GetLeftEvents()) + || (_ctx->timeout <= 0)) break; - } else { - // Talk reel stops at end of speech - FinishTalkingReel(_ctx->pActor, _ctx->actor); - _ctx->bTalkReel = false; - _ctx->bSample = false; + } + + 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 + if (!TinselV2 || (_ctx->bTalkReel && (_ctx->sub == SubStringCount(hText) - 1))) { + CORO_INVOKE_2(FinishTalkingReel, _ctx->pActor, _ctx->actor); + _ctx->bTalkReel = false; + } + _ctx->bSample = false; + } } + } else { + // No sample - just depends on time + if (_ctx->ticks-- <= 0) + break; } - } else { - // No sample - just depends on time - if (_ctx->ticks-- <= 0) - break; + } while (1); + + if (_ctx->pText != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _ctx->pText = NULL; } - } while (1); + if (TinselV2 && _ctx->bSample) + _vm->_sound->stopSpecSample(hText, _ctx->sub); + } /* * The talk is over now - dump the text * Stop the sample * Restore the actor's film or standing reel */ + if (_ctx->bTalkReel) + CORO_INVOKE_2(FinishTalkingReel, _ctx->pActor, _ctx->actor); if (_ctx->pText != NULL) MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); - _vm->_mixer->stopHandle(_ctx->handle); - if (_ctx->bTalkReel) - FinishTalkingReel(_ctx->pActor, _ctx->actor); + + if (TinselV2) { + if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_SAYAT)) { + SetActorTalking(_ctx->actor, false); + if (IsTaggedActor(_ctx->actor)) + CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->actor, ENDTALK, false, 0)); + else if (IsTagPolygon(_ctx->actor | ACTORTAG_KEY)) + CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, + GetTagHandle(_ctx->actor | ACTORTAG_KEY), ENDTALK, 0, false, 0)); + + CORO_SLEEP(1); + } + } else { + _vm->_mixer->stopHandle(_ctx->handle); + } /* * Restore user control and tags, as appropriate * And, finally, release the talk token. */ - if (_ctx->bTookControl) - control(CONTROL_ON); + if (_ctx->bTookControl) { + if (TinselV2) ControlOn(); else Control(CONTROL_ON); + } if (_ctx->bTookTags) EnableTags(); @@ -1923,135 +3460,238 @@ void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool es } /** - * talkat(actor, x, y, text) + * TalkAt(actor, x, y, text) */ -void talkat(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myescEvent) { +static void TalkAt(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myEscape) { if (!coroParam) { // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; if (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text))) - setTextPal(getActorTcol(actor)); + SetTextPal(GetActorRGB(actor)); } - print(coroParam, x, y, text, 0, 0, escOn, myescEvent); + Print(coroParam, x, y, text, 0, false, escOn, myEscape); } /** - * talkats(actor, x, y, text, sustain) + * TalkAtS(actor, x, y, text, sustain) */ -void talkats(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myescEvent) { +static void TalkAtS(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myEscape) { if (!coroParam) { assert(sustain == 2); // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; if (!isJapanMode()) - setTextPal(getActorTcol(actor)); + SetTextPal(GetActorRGB(actor)); } - print(coroParam, x, y, text, 0, sustain, escOn, myescEvent); + Print(coroParam, x, y, text, 0, sustain == 2, escOn, myEscape); } /** * Set talk font's palette entry. */ -void talkattr(int r1, int g1, int b1, bool escOn, int myescEvent) { +static void TalkAttr(int r1, int g1, int b1, bool escOn, int myEscape) { if (isJapanMode()) return; // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != 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)); + SetTextPal(RGB(r1, g1, b1)); +} + +/** + * TalkPaletteIndex + */ +static void TalkPaletteIndex(unsigned index) { + assert(index); + + SetTalkTextOffset(index); +} + +/** + * Set talk font's palette entry. + */ +static void TalkRGB(COLORREF colour, int myescEvent) { + // Don't do it if it's not wanted + if (myescEvent && myescEvent != GetEscEvents()) + return; + + SetTextPal(colour); +} + +/** + * TalkVia("actor"/off) + */ +static void TalkVia(int actor) { + SetSysVar(ISV_DIVERT_ACTOR, actor); +} + +/** + * Declare a temporary text font. + */ +static void TempTagFont(SCNHANDLE hFilm) { + SetTempTagFontHandle(hFilm); // Store the font handle +} + +/** + * Declare a temporary text font. + */ +static void TempTalkFont(SCNHANDLE hFilm) { + SetTempTalkFontHandle(hFilm); // Store the font handle +} + +/** + * ThisObject + */ +static int ThisObject(INV_OBJECT *pinvo) { + assert(pinvo != NULL); + + return pinvo->id; +} + +/** + * ThisTag + */ +static int ThisTag(HPOLYGON hp) { + int tagno; + + assert(hp != NOPOLY); + + tagno = GetTagPolyId(hp); + + assert(IsTagPolygon(tagno)); + assert(tagno); + + return tagno; } /** * Get a timer's current count. */ -int timer(int timerno) { +static int TimerFn(int timerno) { return Timer(timerno); } +/** + * Return the icon that caused the CONVERSE event. + */ +int Topic(void) { + return GetIcon(); +} + /** * 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); +static 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); +} +static void TopPlay(CORO_PARAM, SCNHANDLE hFilm, int x, int y, bool bComplete, int myescEvent, TINSEL_EVENT event) { + Play(coroParam, hFilm, x, y, bComplete, myescEvent, true, event, NOPOLY, 0); } /** * Open or close the 'top window' */ -void topwindow(int bpos) { - assert(bpos == TW_START || bpos == TW_END); +static void TopWindow(int bpos) { + bool isStart = (TinselV2 && (bpos != 0)) || (!TinselV2 && (bpos == TW_START)); - switch (bpos) { - case TW_END: - KillInventory(); - break; + KillInventory(); - case TW_START: - KillInventory(); - PopUpConf(TOPWIN); - break; + if (isStart) + OpenMenu(TOP_WINDOW); +} + +/** + * TranslucentIndex + */ +static void TranslucentIndex(unsigned index) { + assert(index <= 255); + + SetTranslucencyOffset(index); +} + +/** + * Play a sample. + */ +static void TryPlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) { + 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, bComplete, escOn, myEscape)); + CORO_END_CODE; +} + +/** + * UnDimMusic + */ +static void UnDimMusic(void) { + _vm->_pcmMusic->unDim(true); } /** * unhookscene */ -void unhookscene(void) { +static void UnHookSceneFn(void) { UnHookScene(); } /** * Un-define an actor as tagged. */ -void untagactor(int actor) { +static void UnTagActorFn(int actor) { UnTagActor(actor); } /** * vibrate */ -void vibrate(void) { +static void Vibrate(void) { } /** * waitframe(int actor, int frameNumber) */ -void waitframe(CORO_PARAM, int actor, int frameNumber, bool escOn, int myescEvent) { +static void WaitFrame(CORO_PARAM, int actor, int frameNumber, bool escOn, int myEscape) { 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()) + while (GetActorSteps(actor) < frameNumber) { + // Don't do it if it's not wanted + if (escOn && myEscape != GetEscEvents()) break; + + CORO_SLEEP(1); } + CORO_END_CODE; } /** * Return when a key pressed or button pushed. */ -void waitkey(CORO_PARAM, bool escOn, int myescEvent) { +static void WaitKey(CORO_PARAM, bool escOn, int myEscape) { CORO_BEGIN_CONTEXT; int startEvent; int startX, startY; @@ -2060,52 +3700,77 @@ void waitkey(CORO_PARAM, bool escOn, int myescEvent) { CORO_BEGIN_CODE(_ctx); // Don't do it if it's not wanted - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != GetEscEvents()) return; - while (1) { + for (;;) { _ctx->startEvent = getUserEvents(); - // Store cursor position - while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false)) - CORO_SLEEP(1); + if (!TinselV2) { + // 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 + if (!TinselV2) { + int curX, curY; + GetCursorXY(&curX, &curY, false); // Store cursor position + if (curX != _ctx->startX || curY != _ctx->startY) + break; + } - int curX, curY; - GetCursorXY(&curX, &curY, false); // Store cursor position - if (curX != _ctx->startX || curY != _ctx->startY) - break; - - if (IsConfWindow()) + if (MenuActive()) break; } - if (!IsConfWindow()) + if (!MenuActive()) return; - + do { CORO_SLEEP(1); - } while (IsConfWindow()); + } while (MenuActive()); CORO_SLEEP(ONE_SECOND / 2); // Let it die down } CORO_END_CODE; } +/** + * Return when no scrolling is going on. + */ +void WaitScroll(CORO_PARAM, int myescEvent) { + CORO_BEGIN_CONTEXT; + int time; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // wait for ongoing scroll + while (IsScrolling()) { + if (myescEvent && myescEvent != GetEscEvents()) + break; + + CORO_SLEEP(1); + } + + CORO_END_CODE; +} + /** * Pause for requested time. */ -void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) { +static void WaitTime(CORO_PARAM, int time, bool frame, bool escOn, int myEscape) { 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()) + if (escOn && myEscape != GetEscEvents()) return; if (!frame) @@ -2116,51 +3781,97 @@ void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) { CORO_SLEEP(1); // Abort if escapable and ESCAPE is pressed - if (escOn && myescEvent != GetEscEvents()) + if (escOn && myEscape != 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) { +void Walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE hFilm, int hold, bool igPath, + int zOverride, bool escOn, int myescEvent) { CORO_BEGIN_CONTEXT; + int thisWalk; CORO_END_CONTEXT(_ctx); - PMACTOR pActor = GetMover(actor); - assert(pActor); // Can't walk a non-moving actor + bool bQuick = hold != 0; + PMOVER pMover = GetMover(actor); + assert(pMover); // Can't walk a non-moving actor CORO_BEGIN_CODE(_ctx); // Straight there if escaped if (escOn && myescEvent != GetEscEvents()) { - stand(actor, x, y, 0); + if (TinselV2) + StopMover(pMover); + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0)); return; } - assert(pActor->hCpath != NOPOLY); // moving actor not in path + if (TinselV2) { + if (MoverHidden(pMover)) + return; - GetToken(pActor->actorToken); - SetActorDest(pActor, x, y, igPath, film); - DontScrollCursor(); + // Test 10/10/96 + while (!MoverIs(pMover)) + CORO_SLEEP(1); + } + + assert(pMover->hCpath != NOPOLY); // moving actor not in path + + // Croak if he is doing an SWalk() + if (TinselV2) { + // Croak if he is doing an SWalk() + if (MoverIsSWalking(pMover)) + CORO_KILL_SELF(); - if (hold == 2) { - ; + _ctx->thisWalk = SetActorDest(pMover, x, y, igPath, hFilm); + SetMoverZoverride(pMover, zOverride); + DontScrollCursor(); + + if (!bQuick) { + while (MoverMoving(pMover)) { + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + StopMover(pMover); + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0)); + break; + } + + CORO_SLEEP(1); + + // Die if superceded + if (_ctx->thisWalk != GetWalkNumber(pMover)) + CORO_KILL_SELF(); + } + } } else { - while (MAmoving(pActor)) { - CORO_SLEEP(1); - // Straight there if escaped - if (escOn && myescEvent != GetEscEvents()) { - stand(actor, x, y, 0); - FreeToken(pActor->actorToken); - return; + GetToken(pMover->actorToken); + SetActorDest(pMover, x, y, igPath, hFilm); + DontScrollCursor(); + + if (hold == 2) { + ; + } else { + while (MoverMoving(pMover)) { + CORO_SLEEP(1); + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0)); + FreeToken(pMover->actorToken); + return; + } } } + + FreeToken(pMover->actorToken); } - FreeToken(pActor->actorToken); + CORO_END_CODE; } @@ -2168,56 +3879,65 @@ void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool ig * 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) { +static void Walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myEscape, bool &retVal) { // COROUTINE CORO_BEGIN_CONTEXT; - int ticket; + int thisWalk; CORO_END_CONTEXT(_ctx); - PMACTOR pActor = GetMover(actor); - assert(pActor); // Can't walk a non-moving actor + PMOVER pMover = GetMover(actor); + assert(pMover); // Can't walk a non-moving actor CORO_BEGIN_CODE(_ctx); // Straight there if escaped - if (escOn && myescEvent != GetEscEvents()) { - stand(actor, x, y, 0); + if (escOn && myEscape != GetEscEvents()) { + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, 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 (TinselV2) { + if (MoverHidden(pMover) || !MoverIs(pMover)) { + retVal = false; + return; + } + assert(pMover->hCpath != NOPOLY); // moving actor not in path - if (_ctx->ticket != GetActorTicket(pActor)) { + // Not if he is doing an SWalk() + if (MoverIsSWalking(pMover)) { retVal = false; return; } + } else { + // Pause before starting the walk + CORO_SLEEP(ONE_SECOND); + + assert(pMover->hCpath != NOPOLY); // moving actor not in path + + // Briefly aquire token to kill off any other normal walk + GetToken(pMover->actorToken); + FreeToken(pMover->actorToken); + } + + _ctx->thisWalk = SetActorDest(pMover, x, y, false, film); + DontScrollCursor(); + + while (MoverMoving(pMover) && (_ctx->thisWalk == GetWalkNumber(pMover))) { // Straight there if escaped - if (escOn && myescEvent != GetEscEvents()) { - stand(actor, x, y, 0); + if (escOn && myEscape != GetEscEvents()) { + CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0)); retVal = true; return; } + + CORO_SLEEP(1); } int endx, endy; - GetMActorPosition(pActor, &endx, &endy); - retVal = (_ctx->ticket == GetActorTicket(pActor) && endx == x && endy == y); + GetMoverPosition(pMover, &endx, &endy); + retVal = (_ctx->thisWalk == GetWalkNumber(pMover) && endx == x && endy == y); CORO_END_CODE; } @@ -2225,10 +3945,15 @@ void walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int /** * Declare a moving actor. */ -void walkingactor(uint32 id, SCNHANDLE *rp) { - PMACTOR pActor; // Moving actor structure +static void WalkingActor(uint32 id, SCNHANDLE *rp = NULL) { + PMOVER pActor; // Moving actor structure + + if (TinselVersion == TINSEL_V2) { + RegisterMover(id); + return; + } - SetMover(id); // Establish as a moving actor + RegisterMover(id); // Establish as a moving actor pActor = GetMover(id); assert(pActor); @@ -2236,16 +3961,16 @@ void walkingactor(uint32 id, SCNHANDLE *rp) { int i, j; for (i = 0; i < 5; ++i) { for (j = 0; j < 4; ++j) - pActor->WalkReels[i][j] = *rp++; + pActor->walkReels[i][j] = *rp++; for (j = 0; j < 4; ++j) - pActor->StandReels[i][j] = *rp++; + 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]; + pActor->walkReels[i][j] = pActor->walkReels[4][j]; + pActor->standReels[i][j] = pActor->standReels[2][j]; } } } @@ -2254,60 +3979,75 @@ void walkingactor(uint32 id, SCNHANDLE *rp) { * 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) { +static void WalkPoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myEscape) { + int pnodex, pnodey; + // COROUTINE CORO_BEGIN_CONTEXT; + int thisWalk; CORO_END_CONTEXT(_ctx); - PMACTOR pActor = GetMover(actor); - assert(pActor); // Can't walk a non-moving actor + assert(hp != NOPOLY); // WalkPoly() may only be called from a polygon code block + PMOVER pMover = GetMover(actor); + assert(pMover); // 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); + if (escOn && myEscape != GetEscEvents()) { + StandTag(actor, hp); return; } - GetToken(pActor->actorToken); - getPolyNode(hp, &pnodex, &pnodey); - SetActorDest(pActor, pnodex, pnodey, false, film); + if (TinselV2) { + if (MoverHidden(pMover)) + return; + + // Croak if he is doing an SWalk() + if (MoverIsSWalking(pMover)) + CORO_KILL_SELF(); + + } else { + GetToken(pMover->actorToken); + } + + GetPolyNode(hp, &pnodex, &pnodey); + _ctx->thisWalk = SetActorDest(pMover, pnodex, pnodey, false, film); DoScrollCursor(); - do { + while (!MoverIsInPolygon(pMover, hp) && MoverMoving(pMover)) { CORO_SLEEP(1); - if (escOn && myescEvent != GetEscEvents()) { + if (escOn && myEscape != GetEscEvents()) { // Straight there if escaped - standtag(actor, hp); - FreeToken(pActor->actorToken); + StandTag(actor, hp); + if (!TinselV2) + FreeToken(pMover->actorToken); return; } - GetMActorPosition(pActor, &aniX, &aniY); - } while (!MActorIsInPolygon(pActor, hp) && MAmoving(pActor)); + // Die if superceded + if (TinselV2 && (_ctx->thisWalk != GetWalkNumber(pMover))) + CORO_KILL_SELF(); + } - FreeToken(pActor->actorToken); + if (!TinselV2) + FreeToken(pMover->actorToken); CORO_END_CODE; } /** - * walktag(actor, reel, hold) + * WalkTag(actor, reel, hold) */ -void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) { +static void WalkTag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myEscape) { // COROUTINE CORO_BEGIN_CONTEXT; + int thisWalk; CORO_END_CONTEXT(_ctx); - PMACTOR pActor = GetMover(actor); - assert(pActor); // Can't walk a non-moving actor + PMOVER pMover = GetMover(actor); + assert(pMover); // Can't walk a non-moving actor CORO_BEGIN_CODE(_ctx); @@ -2316,67 +4056,102 @@ void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block // Straight there if escaped - if (escOn && myescEvent != GetEscEvents()) { - standtag(actor, hp); + if (escOn && myEscape != GetEscEvents()) { + StandTag(actor, hp); return; } - GetToken(pActor->actorToken); - getPolyNode(hp, &pnodex, &pnodey); - SetActorDest(pActor, pnodex, pnodey, false, film); - DoScrollCursor(); + if (!TinselV2) + GetToken(pMover->actorToken); + else { + if (MoverHidden(pMover)) + return; + } - while (MAmoving(pActor)) { - CORO_SLEEP(1); + GetPolyNode(hp, &pnodex, &pnodey); - if (escOn && myescEvent != GetEscEvents()) { + _ctx->thisWalk = SetActorDest(pMover, pnodex, pnodey, false, film); + DoScrollCursor(); + + while (MoverMoving(pMover)) { + if (escOn && myEscape != GetEscEvents()) { // Straight there if escaped - standtag(actor, hp); - FreeToken(pActor->actorToken); + StandTag(actor, hp); + if (!TinselV2) + FreeToken(pMover->actorToken); return; } + + CORO_SLEEP(1); + + // Die if superceded + if (TinselV2 && (_ctx->thisWalk != GetWalkNumber(pMover))) + CORO_KILL_SELF(); } // Adopt the tag-related reel - SCNHANDLE pfilm = getPolyFilm(hp); + SCNHANDLE pFilm = GetPolyFilm(hp); - switch (pfilm) { + switch (pFilm) { case TF_NONE: break; case TF_UP: - SetMActorDirection(pActor, AWAY); - SetMActorStanding(pActor); + SetMoverDirection(pMover, AWAY); + SetMoverStanding(pMover); break; case TF_DOWN: - SetMActorDirection(pActor, FORWARD); - SetMActorStanding(pActor); + SetMoverDirection(pMover, FORWARD); + SetMoverStanding(pMover); break; case TF_LEFT: - SetMActorDirection(pActor, LEFTREEL); - SetMActorStanding(pActor); + SetMoverDirection(pMover, LEFTREEL); + SetMoverStanding(pMover); break; case TF_RIGHT: - SetMActorDirection(pActor, RIGHTREEL); - SetMActorStanding(pActor); + SetMoverDirection(pMover, RIGHTREEL); + SetMoverStanding(pMover); break; default: - if (actor == LEAD_ACTOR || actor == LeadId()) - AlterMActor(pActor, pfilm, AR_NORMAL); + if (actor == LEAD_ACTOR || actor == GetLeadId()) + AlterMover(pMover, pFilm, AR_NORMAL); else - SetMActorStanding(pActor); + SetMoverStanding(pMover); break; } - FreeToken(pActor->actorToken); + if (!TinselV2) + FreeToken(pMover->actorToken); + CORO_END_CODE; } +/** + * Returns the X co-ordinateof lead actor's last walk. + */ +int WalkXPos(void) { + return GetLastLeadXdest(); +} + +/** + * Returns the Y co-ordinateof lead actor's last walk. + */ +int WalkYPos(void) { + return GetLastLeadYdest(); +} + +/** + * Return which is the current CD, counting from 1. + */ +int WhichCd(void) { + return GetCurrentCD(); +} + /** * whichinventory */ -int whichinventory(void) { +int WhichInventory(void) { return WhichInventoryOpen(); } @@ -2395,544 +4170,1350 @@ int whichinventory(void) { * @param pp Top of parameter stack */ int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState) { - debug(7, "CallLibraryRoutine op %d (escOn %d, myescEvent %d)", operand, pic->escOn, pic->myescEvent); - switch (operand) { + int libCode = TinselV2 ? DW2_CODES[operand] : DW1_CODES[operand]; + + debug(7, "CallLibraryRoutine op %d (escOn %d, myEscape %d)", operand, pic->escOn, pic->myEscape); + switch (libCode) { case ACTORATTR: + // DW1 only pp -= 3; // 4 parameters - actorattr(pp[0], pp[1], pp[2], pp[3]); + ActorAttr(pp[0], pp[1], pp[2], pp[3]); return -4; + case ACTORBRIGHTNESS: + // DW2 only + pp -= 1; + ActorBrightness(pp[0], pp[1]); + return -2; + case ACTORDIRECTION: - pp[0] = actordirection(pp[0]); + // Common to both DW1 & DW2 + pp[0] = ActorDirection(pp[0]); return 0; + case ACTORPALETTE: + // DW2 only + pp -= 2; // 3 parameters + ActorPalette(pp[0], pp[1], pp[2]); + return -3; + + case ACTORPRIORITY: + // DW2 only + pp -= 1; // 2 parameters + ActorPriority(pp[0], pp[1]); + return -2; + case ACTORREF: + // Common to both DW1 & DW2 error("actorref isn't a real function!"); + case ACTORRGB: + // DW2 only + pp -= 1; // 2 parameters + ActorRGB(pp[0], pp[1]); + return -2; + case ACTORSCALE: - pp[0] = actorscale(pp[0]); + // Common to both DW1 & DW2 + pp[0] = ActorScale(pp[0]); return 0; case ACTORSON: - actorson(); + // DW1 only + ActorsOn(); return 0; case ACTORXPOS: - pp[0] = actorpos(ACTORXPOS, pp[0]); + // Common to both DW1 & DW2 + pp[0] = ActorPos(ACTORXPOS, pp[0]); return 0; case ACTORYPOS: - pp[0] = actorpos(ACTORYPOS, pp[0]); + // Common to both DW1 & DW2 + pp[0] = ActorPos(ACTORYPOS, pp[0]); return 0; - case ADDICON: - addicon(pp[0]); + case ADDHIGHLIGHT: + // DW2 only + // Command doesn't actually do anything + pp -= 1; // 2 parameters + return -2; + + case ADDINV: + // DW2 only + AddInv(INV_DEFAULT, pp[0]); return -1; case ADDINV1: - addinv(INV_1, pp[0]); + // Common to both DW1 & DW2 + AddInv(INV_1, pp[0]); return -1; case ADDINV2: - addinv(INV_2, pp[0]); + // Common to both DW1 & DW2 + AddInv(INV_2, pp[0]); return -1; case ADDOPENINV: - addinv(INV_OPEN, pp[0]); + // Common to both DW1 & DW2 + AddInv(TinselV2 ? DW2_INV_OPEN : INV_OPEN, pp[0]); + return -1; + + case ADDTOPIC: + // Common to both DW1 & DW2 + AddTopic(pp[0]); return -1; case AUXSCALE: + // DW1 only pp -= 13; // 14 parameters - auxscale(pp[0], pp[1], (SCNHANDLE *)(pp+2)); + AuxScale(pp[0], pp[1], (SCNHANDLE *)(pp+2)); return -14; case BACKGROUND: - background(pp[0]); + // Common to both DW1 & DW2 + Background(coroParam, pp[0]); + return -1; + + case BLOCKING: + // DW2 only + Blocking(pp[0]); return -1; + case CALLACTOR: + case CALLGLOBALPROCESS: + case CALLOBJECT: + // DW2 only + pp -= 1; // 2 parameters + if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + bool result; + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic, &result); + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + pp[0] = result ? 1 : 0; + } else { + uint32 v; + if (libCode == CALLACTOR) + v = SendActor(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape); + else if (libCode == CALLGLOBALPROCESS) + v = SendGlobalProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape); + else + v = SendObject(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape); + + if (coroParam) + return 0; + pp[0] = v; + } + + if (!pp[0]) + KillSelf(coroParam); + return -2; + + + case CALLPROCESS: + // DW2 only + pp -= 1; // 2 parameters + if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + bool result; + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic, &result); + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + + pp[0] = result ? 1 : 0; + } else { + int result = SendProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape); + if (coroParam) + return 0; + + pp[0] = result; + } + return -2; + + case CALLSCENE: + // DW2 only + error("CallScene isn't a real function!"); + + case CALLTAG: + // DW2 only + pp -= 1; // 2 parameters + if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + bool result; + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic, &result); + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + + pp[0] = result ? 1 : 0; + } else { + bool result; + SendTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape, &result); + if (coroParam) + return 0; + + pp[0] = result ? 1 : 0; + } + + if (!pp[0]) + KillSelf(coroParam); + return -2; + case CAMERA: - camera(pp[0]); + // Common to both DW1 & DW2 + Camera(pp[0]); + return -1; + + case CDCHANGESCENE: + // DW2 only + CdChangeScene(pp[0]); + return -1; + + case CDDOCHANGE: + // DW2 only + CdDoChange(coroParam); + return 0; + + case CDENDACTOR: + // DW2 only + CdEndActor(pp[0], pic->myEscape); return -1; case CDLOAD: - pp -= 1; // 2 parameters - cdload(pp[0], pp[1]); + // Common to both DW1 & DW2 + pp -= 1; // 2 parameters + CDload(pp[0], pp[1], pic->myEscape); return -2; case CDPLAY: + // Common to both DW1 & DW2 error("cdplay isn't a real function!"); case CLEARHOOKSCENE: - clearhookscene(); + // Common to both DW1 & DW2 + ClearHookScene(); return 0; case CLOSEINVENTORY: - closeinventory(); + // Common to both DW1 & DW2 + CloseInventory(); return 0; case CONTROL: - control(pp[0]); + // Common to both DW1 & DW2 + Control(pp[0]); return -1; case CONVERSATION: - conversation(pp[0], pic->hpoly, pic->escOn, pic->myescEvent); + // Common to both DW1 & DW2 + Conversation(coroParam, pp[0], pic->hPoly, pic->idActor, pic->escOn, pic->myEscape); + return -1; + + case CONVTOPIC: + // Common to both DW1 & DW2 + ConvTopic(pp[0]); return -1; - case CONVICON: - convicon(pp[0]); + case CURSOR: + // DW2 only + Cursor(pp[0]); return -1; case CURSORXPOS: - pp[0] = cursorpos(CURSORXPOS); + // Common to both DW1 & DW2 + pp[0] = CursorPos(CURSORXPOS); return 0; case CURSORYPOS: - pp[0] = cursorpos(CURSORYPOS); + // Common to both DW1 & DW2 + pp[0] = CursorPos(CURSORYPOS); return 0; case CUTSCENE: + // DW1 only error("cutscene isn't a real function!"); - case DEC_CONVW: + case DECCONVW: + // Common to both DW1 & DW2 pp -= 7; // 8 parameters - dec_convw(pp[0], pp[1], pp[2], pp[3], + DecConvW(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]); return -8; - case DEC_CSTRINGS: + case DECCSTRINGS: + // DW1 only pp -= 19; // 20 parameters - dec_cstrings((SCNHANDLE *)pp); + DecCStrings((SCNHANDLE *)pp); return -20; - case DEC_CURSOR: - dec_cursor(pp[0]); + case DECCURSOR: + // Common to both DW1 & DW2 + DecCursor(pp[0]); return -1; - case DEC_FLAGS: - dec_flags(pp[0]); + case DECFLAGS: + // Common to both DW1 & DW2 + if (TinselV2) + error("DecFlags() is obsolete!"); + + DecFlags(pp[0]); return -1; - case DEC_INV1: + case DECINV1: + // Common to both DW1 & DW2 pp -= 7; // 8 parameters - dec_inv1(pp[0], pp[1], pp[2], pp[3], + DecInv1(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]); return -8; - case DEC_INV2: + case DECINV2: + // Common to both DW1 & DW2 pp -= 7; // 8 parameters - dec_inv2(pp[0], pp[1], pp[2], pp[3], + DecInv2(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]); + case DECINVW: + // Common to both DW1 & DW2 + DecInvW(pp[0]); return -1; - case DEC_LEAD: - pp -= 61; // 62 parameters - dec_lead(pp[0], (SCNHANDLE *)&pp[1], pp[61]); - return -62; + case DECLARELANGUAGE: + // DW2 only + pp -= 2; // 3 parameters + DeclareLanguage(pp[0], pp[1], pp[2]); + return -3; + + case DECLEAD: + // Common to both DW1 & DW2 + if (TinselV2) { + DecLead(pp[0]); + return -1; + } else { + pp -= 61; // 62 parameters + DecLead(pp[0], (SCNHANDLE *)&pp[1], pp[61]); + return -62; + } + + case DECSCALE: + // DW2 only + pp -= 13; // 14 parameters + DecScale(pp[0], pp[1], pp[2], pp[3], pp[4], + pp[5], pp[6], pp[7], pp[8], pp[9], + pp[10], pp[11], pp[12], pp[13]); + return -14; - case DEC_TAGFONT: - dec_tagfont(pp[0]); + case DECTAGFONT: + // Common to both DW1 & DW2 + DecTagFont(pp[0]); return -1; - case DEC_TALKFONT: - dec_talkfont(pp[0]); + case DECTALKFONT: + // Common to both DW1 & DW2 + DecTalkFont(pp[0]); return -1; case DELICON: - delicon(pp[0]); + // DW1 only + DelIcon(pp[0]); return -1; case DELINV: - delinv(pp[0]); + // DW1 only + DelInv(pp[0]); + return -1; + + case DELTOPIC: + // DW2 only + DelTopic(pp[0]); + return -1; + + case DIMMUSIC: + // DW2 only + DimMusic(); + return 0; + + case DROP: + // DW2 only + Drop(pp[0]); return -1; + case DROPEVERYTHING: + // DW2 only + DropEverything(); + return 0; + + case DROPOUT: + // DW1 only + error("DropOut (%d)\n", pp[0]); + case EFFECTACTOR: - assert(pic->event == ENTER || pic->event == LEAVE); // effectactor() must be from effect poly code + // Common to both DW1 & DW2 + assert(pic->event == WALKIN || pic->event == WALKOUT); // effectactor() must be from effect poly code - pp[0] = pic->actorid; + pp[0] = pic->idActor; return 0; - case ENABLEF1: - enablef1(); + case ENABLEMENU: + // Common to both DW1 & DW2 + EnableMenu(); return 0; + case ENDACTOR: + // DW2 only + EndActor(pp[0]); + return -1; + + case ESCAPE: + case ESCAPEOFF: + case ESCAPEON: + // Common to both DW1 & DW2 + error("Escape isn't a real function!"); + case EVENT: - pp[0] = pic->event; + // Common to both DW1 & DW2 + if (TinselVersion == TINSEL_V2) + pp[0] = pic->event; + else + pp[0] = TINSEL1_EVENT_MAP[pic->event]; + return 0; + + case FACETAG: + // DW2 only + FaceTag(pp[0], pic->hPoly); + return -1; + + case FADEIN: + // DW2 only + FadeIn(); return 0; case FADEMIDI: - fademidi(coroParam, pp[0]); + // DW1 only + FadeMidi(coroParam, pp[0]); return -1; + case FADEOUT: + // DW1 only + FadeOut(); + return 0; + case FRAMEGRAB: + // Common to both DW1 & DW2 + return -1; + + case FREEZECURSOR: + // DW2 only + FreezeCursor(pp[0]); return -1; case GETINVLIMIT: - pp[0] = getinvlimit(pp[0]); + // Common to both DW1 & DW2 + pp[0] = GetInvLimit(pp[0]); return 0; + case GHOST: + // DW2 only + pp -= 2; // 3 parameters + Ghost(pp[0], pp[1], pp[2]); + return -3; + + case GLOBALVAR: + // DW1 only + error("GlobalVar isn't a real function!"); + + case GRABMOVIE: + // DW2 only + return -1; + + case HAILSCENE: + // DW2 only + HailScene(pp[0]); + return -1; + case HASRESTARTED: - pp[0] = hasrestarted(); + // Common to both DW1 & DW2 + pp[0] = HasRestarted(); return 0; + case HAVE: + // DW2 only + pp[0] = Have(pp[0]); + return 0; // using return value + case HELDOBJECT: - pp[0] = heldobject(); + // Common to both DW1 & DW2 + pp[0] = HeldObject(); return 0; - case HIDE: - hide(pp[0]); + case HIDEACTOR: + // Common to both DW1 & DW2 + if (!TinselV2) + HideActorFn(coroParam, pp[0]); + else if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic); + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + } else + HideActorFn(coroParam, pp[0]); + return -1; + + case HIDEBLOCK: + // DW2 only + HideBlock(pp[0]); + return -1; + + case HIDEEFFECT: + // DW2 only + HideEffect(pp[0]); + return -1; + + case HIDEPATH: + // DW2 only + HidePath(pp[0]); + return -1; + + case HIDEREFER: + // DW2 only + HideRefer(pp[0]); + return -1; + + case HIDETAG: + // DW2 only + if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic); + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + } else { + HideTag(coroParam, pp[0], pic->hPoly); + if (coroParam) + return 0; + } + return -1; + + case HOLD: + // DW2 only + Hold(pp[0]); return -1; case HOOKSCENE: + // Common to both DW1 & DW2 pp -= 2; // 3 parameters - hookscene(pp[0], pp[1], pp[2]); + HookScene(pp[0], pp[1], pp[2]); return -3; case IDLETIME: - pp[0] = idletime(); + // Common to both DW1 & DW2 + pp[0] = IdleTime(); return 0; case ININVENTORY: - pp[0] = ininventory(pp[0]); + // DW1 only + pp[0] = InInventory(pp[0]); return 0; // using return value + case INSTANTSCROLL: + // DW2 only + InstantScroll(pp[0]); + return -1; + case INVDEPICT: + // DW1 only pp -= 1; // 2 parameters - invdepict(pp[0], pp[1]); + InvDepict(pp[0], pp[1]); return -2; case INVENTORY: - inventory(pp[0], pic->escOn, pic->myescEvent); + // Common to both DW1 & DW2 + Inventory(pp[0], pic->escOn, pic->myEscape); return -1; + case INVPLAY: + // DW2 only + pp -= 1; // 2 parameters + InvPlay(pp[0], pp[1]); + return -2; + case INWHICHINV: - pp[0] = inwhichinv(pp[0]); + // Common to both DW1 & DW2 + pp[0] = InWhichInv(pp[0]); return 0; // using return value case KILLACTOR: - killactor(pp[0]); + // DW1 only + if (TinselV2) + error("KillActor() was not expected to be required!"); + + KillActor(pp[0]); return -1; case KILLBLOCK: - killblock(pp[0]); + // DW1 only + KillBlock(pp[0]); return -1; case KILLEXIT: - killexit(pp[0]); + // DW1 only + KillExit(pp[0]); + return -1; + + case KILLGLOBALPROCESS: + // DW2 only + KillGlobalProcess(pp[0]); + return -1; + + case KILLPROCESS: + // DW2 only + KillProcess(pp[0]); return -1; case KILLTAG: - killtag(pp[0]); + // DW1 only + KillTag(coroParam, pp[0]); return -1; - case LEFTOFFSET: - pp[0] = ltoffset(LEFTOFFSET); - return 0; + case LOCALVAR: + // DW2 only + error("LocalVar isn't a real function!"); case MOVECURSOR: + // Common to both DW1 & DW2 pp -= 1; // 2 parameters - movecursor(pp[0], pp[1]); + MoveCursor(pp[0], pp[1]); return -2; + case MOVETAG: + // DW2 only + pp -= 2; // 3 parameters + MoveTag(pp[0], pp[1], pp[2], pic->hPoly); + return -3; + + case MOVETAGTO: + // DW2 only + pp -= 2; // 3 parameters + MoveTagTo(pp[0], pp[1], pp[2], pic->hPoly); + return -3; + case NEWSCENE: + // Common to both DW1 & DW2 pp -= 2; // 3 parameters if (*pResumeState == RES_2) *pResumeState = RES_NOT; else - newscene(coroParam, pp[0], pp[1], pp[2]); + NewScene(coroParam, pp[0], pp[1], pp[2]); return -3; case NOBLOCKING: - noblocking(); + // Common to both DW1 & DW2 + NoBlocking(); + return 0; + + case NOPAUSE: + // DW2 only + bNoPause = true; return 0; case NOSCROLL: + // Common to both DW1 & DW2 pp -= 3; // 4 parameters - noscroll(pp[0], pp[1], pp[2], pp[3]); + NoScroll(pp[0], pp[1], pp[2], pp[3]); return -4; case OBJECTHELD: - objectheld(pp[0]); + // DW1 only + ObjectHeld(pp[0]); return -1; case OFFSET: - pp -= 1; // 2 parameters - offset(pp[0], pp[1]); - return -2; + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 2; // 2 parameters + Offset((EXTREME)pp[0], pp[1], pp[2]); + return -3; + } else { + pp -= 1; // 2 parameters + Offset(EX_USEXY, pp[0], pp[1]); + return -2; + } + + case OTHEROBJECT: + // DW2 only + pp[0] = OtherObject(pic->pinvo); + return 0; + + case PAUSE: + // DW2 only + WaitTime(coroParam, 1, true, pic->escOn, pic->myEscape); + return 0; case PLAY: - pp -= 5; // 6 parameters + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 3; // 4 parameters + if (*pResumeState == RES_1 && IsCdPlayHandle(pp[0])) + *pResumeState = RES_NOT; + else { + Play(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape, false, + pic->event, pic->hPoly, pic->idActor); + + if (coroParam) + return 0; + } + return -4; - 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; + } else { + pp -= 5; // 6 parameters + + if (pic->event == WALKIN || pic->event == WALKOUT) + Play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myEscape, false); + else + Play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->idActor, false, 0, pic->escOn, pic->myEscape, false); + return -6; + } case PLAYMIDI: + // Common to both DW1 & DW2 pp -= 2; // 3 parameters - playmidi(coroParam, pp[0], pp[1], pp[2]); + PlayMidi(coroParam, pp[0], pp[1], pp[2]); return -3; + case PLAYMOVIE: + // DW2 only + PlayMovie(coroParam, pp[0], pic->myEscape); + return -1; + + case PLAYMUSIC: + // DW2 only + PlayMusic(pp[0]); + return -1; + case PLAYRTF: + // Common to both DW1 & DW2 error("playrtf only applies to cdi!"); case PLAYSAMPLE: + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 3; // 4 parameters + PlaySample(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape); + return -4; + } else { + pp -= 1; // 2 parameters + PlaySample(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape); + return -2; + } + + case POINTACTOR: + // DW2 only + PointActor(pp[0]); + return -1; + + case POINTTAG: + // DW2 only + PointTag(pp[0], pic->hPoly); + return -1; + + case POSTACTOR: + // DW2 only + pp -= 1; // 2 parameters + PostActor(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->idActor, pic->myEscape); + return -2; + + case POSTGLOBALPROCESS: + // DW2 only + pp -= 1; // 2 parameters + PostGlobalProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape); + return -2; + + case POSTOBJECT: + // DW2 only + pp -= 1; // 2 parameters + PostObject(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape); + return -2; + + case POSTPROCESS: + // DW2 only pp -= 1; // 2 parameters - playsample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + PostProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape); return -2; + case POSTTAG: + // DW2 only + pp -= 1; // 2 parameters + PostTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape); + return -2; + + case PREPARESCENE: - preparescene(pp[0]); + // DW1 only + 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; + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 4; // 5 parameters + Print(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4] != 0, pic->escOn, pic->myEscape); + return -5; + } else { + pp -= 5; // 6 parameters + /* pp[2] was intended to be attribute */ + Print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5] == 2, pic->escOn, pic->myEscape); + return -6; + } + + case PRINTCURSOR: + // DW2 only + PrintTag(pic->hPoly, pp[0], pic->idActor, true); + return -1; case PRINTOBJ: - printobj(coroParam, pp[0], pic->pinvo, pic->event); + // Common to both DW1 & DW2 + PrintObj(coroParam, pp[0], pic->pinvo, pic->event, pic->myEscape); return -1; case PRINTTAG: - printtag(pic->hpoly, pp[0]); + // Common to both DW1 & DW2 + PrintTag(pic->hPoly, pp[0], TinselV2 ? pic->idActor : 0, false); return -1; case QUITGAME: - quitgame(); + // Common to both DW1 & DW2 + QuitGame(); return 0; case RANDOM: + // Common to both DW1 & DW2 pp -= 2; // 3 parameters - pp[0] = dw_random(pp[0], pp[1], pp[2]); + pp[0] = RandomFn(pp[0], pp[1], pp[2]); return -2; // One holds return value case RESETIDLETIME: - resetidletime(); + // Common to both DW1 & DW2 + ResetIdleTime(); return 0; case RESTARTGAME: - restartgame(); + // Common to both DW1 & DW2 + RestartGame(); return 0; + case RESTORESCENE: + // Common to both DW1 & DW2 + if (TinselV2) { + RestoreScene(coroParam, (TRANSITS)pp[0]); + return -1; + } else { + RestoreScene(coroParam, TRANS_FADE); + return 0; + } + case RESTORE_CUT: - restore_scene(false); + // DW1 only + RestoreScene(coroParam, TRANS_CUT); return 0; - case RESTORE_SCENE: - restore_scene(true); + case RESUMELASTGAME: + // DW2 only + ResumeLastGame(); return 0; case RUNMODE: - pp[0] = runmode(); + // Common to both DW1 & DW2 + pp[0] = RunMode(); return 0; case SAMPLEPLAYING: - pp[0] = sampleplaying(pic->escOn, pic->myescEvent); + // DW1 only + pp[0] = SamplePlaying(pic->escOn, pic->myEscape); return 0; - case SAVE_SCENE: + case SAVESCENE: + // Common to both DW1 & DW2 if (*pResumeState == RES_1) *pResumeState = RES_2; else - save_scene(coroParam); + SaveScene(coroParam); return 0; + case SAY: + // DW2 only + pp -= 1; // 2 parameters + TalkOrSay(coroParam, IS_SAY, pp[1], 0, 0, 0, pp[0], false, pic->escOn, pic->myEscape); + return -2; + + case SAYAT: + // DW2 only + pp -= 4; // 5 parameters + TalkOrSay(coroParam, IS_SAYAT, pp[3], pp[1], pp[2], 0, pp[0], pp[4], pic->escOn, pic->myEscape); + return -5; + case SCALINGREELS: + // Common to both DW1 & DW2 pp -= 6; // 7 parameters - scalingreels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); + ScalingReels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); return -7; case SCANICON: - pp[0] = scanicon(); + // DW1 only + pp[0] = ScanIcon(); + return 0; + + case SCREENXPOS: + // Common to both DW1 & DW2 + pp[0] = LToffset(SCREENXPOS); + return 0; + + case SCREENYPOS: + // Common to both DW1 & DW2 + pp[0] = LToffset(SCREENYPOS); return 0; case SCROLL: - pp -= 3; // 4 parameters - scroll(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); - return -4; + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 5; // 6 parameters + Scroll(coroParam, (EXTREME)pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myEscape); + return -6; + } else { + pp -= 3; // 4 parameters + Scroll(coroParam, EX_USEXY, pp[0], pp[1], pp[2], pp[2], pp[3], pic->escOn, pic->myEscape); + return -4; + } + + case SCROLLPARAMETERS: + // DW2 only + pp -= 6; // 7 parameters + ScrollParameters(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); + return -7; + + case SENDTAG: + // DW2 only + pp -= 1; // 2 parameters + if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + bool result; + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic, &result); + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + pp[0] = result ? 1 : 0; + } else { + bool result; + SendTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape, &result); + if (coroParam) + return 0; + + pp[0] = result; + } + return -1; + case SETACTOR: - setactor(pp[0]); + // DW1 only + SetActor(pp[0]); return -1; case SETBLOCK: - setblock(pp[0]); + // DW1 only + SetBlock(pp[0]); return -1; case SETEXIT: - setexit(pp[0]); + // DW1 only + SetExit(pp[0]); return -1; case SETINVLIMIT: + // Common to both DW1 & DW2 pp -= 1; // 2 parameters - setinvlimit(pp[0], pp[1]); + SetInvLimit(pp[0], pp[1]); return -2; case SETINVSIZE: + // Common to both DW1 & DW2 pp -= 6; // 7 parameters - setinvsize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); + SetInvSize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); return -7; case SETLANGUAGE: - setlanguage((LANGUAGE)pp[0]); + // Common to both DW1 & DW2 + SetLanguage((LANGUAGE)pp[0]); return -1; case SETPALETTE: - setpalette(pp[0], pic->escOn, pic->myescEvent); - return -1; + // Common to both DW1 & DW2 + if (TinselV2) { + // Note: Although DW2 introduces parameters for start and length, it doesn't use them + pp -= 2; + SetPalette(pp[0], pic->escOn, pic->myEscape); + return -3; + } else { + SetPalette(pp[0], pic->escOn, pic->myEscape); + return -1; + } + + case SETSYSTEMSTRING: + // DW2 only + pp -= 1; // 2 parameters + SetSystemString(pp[0], pp[1]); + return -2; + + case SETSYSTEMVAR: + // DW1 only + pp -= 1; // 2 parameters + SetSystemVar(pp[0], pp[1]); + return -2; case SETTAG: - settag(pp[0]); + // DW1 only + SetTag(coroParam, pp[0]); return -1; case SETTIMER: + // DW1 only pp -= 3; // 4 parameters - settimer(pp[0], pp[1], pp[2], pp[3]); + SetTimer(pp[0], pp[1], pp[2], pp[3]); return -4; + case SHELL: + // DW2 only + Shell(pp[0]); + return 0; + + case SHOWACTOR: + // DW2 only + if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic); + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + } else + ShowActorFn(coroParam, pp[0]); + return -1; + + case SHOWBLOCK: + // DW2 only + ShowBlock(pp[0]); + return -1; + + case SHOWEFFECT: + // DW2 only + ShowEffect(pp[0]); + return -1; + + case SHOWMENU: + // DW2 only + ShowMenu(); + return 0; + + case SHOWPATH: + // DW2 only + ShowPath(pp[0]); + return -1; + case SHOWPOS: + // DW1 only #ifdef DEBUG showpos(); #endif return 0; + case SHOWREFER: + // DW2 only + ShowRefer(pp[0]); + return -1; + case SHOWSTRING: #ifdef DEBUG showstring(); #endif return 0; + case SHOWTAG: + // DW2 only + if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) { + *pResumeState = RES_NOT; + FinishWaiting(coroParam, pic); + + if (coroParam) { + *pResumeState = RES_1; + return 0; + } + } else { + ShowTag(coroParam, pp[0], pic->hPoly); + } + return -1; + case SPLAY: + // DW1 only 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); + if (pic->event == WALKIN || pic->event == WALKOUT) + SPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myEscape); else - splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->actorid, pic->escOn, pic->myescEvent); + SPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->idActor, pic->escOn, pic->myEscape); return -7; case STAND: + // Common to both DW1 & DW2 pp -= 3; // 4 parameters - stand(pp[0], pp[1], pp[2], pp[3]); + Stand(coroParam, pp[0], pp[1], pp[2], pp[3]); return -4; case STANDTAG: - standtag(pp[0], pic->hpoly); + // Common to both DW1 & DW2 + StandTag(pp[0], pic->hPoly); + return -1; + + case STARTGLOBALPROCESS: + // DW2 only + StartGlobalProcess(coroParam, pp[0]); return -1; - case STOP: - stop(pp[0]); + case STARTPROCESS: + // DW2 only + StartProcess(coroParam, pp[0]); return -1; + case STARTTIMER: + // DW2 only + pp -= 3; // 4 parameters + StartTimerFn(pp[0], pp[1], pp[2], pp[3]); + return -4; + case STOPMIDI: - stopmidi(); + // DW1 only + StopMidiFn(); return 0; case STOPSAMPLE: - stopsample(); - return 0; + // Common to both DW1 & DW2 + if (TinselV2) { + StopSample(pp[0]); + return -1; + } else { + StopSample(); + return 0; + } + + case STOPWALK: + // Common to both DW1 & DW2 only + StopWalk(pp[0]); + return -1; case SUBTITLES: - subtitles(pp[0]); + // Common to both DW1 & DW2 + Subtitles(pp[0]); return -1; case SWALK: + // Common to both DW1 & DW2 pp -= 5; // 6 parameters - swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent); + Swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], -1, pic->escOn, pic->myEscape); return -6; + case SWALKZ: + // DW2 only + pp -= 6; // 7 parameters + Swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pic->escOn, pic->myEscape); + return -7; + + case SYSTEMVAR: + // DW2 only + pp[0] = SystemVar(pp[0]); + return 0; + case TAGACTOR: pp -= 2; // 3 parameters - tagactor(pp[0], pp[1], pp[2]); + TagActor(pp[0], pp[1], pp[2]); return -3; + case TAGTAGXPOS: + case TAGTAGYPOS: + case TAGWALKXPOS: + case TAGWALKYPOS: + // DW2 only + pp[0] = TagPos((MASTER_LIB_CODES)libCode, pp[0], pic->hPoly); + return 0; + case TALK: + // Common to both DW1 & DW2 pp -= 1; // 2 parameters - if (pic->event == ENTER || pic->event == LEAVE) - talk(coroParam, pp[0], pp[1], 0, pic->escOn, pic->myescEvent); + if (TinselV2) + TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], 0, false, pic->escOn, pic->myEscape); + else if (pic->event == WALKIN || pic->event == WALKOUT) + TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], 0, false, pic->escOn, pic->myEscape); else - talk(coroParam, pp[0], pp[1], pic->actorid, pic->escOn, pic->myescEvent); + TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], pic->idActor, false, pic->escOn, pic->myEscape); return -2; case TALKAT: - pp -= 3; // 4 parameters - talkat(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); - return -4; + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 4; // 5 parameters + TalkOrSay(coroParam, IS_TALKAT, pp[3], pp[1], pp[2], 0, pp[0], pp[4], pic->escOn, pic->myEscape); + return -5; + } else { + pp -= 3; // 4 parameters + TalkAt(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myEscape); + return -4; + } case TALKATS: + // DW1 only pp -= 4; // 5 parameters - talkats(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myescEvent); + TalkAtS(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myEscape); return -5; case TALKATTR: + // DW1 only pp -= 2; // 3 parameters - talkattr(pp[0], pp[1], pp[2], pic->escOn, pic->myescEvent); + TalkAttr(pp[0], pp[1], pp[2], pic->escOn, pic->myEscape); + return -3; + + case TALKPALETTEINDEX: + // DW1 only + TalkPaletteIndex(pp[0]); + return -1; + + case TALKRGB: + // DW2 only + TalkRGB(pp[0], pic->myEscape); return -3; + case TALKVIA: + // DW2 only + TalkVia(pp[0]); + return -1; + + case TEMPTAGFONT: + // DW2 only + TempTagFont(pp[0]); + return -1; + + case TEMPTALKFONT: + // DW2 only + TempTalkFont(pp[0]); + return -1; + + case THISOBJECT: + // DW2 only + pp[0] = ThisObject(pic->pinvo); + return 0; + + case THISTAG: + // DW2 only + pp[0] = ThisTag(pic->hPoly); + return 0; + case TIMER: - pp[0] = timer(pp[0]); + // Common to both DW1 & DW2 + pp[0] = TimerFn(pp[0]); return 0; - case TOPOFFSET: - pp[0] = ltoffset(TOPOFFSET); + case TOPIC: + // DW2 only + pp[0] = Topic(); 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; + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 3; // 4 parameters + TopPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape, pic->event); + return -4; + } else { + pp -= 5; // 6 parameters + TopPlay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->idActor, false, 0, pic->escOn, pic->myEscape); + return -6; + } case TOPWINDOW: - topwindow(pp[0]); + // Common to both DW1 & DW2 + TopWindow(pp[0]); + return -1; + + case TRANSLUCENTINDEX: + // DW2 only + TranslucentIndex(pp[0]); return -1; case TRYPLAYSAMPLE: + // DW1 only pp -= 1; // 2 parameters - tryplaysample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + TryPlaySample(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape); return -2; + case UNDIMMUSIC: + // DW2 only + UnDimMusic(); + return 0; + case UNHOOKSCENE: - unhookscene(); + // Common to both DW1 & DW2 + UnHookSceneFn(); return 0; case UNTAGACTOR: - untagactor(pp[0]); + // DW1 only + UnTagActorFn(pp[0]); return -1; case VIBRATE: - vibrate(); - return 0; - - case WAITKEY: - waitkey(coroParam, pic->escOn, pic->myescEvent); + // DW1 only + Vibrate(); return 0; case WAITFRAME: + // Common to both DW1 & DW2 pp -= 1; // 2 parameters - waitframe(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + WaitFrame(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape); return -2; + case WAITKEY: + // Common to both DW1 & DW2 + WaitKey(coroParam, pic->escOn, pic->myEscape); + return 0; + + case WAITSCROLL: + // DW2 only + WaitScroll(coroParam, pic->myEscape); + return 0; + case WAITTIME: + // Common to both DW1 & DW2 pp -= 1; // 2 parameters - waittime(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + WaitTime(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape); return -2; case WALK: + // Common to both DW1 & DW2 pp -= 4; // 5 parameters - walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, pic->escOn, pic->myescEvent); + Walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, -1, pic->escOn, pic->myEscape); return -5; case WALKED: { + // Common to both DW1 & DW2 pp -= 3; // 4 parameters bool tmp; - walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent, tmp); + Walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myEscape, tmp); if (!coroParam) { // Only write the result to the stack if walked actually completed running. pp[0] = tmp; @@ -2941,24 +5522,65 @@ int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pi return -3; case WALKINGACTOR: - pp -= 40; // 41 parameters - walkingactor(pp[0], (SCNHANDLE *)&pp[1]); - return -41; + // Common to both DW1 & DW2 + if (TinselV2) { + // DW2 doesn't use a second parameter to WalkingActor + WalkingActor(pp[0]); + return -1; + } else { + 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; + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 1; // 2 parameters + WalkPoly(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape); + return -2; + } else { + pp -= 2; // 3 parameters + WalkPoly(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape); + return -3; + } case WALKTAG: - pp -= 2; // 3 parameters - walktag(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent); - return -3; + // Common to both DW1 & DW2 + if (TinselV2) { + pp -= 1; // 2 parameters + WalkTag(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape); + return -2; + } else { + pp -= 2; // 3 parameters + WalkTag(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape); + return -3; + } + + case WALKXPOS: + // DW2 only + pp[0] = WalkXPos(); + return 0; + + case WALKYPOS: + // DW2 only + pp[0] = WalkYPos(); + return 0; + + case WHICHCD: + // DW2 only + pp[0] = WhichCd(); + return 0; case WHICHINVENTORY: - pp[0] = whichinventory(); + // Common to both DW1 & DW2 + pp[0] = WhichInventory(); return 0; + case ZZZZZZ: + // DW2 only - dummy routine used during debugging + return -1; + default: error("Unsupported library function"); } @@ -2966,5 +5588,4 @@ int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pi error("Can't possibly get here"); } - } // end of namespace Tinsel diff --git a/engines/tinsel/tinlib.h b/engines/tinsel/tinlib.h index 001de70896..06562c541d 100644 --- a/engines/tinsel/tinlib.h +++ b/engines/tinsel/tinlib.h @@ -28,13 +28,40 @@ #define TINSEL_TINLIB_H #include "tinsel/dw.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" namespace Tinsel { +enum EXTREME { + EX_USEXY, EX_BOTTOM, EX_BOTTOMLEFT, + EX_BOTTOMRIGHT, EX_LEFT, EX_RIGHT, + EX_TOP, EX_TOPLEFT, EX_TOPRIGHT +}; + +enum WHICH_VER {VER_GLITTER, VER_COMPILE}; +#define VER_LEN 10 + +// Support functions +void TinGetVersion(WHICH_VER which, char *buffer, int length); + // Library functions in TINLIB.C -void control(int param); -void stand(int actor, int x, int y, SCNHANDLE film); +void ActorBrightness(int actor, int brightness); +void ActorPalette(int actor, int startColour, int length); +void Control(int param); +void HookScene(SCNHANDLE scene, int entrance, int transition); +void NewScene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition); +void Offset(EXTREME extreme, int x, int y); +void RestoreScene(void); +void ResumeLastGame(void); +void SaveScene(CORO_PARAM); +void Stand(CORO_PARAM, int actor, int x, int y, SCNHANDLE film); +void SetTextPal(COLORREF col); + +void KeepOnScreen(OBJECT *pText, int *pTextX, int *pTextY); + +enum SPEECH_TYPE { IS_SAY, IS_SAYAT, IS_TALK, IS_TALKAT }; } // end of namespace Tinsel diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp index fdf38d9409..85e6c2390f 100644 --- a/engines/tinsel/tinsel.cpp +++ b/engines/tinsel/tinsel.cpp @@ -24,6 +24,7 @@ */ #include "common/endian.h" +#include "common/error.h" #include "common/events.h" #include "common/keyboard.h" #include "common/file.h" @@ -44,13 +45,15 @@ #include "tinsel/background.h" #include "tinsel/config.h" #include "tinsel/cursor.h" +#include "tinsel/drives.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/dialogs.h" +#include "tinsel/mareels.h" #include "tinsel/music.h" #include "tinsel/object.h" #include "tinsel/pid.h" @@ -60,6 +63,7 @@ #include "tinsel/serializer.h" #include "tinsel/sound.h" #include "tinsel/strres.h" +#include "tinsel/sysvar.h" #include "tinsel/timers.h" #include "tinsel/tinsel.h" @@ -71,6 +75,14 @@ namespace Tinsel { extern void SetDoFadeIn(bool tf); extern void DropBackground(void); +// In BMV.CPP +extern void FettleBMV(void); +extern bool MoviePlaying(void); +extern void CopyMovieToScreen(void); +extern void FinishBMV(); +extern int32 MovieAudioLag(); +extern uint32 NextMovieTime(); + // In CURSOR.CPP extern void CursorProcess(CORO_PARAM, const void *); @@ -79,7 +91,6 @@ extern void InventoryProcess(CORO_PARAM, const void *); // In SCENE.CPP extern void PrimeBackground(); -extern void NewScene(SCNHANDLE scene, int entry); extern SCNHANDLE GetSceneHandle(void); // In TIMER.CPP @@ -94,6 +105,12 @@ void SetNewScene(SCNHANDLE scene, int entrance, int transition); bool bRestart = false; bool bHasRestarted = false; +static bool bCuttingScene = false; + +static bool bChangingForRestore = false; + +static Common::Point clickPos; + #ifdef DEBUG bool bFast; // set to make it go ludicrously fast #endif @@ -110,11 +127,11 @@ static Scene NextScene = { 0, 0, 0 }; static Scene HookScene = { 0, 0, 0 }; static Scene DelayedScene = { 0, 0, 0 }; -static bool bHookSuspend = false; - static PROCESS *pMouseProcess = 0; static PROCESS *pKeyboardProcess = 0; +static SCNHANDLE hCdChangeScene; + // Stack of pending mouse button events Common::List mouseButtons; @@ -142,6 +159,7 @@ void KeyboardProcess(CORO_PARAM, const void *) { // Get the next keyboard event off the stack Common::Event evt = *keypresses.begin(); keypresses.erase(keypresses.begin()); + const Common::Point mousePos = _vm->getMousePosition(); // Switch for special keys switch (evt.kbd.keycode) { @@ -150,21 +168,21 @@ void KeyboardProcess(CORO_PARAM, const void *) { case Common::KEYCODE_RALT: if (evt.type == Common::EVENT_KEYDOWN) { if (!bSwapButtons) - ProcessButEvent(BE_RDSTART); + ProcessButEvent(PLR_DRAG2_START); else - ProcessButEvent(BE_LDSTART); + ProcessButEvent(PLR_DRAG1_START); } else { if (!bSwapButtons) - ProcessButEvent(BE_LDEND); + ProcessButEvent(PLR_DRAG1_END); else - ProcessButEvent(BE_RDEND); + ProcessButEvent(PLR_DRAG2_END); } continue; case Common::KEYCODE_LCTRL: case Common::KEYCODE_RCTRL: if (evt.type == Common::EVENT_KEYDOWN) { - ProcessKeyEvent(LOOK_KEY); + ProcessKeyEvent(PLR_LOOK); } else { // Control key release } @@ -186,42 +204,42 @@ void KeyboardProcess(CORO_PARAM, const void *) { switch (evt.kbd.keycode) { /*** SPACE = WALKTO ***/ case Common::KEYCODE_SPACE: - ProcessKeyEvent(WALKTO_KEY); + ProcessKeyEvent(PLR_WALKTO); continue; /*** RETURN = ACTION ***/ case Common::KEYCODE_RETURN: case Common::KEYCODE_KP_ENTER: - ProcessKeyEvent(ACTION_KEY); + ProcessKeyEvent(PLR_ACTION); continue; /*** l = LOOK ***/ case Common::KEYCODE_l: // LOOK - ProcessKeyEvent(LOOK_KEY); + ProcessKeyEvent(PLR_LOOK); 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 - { + if (!TinselV2) { + // WORKAROUND: For Discworld 1, 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 0 // FIXME: Disabled this code for now, as it doesn't work as it should (see bug #2078922). if ((g_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 -#endif - if ((sceneNumber >= 13) && (sceneNumber <= 15) || (sceneNumber == 11)) { + } 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); + ProcessKeyEvent(PLR_ESCAPE); } + } else { + // Running Discworld 2, so process the key normally + ProcessKeyEvent(PLR_ESCAPE); } continue; @@ -236,48 +254,79 @@ void KeyboardProcess(CORO_PARAM, const void *) { case Common::KEYCODE_F1: // Options dialog - ProcessKeyEvent(OPTION_KEY); + ProcessKeyEvent(PLR_MENU); continue; case Common::KEYCODE_F5: // Save game - ProcessKeyEvent(SAVE_KEY); + ProcessKeyEvent(PLR_SAVE); continue; case Common::KEYCODE_F7: // Load game - ProcessKeyEvent(LOAD_KEY); + ProcessKeyEvent(PLR_LOAD); continue; + case Common::KEYCODE_m: + // Debug facility - scene hopper + if (TinselV2 && (evt.kbd.flags == Common::KBD_ALT)) + ProcessKeyEvent(PLR_JUMP); + break; case Common::KEYCODE_q: if ((evt.kbd.flags == Common::KBD_CTRL) || (evt.kbd.flags == Common::KBD_ALT)) - ProcessKeyEvent(QUIT_KEY); + ProcessKeyEvent(PLR_QUIT); continue; case Common::KEYCODE_PAGEUP: case Common::KEYCODE_KP9: - ProcessKeyEvent(PGUP_KEY); + ProcessKeyEvent(PLR_PGUP); continue; case Common::KEYCODE_PAGEDOWN: case Common::KEYCODE_KP3: - ProcessKeyEvent(PGDN_KEY); + ProcessKeyEvent(PLR_PGDN); continue; case Common::KEYCODE_HOME: case Common::KEYCODE_KP7: - ProcessKeyEvent(HOME_KEY); + ProcessKeyEvent(PLR_HOME); continue; case Common::KEYCODE_END: case Common::KEYCODE_KP1: - ProcessKeyEvent(END_KEY); + ProcessKeyEvent(PLR_END); continue; default: - ProcessKeyEvent(NOEVENT_KEY); + ProcessKeyEvent(PLR_NOEVENT); break; } } CORO_END_CODE; } +/** + * Handles launching a single click action result if the timeout for a double-click + * expires + */ +static void SingleLeftProcess(CORO_PARAM, const void *) { + CORO_BEGIN_CONTEXT; + uint32 endTicks; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Work out when to wait until + _ctx->endTicks = DwGetCurrentTime() + (uint32)dclickSpeed; + + // Timeout a double click (may not work once every 49 days!) + do { + CORO_SLEEP(1); + } while(DwGetCurrentTime() < _ctx->endTicks); + + if (GetProvNotProcessed()) + PlayerEvent(PLR_WALKTO, clickPos); + + CORO_KILL_SELF(); + CORO_END_CODE; +} + /** * Process to handle changes in the mouse buttons. */ -void MouseProcess(CORO_PARAM, const void *) { +static void MouseProcess(CORO_PARAM, const void *) { // COROUTINE CORO_BEGIN_CONTEXT; bool lastLWasDouble; @@ -292,9 +341,6 @@ void MouseProcess(CORO_PARAM, const void *) { _ctx->lastLeftClick = _ctx->lastRightClick = DwGetCurrentTime(); while (true) { - // FIXME: I'm still keeping the ctrl/Alt handling in the ProcessKeyEvent method. - // Need to make sure that this works correctly - //DragKeys(); if (mouseButtons.empty()) { // allow scheduling @@ -306,23 +352,46 @@ void MouseProcess(CORO_PARAM, const void *) { Common::EventType type = *mouseButtons.begin(); mouseButtons.erase(mouseButtons.begin()); + int xp, yp; + GetCursorXYNoWait(&xp, &yp, true); + const Common::Point mousePos(xp, yp); + switch (type) { case Common::EVENT_LBUTTONDOWN: // left button press if (DwGetCurrentTime() - _ctx->lastLeftClick < (uint32)dclickSpeed) { - // signal left drag start - ProcessButEvent(BE_LDSTART); + // Left button double-click + + if (TinselV2) { + // Kill off the button process and fire off the action command + g_scheduler->killMatchingProcess(PID_BTN_CLICK, -1); + PlayerEvent(PLR_ACTION, clickPos); + } else { + // signal left drag start + ProcessButEvent(PLR_DRAG1_START); - // signal left double click event - ProcessButEvent(BE_DLEFT); + // signal left double click event + ProcessButEvent(PLR_DLEFT); + } _ctx->lastLWasDouble = true; } else { - // signal left drag start - ProcessButEvent(BE_LDSTART); + // Initial mouse down - either for a single click, or potentially + // the start of a double-click action + + if (TinselV2) { + PlayerEvent(PLR_DRAG1_START, mousePos); + + ProvNotProcessed(); + PlayerEvent(PLR_PROV_WALKTO, mousePos); + + } else { + // signal left drag start + ProcessButEvent(PLR_DRAG1_START); - // signal left single click event - ProcessButEvent(BE_SLEFT); + // signal left single click event + ProcessButEvent(PLR_SLEFT); + } _ctx->lastLWasDouble = false; } @@ -332,32 +401,53 @@ void MouseProcess(CORO_PARAM, const void *) { // left button release // update click timer - if (_ctx->lastLWasDouble == false) + if (_ctx->lastLWasDouble == false) { _ctx->lastLeftClick = DwGetCurrentTime(); - else + + // If player control is enabled, start a process which, if it times out, + // will activate a single button click + if (TinselV2 && ControlIsOn()) { + clickPos = mousePos; + g_scheduler->createProcess(PID_BTN_CLICK, SingleLeftProcess, NULL, 0); + } + } else _ctx->lastLeftClick -= dclickSpeed; - // signal left drag end - ProcessButEvent(BE_LDEND); + if (TinselV2) + // Signal left drag end + PlayerEvent(PLR_DRAG1_END, mousePos); + else + // signal left drag end + ProcessButEvent(PLR_DRAG1_END); break; case Common::EVENT_RBUTTONDOWN: // right button press if (DwGetCurrentTime() - _ctx->lastRightClick < (uint32)dclickSpeed) { - // signal right drag start - ProcessButEvent(BE_RDSTART); + // Right button double-click + if (TinselV2) { + PlayerEvent(PLR_NOEVENT, clickPos); + } else { + // signal right drag start + ProcessButEvent(PLR_DRAG2_START); - // signal right double click event - ProcessButEvent(BE_DRIGHT); + // signal right double click event + ProcessButEvent(PLR_DRIGHT); + } _ctx->lastRWasDouble = true; } else { - // signal right drag start - ProcessButEvent(BE_RDSTART); + if (TinselV2) { + PlayerEvent(PLR_DRAG2_START, mousePos); + PlayerEvent(PLR_LOOK, mousePos); + } else { + // signal right drag start + ProcessButEvent(PLR_DRAG2_START); - // signal right single click event - ProcessButEvent(BE_SRIGHT); + // signal right single click event + ProcessButEvent(PLR_SRIGHT); + } _ctx->lastRWasDouble = false; } @@ -372,8 +462,12 @@ void MouseProcess(CORO_PARAM, const void *) { else _ctx->lastRightClick -= dclickSpeed; - // signal right drag end - ProcessButEvent(BE_RDEND); + if (TinselV2) + // Signal left drag end + PlayerEvent(PLR_DRAG2_END, mousePos); + else + // signal right drag end + ProcessButEvent(PLR_DRAG2_END); break; default: @@ -402,9 +496,24 @@ static void MasterScriptProcess(CORO_PARAM, const void *) { /** * Store the facts pertaining to a scene change. */ - void SetNewScene(SCNHANDLE scene, int entrance, int transition) { - if (HookScene.scene == 0 || bHookSuspend) { + if (!bCuttingScene && TinselV2) + WrapScene(); + + // If CD change will be required, stick in the scene change scene + if (CdNumber(scene) != GetCurrentCD()) { + // This scene gets delayed + DelayedScene.scene = scene; + DelayedScene.entry = entrance; + DelayedScene.trans = transition; + + NextScene.scene = hCdChangeScene; + NextScene.entry = CdNumber(scene) - '0'; + NextScene.trans = TRANS_FADE; + return; + } + + if (HookScene.scene == 0 || bCuttingScene) { // This scene comes next NextScene.scene = scene; NextScene.entry = entrance; @@ -424,6 +533,9 @@ void SetNewScene(SCNHANDLE scene, int entrance, int transition) { } } +/** + * Store a scene as hooked + */ void SetHookScene(SCNHANDLE scene, int entrance, int transition) { assert(HookScene.scene == 0); // scene already hooked @@ -432,6 +544,9 @@ void SetHookScene(SCNHANDLE scene, int entrance, int transition) { HookScene.trans = transition; } +/** + * Hooked scene is over, trigger a change to the delayed scene + */ void UnHookScene(void) { assert(DelayedScene.scene != 0); // no scene delayed @@ -444,11 +559,40 @@ void UnHookScene(void) { } void SuspendHook(void) { - bHookSuspend = true; + bCuttingScene = true; +} + +void CdHasChanged(void) { + if (bChangingForRestore) { + bChangingForRestore = false; + RestoreGame(-2); + } else { + assert(DelayedScene.scene != 0); + + WrapScene(); + + // The delayed scene can go now + NextScene.scene = DelayedScene.scene; + NextScene.entry = DelayedScene.entry; + NextScene.trans = DelayedScene.trans; + + DelayedScene.scene = 0; + } +} + +void SetCdChangeScene(SCNHANDLE hScene) { + hCdChangeScene = hScene; +} + +void CDChangeForRestore(int cdNumber) { + NextScene.scene = hCdChangeScene; + NextScene.entry = cdNumber; + NextScene.trans = TRANS_FADE; + bChangingForRestore = true; } void UnSuspendHook(void) { - bHookSuspend = false; + bCuttingScene = false; } void syncSCdata(Serializer &s) { @@ -468,16 +612,23 @@ static void RestoredProcess(CORO_PARAM, const void *param) { // COROUTINE CORO_BEGIN_CONTEXT; INT_CONTEXT *pic; + bool bConverse; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); // get the stuff copied to process when it was created - _ctx->pic = *((INT_CONTEXT **)param); + _ctx->pic = *((INT_CONTEXT * const *)param); _ctx->pic = RestoreInterpretContext(_ctx->pic); + _ctx->bConverse = TinselV2 && (_ctx->pic->event == CONVERSE); + CORO_INVOKE_1(Interpret, _ctx->pic); + // Restore control after CallScene() from a conversation icon + if (_ctx->bConverse) + ControlOn(); + CORO_END_CODE; } @@ -500,10 +651,17 @@ static int CountOut = 1; // == 1 for immediate start of first scene * When the count expires, the screen will have faded. Ensure the scene | * is loaded, clear the screen, and start the new scene. */ -void ChangeScene() { +bool ChangeScene(bool bReset) { + + // Prevent attempt to fade-out when restarting game + if (bReset) { + CountOut = 1; // immediate start of first scene again + DelayedScene.scene = HookScene.scene = 0; + return false; + } if (IsRestoringScene()) - return; + return true; if (NextScene.scene != 0) { if (!CountOut) { @@ -517,12 +675,15 @@ void ChangeScene() { // Trigger pre-load and fade and start countdown CountOut = COUNTOUT_COUNT; FadeOutFast(NULL); + if (TinselV2) + _vm->_pcmMusic->startFadeOut(COUNTOUT_COUNT); break; } } else if (--CountOut == 0) { - ClearScreen(); + if (!TinselV2) + ClearScreen(); - NewScene(NextScene.scene, NextScene.entry); + StartNewScene(NextScene.scene, NextScene.entry); NextScene.scene = 0; switch (NextScene.trans) { @@ -535,20 +696,32 @@ void ChangeScene() { SetDoFadeIn(true); break; } - } + } else + _vm->_pcmMusic->fadeOutIteration(); } + + return false; } /** - * LoadBasicChunks + * CuttingScene */ +void CuttingScene(bool bCutting) { + bCuttingScene = bCutting; + + if (!bCutting) + WrapScene(); +} +/** + * LoadBasicChunks + */ void LoadBasicChunks(void) { byte *cptr; int numObjects; // Allocate RAM for savescene data - InitialiseSs(); + InitialiseSaveScenes(); // 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 @@ -581,6 +754,23 @@ void LoadBasicChunks(void) { cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY); if (cptr != NULL) MaxPolygons(*cptr); + + if (TinselV2) { + // Global processes + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_NUM_PROCESSES); + assert(cptr && (*cptr < 100)); + int num = *cptr; + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_PROCESSES); + assert(!num || cptr); + GlobalProcesses(num, cptr); + + // CdPlay() stuff + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_CDPLAY_HANDLE); + assert(cptr); + uint32 playHandle = READ_LE_UINT32(cptr); + assert(playHandle < 512); + SetCdPlayHandle(playHandle); + } } //----------------- TinselEngine -------------------- @@ -602,10 +792,39 @@ static const GameSettings tinselSettings[] = { {NULL, NULL, 0, 0, NULL} }; +const char *TinselEngine::_sampleIndices[][3] = { + { "english.idx", "english1.idx", "english2.idx" }, + { "french.idx", "french1.idx", "french2.idx" }, + { "german.idx", "german1.idx", "german2.idx" }, + { "italian.idx", "italian1.idx", "italian2.idx" }, + { "spanish.idx", "spanish1.idx", "spanish2.idx" }, +}; +const char *TinselEngine::_sampleFiles[][3] = { + { "english.smp", "english1.smp", "english2.smp" }, + { "french.smp", "french1.smp", "french2.smp" }, + { "german.smp", "german1.smp", "german2.smp" }, + { "italian.smp", "italian1.smp", "italian2.smp" }, + { "spanish.smp", "spanish1.smp", "spanish2.smp" }, +}; +const char *TinselEngine::_textFiles[][3] = { + { "english.txt", "english1.txt", "english2.txt" }, + { "french.txt", "french1.txt", "french2.txt" }, + { "german.txt", "german1.txt", "german2.txt" }, + { "italian.txt", "italian1.txt", "italian2.txt" }, + { "spanish.txt", "spanish1.txt", "spanish2.txt" } +}; + + TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) { _vm = this; + // Register debug flags + Common::addSpecialDebugLevel(kTinselDebugAnimations, "animations", "Animations debugging"); + Common::addSpecialDebugLevel(kTinselDebugActions, "actions", "Actions debugging"); + Common::addSpecialDebugLevel(kTinselDebugSound, "sound", "Sound debugging"); + Common::addSpecialDebugLevel(kTinselDebugMusic, "music", "Music debugging"); + // Setup mixer _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); @@ -629,9 +848,10 @@ TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) if (native_mt32) _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); - _music = new MusicPlayer(_driver); - //_music->setNativeMT32(native_mt32); - //_music->setAdlib(adlib); + _midiMusic = new MidiMusicPlayer(_driver); + _pcmMusic = new PCMMusicPlayer(); + //_midiMusic->setNativeMT32(native_mt32); + //_midiMusic->setAdlib(adlib); _musicVolume = ConfMan.getInt("music_volume"); @@ -644,25 +864,38 @@ TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) } TinselEngine::~TinselEngine() { + if (MoviePlaying()) + FinishBMV(); + delete _sound; - delete _music; + delete _midiMusic; + delete _pcmMusic; delete _console; delete _driver; _screenSurface.free(); - FreeSs(); + FreeSaveScenes(); FreeTextBuffer(); FreeHandleTable(); FreeActors(); FreeObjectList(); + FreeGlobalProcesses(); FreeGlobals(); delete _scheduler; } Common::Error TinselEngine::init() { // Initialize backend - initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT, false); - - _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, 1); + if (getGameID() == GID_DW2) { +#ifndef DW2_EXACT_SIZE + initGraphics(640, 480, true); +#else + initGraphics(640, 432, true); +#endif + _screenSurface.create(640, 432, 1); + } else { + initGraphics(320, 200, false); + _screenSurface.create(320, 200, 1); + } g_system->getEventManager()->registerRandomSource(_random, "tinsel"); @@ -670,6 +903,8 @@ Common::Error TinselEngine::init() { _scheduler = new Scheduler(); + InitSysVars(); + // init memory manager MemoryInit(); @@ -684,20 +919,19 @@ Common::Error TinselEngine::init() { RebootCursor(); RebootDeadTags(); RebootMovers(); + resetUserEventTime(); RebootTimers(); RebootScalingReels(); DelayedScene.scene = HookScene.scene = 0; #endif + // Load in text strings + ChangeLanguage(g_language); + // Init palette and object managers, scheduler, keyboard and mouse RestartDrivers(); - // TODO: More stuff from dos_main.c may have to be added here - - // load in text strings - ChangeLanguage(g_language); - // load in graphics info SetupHandleTable(); @@ -707,14 +941,23 @@ Common::Error TinselEngine::init() { return Common::kNoError; } +void TinselEngine::syncSoundSettings() { + // Sync the engine with the config manager + int soundVolumeMusic = ConfMan.getInt("music_volume"); + int soundVolumeSFX = ConfMan.getInt("sfx_volume"); + int soundVolumeSpeech = ConfMan.getInt("speech_volume"); + + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic); + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundVolumeSFX); + _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, soundVolumeSpeech); +} + 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) - Common::Error TinselEngine::go() { uint32 timerVal = 0; @@ -758,6 +1001,9 @@ Common::Error TinselEngine::go() { // Save/Restore scene file transfers ProcessSRQueue(); + // Handle any playing movie + FettleBMV(); + #ifdef DEBUG if (bFast) continue; // run flat-out @@ -765,7 +1011,12 @@ Common::Error TinselEngine::go() { // Loop processing events while there are any pending while (pollEvent()); - g_system->delayMillis(10); + DoCdChange(); + + if (MoviePlaying() && NextMovieTime()) + g_system->delayMillis(MAX(NextMovieTime() - g_system->getMillis() + MovieAudioLag(), 0)); + else + g_system->delayMillis(10); } // Write configuration @@ -776,8 +1027,11 @@ Common::Error TinselEngine::go() { void TinselEngine::NextGameCycle(void) { - // - ChangeScene(); + // Dim Music + _pcmMusic->dimIteration(); + + // Check for scene change + ChangeScene(false); // Allow a user event for this schedule ResetEcount(); @@ -785,8 +1039,11 @@ void TinselEngine::NextGameCycle(void) { // schedule process _scheduler->schedule(); - // redraw background - DrawBackgnd(); + if (MoviePlaying()) + CopyMovieToScreen(); + else + // redraw background + DrawBackgnd(); // Why waste resources on yet another process? FettleTimers(); @@ -810,7 +1067,13 @@ bool TinselEngine::pollEvent() { break; case Common::EVENT_MOUSEMOVE: - _mousePos = event.mouse; + { + // This fragment takes care of Tinsel 2 when it's been compiled with + // blank areas at the top and bottom of thes creen + int ySize = (g_system->getHeight() - _vm->screen().h) / 2; + if ((event.mouse.y >= ySize) && (event.mouse.y < (g_system->getHeight() - ySize))) + _mousePos = Common::Point(event.mouse.x, event.mouse.y - ySize); + } break; case Common::EVENT_KEYDOWN: @@ -828,7 +1091,6 @@ bool TinselEngine::pollEvent() { /** * Start the processes that continue between scenes. */ - void TinselEngine::CreateConstProcesses(void) { // Process to run the master script _scheduler->createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0); @@ -841,7 +1103,6 @@ void TinselEngine::CreateConstProcesses(void) { /** * Restart the game */ - void TinselEngine::RestartGame(void) { HoldItem(INV_NOICON); // Holding nothing @@ -878,7 +1139,6 @@ void TinselEngine::RestartGame(void) { /** * Init palette and object managers, scheduler, keyboard and mouse. */ - void TinselEngine::RestartDrivers(void) { // init the palette manager ResetPalAllocator(); @@ -902,13 +1162,12 @@ void TinselEngine::RestartDrivers(void) { } // Set midi volume - SetMidiVolume(volMidi); + SetMidiVolume(volMusic); } /** * Remove keyboard, mouse and joystick drivers. */ - void TinselEngine::ChopDrivers(void) { // remove sound driver StopMidi(); @@ -923,7 +1182,6 @@ void TinselEngine::ChopDrivers(void) { /** * Process a keyboard event */ - void TinselEngine::ProcessKeyEvent(const Common::Event &event) { // Handle any special keys immediately @@ -974,4 +1232,48 @@ void TinselEngine::ProcessKeyEvent(const Common::Event &event) { keypresses.push_back(event); } +const char *TinselEngine::getSampleIndex(LANGUAGE lang) { + int cd; + + if (TinselV2) { + cd = GetCurrentCD(); + assert((cd == 1) || (cd == 2)); + assert(((unsigned int) lang) < 5); + } else { + cd = 0; + lang = TXT_ENGLISH; + } + + return _sampleIndices[lang][cd]; +} + +const char *TinselEngine::getSampleFile(LANGUAGE lang) { + int cd; + + if (TinselV2) { + cd = GetCurrentCD(); + assert((cd == 1) || (cd == 2)); + assert(((unsigned int) lang) < 5); + } else { + cd = 0; + lang = TXT_ENGLISH; + } + + return _sampleFiles[lang][cd]; +} + +const char *TinselEngine::getTextFile(LANGUAGE lang) { + assert(((unsigned int) lang) < 5); + + int cd; + + if (TinselV2) { + cd = GetCurrentCD(); + assert((cd == 1) || (cd == 2)); + } else + cd = 0; + + return _textFiles[lang][cd]; +} + } // End of namespace Tinsel diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h index 080e539f69..370d500abc 100644 --- a/engines/tinsel/tinsel.h +++ b/engines/tinsel/tinsel.h @@ -28,6 +28,7 @@ #include "common/scummsys.h" #include "common/system.h" +#include "common/error.h" #include "common/events.h" #include "common/keyboard.h" #include "common/util.h" @@ -39,10 +40,12 @@ #include "tinsel/debugger.h" #include "tinsel/graphics.h" #include "tinsel/sound.h" +#include "tinsel/dw.h" namespace Tinsel { -class MusicPlayer; +class MidiMusicPlayer; +class PCMMusicPlayer; class Scheduler; class SoundManager; @@ -65,11 +68,34 @@ enum TinselGameFeatures { GF_USE_5FLAGS = 1 << 6 // All 5 flags }; +/** + * The following is the ScummVM definitions of the various Tinsel versions: + * TINSEL_V0 - This was an early engine version that was only used in the Discworld 1 + * demo. It is not currently supported. + * TINSEL_V1 - This was the engine version used by Discworld 1. Note that there were two + * major releases: an earlier version that used *.gra files, and a later one that + * used *.scn files, and contained certain script and engine bugfixes. In ScummVM, + * we treat both releases as 'Tinsel 1', since the engine fixes from the later + * version work equally well the earlier version data. + * TINSEL_V2 - This is the engine used for the Discworld 2 game. + */ enum TinselEngineVersion { - TINSEL_V0 = 0, // Used in the DW1 demo only - TINSEL_V1 = 1 + TINSEL_V0 = 0, + TINSEL_V1 = 1, + TINSEL_V2 = 2 +}; + +enum { + kTinselDebugAnimations = 1 << 0, + kTinselDebugActions = 1 << 1, + kTinselDebugSound = 1 << 2, + kTinselDebugMusic = 2 << 3 }; +#define DEBUG_BASIC 1 +#define DEBUG_INTERMEDIATE 2 +#define DEBUG_DETAILED 3 + struct TinselGameDescription; enum TinselKeyDirection { @@ -79,6 +105,22 @@ enum TinselKeyDirection { typedef bool (*KEYFPTR)(const Common::KeyState &); +#define SCREEN_WIDTH (_vm->screen().w) // PC screen dimensions +#define SCREEN_HEIGHT (_vm->screen().h) +#define SCRN_CENTRE_X ((SCREEN_WIDTH - 1) / 2) // screen centre x +#define SCRN_CENTRE_Y ((SCREEN_HEIGHT - 1) / 2) // screen centre y +#define UNUSED_LINES 48 +#define EXTRA_UNUSED_LINES 3 +//#define SCREEN_BOX_HEIGHT1 (SCREEN_HEIGHT - UNUSED_LINES) +//#define SCREEN_BOX_HEIGHT2 (SCREEN_BOX_HEIGHT1 - EXTRA_UNUSED_LINES) +#define SCREEN_BOX_HEIGHT1 SCREEN_HEIGHT +#define SCREEN_BOX_HEIGHT2 SCREEN_HEIGHT + +#define GAME_FRAME_DELAY (1000 / ONE_SECOND) + +#define TinselVersion (_vm->getVersion()) +#define TinselV2 (TinselVersion == TINSEL_V2) + class TinselEngine : public Engine { int _gameId; Common::KeyState _keyPressed; @@ -89,11 +131,16 @@ class TinselEngine : public Engine { Console *_console; Scheduler *_scheduler; + static const char *_sampleIndices[][3]; + static const char *_sampleFiles[][3]; + static const char *_textFiles[][3]; + protected: // Engine APIs virtual Common::Error init(); virtual Common::Error go(); + virtual void syncSoundSettings(); public: TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc); @@ -109,13 +156,18 @@ public: uint16 getVersion() const; Common::Platform getPlatform() const; + const char *getSampleIndex(LANGUAGE lang); + const char *getSampleFile(LANGUAGE lang); + const char *getTextFile(LANGUAGE lang); + MidiDriver *_driver; SoundManager *_sound; - MusicPlayer *_music; + MidiMusicPlayer *_midiMusic; + PCMMusicPlayer *_pcmMusic; KEYFPTR _keyHandler; private: - //MusicPlayer *_music; + //MidiMusicPlayer *_midiMusic; int _musicVolume; void NextGameCycle(void); @@ -134,7 +186,8 @@ public: Common::Point getMousePosition() const { return _mousePos; } void setMousePosition(const Common::Point &pt) { - g_system->warpMouse(pt.x, pt.y); + int ySize = (g_system->getHeight() - _screenSurface.h) / 2; + g_system->warpMouse(pt.x, pt.y + ySize); _mousePos = pt; } void divertKeyInput(KEYFPTR fptr) { _keyHandler = fptr; } @@ -145,6 +198,11 @@ public: // Global reference to the TinselEngine object extern TinselEngine *_vm; +// Externally available methods +void CuttingScene(bool bCutting); +void CDChangeForRestore(int cdNumber); +void CdHasChanged(void); + } // End of namespace Tinsel #endif /* TINSEL_H */ -- cgit v1.2.3