1 //Copyright Paul Reiche, Fred Ford. 1992-2002
2
3 /*
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19 #define COMM_INTERNAL
20 #include "commanim.h"
21
22 #include "comm.h"
23 #include "element.h"
24 #include "setup.h"
25 #include "libs/compiler.h"
26 #include "libs/graphics/cmap.h"
27 #include "libs/mathlib.h"
28
29
30 static TimeCount LastTime;
31 static SEQUENCE Sequences[MAX_ANIMATIONS + 2];
32 // 2 extra for Talk and Transition animations
33 static DWORD ActiveMask;
34 // Bit mask of all animations that are currently active.
35 // Bit 'i' is set if the animation with index 'i' is active.
36 static ANIMATION_DESC TalkDesc;
37 static ANIMATION_DESC TransitDesc;
38 static SEQUENCE* Talk;
39 static SEQUENCE* Transit;
40 static COUNT FirstAmbient;
41 static COUNT TotalSequences;
42
43
44 static inline DWORD
randomFrameRate(SEQUENCE * pSeq)45 randomFrameRate (SEQUENCE *pSeq)
46 {
47 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
48
49 return ADPtr->BaseFrameRate +
50 TFB_Random () % (ADPtr->RandomFrameRate + 1);
51 }
52
53 static inline DWORD
randomRestartRate(SEQUENCE * pSeq)54 randomRestartRate (SEQUENCE *pSeq)
55 {
56 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
57
58 return ADPtr->BaseRestartRate +
59 TFB_Random () % (ADPtr->RandomRestartRate + 1);
60 }
61
62 static inline COUNT
randomFrameIndex(SEQUENCE * pSeq,COUNT from)63 randomFrameIndex (SEQUENCE *pSeq, COUNT from)
64 {
65 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
66
67 return from + TFB_Random () % (ADPtr->NumFrames - from);
68 }
69
70 static void
SetupAmbientSequences(SEQUENCE * pSeq,COUNT Num)71 SetupAmbientSequences (SEQUENCE *pSeq, COUNT Num)
72 {
73 COUNT i;
74
75 for (i = 0; i < Num; ++i, ++pSeq)
76 {
77 ANIMATION_DESC *ADPtr = &CommData.AlienAmbientArray[i];
78
79 memset (pSeq, 0, sizeof (*pSeq));
80
81 pSeq->ADPtr = ADPtr;
82 if (ADPtr->AnimFlags & COLORXFORM_ANIM)
83 pSeq->AnimType = COLOR_ANIM;
84 else
85 pSeq->AnimType = PICTURE_ANIM;
86 pSeq->Direction = UP_DIR;
87 pSeq->FramesLeft = ADPtr->NumFrames;
88 // Default: first frame is neutral
89 if (ADPtr->AnimFlags & RANDOM_ANIM)
90 { // Set a random frame/colormap
91 pSeq->NextIndex = TFB_Random () % ADPtr->NumFrames;
92 }
93 else if (ADPtr->AnimFlags & YOYO_ANIM)
94 { // Skip the first frame/colormap (it's neutral)
95 pSeq->NextIndex = 1;
96 --pSeq->FramesLeft;
97 }
98 else if (ADPtr->AnimFlags & CIRCULAR_ANIM)
99 { // Exception that makes everything more painful:
100 // *Last* frame is neutral
101 pSeq->CurIndex = ADPtr->NumFrames - 1;
102 pSeq->NextIndex = 0;
103 }
104
105 pSeq->Alarm = randomRestartRate (pSeq) + 1;
106 }
107 }
108
109 static void
SetupTalkSequence(SEQUENCE * pSeq,ANIMATION_DESC * ADPtr)110 SetupTalkSequence (SEQUENCE *pSeq, ANIMATION_DESC *ADPtr)
111 {
112 memset (pSeq, 0, sizeof (*pSeq));
113 // Initially disabled, and until needed
114 ADPtr->AnimFlags |= ANIM_DISABLED;
115 pSeq->ADPtr = ADPtr;
116 pSeq->AnimType = PICTURE_ANIM;
117 }
118
119 static inline BOOLEAN
animAtNeutralIndex(SEQUENCE * pSeq)120 animAtNeutralIndex (SEQUENCE *pSeq)
121 {
122 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
123
124 if (ADPtr->AnimFlags & CIRCULAR_ANIM)
125 { // CIRCULAR_ANIM's neutral frame is the last
126 return pSeq->NextIndex == 0;
127 }
128 else
129 { // All others, neutral frame is the first
130 return pSeq->CurIndex == 0;
131 }
132 }
133
134 static inline BOOLEAN
conflictsWithTalkingAnim(SEQUENCE * pSeq)135 conflictsWithTalkingAnim (SEQUENCE *pSeq)
136 {
137 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
138
139 return ADPtr->AnimFlags & CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING;
140 }
141
142 static void
ProcessColormapAnims(SEQUENCE * pSeq,COUNT Num)143 ProcessColormapAnims (SEQUENCE *pSeq, COUNT Num)
144 {
145 COUNT i;
146
147 for (i = 0; i < Num; ++i, ++pSeq)
148 {
149 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
150
151 if ((ADPtr->AnimFlags & ANIM_DISABLED)
152 || pSeq->AnimType != COLOR_ANIM
153 || !pSeq->Change)
154 continue;
155
156 XFormColorMap (GetColorMapAddress (
157 SetAbsColorMapIndex (CommData.AlienColorMap,
158 ADPtr->StartIndex + pSeq->CurIndex)),
159 pSeq->Alarm - 1);
160 pSeq->Change = FALSE;
161 }
162 }
163
164 static BOOLEAN
AdvanceAmbientSequence(SEQUENCE * pSeq)165 AdvanceAmbientSequence (SEQUENCE *pSeq)
166 {
167 BOOLEAN active;
168 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
169
170 --pSeq->FramesLeft;
171 // YOYO_ANIM does not actually end until it comes back
172 // in reverse direction, even if FramesLeft gets to 0 here
173 if (pSeq->FramesLeft
174 || ((ADPtr->AnimFlags & YOYO_ANIM) && pSeq->NextIndex != 0))
175 {
176 active = TRUE;
177 pSeq->Alarm = randomFrameRate (pSeq) + 1;
178 }
179 else
180 { // last animation frame
181 active = FALSE;
182 pSeq->Alarm = randomRestartRate (pSeq) + 1;
183
184 // RANDOM_ANIM must end on a neutral frame
185 if (ADPtr->AnimFlags & RANDOM_ANIM)
186 pSeq->NextIndex = 0;
187 }
188
189 // Will draw the next frame or change to next colormap
190 pSeq->CurIndex = pSeq->NextIndex;
191 pSeq->Change = TRUE;
192
193 if (pSeq->FramesLeft == 0)
194 { // Animation ended
195 // Set it up for the next round
196 pSeq->FramesLeft = ADPtr->NumFrames;
197
198 if (ADPtr->AnimFlags & YOYO_ANIM)
199 { // YOYO_ANIM never draws the first frame
200 // ("first" depends on direction)
201 --pSeq->FramesLeft;
202 pSeq->Direction = -pSeq->Direction;
203 }
204 else if (ADPtr->AnimFlags & CIRCULAR_ANIM)
205 { // Rewind the CIRCULAR_ANIM
206 // NextIndex will be brought to 0 just below
207 pSeq->NextIndex = -1;
208 }
209 // RANDOM_ANIM is setup just below
210 }
211
212 if (ADPtr->AnimFlags & RANDOM_ANIM)
213 pSeq->NextIndex = randomFrameIndex (pSeq, 0);
214 else
215 pSeq->NextIndex += pSeq->Direction;
216
217 return active;
218 }
219
220 static void
ResetSequence(SEQUENCE * pSeq)221 ResetSequence (SEQUENCE *pSeq)
222 {
223 // Reset the animation and cause a redraw of the neutral frame,
224 // assuming it is not ANIM_DISABLED
225 // NOTE: This does not handle CIRCULAR_ANIM properly
226 pSeq->Direction = NO_DIR;
227 pSeq->CurIndex = 0;
228 pSeq->Change = TRUE;
229 }
230
231 static void
AdvanceTalkingSequence(SEQUENCE * pSeq,DWORD ElapsedTicks)232 AdvanceTalkingSequence (SEQUENCE *pSeq, DWORD ElapsedTicks)
233 {
234 // We use the actual descriptor for flags processing and
235 // a copied one for drawing. A copied one is updated only
236 // when it is safe to do so.
237 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
238
239 if (pSeq->Direction == NO_DIR)
240 { // just starting now
241 pSeq->Direction = UP_DIR;
242 // It's now safe to pick up new Talk descriptor if changed
243 // (e.g. Zoq and Pik taking turns to talk)
244 if (CommData.AlienTalkDesc.StartIndex != ADPtr->StartIndex)
245 { // copy the new one
246 *ADPtr = CommData.AlienTalkDesc;
247 }
248
249 assert (pSeq->CurIndex == 0);
250 pSeq->Alarm = 0; // now!
251 ADPtr->AnimFlags &= ~ANIM_DISABLED;
252 }
253
254 if (pSeq->Alarm > ElapsedTicks)
255 { // Not time yet
256 pSeq->Alarm -= ElapsedTicks;
257 return;
258 }
259
260 // Time to start or advance the animation
261 pSeq->Alarm = randomFrameRate (pSeq);
262 pSeq->Change = TRUE;
263 // Talking animation is like RANDOM_ANIM, except that
264 // random frames always alternate with the neutral one
265 // The animation does not stop until we reset it
266 if (pSeq->CurIndex == 0)
267 { // random frame next
268 pSeq->CurIndex = randomFrameIndex (pSeq, 1);
269 pSeq->Alarm += randomRestartRate (pSeq);
270 }
271 else
272 { // neutral frame next
273 pSeq->CurIndex = 0;
274 }
275 }
276
277 static BOOLEAN
AdvanceTransitSequence(SEQUENCE * pSeq,DWORD ElapsedTicks)278 AdvanceTransitSequence (SEQUENCE *pSeq, DWORD ElapsedTicks)
279 {
280 BOOLEAN done = FALSE;
281 // We use the actual descriptor for flags processing and
282 // a copied one for drawing. A copied one is updated only
283 // when it is safe to do so.
284 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
285
286 if (pSeq->Direction == NO_DIR)
287 { // just starting now
288 pSeq->Alarm = 0; // now!
289 ADPtr->AnimFlags &= ~ANIM_DISABLED;
290 }
291
292 if (pSeq->Alarm > ElapsedTicks)
293 { // Not time yet
294 pSeq->Alarm -= ElapsedTicks;
295 return FALSE;
296 }
297
298 // Time to start or advance the animation
299 pSeq->Change = TRUE;
300
301 if (pSeq->Direction == NO_DIR)
302 { // just starting now
303 pSeq->FramesLeft = ADPtr->NumFrames;
304 // Both INTRO and DONE may be set at the same time,
305 // when e.g. Zoq and Pik are taking turns to talk
306 // Process the DONE transition first to go into
307 // a neutral state before switching over.
308 if (CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)
309 {
310 pSeq->Direction = DOWN_DIR;
311 pSeq->CurIndex = ADPtr->NumFrames - 1;
312 }
313 else if (CommData.AlienTransitionDesc.AnimFlags & TALK_INTRO)
314 {
315 pSeq->Direction = UP_DIR;
316 // It's now safe to pick up new Transition descriptor if changed
317 // (e.g. Zoq and Pik taking turns to talk)
318 if (CommData.AlienTransitionDesc.StartIndex
319 != ADPtr->StartIndex)
320 { // copy the new one
321 *ADPtr = CommData.AlienTransitionDesc;
322 }
323
324 pSeq->CurIndex = 0;
325 }
326 }
327
328 --pSeq->FramesLeft;
329 if (pSeq->FramesLeft == 0)
330 { // animation is done
331 if (pSeq->Direction == UP_DIR)
332 { // done with TALK_INTRO transition
333 CommData.AlienTransitionDesc.AnimFlags &= ~TALK_INTRO;
334 }
335 else if (pSeq->Direction == DOWN_DIR)
336 { // done with TALK_DONE transition
337 CommData.AlienTransitionDesc.AnimFlags &= ~TALK_DONE;
338
339 // Done with all transition frames
340 ADPtr->AnimFlags |= ANIM_DISABLED;
341 done = TRUE;
342 }
343 pSeq->Direction = NO_DIR;
344 }
345 else
346 { // next frame
347 pSeq->Alarm = randomFrameRate (pSeq);
348 pSeq->CurIndex += pSeq->Direction;
349 }
350
351 return done;
352 }
353
354 void
InitCommAnimations(void)355 InitCommAnimations (void)
356 {
357 ActiveMask = 0;
358
359 TalkDesc = CommData.AlienTalkDesc;
360 TransitDesc = CommData.AlienTransitionDesc;
361
362 // Animation sequences have to be drawn in reverse, and
363 // talk animations have to be drawn last (so we add them first)
364 TotalSequences = 0;
365 // Transition animation last
366 Transit = Sequences + TotalSequences;
367 SetupTalkSequence (Transit, &TransitDesc);
368 ++TotalSequences;
369 // Talk animation second last
370 Talk = Sequences + TotalSequences;
371 SetupTalkSequence (Talk, &TalkDesc);
372 ++TotalSequences;
373 FirstAmbient = TotalSequences;
374 SetupAmbientSequences (Sequences + FirstAmbient, CommData.NumAnimations);
375 TotalSequences += CommData.NumAnimations;
376
377 LastTime = GetTimeCounter ();
378 }
379
380 BOOLEAN
ProcessCommAnimations(BOOLEAN FullRedraw,BOOLEAN paused)381 ProcessCommAnimations (BOOLEAN FullRedraw, BOOLEAN paused)
382 {
383 if (paused)
384 { // Drive colormap xforms and nothing else
385 XFormColorMap_step ();
386 return FALSE;
387 }
388 else
389 {
390 COUNT i;
391 SEQUENCE *pSeq;
392 BOOLEAN Change;
393 BOOLEAN CanTalk = TRUE;
394 TimeCount CurTime;
395 DWORD ElapsedTicks;
396 DWORD NextActiveMask;
397
398 CurTime = GetTimeCounter ();
399 ElapsedTicks = CurTime - LastTime;
400 LastTime = CurTime;
401
402 // Process ambient animations
403 NextActiveMask = ActiveMask;
404 pSeq = Sequences + FirstAmbient;
405 for (i = 0; i < CommData.NumAnimations; ++i, ++pSeq)
406 {
407 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
408 DWORD ActiveBit = 1L << i;
409
410 if (ADPtr->AnimFlags & ANIM_DISABLED)
411 continue;
412
413 if (pSeq->Direction == NO_DIR)
414 { // animation is paused
415 if (!conflictsWithTalkingAnim (pSeq))
416 { // start it up
417 pSeq->Direction = UP_DIR;
418 }
419 }
420 else if (pSeq->Alarm > ElapsedTicks)
421 { // not time yet
422 pSeq->Alarm -= ElapsedTicks;
423 }
424 else if (ActiveMask & ADPtr->BlockMask)
425 { // animation is blocked
426 assert (!(ActiveMask & ActiveBit) &&
427 "Check animations' mutual blocking masks");
428 assert (animAtNeutralIndex (pSeq));
429 // reschedule
430 pSeq->Alarm = randomRestartRate (pSeq) + 1;
431 continue;
432 }
433 else
434 { // Time to start or advance the animation
435 if (AdvanceAmbientSequence (pSeq))
436 { // Animation is active this frame and the next
437 ActiveMask |= ActiveBit;
438 NextActiveMask |= ActiveBit;
439 }
440 else
441 { // Animation remains active this frame but not the next
442 // This keeps any conflicting animations (BlockMask)
443 // from activating in the same frame and scribbling over
444 // our last image.
445 NextActiveMask &= ~ActiveBit;
446 }
447 }
448
449 if (pSeq->AnimType == PICTURE_ANIM && pSeq->Direction != NO_DIR
450 && conflictsWithTalkingAnim (pSeq))
451 {
452 // We want to talk, but this is a running picture animation
453 // which conflicts with the talking animation
454 // See if it is safe to stop it now.
455 if (animAtNeutralIndex (pSeq))
456 { // pause the animation
457 pSeq->Direction = NO_DIR;
458 NextActiveMask &= ~ActiveBit;
459 // Talk animation is drawn last, so it's not a conflict
460 // for this frame. The talk animation will be drawn
461 // over the neutral frame.
462 }
463 else
464 { // Otherwise, let the animation run until it's safe
465 CanTalk = FALSE;
466 }
467 }
468 }
469 // All ambient animations have been processed. Advance the mask.
470 ActiveMask = NextActiveMask;
471
472 // Process the talking and transition animations
473 if (CanTalk && haveTalkingAnim () && runningTalkingAnim ())
474 {
475 BOOLEAN done = FALSE;
476
477 if (signaledStopTalkingAnim () && haveTransitionAnim ())
478 { // Run the transition. We will clear everything
479 // when it is done
480 CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE;
481 }
482
483 if (CommData.AlienTransitionDesc.AnimFlags
484 & (TALK_INTRO | TALK_DONE))
485 { // Transitioning in or out of talking
486 if ((CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)
487 && Transit->Direction == NO_DIR)
488 { // This is needed when switching talking anims
489 ResetSequence (Talk);
490 }
491 done = AdvanceTransitSequence (Transit, ElapsedTicks);
492 }
493 else if (!signaledStopTalkingAnim ())
494 { // Talking, transition is done
495 AdvanceTalkingSequence (Talk, ElapsedTicks);
496 }
497 else
498 { // Not talking
499 ResetSequence (Talk);
500 done = TRUE;
501 }
502
503 if (signaledStopTalkingAnim () && done)
504 {
505 clearRunTalkingAnim ();
506 clearStopTalkingAnim ();
507 }
508 }
509 else
510 { // Not talking -- disable talking anim if it is done
511 if (Talk->Direction == NO_DIR)
512 TalkDesc.AnimFlags |= ANIM_DISABLED;
513 }
514
515 BatchGraphics ();
516
517 // Draw all animations
518 {
519 BOOLEAN ColorChange = XFormColorMap_step ();
520
521 if (ColorChange)
522 FullRedraw = TRUE;
523
524 // Colormap animations are processed separately
525 // from picture anims (see XFormColorMap_step)
526 ProcessColormapAnims (Sequences + FirstAmbient,
527 CommData.NumAnimations);
528
529 Change = DrawAlienFrame (Sequences, TotalSequences, FullRedraw);
530 if (FullRedraw)
531 Change = TRUE;
532 }
533
534 UnbatchGraphics ();
535
536 // Post-process ambient animations
537 pSeq = Sequences + FirstAmbient;
538 for (i = 0; i < CommData.NumAnimations; ++i, ++pSeq)
539 {
540 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
541 DWORD ActiveBit = 1L << i;
542
543 if (ADPtr->AnimFlags & ANIM_DISABLED)
544 continue;
545
546 // We can only disable a one-shot anim here, otherwise the
547 // last frame will not be drawn
548 if ((ADPtr->AnimFlags & ONE_SHOT_ANIM)
549 && !(NextActiveMask & ActiveBit))
550 { // One-shot animation, inactive next frame
551 ADPtr->AnimFlags |= ANIM_DISABLED;
552 }
553 }
554
555 return Change;
556 }
557 }
558
559 BOOLEAN
DrawAlienFrame(SEQUENCE * Sequences,COUNT Num,BOOLEAN fullRedraw)560 DrawAlienFrame (SEQUENCE *Sequences, COUNT Num, BOOLEAN fullRedraw)
561 {
562 int i;
563 STAMP s;
564 BOOLEAN Change = FALSE;
565
566 BatchGraphics ();
567
568 s.origin.x = -SAFE_X;
569 s.origin.y = 0;
570
571 if (fullRedraw)
572 {
573 // Draw the main frame
574 s.frame = CommData.AlienFrame;
575 DrawStamp (&s);
576
577 // Draw any static frames (has to be in reverse)
578 for (i = CommData.NumAnimations - 1; i >= 0; --i)
579 {
580 ANIMATION_DESC *ADPtr = &CommData.AlienAmbientArray[i];
581
582 if (ADPtr->AnimFlags & ANIM_MASK)
583 continue;
584
585 ADPtr->AnimFlags |= ANIM_DISABLED;
586
587 if (!(ADPtr->AnimFlags & COLORXFORM_ANIM))
588 { // It's a static frame (e.g. Flagship picture at Starbase)
589 s.frame = SetAbsFrameIndex (CommData.AlienFrame,
590 ADPtr->StartIndex);
591 DrawStamp (&s);
592 }
593 }
594 }
595
596 if (Sequences)
597 { // Draw the animation sequences (has to be in reverse)
598 for (i = Num - 1; i >= 0; --i)
599 {
600 SEQUENCE *pSeq = &Sequences[i];
601 ANIMATION_DESC *ADPtr = pSeq->ADPtr;
602
603 if ((ADPtr->AnimFlags & ANIM_DISABLED)
604 || pSeq->AnimType != PICTURE_ANIM)
605 continue;
606
607 // Draw current animation frame only if changed
608 if (!fullRedraw && !pSeq->Change)
609 continue;
610
611 s.frame = SetAbsFrameIndex (CommData.AlienFrame,
612 ADPtr->StartIndex + pSeq->CurIndex);
613 DrawStamp (&s);
614 pSeq->Change = FALSE;
615
616 Change = TRUE;
617 }
618 }
619
620 UnbatchGraphics ();
621
622 return Change;
623 }
624