scummvm/engines/tinsel/play.cpp

1180 lines
32 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.
*
* $URL$
* $Id$
*
* Plays films within a scene, takes into account the actor in each 'column'. |
*/
#include "tinsel/actors.h"
#include "tinsel/background.h"
#include "tinsel/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()
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
uint8 splay; // Set if called from splay()
uint8 bTop; // Set if called from topplay()
uint8 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.
*/
static void PokeInPalette(SCNHANDLE hMulFrame) {
const FRAME *pFrame; // Pointer to frame
IMAGE *pim; // Pointer to image
// Could be an empty column
if (hMulFrame) {
pFrame = (const FRAME *)LockMem(hMulFrame);
// get pointer to image
pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image
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(FROM_LE_32(pmi->hMulFrame));
// get pointer to image
pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image
pim->hImgPal = TO_LE_32(BgPal());
}
}
int32 NoNameFunc(int actorID, bool bNewMover) {
PMOVER pActor;
int32 retval;
pActor = GetMover(actorID);
if (pActor != NULL && !bNewMover) {
// If no path, just use first path in the scene
if (pActor->hCpath == NOPOLY)
retval = GetPolyZfactor(FirstPathPoly());
else
retval = GetPolyZfactor(pActor->hCpath);
} else {
switch (actorMaskType(actorID)) {
case ACT_DEFAULT:
retval = 0;
break;
case ACT_MASK:
retval = 0;
break;
case ACT_ALWAYS:
retval = 10;
break;
default:
retval = actorMaskType(actorID);
break;
}
}
return retval;
}
static FREEL *GetReel(SCNHANDLE hFilm, int column) {
FILM *pFilm = (FILM *)LockMem(hFilm);
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() {
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(FROM_LE_32(pReel->mobj));
_ctx->reelActor = (int32)FROM_LE_32(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(FROM_LE_32(pReel->script));
if (_ctx->speed == -1) {
_ctx->speed = (ONE_SECOND/FROM_LE_32(pFilm->frate));
// Restored reel
for (;;) {
if (FROM_LE_32(pAni[_ctx->frameNumber].op) == ANI_END)
break;
else if (FROM_LE_32(pAni[_ctx->frameNumber].op) == ANI_JUMP) {
_ctx->frameNumber++;
_ctx->frameNumber += FROM_LE_32(pAni[_ctx->frameNumber].op);
break;
}
// Could check for the other stuff here
// but they really dont happen
// OH YES THEY DO
else if (FROM_LE_32(pAni[_ctx->frameNumber].op) == ANI_ADJUSTX
|| FROM_LE_32(pAni[_ctx->frameNumber].op) == ANI_ADJUSTY) {
_ctx->frameNumber += 2;
} else if (FROM_LE_32(pAni[_ctx->frameNumber].op) == ANI_ADJUSTXY) {
_ctx->frameNumber += 3;
} else {
// ANI_STOP, ANI_HIDE, ANI_HFLIP,
// ANI_VFLIP, ANI_HVFLIP, default
_ctx->frameNumber++;
}
}
}
switch (FROM_LE_32(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((int32)FROM_LE_32(pAni[_ctx->frameNumber].op) < 0);
_ctx->frameNumber += FROM_LE_32(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 = FROM_LE_32(pAni[_ctx->frameNumber++].op);
if (_ctx->sampleNumber > 0)
_ctx->bLooped = false;
else {
_ctx->sampleNumber = ~_ctx->sampleNumber;
_ctx->bLooped = true;
}
x = (short)(FROM_LE_32(pAni[_ctx->frameNumber].op) >> 16);
y = (short)(FROM_LE_32(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 *param) {
// get the stuff copied to process when it was created
int i = *(const int *)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() {
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.
* - If explicit co-ordinates, use these, If embedded co-ordinates,
* leave alone, otherwise use actor's current position.
* - Moving actors get hidden during this play, other actors get
* _ctx->replaced by this play.
* - Column 0 of a film gets its appropriate Z-position, slave columns
* get slightly bigger Z-positions, in column order.
* - Play proceeds until the script finishes, another reel starts up for
* this actor, or the actor gets killed.
* - If called from an splay(), moving actor's co-ordinates are updated
* after the play, any walk still in progress will go on from there.
*/
static void t1PlayReel(CORO_PARAM, const PPINIT *ppi) {
CORO_BEGIN_CONTEXT;
OBJECT *pPlayObj; // Object
ANIM thisAnim; // Animation structure
bool mActor; // Gets set if this is a moving actor
bool lifeNoMatter;
bool replaced;
const FREEL *pfreel; // The 'column' to play
int stepCount;
int frameCount;
int reelActor;
PMOVER pActor;
int tmpX, tmpY;
CORO_END_CONTEXT(_ctx);
static int firstColZ = 0; // Z-position of column zero
static int32 fColZfactor = 0; // Z-factor of column zero's actor
CORO_BEGIN_CODE(_ctx);
const MULTI_INIT *pmi; // MULTI_INIT structure
bool bNewMover; // Gets set if a moving actor that isn't in scene yet
const FILM *pfilm;
_ctx->lifeNoMatter = false;
_ctx->replaced = false;
_ctx->pActor = NULL;
bNewMover = false;
pfilm = (const FILM *)LockMem(ppi->hFilm);
_ctx->pfreel = &pfilm->reels[ppi->column];
// Get the MULTI_INIT structure
pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(_ctx->pfreel->mobj));
// Save actor's ID
_ctx->reelActor = (int32)FROM_LE_32(pmi->mulID);
/**** New (experimental? bit 5/1/95 ****/
if (!TinselV0 && !actorAlive(_ctx->reelActor))
return;
/**** Delete a bit down there if this stays ****/
UpdateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent);
// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios
if (ppi->hFilm != GetActorLatestFilm(_ctx->reelActor)) {
// This in not the last film scheduled for this actor
// It may be the last non-talk film though
if (ActorIsTalking(_ctx->reelActor))
SetActorPlayFilm(_ctx->reelActor, ppi->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 (ppi->hFilm != GetActorTalkFilm(_ctx->reelActor)) {
SetActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk
return;
}
} else {
SetActorPlayFilm(_ctx->reelActor, ppi->hFilm);
}
// If this reel is already playing for this actor, just forget it.
if (actorReel(_ctx->reelActor) == _ctx->pfreel)
return;
// Poke in the background palette
PokeInPalette(FROM_LE_32(pmi->hMulFrame));
// Set up and insert the multi-object
_ctx->pPlayObj = MultiInitObject(pmi);
if (!ppi->bTop)
MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
else
MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
// If co-ordinates are specified, use specified.
// Otherwise, use actor's position if there are not embedded co-ords.
// Add this first test for nth columns with offsets
// in plays with (x,y)
_ctx->tmpX = ppi->x;
_ctx->tmpY = ppi->y;
if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) {
} else if (_ctx->tmpX != -1 || _ctx->tmpY != -1) {
MultiSetAniXY(_ctx->pPlayObj, _ctx->tmpX, _ctx->tmpY);
} else if (!pmi->mulX && !pmi->mulY) {
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 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.
if (_ctx->mActor) {
_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, _ctx->tmpX, _ctx->tmpY);
/**** Will get rid of this if the above is kept ****/
// We may be temporarily resuscitating a dead actor
if (ppi->actorid == 0 && !actorAlive(_ctx->reelActor))
_ctx->lifeNoMatter = true;
InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj, FROM_LE_32(_ctx->pfreel->script), ppi->speed);
// If first column, set Z position as per
// Otherwise, column 0's + column number
// N.B. It HAS been ensured that the first column gets here first
if (ppi->z != 0) {
MultiSetZPosition(_ctx->pPlayObj, ppi->z);
StoreActorZpos(_ctx->reelActor, ppi->z);
} else if (ppi->bTop) {
if (ppi->column == 0) {
firstColZ = Z_TOPPLAY + actorMaskType(_ctx->reelActor);
MultiSetZPosition(_ctx->pPlayObj, firstColZ);
StoreActorZpos(_ctx->reelActor, firstColZ);
} else {
MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
StoreActorZpos(_ctx->reelActor, firstColZ + ppi->column);
}
} else if (ppi->column == 0) {
if (_ctx->mActor && !bNewMover) {
// If no path, just use first path in the scene
if (_ctx->pActor->hCpath == NOPOLY)
fColZfactor = GetPolyZfactor(FirstPathPoly());
else
fColZfactor = GetPolyZfactor(_ctx->pActor->hCpath);
firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
} else {
switch (actorMaskType(_ctx->reelActor)) {
case ACT_DEFAULT:
fColZfactor = 0;
firstColZ = 2;
MultiSetZPosition(_ctx->pPlayObj, firstColZ);
break;
case ACT_MASK:
fColZfactor = 0;
firstColZ = MultiLowest(_ctx->pPlayObj);
MultiSetZPosition(_ctx->pPlayObj, firstColZ);
break;
case ACT_ALWAYS:
fColZfactor = 10;
firstColZ = 10000;
MultiSetZPosition(_ctx->pPlayObj, firstColZ);
break;
default:
fColZfactor = actorMaskType(_ctx->reelActor);
firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
if (firstColZ < 2) {
// This is an experiment!
firstColZ = 2;
MultiSetZPosition(_ctx->pPlayObj, firstColZ);
}
break;
}
}
StoreActorZpos(_ctx->reelActor, firstColZ);
} else {
if (NoNameFunc(_ctx->reelActor, bNewMover) > fColZfactor) {
fColZfactor = NoNameFunc(_ctx->reelActor, bNewMover);
firstColZ = fColZfactor << 10;
}
MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
StoreActorZpos(_ctx->reelActor, firstColZ + ppi->column);
}
/*
* Play until the script finishes,
* another reel starts up for this actor,
* or the actor gets killed.
*/
_ctx->stepCount = 0;
_ctx->frameCount = 0;
do {
if (_ctx->stepCount++ == 0) {
_ctx->frameCount++;
StoreActorSteps(_ctx->reelActor, _ctx->frameCount);
}
if (_ctx->stepCount == ppi->speed)
_ctx->stepCount = 0;
if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished)
break;
int x, y;
GetAniPosition(_ctx->pPlayObj, &x, &y);
StoreActorPos(_ctx->reelActor, x, y);
CORO_SLEEP(1);
if (actorReel(_ctx->reelActor) != _ctx->pfreel) {
_ctx->replaced = true;
break;
}
if (ActorEsc(_ctx->reelActor) && ActorEev(_ctx->reelActor) != GetEscEvents())
break;
} while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor));
// Register the fact that we're NOT playing this for this actor
if (actorReel(_ctx->reelActor) == _ctx->pfreel)
storeActorReel(_ctx->reelActor, NULL, 0, NULL, 0, 0, 0);
// Ditch the object
if (!ppi->bTop)
MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
else
MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
if (_ctx->mActor) {
if (!_ctx->replaced)
unHideMovingActor(_ctx->reelActor); // Restore moving actor
// Update it's co-ordinates if this is an splay()
if (ppi->splay)
restoreMovement(_ctx->reelActor);
}
CORO_END_CODE;
}
/**
* - 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(FROM_LE_32(_ctx->pFreel->mobj));
if ((int32)FROM_LE_32(_ctx->pmi->mulID) == -2) {
CORO_INVOKE_ARGS(SoundReel, (CORO_SUBCTX, hFilm, column, speed, myescEvent,
FROM_LE_32(_ctx->pmi->otherFlags) & OTH_RELATEDACTOR));
return;
}
// Save actor's ID
_ctx->reelActor = FROM_LE_32(_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(FROM_LE_32(_ctx->pmi->mulFlags) == DMA_WNZ || FROM_LE_32(_ctx->pmi->mulFlags) == (DMA_WNZ | DMA_GHOST));
_ctx->pmi->mulFlags = TO_LE_32(FROM_LE_32(_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( ((FROM_LE_32(_ctx->pmi->otherFlags) & OTH_RELATIVE) && !(FROM_LE_32(_ctx->pmi->otherFlags) & OTH_ABSOLUTE))
|| ((FROM_LE_32(_ctx->pmi->otherFlags) & OTH_ABSOLUTE) && !(FROM_LE_32(_ctx->pmi->otherFlags) & OTH_RELATIVE)) );
_ctx->bRelative = FROM_LE_32(_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 + FROM_LE_32(_ctx->pmi->mulX), y + FROM_LE_32(_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 ((int32)FROM_LE_32(_ctx->pmi->mulZ) != -1) {
// Z override in script
baseZfact = FROM_LE_32(_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, FROM_LE_32(_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) {
CORO_BEGIN_CONTEXT;
CORO_END_CONTEXT(_ctx);
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) {
const MULTI_INIT *pmi; // MULTI_INIT structure
// Get the MULTI_INIT structure
pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(reel->mobj));
if (!TinselV2 || ((int32)FROM_LE_32(pmi->mulID) != -2))
SetActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film);
}
// *******************************************************
/**
* Start up a play process for each column in a film.
*
* NOTE: The processes are started in reverse order so that the first
* column's process kicks in first.
*/
void PlayFilm(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool splay, bool sfact, bool escOn,
int myescEvent, bool bTop) {
assert(hFilm != 0); // Trying to play NULL film
const FILM *pFilm;
CORO_BEGIN_CONTEXT;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
pFilm = (const FILM *)LockMem(hFilm);
PPINIT ppi;
// Now allowed empty films!
if (pFilm->numreels == 0)
return; // Nothing to do!
ppi.hFilm = hFilm;
ppi.x = x;
ppi.y = y;
ppi.z = 0;
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;
// Start display process for each reel in the film
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));
}
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 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(hFilm != 0); // Trying to play NULL film
const FILM *pFilm;
pFilm = (const FILM *)LockMem(hFilm);
// Now allowed empty films!
if (pFilm->numreels == 0)
return; // Already played to completion!
_ctx->ppi.hFilm = hFilm;
_ctx->ppi.x = x;
_ctx->ppi.y = y;
_ctx->ppi.z = 0;
_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;
_ctx->ppi.sf = sfact;
_ctx->ppi.escOn = escOn;
_ctx->ppi.myescEvent = myescEvent;
// 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));
}
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->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;
}
/**
* Start up a play process for a particular column in a film.
*
* NOTE: This is specifically for actors during a Tinsel 1 restore scene.
*/
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 = hFilm;
ppi.x = x;
ppi.y = y;
ppi.z = z;
ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
ppi.actorid = 0;
ppi.splay = false;
ppi.bTop = false;
ppi.bRestore = true;
ppi.sf = 0;
ppi.column = reelnum;
ppi.myescEvent = 0;
ppi.escOn = false;
ppi.myescEvent = GetEscEvents();
assert(pfilm->numreels);
NewestFilm(hFilm, &pfilm->reels[reelnum]);
// Start display process for the reel
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/FROM_LE_32(pFilm->frate));
ppi.bTop = false;
ppi.myescEvent = 0;
// Search backwards for now as later column will be the one
for (i = (int)FROM_LE_32(pFilm->numreels) - 1; i >= 0; i--) {
pFreel = &pFilm->reels[i];
pmi = (PMULTI_INIT) LockMem(FROM_LE_32(pFreel->mobj));
if ((int32)FROM_LE_32(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++;
}
}
}
/**
* Get the actor id from a film (column 0)
*/
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);
}
} // End of namespace Tinsel