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 "comm.h"
21
22 #include "build.h"
23 #include "commanim.h"
24 #include "commglue.h"
25 #include "controls.h"
26 #include "colors.h"
27 #include "sis.h"
28 #include "units.h"
29 #include "encount.h"
30 #include "starmap.h"
31 #include "endian_uqm.h"
32 #include "gamestr.h"
33 #include "options.h"
34 #include "oscill.h"
35 #include "save.h"
36 #include "settings.h"
37 #include "setup.h"
38 #include "sounds.h"
39 #include "nameref.h"
40 #include "uqmdebug.h"
41 #include "libs/graphics/gfx_common.h"
42 #include "libs/inplib.h"
43 #include "libs/sound/sound.h"
44 #include "libs/sound/trackplayer.h"
45 #include "libs/log.h"
46
47 #include <ctype.h>
48
49 #define MAX_RESPONSES 8
50 #define BACKGROUND_VOL (usingSpeech ? (NORMAL_VOLUME / 2) : NORMAL_VOLUME)
51 #define FOREGROUND_VOL NORMAL_VOLUME
52
53 // Oscilloscope frame rate
54 // Should be <= COMM_ANIM_RATE
55 // XXX: was 32 picked experimentally?
56 #define OSCILLOSCOPE_RATE (ONE_SECOND / 32)
57
58 // Maximum comm animation frame rate (actual execution rate)
59 // A gfx frame is not always produced during an execution frame,
60 // and several animations are combined into one gfx frame.
61 // The rate was originally 120fps which allowed for more animation
62 // precision which is ultimately wasted on the human eye anyway.
63 // The highest known stable animation rate is 40fps, so that's what we use.
64 #define COMM_ANIM_RATE (ONE_SECOND / 40)
65
66 static CONTEXT AnimContext;
67
68 LOCDATA CommData;
69 UNICODE shared_phrase_buf[2048];
70
71 static BOOLEAN TalkingFinished;
72 static CommIntroMode curIntroMode = CIM_DEFAULT;
73 static TimeCount fadeTime;
74
75 typedef struct response_entry
76 {
77 RESPONSE_REF response_ref;
78 TEXT response_text;
79 RESPONSE_FUNC response_func;
80 } RESPONSE_ENTRY;
81
82 typedef struct encounter_state
83 {
84 BOOLEAN (*InputFunc) (struct encounter_state *pES);
85
86 COUNT Initialized;
87 TimeCount NextTime; // framerate control
88 BYTE num_responses;
89 BYTE cur_response;
90 BYTE top_response;
91 RESPONSE_ENTRY response_list[MAX_RESPONSES];
92
93 UNICODE phrase_buf[1024];
94 } ENCOUNTER_STATE;
95
96 static ENCOUNTER_STATE *pCurInputState;
97
98 static BOOLEAN clear_subtitles;
99 static TEXT SubtitleText;
100 static const UNICODE *last_subtitle;
101
102 static CONTEXT TextCacheContext;
103 static FRAME TextCacheFrame;
104
105
106 RECT CommWndRect = {
107 // default values; actually inited by HailAlien()
108 {SIS_ORG_X, SIS_ORG_Y},
109 {0, 0}
110 };
111
112 static void ClearSubtitles (void);
113 static void CheckSubtitles (void);
114 static void RedrawSubtitles (void);
115
116
117 /* _count_lines - sees how many lines a given input string would take to
118 * display given the line wrapping information
119 */
120 static int
_count_lines(TEXT * pText)121 _count_lines (TEXT *pText)
122 {
123 SIZE text_width;
124 const char *pStr;
125 int numLines = 0;
126 BOOLEAN eol;
127
128 text_width = CommData.AlienTextWidth;
129 SetContextFont (CommData.AlienFont);
130
131 pStr = pText->pStr;
132 do
133 {
134 ++numLines;
135 pText->pStr = pStr;
136 eol = getLineWithinWidth (pText, &pStr, text_width, (COUNT)~0);
137 } while (!eol);
138 pText->pStr = pStr;
139
140 return numLines;
141 }
142
143 // status == -1: draw highlighted player dialog option
144 // status == -2: draw non-highlighted player dialog option
145 // status == -4: use current context, and baseline from pTextIn
146 // status == 1: draw alien speech; subtitle cache is used
147 static COORD
add_text(int status,TEXT * pTextIn)148 add_text (int status, TEXT *pTextIn)
149 {
150 COUNT maxchars, numchars;
151 TEXT locText;
152 TEXT *pText;
153 SIZE leading;
154 const char *pStr;
155 SIZE text_width;
156 int num_lines = 0;
157 static COORD last_baseline;
158 BOOLEAN eol;
159 CONTEXT OldContext = NULL;
160
161 BatchGraphics ();
162
163 maxchars = (COUNT)~0;
164 if (status == 1)
165 {
166 if (last_subtitle == pTextIn->pStr)
167 {
168 // draws cached subtitle
169 STAMP s;
170
171 s.origin.x = 0;
172 s.origin.y = 0;
173 s.frame = TextCacheFrame;
174 DrawStamp (&s);
175 UnbatchGraphics ();
176 return last_baseline;
177 }
178 else
179 {
180 // draw to subtitle cache; prepare first
181 OldContext = SetContext (TextCacheContext);
182 ClearDrawable ();
183
184 last_subtitle = pTextIn->pStr;
185 }
186
187 text_width = CommData.AlienTextWidth;
188 SetContextFont (CommData.AlienFont);
189 GetContextFontLeading (&leading);
190
191 pText = pTextIn;
192 }
193 else if (GetContextFontLeading (&leading), status <= -4)
194 {
195 text_width = (SIZE) (SIS_SCREEN_WIDTH - 8 - (TEXT_X_OFFS << 2));
196
197 pText = pTextIn;
198 }
199 else
200 {
201 text_width = (SIZE) (SIS_SCREEN_WIDTH - 8 - (TEXT_X_OFFS << 2));
202
203 switch (status)
204 {
205 case -3:
206 // Unknown. Never reached; color matches the background color.
207 SetContextForeGroundColor (
208 BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
209 break;
210 case -2:
211 // Not highlighted dialog options.
212 SetContextForeGroundColor (COMM_PLAYER_TEXT_NORMAL_COLOR);
213 break;
214 case -1:
215 // Currently highlighted dialog option.
216 SetContextForeGroundColor (COMM_PLAYER_TEXT_HIGHLIGHT_COLOR);
217 break;
218 }
219
220 maxchars = pTextIn->CharCount;
221 locText = *pTextIn;
222 locText.baseline.x -= 8;
223 locText.CharCount = (COUNT)~0;
224 locText.pStr = STR_BULLET;
225 font_DrawText (&locText);
226
227 locText = *pTextIn;
228 pText = &locText;
229 pText->baseline.y -= leading;
230 }
231
232 numchars = 0;
233 pStr = pText->pStr;
234
235 if (status > 0 && (CommData.AlienTextValign &
236 (VALIGN_MIDDLE | VALIGN_BOTTOM)))
237 {
238 num_lines = _count_lines(pText);
239 if (CommData.AlienTextValign == VALIGN_BOTTOM)
240 pText->baseline.y -= (leading * num_lines);
241 else if (CommData.AlienTextValign == VALIGN_MIDDLE)
242 pText->baseline.y -= ((leading * num_lines) / 2);
243 if (pText->baseline.y < 0)
244 pText->baseline.y = 0;
245 }
246
247 do
248 {
249 pText->pStr = pStr;
250 pText->baseline.y += leading;
251
252 eol = getLineWithinWidth (pText, &pStr, text_width, maxchars);
253
254 maxchars -= pText->CharCount;
255 if (maxchars != 0)
256 --maxchars;
257 numchars += pText->CharCount;
258
259 if (status <= 0)
260 {
261 // Player dialog option or (status == -4) other non-alien
262 // text.
263 if (pText->baseline.y < SIS_SCREEN_HEIGHT)
264 font_DrawText (pText);
265
266 if (status < -4 && pText->baseline.y >= -status - 10)
267 {
268 // Never actually reached. Status is never <-4.
269 ++pStr;
270 break;
271 }
272 }
273 else
274 {
275 // Alien speech
276 font_DrawTracedText (pText,
277 CommData.AlienTextFColor, CommData.AlienTextBColor);
278 }
279 } while (!eol && maxchars);
280 pText->pStr = pStr;
281
282 if (status == 1)
283 {
284 STAMP s;
285
286 // We were drawing to cache -- flush to screen
287 SetContext (OldContext);
288 s.origin.x = s.origin.y = 0;
289 s.frame = TextCacheFrame;
290 DrawStamp (&s);
291
292 last_baseline = pText->baseline.y;
293 }
294
295 UnbatchGraphics ();
296 return (pText->baseline.y);
297 }
298
299 // This function calculates how much of a string can be fitted within
300 // a specific width, up to a newline or terminating \0.
301 // pText is the text to be fitted. pText->CharCount will be set to the
302 // number of characters that fitted.
303 // startNext will be filled with the start of the first word that
304 // doesn't fit in one line, or if an entire line fits, to the character
305 // past the newline, or if the entire string fits, to the end of the
306 // string.
307 // maxWidth is the maximum number of pixels that a line may be wide
308 // ASSUMPTION: there are no words in the text wider than maxWidth
309 // maxChars is the maximum number of characters (not bytes) that are to
310 // be fitted.
311 // TRUE is returned if a complete line fitted
312 // FALSE otherwise
313 BOOLEAN
getLineWithinWidth(TEXT * pText,const char ** startNext,SIZE maxWidth,COUNT maxChars)314 getLineWithinWidth(TEXT *pText, const char **startNext,
315 SIZE maxWidth, COUNT maxChars)
316 {
317 BOOLEAN eol;
318 // The end of the line of text has been reached.
319 BOOLEAN done;
320 // We cannot add any more words.
321 RECT rect;
322 COUNT oldCount;
323 const char *ptr;
324 const char *wordStart;
325 UniChar ch;
326 COUNT charCount;
327
328 //GetContextClipRect (&rect);
329
330 eol = FALSE;
331 done = FALSE;
332 oldCount = 1;
333 charCount = 0;
334 ch = '\0';
335 ptr = pText->pStr;
336 for (;;)
337 {
338 wordStart = ptr;
339
340 // Scan one word.
341 for (;;)
342 {
343 if (*ptr == '\0')
344 {
345 eol = TRUE;
346 done = TRUE;
347 break;
348 }
349 ch = getCharFromString (&ptr);
350 eol = ch == '\0' || ch == '\n' || ch == '\r';
351 done = eol || charCount >= maxChars;
352 if (done || ch == ' ')
353 break;
354 charCount++;
355 }
356
357 oldCount = pText->CharCount;
358 pText->CharCount = charCount;
359 TextRect (pText, &rect, NULL);
360
361 if (rect.extent.width >= maxWidth)
362 {
363 pText->CharCount = oldCount;
364 *startNext = wordStart;
365 return FALSE;
366 }
367
368 if (done)
369 {
370 *startNext = ptr;
371 return eol;
372 }
373 charCount++;
374 // For the space in between words.
375 }
376 }
377
378 static void
DrawSISComWindow(void)379 DrawSISComWindow (void)
380 {
381 CONTEXT OldContext;
382
383 if (LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
384 {
385 RECT r;
386
387 OldContext = SetContext (SpaceContext);
388
389 r.corner.x = 0;
390 r.corner.y = SLIDER_Y + SLIDER_HEIGHT;
391 r.extent.width = SIS_SCREEN_WIDTH;
392 r.extent.height = SIS_SCREEN_HEIGHT - r.corner.y;
393 SetContextForeGroundColor (COMM_PLAYER_BACKGROUND_COLOR);
394 DrawFilledRectangle (&r);
395
396 SetContext (OldContext);
397 }
398 }
399
400 void
init_communication(void)401 init_communication (void)
402 {
403 // now a no-op
404 }
405
406 void
uninit_communication(void)407 uninit_communication (void)
408 {
409 // now a no-op
410 }
411
412 static void
RefreshResponses(ENCOUNTER_STATE * pES)413 RefreshResponses (ENCOUNTER_STATE *pES)
414 {
415 COORD y;
416 BYTE response;
417 SIZE leading;
418 STAMP s;
419
420 SetContext (SpaceContext);
421 GetContextFontLeading (&leading);
422 BatchGraphics ();
423
424 DrawSISComWindow ();
425 y = SLIDER_Y + SLIDER_HEIGHT + 1;
426 for (response = pES->top_response; response < pES->num_responses;
427 ++response)
428 {
429 pES->response_list[response].response_text.baseline.x = TEXT_X_OFFS + 8;
430 pES->response_list[response].response_text.baseline.y = y + leading;
431 pES->response_list[response].response_text.align = ALIGN_LEFT;
432 if (response == pES->cur_response)
433 y = add_text (-1, &pES->response_list[response].response_text);
434 else
435 y = add_text (-2, &pES->response_list[response].response_text);
436 }
437
438 if (pES->top_response)
439 {
440 s.origin.y = SLIDER_Y + SLIDER_HEIGHT + 1;
441 s.frame = SetAbsFrameIndex (ActivityFrame, 6);
442 }
443 else if (y > SIS_SCREEN_HEIGHT)
444 {
445 s.origin.y = SIS_SCREEN_HEIGHT - 2;
446 s.frame = SetAbsFrameIndex (ActivityFrame, 7);
447 }
448 else
449 s.frame = 0;
450 if (s.frame)
451 {
452 RECT r;
453
454 GetFrameRect (s.frame, &r);
455 s.origin.x = SIS_SCREEN_WIDTH - r.extent.width - 1;
456 DrawStamp (&s);
457 }
458
459 UnbatchGraphics ();
460 }
461
462 static void
FeedbackPlayerPhrase(UNICODE * pStr)463 FeedbackPlayerPhrase (UNICODE *pStr)
464 {
465 SetContext (SpaceContext);
466
467 BatchGraphics ();
468 DrawSISComWindow ();
469 if (pStr[0])
470 {
471 TEXT ct;
472
473 ct.baseline.x = SIS_SCREEN_WIDTH >> 1;
474 ct.baseline.y = SLIDER_Y + SLIDER_HEIGHT + 13;
475 ct.align = ALIGN_CENTER;
476 ct.CharCount = (COUNT)~0;
477
478 ct.pStr = GAME_STRING (FEEDBACK_STRING_BASE);
479 // "(In response to your statement)"
480 SetContextForeGroundColor (COMM_RESPONSE_INTRO_TEXT_COLOR);
481 font_DrawText (&ct);
482
483 ct.baseline.y += 16;
484 SetContextForeGroundColor (COMM_FEEDBACK_TEXT_COLOR);
485 ct.pStr = pStr;
486 add_text (-4, &ct);
487 }
488 UnbatchGraphics ();
489 }
490
491 static void
InitSpeechGraphics(void)492 InitSpeechGraphics (void)
493 {
494 InitOscilloscope (SetAbsFrameIndex (ActivityFrame, 9));
495
496 InitSlider (0, SLIDER_Y, SIS_SCREEN_WIDTH,
497 SetAbsFrameIndex (ActivityFrame, 5),
498 SetAbsFrameIndex (ActivityFrame, 2));
499 }
500
501 static void
UpdateSpeechGraphics(void)502 UpdateSpeechGraphics (void)
503 {
504 static TimeCount NextTime;
505 CONTEXT OldContext;
506
507 if (GetTimeCounter () < NextTime)
508 return; // too early
509
510 NextTime = GetTimeCounter () + OSCILLOSCOPE_RATE;
511
512 OldContext = SetContext (RadarContext);
513 DrawOscilloscope ();
514 SetContext (SpaceContext);
515 DrawSlider ();
516 SetContext (OldContext);
517 }
518
519 static void
UpdateAnimations(bool paused)520 UpdateAnimations (bool paused)
521 {
522 static TimeCount NextTime;
523 CONTEXT OldContext;
524 BOOLEAN change;
525
526 if (GetTimeCounter () < NextTime)
527 return; // too early
528
529 NextTime = GetTimeCounter () + COMM_ANIM_RATE;
530
531 OldContext = SetContext (AnimContext);
532 BatchGraphics ();
533 // Advance and draw ambient, transit and talk animations
534 change = ProcessCommAnimations (clear_subtitles, paused);
535 if (change || clear_subtitles)
536 RedrawSubtitles ();
537 UnbatchGraphics ();
538 clear_subtitles = FALSE;
539 SetContext (OldContext);
540 }
541
542 static void
UpdateCommGraphics(void)543 UpdateCommGraphics (void)
544 {
545 UpdateAnimations (false);
546 UpdateSpeechGraphics ();
547 }
548
549 // Derived from INPUT_STATE_DESC
550 typedef struct talking_state
551 {
552 // Fields required by DoInput()
553 BOOLEAN (*InputFunc) (struct talking_state *);
554
555 TimeCount NextTime; // framerate control
556 COUNT waitTrack;
557 bool rewind;
558 bool seeking;
559 bool ended;
560
561 } TALKING_STATE;
562
563 static BOOLEAN
DoTalkSegue(TALKING_STATE * pTS)564 DoTalkSegue (TALKING_STATE *pTS)
565 {
566 bool left = false;
567 bool right = false;
568 COUNT curTrack;
569
570 if (GLOBAL (CurrentActivity) & CHECK_ABORT)
571 {
572 pTS->ended = true;
573 return FALSE;
574 }
575
576 if (PulsedInputState.menu[KEY_MENU_CANCEL])
577 {
578 JumpTrack ();
579 pTS->ended = true;
580 return FALSE;
581 }
582
583 if (optSmoothScroll == OPT_PC)
584 {
585 left = PulsedInputState.menu[KEY_MENU_LEFT] != 0;
586 right = PulsedInputState.menu[KEY_MENU_RIGHT] != 0;
587 }
588 else if (optSmoothScroll == OPT_3DO)
589 {
590 left = CurrentInputState.menu[KEY_MENU_LEFT] != 0;
591 right = CurrentInputState.menu[KEY_MENU_RIGHT] != 0;
592 }
593
594 #if DEMO_MODE || CREATE_JOURNAL
595 left = false;
596 right = false;
597 #endif
598
599 if (right)
600 {
601 SetSliderImage (SetAbsFrameIndex (ActivityFrame, 3));
602 if (optSmoothScroll == OPT_PC)
603 FastForward_Page ();
604 else if (optSmoothScroll == OPT_3DO)
605 FastForward_Smooth ();
606 pTS->seeking = true;
607 }
608 else if (left || pTS->rewind)
609 {
610 pTS->rewind = false;
611 SetSliderImage (SetAbsFrameIndex (ActivityFrame, 4));
612 if (optSmoothScroll == OPT_PC)
613 FastReverse_Page ();
614 else if (optSmoothScroll == OPT_3DO)
615 FastReverse_Smooth ();
616 pTS->seeking = true;
617 }
618 else if (pTS->seeking)
619 {
620 // This is only done once the seeking is over (in the smooth
621 // scroll case, once the user releases the seek button)
622 pTS->seeking = false;
623 SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2));
624 }
625 else
626 {
627 // This used to have a buggy guard condition, which
628 // would cause the animations to remain paused in a couple cases
629 // after seeking back to the beginning.
630 // Broken cases were: Syreen "several hours later" and Starbase
631 // VUX Beast analysis by the scientist.
632 CheckSubtitles ();
633 }
634
635 // XXX: When seeking, all animations (talking and ambient) stop
636 // progressing. This is an original 3DO behavior, and I see no
637 // reason why the animations cannot continue while seeking.
638 UpdateAnimations (pTS->seeking);
639 UpdateSpeechGraphics ();
640
641 curTrack = PlayingTrack ();
642 pTS->ended = !pTS->seeking && !curTrack;
643
644 SleepThreadUntil (pTS->NextTime);
645 // Need a high enough framerate for 3DO smooth seeking
646 pTS->NextTime = GetTimeCounter () + ONE_SECOND / 60;
647
648 return pTS->seeking || (curTrack && curTrack <= pTS->waitTrack);
649 }
650
651 static void
runCommAnimFrame(void)652 runCommAnimFrame (void)
653 {
654 UpdateCommGraphics ();
655 SleepThread (COMM_ANIM_RATE);
656 }
657
658 static BOOLEAN
TalkSegue(COUNT wait_track)659 TalkSegue (COUNT wait_track)
660 {
661 TALKING_STATE talkingState;
662
663 // Transition animation to talking state, if necessary
664 if (wantTalkingAnim () && haveTalkingAnim ())
665 {
666 if (haveTransitionAnim ())
667 setRunIntroAnim ();
668
669 setRunTalkingAnim ();
670
671 // wait until the transition finishes
672 while (runningIntroAnim ())
673 runCommAnimFrame ();
674 }
675
676 memset (&talkingState, 0, sizeof talkingState);
677
678 if (wait_track == 0)
679 { // Restarting with a rewind
680 wait_track = WAIT_TRACK_ALL;
681 talkingState.rewind = true;
682 }
683 else if (!PlayingTrack ())
684 { // initial start of player
685 PlayTrack ();
686 assert (PlayingTrack ());
687 }
688
689 // Run the talking controls
690 SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
691 talkingState.InputFunc = DoTalkSegue;
692 talkingState.waitTrack = wait_track;
693 DoInput (&talkingState, FALSE);
694
695 ClearSubtitles ();
696
697 if (talkingState.ended)
698 { // reached the end; set STOP icon
699 SetSliderImage (SetAbsFrameIndex (ActivityFrame, 8));
700 }
701
702 // transition back to silent, if necessary
703 if (runningTalkingAnim ())
704 setStopTalkingAnim ();
705
706 // Wait until the animation task stops "talking"
707 while (runningTalkingAnim ())
708 runCommAnimFrame ();
709
710 return talkingState.ended;
711 }
712
713 static void
CommIntroTransition(void)714 CommIntroTransition (void)
715 {
716 if (curIntroMode == CIM_CROSSFADE_SCREEN)
717 {
718 ScreenTransition (3, NULL);
719 UnbatchGraphics ();
720 }
721 else if (curIntroMode == CIM_CROSSFADE_SPACE)
722 {
723 RECT r;
724 r.corner.x = SIS_ORG_X;
725 r.corner.y = SIS_ORG_Y;
726 r.extent.width = SIS_SCREEN_WIDTH;
727 r.extent.height = SIS_SCREEN_HEIGHT;
728 ScreenTransition (3, &r);
729 UnbatchGraphics ();
730 }
731 else if (curIntroMode == CIM_CROSSFADE_WINDOW)
732 {
733 ScreenTransition (3, &CommWndRect);
734 UnbatchGraphics ();
735 }
736 else if (curIntroMode == CIM_FADE_IN_SCREEN)
737 {
738 UnbatchGraphics ();
739 FadeScreen (FadeAllToColor, fadeTime);
740 }
741 else
742 { // Uknown transition
743 // Have to unbatch anyway or no more graphics, ever
744 UnbatchGraphics ();
745 assert (0 && "Unknown comm intro transition");
746 }
747
748 // Reset the mode for next time. Everything that needs a
749 // different one will let us know.
750 curIntroMode = CIM_DEFAULT;
751 }
752
753 void
AlienTalkSegue(COUNT wait_track)754 AlienTalkSegue (COUNT wait_track)
755 {
756 // this skips any talk segues that follow an aborted one
757 if ((GLOBAL (CurrentActivity) & CHECK_ABORT) || TalkingFinished)
758 return;
759
760 if (!pCurInputState->Initialized)
761 {
762 InitSpeechGraphics ();
763 SetColorMap (GetColorMapAddress (CommData.AlienColorMap));
764 SetContext (AnimContext);
765 DrawAlienFrame (NULL, 0, TRUE);
766 UpdateSpeechGraphics ();
767 CommIntroTransition ();
768
769 pCurInputState->Initialized = TRUE;
770
771 PlayMusic (CommData.AlienSong, TRUE, 1);
772 SetMusicVolume (BACKGROUND_VOL);
773
774 InitCommAnimations ();
775
776 LastActivity &= ~CHECK_LOAD;
777 }
778
779 TalkingFinished = TalkSegue (wait_track);
780 if (TalkingFinished)
781 FadeMusic (FOREGROUND_VOL, ONE_SECOND);
782 }
783
784
785 typedef struct summary_state
786 {
787 // standard state required by DoInput
788 BOOLEAN (*InputFunc) (struct summary_state *pSS);
789
790 // extended state
791 BOOLEAN Initialized;
792 BOOLEAN PrintNext;
793 SUBTITLE_REF NextSub;
794 const UNICODE *LeftOver;
795
796 } SUMMARY_STATE;
797
798 static BOOLEAN
DoConvSummary(SUMMARY_STATE * pSS)799 DoConvSummary (SUMMARY_STATE *pSS)
800 {
801 #define DELTA_Y_SUMMARY 8
802 #define MAX_SUMM_ROWS ((SIS_SCREEN_HEIGHT - SLIDER_Y - SLIDER_HEIGHT) \
803 / DELTA_Y_SUMMARY) - 1
804
805 if (!pSS->Initialized)
806 {
807 pSS->PrintNext = TRUE;
808 pSS->NextSub = GetFirstTrackSubtitle ();
809 pSS->LeftOver = NULL;
810 pSS->InputFunc = DoConvSummary;
811 pSS->Initialized = TRUE;
812 DoInput (pSS, FALSE);
813 }
814 else if (GLOBAL (CurrentActivity) & CHECK_ABORT)
815 {
816 return FALSE; // bail out
817 }
818 else if (PulsedInputState.menu[KEY_MENU_SELECT]
819 || PulsedInputState.menu[KEY_MENU_CANCEL]
820 || PulsedInputState.menu[KEY_MENU_RIGHT])
821 {
822 if (pSS->NextSub)
823 { // we want the next page
824 pSS->PrintNext = TRUE;
825 }
826 else
827 { // no more, we are done
828 return FALSE;
829 }
830 }
831 else if (pSS->PrintNext)
832 { // print the next page
833 RECT r;
834 TEXT t;
835 int row;
836
837 r.corner.x = 0;
838 r.corner.y = 0;
839 r.extent.width = SIS_SCREEN_WIDTH;
840 r.extent.height = SIS_SCREEN_HEIGHT - SLIDER_Y - SLIDER_HEIGHT + 2;
841
842 SetContext (AnimContext);
843 SetContextForeGroundColor (COMM_HISTORY_BACKGROUND_COLOR);
844 DrawFilledRectangle (&r);
845
846 SetContextForeGroundColor (COMM_HISTORY_TEXT_COLOR);
847
848 r.extent.width -= 2 + 2;
849 t.baseline.x = 2;
850 t.align = ALIGN_LEFT;
851 t.baseline.y = DELTA_Y_SUMMARY;
852 SetContextFont (TinyFont);
853
854 for (row = 0; row < MAX_SUMM_ROWS && pSS->NextSub;
855 ++row, pSS->NextSub = GetNextTrackSubtitle (pSS->NextSub))
856 {
857 const char *next = NULL;
858
859 if (pSS->LeftOver)
860 { // some text left from last subtitle
861 t.pStr = pSS->LeftOver;
862 pSS->LeftOver = NULL;
863 }
864 else
865 {
866 t.pStr = GetTrackSubtitleText (pSS->NextSub);
867 if (!t.pStr)
868 continue;
869 }
870
871 t.CharCount = (COUNT)~0;
872 for ( ; row < MAX_SUMM_ROWS &&
873 !getLineWithinWidth (&t, &next, r.extent.width, (COUNT)~0);
874 ++row)
875 {
876 font_DrawText (&t);
877 t.baseline.y += DELTA_Y_SUMMARY;
878 t.pStr = next;
879 t.CharCount = (COUNT)~0;
880 }
881
882 if (row >= MAX_SUMM_ROWS)
883 { // no more space on screen, but some text left over
884 // from the current subtitle
885 pSS->LeftOver = next;
886 break;
887 }
888
889 // this subtitle fit completely
890 font_DrawText (&t);
891 t.baseline.y += DELTA_Y_SUMMARY;
892 }
893
894 if (row >= MAX_SUMM_ROWS && (pSS->NextSub || pSS->LeftOver))
895 { // draw *MORE*
896 TEXT mt;
897 UNICODE buffer[80];
898
899 mt.baseline.x = SIS_SCREEN_WIDTH >> 1;
900 mt.baseline.y = t.baseline.y;
901 mt.align = ALIGN_CENTER;
902 snprintf (buffer, sizeof (buffer), "%s%s%s", // "MORE"
903 STR_MIDDLE_DOT, GAME_STRING (FEEDBACK_STRING_BASE + 1),
904 STR_MIDDLE_DOT);
905 mt.pStr = buffer;
906 SetContextForeGroundColor (COMM_MORE_TEXT_COLOR);
907 font_DrawText (&mt);
908 }
909
910
911 pSS->PrintNext = FALSE;
912 }
913 else
914 {
915 SleepThread (ONE_SECOND / 20);
916 }
917
918 return TRUE; // keep going
919 }
920
921 // Called when the player presses the select button on a response.
922 static void
SelectResponse(ENCOUNTER_STATE * pES)923 SelectResponse (ENCOUNTER_STATE *pES)
924 {
925 TEXT *response_text =
926 &pES->response_list[pES->cur_response].response_text;
927 utf8StringCopy (pES->phrase_buf, sizeof pES->phrase_buf,
928 response_text->pStr);
929 FeedbackPlayerPhrase (pES->phrase_buf);
930 StopTrack ();
931 ClearSubtitles ();
932 SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2));
933
934 FadeMusic (BACKGROUND_VOL, ONE_SECOND);
935
936 TalkingFinished = FALSE;
937 pES->num_responses = 0;
938 (*pES->response_list[pES->cur_response].response_func)
939 (pES->response_list[pES->cur_response].response_ref);
940 }
941
942 // Called when the player presses the cancel button in comm.
943 static void
SelectConversationSummary(ENCOUNTER_STATE * pES)944 SelectConversationSummary (ENCOUNTER_STATE *pES)
945 {
946 SUMMARY_STATE SummaryState;
947
948 if (pES)
949 FeedbackPlayerPhrase (pES->phrase_buf);
950
951 SummaryState.Initialized = FALSE;
952 DoConvSummary (&SummaryState);
953
954 if (pES)
955 RefreshResponses (pES);
956 clear_subtitles = TRUE;
957 }
958
959 static void
SelectReplay(ENCOUNTER_STATE * pES)960 SelectReplay (ENCOUNTER_STATE *pES)
961 {
962 FadeMusic (BACKGROUND_VOL, ONE_SECOND);
963 if (pES)
964 FeedbackPlayerPhrase (pES->phrase_buf);
965
966 TalkSegue (0);
967 }
968
969 static void
PlayerResponseInput(ENCOUNTER_STATE * pES)970 PlayerResponseInput (ENCOUNTER_STATE *pES)
971 {
972 BYTE response;
973
974 if (pES->top_response == (BYTE)~0)
975 {
976 pES->top_response = 0;
977 RefreshResponses (pES);
978 }
979
980 if (PulsedInputState.menu[KEY_MENU_SELECT])
981 {
982 SelectResponse (pES);
983 }
984 else if (PulsedInputState.menu[KEY_MENU_CANCEL] &&
985 LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
986 {
987 SelectConversationSummary (pES);
988 }
989 else
990 {
991 response = pES->cur_response;
992 if (PulsedInputState.menu[KEY_MENU_LEFT])
993 {
994 SelectReplay (pES);
995
996 if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
997 {
998 RefreshResponses (pES);
999 FadeMusic (FOREGROUND_VOL, ONE_SECOND);
1000 }
1001 }
1002 else if (PulsedInputState.menu[KEY_MENU_UP])
1003 response = (BYTE)((response + (BYTE)(pES->num_responses - 1))
1004 % pES->num_responses);
1005 else if (PulsedInputState.menu[KEY_MENU_DOWN])
1006 response = (BYTE)((BYTE)(response + 1) % pES->num_responses);
1007
1008 if (response != pES->cur_response)
1009 {
1010 COORD y;
1011
1012 BatchGraphics ();
1013 add_text (-2,
1014 &pES->response_list[pES->cur_response].response_text);
1015
1016 pES->cur_response = response;
1017
1018 y = add_text (-1,
1019 &pES->response_list[pES->cur_response].response_text);
1020 if (response < pES->top_response)
1021 {
1022 pES->top_response = 0;
1023 RefreshResponses (pES);
1024 }
1025 else if (y > SIS_SCREEN_HEIGHT)
1026 {
1027 pES->top_response = response;
1028 RefreshResponses (pES);
1029 }
1030 UnbatchGraphics ();
1031 }
1032
1033 UpdateCommGraphics ();
1034
1035 SleepThreadUntil (pES->NextTime);
1036 pES->NextTime = GetTimeCounter () + COMM_ANIM_RATE;
1037 }
1038 }
1039
1040 // Derived from INPUT_STATE_DESC
1041 typedef struct last_replay_state
1042 {
1043 // Fields required by DoInput()
1044 BOOLEAN (*InputFunc) (struct last_replay_state *);
1045
1046 TimeCount NextTime; // framerate control
1047 TimeCount TimeOut;
1048
1049 } LAST_REPLAY_STATE;
1050
1051 static BOOLEAN
DoLastReplay(LAST_REPLAY_STATE * pLRS)1052 DoLastReplay (LAST_REPLAY_STATE *pLRS)
1053 {
1054 if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1055 return FALSE;
1056
1057 if (GetTimeCounter () > pLRS->TimeOut)
1058 return FALSE; // timed out and done
1059
1060 if (PulsedInputState.menu[KEY_MENU_CANCEL] &&
1061 LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
1062 {
1063 FadeMusic (BACKGROUND_VOL, ONE_SECOND);
1064 SelectConversationSummary (NULL);
1065 pLRS->TimeOut = FadeMusic (0, ONE_SECOND * 2) + ONE_SECOND / 60;
1066 }
1067 else if (PulsedInputState.menu[KEY_MENU_LEFT])
1068 {
1069 SelectReplay (NULL);
1070 pLRS->TimeOut = FadeMusic (0, ONE_SECOND * 2) + ONE_SECOND / 60;
1071 }
1072
1073 UpdateCommGraphics ();
1074
1075 SleepThreadUntil (pLRS->NextTime);
1076 pLRS->NextTime = GetTimeCounter () + COMM_ANIM_RATE;
1077
1078 return TRUE;
1079 }
1080
1081 static BOOLEAN
DoCommunication(ENCOUNTER_STATE * pES)1082 DoCommunication (ENCOUNTER_STATE *pES)
1083 {
1084 SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN, MENU_SOUND_SELECT);
1085
1086 // First, finish playing all queued tracks if not done yet
1087 if (!TalkingFinished)
1088 {
1089 AlienTalkSegue (WAIT_TRACK_ALL);
1090 return TRUE;
1091 }
1092
1093 if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1094 ;
1095 else if (pES->num_responses == 0)
1096 {
1097 // The player doesn't get a chance to say anything,
1098 // but can still review alien's last phrases.
1099 LAST_REPLAY_STATE replayState;
1100
1101 memset (&replayState, 0, sizeof replayState);
1102 replayState.TimeOut = FadeMusic (0, ONE_SECOND * 3) + ONE_SECOND / 60;
1103 replayState.InputFunc = DoLastReplay;
1104 DoInput (&replayState, FALSE);
1105 }
1106 else
1107 {
1108 PlayerResponseInput (pES);
1109 return TRUE;
1110 }
1111
1112 SetContext (SpaceContext);
1113 DestroyContext (AnimContext);
1114 AnimContext = NULL;
1115
1116 FlushColorXForms ();
1117 ClearSubtitles ();
1118
1119 StopMusic ();
1120 StopSound ();
1121 StopTrack ();
1122 SleepThreadUntil (FadeMusic (NORMAL_VOLUME, 0) + ONE_SECOND / 60);
1123
1124 return FALSE;
1125 }
1126
1127 void
DoResponsePhrase(RESPONSE_REF R,RESPONSE_FUNC response_func,UNICODE * ConstructStr)1128 DoResponsePhrase (RESPONSE_REF R, RESPONSE_FUNC response_func,
1129 UNICODE *ConstructStr)
1130 {
1131 ENCOUNTER_STATE *pES = pCurInputState;
1132 RESPONSE_ENTRY *pEntry;
1133
1134 if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1135 return;
1136
1137 if (pES->num_responses == 0)
1138 {
1139 pES->cur_response = 0;
1140 pES->top_response = (BYTE)~0;
1141 }
1142
1143 pEntry = &pES->response_list[pES->num_responses];
1144 pEntry->response_ref = R;
1145 pEntry->response_text.pStr = ConstructStr;
1146 if (pEntry->response_text.pStr)
1147 pEntry->response_text.CharCount = (COUNT)~0;
1148 else
1149 {
1150 STRING locString;
1151
1152 locString = SetAbsStringTableIndex (CommData.ConversationPhrases,
1153 (COUNT) (R - 1));
1154 pEntry->response_text.pStr =
1155 (UNICODE *) GetStringAddress (locString);
1156 pEntry->response_text.CharCount = GetStringLength (locString);
1157 //#define BVT_PROBLEM
1158 #ifdef BVT_PROBLEM
1159 if (pEntry->response_text.pStr[pEntry->response_text.CharCount - 1]
1160 == '\0')
1161 --pEntry->response_text.CharCount;
1162 #endif /* BVT_PROBLEM */
1163 }
1164 pEntry->response_func = response_func;
1165 ++pES->num_responses;
1166 }
1167
1168 static void
HailAlien(void)1169 HailAlien (void)
1170 {
1171 ENCOUNTER_STATE ES;
1172 FONT PlayerFont, OldFont;
1173 MUSIC_REF SongRef = 0;
1174 Color TextBack;
1175
1176 pCurInputState = &ES;
1177 memset (pCurInputState, 0, sizeof (*pCurInputState));
1178
1179 TalkingFinished = FALSE;
1180
1181 ES.InputFunc = DoCommunication;
1182 PlayerFont = LoadFont (PLAYER_FONT);
1183
1184 CommData.AlienFrame = CaptureDrawable (
1185 LoadGraphic (CommData.AlienFrameRes));
1186 CommData.AlienFont = LoadFont (CommData.AlienFontRes);
1187 CommData.AlienColorMap = CaptureColorMap (
1188 LoadColorMap (CommData.AlienColorMapRes));
1189 if ((CommData.AlienSongFlags & LDASF_USE_ALTERNATE)
1190 && CommData.AlienAltSongRes)
1191 SongRef = LoadMusic (CommData.AlienAltSongRes);
1192 if (SongRef)
1193 CommData.AlienSong = SongRef;
1194 else
1195 CommData.AlienSong = LoadMusic (CommData.AlienSongRes);
1196
1197 CommData.ConversationPhrases = CaptureStringTable (
1198 LoadStringTable (CommData.ConversationPhrasesRes));
1199
1200 SubtitleText.baseline = CommData.AlienTextBaseline;
1201 SubtitleText.align = CommData.AlienTextAlign;
1202
1203
1204 // init subtitle cache context
1205 TextCacheContext = CreateContext ("TextCacheContext");
1206 TextCacheFrame = CaptureDrawable (
1207 CreateDrawable (WANT_PIXMAP, SIS_SCREEN_WIDTH,
1208 SIS_SCREEN_HEIGHT - SLIDER_Y - SLIDER_HEIGHT + 2, 1));
1209 SetContext (TextCacheContext);
1210 SetContextFGFrame (TextCacheFrame);
1211 TextBack = BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x10), 0x00);
1212 // Color key for the background.
1213 SetContextBackGroundColor (TextBack);
1214 ClearDrawable ();
1215 SetFrameTransparentColor (TextCacheFrame, TextBack);
1216
1217 ES.phrase_buf[0] = '\0';
1218
1219 SetContext (SpaceContext);
1220 OldFont = SetContextFont (PlayerFont);
1221
1222 {
1223 RECT r;
1224
1225 AnimContext = CreateContext ("AnimContext");
1226 SetContext (AnimContext);
1227 SetContextFGFrame (Screen);
1228 GetFrameRect (CommData.AlienFrame, &r);
1229 r.extent.width = SIS_SCREEN_WIDTH;
1230 CommWndRect.extent = r.extent;
1231
1232 SetTransitionSource (NULL);
1233 BatchGraphics ();
1234 if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
1235 {
1236 r.corner = CommWndRect.corner;
1237 SetContextClipRect (&r);
1238 }
1239 else
1240 {
1241 r.corner.x = SIS_ORG_X;
1242 r.corner.y = SIS_ORG_Y;
1243 SetContextClipRect (&r);
1244 CommWndRect.corner = r.corner;
1245
1246 DrawSISFrame ();
1247 // TODO: find a better way to do this, perhaps set the titles
1248 // forward from callers.
1249 if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) == (BYTE)~0
1250 && GET_GAME_STATE (STARBASE_AVAILABLE))
1251 { // Talking to allied Starbase
1252 DrawSISMessage (GAME_STRING (STARBASE_STRING_BASE + 1));
1253 // "Starbase Commander"
1254 DrawSISTitle (GAME_STRING (STARBASE_STRING_BASE + 0));
1255 // "Starbase"
1256 }
1257 else
1258 { // Default titles: star name + planet name
1259 DrawSISMessage (NULL);
1260 DrawSISTitle (GLOBAL_SIS (PlanetName));
1261 }
1262 }
1263
1264 DrawSISComWindow ();
1265 }
1266
1267
1268 LastActivity |= CHECK_LOAD; /* prevent spurious input */
1269 (*CommData.init_encounter_func) ();
1270 DoInput (&ES, FALSE);
1271 if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
1272 (*CommData.post_encounter_func) ();
1273 (*CommData.uninit_encounter_func) ();
1274
1275 SetContext (SpaceContext);
1276 SetContextFont (OldFont);
1277
1278 DestroyStringTable (ReleaseStringTable (CommData.ConversationPhrases));
1279 DestroyMusic (CommData.AlienSong);
1280 DestroyColorMap (ReleaseColorMap (CommData.AlienColorMap));
1281 DestroyFont (CommData.AlienFont);
1282 DestroyDrawable (ReleaseDrawable (CommData.AlienFrame));
1283
1284 DestroyContext (TextCacheContext);
1285 DestroyDrawable (ReleaseDrawable (TextCacheFrame));
1286
1287 DestroyFont (PlayerFont);
1288
1289 // Some support code tests either of these to see if the
1290 // game is currently in comm or encounter
1291 CommData.ConversationPhrasesRes = 0;
1292 CommData.ConversationPhrases = 0;
1293 pCurInputState = 0;
1294 }
1295
1296 void
SetCommIntroMode(CommIntroMode newMode,TimeCount howLong)1297 SetCommIntroMode (CommIntroMode newMode, TimeCount howLong)
1298 {
1299 curIntroMode = newMode;
1300 fadeTime = howLong;
1301 }
1302
1303 COUNT
InitCommunication(CONVERSATION which_comm)1304 InitCommunication (CONVERSATION which_comm)
1305 {
1306 COUNT status;
1307 LOCDATA *LocDataPtr;
1308
1309 #ifdef DEBUG
1310 if (disableInteractivity)
1311 return 0;
1312 #endif
1313
1314
1315 if (LastActivity & CHECK_LOAD)
1316 {
1317 LastActivity &= ~CHECK_LOAD;
1318 if (which_comm != COMMANDER_CONVERSATION)
1319 {
1320 if (LOBYTE (LastActivity) == 0)
1321 {
1322 DrawSISFrame ();
1323 }
1324 else
1325 {
1326 ClearSISRect (DRAW_SIS_DISPLAY);
1327 RepairSISBorder ();
1328 }
1329 DrawSISMessage (NULL);
1330 if (inHQSpace ())
1331 DrawHyperCoords (GLOBAL (ShipStamp.origin));
1332 else if (GLOBAL (ip_planet) == 0)
1333 DrawHyperCoords (CurStarDescPtr->star_pt);
1334 else
1335 DrawSISTitle (GLOBAL_SIS (PlanetName));
1336 }
1337 }
1338
1339
1340 if (which_comm == URQUAN_DRONE_CONVERSATION)
1341 {
1342 status = URQUAN_DRONE_SHIP;
1343 which_comm = URQUAN_CONVERSATION;
1344 }
1345 else
1346 {
1347 if (which_comm == YEHAT_REBEL_CONVERSATION)
1348 {
1349 status = YEHAT_REBEL_SHIP;
1350 which_comm = YEHAT_CONVERSATION;
1351 }
1352 else
1353 {
1354 COUNT commToShip[] = {
1355 RACE_SHIP_FOR_COMM
1356 };
1357 status = commToShip[which_comm];
1358 if (status >= YEHAT_REBEL_SHIP) {
1359 /* conversation exception, set to self */
1360 status = HUMAN_SHIP;
1361 }
1362 }
1363 StartSphereTracking (status);
1364
1365 if (which_comm == ORZ_CONVERSATION
1366 || (which_comm == TALKING_PET_CONVERSATION
1367 && (!GET_GAME_STATE (TALKING_PET_ON_SHIP)
1368 || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE))
1369 || (which_comm != CHMMR_CONVERSATION
1370 && which_comm != SYREEN_CONVERSATION
1371 ))//&& CheckAlliance (status) == BAD_GUY))
1372 BuildBattle (NPC_PLAYER_NUM);
1373 }
1374
1375 LocDataPtr = init_race (
1376 status != YEHAT_REBEL_SHIP ? which_comm :
1377 YEHAT_REBEL_CONVERSATION);
1378 if (LocDataPtr)
1379 { // We make a copy here
1380 CommData = *LocDataPtr;
1381 }
1382
1383 if (GET_GAME_STATE (BATTLE_SEGUE) == 0)
1384 {
1385 // Not offered the chance to attack.
1386 status = HAIL;
1387 }
1388 else if ((status = InitEncounter ()) == HAIL && LocDataPtr)
1389 {
1390 // The player chose to talk.
1391 SET_GAME_STATE (BATTLE_SEGUE, 0);
1392 }
1393 else
1394 {
1395 // The player chose to attack.
1396 status = ATTACK;
1397 SET_GAME_STATE (BATTLE_SEGUE, 1);
1398 }
1399
1400 if (status == HAIL)
1401 {
1402 HailAlien ();
1403 }
1404 else if (LocDataPtr)
1405 { // only when comm initied successfully
1406 if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
1407 (*CommData.post_encounter_func) (); // process states
1408
1409 (*CommData.uninit_encounter_func) (); // cleanup
1410 }
1411
1412 status = 0;
1413 if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
1414 {
1415 // The Sa-Matra battle is skipped when Cyborg is enabled.
1416 // Most likely because the Cyborg is too dumb to know what
1417 // to do in this battle.
1418 if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE
1419 && (GLOBAL (glob_flags) & CYBORG_ENABLED))
1420 ReinitQueue (&GLOBAL (npc_built_ship_q));
1421
1422 // Clear the location flags
1423 SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 0);
1424 status = (GET_GAME_STATE (BATTLE_SEGUE)
1425 && GetHeadLink (&GLOBAL (npc_built_ship_q)));
1426 if (status)
1427 {
1428 // Start combat
1429 BuildBattle (RPG_PLAYER_NUM);
1430 EncounterBattle ();
1431 }
1432 else
1433 {
1434 SET_GAME_STATE (BATTLE_SEGUE, 0);
1435 }
1436 }
1437
1438 UninitEncounter ();
1439
1440 return (status);
1441 }
1442
1443 void
RaceCommunication(void)1444 RaceCommunication (void)
1445 {
1446 COUNT i, status;
1447 HSHIPFRAG hStarShip;
1448 SHIP_FRAGMENT *FragPtr;
1449 HENCOUNTER hEncounter = 0;
1450 CONVERSATION RaceComm[] =
1451 {
1452 RACE_COMMUNICATION
1453 };
1454
1455 if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
1456 {
1457 /* Going into talking pet conversation */
1458 ReinitQueue (&GLOBAL (npc_built_ship_q));
1459 CloneShipFragment (SAMATRA_SHIP, &GLOBAL (npc_built_ship_q), 0);
1460 InitCommunication (TALKING_PET_CONVERSATION);
1461 if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
1462 && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
1463 {
1464 GLOBAL (CurrentActivity) = WON_LAST_BATTLE;
1465 }
1466 return;
1467 }
1468 else if (NextActivity & CHECK_LOAD)
1469 {
1470 BYTE ec;
1471
1472 ec = GET_GAME_STATE (ESCAPE_COUNTER);
1473
1474 if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1)
1475 InitCommunication (SPATHI_CONVERSATION);
1476 else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) == 0)
1477 InitCommunication (TALKING_PET_CONVERSATION);
1478 else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) &
1479 ((1 << 4) | (1 << 5)))
1480 // Communicate with the Ilwrath using a Hyperwave Broadcaster.
1481 InitCommunication (ILWRATH_CONVERSATION);
1482 else
1483 InitCommunication (CHMMR_CONVERSATION);
1484 if (GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
1485 {
1486 NextActivity = GLOBAL (CurrentActivity) & ~START_ENCOUNTER;
1487 if (LOBYTE (NextActivity) == IN_INTERPLANETARY)
1488 NextActivity |= START_INTERPLANETARY;
1489 GLOBAL (CurrentActivity) |= CHECK_LOAD; /* fake a load game */
1490 }
1491
1492 SET_GAME_STATE (ESCAPE_COUNTER, ec);
1493 return;
1494 }
1495 else if (inHQSpace ())
1496 {
1497 ReinitQueue (&GLOBAL (npc_built_ship_q));
1498 if (GET_GAME_STATE (ARILOU_SPACE_SIDE) >= 2)
1499 {
1500 InitCommunication (ARILOU_CONVERSATION);
1501 return;
1502 }
1503 else
1504 {
1505 /* Encounter with a black globe in HS, prepare enemy ship list */
1506 ENCOUNTER *EncounterPtr;
1507
1508 // The encounter globe that the flagship collided with is moved
1509 // to the head of the queue in hyper.c:cleanup_hyperspace()
1510 hEncounter = GetHeadEncounter ();
1511 LockEncounter (hEncounter, &EncounterPtr);
1512
1513 for (i = 0; i < EncounterPtr->num_ships; ++i)
1514 {
1515 CloneShipFragment (EncounterPtr->race_id,
1516 &GLOBAL (npc_built_ship_q),
1517 EncounterPtr->ShipList[i].crew_level);
1518 }
1519
1520 // XXX: Bug: CurStarDescPtr was abused to point within
1521 // an ENCOUNTER struct, which is immediately unlocked
1522 //CurStarDescPtr = (STAR_DESC*)&EncounterPtr->SD;
1523 UnlockEncounter (hEncounter);
1524 }
1525 }
1526
1527 // First ship in the npc queue defines which alien race
1528 // the player will be talking to
1529 hStarShip = GetHeadLink (&GLOBAL (npc_built_ship_q));
1530 FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
1531 i = FragPtr->race_id;
1532 UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
1533
1534 status = InitCommunication (RaceComm[i]);
1535
1536 if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
1537 return;
1538
1539 if (i == CHMMR_SHIP)
1540 ReinitQueue (&GLOBAL (npc_built_ship_q));
1541
1542 if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
1543 {
1544 /* if used destruct code in interplanetary */
1545 if (i == SLYLANDRO_SHIP && status == 0)
1546 ReinitQueue (&GLOBAL (npc_built_ship_q));
1547 }
1548 else if (hEncounter)
1549 {
1550 /* Update HSpace encounter info, ships lefts, etc. */
1551 BYTE i, NumShips;
1552 ENCOUNTER *EncounterPtr;
1553
1554 LockEncounter (hEncounter, &EncounterPtr);
1555
1556 NumShips = CountLinks (&GLOBAL (npc_built_ship_q));
1557 EncounterPtr->num_ships = NumShips;
1558 EncounterPtr->flags |= ENCOUNTER_REFORMING;
1559 if (status == 0)
1560 EncounterPtr->flags |= ONE_SHOT_ENCOUNTER;
1561
1562 for (i = 0; i < NumShips; ++i)
1563 {
1564 HSHIPFRAG hStarShip;
1565 SHIP_FRAGMENT *FragPtr;
1566 BRIEF_SHIP_INFO *BSIPtr;
1567
1568 hStarShip = GetStarShipFromIndex (&GLOBAL (npc_built_ship_q), i);
1569 FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
1570 BSIPtr = &EncounterPtr->ShipList[i];
1571 BSIPtr->race_id = FragPtr->race_id;
1572 BSIPtr->crew_level = FragPtr->crew_level;
1573 BSIPtr->max_crew = FragPtr->max_crew;
1574 BSIPtr->max_energy = FragPtr->max_energy;
1575 UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
1576 }
1577
1578 UnlockEncounter (hEncounter);
1579 ReinitQueue (&GLOBAL (npc_built_ship_q));
1580 }
1581 }
1582
1583 static void
RedrawSubtitles(void)1584 RedrawSubtitles (void)
1585 {
1586 TEXT t;
1587
1588 if (!optSubtitles)
1589 return;
1590
1591 if (SubtitleText.pStr)
1592 {
1593 t = SubtitleText;
1594 add_text (1, &t);
1595 }
1596 }
1597
1598 static void
ClearSubtitles(void)1599 ClearSubtitles (void)
1600 {
1601 clear_subtitles = TRUE;
1602 last_subtitle = NULL;
1603 SubtitleText.pStr = NULL;
1604 SubtitleText.CharCount = 0;
1605 }
1606
1607 static void
CheckSubtitles(void)1608 CheckSubtitles (void)
1609 {
1610 const UNICODE *pStr;
1611 POINT baseline;
1612 TEXT_ALIGN align;
1613
1614 pStr = GetTrackSubtitle ();
1615 baseline = CommData.AlienTextBaseline;
1616 align = CommData.AlienTextAlign;
1617
1618 if (pStr != SubtitleText.pStr ||
1619 SubtitleText.baseline.x != baseline.x ||
1620 SubtitleText.baseline.y != baseline.y ||
1621 SubtitleText.align != align)
1622 { // Subtitles changed
1623 clear_subtitles = TRUE;
1624 // Baseline may be updated by the ZFP
1625 SubtitleText.baseline = baseline;
1626 SubtitleText.align = align;
1627 // Make a note in the logs if the update was multiframe
1628 if (SubtitleText.pStr == pStr)
1629 {
1630 log_add (log_Warning, "Dialog text and location changed out of sync");
1631 }
1632
1633 SubtitleText.pStr = pStr;
1634 // may have been cleared too
1635 if (pStr)
1636 SubtitleText.CharCount = (COUNT)~0;
1637 else
1638 SubtitleText.CharCount = 0;
1639 }
1640 }
1641
1642 void
EnableTalkingAnim(BOOLEAN enable)1643 EnableTalkingAnim (BOOLEAN enable)
1644 {
1645 if (enable)
1646 CommData.AlienTalkDesc.AnimFlags &= ~PAUSE_TALKING;
1647 else
1648 CommData.AlienTalkDesc.AnimFlags |= PAUSE_TALKING;
1649 }
1650