From 39bde10b0c786169828117ab4d45b6ae94e5b5ac Mon Sep 17 00:00:00 2001 From: avolkov Date: Sun, 15 Nov 2009 06:43:03 +0000 Subject: [PATCH] Comm animation processing rewrite; cleaner and meaner; a bunch of bugs fixed, and some probably added ;) git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@3305 8092fc87-c524-0410-9efc-e669fe64eaf9 --- sc2/ChangeLog | 1 + sc2/src/uqm/comm.c | 163 ++---- sc2/src/uqm/comm.h | 71 ++- sc2/src/uqm/comm/orz/orzc.c | 9 +- sc2/src/uqm/comm/starbas/starbas.c | 5 +- sc2/src/uqm/comm/zoqfot/zoqfotc.c | 10 +- sc2/src/uqm/commanim.c | 866 ++++++++++++++++------------- sc2/src/uqm/commanim.h | 93 +++- sc2/src/uqm/globdata.h | 54 +- 9 files changed, 706 insertions(+), 566 deletions(-) diff --git a/sc2/ChangeLog b/sc2/ChangeLog index ea8b783e2..e66308034 100644 --- a/sc2/ChangeLog +++ b/sc2/ChangeLog @@ -1,4 +1,5 @@ Changes towards version 0.7: +- Comm animation processing rewrite, bugs fixed - Alex - Added graphics context debugging function - SvdB - Thread down-throttling and game sleep when inactive (currently disabled), (bug #1070), from Flandry diff --git a/sc2/src/uqm/comm.c b/sc2/src/uqm/comm.c index 8b5ce1d7e..f8b1649f3 100644 --- a/sc2/src/uqm/comm.c +++ b/sc2/src/uqm/comm.c @@ -53,6 +53,8 @@ LOCDATA CommData; int cur_comm; UNICODE shared_phrase_buf[2048]; +static BOOLEAN TalkingFinished; + typedef struct response_entry { RESPONSE_REF response_ref; @@ -87,7 +89,6 @@ static const UNICODE * volatile last_subtitle; static CONTEXT TextCacheContext; static FRAME TextCacheFrame; -volatile BOOLEAN ClearSummary; RECT CommWndRect = { // default values; actually inited by HailAlien() @@ -382,51 +383,6 @@ DrawSISComWindow (void) } } -void -DrawAlienFrame (FRAME aframe, SEQUENCE *pSeq) -{ - COUNT i; - STAMP s; - ANIMATION_DESC *ADPtr; - - s.origin.x = -SAFE_X; - s.origin.y = 0; - s.frame = CommData.AlienFrame; - if (s.frame == 0) - s.frame = aframe; - - BatchGraphics (); - DrawStamp (&s); - i = CommData.NumAnimations; - ADPtr = &CommData.AlienAmbientArray[i]; - while (i--) - { - --ADPtr; - - if (!(ADPtr->AnimFlags & ANIM_MASK)) - { - s.frame = SetAbsFrameIndex (s.frame, ADPtr->StartIndex); - DrawStamp (&s); - ADPtr->AnimFlags |= ANIM_DISABLED; - } - else if (pSeq) - { - if (pSeq->AnimType == PICTURE_ANIM) - { - s.frame = pSeq->AnimObj.CurFrame; - DrawStamp (&s); - } - --pSeq; - } - } - if (aframe && CommData.AlienFrame && aframe != CommData.AlienFrame) - { - s.frame = aframe; - DrawStamp (&s); - } - UnbatchGraphics (); -} - void init_communication (void) { @@ -553,13 +509,11 @@ SpewPhrases (COUNT wait_track) BOOLEAN ContinuityBreak; DWORD TimeIn; COUNT which_track; - FRAME F; BOOLEAN rewind = FALSE; TimeIn = GetTimeCounter (); ContinuityBreak = FALSE; - F = CommData.AlienFrame; if (wait_track == 0) { // Restarting with a rewind wait_track = (COUNT)~0; @@ -636,12 +590,10 @@ SpewPhrases (COUNT wait_track) else if (optSmoothScroll == OPT_3DO) FastForward_Smooth (); ContinuityBreak = TRUE; - // XXX: Ugly hack: This causes all animations (talking and ambient) + // XXX: This causes all animations (talking and ambient) // in ambient_anim_task to stop progressing. I see no reason why - // the animations cannot continue while seeking. This hack has - // spawned a multitude of workarounds in the comm code, and IMHO - // should be removed. - CommData.AlienFrame = 0; + // the animations cannot continue while seeking. + PauseAnimTask = TRUE; } else if (left || rewind) { @@ -652,8 +604,8 @@ SpewPhrases (COUNT wait_track) else if (optSmoothScroll == OPT_3DO) FastReverse_Smooth (); ContinuityBreak = TRUE; - // XXX: See ugly hack discussion above - CommData.AlienFrame = 0; + // XXX: See pause discussion above + PauseAnimTask = TRUE; } else if (ContinuityBreak) { @@ -663,20 +615,20 @@ SpewPhrases (COUNT wait_track) SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2)); } else - { // XXX: See ugly hack discussion above + { // XXX: See pause discussion above // Additionally, this used to have a buggy guard condition, which // would cause the animations to remain paused in a couple cases // after seeking back to the beginning. // Broken cases were: Syreen "several hours later" and Starbase // VUX Beast analysis by the scientist. - CommData.AlienFrame = F; + PauseAnimTask = FALSE; } which_track = PlayingTrack (); } while (ContinuityBreak || (which_track && which_track <= wait_track)); - CommData.AlienFrame = F; + PauseAnimTask = FALSE; ClearSubtitles (); if (!which_track || wait_track == (COUNT)~0) @@ -696,22 +648,17 @@ DoTalkSegue (COUNT wait_track) BOOLEAN done; // Transition animation to talking state, if necessary - if (CommData.AlienTalkDesc.NumFrames) - { - if (!(CommData.AlienTransitionDesc.AnimFlags & TALK_INTRO)) - { - CommData.AlienTransitionDesc.AnimFlags |= TALK_INTRO; - if (CommData.AlienTransitionDesc.NumFrames) - CommData.AlienTalkDesc.AnimFlags |= TALK_INTRO; - } + if (wantTalkingAnim () && haveTalkingAnim ()) + { + if (haveTransitionAnim ()) + setRunIntroAnim (); - CommData.AlienTransitionDesc.AnimFlags &= ~PAUSE_TALKING; - if (CommData.AlienTalkDesc.NumFrames) - CommData.AlienTalkDesc.AnimFlags |= WAIT_TALKING; - while (CommData.AlienTalkDesc.AnimFlags & TALK_INTRO) + setRunTalkingAnim (); + + while (runningIntroAnim ()) { // wait until the transition finishes UnlockMutex (GraphicsLock); - TaskSwitch (); + SleepThread (ONE_SECOND / 30); LockMutex (GraphicsLock); } } @@ -719,14 +666,8 @@ DoTalkSegue (COUNT wait_track) done = !SpewPhrases (wait_track); // transition back to silent, if necessary - if (CommData.AlienTalkDesc.NumFrames) - { - // must set the TALK_DONE flag so that the animation task - // releases WAIT_TALKING from AlienTalkDesc - CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE; - if ((CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING)) - CommData.AlienTalkDesc.AnimFlags |= PAUSE_TALKING; - } + if (runningTalkingAnim ()) + setStopTalkingAnim (); return done; } @@ -734,13 +675,15 @@ DoTalkSegue (COUNT wait_track) static void FlushTalkSegue (void) { + WaitForNoInput (ONE_SECOND / 2); FlushInput (); - while (AnyButtonPress (TRUE)) - TaskSwitch (); + // Wait until the animation task stops "talking" do - TaskSwitch (); - while (CommData.AlienTalkDesc.AnimFlags & PAUSE_TALKING); + SleepThread (ONE_SECOND / 30); + while (runningTalkingAnim ()); + + TalkingFinished = TRUE; } void @@ -749,8 +692,7 @@ AlienTalkSegue (COUNT wait_track) BOOLEAN done; // this skips any talk segues that follow an aborted one - if ((GLOBAL (CurrentActivity) & CHECK_ABORT) - || (CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)) + if ((GLOBAL (CurrentActivity) & CHECK_ABORT) || TalkingFinished) return; LockMutex (GraphicsLock); @@ -758,7 +700,7 @@ AlienTalkSegue (COUNT wait_track) if (!pCurInputState->Initialized) { SetColorMap (GetColorMapAddress (CommData.AlienColorMap)); - DrawAlienFrame (CommData.AlienFrame, NULL); + DrawAlienFrame (NULL, 0, TRUE); UpdateSpeechGraphics (TRUE); if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE @@ -807,8 +749,9 @@ AlienTalkSegue (COUNT wait_track) DWORD TimeOut; TimeOut = GetTimeCounter () + (ONE_SECOND >> 1); -/* if (CommData.NumAnimations) */ - pCurInputState->AnimTask = StartCommAnimTask (); + // Anim task processes not only ambient animations, but also + // talking animations and subtitles + pCurInputState->AnimTask = StartCommAnimTask (); UnlockMutex (GraphicsLock); SleepThreadUntil (TimeOut); @@ -825,15 +768,9 @@ AlienTalkSegue (COUNT wait_track) UnlockMutex (GraphicsLock); FlushTalkSegue (); - if (done || wait_track == (COUNT)~0) - { // all done talking here - CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE; - } - else + if (!done && wait_track != (COUNT)~0) { // there is more to come - CommData.AlienTransitionDesc.AnimFlags &= ~TALK_DONE; - // allow a transition to talking state again later - CommData.AlienTransitionDesc.AnimFlags &= ~TALK_INTRO; + TalkingFinished = FALSE; } } @@ -1000,7 +937,7 @@ SelectResponse (ENCOUNTER_STATE *pES) FadeMusic (BACKGROUND_VOL, ONE_SECOND); - CommData.AlienTransitionDesc.AnimFlags &= ~(TALK_INTRO | TALK_DONE); + TalkingFinished = FALSE; pES->num_responses = 0; (*pES->response_list[pES->cur_response].response_func) (pES->response_list[pES->cur_response].response_ref); @@ -1024,7 +961,7 @@ SelectConversationSummary (ENCOUNTER_STATE *pES) LockMutex (GraphicsLock); RefreshResponses (pES); - ClearSummary = TRUE; + clear_subtitles = TRUE; PauseAnimTask = FALSE; UnlockMutex (GraphicsLock); } @@ -1060,9 +997,7 @@ PlayerResponseInput (ENCOUNTER_STATE *pES) FadeMusic (BACKGROUND_VOL, ONE_SECOND); LockMutex (GraphicsLock); FeedbackPlayerPhrase (pES->phrase_buf); - // reset transition state - CommData.AlienTransitionDesc.AnimFlags &= - ~(TALK_INTRO | TALK_DONE); + TalkingFinished = FALSE; DoTalkSegue (0); if (!(GLOBAL (CurrentActivity) & CHECK_ABORT)) @@ -1073,9 +1008,6 @@ PlayerResponseInput (ENCOUNTER_STATE *pES) UnlockMutex (GraphicsLock); FlushTalkSegue (); - // done one way or the other - CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE; - } else if (PulsedInputState.menu[KEY_MENU_UP]) response = (BYTE)((response + (BYTE)(pES->num_responses - 1)) @@ -1119,7 +1051,8 @@ DoCommunication (ENCOUNTER_STATE *pES) { SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN, MENU_SOUND_SELECT); - if (!(CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)) + // First, finish playing all queued tracks if not done yet + if (!TalkingFinished) AlienTalkSegue ((COUNT)~0); if (GLOBAL (CurrentActivity) & CHECK_ABORT) @@ -1141,14 +1074,10 @@ DoCommunication (ENCOUNTER_STATE *pES) { FadeMusic (BACKGROUND_VOL, ONE_SECOND); LockMutex (GraphicsLock); - // reset transition state - CommData.AlienTransitionDesc.AnimFlags &= - ~(TALK_INTRO | TALK_DONE); + TalkingFinished = FALSE; DoTalkSegue (0); UnlockMutex (GraphicsLock); FlushTalkSegue (); - // done one way or the other - CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE; if (GLOBAL (CurrentActivity) & CHECK_ABORT) break; @@ -1172,7 +1101,6 @@ DoCommunication (ENCOUNTER_STATE *pES) LockMutex (GraphicsLock); pES->AnimTask = 0; } - CommData.AlienTransitionDesc.AnimFlags &= ~(TALK_INTRO | TALK_DONE); SetContext (SpaceContext); DestroyContext (TaskContext); @@ -1243,6 +1171,8 @@ HailAlien (void) pCurInputState = &ES; memset (pCurInputState, 0, sizeof (*pCurInputState)); + TalkingFinished = FALSE; + ES.InputFunc = DoCommunication; PlayerFont = LoadFont (PLAYER_FONT); @@ -1443,7 +1373,9 @@ InitCommunication (CONVERSATION which_comm) status != YEHAT_REBEL_SHIP ? which_comm : YEHAT_REBEL_CONVERSATION); if (LocDataPtr) + { // We make a copy here CommData = *LocDataPtr; + } UnlockMutex (GraphicsLock); @@ -1717,3 +1649,12 @@ CheckSubtitles (void) } UnlockMutex (subtitle_mutex); } + +void +EnableTalkingAnim (BOOLEAN enable) +{ + if (enable) + CommData.AlienTalkDesc.AnimFlags &= ~PAUSE_TALKING; + else + CommData.AlienTalkDesc.AnimFlags |= PAUSE_TALKING; +} diff --git a/sc2/src/uqm/comm.h b/sc2/src/uqm/comm.h index b1ed4d6c1..a5b83bfbe 100644 --- a/sc2/src/uqm/comm.h +++ b/sc2/src/uqm/comm.h @@ -28,10 +28,73 @@ #include "commanim.h" -extern void DrawAlienFrame (FRAME aframe, SEQUENCE *pSeq); - extern LOCDATA CommData; -extern volatile BOOLEAN ClearSummary; + +static inline BOOLEAN +haveTalkingAnim (void) +{ + return CommData.AlienTalkDesc.NumFrames > 0; +} + +static inline BOOLEAN +haveTransitionAnim (void) +{ + return CommData.AlienTransitionDesc.NumFrames > 0; +} + +static inline BOOLEAN +wantTalkingAnim (void) +{ + return !(CommData.AlienTalkDesc.AnimFlags & PAUSE_TALKING); +} + +static inline void +setRunTalkingAnim (void) +{ + CommData.AlienTalkDesc.AnimFlags |= WAIT_TALKING; +} + +static inline void +clearRunTalkingAnim (void) +{ + CommData.AlienTalkDesc.AnimFlags &= ~WAIT_TALKING; +} + +static inline BOOLEAN +runningTalkingAnim (void) +{ + return (CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING); +} + +static inline void +setRunIntroAnim (void) +{ + CommData.AlienTransitionDesc.AnimFlags |= TALK_INTRO; +} + +static inline BOOLEAN +runningIntroAnim (void) +{ + return (CommData.AlienTransitionDesc.AnimFlags & TALK_INTRO); +} + +static inline void +setStopTalkingAnim (void) +{ + CommData.AlienTalkDesc.AnimFlags |= TALK_DONE; +} + +static inline void +clearStopTalkingAnim (void) +{ + CommData.AlienTalkDesc.AnimFlags &= ~TALK_DONE; +} + +static inline BOOLEAN +signaledStopTalkingAnim (void) +{ + return CommData.AlienTalkDesc.AnimFlags & TALK_DONE; +} #endif @@ -45,6 +108,8 @@ extern BOOLEAN HaveSubtitlesChanged (void); extern RECT CommWndRect; /* comm window rect */ +extern void EnableTalkingAnim (BOOLEAN enable); + #endif /* _COMM_H */ diff --git a/sc2/src/uqm/comm/orz/orzc.c b/sc2/src/uqm/comm/orz/orzc.c index 7ca45159d..43a0526c4 100644 --- a/sc2/src/uqm/comm/orz/orzc.c +++ b/sc2/src/uqm/comm/orz/orzc.c @@ -838,14 +838,13 @@ Intro (void) if (!GET_GAME_STATE (MET_ORZ_BEFORE)) { - BYTE N; - SET_GAME_STATE (MET_ORZ_BEFORE, 1); - N = CommData.AlienTalkDesc.NumFrames; - CommData.AlienTalkDesc.NumFrames = 0; + // Disable talking anim and run the computer report + EnableTalkingAnim (FALSE); AlienTalkSegue (1); - CommData.AlienTalkDesc.NumFrames = N; + // Run whatever is left with talking anim + EnableTalkingAnim (TRUE); } } diff --git a/sc2/src/uqm/comm/starbas/starbas.c b/sc2/src/uqm/comm/starbas/starbas.c index 3525d954e..2e21d9342 100644 --- a/sc2/src/uqm/comm/starbas/starbas.c +++ b/sc2/src/uqm/comm/starbas/starbas.c @@ -1291,11 +1291,10 @@ DiscussDevices (BOOLEAN TalkAbout) // Run all tracks upto the Vux Beast scientist's report AlienTalkSegue (VuxBeastIndex - 1); // Disable Commander's speech animation and run the report - i = CommData.AlienTalkDesc.NumFrames; - CommData.AlienTalkDesc.NumFrames = 0; + EnableTalkingAnim (FALSE); AlienTalkSegue (VuxBeastIndex); // Enable Commander's speech animation and run the rest - CommData.AlienTalkDesc.NumFrames = i; + EnableTalkingAnim (TRUE); AlienTalkSegue ((COUNT)~0); } } diff --git a/sc2/src/uqm/comm/zoqfot/zoqfotc.c b/sc2/src/uqm/comm/zoqfot/zoqfotc.c index eda47ad8f..cb083f744 100644 --- a/sc2/src/uqm/comm/zoqfot/zoqfotc.c +++ b/sc2/src/uqm/comm/zoqfot/zoqfotc.c @@ -123,9 +123,12 @@ SelectAlienZOQ (void) { if (LastAlien != ZOQ_ALIEN) { + // Transition to neutral state first if Pik was talking + if (LastAlien != FOT_ALIEN) + CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE; LastAlien = ZOQ_ALIEN; + CommData.AlienTransitionDesc.AnimFlags |= TALK_INTRO; CommData.AlienTransitionDesc.StartIndex = FOT_TO_ZOQ; - CommData.AlienTalkDesc.AnimFlags |= TALK_INTRO; CommData.AlienTalkDesc.StartIndex = ZOQ_TALK_INDEX; CommData.AlienTalkDesc.NumFrames = ZOQ_TALK_FRAMES; CommData.AlienAmbientArray[1].AnimFlags &= ~WAIT_TALKING; @@ -142,9 +145,12 @@ SelectAlienPIK (void) { if (LastAlien != PIK_ALIEN) { + // Transition to neutral state first if Zoq was talking + if (LastAlien != FOT_ALIEN) + CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE; LastAlien = PIK_ALIEN; + CommData.AlienTransitionDesc.AnimFlags |= TALK_INTRO; CommData.AlienTransitionDesc.StartIndex = FOT_TO_PIK; - CommData.AlienTalkDesc.AnimFlags |= TALK_INTRO; CommData.AlienTalkDesc.StartIndex = PIK_TALK_INDEX; CommData.AlienTalkDesc.NumFrames = PIK_TALK_FRAMES; CommData.AlienAmbientArray[1].AnimFlags |= WAIT_TALKING; diff --git a/sc2/src/uqm/commanim.c b/sc2/src/uqm/commanim.c index b472bbe32..d7a4f3cb4 100644 --- a/sc2/src/uqm/commanim.c +++ b/sc2/src/uqm/commanim.c @@ -44,118 +44,371 @@ static int ambient_anim_task (void *data); volatile BOOLEAN PauseAnimTask = FALSE; +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 -SetUpSequence (SEQUENCE *pSeq) +SetupAmbientSequences (SEQUENCE *pSeq, COUNT Num) { COUNT i; - ANIMATION_DESC *ADPtr; - - i = CommData.NumAnimations; - pSeq = &pSeq[i]; - ADPtr = &CommData.AlienAmbientArray[i]; - while (i--) + + for (i = 0; i < Num; ++i, ++pSeq) { - --ADPtr; - --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; - if (pSeq->AnimType == COLOR_ANIM) - pSeq->AnimObj.CurCMap = SetAbsColorMapIndex ( - CommData.AlienColorMap, ADPtr->StartIndex); - else - pSeq->AnimObj.CurFrame = SetAbsFrameIndex ( - CommData.AlienFrame, ADPtr->StartIndex); - + // Default: first frame is neutral if (ADPtr->AnimFlags & RANDOM_ANIM) - { - if (pSeq->AnimType == COLOR_ANIM) - pSeq->AnimObj.CurCMap = - SetRelColorMapIndex (pSeq->AnimObj.CurCMap, - (COUNT)((COUNT)TFB_Random () % pSeq->FramesLeft)); - else - pSeq->AnimObj.CurFrame = - SetRelFrameIndex (pSeq->AnimObj.CurFrame, - (COUNT)((COUNT)TFB_Random () % pSeq->FramesLeft)); + { // 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; - if (pSeq->AnimType == COLOR_ANIM) - pSeq->AnimObj.CurCMap = - SetRelColorMapIndex (pSeq->AnimObj.CurCMap, 1); - else - pSeq->AnimObj.CurFrame = - IncFrameIndex (pSeq->AnimObj.CurFrame); + } + 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 = ADPtr->BaseRestartRate - + ((COUNT)TFB_Random () % (ADPtr->RandomRestartRate + 1)) + 1; + 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 + { // animation ended + active = FALSE; + pSeq->Alarm = randomRestartRate (pSeq) + 1; + } + + // 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; +} + static int ambient_anim_task (void *data) { - SIZE TalkAlarm; - FRAME TalkFrame; - FRAME ResetTalkFrame = NULL; - FRAME TransitionFrame = NULL; - FRAME AnimFrame[MAX_ANIMATIONS]; COUNT i; - DWORD LastTime; - FRAME CommFrame; - SEQUENCE Sequencer[MAX_ANIMATIONS]; - ANIMATION_DESC TalkBuffer = CommData.AlienTalkDesc; + TimeCount LastTime; + SEQUENCE Sequences[MAX_ANIMATIONS + 2]; + // 2 extra for Talk and Transition animations SEQUENCE *pSeq; - ANIMATION_DESC *ADPtr; DWORD ActiveMask; // Bit mask of all animations that are currently active. // Bit 'i' is set if the animation with index 'i' is active. DWORD LastOscillTime; Task task = (Task) data; - BOOLEAN TransitionDone = FALSE; - BOOLEAN TalkFrameChanged = FALSE; - BOOLEAN FrameChanged[MAX_ANIMATIONS]; BOOLEAN ColorChange = FALSE; - while ((CommFrame = CommData.AlienFrame) == 0 - && !Task_ReadState (task, TASK_EXIT)) + ANIMATION_DESC TalkDesc; + ANIMATION_DESC TransitDesc; + SEQUENCE* Talk; + SEQUENCE* Transit; + COUNT FirstAmbient; + COUNT TotalSequences; + + while (!CommData.AlienFrame && !Task_ReadState (task, TASK_EXIT)) TaskSwitch (); - LockMutex (GraphicsLock); - memset (&DisplayArray[0], 0, sizeof (DisplayArray)); - SetUpSequence (Sequencer); - UnlockMutex (GraphicsLock); - ActiveMask = 0; - TalkAlarm = 0; - TalkFrame = 0; + + // Certain data must be accessed under GraphicsLock + LockMutex (GraphicsLock); + + memset (&DisplayArray[0], 0, sizeof (DisplayArray)); + 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; + + UnlockMutex (GraphicsLock); + LastTime = GetTimeCounter (); LastOscillTime = LastTime; - memset (FrameChanged, 0, sizeof (FrameChanged)); - memset (AnimFrame, 0, sizeof (AnimFrame)); - for (i = 0; i < CommData.NumAnimations; i++) - { - COUNT nextIndex; - - if (CommData.AlienAmbientArray[i].AnimFlags & YOYO_ANIM) - nextIndex = CommData.AlienAmbientArray[i].StartIndex; - else - nextIndex = (CommData.AlienAmbientArray[i].StartIndex - + CommData.AlienAmbientArray[i].NumFrames - 1); - AnimFrame[i] = SetAbsFrameIndex (CommFrame, nextIndex); - } while (!Task_ReadState (task, TASK_EXIT)) { - BOOLEAN Change, CanTalk; - DWORD CurTime, ElapsedTicks; + BOOLEAN CanTalk; + TimeCount CurTime; + DWORD ElapsedTicks; SleepThreadUntil (LastTime + ONE_SECOND / AMBIENT_ANIM_RATE); @@ -165,368 +418,153 @@ ambient_anim_task (void *data) ElapsedTicks = CurTime - LastTime; LastTime = CurTime; - Change = FALSE; - i = CommData.NumAnimations; - if (CommData.AlienFrame) - CanTalk = TRUE; + if (PauseAnimTask) + { // anims not processed + i = CommData.NumAnimations; + CanTalk = FALSE; + } else { i = 0; - CanTalk = FALSE; + CanTalk = TRUE; } - pSeq = &Sequencer[i]; - ADPtr = &CommData.AlienAmbientArray[i]; - while (i-- && !Task_ReadState (task, TASK_EXIT)) + // Process ambient animations + pSeq = Sequences + FirstAmbient; + for ( ; i < CommData.NumAnimations; ++i, ++pSeq) { - --ADPtr; - --pSeq; + ANIMATION_DESC *ADPtr = pSeq->ADPtr; + DWORD ActiveBit = 1L << i; + if (ADPtr->AnimFlags & ANIM_DISABLED) continue; + if (pSeq->Direction == NO_DIR) - { - if (!(ADPtr->AnimFlags - & CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING)) - { - // This animation does not conflict with the talking - // animation right now. + { // animation is paused + if (!conflictsWithTalkingAnim (pSeq)) + { // start it up pSeq->Direction = UP_DIR; } } - else if ((DWORD)pSeq->Alarm > ElapsedTicks) - pSeq->Alarm -= (COUNT)ElapsedTicks; + else if (pSeq->Alarm > ElapsedTicks) + { // not time yet + pSeq->Alarm -= ElapsedTicks; + } + else if (ActiveMask & ADPtr->BlockMask) + { // animation is blocked + assert (!(ActiveMask & ActiveBit)); + assert (animAtNeutralIndex (pSeq)); + // reschedule + pSeq->Alarm = randomRestartRate (pSeq) + 1; + continue; + } else - { - if (!(ActiveMask & ADPtr->BlockMask) - && (--pSeq->FramesLeft - || ((ADPtr->AnimFlags & YOYO_ANIM) - && pSeq->Direction == UP_DIR))) - { - ActiveMask |= 1L << i; - pSeq->Alarm = ADPtr->BaseFrameRate - + ((COUNT)TFB_Random () - % (ADPtr->RandomFrameRate + 1)) + 1; - } + { // Time to start or advance the animation + if (AdvanceAmbientSequence (pSeq)) + ActiveMask |= ActiveBit; else - { - ActiveMask &= ~(1L << i); - pSeq->Alarm = ADPtr->BaseRestartRate - + ((COUNT)TFB_Random () - % (ADPtr->RandomRestartRate + 1)) + 1; - if (ActiveMask & ADPtr->BlockMask) - continue; - } - - if (pSeq->AnimType == COLOR_ANIM) - { - XFormColorMap (GetColorMapAddress (pSeq->AnimObj.CurCMap), - (COUNT) (pSeq->Alarm - 1)); - } - else - { - Change = TRUE; - AnimFrame[i] = pSeq->AnimObj.CurFrame; - FrameChanged[i] = 1; - } - - if (pSeq->FramesLeft == 0) - { - pSeq->FramesLeft = (BYTE)(ADPtr->NumFrames - 1); - - if (pSeq->Direction == DOWN_DIR) - pSeq->Direction = UP_DIR; - else if (ADPtr->AnimFlags & YOYO_ANIM) - pSeq->Direction = DOWN_DIR; - else - { - ++pSeq->FramesLeft; - if (pSeq->AnimType == COLOR_ANIM) - pSeq->AnimObj.CurCMap = SetRelColorMapIndex ( - pSeq->AnimObj.CurCMap, - (SWORD) (-pSeq->FramesLeft)); - else - { - pSeq->AnimObj.CurFrame = SetRelFrameIndex ( - pSeq->AnimObj.CurFrame, - (SWORD) (-pSeq->FramesLeft)); - } - } - } - - if (ADPtr->AnimFlags & RANDOM_ANIM) - { - COUNT nextIndex = ADPtr->StartIndex + - (TFB_Random () % ADPtr->NumFrames); - - if (pSeq->AnimType == COLOR_ANIM) - pSeq->AnimObj.CurCMap = SetAbsColorMapIndex ( - pSeq->AnimObj.CurCMap, nextIndex); - else - pSeq->AnimObj.CurFrame = SetAbsFrameIndex ( - pSeq->AnimObj.CurFrame, nextIndex); - } - else if (pSeq->AnimType == COLOR_ANIM) - { - if (pSeq->Direction == UP_DIR) - pSeq->AnimObj.CurCMap = SetRelColorMapIndex ( - pSeq->AnimObj.CurCMap, 1); - else - pSeq->AnimObj.CurCMap = SetRelColorMapIndex ( - pSeq->AnimObj.CurCMap, -1); - } - else - { - if (pSeq->Direction == UP_DIR) - pSeq->AnimObj.CurFrame = - IncFrameIndex (pSeq->AnimObj.CurFrame); - else - pSeq->AnimObj.CurFrame = - DecFrameIndex (pSeq->AnimObj.CurFrame); - } + ActiveMask &= ~ActiveBit; } - if (pSeq->AnimType == PICTURE_ANIM - && (ADPtr->AnimFlags - & CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING) - && pSeq->Direction != NO_DIR) + if (pSeq->AnimType == PICTURE_ANIM && pSeq->Direction != NO_DIR + && conflictsWithTalkingAnim (pSeq)) { - // This is a picture animation which would conflict with - // the talking animation, and it is not stopped yet. - COUNT index; - - CanTalk = FALSE; - if (!(pSeq->Direction != UP_DIR - || (index = GetFrameIndex (pSeq->AnimObj.CurFrame)) > - ADPtr->StartIndex + 1 - || (index == ADPtr->StartIndex + 1 - && (ADPtr->AnimFlags & CIRCULAR_ANIM)))) + // 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; + ActiveMask &= ~ActiveBit; + } + else + { // Otherwise, let the animation run until it's safe + CanTalk = FALSE; + } } } - ADPtr = &CommData.AlienTalkDesc; - if (CanTalk - && ADPtr->NumFrames - && (ADPtr->AnimFlags & WAIT_TALKING) - && !(CommData.AlienTransitionDesc.AnimFlags & PAUSE_TALKING)) + // Process the talking and transition animations + if (CanTalk && haveTalkingAnim () && runningTalkingAnim ()) { BOOLEAN done = FALSE; - for (i = 0; i < CommData.NumAnimations; i++) - if (ActiveMask & (1L << i) - && CommData.AlienAmbientArray[i].AnimFlags & WAIT_TALKING) - done = TRUE; - if (!done) - { - if (ADPtr->StartIndex != TalkBuffer.StartIndex) - { - Change = TRUE; - ResetTalkFrame = SetAbsFrameIndex (CommFrame, - TalkBuffer.StartIndex); - TalkBuffer = CommData.AlienTalkDesc; - } - - if ((long)TalkAlarm > (long)ElapsedTicks) - TalkAlarm -= (SIZE)ElapsedTicks; - else - { - BYTE AFlags; - SIZE FrameRate; - - if (TalkAlarm > 0) - TalkAlarm = 0; - else - TalkAlarm = -1; - - AFlags = ADPtr->AnimFlags; - if (!(AFlags & (TALK_INTRO | TALK_DONE))) - { - FrameRate = - ADPtr->BaseFrameRate - + ((COUNT)TFB_Random () - % (ADPtr->RandomFrameRate + 1)); - if (TalkAlarm < 0 - || GetFrameIndex (TalkFrame) == - ADPtr->StartIndex) - { - TalkFrame = SetAbsFrameIndex (CommFrame, - (COUNT) (ADPtr->StartIndex + 1 - + ((COUNT)TFB_Random () - % (ADPtr->NumFrames - 1)))); - FrameRate += ADPtr->BaseRestartRate - + ((COUNT)TFB_Random () - % (ADPtr->RandomRestartRate + 1)); - } - else - { - TalkFrame = SetAbsFrameIndex (CommFrame, - ADPtr->StartIndex); - if (ADPtr->AnimFlags & PAUSE_TALKING) - { - if (!(CommData.AlienTransitionDesc.AnimFlags - & TALK_DONE)) - { - CommData.AlienTransitionDesc.AnimFlags |= - PAUSE_TALKING; - ADPtr->AnimFlags &= ~PAUSE_TALKING; - } - else if (CommData.AlienTransitionDesc.NumFrames) - ADPtr->AnimFlags |= TALK_DONE; - else - ADPtr->AnimFlags &= - ~(WAIT_TALKING | PAUSE_TALKING); - - FrameRate = 0; - } - } - } - else - { - ADPtr = &CommData.AlienTransitionDesc; - if (AFlags & TALK_INTRO) - { - FrameRate = ADPtr->BaseFrameRate - + ((COUNT)TFB_Random () - % (ADPtr->RandomFrameRate + 1)); - if (TalkAlarm < 0 || TransitionDone) - { - TalkFrame = SetAbsFrameIndex (CommFrame, - ADPtr->StartIndex); - TransitionDone = FALSE; - } - else - TalkFrame = IncFrameIndex (TalkFrame); - - if ((BYTE)(GetFrameIndex (TalkFrame) - - ADPtr->StartIndex + 1) == - ADPtr->NumFrames) - { - CommData.AlienTalkDesc.AnimFlags &= - ~TALK_INTRO; - TransitionDone = TRUE; - } - TransitionFrame = TalkFrame; - } - else /* if (AFlags & TALK_DONE) */ - { - FrameRate = ADPtr->BaseFrameRate - + ((COUNT)TFB_Random () - % (ADPtr->RandomFrameRate + 1)); - if (TalkAlarm < 0) - TalkFrame = SetAbsFrameIndex (CommFrame, - (COUNT) (ADPtr->StartIndex - + ADPtr->NumFrames - 1)); - else - TalkFrame = DecFrameIndex (TalkFrame); - - if (GetFrameIndex (TalkFrame) == - ADPtr->StartIndex) - { - CommData.AlienTalkDesc.AnimFlags &= - ~(PAUSE_TALKING | TALK_DONE); - if (ADPtr->AnimFlags & TALK_INTRO) - CommData.AlienTalkDesc.AnimFlags &= - ~WAIT_TALKING; - else - { - ADPtr->AnimFlags |= PAUSE_TALKING; - ADPtr->AnimFlags &= ~TALK_DONE; - } - FrameRate = 0; - } - TransitionFrame = NULL; - } - } - TalkFrameChanged = TRUE; - - Change = TRUE; - - TalkAlarm = FrameRate; - } + if (signaledStopTalkingAnim () && haveTransitionAnim ()) + { // Run the transition. We will clear everything + // when it is done + CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE; } - else if (done && (ADPtr->AnimFlags & PAUSE_TALKING)) + + 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) { - ADPtr->AnimFlags &= ~(WAIT_TALKING | PAUSE_TALKING); + clearRunTalkingAnim (); + clearStopTalkingAnim (); } } + else + { // Not talking (task may be paused) + // Disable talking anim if it is done + if (Talk->Direction == NO_DIR) + TalkDesc.AnimFlags |= ANIM_DISABLED; + } + // Draw all animations if (!PauseAnimTask) { CONTEXT OldContext; - BOOLEAN CheckSub = FALSE; BOOLEAN SubtitleChange; + BOOLEAN FullRedraw; + BOOLEAN Change; // Clearing any active subtitles counts as 'change' SubtitleChange = HaveSubtitlesChanged (); + FullRedraw = ColorChange || SubtitleChange; OldContext = SetContext (TaskContext); - if (ColorChange || ClearSummary) - { // Redraw the whole comm screen - FRAME F; - F = CommData.AlienFrame; - CommData.AlienFrame = CommFrame; - DrawAlienFrame (TalkFrame, - &Sequencer[CommData.NumAnimations - 1]); - CommData.AlienFrame = F; - CheckSub = TRUE; - SubtitleChange = ClearSummary; - ColorChange = FALSE; - ClearSummary = FALSE; - } - if (Change || SubtitleChange) - { - STAMP s; - s.origin.x = -SAFE_X; - s.origin.y = 0; - if (SubtitleChange) - { - s.frame = CommFrame; - DrawStamp (&s); - } - i = CommData.NumAnimations; - while (i--) - { - if (SubtitleChange || FrameChanged[i]) - { - s.frame = AnimFrame[i]; - DrawStamp (&s); - FrameChanged[i] = 0; - } - } - if (SubtitleChange && TransitionFrame) - { - s.frame = TransitionFrame; - DrawStamp (&s); - } - if (ResetTalkFrame) - { - s.frame = ResetTalkFrame; - DrawStamp (&s); - ResetTalkFrame = NULL; - } - if (TalkFrame && TalkFrameChanged) - { - s.frame = TalkFrame; - DrawStamp (&s); - TalkFrameChanged = FALSE; - } - Change = FALSE; - CheckSub = TRUE; - } + // Colormap animations are processed separately + // from picture anims (see XFormColorMap_step) + ProcessColormapAnims (Sequences + FirstAmbient, + CommData.NumAnimations); - if (CheckSub) + Change = DrawAlienFrame (Sequences, TotalSequences, FullRedraw); + if (FullRedraw || Change) RedrawSubtitles (); SetContext (OldContext); + + ColorChange = FALSE; } + if (LastOscillTime + (ONE_SECOND / OSCILLOSCOPE_RATE) < CurTime) { LastOscillTime = CurTime; UpdateSpeechGraphics (FALSE); } + UnbatchGraphics (); UnlockMutex (GraphicsLock); + ColorChange = XFormColorMap_step (); } FinishTask (task); @@ -539,4 +577,68 @@ StartCommAnimTask (void) return AssignTask (ambient_anim_task, 3072, "ambient animations"); } +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; +} diff --git a/sc2/src/uqm/commanim.h b/sc2/src/uqm/commanim.h index ec84bdfe2..7adfcf1ab 100644 --- a/sc2/src/uqm/commanim.h +++ b/sc2/src/uqm/commanim.h @@ -21,14 +21,81 @@ #include "libs/gfxlib.h" #include "libs/tasklib.h" +// Some background: every animation has a neutral frame which returns +// the image to the state it was in before the animation began. Which +// frame is neutral depends on the animation type. +// Animation types: +#define RANDOM_ANIM (1 << 0) + // The next index is randomly chosen. + // The first frame is neutral +#define CIRCULAR_ANIM (1 << 1) + // After the last index has been reached, the animation starts over. + // The last frame is neutral. This complicates the animation task. +#define YOYO_ANIM (1 << 2) + // After the last index has been reached, the order that the + // animation frames are used is reversed. + // The first frame is neutral +#define ANIM_MASK (RANDOM_ANIM | CIRCULAR_ANIM | YOYO_ANIM) + // Mask of all animation types. + // Static frames do not have any of these flags set. + +#define WAIT_TALKING (1 << 3) + // This is set in AlienTalkDesc when the talking animation is active + // or should be active. + // In AlienAmbientArray, this is set for those ambient animations + // which can not be active while the talking animation is active. + // Such animations stop at the end of the current animation cycle + // when the talking animation activates. +#define PAUSE_TALKING (1 << 4) + // Set in AlienTalkDesc when we do not want the talking animation +#define TALK_INTRO (1 << 5) + // In AlienTransitionDesc: indicates a transition to talking state +#define TALK_DONE (1 << 6) + // In AlienTransitionDesc: indicates a transition to silent state + // In AlienTalkDesc: signals the end of talking animation +#define ANIM_DISABLED (1 << 7) + +#define COLORXFORM_ANIM PAUSE_TALKING + +typedef struct +{ + COUNT StartIndex; + // Index of the first image (for image animation) or + // index of the first color map (for palette animation) + BYTE NumFrames; + // Number of frames in the animation. + + BYTE AnimFlags; + // One of RANDOM_ANIM, CIRCULAR_ANIM, or YOYO_ANIM + // plus flags (WAIT_TALKING, ANIM_DISABLED) + + COUNT BaseFrameRate; + // Minimum interframe delay + COUNT RandomFrameRate; + // Maximum additional interframe delay + // Actual delay: BaseFrameRate + Random(0..RandomFrameRate) + COUNT BaseRestartRate; + // Minimum delay before restarting animation + COUNT RandomRestartRate; + // Maximum additional delay before restarting animation + // Actual delay: BaseRestartRate + Random(0..RandomRestartRate) + + DWORD BlockMask; + // Bit mask of the indices of all animations that can not + // be active at the same time as this animation, usually, + // due to the image overlap conflicts. +} ANIMATION_DESC; + +#define MAX_ANIMATIONS 20 + + #ifdef COMM_INTERNAL + typedef enum { - UP_DIR, - // Animation indices are increasing - DOWN_DIR, - // Animation indices are decreasing - NO_DIR + DOWN_DIR = -1, // Animation indices are decreasing + NO_DIR = 0, + UP_DIR = 1, // Animation indices are increasing } ANIM_DIR; typedef enum @@ -42,20 +109,22 @@ typedef enum // Describes an active animation. struct SEQUENCE { - COUNT Alarm; + ANIMATION_DESC *ADPtr; + DWORD Alarm; ANIM_DIR Direction; - BYTE FramesLeft; + COUNT CurIndex; + COUNT NextIndex; + COUNT FramesLeft; ANIM_TYPE AnimType; - union - { - FRAME CurFrame; - COLORMAP CurCMap; - } AnimObj; + BOOLEAN Change; }; #endif typedef struct SEQUENCE SEQUENCE; +// Returns TRUE if there was an animation change +extern BOOLEAN DrawAlienFrame (SEQUENCE *pSeq, COUNT Num, BOOLEAN fullRedraw); + void UpdateSpeechGraphics (BOOLEAN Initialize); Task StartCommAnimTask(void); diff --git a/sc2/src/uqm/globdata.h b/sc2/src/uqm/globdata.h index efb8b2a2f..1a696edd9 100644 --- a/sc2/src/uqm/globdata.h +++ b/sc2/src/uqm/globdata.h @@ -24,56 +24,9 @@ #include "libs/reslib.h" #include "sis.h" #include "velocity.h" +#include "commanim.h" -// XXX: the following should be moved to commanim.h -// Animation types: -#define RANDOM_ANIM (1 << 0) - // The next index is randomly chosen. -#define CIRCULAR_ANIM (1 << 1) - // After the last index has been reached, the animation starts over. -#define YOYO_ANIM (1 << 2) - // After the last index has been reached, the order that the - // animation frames are used is reversed. -#define ANIM_MASK (RANDOM_ANIM | CIRCULAR_ANIM | YOYO_ANIM) - // Mask of all animation types. - -#define WAIT_TALKING (1 << 3) - // This is set in AlienTalkDesc when the ambient animations should - // stop at the end of the current animation cycle. - // In AlienAmbientArray, this is set for those ambient animations - // which can not be active while the talking animation is active. -#define PAUSE_TALKING (1 << 4) -#define TALK_INTRO (1 << 5) -#define TALK_DONE (1 << 6) - -#define ANIM_DISABLED (1 << 7) - -#define COLORXFORM_ANIM PAUSE_TALKING - -typedef struct -{ - COUNT StartIndex; - // Index of the first image (for image animation) or - // index of the first color map (for palette animation) - BYTE NumFrames; - // Number of frames in the animation. - - BYTE AnimFlags; - // One of RANDOM_ANIM, CIRCULAR_ANIM, or YOYO_ANIM - - COUNT BaseFrameRate; - COUNT RandomFrameRate; - COUNT BaseRestartRate; - COUNT RandomRestartRate; - - DWORD BlockMask; - // Bit mask of the indices of all animations that can not - // be active at the same time as this animation. -} ANIMATION_DESC; - -#define MAX_ANIMATIONS 20 - // general numbers-speech generator info // should accomodate most common base-10 languages // many languages require various plural forms @@ -160,7 +113,12 @@ typedef struct COUNT NumAnimations; ANIMATION_DESC AlienAmbientArray[MAX_ANIMATIONS]; + // Transition animation to/from talking state; + // the first frame is neutral (sort of like YOYO_ANIM) ANIMATION_DESC AlienTransitionDesc; + // Talking animation, like RANDOM_ANIM, except random frames + // always alternate with a neutral frame; + // the first frame is neutral ANIMATION_DESC AlienTalkDesc; NUMBER_SPEECH AlienNumberSpeech;