summaryrefslogtreecommitdiff
path: root/src/uqm/commanim.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/commanim.c')
-rw-r--r--src/uqm/commanim.c623
1 files changed, 623 insertions, 0 deletions
diff --git a/src/uqm/commanim.c b/src/uqm/commanim.c
new file mode 100644
index 0000000..02e9362
--- /dev/null
+++ b/src/uqm/commanim.c
@@ -0,0 +1,623 @@
+//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;
+}