Files
commandergenius/project/jni/application/sc2/src/uqm/commanim.c
T

624 lines
15 KiB
C

//Copyright Paul Reiche, Fred Ford. 1992-2002
/*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#define COMM_INTERNAL
#include "commanim.h"
#include "comm.h"
#include "element.h"
#include "setup.h"
#include "libs/compiler.h"
#include "libs/graphics/cmap.h"
#include "libs/mathlib.h"
static TimeCount LastTime;
static SEQUENCE Sequences[MAX_ANIMATIONS + 2];
// 2 extra for Talk and Transition animations
static DWORD ActiveMask;
// Bit mask of all animations that are currently active.
// Bit 'i' is set if the animation with index 'i' is active.
static ANIMATION_DESC TalkDesc;
static ANIMATION_DESC TransitDesc;
static SEQUENCE* Talk;
static SEQUENCE* Transit;
static COUNT FirstAmbient;
static COUNT TotalSequences;
static inline DWORD
randomFrameRate (SEQUENCE *pSeq)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
return ADPtr->BaseFrameRate +
TFB_Random () % (ADPtr->RandomFrameRate + 1);
}
static inline DWORD
randomRestartRate (SEQUENCE *pSeq)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
return ADPtr->BaseRestartRate +
TFB_Random () % (ADPtr->RandomRestartRate + 1);
}
static inline COUNT
randomFrameIndex (SEQUENCE *pSeq, COUNT from)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
return from + TFB_Random () % (ADPtr->NumFrames - from);
}
static void
SetupAmbientSequences (SEQUENCE *pSeq, COUNT Num)
{
COUNT i;
for (i = 0; i < Num; ++i, ++pSeq)
{
ANIMATION_DESC *ADPtr = &CommData.AlienAmbientArray[i];
memset (pSeq, 0, sizeof (*pSeq));
pSeq->ADPtr = ADPtr;
if (ADPtr->AnimFlags & COLORXFORM_ANIM)
pSeq->AnimType = COLOR_ANIM;
else
pSeq->AnimType = PICTURE_ANIM;
pSeq->Direction = UP_DIR;
pSeq->FramesLeft = ADPtr->NumFrames;
// Default: first frame is neutral
if (ADPtr->AnimFlags & RANDOM_ANIM)
{ // Set a random frame/colormap
pSeq->NextIndex = TFB_Random () % ADPtr->NumFrames;
}
else if (ADPtr->AnimFlags & YOYO_ANIM)
{ // Skip the first frame/colormap (it's neutral)
pSeq->NextIndex = 1;
--pSeq->FramesLeft;
}
else if (ADPtr->AnimFlags & CIRCULAR_ANIM)
{ // Exception that makes everything more painful:
// *Last* frame is neutral
pSeq->CurIndex = ADPtr->NumFrames - 1;
pSeq->NextIndex = 0;
}
pSeq->Alarm = randomRestartRate (pSeq) + 1;
}
}
static void
SetupTalkSequence (SEQUENCE *pSeq, ANIMATION_DESC *ADPtr)
{
memset (pSeq, 0, sizeof (*pSeq));
// Initially disabled, and until needed
ADPtr->AnimFlags |= ANIM_DISABLED;
pSeq->ADPtr = ADPtr;
pSeq->AnimType = PICTURE_ANIM;
}
static inline BOOLEAN
animAtNeutralIndex (SEQUENCE *pSeq)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
if (ADPtr->AnimFlags & CIRCULAR_ANIM)
{ // CIRCULAR_ANIM's neutral frame is the last
return pSeq->NextIndex == 0;
}
else
{ // All others, neutral frame is the first
return pSeq->CurIndex == 0;
}
}
static inline BOOLEAN
conflictsWithTalkingAnim (SEQUENCE *pSeq)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
return ADPtr->AnimFlags & CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING;
}
static void
ProcessColormapAnims (SEQUENCE *pSeq, COUNT Num)
{
COUNT i;
for (i = 0; i < Num; ++i, ++pSeq)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
if ((ADPtr->AnimFlags & ANIM_DISABLED)
|| pSeq->AnimType != COLOR_ANIM
|| !pSeq->Change)
continue;
XFormColorMap (GetColorMapAddress (
SetAbsColorMapIndex (CommData.AlienColorMap,
ADPtr->StartIndex + pSeq->CurIndex)),
pSeq->Alarm - 1);
pSeq->Change = FALSE;
}
}
static BOOLEAN
AdvanceAmbientSequence (SEQUENCE *pSeq)
{
BOOLEAN active;
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
--pSeq->FramesLeft;
// YOYO_ANIM does not actually end until it comes back
// in reverse direction, even if FramesLeft gets to 0 here
if (pSeq->FramesLeft
|| ((ADPtr->AnimFlags & YOYO_ANIM) && pSeq->NextIndex != 0))
{
active = TRUE;
pSeq->Alarm = randomFrameRate (pSeq) + 1;
}
else
{ // last animation frame
active = FALSE;
pSeq->Alarm = randomRestartRate (pSeq) + 1;
// RANDOM_ANIM must end on a neutral frame
if (ADPtr->AnimFlags & RANDOM_ANIM)
pSeq->NextIndex = 0;
}
// Will draw the next frame or change to next colormap
pSeq->CurIndex = pSeq->NextIndex;
pSeq->Change = TRUE;
if (pSeq->FramesLeft == 0)
{ // Animation ended
// Set it up for the next round
pSeq->FramesLeft = ADPtr->NumFrames;
if (ADPtr->AnimFlags & YOYO_ANIM)
{ // YOYO_ANIM never draws the first frame
// ("first" depends on direction)
--pSeq->FramesLeft;
pSeq->Direction = -pSeq->Direction;
}
else if (ADPtr->AnimFlags & CIRCULAR_ANIM)
{ // Rewind the CIRCULAR_ANIM
// NextIndex will be brought to 0 just below
pSeq->NextIndex = -1;
}
// RANDOM_ANIM is setup just below
}
if (ADPtr->AnimFlags & RANDOM_ANIM)
pSeq->NextIndex = randomFrameIndex (pSeq, 0);
else
pSeq->NextIndex += pSeq->Direction;
return active;
}
static void
ResetSequence (SEQUENCE *pSeq)
{
// Reset the animation and cause a redraw of the neutral frame,
// assuming it is not ANIM_DISABLED
// NOTE: This does not handle CIRCULAR_ANIM properly
pSeq->Direction = NO_DIR;
pSeq->CurIndex = 0;
pSeq->Change = TRUE;
}
static void
AdvanceTalkingSequence (SEQUENCE *pSeq, DWORD ElapsedTicks)
{
// We use the actual descriptor for flags processing and
// a copied one for drawing. A copied one is updated only
// when it is safe to do so.
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
if (pSeq->Direction == NO_DIR)
{ // just starting now
pSeq->Direction = UP_DIR;
// It's now safe to pick up new Talk descriptor if changed
// (e.g. Zoq and Pik taking turns to talk)
if (CommData.AlienTalkDesc.StartIndex != ADPtr->StartIndex)
{ // copy the new one
*ADPtr = CommData.AlienTalkDesc;
}
assert (pSeq->CurIndex == 0);
pSeq->Alarm = 0; // now!
ADPtr->AnimFlags &= ~ANIM_DISABLED;
}
if (pSeq->Alarm > ElapsedTicks)
{ // Not time yet
pSeq->Alarm -= ElapsedTicks;
return;
}
// Time to start or advance the animation
pSeq->Alarm = randomFrameRate (pSeq);
pSeq->Change = TRUE;
// Talking animation is like RANDOM_ANIM, except that
// random frames always alternate with the neutral one
// The animation does not stop until we reset it
if (pSeq->CurIndex == 0)
{ // random frame next
pSeq->CurIndex = randomFrameIndex (pSeq, 1);
pSeq->Alarm += randomRestartRate (pSeq);
}
else
{ // neutral frame next
pSeq->CurIndex = 0;
}
}
static BOOLEAN
AdvanceTransitSequence (SEQUENCE *pSeq, DWORD ElapsedTicks)
{
BOOLEAN done = FALSE;
// We use the actual descriptor for flags processing and
// a copied one for drawing. A copied one is updated only
// when it is safe to do so.
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
if (pSeq->Direction == NO_DIR)
{ // just starting now
pSeq->Alarm = 0; // now!
ADPtr->AnimFlags &= ~ANIM_DISABLED;
}
if (pSeq->Alarm > ElapsedTicks)
{ // Not time yet
pSeq->Alarm -= ElapsedTicks;
return FALSE;
}
// Time to start or advance the animation
pSeq->Change = TRUE;
if (pSeq->Direction == NO_DIR)
{ // just starting now
pSeq->FramesLeft = ADPtr->NumFrames;
// Both INTRO and DONE may be set at the same time,
// when e.g. Zoq and Pik are taking turns to talk
// Process the DONE transition first to go into
// a neutral state before switching over.
if (CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)
{
pSeq->Direction = DOWN_DIR;
pSeq->CurIndex = ADPtr->NumFrames - 1;
}
else if (CommData.AlienTransitionDesc.AnimFlags & TALK_INTRO)
{
pSeq->Direction = UP_DIR;
// It's now safe to pick up new Transition descriptor if changed
// (e.g. Zoq and Pik taking turns to talk)
if (CommData.AlienTransitionDesc.StartIndex
!= ADPtr->StartIndex)
{ // copy the new one
*ADPtr = CommData.AlienTransitionDesc;
}
pSeq->CurIndex = 0;
}
}
--pSeq->FramesLeft;
if (pSeq->FramesLeft == 0)
{ // animation is done
if (pSeq->Direction == UP_DIR)
{ // done with TALK_INTRO transition
CommData.AlienTransitionDesc.AnimFlags &= ~TALK_INTRO;
}
else if (pSeq->Direction == DOWN_DIR)
{ // done with TALK_DONE transition
CommData.AlienTransitionDesc.AnimFlags &= ~TALK_DONE;
// Done with all transition frames
ADPtr->AnimFlags |= ANIM_DISABLED;
done = TRUE;
}
pSeq->Direction = NO_DIR;
}
else
{ // next frame
pSeq->Alarm = randomFrameRate (pSeq);
pSeq->CurIndex += pSeq->Direction;
}
return done;
}
void
InitCommAnimations (void)
{
ActiveMask = 0;
TalkDesc = CommData.AlienTalkDesc;
TransitDesc = CommData.AlienTransitionDesc;
// Animation sequences have to be drawn in reverse, and
// talk animations have to be drawn last (so we add them first)
TotalSequences = 0;
// Transition animation last
Transit = Sequences + TotalSequences;
SetupTalkSequence (Transit, &TransitDesc);
++TotalSequences;
// Talk animation second last
Talk = Sequences + TotalSequences;
SetupTalkSequence (Talk, &TalkDesc);
++TotalSequences;
FirstAmbient = TotalSequences;
SetupAmbientSequences (Sequences + FirstAmbient, CommData.NumAnimations);
TotalSequences += CommData.NumAnimations;
LastTime = GetTimeCounter ();
}
BOOLEAN
ProcessCommAnimations (BOOLEAN FullRedraw, BOOLEAN paused)
{
if (paused)
{ // Drive colormap xforms and nothing else
XFormColorMap_step ();
return FALSE;
}
else
{
COUNT i;
SEQUENCE *pSeq;
BOOLEAN Change;
BOOLEAN CanTalk = TRUE;
TimeCount CurTime;
DWORD ElapsedTicks;
DWORD NextActiveMask;
CurTime = GetTimeCounter ();
ElapsedTicks = CurTime - LastTime;
LastTime = CurTime;
// Process ambient animations
NextActiveMask = ActiveMask;
pSeq = Sequences + FirstAmbient;
for (i = 0; i < CommData.NumAnimations; ++i, ++pSeq)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
DWORD ActiveBit = 1L << i;
if (ADPtr->AnimFlags & ANIM_DISABLED)
continue;
if (pSeq->Direction == NO_DIR)
{ // animation is paused
if (!conflictsWithTalkingAnim (pSeq))
{ // start it up
pSeq->Direction = UP_DIR;
}
}
else if (pSeq->Alarm > ElapsedTicks)
{ // not time yet
pSeq->Alarm -= ElapsedTicks;
}
else if (ActiveMask & ADPtr->BlockMask)
{ // animation is blocked
assert (!(ActiveMask & ActiveBit) &&
"Check animations' mutual blocking masks");
assert (animAtNeutralIndex (pSeq));
// reschedule
pSeq->Alarm = randomRestartRate (pSeq) + 1;
continue;
}
else
{ // Time to start or advance the animation
if (AdvanceAmbientSequence (pSeq))
{ // Animation is active this frame and the next
ActiveMask |= ActiveBit;
NextActiveMask |= ActiveBit;
}
else
{ // Animation remains active this frame but not the next
// This keeps any conflicting animations (BlockMask)
// from activating in the same frame and scribbling over
// our last image.
NextActiveMask &= ~ActiveBit;
}
}
if (pSeq->AnimType == PICTURE_ANIM && pSeq->Direction != NO_DIR
&& conflictsWithTalkingAnim (pSeq))
{
// We want to talk, but this is a running picture animation
// which conflicts with the talking animation
// See if it is safe to stop it now.
if (animAtNeutralIndex (pSeq))
{ // pause the animation
pSeq->Direction = NO_DIR;
NextActiveMask &= ~ActiveBit;
// Talk animation is drawn last, so it's not a conflict
// for this frame. The talk animation will be drawn
// over the neutral frame.
}
else
{ // Otherwise, let the animation run until it's safe
CanTalk = FALSE;
}
}
}
// All ambient animations have been processed. Advance the mask.
ActiveMask = NextActiveMask;
// Process the talking and transition animations
if (CanTalk && haveTalkingAnim () && runningTalkingAnim ())
{
BOOLEAN done = FALSE;
if (signaledStopTalkingAnim () && haveTransitionAnim ())
{ // Run the transition. We will clear everything
// when it is done
CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE;
}
if (CommData.AlienTransitionDesc.AnimFlags
& (TALK_INTRO | TALK_DONE))
{ // Transitioning in or out of talking
if ((CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)
&& Transit->Direction == NO_DIR)
{ // This is needed when switching talking anims
ResetSequence (Talk);
}
done = AdvanceTransitSequence (Transit, ElapsedTicks);
}
else if (!signaledStopTalkingAnim ())
{ // Talking, transition is done
AdvanceTalkingSequence (Talk, ElapsedTicks);
}
else
{ // Not talking
ResetSequence (Talk);
done = TRUE;
}
if (signaledStopTalkingAnim () && done)
{
clearRunTalkingAnim ();
clearStopTalkingAnim ();
}
}
else
{ // Not talking -- disable talking anim if it is done
if (Talk->Direction == NO_DIR)
TalkDesc.AnimFlags |= ANIM_DISABLED;
}
BatchGraphics ();
// Draw all animations
{
BOOLEAN ColorChange = XFormColorMap_step ();
if (ColorChange)
FullRedraw = TRUE;
// Colormap animations are processed separately
// from picture anims (see XFormColorMap_step)
ProcessColormapAnims (Sequences + FirstAmbient,
CommData.NumAnimations);
Change = DrawAlienFrame (Sequences, TotalSequences, FullRedraw);
if (FullRedraw)
Change = TRUE;
}
UnbatchGraphics ();
// Post-process ambient animations
pSeq = Sequences + FirstAmbient;
for (i = 0; i < CommData.NumAnimations; ++i, ++pSeq)
{
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
DWORD ActiveBit = 1L << i;
if (ADPtr->AnimFlags & ANIM_DISABLED)
continue;
// We can only disable a one-shot anim here, otherwise the
// last frame will not be drawn
if ((ADPtr->AnimFlags & ONE_SHOT_ANIM)
&& !(NextActiveMask & ActiveBit))
{ // One-shot animation, inactive next frame
ADPtr->AnimFlags |= ANIM_DISABLED;
}
}
return Change;
}
}
BOOLEAN
DrawAlienFrame (SEQUENCE *Sequences, COUNT Num, BOOLEAN fullRedraw)
{
int i;
STAMP s;
BOOLEAN Change = FALSE;
BatchGraphics ();
s.origin.x = -SAFE_X;
s.origin.y = 0;
if (fullRedraw)
{
// Draw the main frame
s.frame = CommData.AlienFrame;
DrawStamp (&s);
// Draw any static frames (has to be in reverse)
for (i = CommData.NumAnimations - 1; i >= 0; --i)
{
ANIMATION_DESC *ADPtr = &CommData.AlienAmbientArray[i];
if (ADPtr->AnimFlags & ANIM_MASK)
continue;
ADPtr->AnimFlags |= ANIM_DISABLED;
if (!(ADPtr->AnimFlags & COLORXFORM_ANIM))
{ // It's a static frame (e.g. Flagship picture at Starbase)
s.frame = SetAbsFrameIndex (CommData.AlienFrame,
ADPtr->StartIndex);
DrawStamp (&s);
}
}
}
if (Sequences)
{ // Draw the animation sequences (has to be in reverse)
for (i = Num - 1; i >= 0; --i)
{
SEQUENCE *pSeq = &Sequences[i];
ANIMATION_DESC *ADPtr = pSeq->ADPtr;
if ((ADPtr->AnimFlags & ANIM_DISABLED)
|| pSeq->AnimType != PICTURE_ANIM)
continue;
// Draw current animation frame only if changed
if (!fullRedraw && !pSeq->Change)
continue;
s.frame = SetAbsFrameIndex (CommData.AlienFrame,
ADPtr->StartIndex + pSeq->CurIndex);
DrawStamp (&s);
pSeq->Change = FALSE;
Change = TRUE;
}
}
UnbatchGraphics ();
return Change;
}