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