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