1 #include "Dialogue_Control.h"
2 #include "AI.h"
3 #include "AIMMembers.h"
4 #include "Assignments.h"
5 #include "Civ_Quotes.h"
6 #include "ContentManager.h"
7 #include "Cursors.h"
8 #include "Dialogs.h"
9 #include "Directories.h"
10 #include "Faces.h"
11 #include "Facts.h"
12 #include "Font.h"
13 #include "Font_Control.h"
14 #include "Game_Clock.h"
15 #include "GameInstance.h"
16 #include "GameLoop.h"
17 #include "GameRes.h"
18 #include "GameScreen.h"
19 #include "GameSettings.h"
20 #include "Handle_UI.h"
21 #include "Interface_Control.h"
22 #include "Interface_Dialogue.h"
23 #include "Interface_Utils.h"
24 #include "Isometric_Utils.h"
25 #include "JAScreens.h"
26 #include "Logger.h"
27 #include "LOS.h"
28 #include "Map_Screen_Interface.h"
29 #include "MapScreen.h"
30 #include "Meanwhile.h"
31 #include "MercProfile.h"
32 #include "Mercs.h"
33 #include "MercTextBox.h"
34 #include "Message.h"
35 #include "MouseSystem.h"
36 #include "NPC.h"
37 #include "OppList.h"
38 #include "Overhead.h"
39 #include "Overhead_Types.h"
40 #include "PreBattle_Interface.h"
41 #include "QArray.h"
42 #include "Quests.h"
43 #include "Random.h"
44 #include "Render_Dirty.h"
45 #include "RenderWorld.h"
46 #include "ScreenIDs.h"
47 #include "ShopKeeper_Interface.h"
48 #include "SkillCheck.h"
49 #include "Soldier_Macros.h"
50 #include "SoundMan.h"
51 #include "Squads.h"
52 #include "StrategicMap.h"
53 #include "UILayout.h"
54 #include "Video.h"
55 #include "VObject.h"
56 #include "VSurface.h"
57 #include "WCheck.h"
58 #include "WordWrap.h"
59 #include "WorldMan.h"
60 #include <map>
61 #include <queue>
62 #include <string_theory/format>
63 struct MercPopUpBox;
64 
65 
66 #define QUOTE_MESSAGE_SIZE			520
67 
68 #define DIALOGUE_DEFAULT_SUBTITLE_WIDTH	200
69 #define TEXT_DELAY_MODIFIER			60
70 
71 
72 typedef std::queue<DialogueEvent*>DialogueQueue;
73 
74 BOOLEAN fExternFacesLoaded = FALSE;
75 
76 static std::map<ProfileID, FACETYPE*> externalNPCFaces;
77 
78 const ProfileID preloadedExternalNPCFaces[] = {
79 	SKYRIDER,
80 	FRED,
81 	MATT,
82 	OSWALD,
83 	CALVIN,
84 	CARL
85 };
86 
87 
88 static UINT8 const gubMercValidPrecedentQuoteID[] =
89 {
90 	QUOTE_REPUTATION_REFUSAL,
91 	QUOTE_DEATH_RATE_REFUSAL,
92 	QUOTE_LAME_REFUSAL,
93 	QUOTE_WONT_RENEW_CONTRACT_LAME_REFUSAL,
94 	QUOTE_HATE_MERC_1_ON_TEAM,
95 	QUOTE_HATE_MERC_2_ON_TEAM,
96 	QUOTE_LEARNED_TO_HATE_MERC_ON_TEAM,
97 	QUOTE_REFUSAL_RENEW_DUE_TO_MORALE,
98 	QUOTE_REFUSAL_TO_JOIN_LACK_OF_FUNDS,
99 	QUOTE_DEATH_RATE_RENEWAL,
100 	QUOTE_HATE_MERC_1_ON_TEAM_WONT_RENEW,
101 	QUOTE_HATE_MERC_2_ON_TEAM_WONT_RENEW,
102 	QUOTE_LEARNED_TO_HATE_MERC_1_ON_TEAM_WONT_RENEW
103 };
104 
105 
106 static UINT16 const gusStopTimeQuoteList[] =
107 {
108 	QUOTE_BOOBYTRAP_ITEM,
109 	QUOTE_SUSPICIOUS_GROUND
110 };
111 
112 
113 // QUEUE UP DIALOG!
114 static DialogueQueue ghDialogueQ;
115 FACETYPE *gpCurrentTalkingFace	= NULL;
116 static ProfileID       gubCurrentTalkingID = NO_PROFILE;
117 static DialogueHandler gbUIHandlerID;
118 
119 INT32 giNPCReferenceCount = 0;
120 
121 static INT16 gsExternPanelXPosition;
122 static INT16 gsExternPanelYPosition;
123 
124 static BOOLEAN        gfDialogueQueuePaused = FALSE;
125 static UINT16         gusSubtitleBoxWidth;
126 static UINT16         gusSubtitleBoxHeight;
127 static VIDEO_OVERLAY* g_text_box_overlay = NULL;
128 BOOLEAN               gfFacePanelActive = FALSE;
129 static UINT32         guiScreenIDUsedWhenUICreated;
130 static MOUSE_REGION   gTextBoxMouseRegion;
131 static MOUSE_REGION   gFacePopupMouseRegion;
132 
133 MercPopUpBox* g_dialogue_box;
134 
135 
136 static BOOLEAN fWasPausedDuringDialogue = FALSE;
137 
138 static INT8 gubLogForMeTooBleeds = FALSE;
139 
140 
141 // has the text region been created?
142 static BOOLEAN fTextBoxMouseRegionCreated  = FALSE;
143 static BOOLEAN fExternFaceBoxRegionCreated = FALSE;
144 
145 
146 static SGPVObject* guiCOMPANEL;
147 static SGPVObject* guiCOMPANELB;
148 
149 
DialogueActive()150 BOOLEAN DialogueActive( )
151 {
152 	if ( gpCurrentTalkingFace != NULL )
153 	{
154 		return( TRUE );
155 	}
156 
157 	return( FALSE );
158 }
159 
160 
InitalizeDialogueControl()161 void InitalizeDialogueControl()
162 {
163 	giNPCReferenceCount = 0;
164 	gsExternPanelXPosition = DEFAULT_EXTERN_PANEL_X_POS;
165 	gsExternPanelYPosition = DEFAULT_EXTERN_PANEL_Y_POS;
166 }
167 
ShutdownDialogueControl()168 void ShutdownDialogueControl()
169 {
170 	EmptyDialogueQueue();
171 
172 	// shutdown external static NPC faces
173 	UnloadExternalNPCFaces();
174 
175 	// gte rid of portraits for cars
176 	UnLoadCarPortraits();
177 }
178 
179 
LoadExternalNPCFace(ProfileID mercID)180 void LoadExternalNPCFace(ProfileID mercID)
181 {
182 	if (externalNPCFaces.find(mercID) == externalNPCFaces.end())
183 	{
184 		externalNPCFaces[mercID] = &InitFace(mercID, nullptr, FACE_FORCE_SMALL);
185 	}
186 }
187 
GetExternalNPCFace(ProfileID mercID)188 FACETYPE* GetExternalNPCFace(ProfileID mercID)
189 {
190 	LoadExternalNPCFace(mercID); // ensure we have loaded the face
191 	return externalNPCFaces.at(mercID);
192 }
193 
PreloadExternalNPCFaces()194 void PreloadExternalNPCFaces()
195 {
196 	// go and grab all external NPC faces that are needed for the game who won't exist as soldiertypes
197 
198 	if (fExternFacesLoaded) return;
199 
200 	fExternFacesLoaded = TRUE;
201 
202 	for (size_t i = 0; i < lengthof(preloadedExternalNPCFaces); i++)
203 	{
204 		LoadExternalNPCFace(preloadedExternalNPCFaces[i]);
205 	}
206 }
207 
208 
UnloadExternalNPCFaces()209 void UnloadExternalNPCFaces()
210 {
211 	if (!fExternFacesLoaded) return;
212 	fExternFacesLoaded = FALSE;
213 
214 	// Remove all external NPC faces.
215 	for (auto entry : externalNPCFaces)
216 	{
217 		DeleteFace(entry.second);
218 	}
219 }
220 
221 
EmptyDialogueQueue()222 void EmptyDialogueQueue()
223 {
224 	while(!ghDialogueQ.empty())
225 		ghDialogueQ.pop();
226 
227 	gfWaitingForTriggerTimer = FALSE;
228 }
229 
230 
DialogueQueueIsEmpty()231 BOOLEAN DialogueQueueIsEmpty( )
232 {
233 	return ghDialogueQ.empty();
234 }
235 
236 
DialogueQueueIsEmptyAndNobodyIsTalking()237 BOOLEAN	DialogueQueueIsEmptyAndNobodyIsTalking()
238 {
239 	if ( gpCurrentTalkingFace != NULL )
240 	{
241 		return( FALSE );
242 	}
243 
244 	if ( !DialogueQueueIsEmpty( ) )
245 	{
246 		return( FALSE );
247 	}
248 
249 	return( TRUE );
250 }
251 
DialogueAdvanceSpeech()252 void DialogueAdvanceSpeech( )
253 {
254 	// Shut them up!
255 	InternalShutupaYoFace(gpCurrentTalkingFace, FALSE);
256 }
257 
258 
StopAnyCurrentlyTalkingSpeech()259 void StopAnyCurrentlyTalkingSpeech( )
260 {
261 	// ATE; Make sure guys stop talking....
262 	if ( gpCurrentTalkingFace != NULL )
263 	{
264 		InternalShutupaYoFace(gpCurrentTalkingFace, TRUE);
265 	}
266 }
267 
268 
269 static void CheckForStopTimeQuotes(UINT16 usQuoteNum);
270 static void HandleTacticalSpeechUI(UINT8 ubCharacterNum, FACETYPE&);
271 
272 
HandleDialogue()273 void HandleDialogue()
274 {
275 	static BOOLEAN fOldEngagedInConvFlagOn = FALSE;
276 
277 	// we don't want to just delay action of some events, we want to pause the whole queue, regardless of the event
278 	if (gfDialogueQueuePaused) return;
279 
280 	bool const empty = ghDialogueQ.empty();
281 
282 	if (empty && gpCurrentTalkingFace == NULL)
283 	{
284 		HandlePendingInitConv();
285 	}
286 
287 	HandleCivQuote();
288 
289 	// Alrighty, check for a change in state, do stuff appropriately....
290 	// Turned on
291 	if (!fOldEngagedInConvFlagOn && gTacticalStatus.uiFlags & ENGAGED_IN_CONV)
292 	{
293 		// OK, we have just entered...
294 		fOldEngagedInConvFlagOn = TRUE;
295 
296 		PauseGame();
297 		LockPauseState(LOCK_PAUSE_ENGAGED_IN_CONV);
298 	}
299 	else if (fOldEngagedInConvFlagOn && !(gTacticalStatus.uiFlags & ENGAGED_IN_CONV))
300 	{
301 		// OK, we left...
302 		fOldEngagedInConvFlagOn = FALSE;
303 
304 		UnLockPauseState();
305 		UnPauseGame();
306 
307 		// if we're exiting boxing with the UI lock set then DON'T OVERRIDE THIS!
308 		if (!(gTacticalStatus.uiFlags & IGNORE_ENGAGED_IN_CONV_UI_UNLOCK))
309 		{
310 			switch (gTacticalStatus.bBoxingState)
311 			{
312 				case WON_ROUND:
313 				case LOST_ROUND:
314 				case DISQUALIFIED:
315 					break;
316 
317 				default:
318 					guiPendingOverrideEvent = LU_ENDUILOCK;
319 					HandleTacticalUI();
320 
321 					// ATE: If this is NOT the player's turn.. engage AI UI lock!
322 					if (gTacticalStatus.ubCurrentTeam != OUR_TEAM)
323 					{
324 						// Setup locked UI
325 						guiPendingOverrideEvent = LU_BEGINUILOCK;
326 						HandleTacticalUI();
327 					}
328 					break;
329 			}
330 		}
331 
332 		gTacticalStatus.uiFlags &= ~IGNORE_ENGAGED_IN_CONV_UI_UNLOCK;
333 	}
334 
335 	if (gTacticalStatus.uiFlags & ENGAGED_IN_CONV &&
336 		!gfInTalkPanel && // Are we in here because of the dialogue system up?
337 		guiPendingScreen != MSG_BOX_SCREEN && // ATE: NOT if we have a message box pending
338 		guiCurrentScreen != MSG_BOX_SCREEN)
339 	{
340 		// No, so we should lock the UI!
341 		guiPendingOverrideEvent = LU_BEGINUILOCK;
342 		HandleTacticalUI();
343 	}
344 
345 	// OK, check if we are still taking
346 	if (gpCurrentTalkingFace)
347 	{
348 		FACETYPE& f = *gpCurrentTalkingFace;
349 		if (f.fTalking)
350 		{
351 			// ATE: OK, MANAGE THE DISPLAY OF OUR CURRENTLY ACTIVE FACE IF WE / IT CHANGES STATUS
352 			// THINGS THAT CAN CHANGE STATUS:
353 			//   CHANGE TO MAPSCREEN
354 			//   CHANGE TO GAMESCREEN
355 			//   CHANGE IN MERC STATUS TO BE IN A SQUAD
356 			//   CHANGE FROM TEAM TO INV INTERFACE
357 
358 			// Where are we and where did this face once exist?
359 			if (guiScreenIDUsedWhenUICreated == GAME_SCREEN && guiCurrentScreen == MAP_SCREEN)
360 			{
361 				// GO FROM GAMESCREEN TO MAPSCREEN
362 
363 				// delete face panel if there is one!
364 				if (gfFacePanelActive)
365 				{
366 					// Set face inactive!
367 					if (f.video_overlay)
368 					{
369 						RemoveVideoOverlay(f.video_overlay);
370 						f.video_overlay = 0;
371 					}
372 
373 					if (fExternFaceBoxRegionCreated)
374 					{
375 						fExternFaceBoxRegionCreated = FALSE;
376 						MSYS_RemoveRegion(&gFacePopupMouseRegion);
377 					}
378 
379 					// Set face inactive....
380 					f.fCanHandleInactiveNow = TRUE;
381 					SetAutoFaceInActive(f);
382 					HandleTacticalSpeechUI(gubCurrentTalkingID, f);
383 
384 					// ATE: Force mapscreen to set face active again.....
385 					fReDrawFace = TRUE;
386 					DrawFace();
387 
388 					gfFacePanelActive = FALSE;
389 				}
390 
391 				guiScreenIDUsedWhenUICreated = guiCurrentScreen;
392 			}
393 			else if (guiScreenIDUsedWhenUICreated == MAP_SCREEN && guiCurrentScreen == GAME_SCREEN)
394 			{
395 				HandleTacticalSpeechUI(gubCurrentTalkingID, f);
396 				guiScreenIDUsedWhenUICreated = guiCurrentScreen;
397 			}
398 			return;
399 		}
400 
401 		// Check special flags
402 		// If we are done, check special face flag for trigger NPC!
403 		if (f.uiFlags & FACE_PCTRIGGER_NPC)
404 		{
405 			// Decrement refrence count...
406 			giNPCReferenceCount--;
407 
408 			TriggerNPCRecord(f.u.trigger.npc, f.u.trigger.record);
409 			//Reset flag!
410 			f.uiFlags &= ~FACE_PCTRIGGER_NPC;
411 		}
412 
413 		if (f.uiFlags & FACE_MODAL)
414 		{
415 			f.uiFlags &= ~FACE_MODAL;
416 			EndModalTactical();
417 			SLOGD("Ending Modal Tactical Quote.");
418 		}
419 
420 		if (f.uiFlags & FACE_TRIGGER_PREBATTLE_INT)
421 		{
422 			UnLockPauseState();
423 			InitPreBattleInterface(f.u.initiating_battle.group, true);
424 			//Reset flag!
425 			f.uiFlags &= ~FACE_TRIGGER_PREBATTLE_INT;
426 		}
427 
428 		gpCurrentTalkingFace = NULL;
429 		gubCurrentTalkingID = NO_PROFILE;
430 		gTacticalStatus.ubLastQuoteProfileNUm = NO_PROFILE;
431 
432 		if (fWasPausedDuringDialogue)
433 		{
434 			fWasPausedDuringDialogue = FALSE;
435 			UnLockPauseState();
436 			UnPauseGame();
437 		}
438 	}
439 
440 	if (empty)
441 	{
442 		if (gfMikeShouldSayHi == TRUE)
443 		{
444 			SOLDIERTYPE* const pMike = FindSoldierByProfileID(MIKE);
445 			if (pMike)
446 			{
447 				INT16 const sPlayerGridNo = ClosestPC(pMike, NULL);
448 				if (sPlayerGridNo != NOWHERE)
449 				{
450 					SOLDIERTYPE* const player = WhoIsThere2(sPlayerGridNo, 0);
451 					if (player != NULL)
452 					{
453 						InitiateConversation(pMike, player, NPC_INITIAL_QUOTE);
454 						gMercProfiles[pMike->ubProfile].ubMiscFlags2 |= PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE;
455 						// JA2Gold: special hack value of 2 to prevent dialogue from coming up more than once
456 						gfMikeShouldSayHi = 2;
457 					}
458 				}
459 			}
460 		}
461 
462 		return;
463 	}
464 
465 	// If here, pick current one from queue and play
466 	DialogueEvent*const d = ghDialogueQ.front();
467 
468 	// If we are in auto bandage, ignore any quotes!
469 	if (gTacticalStatus.fAutoBandageMode || !d->Execute())
470 	{
471 		delete d;
472 		if(!ghDialogueQ.empty()) ghDialogueQ.pop();
473 	}
474 }
475 
476 
Add(DialogueEvent * const d)477 void DialogueEvent::Add(DialogueEvent* const d)
478 {
479 	try
480 	{
481 		ghDialogueQ.push(d);
482 	}
483 	catch (...)
484 	{
485 		delete d;
486 		throw;
487 	}
488 }
489 
490 
MayExecute() const491 bool CharacterDialogueEvent::MayExecute() const
492 {
493 	return !SoundIsPlaying(soldier_.uiBattleSoundID);
494 }
495 
496 
MakeCharacterDialogueEventSleep(SOLDIERTYPE & s,bool const sleep)497 void MakeCharacterDialogueEventSleep(SOLDIERTYPE& s, bool const sleep)
498 {
499 	class CharacterDialogueEventSleep : public CharacterDialogueEvent
500 	{
501 		public:
502 			CharacterDialogueEventSleep(SOLDIERTYPE& soldier, bool const sleep) :
503 				CharacterDialogueEvent(soldier),
504 				sleep_(sleep)
505 			{}
506 
507 			bool Execute()
508 			{
509 				if (!MayExecute()) return true;
510 
511 				soldier_.fMercAsleep     = sleep_; // wake merc up or put them back down?
512 				fCharacterInfoPanelDirty = TRUE;
513 				fTeamPanelDirty          = TRUE;
514 				return false;
515 			}
516 
517 		private:
518 			bool const sleep_;
519 	};
520 
521 	DialogueEvent::Add(new CharacterDialogueEventSleep(s, sleep));
522 }
523 
524 
CanSayQuote(SOLDIERTYPE const & s,UINT16 const quote)525 static bool CanSayQuote(SOLDIERTYPE const& s, UINT16 const quote)
526 {
527 	if (s.ubProfile == NO_PROFILE)
528 		return false;
529 	INT8 const min_life = quote == QUOTE_SERIOUSLY_WOUNDED ? CONSCIOUSNESS : OKLIFE;
530 	if (s.bLife < min_life)
531 		return false;
532 	if (AM_A_ROBOT(&s))
533 		return false;
534 	if (s.uiStatusFlags & SOLDIER_GASSED)
535 		return false;
536 	if (s.bAssignment == ASSIGNMENT_POW)
537 		return false;
538 	return true;
539 }
540 
541 
DelayedTacticalCharacterDialogue(SOLDIERTYPE * pSoldier,UINT16 usQuoteNum)542 BOOLEAN DelayedTacticalCharacterDialogue( SOLDIERTYPE *pSoldier, UINT16 usQuoteNum )
543 {
544 	if (!CanSayQuote(*pSoldier, usQuoteNum))
545 		return FALSE;
546 	CharacterDialogue(pSoldier->ubProfile, usQuoteNum, pSoldier->face, DIALOGUE_TACTICAL_UI, TRUE, true);
547 	return TRUE;
548 }
549 
550 
TacticalCharacterDialogue(const SOLDIERTYPE * pSoldier,UINT16 usQuoteNum)551 BOOLEAN TacticalCharacterDialogue(const SOLDIERTYPE* pSoldier, UINT16 usQuoteNum)
552 {
553 	if (!CanSayQuote(*pSoldier, usQuoteNum))
554 		return FALSE;
555 
556 	if ( AreInMeanwhile( ) )
557 	{
558 		return( FALSE );
559 	}
560 
561 	// OK, let's check if this is the exact one we just played, if so, skip.
562 	if (pSoldier->ubProfile == gTacticalStatus.ubLastQuoteProfileNUm &&
563 		usQuoteNum == gTacticalStatus.ubLastQuoteSaid)
564 	{
565 		return( FALSE );
566 	}
567 
568 
569 	// If we are a robot, play the controller's quote!
570 	if ( pSoldier->uiStatusFlags & SOLDIER_ROBOT )
571 	{
572 		if ( CanRobotBeControlled( pSoldier ) )
573 		{
574 			return TacticalCharacterDialogue(pSoldier->robot_remote_holder, usQuoteNum);
575 		}
576 		else
577 		{
578 			return( FALSE );
579 		}
580 	}
581 
582 	if (AM_AN_EPC(pSoldier) &&
583 		!(gMercProfiles[pSoldier->ubProfile].ubMiscFlags & PROFILE_MISC_FLAG_FORCENPCQUOTE))
584 		return( FALSE );
585 
586 	// Check for logging of me too bleeds...
587 	if ( usQuoteNum == QUOTE_STARTING_TO_BLEED )
588 	{
589 		if ( gubLogForMeTooBleeds )
590 		{
591 			// If we are greater than one...
592 			if ( gubLogForMeTooBleeds > 1 )
593 			{
594 				//Replace with me too....
595 				usQuoteNum = QUOTE_ME_TOO;
596 			}
597 			gubLogForMeTooBleeds++;
598 		}
599 	}
600 
601 	CharacterDialogue(pSoldier->ubProfile, usQuoteNum, pSoldier->face, DIALOGUE_TACTICAL_UI, TRUE);
602 	return TRUE;
603 }
604 
605 // This function takes a profile num, quote num, faceindex and a UI hander ID.
606 // What it does is queues up the dialog to be ultimately loaded/displayed
607 // FACEINDEX
608 //    The face index is an index into an ACTIVE face. The face is considered to
609 //    be active, and if it's not, either that has to be handled by the UI handler
610 //    ir nothing will show.  What this function does is set the face to talking,
611 //    and the face sprite system should handle the rest.
612 // bUIHandlerID
613 //    Because this could be used in any place, the UI handleID is used to differentiate
614 //    places in the game. For example, specific things happen in the tactical engine
615 //    that may not be the place where in the AIM contract screen uses.....
616 
617 // NB; The queued system is not yet implemented, but will be transpatent to the caller....
618 
619 
CharacterDialogue(UINT8 const character,UINT16 const quote,FACETYPE * const face,DialogueHandler const dialogue_handler,BOOLEAN const fFromSoldier,bool const delayed)620 void CharacterDialogue(UINT8 const character, UINT16 const quote, FACETYPE* const face, DialogueHandler const dialogue_handler, BOOLEAN const fFromSoldier, bool const delayed)
621 {
622 	class DialogueEventQuote : public DialogueEvent
623 	{
624 		public:
625 			DialogueEventQuote(ProfileID const character, UINT16 const quote, FACETYPE* const face_, DialogueHandler const dialogue_handler, bool const from_soldier, bool const delayed) :
626 				quote_(quote),
627 				character_(character),
628 				dialogue_handler_(dialogue_handler),
629 				face(face_),
630 				from_soldier_(from_soldier),
631 				delayed_(delayed)
632 			{}
633 
634 			bool Execute()
635 			{
636 				// Check if this one is to be delayed until we gain control.
637 				if (delayed_ && gTacticalStatus.ubCurrentTeam != OUR_TEAM) return true;
638 
639 				// Try to find soldier...
640 				SOLDIERTYPE* s = FindSoldierByProfileIDOnPlayerTeam(character_);
641 				if (s && SoundIsPlaying(s->uiBattleSoundID))
642 				{
643 					// Place back in!
644 					return true;
645 				}
646 
647 				if (fInMapMode && !GamePaused())
648 				{
649 					PauseGame();
650 					LockPauseState(LOCK_PAUSE_MERC_TALKING);
651 					fWasPausedDuringDialogue = TRUE;
652 				}
653 
654 				if (s && s->fMercAsleep) // wake grunt up to say
655 				{
656 					s->fMercAsleep = FALSE;
657 
658 					// refresh map screen
659 					fCharacterInfoPanelDirty = TRUE;
660 					fTeamPanelDirty = TRUE;
661 
662 					// allow them to go back to sleep
663 					MakeCharacterDialogueEventSleep(*s, true);
664 				}
665 
666 				gTacticalStatus.ubLastQuoteSaid       = quote_;
667 				gTacticalStatus.ubLastQuoteProfileNUm = character_;
668 
669 				ExecuteCharacterDialogue(character_, quote_, face, dialogue_handler_, from_soldier_, false);
670 
671 				s = FindSoldierByProfileID(character_);
672 				if (s && s->bTeam == OUR_TEAM)
673 				{
674 					CheckForStopTimeQuotes(quote_);
675 				}
676 
677 				return false;
678 			}
679 
680 		private:
681 			UINT16          const quote_;
682 			UINT8           const character_;
683 			DialogueHandler const dialogue_handler_;
684 			FACETYPE*       const face;
685 			bool            const from_soldier_;
686 			bool            const delayed_;
687 	};
688 
689 	DialogueEvent::Add(new DialogueEventQuote(character, quote, face, dialogue_handler, fFromSoldier, delayed));
690 }
691 
692 
CharacterDialogueUsingAlternateFile(SOLDIERTYPE & s,UINT16 const quote,DialogueHandler const handler)693 void CharacterDialogueUsingAlternateFile(SOLDIERTYPE& s, UINT16 const quote, DialogueHandler const handler)
694 {
695 	class CharacterDialogueEventUsingAlternateFile : public CharacterDialogueEvent
696 	{
697 		public:
698 			CharacterDialogueEventUsingAlternateFile(SOLDIERTYPE& soldier, UINT16 const quote, DialogueHandler const handler) :
699 				CharacterDialogueEvent(soldier),
700 				quote_(quote),
701 				handler_(handler)
702 			{}
703 
704 			bool Execute()
705 			{
706 				if (!MayExecute()) return true;
707 
708 				SOLDIERTYPE const& s = soldier_;
709 				ExecuteCharacterDialogue(s.ubProfile, quote_, s.face, handler_, TRUE, true);
710 				return false;
711 			}
712 
713 		private:
714 			UINT16          const quote_;
715 			DialogueHandler const handler_;
716 	};
717 
718 	DialogueEvent::Add(new CharacterDialogueEventUsingAlternateFile(s, gTacticalStatus.ubGuideDescriptionToUse, handler));
719 }
720 
721 
722 static void CreateTalkingUI(DialogueHandler bUIHandlerID, FACETYPE& f, UINT8 ubCharacterNum, const ST::string& zQuoteStr);
723 static BOOLEAN GetDialogue(const MercProfile &profile, UINT16 usQuoteNum, ST::string& zDialogueText, CHAR8* zSoundString, bool useAlternateDialogueFile);
724 
725 
726 // execute specific character dialogue
ExecuteCharacterDialogue(UINT8 const ubCharacterNum,UINT16 const usQuoteNum,FACETYPE * const face,DialogueHandler const bUIHandlerID,BOOLEAN const fFromSoldier,bool useAlternateDialogueFile)727 BOOLEAN ExecuteCharacterDialogue(UINT8 const ubCharacterNum, UINT16 const usQuoteNum, FACETYPE* const face, DialogueHandler const bUIHandlerID, BOOLEAN const fFromSoldier, bool useAlternateDialogueFile)
728 {
729 	gpCurrentTalkingFace = face;
730 	gubCurrentTalkingID  = ubCharacterNum;
731 
732 	CHAR8 zSoundString[164];
733 
734 	// Check if we are dead now or not....( if from a soldier... )
735 
736 	// Try to find soldier...
737 	const SOLDIERTYPE* const pSoldier = FindSoldierByProfileIDOnPlayerTeam(ubCharacterNum);
738 	if ( pSoldier != NULL )
739 	{
740 		// Check vital stats
741 		if (pSoldier->bLife < CONSCIOUSNESS )
742 		{
743 			return( FALSE );
744 		}
745 
746 		if ( pSoldier->uiStatusFlags & SOLDIER_GASSED )
747 			return( FALSE );
748 
749 		if ( (AM_A_ROBOT( pSoldier )) )
750 		{
751 			return( FALSE );
752 		}
753 
754 		if (pSoldier->bLife < OKLIFE && usQuoteNum != QUOTE_SERIOUSLY_WOUNDED )
755 		{
756 			return( FALSE );
757 		}
758 
759 		if( pSoldier->bAssignment == ASSIGNMENT_POW )
760 		{
761 			return( FALSE );
762 		}
763 
764 		// sleeping guys don't talk.. go to standby to talk
765 		if (pSoldier->fMercAsleep)
766 		{
767 			// check if the soldier was compaining about lack of sleep and was alseep, if so, leave them alone
768 			if( ( usQuoteNum == QUOTE_NEED_SLEEP ) || ( usQuoteNum == QUOTE_OUT_OF_BREATH ) )
769 			{
770 				// leave them alone
771 				return ( TRUE );
772 			}
773 
774 			// may want to wake up any character that has VERY important dialogue to say
775 			// MC to flesh out
776 
777 		}
778 
779 		// now being used in a different way...
780 		/*
781 		if ( ( (usQuoteNum == QUOTE_PERSONALITY_TRAIT &&
782 			(gMercProfiles[ubCharacterNum].bPersonalityTrait == FORGETFUL ||
783 			gMercProfiles[ubCharacterNum].bPersonalityTrait == CLAUSTROPHOBIC ||
784 			gMercProfiles[ubCharacterNum].bPersonalityTrait == NERVOUS ||
785 			gMercProfiles[ubCharacterNum].bPersonalityTrait == NONSWIMMER ||
786 			gMercProfiles[ubCharacterNum].bPersonalityTrait == FEAR_OF_INSECTS))
787 			//usQuoteNum == QUOTE_STARTING_TO_WHINE ||
788 		) )
789 		{
790 			// This quote might spawn another quote from someone
791 			FOR_EACH_IN_TEAM(s, OUR_TEAM)
792 			{
793 				if (s->ubProfile != ubCharacterNum &&
794 					OkControllableMerc(s) &&
795 					SpacesAway(pSoldier->sGridNo, s->sGridNo) < 5)
796 				{
797 					// if this merc disliked the whining character sufficiently and hasn't
798 					// already retorted
799 					if (gMercProfiles[s->ubProfile].bMercOpinion[ubCharacterNum] < -2 &&
800 							!(s->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_ANNOYING_MERC))
801 					{
802 						// make a comment!
803 						TacticalCharacterDialogue(s, QUOTE_ANNOYING_PC);
804 						s->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_ANNOYING_MERC;
805 						break;
806 					}
807 				}
808 			}
809 		}*/
810 	}
811 	else
812 	{
813 		// If from a soldier, and he does not exist anymore, donot play!
814 		if ( fFromSoldier )
815 		{
816 			return( FALSE );
817 		}
818 	}
819 
820 	// Check face index
821 	CHECKF(face != NULL);
822 
823 	ST::string gzQuoteStr;
824 	if (!GetDialogue(MercProfile(ubCharacterNum), usQuoteNum, gzQuoteStr,
825 		zSoundString, useAlternateDialogueFile))
826 	{
827 		return( FALSE );
828 	}
829 
830 	SetFaceTalking(*face, zSoundString, gzQuoteStr);
831 	CreateTalkingUI(bUIHandlerID, *face, ubCharacterNum, gzQuoteStr);
832 
833 	// Set global handleer ID value, used when face desides it's done...
834 	gbUIHandlerID = bUIHandlerID;
835 
836 	guiScreenIDUsedWhenUICreated = guiCurrentScreen;
837 
838 	return( TRUE );
839 }
840 
841 
842 static void DisplayTextForExternalNPC(UINT8 ubCharacterNum, const ST::string& zQuoteStr);
843 static void HandleExternNPCSpeechFace(FACETYPE&);
844 static void HandleTacticalNPCTextUI(UINT8 ubCharacterNum, const ST::string& zQuoteStr);
845 static void HandleTacticalTextUI(ProfileID profile_id, const ST::string& zQuoteStr);
846 
847 
CreateTalkingUI(DialogueHandler bUIHandlerID,FACETYPE & f,UINT8 ubCharacterNum,const ST::string & zQuoteStr)848 static void CreateTalkingUI(DialogueHandler bUIHandlerID, FACETYPE& f, UINT8 ubCharacterNum, const ST::string& zQuoteStr)
849 {
850 	// Show text, if on
851 	if (gGameSettings.fOptions[TOPTION_SUBTITLES] || !f.fValidSpeech)
852 	{
853 		switch (bUIHandlerID)
854 		{
855 			case DIALOGUE_TACTICAL_UI:
856 				HandleTacticalTextUI(ubCharacterNum, zQuoteStr);
857 				break;
858 			case DIALOGUE_NPC_UI:
859 				HandleTacticalNPCTextUI(ubCharacterNum, zQuoteStr);
860 				break;
861 			case DIALOGUE_CONTACTPAGE_UI:
862 				DisplayTextForMercFaceVideoPopUp(zQuoteStr);
863 				break;
864 			case DIALOGUE_SPECK_CONTACT_PAGE_UI:
865 				DisplayTextForSpeckVideoPopUp(zQuoteStr);
866 				break;
867 			case DIALOGUE_EXTERNAL_NPC_UI:
868 				DisplayTextForExternalNPC(ubCharacterNum, zQuoteStr);
869 				break;
870 			case DIALOGUE_SHOPKEEPER_UI:
871 				InitShopKeeperSubTitledText(zQuoteStr);
872 				break;
873 			default:
874 				break;
875 		}
876 	}
877 
878 	if (gGameSettings.fOptions[TOPTION_SPEECH])
879 	{
880 		switch (bUIHandlerID)
881 		{
882 			case DIALOGUE_TACTICAL_UI:
883 				HandleTacticalSpeechUI(ubCharacterNum, f);
884 				break;
885 			case DIALOGUE_EXTERNAL_NPC_UI:
886 				HandleExternNPCSpeechFace(f);
887 				break;
888 			//case DIALOGUE_CONTACTPAGE_UI:
889 			//case DIALOGUE_SPECK_CONTACT_PAGE_UI:
890 			default:
891 				break;
892 		}
893 	}
894 }
895 
GetDialogue(const MercProfile & profile,UINT16 usQuoteNum,ST::string & zDialogueText,CHAR8 * zSoundString,bool useAlternateDialogueFile)896 static BOOLEAN GetDialogue(const MercProfile &profile, UINT16 usQuoteNum, ST::string& zDialogueText, CHAR8* zSoundString, bool useAlternateDialogueFile)
897 {
898 	// first things first  - gDIALOGUESIZErab the text (if player has SUBTITLE PREFERENCE ON)
899 	//if ( gGameSettings.fOptions[ TOPTION_SUBTITLES ] )
900 	{
901 		ST::string zFilename = Content::GetDialogueTextFilename(
902 						profile,
903 						useAlternateDialogueFile,
904 						ProfileCurrentlyTalkingInDialoguePanel(profile.getID()));
905 
906 		bool success = false;
907 		try
908 		{
909 			ST::string* quote = GCM->loadDialogQuoteFromFile(zFilename.c_str(), usQuoteNum);
910 			if(quote)
911 			{
912 				zDialogueText = *quote;
913 				delete quote;
914 				success = !zDialogueText.empty();
915 			}
916 		}
917 		catch (...) { success = false; }
918 		if (!success)
919 		{
920 			zDialogueText = ST::format("I have no text in the EDT file ({}) {}", usQuoteNum, zFilename);
921 			return( FALSE );
922 		}
923 	}
924 
925 	// CHECK IF THE FILE EXISTS, IF NOT, USE DEFAULT!
926 	ST::string zFilename = Content::GetDialogueVoiceFilename(
927 		profile, usQuoteNum, useAlternateDialogueFile,
928 		ProfileCurrentlyTalkingInDialoguePanel(profile.getID()),
929 		isRussianVersion() || isRussianGoldVersion());
930 
931 	strcpy(zSoundString, zFilename.c_str());
932 	return(TRUE);
933 }
934 
935 
936 // Handlers for tactical UI stuff
HandleTacticalNPCTextUI(UINT8 ubCharacterNum,const ST::string & zQuoteStr)937 static void HandleTacticalNPCTextUI(UINT8 ubCharacterNum, const ST::string& zQuoteStr)
938 {
939 	// Setup dialogue text box
940 	if ( guiCurrentScreen != MAP_SCREEN )
941 	{
942 		gTalkPanel.fRenderSubTitlesNow = TRUE;
943 		gTalkPanel.fSetupSubTitles = TRUE;
944 	}
945 
946 	// post message to mapscreen message system
947 	gTalkPanel.zQuoteStr = ST::format("\"{}\"", zQuoteStr);
948 	MapScreenMessage(FONT_MCOLOR_WHITE, MSG_DIALOG, ST::format("{}: \"{}\"", GetProfile(ubCharacterNum).zNickname, zQuoteStr));
949 }
950 
951 
952 static void ExecuteTacticalTextBox(INT16 sLeftPosition, INT16 sTopPosition, const ST::string& pString);
953 
954 
955 // Handlers for tactical UI stuff
DisplayTextForExternalNPC(UINT8 ubCharacterNum,const ST::string & zQuoteStr)956 static void DisplayTextForExternalNPC(UINT8 ubCharacterNum, const ST::string& zQuoteStr)
957 {
958 	INT16 sLeft;
959 	INT16 sTop;
960 
961 	// Setup dialogue text box
962 	if ( guiCurrentScreen != MAP_SCREEN )
963 	{
964 		gTalkPanel.fRenderSubTitlesNow = TRUE;
965 		gTalkPanel.fSetupSubTitles = TRUE;
966 	}
967 
968 	// post message to mapscreen message system
969 	gTalkPanel.zQuoteStr = ST::format("\"{}\"", zQuoteStr);
970 	MapScreenMessage(FONT_MCOLOR_WHITE, MSG_DIALOG, ST::format("{}: \"{}\"",
971 				GetProfile(ubCharacterNum).zNickname, zQuoteStr));
972 
973 	if ( guiCurrentScreen == MAP_SCREEN )
974 	{
975 		// on the map screen dialog can be in any place requested
976 		sLeft = gsExternPanelXPosition + 97;
977 		sTop = gsExternPanelYPosition;
978 	}
979 	else
980 	{
981 		// on the tactical screen show message always in the same position (corner
982 		// of the screen)
983 		sLeft = 110;
984 		sTop = 20;
985 	}
986 
987 	ExecuteTacticalTextBox( sLeft, sTop, gTalkPanel.zQuoteStr );
988 }
989 
990 
HandleTacticalTextUI(ProfileID profile_id,const ST::string & zQuoteStr)991 static void HandleTacticalTextUI(ProfileID profile_id, const ST::string& zQuoteStr)
992 {
993 	ST::string zText = ST::format("\"{}\"", zQuoteStr);
994 
995 	ExecuteTacticalTextBox( g_ui.getTacticalTextBoxX(), g_ui.getTacticalTextBoxY(), zText );
996 
997 	MapScreenMessage(FONT_MCOLOR_WHITE, MSG_DIALOG, ST::format("{}: \"{}\"", GetProfile(profile_id).zNickname, zQuoteStr));
998 }
999 
1000 
1001 static void RenderSubtitleBoxOverlay(VIDEO_OVERLAY* pBlitter);
1002 static void TextOverlayClickCallback(MOUSE_REGION* pRegion, INT32 iReason);
1003 
1004 
ExecuteTacticalTextBox(INT16 sLeftPosition,INT16 sTopPosition,const ST::string & pString)1005 static void ExecuteTacticalTextBox(INT16 sLeftPosition, INT16 sTopPosition, const ST::string& pString)
1006 {
1007 	// check if mouse region created, if so, do not recreate
1008 	if (fTextBoxMouseRegionCreated) return;
1009 
1010 	// Prepare text box
1011 	g_dialogue_box = PrepareMercPopupBox(g_dialogue_box, BASIC_MERC_POPUP_BACKGROUND, BASIC_MERC_POPUP_BORDER, pString, DIALOGUE_DEFAULT_SUBTITLE_WIDTH, 0, 0, 0, &gusSubtitleBoxWidth, &gusSubtitleBoxHeight);
1012 
1013 	INT16  const x = sLeftPosition;
1014 	INT16  const y = sTopPosition;
1015 	UINT16 const w = gusSubtitleBoxWidth;
1016 	UINT16 const h = gusSubtitleBoxHeight;
1017 
1018 	g_text_box_overlay = RegisterVideoOverlay(RenderSubtitleBoxOverlay, x, y, w, h);
1019 
1020 	//Define main region
1021 	MSYS_DefineRegion(&gTextBoxMouseRegion, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST, CURSOR_NORMAL, MSYS_NO_CALLBACK, TextOverlayClickCallback);
1022 
1023 	fTextBoxMouseRegionCreated = TRUE;
1024 }
1025 
1026 
1027 static void FaceOverlayClickCallback(MOUSE_REGION* pRegion, INT32 iReason);
1028 static void RenderFaceOverlay(VIDEO_OVERLAY* pBlitter);
1029 
1030 
HandleExternNPCSpeechFace(FACETYPE & f)1031 static void HandleExternNPCSpeechFace(FACETYPE& f)
1032 {
1033 	// Enable it!
1034 	SetAutoFaceActive(FACE_AUTO_DISPLAY_BUFFER, FACE_AUTO_RESTORE_BUFFER, f, 0, 0);
1035 
1036 	// Set flag to say WE control when to set inactive!
1037 	f.uiFlags |= FACE_INACTIVE_HANDLED_ELSEWHERE;
1038 
1039 	INT16       x;
1040 	INT16       y;
1041 	INT16 const w = 99;
1042 	INT16 const h = 98;
1043 	if (guiCurrentScreen != MAP_SCREEN)
1044 	{
1045 		x = 10;
1046 		y = 20;
1047 	}
1048 	else
1049 	{
1050 		x = gsExternPanelXPosition;
1051 		y = gsExternPanelYPosition;
1052 	}
1053 
1054 	gpCurrentTalkingFace->video_overlay = RegisterVideoOverlay(RenderFaceOverlay, x, y, w, h);
1055 
1056 	RenderAutoFace(f);
1057 
1058 	// ATE: Create mouse region.......
1059 	if ( !fExternFaceBoxRegionCreated )
1060 	{
1061 		fExternFaceBoxRegionCreated = TRUE;
1062 
1063 		//Define main region
1064 		MSYS_DefineRegion(&gFacePopupMouseRegion, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST, CURSOR_NORMAL, MSYS_NO_CALLBACK, FaceOverlayClickCallback);
1065 	}
1066 
1067 	gfFacePanelActive = TRUE;
1068 }
1069 
1070 
HandleTacticalSpeechUI(const UINT8 ubCharacterNum,FACETYPE & f)1071 static void HandleTacticalSpeechUI(const UINT8 ubCharacterNum, FACETYPE& f)
1072 {
1073 	BOOLEAN fDoExternPanel = FALSE;
1074 
1075 	// Get soldier pointer, if there is one...
1076 	SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubCharacterNum);
1077 
1078 	// PLEASE NOTE:  pSoldier may legally be NULL (e.g. Skyrider) !!!
1079 
1080 	if ( pSoldier == NULL )
1081 	{
1082 		fDoExternPanel = TRUE;
1083 	}
1084 	else
1085 	{
1086 		// If we are not an active face!
1087 		if ( guiCurrentScreen != MAP_SCREEN )
1088 		{
1089 			fDoExternPanel = TRUE;
1090 		}
1091 	}
1092 
1093 	if ( fDoExternPanel )
1094 	{
1095 		// Enable it!
1096 		SetAutoFaceActive(FACE_AUTO_DISPLAY_BUFFER, FACE_AUTO_RESTORE_BUFFER, f, 0, 0);
1097 
1098 		// Set flag to say WE control when to set inactive!
1099 		f.uiFlags |= FACE_INACTIVE_HANDLED_ELSEWHERE | FACE_MAKEACTIVE_ONCE_DONE;
1100 
1101 		// IF we are in tactical and this soldier is on the current squad
1102 		if ((guiCurrentScreen == GAME_SCREEN) && (pSoldier != NULL) &&
1103 			(pSoldier->bAssignment == iCurrentTacticalSquad))
1104 		{
1105 			// Make the interface panel dirty..
1106 			// This will dirty the panel next frame...
1107 			gfRerenderInterfaceFromHelpText = TRUE;
1108 		}
1109 
1110 		INT16 const x = 10;
1111 		INT16 const y = 20;
1112 		INT16 const w = 99;
1113 		INT16 const h = 98;
1114 
1115 		gpCurrentTalkingFace->video_overlay = RegisterVideoOverlay(RenderFaceOverlay, x, y, w, h);
1116 
1117 		RenderAutoFace(f);
1118 
1119 		// ATE: Create mouse region.......
1120 		if ( !fExternFaceBoxRegionCreated )
1121 		{
1122 			fExternFaceBoxRegionCreated = TRUE;
1123 
1124 			//Define main region
1125 			MSYS_DefineRegion(&gFacePopupMouseRegion, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST,
1126 						CURSOR_NORMAL, MSYS_NO_CALLBACK, FaceOverlayClickCallback);
1127 		}
1128 
1129 		gfFacePanelActive = TRUE;
1130 
1131 	}
1132 	else if ( guiCurrentScreen == MAP_SCREEN )
1133 	{
1134 		// Are we in mapscreen?
1135 		// If so, set current guy active to talk.....
1136 		if ( pSoldier != NULL )
1137 		{
1138 			ContinueDialogue( pSoldier, FALSE );
1139 		}
1140 	}
1141 
1142 }
1143 
1144 
HandleDialogueEnd(FACETYPE & f)1145 void HandleDialogueEnd(FACETYPE& f)
1146 {
1147 	if ( gGameSettings.fOptions[ TOPTION_SPEECH ] )
1148 	{
1149 
1150 		if (&f != gpCurrentTalkingFace)
1151 		{
1152 			//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, "HandleDialogueEnd() face mismatch." );
1153 			return;
1154 		}
1155 
1156 		if (f.fTalking)
1157 		{
1158 			SLOGD("HandleDialogueEnd() face still talking." );
1159 			return;
1160 		}
1161 
1162 		switch( gbUIHandlerID )
1163 		{
1164 			case DIALOGUE_TACTICAL_UI:
1165 				if ( gfFacePanelActive )
1166 				{
1167 					// Set face inactive!
1168 					f.fCanHandleInactiveNow = TRUE;
1169 					SetAutoFaceInActive(f);
1170 					gfFacePanelActive = FALSE;
1171 
1172 					if ( fExternFaceBoxRegionCreated )
1173 					{
1174 						fExternFaceBoxRegionCreated = FALSE;
1175 						MSYS_RemoveRegion(&(gFacePopupMouseRegion) );
1176 					}
1177 				}
1178 				break;
1179 
1180 			case DIALOGUE_NPC_UI:
1181 				break;
1182 
1183 			case DIALOGUE_EXTERNAL_NPC_UI:
1184 				f.fCanHandleInactiveNow = TRUE;
1185 				SetAutoFaceInActive(f);
1186 				gfFacePanelActive = FALSE;
1187 
1188 				if ( fExternFaceBoxRegionCreated )
1189 				{
1190 					fExternFaceBoxRegionCreated = FALSE;
1191 					MSYS_RemoveRegion(&(gFacePopupMouseRegion) );
1192 				}
1193 				break;
1194 
1195 			default:
1196 				break;
1197 		}
1198 	}
1199 
1200 
1201 	if (gGameSettings.fOptions[TOPTION_SUBTITLES] || !f.fValidSpeech)
1202 	{
1203 		switch( gbUIHandlerID )
1204 		{
1205 			case DIALOGUE_TACTICAL_UI:
1206 			case DIALOGUE_EXTERNAL_NPC_UI:
1207 				// Remove if created
1208 				if (g_text_box_overlay != NULL)
1209 				{
1210 					RemoveVideoOverlay(g_text_box_overlay);
1211 					g_text_box_overlay = NULL;
1212 
1213 					if ( fTextBoxMouseRegionCreated )
1214 					{
1215 						RemoveMercPopupBox(g_dialogue_box);
1216 						g_dialogue_box = 0;
1217 
1218 						MSYS_RemoveRegion( &gTextBoxMouseRegion );
1219 						fTextBoxMouseRegionCreated = FALSE;
1220 					}
1221 
1222 				}
1223 
1224 				break;
1225 
1226 			case DIALOGUE_NPC_UI:
1227 
1228 
1229 				// Remove region
1230 				if ( gTalkPanel.fTextRegionOn )
1231 				{
1232 					MSYS_RemoveRegion(&(gTalkPanel.TextRegion) );
1233 					gTalkPanel.fTextRegionOn = FALSE;
1234 
1235 				}
1236 
1237 				SetRenderFlags( RENDER_FLAG_FULL );
1238 				gTalkPanel.fRenderSubTitlesNow = FALSE;
1239 
1240 				// Delete subtitle box
1241 				RemoveMercPopupBox(g_interface_dialogue_box);
1242 				g_interface_dialogue_box = 0;
1243 				break;
1244 
1245 			case DIALOGUE_CONTACTPAGE_UI:
1246 				break;
1247 
1248 			case DIALOGUE_SPECK_CONTACT_PAGE_UI:
1249 				break;
1250 
1251 			default:
1252 				break;
1253 		}
1254 	}
1255 
1256 	TurnOffSectorLocator();
1257 
1258 	gsExternPanelXPosition = DEFAULT_EXTERN_PANEL_X_POS;
1259 	gsExternPanelYPosition = DEFAULT_EXTERN_PANEL_Y_POS;
1260 
1261 }
1262 
1263 
RenderFaceOverlay(VIDEO_OVERLAY * const blt)1264 static void RenderFaceOverlay(VIDEO_OVERLAY* const blt)
1265 {
1266 	INT16 sFontX;
1267 	INT16 sFontY;
1268 
1269 	if (!gfFacePanelActive) return;
1270 
1271 	if (!gpCurrentTalkingFace) return;
1272 	FACETYPE    const&       f   = *gpCurrentTalkingFace;
1273 	SOLDIERTYPE const* const s   = FindSoldierByProfileID(f.ubCharacterNum);
1274 	INT16              const x   = blt->sX;
1275 	INT16              const y   = blt->sY;
1276 	SGPVSurface*       const dst = blt->uiDestBuff;
1277 
1278 	// a living soldier?..or external NPC?..choose panel based on this
1279 	SGPVObject const* const vo = s ? guiCOMPANEL : guiCOMPANELB;
1280 	BltVideoObject(dst, vo, 0, x, y);
1281 
1282 	// Display name, location ( if not current )
1283 	SetFontAttributes(BLOCKFONT2, FONT_MCOLOR_LTGRAY);
1284 
1285 	if (s)
1286 	{
1287 		SetFontDestBuffer(dst);
1288 
1289 		FindFontCenterCoordinates(x + 12, y + 55, 73, 9, s->name, BLOCKFONT2, &sFontX, &sFontY);
1290 		MPrint(sFontX, sFontY, s->name);
1291 
1292 		// What sector are we in, (and is it the same as ours?)
1293 		if (s->sSectorX != gWorldSectorX || s->sSectorY != gWorldSectorY ||
1294 			s->bSectorZ != gbWorldSectorZ || s->fBetweenSectors)
1295 		{
1296 			ST::string sector_id = GetSectorIDString(s->sSectorX, s->sSectorY, s->bSectorZ, FALSE);
1297 			sector_id = ReduceStringLength(sector_id, 64, BLOCKFONT2);
1298 			FindFontCenterCoordinates(x + 12, y + 68, 73, 9, sector_id, BLOCKFONT2, &sFontX, &sFontY);
1299 			MPrint(sFontX, sFontY, sector_id);
1300 		}
1301 
1302 		SetFontDestBuffer(FRAME_BUFFER);
1303 
1304 		DrawSoldierUIBars(*s, x + 69, y + 47, FALSE, dst);
1305 	}
1306 	else
1307 	{
1308 		ST::string name = GetProfile(f.ubCharacterNum).zNickname;
1309 		FindFontCenterCoordinates(x + 9, y + 55, 73, 9, name, BLOCKFONT2, &sFontX, &sFontY);
1310 		MPrint(sFontX, sFontY, name);
1311 	}
1312 
1313 	SGPBox const r = { 0, 0, f.usFaceWidth, f.usFaceHeight };
1314 	BltVideoSurface(dst, f.uiAutoDisplayBuffer, x + 14, y + 6, &r);
1315 
1316 	InvalidateRegion(x, y, x + 99, y + 98);
1317 }
1318 
1319 
RenderSubtitleBoxOverlay(VIDEO_OVERLAY * pBlitter)1320 static void RenderSubtitleBoxOverlay(VIDEO_OVERLAY* pBlitter)
1321 {
1322 	if (g_text_box_overlay == NULL) return;
1323 
1324 	RenderMercPopUpBox(g_dialogue_box, pBlitter->sX, pBlitter->sY, pBlitter->uiDestBuff);
1325 	InvalidateRegion(pBlitter->sX, pBlitter->sY, pBlitter->sX + gusSubtitleBoxWidth, pBlitter->sY + gusSubtitleBoxHeight);
1326 }
1327 
1328 
1329 // Let Red talk, if he is in the list and the quote is QUOTE_AIR_RAID.  Choose
1330 // somebody else otherwise
ChooseRedIfPresentAndAirRaid(SOLDIERTYPE * const * const mercs_in_sector,UINT32 merc_count,UINT16 quote)1331 static void ChooseRedIfPresentAndAirRaid(SOLDIERTYPE*const*const mercs_in_sector, UINT32 merc_count, UINT16 quote)
1332 {
1333 	if (merc_count == 0) return;
1334 
1335 	SOLDIERTYPE* chosen;
1336 	if (quote == QUOTE_AIR_RAID)
1337 	{
1338 		for (SOLDIERTYPE*const* i = mercs_in_sector; i != mercs_in_sector + merc_count; ++i)
1339 		{
1340 			if ((*i)->ubProfile == RED)
1341 			{
1342 				chosen = *i;
1343 				goto talk;
1344 			}
1345 		}
1346 	}
1347 	chosen = mercs_in_sector[Random(merc_count)];
1348 talk:
1349 	TacticalCharacterDialogue(chosen, quote);
1350 }
1351 
1352 
SayQuoteFromAnyBodyInSector(UINT16 const quote_id)1353 void SayQuoteFromAnyBodyInSector(UINT16 const quote_id)
1354 {
1355 	// Loop through all our guys and randomly say one from someone in our sector
1356 	INT32       n_mercs = 0;
1357 	SOLDIERTYPE* mercs_in_sector[20];
1358 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1359 	{
1360 		// Add guy if he's a candidate
1361 		SOLDIERTYPE& s = *i;
1362 		if (!OkControllableMerc(&s))
1363 			continue;
1364 		if (AM_AN_EPC(&s))
1365 			continue;
1366 		if (s.uiStatusFlags & SOLDIER_GASSED)
1367 			continue;
1368 		if (AM_A_ROBOT(&s))
1369 			continue;
1370 		if (s.fMercAsleep)
1371 			continue;
1372 
1373 		if (gTacticalStatus.bNumFoughtInBattle[ENEMY_TEAM] == 0)
1374 		{
1375 			// Skip quotes referring to Deidranna's men, if there were no army guys
1376 			// fought
1377 			switch (quote_id)
1378 			{
1379 				case QUOTE_SECTOR_SAFE:
1380 					switch (s.ubProfile)
1381 					{
1382 						case IRA:    continue;
1383 						case MIGUEL: continue;
1384 						case SHANK:  continue;
1385 					}
1386 					break;
1387 
1388 				case QUOTE_ENEMY_PRESENCE:
1389 					switch (s.ubProfile)
1390 					{
1391 						case DIMITRI: continue;
1392 						case DYNAMO:  continue;
1393 						case IRA:     continue;
1394 						case SHANK:   continue;
1395 					}
1396 					break;
1397 			}
1398 		}
1399 
1400 		mercs_in_sector[n_mercs++] = &s;
1401 	}
1402 
1403 	ChooseRedIfPresentAndAirRaid(mercs_in_sector, n_mercs, quote_id);
1404 }
1405 
1406 
SayQuoteFromAnyBodyInThisSector(INT16 const x,INT16 const y,INT8 const z,UINT16 const quote_id)1407 void SayQuoteFromAnyBodyInThisSector(INT16 const x, INT16 const y, INT8 const z, UINT16 const quote_id)
1408 {
1409 	// Loop through all our guys and randomly say one from someone in our sector
1410 	INT32       n_mercs = 0;
1411 	SOLDIERTYPE* mercs_in_sector[20];
1412 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1413 	{
1414 		// Add guy if he's a candidate
1415 		SOLDIERTYPE& s = *i;
1416 		if (s.sSectorX != x)
1417 			continue;
1418 		if (s.sSectorY != y)
1419 			continue;
1420 		if (s.bSectorZ != z)
1421 			continue;
1422 		if (AM_AN_EPC(&s))
1423 			continue;
1424 		if (s.uiStatusFlags & SOLDIER_GASSED)
1425 			continue;
1426 		if (AM_A_ROBOT(&s))
1427 			continue;
1428 		if (s.fMercAsleep)
1429 			continue;
1430 		mercs_in_sector[n_mercs++] = &s;
1431 	}
1432 
1433 	ChooseRedIfPresentAndAirRaid(mercs_in_sector, n_mercs, quote_id);
1434 }
1435 
1436 
SayQuoteFromNearbyMercInSector(GridNo const gridno,INT8 const distance,UINT16 const quote_id)1437 void SayQuoteFromNearbyMercInSector(GridNo const gridno, INT8 const distance, UINT16 const quote_id)
1438 {
1439 	// Loop through all our guys and randomly say one from someone in our sector
1440 	INT32       n_mercs = 0;
1441 	SOLDIERTYPE* mercs_in_sector[20];
1442 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1443 	{
1444 		// Add guy if he's a candidate
1445 		SOLDIERTYPE& s = *i;
1446 		if (!OkControllableMerc(&s))
1447 			continue;
1448 		if (PythSpacesAway(gridno, s.sGridNo) >= distance)
1449 			continue;
1450 		if (AM_AN_EPC(&s))
1451 			continue;
1452 		if (s.uiStatusFlags & SOLDIER_GASSED)
1453 			continue;
1454 		if (AM_A_ROBOT(&s))
1455 			continue;
1456 		if (s.fMercAsleep)
1457 			continue;
1458 		if (!SoldierTo3DLocationLineOfSightTest(&s, gridno, 0, 0, MaxDistanceVisible(), TRUE))
1459 			continue;
1460 		if (quote_id == QUOTE_STUFF_MISSING_DRASSEN && static_cast<INT8>(Random(100)) > EffectiveWisdom(&s))
1461 			continue;
1462 		mercs_in_sector[n_mercs++] = &s;
1463 	}
1464 
1465 	if (n_mercs > 0)
1466 	{
1467 		SOLDIERTYPE* const chosen = mercs_in_sector[Random(n_mercs)];
1468 		if (quote_id == QUOTE_STUFF_MISSING_DRASSEN)
1469 		{
1470 			SetFactTrue(FACT_PLAYER_FOUND_ITEMS_MISSING);
1471 		}
1472 		TacticalCharacterDialogue(chosen, quote_id);
1473 	}
1474 }
1475 
1476 
SayQuote58FromNearbyMercInSector(GridNo const gridno,INT8 const distance,UINT16 const quote_id,INT8 const sex)1477 void SayQuote58FromNearbyMercInSector(GridNo const gridno, INT8 const distance, UINT16 const quote_id, INT8 const sex)
1478 {
1479 	// Loop through all our guys and randomly say one from someone in our sector
1480 	INT32       n_mercs = 0;
1481 	SOLDIERTYPE* mercs_in_sector[20];
1482 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1483 	{
1484 		// Add guy if he's a candidate
1485 		SOLDIERTYPE& s = *i;
1486 		if (!OkControllableMerc(&s))
1487 			continue;
1488 		if (PythSpacesAway(gridno, s.sGridNo) >= distance)
1489 			continue;
1490 		if (AM_AN_EPC(&s))
1491 			continue;
1492 		if (s.uiStatusFlags & SOLDIER_GASSED)
1493 			continue;
1494 		if (AM_A_ROBOT(&s))
1495 			continue;
1496 		if (s.fMercAsleep)
1497 			continue;
1498 		if (!SoldierTo3DLocationLineOfSightTest(&s, gridno, 0, 0, MaxDistanceVisible(), TRUE))
1499 			continue;
1500 
1501 		// ATE: This is to check gedner for this quote
1502 		switch (QuoteExp_GenderCode[s.ubProfile])
1503 		{
1504 			case 0:
1505 				if (sex == FEMALE)
1506 					continue;
1507 				break;
1508 			case 1:
1509 				if (sex == MALE)
1510 					continue;
1511 				break;
1512 		}
1513 
1514 		mercs_in_sector[n_mercs++] = &s;
1515 	}
1516 
1517 	if (n_mercs > 0)
1518 	{
1519 		SOLDIERTYPE* const chosen = mercs_in_sector[Random(n_mercs)];
1520 		TacticalCharacterDialogue(chosen, quote_id);
1521 	}
1522 }
1523 
1524 
TextOverlayClickCallback(MOUSE_REGION * pRegion,INT32 iReason)1525 static void TextOverlayClickCallback(MOUSE_REGION* pRegion, INT32 iReason)
1526 {
1527 	static BOOLEAN fLButtonDown = FALSE;
1528 
1529 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_DWN )
1530 	{
1531 		fLButtonDown = TRUE;
1532 	}
1533 
1534 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP && fLButtonDown )
1535 	{
1536 		if(  gpCurrentTalkingFace != NULL )
1537 		{
1538 			InternalShutupaYoFace(gpCurrentTalkingFace, FALSE);
1539 		}
1540 	}
1541 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
1542 	{
1543 		fLButtonDown = FALSE;
1544 	}
1545 }
1546 
1547 
FaceOverlayClickCallback(MOUSE_REGION * pRegion,INT32 iReason)1548 static void FaceOverlayClickCallback(MOUSE_REGION* pRegion, INT32 iReason)
1549 {
1550 	static BOOLEAN fLButtonDown = FALSE;
1551 
1552 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_DWN )
1553 	{
1554 		fLButtonDown = TRUE;
1555 	}
1556 
1557 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP && fLButtonDown )
1558 	{
1559 		if(  gpCurrentTalkingFace != NULL )
1560 		{
1561 			InternalShutupaYoFace(gpCurrentTalkingFace, FALSE);
1562 		}
1563 
1564 	}
1565 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
1566 	{
1567 		fLButtonDown = FALSE;
1568 	}
1569 }
1570 
1571 
FindDelayForString(const ST::string & str)1572 UINT32 FindDelayForString(const ST::string& str)
1573 {
1574 	return( (UINT32)str.to_utf32().size() * TEXT_DELAY_MODIFIER );
1575 }
1576 
BeginLoggingForBleedMeToos(BOOLEAN fStart)1577 void BeginLoggingForBleedMeToos( BOOLEAN fStart )
1578 {
1579 	gubLogForMeTooBleeds = fStart;
1580 }
1581 
1582 
SetEngagedInConvFromPCAction(SOLDIERTYPE * pSoldier)1583 void SetEngagedInConvFromPCAction( SOLDIERTYPE *pSoldier )
1584 {
1585 	// OK, If a good give, set engaged in conv...
1586 	gTacticalStatus.uiFlags |= ENGAGED_IN_CONV;
1587 	gTacticalStatus.ubEngagedInConvFromActionMercID = pSoldier->ubID;
1588 }
1589 
UnSetEngagedInConvFromPCAction(SOLDIERTYPE * pSoldier)1590 void UnSetEngagedInConvFromPCAction( SOLDIERTYPE *pSoldier )
1591 {
1592 	if ( gTacticalStatus.ubEngagedInConvFromActionMercID == pSoldier->ubID )
1593 	{
1594 		// OK, If a good give, set engaged in conv...
1595 		gTacticalStatus.uiFlags &= ( ~ENGAGED_IN_CONV );
1596 	}
1597 }
1598 
1599 
IsStopTimeQuote(UINT16 const quote_id)1600 static bool IsStopTimeQuote(UINT16 const quote_id)
1601 {
1602 	FOR_EACH(UINT16 const, i, gusStopTimeQuoteList)
1603 	{
1604 		if (*i == quote_id)
1605 			return true;
1606 	}
1607 	return false;
1608 }
1609 
1610 
CheckForStopTimeQuotes(UINT16 const usQuoteNum)1611 static void CheckForStopTimeQuotes(UINT16 const usQuoteNum)
1612 {
1613 	if (!IsStopTimeQuote(usQuoteNum)) return;
1614 	// Stop Time, game
1615 	EnterModalTactical(TACTICAL_MODAL_NOMOUSE);
1616 	gpCurrentTalkingFace->uiFlags |= FACE_MODAL;
1617 	SLOGD("Starting Modal Tactical Quote.");
1618 }
1619 
1620 
SetStopTimeQuoteCallback(MODAL_HOOK pCallBack)1621 void SetStopTimeQuoteCallback( MODAL_HOOK pCallBack )
1622 {
1623 	gModalDoneCallback = pCallBack;
1624 }
1625 
1626 
IsMercSayingDialogue(ProfileID const pid)1627 bool IsMercSayingDialogue(ProfileID const pid)
1628 {
1629 	return gpCurrentTalkingFace && gubCurrentTalkingID == pid;
1630 }
1631 
1632 
GetMercPrecedentQuoteBitStatus(const MERCPROFILESTRUCT * const p,UINT8 const ubQuoteBit)1633 BOOLEAN GetMercPrecedentQuoteBitStatus(const MERCPROFILESTRUCT* const p, UINT8 const ubQuoteBit)
1634 {
1635 	return (p->uiPrecedentQuoteSaid & 1 << (ubQuoteBit - 1)) != 0;
1636 }
1637 
1638 
SetMercPrecedentQuoteBitStatus(MERCPROFILESTRUCT * const p,UINT8 const ubBitToSet)1639 void SetMercPrecedentQuoteBitStatus(MERCPROFILESTRUCT* const p, UINT8 const ubBitToSet)
1640 {
1641 	p->uiPrecedentQuoteSaid |= 1 << (ubBitToSet - 1);
1642 }
1643 
1644 
GetQuoteBitNumberFromQuoteID(UINT32 const uiQuoteID)1645 UINT8 GetQuoteBitNumberFromQuoteID(UINT32 const uiQuoteID)
1646 {
1647 	for (size_t i = 0; i != lengthof(gubMercValidPrecedentQuoteID); ++i)
1648 	{
1649 		if (gubMercValidPrecedentQuoteID[i] == uiQuoteID)
1650 			return static_cast<UINT8>(i);
1651 	}
1652 	return 0;
1653 }
1654 
1655 
HandleShutDownOfMapScreenWhileExternfaceIsTalking(void)1656 void HandleShutDownOfMapScreenWhileExternfaceIsTalking( void )
1657 {
1658 	if ( ( fExternFaceBoxRegionCreated ) && ( gpCurrentTalkingFace) )
1659 	{
1660 		RemoveVideoOverlay(gpCurrentTalkingFace->video_overlay);
1661 		gpCurrentTalkingFace->video_overlay = NULL;
1662 	}
1663 }
1664 
1665 
HandleImportantMercQuote(SOLDIERTYPE * const s,UINT16 const usQuoteNumber)1666 void HandleImportantMercQuote(SOLDIERTYPE* const s, UINT16 const usQuoteNumber)
1667 {
1668 	// Wake merc up for THIS quote
1669 	bool const asleep = s->fMercAsleep;
1670 	if (asleep) MakeCharacterDialogueEventSleep(*s, false);
1671 	TacticalCharacterDialogue(s, usQuoteNumber);
1672 	if (asleep) MakeCharacterDialogueEventSleep(*s, true);
1673 }
1674 
1675 
HandleImportantMercQuoteLocked(SOLDIERTYPE * const s,UINT16 const quote)1676 void HandleImportantMercQuoteLocked(SOLDIERTYPE* const s, UINT16 const quote)
1677 {
1678 	LockMapScreenInterface(true);
1679 	HandleImportantMercQuote(s, quote);
1680 	LockMapScreenInterface(false);
1681 }
1682 
1683 
1684 // handle pausing of the dialogue queue
PauseDialogueQueue(void)1685 void PauseDialogueQueue( void )
1686 {
1687 	gfDialogueQueuePaused = TRUE;
1688 }
1689 
1690 // unpause the dialogue queue
UnPauseDialogueQueue(void)1691 void UnPauseDialogueQueue( void )
1692 {
1693 	gfDialogueQueuePaused = FALSE;
1694 }
1695 
1696 
SetExternMapscreenSpeechPanelXY(INT16 sXPos,INT16 sYPos)1697 void SetExternMapscreenSpeechPanelXY( INT16 sXPos, INT16 sYPos )
1698 {
1699 	gsExternPanelXPosition = sXPos;
1700 	gsExternPanelYPosition = sYPos;
1701 }
1702 
1703 
LoadDialogueControlGraphics()1704 void LoadDialogueControlGraphics()
1705 {
1706 	guiCOMPANEL  = AddVideoObjectFromFile(INTERFACEDIR "/communicationpopup.sti");
1707 	guiCOMPANELB = AddVideoObjectFromFile(INTERFACEDIR "/communicationpopup_2.sti");
1708 }
1709 
1710 
DeleteDialogueControlGraphics()1711 void DeleteDialogueControlGraphics()
1712 {
1713 	DeleteVideoObject(guiCOMPANEL);
1714 	DeleteVideoObject(guiCOMPANELB);
1715 }
1716 
1717 
1718 #ifdef WITH_UNITTESTS
1719 #undef FAIL
1720 #include "gtest/gtest.h"
1721 
TEST(DialogueControl,asserts)1722 TEST(DialogueControl, asserts)
1723 {
1724 	EXPECT_EQ(lengthof(preloadedExternalNPCFaces), 6);
1725 }
1726 
1727 #endif
1728