mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
Renamed rince.* files to movers to be more game independent. Added elementary support for Noir movers which can use different logic. Allows game to boot to the first interactive scene, but there is no 3D model rendered (that is WIP).
1710 lines
42 KiB
C++
1710 lines
42 KiB
C++
/* 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.
|
|
*
|
|
* Handles things to do with actors, delegates much moving actor stuff.
|
|
*/
|
|
|
|
#include "tinsel/actors.h"
|
|
#include "tinsel/background.h"
|
|
#include "tinsel/events.h"
|
|
#include "tinsel/film.h" // for FREEL
|
|
#include "tinsel/handle.h"
|
|
#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/movers.h"
|
|
#include "tinsel/sched.h"
|
|
#include "common/serializer.h"
|
|
#include "tinsel/sysvar.h"
|
|
#include "tinsel/tinsel.h"
|
|
#include "tinsel/token.h"
|
|
|
|
#include "common/textconsole.h"
|
|
#include "common/util.h"
|
|
|
|
namespace Tinsel {
|
|
|
|
#include "common/pack-start.h" // START STRUCT PACKING
|
|
|
|
/** actor struct - one per actor */
|
|
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
|
|
|
|
#define RANGE_CHECK(num) assert(num > 0 && num <= _numActors);
|
|
|
|
struct ACTORINFO {
|
|
bool bAlive; // TRUE == alive
|
|
bool bHidden; // TRUE == hidden
|
|
bool completed; // TRUE == script played out
|
|
|
|
int x, y, z;
|
|
|
|
int32 mtype; // DEFAULT(b'ground), MASK, ALWAYS
|
|
SCNHANDLE actorCode; // The actor's script
|
|
|
|
const FREEL *presReel; // the present reel
|
|
int presRnum; // the present reel number
|
|
SCNHANDLE presFilm; // the film that reel belongs to
|
|
OBJECT *presObj; // reference for position information
|
|
int presPlayX, presPlayY;
|
|
|
|
bool tagged; // actor tagged?
|
|
SCNHANDLE hTag; // handle to tag text
|
|
int tType; // e.g. TAG_Q1TO3
|
|
|
|
bool bEscOn;
|
|
int escEvent;
|
|
|
|
COLORREF textColor; // Text color
|
|
|
|
SCNHANDLE playFilm; // revert to this after talks
|
|
SCNHANDLE talkFilm; // this be deleted in the future!
|
|
SCNHANDLE latestFilm; // the last film ordered
|
|
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 RATP_INIT {
|
|
INT_CONTEXT *pic;
|
|
int id; // Actor number
|
|
};
|
|
|
|
//-------------------- METHOD LIST -----------------------
|
|
|
|
Actor::Actor() : _actorInfo(nullptr), _defaultColor(0), _actorsOn(false), ti(0), _numTaggedActors(0), _zFactors(nullptr), _leadActorId(0), _numActors(0) {
|
|
}
|
|
|
|
Actor::~Actor() {
|
|
free(_actorInfo);
|
|
_actorInfo = nullptr;
|
|
if (TinselV2) {
|
|
free(_zFactors);
|
|
_zFactors = nullptr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called once at start-up time, and again at restart time.
|
|
* Registers the total number of actors in the game.
|
|
* @param num Chunk Id
|
|
*/
|
|
void Actor::RegisterActors(int num) {
|
|
if (_actorInfo == NULL) {
|
|
// Store the total number of actors in the game
|
|
_numActors = num;
|
|
|
|
// Check we can save so many
|
|
assert(_numActors <= MAX_SAVED_ALIVES);
|
|
|
|
// Allocate RAM for actor structures
|
|
|
|
// FIXME: For now, we always allocate MAX_SAVED_ALIVES blocks,
|
|
// as this makes the save/load code simpler
|
|
// size of ACTORINFO is 148, so this allocates 512 * 148 = 75776 bytes, about 74KB
|
|
_actorInfo = (ACTORINFO *)calloc(MAX_SAVED_ALIVES, sizeof(ACTORINFO));
|
|
if (TinselV2)
|
|
_zFactors = (uint8 *)malloc(MAX_SAVED_ALIVES);
|
|
|
|
// make sure memory allocated
|
|
if (_actorInfo == NULL) {
|
|
error("Cannot allocate memory for actors");
|
|
}
|
|
} else {
|
|
// Check the total number of actors is still the same
|
|
assert(num == _numActors);
|
|
|
|
memset(_actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO));
|
|
if (TinselV2)
|
|
memset(_zFactors, 0, MAX_SAVED_ALIVES);
|
|
}
|
|
|
|
// All actors start off alive.
|
|
while (num--)
|
|
_actorInfo[num].bAlive = true;
|
|
}
|
|
|
|
/**
|
|
* Called from dec_lead(), i.e. normally once at start of master script.
|
|
* @param leadID Lead Id
|
|
*/
|
|
void Actor::SetLeadId(int leadID) {
|
|
_leadActorId = leadID;
|
|
_actorInfo[leadID-1].mtype = ACT_MASK;
|
|
}
|
|
|
|
/**
|
|
* No comment.
|
|
*/
|
|
int Actor::GetLeadId() {
|
|
return _leadActorId;
|
|
}
|
|
|
|
bool Actor::ActorIsGhost(int actor) {
|
|
return actor == SysVar(ISV_GHOST_ACTOR);
|
|
}
|
|
|
|
struct ATP_INIT {
|
|
int id; // Actor number
|
|
TINSEL_EVENT event; // Event
|
|
PLR_EVENT bev; // Causal mouse event
|
|
|
|
PINT_CONTEXT pic;
|
|
};
|
|
|
|
/**
|
|
* Convert actor id to index into TaggedActors[]
|
|
*/
|
|
int Actor::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\"");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* 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 Actor::StartActor(const T1_ACTOR_STRUC *as, bool bRunScript) {
|
|
SCNHANDLE hActorId = FROM_32(as->hActorId);
|
|
|
|
// Zero-out many things
|
|
_actorInfo[hActorId - 1].bHidden = false;
|
|
_actorInfo[hActorId - 1].completed = false;
|
|
_actorInfo[hActorId - 1].x = 0;
|
|
_actorInfo[hActorId - 1].y = 0;
|
|
_actorInfo[hActorId - 1].presReel = nullptr;
|
|
_actorInfo[hActorId - 1].presFilm = 0;
|
|
_actorInfo[hActorId - 1].presObj = nullptr;
|
|
|
|
// Store current scene's parameters for this actor
|
|
_actorInfo[hActorId - 1].mtype = FROM_32(as->masking);
|
|
_actorInfo[hActorId - 1].actorCode = FROM_32(as->hActorCode);
|
|
|
|
// Run actor's script for this scene
|
|
if (bRunScript) {
|
|
if (_actorsOn)
|
|
_actorInfo[hActorId - 1].bAlive = true;
|
|
|
|
if (_actorInfo[hActorId - 1].bAlive && FROM_32(as->hActorCode))
|
|
ActorEvent(hActorId, STARTUP, PLR_NOEVENT);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called at the start of each scene. Start each actor with a code block.
|
|
* @param ah Scene handle
|
|
* @param numActors Number of actors
|
|
* @param bRunScript Flag for whether to run actor scene scripts
|
|
*/
|
|
void Actor::StartTaggedActors(SCNHANDLE ah, int numActors, bool bRunScript) {
|
|
int i;
|
|
|
|
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-initialized, so...
|
|
for (i = 0; i < _numActors; i++) {
|
|
_actorInfo[i].x = _actorInfo[i].y = 0;
|
|
_actorInfo[i].mtype = 0;
|
|
}
|
|
}
|
|
|
|
if (!TinselV2) {
|
|
// Tinsel 1 load variation
|
|
const T1_ACTOR_STRUC *as = (const T1_ACTOR_STRUC *)_vm->_handle->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 *)_vm->_handle->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_32(as->hActorId);
|
|
_taggedActors[i].hTagText = FROM_32(as->hTagText);
|
|
_taggedActors[i].tagPortionV = FROM_32(as->tagPortionV);
|
|
_taggedActors[i].tagPortionH = FROM_32(as->tagPortionH);
|
|
_taggedActors[i].hActorCode = FROM_32(as->hActorCode);
|
|
|
|
// Run actor's script for this scene
|
|
if (bRunScript) {
|
|
// Send in reverse order - they get swapped round in the scheduler
|
|
ActorEvent(_taggedActors[i].id, SHOWEVENT, false, 0);
|
|
ActorEvent(_taggedActors[i].id, STARTUP, false, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called between scenes, zeroises all actors.
|
|
*/
|
|
void Actor::DropActors() {
|
|
|
|
for (int i = 0; i < _numActors; i++) {
|
|
if (TinselV2) {
|
|
// Save text color
|
|
COLORREF tColor = _actorInfo[i].textColor;
|
|
|
|
memset(&_actorInfo[i], 0, sizeof(ACTORINFO));
|
|
|
|
// Restor text color
|
|
_actorInfo[i].textColor = tColor;
|
|
|
|
// 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 = nullptr; // No reel running
|
|
_actorInfo[i].presFilm = 0; // ditto
|
|
_actorInfo[i].presObj = nullptr; // 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kill actors.
|
|
* @param ano Actor Id
|
|
*/
|
|
void Actor::DisableActor(int ano) {
|
|
PMOVER pActor;
|
|
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_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)
|
|
KillMover(pActor);
|
|
}
|
|
|
|
/**
|
|
* Enable actors.
|
|
* @param ano Actor Id
|
|
*/
|
|
void Actor::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].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, PLR_NOEVENT);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the aliveness (to coin a word) of the actor.
|
|
* @param ano Actor Id
|
|
*/
|
|
bool Actor::actorAlive(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].bAlive;
|
|
}
|
|
|
|
/**
|
|
* Define an actor as being tagged.
|
|
* @param ano Actor Id
|
|
* @param tagtext Scene handle
|
|
* @param tp tType
|
|
*/
|
|
void Actor::Tag_Actor(int ano, SCNHANDLE tagtext, int tp) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano-1].tagged = true;
|
|
_actorInfo[ano-1].hTag = tagtext;
|
|
_actorInfo[ano-1].tType = tp;
|
|
}
|
|
|
|
/**
|
|
* Undefine an actor as being tagged.
|
|
* @param ano Actor Id
|
|
* @param tagtext Scene handle
|
|
* @param tp tType
|
|
*/
|
|
void Actor::UnTagActor(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano-1].tagged = false;
|
|
}
|
|
|
|
/**
|
|
* Redefine an actor as being tagged.
|
|
* @param ano Actor Id
|
|
* @param tagtext Scene handle
|
|
* @param tp tType
|
|
*/
|
|
void Actor::ReTagActor(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
if (_actorInfo[ano-1].hTag)
|
|
_actorInfo[ano-1].tagged = true;
|
|
}
|
|
|
|
/**
|
|
* Returns a tagged actor's tag type. e.g. TAG_Q1TO3
|
|
* @param ano Actor Id
|
|
*/
|
|
int Actor::TagType(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano-1].tType;
|
|
}
|
|
|
|
/**
|
|
* Returns handle to tagged actor's tag text
|
|
* @param ano Actor Id
|
|
*/
|
|
SCNHANDLE Actor::GetActorTag(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].hTag;
|
|
}
|
|
|
|
/**
|
|
* Called from TagProcess, FirstTaggedActor() resets the index, then
|
|
* NextTagged Actor is repeatedly called until the caller gets fed up
|
|
* or there are no more tagged actors to look at.
|
|
*/
|
|
void Actor::FirstTaggedActor() {
|
|
ti = 0;
|
|
}
|
|
|
|
/**
|
|
* Called from TagProcess, FirstTaggedActor() resets the index, then
|
|
* NextTagged Actor is repeatedly called until the caller gets fed up
|
|
* or there are no more tagged actors to look at.
|
|
*/
|
|
int Actor::NextTaggedActor() {
|
|
PMOVER pActor;
|
|
bool hid;
|
|
|
|
while (ti < _numActors) {
|
|
if (_actorInfo[ti].tagged) {
|
|
pActor = GetMover(ti+1);
|
|
if (pActor)
|
|
hid = MoverHidden(pActor);
|
|
else
|
|
hid = _actorInfo[ti].bHidden;
|
|
|
|
if (!hid) {
|
|
return ++ti;
|
|
}
|
|
}
|
|
++ti;
|
|
}
|
|
|
|
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 Actor::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
|
|
*/
|
|
int32 Actor::actorMaskType(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].mtype;
|
|
}
|
|
|
|
/**
|
|
* Store/Return the currently stored co-ordinates of the actor.
|
|
* Delegate the task for moving actors.
|
|
* @param ano Actor Id
|
|
* @param x X position
|
|
* @param y Y position
|
|
*/
|
|
void Actor::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 Actor::StoreActorSteps(int ano, int steps) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano - 1].steps = steps;
|
|
}
|
|
|
|
int Actor::GetActorSteps(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].steps;
|
|
}
|
|
|
|
void Actor::StoreActorZpos(int ano, int z, int column) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
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 Actor::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 Actor::IncLoopCount(int ano) {
|
|
RANGE_CHECK(ano);
|
|
|
|
_actorInfo[ano - 1].loopCount++;
|
|
}
|
|
|
|
int Actor::GetLoopCount(int ano) {
|
|
RANGE_CHECK(ano);
|
|
|
|
return _actorInfo[ano - 1].loopCount;
|
|
}
|
|
|
|
void Actor::GetActorPos(int ano, int *x, int *y) {
|
|
PMOVER pActor;
|
|
|
|
assert((ano > 0 && ano <= _numActors) || ano == LEAD_ACTOR); // unknown actor
|
|
|
|
pActor = GetMover(ano);
|
|
|
|
if (pActor)
|
|
GetMoverPosition(pActor, x, y);
|
|
else {
|
|
*x = _actorInfo[ano - 1].x;
|
|
*y = _actorInfo[ano - 1].y;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the position of the mid-top of the actor.
|
|
* Delegate the task for moving actors.
|
|
* @param ano Actor Id
|
|
* @param x Output x
|
|
* @param y Output y
|
|
*/
|
|
void Actor::GetActorMidTop(int ano, int *x, int *y) {
|
|
// Not used in JAPAN version
|
|
PMOVER pActor;
|
|
|
|
assert((ano > 0 && ano <= _numActors) || ano == LEAD_ACTOR); // unknown actor
|
|
|
|
pActor = GetMover(ano);
|
|
|
|
if (pActor) {
|
|
GetMoverMidTop(pActor, x, y);
|
|
} else if (TinselV3) {
|
|
int i;
|
|
for (i = 0; i < MAX_REELS; i++) {
|
|
if (_actorInfo[ano-1].presObjs[i] && MultiHasShape(_actorInfo[ano-1].presObjs[i])) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == MAX_REELS) {
|
|
*x = 320;
|
|
*y = 144;
|
|
} else {
|
|
*x = (GetActorLeft(ano) + GetActorRight(ano)) / 2;
|
|
*y = GetActorTop(ano);
|
|
}
|
|
} 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);
|
|
} else {
|
|
GetActorPos(ano, x, y); // The best we can do!
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the appropriate co-ordinate of the actor.
|
|
* @param ano Actor Id
|
|
*/
|
|
int Actor::GetActorLeft(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
if (!TinselV2) {
|
|
// Tinsel 1 version
|
|
if (!_actorInfo[ano - 1].presObj)
|
|
return 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the appropriate co-ordinate of the actor.
|
|
* @param ano Actor Id
|
|
*/
|
|
int Actor::GetActorRight(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the appropriate co-ordinate of the actor.
|
|
* @param ano Actor Id
|
|
*/
|
|
int Actor::GetActorTop(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
if (!TinselV2) {
|
|
// Tinsel 1 version
|
|
if (!_actorInfo[ano - 1].presObj)
|
|
return 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the appropriate co-ordinate of the actor.
|
|
*/
|
|
int Actor::GetActorBottom(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return actor hidden status.
|
|
*/
|
|
bool Actor::ActorHidden(int ano) {
|
|
RANGE_CHECK(ano);
|
|
|
|
return _actorInfo[ano - 1].bHidden;
|
|
}
|
|
|
|
/**
|
|
* Hide an actor if it's a moving actor.
|
|
* @param ano Actor Id
|
|
* @param sf sf
|
|
*/
|
|
bool Actor::HideMovingActor(int ano, int sf) {
|
|
PMOVER pActor;
|
|
|
|
assert((ano > 0 && ano <= _numActors) || ano == LEAD_ACTOR); // illegal actor
|
|
|
|
// Get moving actor involved
|
|
pActor = GetMover(ano);
|
|
|
|
if (pActor) {
|
|
HideMover(pActor, sf);
|
|
return true;
|
|
} else {
|
|
if (_actorInfo[ano - 1].presObj != NULL)
|
|
MultiHideObject(_actorInfo[ano - 1].presObj); // Hidee object
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unhide an actor if it's a moving actor.
|
|
* @param ano Actor Id
|
|
*/
|
|
void Actor::unHideMovingActor(int ano) {
|
|
PMOVER pActor;
|
|
|
|
assert((ano > 0 && ano <= _numActors) || ano == LEAD_ACTOR); // illegal actor
|
|
|
|
// Get moving actor involved
|
|
pActor = GetMover(ano);
|
|
|
|
assert(pActor); // not a moving actor
|
|
|
|
UnHideMover(pActor);
|
|
}
|
|
|
|
/**
|
|
* Called after a moving actor had been replaced by an splay().
|
|
* Moves the actor to where the splay() left it, and continues the
|
|
* actor's walk (if any) from the new co-ordinates.
|
|
*/
|
|
void Actor::restoreMovement(int ano) {
|
|
PMOVER pActor;
|
|
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
// Get moving actor involved
|
|
pActor = GetMover(ano);
|
|
|
|
assert(pActor); // not a moving actor
|
|
|
|
if (pActor->objX == _actorInfo[ano - 1].x && pActor->objY == _actorInfo[ano - 1].y)
|
|
return;
|
|
|
|
pActor->objX = _actorInfo[ano - 1].x;
|
|
pActor->objY = _actorInfo[ano - 1].y;
|
|
|
|
if (pActor->actorObj)
|
|
SSetActorDest(pActor);
|
|
}
|
|
|
|
/**
|
|
* More properly should be called:
|
|
* 'store_actor_reel_and/or_film_and/or_object()'
|
|
*/
|
|
void Actor::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 MoverProcess()
|
|
// (MoverProcess() calls with reel=film=NULL, pobj not NULL)
|
|
if (!pActor
|
|
|| !(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 = hFilm; // Store film
|
|
_actorInfo[ano - 1].presPlayX = x;
|
|
_actorInfo[ano - 1].presPlayY = y;
|
|
}
|
|
|
|
// 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 && hFilm == 0 && pobj != NULL) {
|
|
_actorInfo[ano - 1].presObj = pobj; // Store object
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the present reel/film of the actor.
|
|
*/
|
|
const FREEL *Actor::actorReel(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].presReel; // the present reel
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
void Actor::SetActorPlayFilm(int ano, SCNHANDLE hFilm) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano - 1].playFilm = hFilm;
|
|
}
|
|
|
|
SCNHANDLE Actor::GetActorPlayFilm(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].playFilm;
|
|
}
|
|
|
|
void Actor::SetActorTalkFilm(int ano, SCNHANDLE hFilm) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano - 1].talkFilm = hFilm;
|
|
}
|
|
|
|
SCNHANDLE Actor::GetActorTalkFilm(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].talkFilm;
|
|
}
|
|
|
|
void Actor::SetActorTalking(int ano, bool tf) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano - 1].bTalking = tf;
|
|
}
|
|
|
|
bool Actor::ActorIsTalking(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].bTalking;
|
|
}
|
|
|
|
void Actor::SetActorLatestFilm(int ano, SCNHANDLE hFilm) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano - 1].latestFilm = hFilm;
|
|
_actorInfo[ano - 1].steps = 0;
|
|
}
|
|
|
|
SCNHANDLE Actor::GetActorLatestFilm(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].latestFilm;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
void Actor::UpdateActorEsc(int ano, bool escOn, int escEvent) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
_actorInfo[ano - 1].bEscOn = escOn;
|
|
_actorInfo[ano - 1].escEvent = escEvent;
|
|
}
|
|
|
|
void Actor::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 Actor::ActorEsc(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].bEscOn;
|
|
}
|
|
|
|
int Actor::ActorEev(int ano) {
|
|
assert(ano > 0 && ano <= _numActors); // illegal actor number
|
|
|
|
return _actorInfo[ano - 1].escEvent;
|
|
}
|
|
|
|
/**
|
|
* Guess what these do.
|
|
*/
|
|
int Actor::AsetZPos(OBJECT *pObj, int y, int32 z) {
|
|
int zPos;
|
|
|
|
z += z ? -1 : 0;
|
|
|
|
zPos = y + (z << ZSHIFT);
|
|
MultiSetZPosition(pObj, zPos);
|
|
return zPos;
|
|
}
|
|
|
|
/**
|
|
* Stores actor's attributes.
|
|
* Currently only the speech colors.
|
|
*/
|
|
void Actor::storeActorAttr(int ano, int r1, int g1, int b1) {
|
|
assert((ano > 0 && ano <= _numActors) || ano == -1); // illegal actor number
|
|
|
|
if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure
|
|
if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits
|
|
if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // }
|
|
|
|
if (ano == -1)
|
|
_defaultColor = TINSEL_RGB(r1, g1, b1);
|
|
else
|
|
_actorInfo[ano - 1].textColor = TINSEL_RGB(r1, g1, b1);
|
|
}
|
|
|
|
/**
|
|
* Called from ActorRGB() - Stores actor's speech color.
|
|
*/
|
|
|
|
void Actor::SetActorRGB(int ano, COLORREF color) {
|
|
assert(ano >= 0 && ano <= _numActors);
|
|
|
|
if (ano)
|
|
_actorInfo[ano - 1].textColor = TO_32(color);
|
|
else
|
|
_defaultColor = TO_32(color);
|
|
}
|
|
|
|
/**
|
|
* Get the actor's stored speech color.
|
|
* @param ano Actor Id
|
|
*/
|
|
COLORREF Actor::GetActorRGB(int ano) {
|
|
// Not used in JAPAN version
|
|
assert((ano >= -1) && (ano <= _numActors)); // illegal actor number
|
|
|
|
if ((ano == -1) || !_actorInfo[ano - 1].textColor)
|
|
return _defaultColor;
|
|
else
|
|
return _actorInfo[ano - 1].textColor;
|
|
}
|
|
|
|
/**
|
|
* Set the actor's Z-factor
|
|
*/
|
|
void Actor::SetActorZfactor(int ano, uint32 zFactor) {
|
|
RANGE_CHECK(ano);
|
|
|
|
_zFactors[ano - 1] = (uint8)zFactor;
|
|
}
|
|
|
|
uint32 Actor::GetActorZfactor(int ano) {
|
|
RANGE_CHECK(ano);
|
|
|
|
return _zFactors[ano - 1];
|
|
}
|
|
|
|
/**
|
|
* Store relevant information pertaining to currently existing actors.
|
|
*/
|
|
int Actor::SaveActors(PSAVED_ACTOR sActorInfo) {
|
|
int i, j, k;
|
|
|
|
for (i = 0, j = 0; i < _numActors; i++) {
|
|
for (k = 0; k < (TinselV2 ? MAX_REELS : 1); ++k) {
|
|
bool presFlag = !TinselV2 ? _actorInfo[i].presObj != NULL :
|
|
(_actorInfo[i].presObjs[k] != NULL) && !_vm->_handle->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 Actor::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 Actor::SaveZpositions(void *zpp) {
|
|
memcpy(zpp, _zPositions, sizeof(_zPositions));
|
|
}
|
|
|
|
void Actor::RestoreZpositions(void *zpp) {
|
|
memcpy(_zPositions, zpp, sizeof(_zPositions));
|
|
}
|
|
|
|
void Actor::SaveActorZ(byte *saveActorZ) {
|
|
assert(_numActors <= MAX_SAVED_ACTOR_Z);
|
|
|
|
memcpy(saveActorZ, _zFactors, _numActors);
|
|
}
|
|
|
|
void Actor::RestoreActorZ(byte *saveActorZ) {
|
|
memcpy(_zFactors, saveActorZ, _numActors);
|
|
}
|
|
|
|
void Actor::SetActorsOn() {
|
|
_actorsOn = true;
|
|
}
|
|
|
|
void Actor::ToggleActor(int ano, bool show) {
|
|
assert((ano > 0 && ano <= _numActors) || ano == -1); // illegal actor number
|
|
_actorInfo[ano - 1].bHidden = !show;
|
|
}
|
|
|
|
SCNHANDLE Actor::GetActorCode(int ano) {
|
|
assert((ano > 0 && ano <= _numActors) || ano == -1); // illegal actor number
|
|
return _actorInfo[ano - 1].actorCode;
|
|
}
|
|
|
|
SCNHANDLE Actor::GetTaggedActorCode(int ano) {
|
|
return _taggedActors[ano - 1].hActorCode;
|
|
}
|
|
|
|
void Actor::RunCodeToCompletion(int ano) {
|
|
assert((ano > 0 && ano <= _numActors) || ano == -1); // illegal actor number
|
|
_actorInfo[ano - 1].completed = true;
|
|
}
|
|
|
|
void Actor::ActorsLife(int ano, bool bAlive) {
|
|
assert((ano > 0 && ano <= _numActors) || ano == -1); // illegal actor number
|
|
_actorInfo[ano - 1].bAlive = bAlive;
|
|
}
|
|
|
|
void Actor::syncAllActorsAlive(Common::Serializer &s) {
|
|
for (int i = 0; i < MAX_SAVED_ALIVES; i++) {
|
|
s.syncAsByte(_actorInfo[i].bAlive);
|
|
s.syncAsByte(_actorInfo[i].tagged);
|
|
s.syncAsByte(_actorInfo[i].tType);
|
|
s.syncAsUint32LE(_actorInfo[i].hTag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called from EndActor()
|
|
*/
|
|
void Actor::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 = nullptr;
|
|
// 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] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a tagged actor's tag portion.
|
|
*/
|
|
void Actor::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 Actor::GetActorTagHandle(int ano) {
|
|
// Convert actor number to index
|
|
ano = TaggedActorIndex(ano);
|
|
|
|
return _taggedActors[ano].hOverrideTag ?
|
|
_taggedActors[ano].hOverrideTag : _taggedActors[ano].hTagText;
|
|
}
|
|
|
|
void Actor::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 Actor::ActorIsPointedTo(int actor) {
|
|
// Convert actor number to index
|
|
actor = TaggedActorIndex(actor);
|
|
|
|
return (_taggedActors[actor].tagFlags & POINTING);
|
|
}
|
|
|
|
void Actor::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 Actor::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 Actor::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 Actor::FrontTaggedActor() {
|
|
int i;
|
|
|
|
for (i = 0; i < _numTaggedActors; i++) {
|
|
if (_taggedActors[i].tagFlags & POINTING)
|
|
return _taggedActors[i].id;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* GetActorTagPos
|
|
*/
|
|
void Actor::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) {
|
|
_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
|
|
*pTagX -= Loffset;
|
|
*pTagY -= Toffset;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is Tagged Actor
|
|
*/
|
|
bool Actor::IsTaggedActor(int actor) {
|
|
int i;
|
|
|
|
for (i = 0; i < _numTaggedActors; i++) {
|
|
if (_taggedActors[i].id == actor)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* StoreActorPresFilm
|
|
*/
|
|
void Actor::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] = nullptr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GetActorPresFilm
|
|
*/
|
|
SCNHANDLE Actor::GetActorPresFilm(int ano) {
|
|
RANGE_CHECK(ano);
|
|
|
|
return _actorInfo[ano - 1].presFilm;
|
|
}
|
|
|
|
|
|
/**
|
|
* GetActorFilmNumber
|
|
*/
|
|
int Actor::GetActorFilmNumber(int ano) {
|
|
RANGE_CHECK(ano);
|
|
|
|
return _actorInfo[ano - 1].filmNum;
|
|
}
|
|
|
|
/**
|
|
* More properly should be called:
|
|
* 'StoreActorReelAndObject()'
|
|
*/
|
|
void Actor::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 Actor::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] = nullptr;
|
|
_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 Actor::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;
|
|
}
|
|
|
|
/**
|
|
* Runs actor's glitter code.
|
|
*/
|
|
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
|
|
const ATP_INIT *atp = (const ATP_INIT *)param;
|
|
|
|
CORO_BEGIN_CODE(_ctx);
|
|
|
|
if (TinselV2) {
|
|
// Take control for CONVERSE events
|
|
if (atp->event == CONVERSE) {
|
|
_ctx->bTookControl = GetControl();
|
|
_vm->_dialogs->HideConversation(true);
|
|
} else
|
|
_ctx->bTookControl = false;
|
|
|
|
// Run the Glitter code
|
|
CORO_INVOKE_1(Interpret, atp->pic);
|
|
|
|
// Restore conv window if applicable
|
|
if (atp->event == CONVERSE) {
|
|
// Free control if we took it
|
|
if (_ctx->bTookControl)
|
|
ControlOn();
|
|
|
|
_vm->_dialogs->HideConversation(false);
|
|
}
|
|
} else {
|
|
CORO_INVOKE_1(AllowDclick, atp->bev); // May kill us if single click
|
|
|
|
// Run the Glitter code
|
|
assert(_vm->_actor->GetActorCode(atp->id)); // no code to run
|
|
|
|
_ctx->pic = InitInterpretContext(GS_ACTOR, _vm->_actor->GetActorCode(atp->id),
|
|
atp->event, NOPOLY, atp->id, NULL);
|
|
CORO_INVOKE_1(Interpret, _ctx->pic);
|
|
|
|
// If it gets here, actor's code has run to completion
|
|
_vm->_actor->RunCodeToCompletion(atp->id);
|
|
}
|
|
|
|
CORO_END_CODE;
|
|
}
|
|
|
|
static void ActorRestoredProcess(CORO_PARAM, const void *param) {
|
|
// COROUTINE
|
|
CORO_BEGIN_CONTEXT;
|
|
INT_CONTEXT *pic;
|
|
CORO_END_CONTEXT(_ctx);
|
|
|
|
// get the stuff copied to process when it was created
|
|
const RATP_INIT *r = (const RATP_INIT *)param;
|
|
bool isSavegame = r->pic->resumeState == RES_SAVEGAME;
|
|
|
|
CORO_BEGIN_CODE(_ctx);
|
|
|
|
_ctx->pic = RestoreInterpretContext(r->pic);
|
|
|
|
// The newly added check here specially sets the process to RES_NOT when loading a savegame.
|
|
// This is needed particularly for the Psychiatrist scene in Discworld 1 - otherwise Rincewind
|
|
// can't go upstairs without leaving the building and returning. If this patch causes problems
|
|
// in other scenes, an added check for the hCode == 1174490602 could be added.
|
|
if (isSavegame && TinselV1)
|
|
_ctx->pic->resumeState = RES_NOT;
|
|
|
|
CORO_INVOKE_1(Interpret, _ctx->pic);
|
|
|
|
// If it gets here, actor's code has run to completion
|
|
_vm->_actor->RunCodeToCompletion(r->id);
|
|
|
|
CORO_END_CODE;
|
|
}
|
|
|
|
/**
|
|
* Starts up process to runs actor's glitter code.
|
|
* @param ano Actor Id
|
|
* @param event Event structure
|
|
* @param be ButEvent
|
|
*/
|
|
void ActorEvent(int ano, TINSEL_EVENT event, PLR_EVENT be) {
|
|
ATP_INIT atp;
|
|
|
|
// Only if there is Glitter code associated with this actor.
|
|
if (_vm->_actor->GetActorCode(ano)) {
|
|
atp.id = ano;
|
|
atp.event = event;
|
|
atp.bev = be;
|
|
atp.pic = nullptr;
|
|
CoroScheduler.createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Same with the normal ActorEvent, but with null CORO context
|
|
*/
|
|
void ActorEvent(int ano, TINSEL_EVENT tEvent, bool bWait, int myEscape, bool *result) {
|
|
ActorEvent(Common::nullContext, ano, tEvent, bWait, myEscape, result);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
Common::PPROCESS pProc;
|
|
CORO_END_CONTEXT(_ctx);
|
|
|
|
CORO_BEGIN_CODE(_ctx);
|
|
|
|
index = _vm->_actor->TaggedActorIndex(ano);
|
|
assert(_vm->_actor->GetTaggedActorCode(index + 1));
|
|
if (result)
|
|
*result = false;
|
|
|
|
atp.id = 0;
|
|
atp.event = tEvent;
|
|
atp.pic = InitInterpretContext(GS_ACTOR,
|
|
_vm->_actor->GetTaggedActorCode(index + 1),
|
|
tEvent,
|
|
NOPOLY, // No polygon
|
|
ano, // Actor
|
|
NULL, // No object
|
|
myEscape);
|
|
|
|
if (atp.pic != NULL) {
|
|
_ctx->pProc = CoroScheduler.createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
|
|
AttachInterpret(atp.pic, _ctx->pProc);
|
|
|
|
if (bWait)
|
|
CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result);
|
|
}
|
|
|
|
CORO_END_CODE;
|
|
}
|
|
|
|
/**
|
|
* Shows the given actor
|
|
*/
|
|
void ShowActor(CORO_PARAM, int ano) {
|
|
PMOVER pMover;
|
|
assert(ano > 0 && ano <= _vm->_actor->GetCount());
|
|
|
|
CORO_BEGIN_CONTEXT;
|
|
CORO_END_CONTEXT(_ctx);
|
|
|
|
CORO_BEGIN_CODE(_ctx);
|
|
|
|
// reset hidden flag
|
|
_vm->_actor->ToggleActor(ano, true);
|
|
|
|
// Send event to tagged actors
|
|
if (_vm->_actor->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;
|
|
}
|
|
|
|
/**
|
|
* Set actor hidden status to true.
|
|
* For a moving actor, actually hide it.
|
|
* @param ano Actor Id
|
|
*/
|
|
void HideActor(CORO_PARAM, int ano) {
|
|
PMOVER pMover;
|
|
assert((ano > 0 && ano <= _vm->_actor->GetCount()) || ano == LEAD_ACTOR); // illegal actor
|
|
|
|
CORO_BEGIN_CONTEXT;
|
|
CORO_END_CONTEXT(_ctx);
|
|
|
|
CORO_BEGIN_CODE(_ctx);
|
|
|
|
if (TinselV2) {
|
|
_vm->_actor->ToggleActor(ano, false);
|
|
|
|
// Send event to tagged actors
|
|
// (this is duplicated in HideMover())
|
|
if (_vm->_actor->IsTaggedActor(ano)) {
|
|
CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, ano, HIDEEVENT, true, 0));
|
|
|
|
// It may be pointed to
|
|
_vm->_actor->SetActorPointedTo(ano, false);
|
|
_vm->_actor->SetActorTagWanted(ano, false, false, 0);
|
|
}
|
|
}
|
|
|
|
// Get moving actor involved
|
|
pMover = GetMover(ano);
|
|
|
|
if (pMover)
|
|
HideMover(pMover, 0);
|
|
else if (!TinselV2)
|
|
_vm->_actor->ToggleActor(ano, false);
|
|
|
|
CORO_END_CODE;
|
|
}
|
|
|
|
void RestoreActorProcess(int id, INT_CONTEXT *pic, bool savegameFlag) {
|
|
RATP_INIT r = {pic, id};
|
|
if (savegameFlag)
|
|
pic->resumeState = RES_SAVEGAME;
|
|
|
|
CoroScheduler.createProcess(PID_TCODE, ActorRestoredProcess, &r, sizeof(r));
|
|
}
|
|
|
|
} // End of namespace Tinsel
|