diff options
Diffstat (limited to 'src/uqm/commanim.c')
-rw-r--r-- | src/uqm/commanim.c | 623 |
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; +} |