1 #include "Interface_Dialogue.h"
2
3 #include "AI.h"
4 #include "AIInternals.h"
5 #include "Animation_Control.h"
6 #include "Animation_Data.h"
7 #include "Arms_Dealer_Init.h"
8 #include "Assignments.h"
9 #include "BobbyRMailOrder.h"
10 #include "Boxing.h"
11 #include "Button_System.h"
12 #include "Campaign.h"
13 #include "Cheats.h"
14 #include "ContentManager.h"
15 #include "Cursors.h"
16 #include "Debug.h"
17 #include "Dialogue_Control.h"
18 #include "Directories.h"
19 #include "End_Game.h"
20 #include "Faces.h"
21 #include "Fade_Screen.h"
22 #include "Files.h"
23 #include "FindLocations.h"
24 #include "Font.h"
25 #include "Font_Control.h"
26 #include "GameInstance.h"
27 #include "GameLoop.h"
28 #include "GameRes.h"
29 #include "GameScreen.h"
30 #include "GameSettings.h"
31 #include "Game_Clock.h"
32 #include "Game_Event_Hook.h"
33 #include "HImage.h"
34 #include "Handle_Doors.h"
35 #include "Handle_Items.h"
36 #include "Handle_UI.h"
37 #include "History.h"
38 #include "Interactive_Tiles.h"
39 #include "Interface.h"
40 #include "Interface_Control.h"
41 #include "Interface_Panels.h"
42 #include "Isometric_Utils.h"
43 #include "Items.h"
44 #include "Keys.h"
45 #include "Local.h"
46 #include "Logger.h"
47 #include "MapScreen.h"
48 #include "Map_Screen_Helicopter.h"
49 #include "Map_Screen_Interface.h"
50 #include "Meanwhile.h"
51 #include "MercTextBox.h"
52 #include "Message.h"
53 #include "MessageBoxScreen.h"
54 #include "Morale.h"
55 #include "NPC.h"
56 #include "NpcActionParamsModel.h"
57 #include "OppList.h"
58 #include "Overhead.h"
59 #include "Personnel.h"
60 #include "PreBattle_Interface.h"
61 #include "Queen_Command.h"
62 #include "Quests.h"
63 #include "RenderWorld.h"
64 #include "Render_Dirty.h"
65 #include "Render_Fun.h"
66 #include "SaveLoadMap.h"
67 #include "ScreenIDs.h"
68 #include "ShopKeeper_Interface.h"
69 #include "SkillCheck.h"
70 #include "Soldier_Control.h"
71 #include "Soldier_Create.h"
72 #include "Soldier_Profile.h"
73 #include "Sound_Control.h"
74 #include "Squads.h"
75 #include "Strategic.h"
76 #include "StrategicMap.h"
77 #include "StrategicMap_Secrets.h"
78 #include "Strategic_AI.h"
79 #include "Strategic_Mines.h"
80 #include "Strategic_Movement.h"
81 #include "Strategic_Town_Loyalty.h"
82 #include "Structure.h"
83 #include "SysUtil.h"
84 #include "Tactical_Save.h"
85 #include "Text.h"
86 #include "TileDat.h"
87 #include "Timer_Control.h"
88 #include "UILayout.h"
89 #include "VObject.h"
90 #include "VSurface.h"
91 #include "Video.h"
92 #include "WCheck.h"
93 #include "WorldMan.h"
94
95 #include <string_theory/format>
96 #include <string_theory/string>
97
98
99 static const INT16 sBasementEnterGridNos[] = { 13362, 13363, 13364, 13365, 13525, 13524 };
100 static const INT16 sBasementExitGridNos[] = { 8047, 8207, 8208, 8048, 7888, 7728, 7727, 7567 };
101
102
103 #define TALK_PANEL_FACE_X 6
104 #define TALK_PANEL_FACE_Y 9
105 #define TALK_PANEL_NAME_X 5
106 #define TALK_PANEL_NAME_Y 114
107 #define TALK_PANEL_NAME_WIDTH 92
108 #define TALK_PANEL_NAME_HEIGHT 15
109 #define TALK_PANEL_REGION_STARTX 102
110 #define TALK_PANEL_REGION_STARTY 14
111 #define TALK_PANEL_REGION_SPACEY 16
112 #define TALK_PANEL_REGION_HEIGHT 12
113 #define TALK_PANEL_REGION_WIDTH 95
114
115 #define TALK_PANEL_MENUTEXT_STARTX 102
116 #define TALK_PANEL_MENUTEXT_STARTY 16
117 #define TALK_PANEL_MENUTEXT_SPACEY 16
118 #define TALK_PANEL_MENUTEXT_HEIGHT 13
119 #define TALK_PANEL_MENUTEXT_WIDTH 95
120 #define TALK_PANEL_BUTTON_X 112
121 #define TALK_PANEL_BUTTON_Y 114
122 #define TALK_PANEL_SHADOW_AREA_X 97
123 #define TALK_PANEL_SHADOW_AREA_Y 9
124 #define TALK_PANEL_SHADOW_AREA_WIDTH 107
125 #define TALK_PANEL_SHADOW_AREA_HEIGHT 102
126
127 #define TALK_PANEL_DEFAULT_SUBTITLE_WIDTH 200
128
129 #define TALK_PANEL_CALC_SUBTITLE_WIDTH 280
130 #define TALK_PANEL_CALC_SUBTITLE_HEIGHT 125
131
132
133 #define TALK_PANEL_POPUP_LEFT 0
134 #define TALK_PANEL_POPUP_TOP 1
135 #define TALK_PANEL_POPUP_BOTTOM 2
136 #define TALK_PANEL_POPUP_RIGHT 3
137
138 // chance vince will say random quote to player during conv.
139 #define CHANCE_FOR_DOCTOR_TO_SAY_RANDOM_QUOTE 20
140
141
142 static Approach const ubTalkMenuApproachIDs[] =
143 {
144 APPROACH_REPEAT,
145 APPROACH_FRIENDLY,
146 APPROACH_DIRECT,
147 APPROACH_THREATEN,
148 APPROACH_BUYSELL,
149 APPROACH_RECRUIT
150 };
151
152
153 // GLOBAL NPC STRUCT
154 NPC_DIALOGUE_TYPE gTalkPanel;
155 BOOLEAN gfInTalkPanel = FALSE;
156 SOLDIERTYPE *gpSrcSoldier = NULL;
157 SOLDIERTYPE *gpDestSoldier = NULL;
158 UINT8 gubSrcSoldierProfile;
159 static UINT8 gubNiceNPCProfile = NO_PROFILE;
160 static UINT8 gubNastyNPCProfile = NO_PROFILE;
161
162 static UINT8 gubTargetNPC;
163 static UINT8 gubTargetRecord;
164 static Approach gubTargetApproach;
165 static BOOLEAN gfShowDialogueMenu;
166 BOOLEAN gfWaitingForTriggerTimer;
167 static UINT32 guiWaitingForTriggerTime;
168 MercPopUpBox* g_interface_dialogue_box;
169 static UINT8 ubRecordThatTriggeredLiePrompt;
170 static BOOLEAN gfConversationPending = FALSE;
171 static SOLDIERTYPE* gpPendingDestSoldier;
172 static SOLDIERTYPE* gpPendingSrcSoldier;
173 static Approach gbPendingApproach;
174 static UINT8 g_pending_approach_record;
175 static OBJECTTYPE* g_pending_approach_object;
176
177 INT32 giHospitalTempBalance; // stores amount of money for current doctoring
178 INT32 giHospitalRefund; // stores amount of money given to hospital for doctoring that wasn't used
179 INT8 gbHospitalPriceModifier; // stores discount being offered
180
181 enum
182 {
183 HOSPITAL_UNSET = 0,
184 HOSPITAL_NORMAL,
185 HOSPITAL_BREAK,
186 HOSPITAL_COST,
187 HOSPITAL_FREEBIE,
188 HOSPITAL_RANDOM_FREEBIE,
189 };
190
191
192 static void InternalInitiateConversation(SOLDIERTYPE* pDestSoldier, SOLDIERTYPE* pSrcSoldier, Approach, UINT8 approach_record, OBJECTTYPE* approach_object);
193
194
InitiateConversationFull(SOLDIERTYPE * const pDestSoldier,SOLDIERTYPE * const pSrcSoldier,Approach const bApproach,UINT8 const approach_record,OBJECTTYPE * const approach_object)195 BOOLEAN InitiateConversationFull(SOLDIERTYPE* const pDestSoldier, SOLDIERTYPE* const pSrcSoldier, Approach const bApproach, UINT8 const approach_record, OBJECTTYPE* const approach_object)
196 try
197 {
198 // ATE: OK, let's check the status of the Q
199 // If it has something in it....delay this until after....
200 if (DialogueQueueIsEmptyAndNobodyIsTalking())
201 {
202 gfConversationPending = FALSE;
203
204 // Initiate directly....
205 InternalInitiateConversation(pDestSoldier, pSrcSoldier, bApproach, approach_record, approach_object);
206 return TRUE;
207 }
208 else
209 {
210 // Wait.....
211 gfConversationPending = TRUE;
212
213 gpPendingDestSoldier = pDestSoldier;
214 gpPendingSrcSoldier = pSrcSoldier;
215 gbPendingApproach = bApproach;
216 g_pending_approach_record = approach_record;
217 g_pending_approach_object = approach_object;
218
219 //Engaged on conv...
220 gTacticalStatus.uiFlags |= ENGAGED_IN_CONV;
221
222 // Turn off lock UI ( if on )
223 guiPendingOverrideEvent = LU_ENDUILOCK;
224 HandleTacticalUI( );
225
226 // Turn on talking ui
227 guiPendingOverrideEvent = TA_TALKINGMENU;
228 HandleTacticalUI( );
229
230 return( FALSE );
231 }
232 }
233 catch (...) { return FALSE; /* XXX fishy, exception should probably propagate */ }
234
235
InitiateConversation(SOLDIERTYPE * const pDestSoldier,SOLDIERTYPE * const pSrcSoldier,Approach const bApproach)236 BOOLEAN InitiateConversation(SOLDIERTYPE* const pDestSoldier, SOLDIERTYPE* const pSrcSoldier, Approach const bApproach)
237 {
238 return InitiateConversationFull(pDestSoldier, pSrcSoldier, bApproach, 0, 0);
239 }
240
241
HandlePendingInitConv()242 void HandlePendingInitConv( )
243 {
244 if ( gfConversationPending )
245 {
246 // OK, if pending, remove and now call........
247 InternalInitiateConversation(gpPendingDestSoldier, gpPendingSrcSoldier, gbPendingApproach,
248 g_pending_approach_record, g_pending_approach_object);
249 }
250 }
251
252
253 static void InitTalkingMenu(UINT8 ubCharacterNum, INT16 sGridNo);
254
255
InternalInitiateConversation(SOLDIERTYPE * const pDestSoldier,SOLDIERTYPE * const pSrcSoldier,Approach const bApproach,UINT8 const approach_record,OBJECTTYPE * const approach_object)256 static void InternalInitiateConversation(SOLDIERTYPE* const pDestSoldier, SOLDIERTYPE* const pSrcSoldier, Approach const bApproach, UINT8 const approach_record, OBJECTTYPE* const approach_object)
257 {
258 // OK, init talking menu
259 BOOLEAN fFromPending;
260
261 fFromPending = gfConversationPending;
262
263 // Set pending false
264 gfConversationPending = FALSE;
265
266 // ATE: If we are already in menu, delete!
267 if ( gfInTalkPanel )
268 {
269 DeleteTalkingMenu( );
270 }
271
272 try
273 {
274 InitTalkingMenu(pDestSoldier->ubProfile, pDestSoldier->sGridNo);
275 }
276 catch (...)
277 {
278 // If failed, and we were pending, unlock UI
279 if ( fFromPending )
280 {
281 gTacticalStatus.uiFlags &= (~ENGAGED_IN_CONV);
282 }
283 SLOGD("Cannot initiate conversation menu.. check for face file for ID: %d.", pDestSoldier->ubProfile );
284 throw;
285 }
286
287 // Set soldier pointer
288 gpSrcSoldier = pSrcSoldier;
289 gpDestSoldier = pDestSoldier;
290
291 // Say first line...
292 // CHRIS LOOK HERE
293 if ( gpSrcSoldier != NULL )
294 {
295 gubSrcSoldierProfile = gpSrcSoldier->ubProfile;
296 }
297 else
298 {
299 gubSrcSoldierProfile = NO_PROFILE;
300 }
301
302 // find which squad this guy is, then set selected squad to this guy
303 if ( pSrcSoldier->bTeam == OUR_TEAM && gTacticalStatus.ubCurrentTeam == OUR_TEAM )
304 {
305 SetCurrentSquad( gpSrcSoldier->bAssignment, FALSE );
306
307 SelectSoldier(pSrcSoldier, SELSOLDIER_NONE);
308 }
309
310 ConverseFull(gTalkPanel.ubCharNum, gubSrcSoldierProfile, bApproach, approach_record, approach_object);
311
312 // If not from pedning...
313 if ( !fFromPending )
314 {
315 //Engaged on conv...
316 gTacticalStatus.uiFlags |= ENGAGED_IN_CONV;
317
318 // Turn off lock UI ( if on )
319 guiPendingOverrideEvent = LU_ENDUILOCK;
320 HandleTacticalUI( );
321
322 // Turn on talking ui
323 guiPendingOverrideEvent = TA_TALKINGMENU;
324 HandleTacticalUI( );
325 }
326 else
327 {
328 guiPendingOverrideEvent = TA_TALKINGMENU;
329 HandleTacticalUI( );
330 }
331 }
332
333
334 // This fuction will allocate and setup an NPCDiaogue structure. Loads the face for the character..
InitTalkingMenu(UINT8 const ubCharacterNum,INT16 const sGridNo)335 static void InitTalkingMenu(UINT8 const ubCharacterNum, INT16 const sGridNo)
336 {
337 INT16 sXMapPos, sYMapPos, sScreenX, sScreenY;
338 INT16 sX, sY;
339
340 // Get XY values
341 {
342 // Get XY locations for gridno.
343 ConvertGridNoToXY( sGridNo, &sXMapPos, &sYMapPos );
344
345 // Get screen XY pos from map XY
346 // Be carefull to convert to cell cords
347 CellXYToScreenXY( (INT16)((sXMapPos*CELL_X_SIZE)), (INT16)((sYMapPos*CELL_Y_SIZE)), &sScreenX, &sScreenY);
348
349
350 // First get mouse xy screen location
351 sX = sScreenX;
352 sY = sScreenY;
353
354 InternalInitTalkingMenu(ubCharacterNum, sX, sY);
355 }
356 }
357
358
359 static void CalculatePopupTextOrientation(INT16 sWidth, INT16 sHeight);
360 static void TalkPanelMoveCallback(MOUSE_REGION* pRegion, INT32 iReason);
361 static void TalkPanelClickCallback(MOUSE_REGION* pRegion, INT32 iReason);
362 static void TalkPanelBaseRegionClickCallback(MOUSE_REGION* pRegion, INT32 iReason);
363 static void TalkPanelNameRegionClickCallback(MOUSE_REGION* pRegion, INT32 iReason);
364 static void TalkPanelNameRegionMoveCallback(MOUSE_REGION* pRegion, INT32 iReason);
365 static void DoneTalkingButtonClickCallback(GUI_BUTTON* btn, INT32 reason);
366
367
InternalInitTalkingMenu(UINT8 const ubCharacterNum,INT16 sX,INT16 sY)368 void InternalInitTalkingMenu(UINT8 const ubCharacterNum, INT16 sX, INT16 sY)
369 {
370 INT16 sCenterYVal, sCenterXVal;
371
372 // disable scroll messages
373 HideMessagesDuringNPCDialogue( );
374
375 //ATE: OK, let go of any other dialogues up.....
376 EraseInterfaceMenus( FALSE );
377
378
379 gTalkPanel.ubCharNum = ubCharacterNum;
380 gTalkPanel.bCurSelect = -1;
381 gTalkPanel.bOldCurSelect = -1;
382 gTalkPanel.fHandled = FALSE;
383 gTalkPanel.fOnName = FALSE;
384
385 gTalkPanel.uiPanelVO = AddVideoObjectFromFile(INTERFACEDIR "/talkbox1.sti");
386
387 ETRLEObject const& ETRLEProps = gTalkPanel.uiPanelVO->SubregionProperties(0);
388 gTalkPanel.usWidth = ETRLEProps.usWidth;
389 gTalkPanel.usHeight = ETRLEProps.usHeight;
390
391 // Check coords
392 {
393
394 // CHECK FOR LEFT/RIGHT
395 sCenterXVal = gTalkPanel.usWidth / 2;
396
397 sX -= sCenterXVal;
398
399 // Check right
400 if (sX + gTalkPanel.usWidth > SCREEN_WIDTH)
401 {
402 sX = SCREEN_WIDTH - gTalkPanel.usWidth;
403 }
404
405 // Check left
406 if ( sX < 0 )
407 {
408 sX = 0;
409 }
410
411 // Now check for top
412 // Center in the y
413 sCenterYVal = gTalkPanel.usHeight / 2;
414
415 sY -= sCenterYVal;
416
417 if ( sY < gsVIEWPORT_WINDOW_START_Y )
418 {
419 sY = gsVIEWPORT_WINDOW_START_Y;
420 }
421
422 // Check for bottom
423 sY = MIN(sY, gsVIEWPORT_WINDOW_END_Y - gTalkPanel.usHeight);
424 }
425
426 //Set values
427 gTalkPanel.sX = sX;
428 gTalkPanel.sY = sY;
429
430 CalculatePopupTextOrientation( TALK_PANEL_CALC_SUBTITLE_WIDTH, TALK_PANEL_CALC_SUBTITLE_HEIGHT );
431
432 // Create face ( a big face! )....
433 FACETYPE& f = InitFace(ubCharacterNum, 0, FACE_BIGFACE | FACE_POTENTIAL_KEYWAIT);
434 gTalkPanel.face = &f;
435
436 // Create mouse regions...
437 sX = gTalkPanel.sX + TALK_PANEL_REGION_STARTX;
438 sY = gTalkPanel.sY + TALK_PANEL_REGION_STARTY;
439
440
441 //Define main region
442 MSYS_DefineRegion(&gTalkPanel.ScreenRegion, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MSYS_PRIORITY_HIGHEST, CURSOR_NORMAL, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK);
443
444 //Define main region
445 MSYS_DefineRegion(&(gTalkPanel.BackRegion), (INT16)(gTalkPanel.sX), (INT16)(gTalkPanel.sY),
446 (INT16)(gTalkPanel.sX + gTalkPanel.usWidth),
447 (INT16)(gTalkPanel.sY + gTalkPanel.usHeight),
448 MSYS_PRIORITY_HIGHEST, CURSOR_NORMAL, MSYS_NO_CALLBACK,
449 TalkPanelBaseRegionClickCallback);
450
451 //Define name region
452 MSYS_DefineRegion(&(gTalkPanel.NameRegion), (INT16)(gTalkPanel.sX + TALK_PANEL_NAME_X),
453 (INT16)(gTalkPanel.sY + TALK_PANEL_NAME_Y),
454 (INT16)(gTalkPanel.sX + TALK_PANEL_NAME_WIDTH + TALK_PANEL_NAME_X),
455 (INT16)(gTalkPanel.sY + TALK_PANEL_NAME_HEIGHT + TALK_PANEL_NAME_Y), MSYS_PRIORITY_HIGHEST, CURSOR_NORMAL, TalkPanelNameRegionMoveCallback,
456 TalkPanelNameRegionClickCallback );
457
458 for (INT32 cnt = 0; cnt < 6; ++cnt)
459 {
460 // Build a mouse region here that is over any others.....
461 MSYS_DefineRegion(&(gTalkPanel.Regions[cnt]), (INT16)(sX), (INT16)(sY),
462 (INT16)(sX + TALK_PANEL_REGION_WIDTH ),
463 (INT16)( sY + TALK_PANEL_REGION_HEIGHT ), MSYS_PRIORITY_HIGHEST,
464 CURSOR_NORMAL, TalkPanelMoveCallback, TalkPanelClickCallback);
465 MSYS_SetRegionUserData( &(gTalkPanel.Regions[cnt]), 0, cnt );
466
467 sY += TALK_PANEL_REGION_SPACEY;
468 }
469
470 // Build save buffer
471 // Create a buffer for him to go!
472 // OK, ignore screen widths, height, only use BPP
473 gTalkPanel.uiSaveBuffer = AddVideoSurface(f.usFaceWidth, f.usFaceHeight, PIXEL_DEPTH);
474
475 // Set face to auto
476 SetAutoFaceActive(gTalkPanel.uiSaveBuffer, FACE_AUTO_RESTORE_BUFFER, f, 0, 0);
477 f.uiFlags |= FACE_INACTIVE_HANDLED_ELSEWHERE;
478
479 // Load buttons, create button
480 gTalkPanel.iButtonImages = LoadButtonImage(INTERFACEDIR "/talkbox2.sti", 3, 4);
481
482 gTalkPanel.uiCancelButton = CreateIconAndTextButton(gTalkPanel.iButtonImages, zDialogActions, MILITARYFONT1,
483 33, DEFAULT_SHADOW, 33, DEFAULT_SHADOW,
484 gTalkPanel.sX + TALK_PANEL_BUTTON_X,
485 gTalkPanel.sY + TALK_PANEL_BUTTON_Y,
486 MSYS_PRIORITY_HIGHEST,
487 DoneTalkingButtonClickCallback );
488
489 gTalkPanel.uiCancelButton->SpecifyHilitedTextColors(FONT_MCOLOR_WHITE, DEFAULT_SHADOW);
490
491 // Turn off dirty flags
492 gTalkPanel.uiCancelButton->uiFlags &= ~BUTTON_DIRTY;
493
494 // Render once!
495 RenderAutoFace(f);
496
497 gfInTalkPanel = TRUE;
498
499 gfIgnoreScrolling = TRUE;
500 }
501
502
DoneTalkingButtonClickCallback(GUI_BUTTON * btn,INT32 reason)503 static void DoneTalkingButtonClickCallback(GUI_BUTTON* btn, INT32 reason)
504 {
505 if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
506 {
507 // OK, pickup item....
508 gTalkPanel.fHandled = TRUE;
509 gTalkPanel.fHandledTalkingVal = gTalkPanel.face->fTalking;
510 gTalkPanel.fHandledCanDeleteVal = TRUE;
511 }
512 }
513
514
DeleteTalkingMenu()515 void DeleteTalkingMenu( )
516 {
517 INT32 cnt;
518
519 if ( !gfInTalkPanel )
520 return;
521
522 // Delete sound if playing....
523 ShutupaYoFace(gTalkPanel.face);
524
525 // Delete screen region
526 MSYS_RemoveRegion( &(gTalkPanel.ScreenRegion));
527
528 // Delete main region
529 MSYS_RemoveRegion( &(gTalkPanel.BackRegion));
530
531 // Delete name region
532 MSYS_RemoveRegion( &(gTalkPanel.NameRegion));
533
534 // Delete mouse regions
535 for ( cnt = 0; cnt < 6; cnt++ )
536 {
537 MSYS_RemoveRegion( &(gTalkPanel.Regions[cnt]));
538 }
539
540 if ( gTalkPanel.fTextRegionOn )
541 {
542 // Remove
543 MSYS_RemoveRegion( &(gTalkPanel.TextRegion) );
544 gTalkPanel.fTextRegionOn = FALSE;
545 }
546
547 DeleteVideoSurface(gTalkPanel.uiSaveBuffer);
548
549 // Remove video object
550 DeleteVideoObject(gTalkPanel.uiPanelVO);
551
552 // Remove face....
553 DeleteFace(gTalkPanel.face);
554
555 // Remove button
556 RemoveButton( gTalkPanel.uiCancelButton );
557
558 // Remove button images
559 UnloadButtonImage( gTalkPanel.iButtonImages );
560
561 // Set cursor back to normal mode...
562 guiPendingOverrideEvent = A_CHANGE_TO_MOVE;
563
564 // Rerender world
565 SetRenderFlags( RENDER_FLAG_FULL );
566
567 gfInTalkPanel = FALSE;
568
569 gfIgnoreScrolling = FALSE;
570
571 // Set this guy up as NOT engaged in conversation
572 gpDestSoldier->uiStatusFlags &= (~SOLDIER_ENGAGEDINACTION);
573
574 //NOT Engaged on conv...
575 if ( !giNPCReferenceCount )
576 {
577 gTacticalStatus.uiFlags &= (~ENGAGED_IN_CONV);
578 }
579
580 // restore scroll messages in tactical
581 UnHideMessagesDuringNPCDialogue( );
582
583 if ( CheckFact( FACT_NEED_TO_SAY_SOMETHING, 0 ) )
584 {
585 if ( DialogueQueueIsEmpty() && !gfWaitingForTriggerTimer )
586 {
587 UINT8 ubNPC;
588 BOOLEAN fNice = FALSE;
589
590 if ( gubNiceNPCProfile != NO_PROFILE )
591 {
592 ubNPC = gubNiceNPCProfile;
593 fNice = TRUE;
594 }
595 else
596 {
597 ubNPC = gubNastyNPCProfile;
598 }
599
600 if ( ubNPC != NO_PROFILE )
601 {
602 const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubNPC);
603 if (pNPC)
604 {
605 // find someone to say their "nice guy" line
606 if ( fNice )
607 {
608 SayQuote58FromNearbyMercInSector( pNPC->sGridNo, 10, QUOTE_LISTEN_LIKABLE_PERSON, gMercProfiles[ ubNPC ].bSex );
609 }
610 else
611 {
612 SayQuoteFromNearbyMercInSector( pNPC->sGridNo, 10, QUOTE_ANNOYING_PC );
613 }
614 gubNiceNPCProfile = NO_PROFILE;
615 gubNastyNPCProfile = NO_PROFILE;
616 SetFactFalse( FACT_NEED_TO_SAY_SOMETHING );
617 }
618 }
619 }
620 }
621
622 if (g_interface_dialogue_box)
623 {
624 RemoveMercPopupBox(g_interface_dialogue_box);
625 g_interface_dialogue_box = 0;
626 }
627 }
628
629
630 static void CalculatePopupTextPosition(INT16 sWidth, INT16 sHeight);
631 static void TextRegionClickCallback(MOUSE_REGION* pRegion, INT32 iReason);
632
633
RenderTalkingMenu()634 void RenderTalkingMenu()
635 {
636 NPC_DIALOGUE_TYPE* const tp = &gTalkPanel;
637 ProfileID const pid = tp->ubCharNum;
638
639 if (!gfInTalkPanel) return;
640
641 // Render box!
642 BltVideoObject(FRAME_BUFFER, tp->uiPanelVO, 0, tp->sX, tp->sY);
643
644 // Render name
645 SetFontAttributes(MILITARYFONT1, tp->fOnName ? FONT_WHITE : 33);
646 ST::string name = GetProfile(pid).zNickname;
647 INT16 sFontX;
648 INT16 sFontY;
649 FindFontCenterCoordinates(tp->sX + TALK_PANEL_NAME_X, tp->sY + TALK_PANEL_NAME_Y, TALK_PANEL_NAME_WIDTH, TALK_PANEL_NAME_HEIGHT, name, MILITARYFONT1, &sFontX, &sFontY);
650 MPrint(sFontX, sFontY, name);
651
652 SetFontShadow(DEFAULT_SHADOW);
653
654 BltVideoSurface(FRAME_BUFFER, tp->uiSaveBuffer, tp->sX + TALK_PANEL_FACE_X, tp->sY + TALK_PANEL_FACE_Y, NULL);
655
656 MarkButtonsDirty();
657
658 // If guy is talking.... shadow area
659 if (tp->face->fTalking || !DialogueQueueIsEmpty())
660 {
661 INT32 const x = tp->sX + TALK_PANEL_SHADOW_AREA_X;
662 INT32 const y = tp->sY + TALK_PANEL_SHADOW_AREA_Y;
663 FRAME_BUFFER->ShadowRect(x, y, x + TALK_PANEL_SHADOW_AREA_WIDTH, y + TALK_PANEL_SHADOW_AREA_HEIGHT);
664
665 // Disable mouse regions....
666 for (INT32 cnt = 0; cnt < 6; ++cnt)
667 {
668 tp->Regions[cnt].Disable();
669 }
670
671 DisableButton(tp->uiCancelButton);
672
673 tp->bCurSelect = -1;
674 }
675 else
676 {
677 // Enable mouse regions....
678 for (INT32 cnt = 0; cnt < 6; ++cnt)
679 {
680 tp->Regions[cnt].Enable();
681 }
682
683 EnableButton(tp->uiCancelButton);
684
685 // Restore selection....
686 tp->bCurSelect = tp->bOldCurSelect;
687 }
688
689 InvalidateRegion(tp->sX, tp->sY, tp->sX + tp->usWidth, tp->sY + tp->usHeight);
690
691 if (tp->fSetupSubTitles)
692 {
693 if (g_interface_dialogue_box)
694 {
695 // Remove any old ones....
696 RemoveMercPopupBox(g_interface_dialogue_box);
697 g_interface_dialogue_box = 0;
698 }
699
700 UINT16 usTextBoxWidth;
701 UINT16 usTextBoxHeight;
702 g_interface_dialogue_box = PrepareMercPopupBox(0, BASIC_MERC_POPUP_BACKGROUND, BASIC_MERC_POPUP_BORDER, tp->zQuoteStr, TALK_PANEL_DEFAULT_SUBTITLE_WIDTH, 0, 0, 0, &usTextBoxWidth, &usTextBoxHeight);
703 SetFont(MILITARYFONT1); // PrepareMercPopupBox() overwrites the current font
704
705 tp->fSetupSubTitles = FALSE;
706
707 CalculatePopupTextOrientation(usTextBoxWidth, usTextBoxHeight);
708 CalculatePopupTextPosition( usTextBoxWidth, usTextBoxHeight);
709
710 //Define main region
711 if (tp->fTextRegionOn)
712 {
713 // Remove
714 MSYS_RemoveRegion(&tp->TextRegion);
715 tp->fTextRegionOn = FALSE;
716 }
717
718 UINT16 const x = tp->sPopupX;
719 UINT16 const y = tp->sPopupY;
720 MSYS_DefineRegion(&tp->TextRegion, x, y, x + usTextBoxWidth, y + usTextBoxHeight, MSYS_PRIORITY_HIGHEST, CURSOR_NORMAL, MSYS_NO_CALLBACK, TextRegionClickCallback);
721
722 tp->fTextRegionOn = TRUE;
723 }
724
725 if (tp->fRenderSubTitlesNow)
726 {
727 RenderMercPopUpBox(g_interface_dialogue_box, tp->sPopupX, tp->sPopupY, FRAME_BUFFER);
728 }
729
730 // Create menu selections....
731 INT16 const x = tp->sX + TALK_PANEL_MENUTEXT_STARTX;
732 INT16 y = tp->sY + TALK_PANEL_MENUTEXT_STARTY;
733 for (INT32 cnt = 0; cnt < 6; cnt++)
734 {
735 if (tp->bCurSelect == cnt)
736 {
737 SetFontForeground(FONT_WHITE);
738 SetFontShadow(DEFAULT_SHADOW);
739 }
740 else
741 {
742 SetFontForeground(FONT_BLACK);
743 SetFontShadow(MILITARY_SHADOW);
744 }
745
746 ST::string str;
747 ST::string buf;
748 if (cnt == 4 && IsMercADealer(pid))
749 {
750 str = zDealerStrings[GetTypeOfArmsDealer(GetArmsDealerIDFromMercID(pid))];
751 }
752 else if (cnt != 0 &&
753 #ifndef _DEBUG
754 CHEATER_CHEAT_LEVEL() &&
755 #endif
756 gubSrcSoldierProfile != NO_PROFILE &&
757 pid != NO_PROFILE)
758 {
759 UINT8 const desire = CalcDesireToTalk(pid, gubSrcSoldierProfile, ubTalkMenuApproachIDs[cnt]);
760 buf = ST::format("{} ({})", zTalkMenuStrings[cnt], desire);
761 str = buf;
762 }
763 else
764 {
765 str = zTalkMenuStrings[cnt];
766 }
767 INT16 sFontX;
768 INT16 sFontY;
769 FindFontCenterCoordinates(x, y, TALK_PANEL_MENUTEXT_WIDTH, TALK_PANEL_MENUTEXT_HEIGHT, str, MILITARYFONT1, &sFontX, &sFontY);
770 MPrint(sFontX, sFontY, str);
771
772 y += TALK_PANEL_MENUTEXT_SPACEY;
773 }
774
775 SetFontShadow(DEFAULT_SHADOW);
776 }
777
778
TalkPanelMoveCallback(MOUSE_REGION * pRegion,INT32 iReason)779 static void TalkPanelMoveCallback(MOUSE_REGION* pRegion, INT32 iReason)
780 {
781 UINT32 uiItemPos;
782
783 uiItemPos = MSYS_GetRegionUserData( pRegion, 0 );
784
785 if (iReason & MSYS_CALLBACK_REASON_MOVE)
786 {
787 // Set current selected guy....
788 gTalkPanel.bCurSelect =(INT8)uiItemPos;
789 gTalkPanel.bOldCurSelect = gTalkPanel.bCurSelect;
790 }
791 else if ( iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
792 {
793 gTalkPanel.bCurSelect = -1;
794 gTalkPanel.bOldCurSelect = -1;
795 }
796
797 }
798
799
TalkPanelClickCallback(MOUSE_REGION * pRegion,INT32 iReason)800 static void TalkPanelClickCallback(MOUSE_REGION* pRegion, INT32 iReason)
801 {
802 UINT32 uiItemPos;
803 BOOLEAN fDoConverse = TRUE;
804 uiItemPos = MSYS_GetRegionUserData( pRegion, 0 );
805
806
807 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
808 {
809 // Donot do this if we are talking already
810 if (!gTalkPanel.face->fTalking)
811 {
812 if ( ubTalkMenuApproachIDs[ uiItemPos ] == APPROACH_BUYSELL )
813 {
814 //if its an arms dealer
815 if( IsMercADealer( gTalkPanel.ubCharNum ) )
816 {
817 if ( NPCHasUnusedRecordWithGivenApproach( gTalkPanel.ubCharNum, APPROACH_BUYSELL ) )
818 {
819 TriggerNPCWithGivenApproach(gTalkPanel.ubCharNum, APPROACH_BUYSELL);
820 }
821 else
822 {
823 DeleteTalkingMenu( );
824
825 //Enter the shopkeeper interface
826 EnterShopKeeperInterfaceScreen( gTalkPanel.ubCharNum );
827 }
828
829 /*
830 // check if this is a shopkeeper who has been shutdown
831 if (!HandleShopKeepHasBeenShutDown(gTalkPanel.ubCharNum))
832 {
833 DeleteTalkingMenu( );
834
835 EnterShopKeeperInterfaceScreen( gTalkPanel.ubCharNum );
836 }*/
837 }
838 else
839 {
840 // Do something different if we selected the 'give' approach
841 // Close panel, set UI guy to wait a sec, open inv if not done so yet
842 gTalkPanel.fHandled = TRUE;
843 gTalkPanel.fHandledTalkingVal = gTalkPanel.face->fTalking;
844 gTalkPanel.fHandledCanDeleteVal = TRUE;
845
846 // open inv panel...
847 SetNewPanel(gpSrcSoldier);
848
849 // Wait!
850 gpDestSoldier->bNextAction = AI_ACTION_WAIT;
851 gpDestSoldier->usNextActionData = 10000;
852
853 // UNless he's has a pending action, delete what he was doing!
854 // Cancel anything he was doing
855 if ( gpDestSoldier->bAction != AI_ACTION_PENDING_ACTION )
856 {
857 CancelAIAction(gpDestSoldier);
858 }
859 }
860 }
861 else
862 {
863 if ( fDoConverse )
864 {
865 //Speak
866 Converse(gTalkPanel.ubCharNum, gubSrcSoldierProfile, ubTalkMenuApproachIDs[uiItemPos]);
867 }
868 }
869 }
870 }
871 }
872
873
TalkPanelBaseRegionClickCallback(MOUSE_REGION * pRegion,INT32 iReason)874 static void TalkPanelBaseRegionClickCallback(MOUSE_REGION* pRegion, INT32 iReason)
875 {
876 static BOOLEAN fLButtonDown = FALSE;
877
878 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_DWN )
879 {
880 fLButtonDown = TRUE;
881 }
882
883 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP && fLButtonDown )
884 {
885 // Only do this if we are talking already
886 if (gTalkPanel.face->fTalking)
887 {
888 // Stop speech, cancel
889 InternalShutupaYoFace(gTalkPanel.face, FALSE);
890
891 fLButtonDown = FALSE;
892 }
893 }
894 else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
895 {
896 fLButtonDown = FALSE;
897 }
898 }
899
900
TalkPanelNameRegionClickCallback(MOUSE_REGION * pRegion,INT32 iReason)901 static void TalkPanelNameRegionClickCallback(MOUSE_REGION* pRegion, INT32 iReason)
902 {
903 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
904 {
905 // Donot do this if we are talking already
906 if (!gTalkPanel.face->fTalking)
907 {
908 // Say who are you?
909 Converse(gTalkPanel.ubCharNum, gubSrcSoldierProfile, NPC_WHOAREYOU);
910 }
911 }
912 }
913
914
TalkPanelNameRegionMoveCallback(MOUSE_REGION * pRegion,INT32 iReason)915 static void TalkPanelNameRegionMoveCallback(MOUSE_REGION* pRegion, INT32 iReason)
916 {
917 // Donot do this if we are talking already
918 if (gTalkPanel.face->fTalking) return;
919
920 if (iReason & MSYS_CALLBACK_REASON_MOVE)
921 {
922 // Set current selected guy....
923 gTalkPanel.fOnName = TRUE;
924 }
925 else if ( iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
926 {
927 gTalkPanel.fOnName = FALSE;
928 }
929
930 }
931
932
HandleTalkingMenu()933 BOOLEAN HandleTalkingMenu( )
934 {
935 if ( !gfInTalkPanel )
936 {
937 return( FALSE );
938 }
939
940 if ( gTalkPanel.fHandled )
941 {
942 return( HandleTalkingMenuEscape( gTalkPanel.fHandledCanDeleteVal, FALSE ) );
943 }
944
945 return( FALSE );
946 }
947
948
TalkingMenuDialogue(UINT16 const usQuoteNum)949 void TalkingMenuDialogue(UINT16 const usQuoteNum)
950 {
951 // Set back current select....
952 gTalkPanel.bCurSelect = -1;
953 gTalkPanel.fOnName = FALSE;
954 //gTalkPanel.fHandled = FALSE;
955
956 CharacterDialogue(gTalkPanel.ubCharNum, usQuoteNum, gTalkPanel.face, DIALOGUE_NPC_UI, FALSE);
957 }
958
959
ProfileCurrentlyTalkingInDialoguePanel(UINT8 const ubProfile)960 bool ProfileCurrentlyTalkingInDialoguePanel(UINT8 const ubProfile)
961 {
962 return gfInTalkPanel &&
963 gpDestSoldier &&
964 gpDestSoldier->ubProfile == ubProfile;
965 }
966
967
HandleTalkingMenuEscape(BOOLEAN fCanDelete,BOOLEAN fFromEscKey)968 BOOLEAN HandleTalkingMenuEscape( BOOLEAN fCanDelete , BOOLEAN fFromEscKey )
969 {
970 BOOLEAN fTalking = FALSE;
971
972 if ( !gfInTalkPanel )
973 {
974 return( FALSE );
975 }
976
977 // If we are in the process of speaking, stop this quote an move on...
978 // If we have been 'handled' by an outside source, check what was our talking value at the time
979 if ( gTalkPanel.fHandled )
980 {
981 fTalking = gTalkPanel.fHandledTalkingVal;
982 }
983 else
984 {
985 fTalking = gTalkPanel.face->fTalking;
986 }
987
988 // Set to false
989 gTalkPanel.fHandled = FALSE;
990
991 if ( !fFromEscKey )
992 {
993 if ( fTalking )
994 {
995 ShutupaYoFace(gTalkPanel.face);
996 }
997 // Else if our queue is empty, delete emnu
998 else
999 {
1000 if ( DialogueQueueIsEmpty( ) && fCanDelete )
1001 {
1002 // Delete panel
1003 DeleteTalkingMenu( );
1004 // reset records which are on a can-say-once-per-convo basis
1005 ResetOncePerConvoRecordsForNPC( gpDestSoldier->ubProfile );
1006 return( TRUE );
1007 }
1008 }
1009 }
1010 else
1011 {
1012 if ( DialogueQueueIsEmpty( ) && fCanDelete )
1013 {
1014 // Delete panel
1015 DeleteTalkingMenu( );
1016 // reset records which are on a can-say-once-per-convo basis
1017 ResetOncePerConvoRecordsForNPC( gpDestSoldier->ubProfile );
1018 return( TRUE );
1019 }
1020 }
1021 return( FALSE );
1022 }
1023
1024
HandleTalkingMenuBackspace(void)1025 void HandleTalkingMenuBackspace(void)
1026 {
1027 if ( !gfInTalkPanel )
1028 {
1029 return;
1030 }
1031
1032 const FACETYPE* const pFace = gTalkPanel.face;
1033
1034 // If we are in the process of speaking, stop this quote an move on...
1035 // If we have been 'handled' by an outside source, check what was our talking value at the time
1036 /*
1037 BOOLEAN fTalking;
1038 if ( gTalkPanel.fHandled )
1039 {
1040 fTalking = gTalkPanel.fHandledTalkingVal;
1041 }
1042 else
1043 {
1044 fTalking = pFace->fTalking;
1045 }
1046
1047 // Set to false
1048 gTalkPanel.fHandled = FALSE;
1049
1050
1051 if ( fTalking ) */
1052 if (pFace->fTalking) ShutupaYoFace(gTalkPanel.face);
1053 }
1054
1055
CalculatePopupTextOrientation(INT16 sWidth,INT16 sHeight)1056 static void CalculatePopupTextOrientation(INT16 sWidth, INT16 sHeight)
1057 {
1058 BOOLEAN fOKLeft = FALSE, fOKTop = FALSE, fOKBottom = FALSE, fOK = FALSE;
1059 INT16 sX, sY;
1060
1061 // Check Left
1062 sX = gTalkPanel.sX - sWidth;
1063
1064 if ( sX > 0 )
1065 {
1066 fOKLeft = TRUE;
1067 }
1068
1069 // Check bottom
1070 sY = gTalkPanel.sY + sHeight + gTalkPanel.usHeight;
1071
1072 if ( sY < 340 )
1073 {
1074 fOKBottom = TRUE;
1075 }
1076
1077 // Check top
1078 sY = gTalkPanel.sY - sHeight;
1079
1080 if ( sY > gsVIEWPORT_WINDOW_START_Y )
1081 {
1082 fOKTop = TRUE;
1083 }
1084
1085 // OK, now decide where to put it!
1086
1087 // First precidence is bottom
1088 if ( fOKBottom )
1089 {
1090 gTalkPanel.ubPopupOrientation = TALK_PANEL_POPUP_BOTTOM;
1091
1092 fOK = TRUE;
1093 }
1094
1095 if ( !fOK )
1096 {
1097 // Try left
1098 if ( fOKLeft )
1099 {
1100 // Our panel should not be heigher than our dialogue talking panel, so don't bother with the height checks!
1101 gTalkPanel.ubPopupOrientation = TALK_PANEL_POPUP_LEFT;
1102 fOK = TRUE;
1103 }
1104
1105 }
1106
1107 // Now at least top should work
1108 if ( !fOK )
1109 {
1110 // Try top!
1111 if ( fOKTop )
1112 {
1113 gTalkPanel.ubPopupOrientation = TALK_PANEL_POPUP_TOP;
1114
1115 fOK = TRUE;
1116 }
1117 }
1118
1119 // failed all the above
1120 if( !fOK )
1121 {
1122 // when all else fails go right
1123 gTalkPanel.ubPopupOrientation = TALK_PANEL_POPUP_RIGHT;
1124 fOK = TRUE;
1125
1126 }
1127 // If we don't have anything here... our viewport/box is too BIG! ( which should never happen
1128 }
1129
1130
CalculatePopupTextPosition(INT16 sWidth,INT16 sHeight)1131 static void CalculatePopupTextPosition(INT16 sWidth, INT16 sHeight)
1132 {
1133 switch( gTalkPanel.ubPopupOrientation )
1134 {
1135 case TALK_PANEL_POPUP_LEFT:
1136
1137 // Set it here!
1138 gTalkPanel.sPopupX = gTalkPanel.sX - sWidth;
1139 // Center in height!
1140 gTalkPanel.sPopupY = gTalkPanel.sY + ( gTalkPanel.usHeight / 2 ) - ( sHeight / 2 );
1141 break;
1142 case TALK_PANEL_POPUP_RIGHT:
1143 // Set it here!
1144 gTalkPanel.sPopupX = gTalkPanel.sX + gTalkPanel.usWidth + 1;
1145 // Center in height!
1146 gTalkPanel.sPopupY = gTalkPanel.sY + ( gTalkPanel.usHeight / 2 ) - ( sHeight / 2 );
1147 break;
1148 case TALK_PANEL_POPUP_BOTTOM:
1149
1150 // Center in X
1151 gTalkPanel.sPopupX = gTalkPanel.sX + ( gTalkPanel.usWidth / 2 ) - ( sWidth / 2 );
1152 // Calc height
1153 gTalkPanel.sPopupY = gTalkPanel.sY + gTalkPanel.usHeight;
1154 break;
1155
1156 case TALK_PANEL_POPUP_TOP:
1157
1158 // Center in X
1159 gTalkPanel.sPopupX = gTalkPanel.sX + ( gTalkPanel.usWidth / 2 ) - ( sWidth / 2 );
1160 // Calc height
1161 gTalkPanel.sPopupY = gTalkPanel.sY - sHeight;
1162 break;
1163 }
1164 }
1165
1166
1167 static void HandleNPCGotoGridNo(UINT8 ubTargetNPC, UINT16 usGridNo, UINT8 ubQuoteNum);
1168
1169
NPCGotoGridNo(ProfileID const ubTargetNPC,UINT16 const usGridNo,UINT8 const ubRecordNum)1170 void NPCGotoGridNo(ProfileID const ubTargetNPC, UINT16 const usGridNo, UINT8 const ubRecordNum)
1171 {
1172 class DialogueEventGotoGridno : public DialogueEvent
1173 {
1174 public:
1175 DialogueEventGotoGridno(ProfileID const npc, GridNo const gridno, UINT8 const record) :
1176 gridno_(gridno),
1177 npc_(npc),
1178 record_(record)
1179 {}
1180
1181 bool Execute()
1182 {
1183 HandleNPCGotoGridNo(npc_, gridno_, record_);
1184 return false;
1185 }
1186
1187 private:
1188 GridNo const gridno_;
1189 ProfileID const npc_;
1190 UINT8 const record_;
1191 };
1192
1193 DialogueEvent::Add(new DialogueEventGotoGridno(ubTargetNPC, usGridNo, ubRecordNum));
1194 }
1195
1196
NPCDoAction(UINT8 const ubTargetNPC,UINT16 const usActionCode,UINT8 const ubQuoteNum)1197 void NPCDoAction(UINT8 const ubTargetNPC, UINT16 const usActionCode, UINT8 const ubQuoteNum)
1198 {
1199 class DialogueEventDoAction : public DialogueEvent
1200 {
1201 public:
1202 DialogueEventDoAction(ProfileID npc, UINT16 const action, UINT8 const quote) :
1203 action_(action),
1204 npc_(npc),
1205 quote_(quote)
1206 {}
1207
1208 bool Execute()
1209 {
1210 HandleNPCDoAction(npc_, action_, quote_);
1211 return false;
1212 }
1213
1214 private:
1215 UINT16 const action_;
1216 ProfileID const npc_;
1217 UINT8 const quote_;
1218 };
1219
1220 DialogueEvent::Add(new DialogueEventDoAction(ubTargetNPC, usActionCode, ubQuoteNum));
1221 }
1222
1223
SourceSoldierPointerIsValidAndReachableForGive(SOLDIERTYPE * pGiver)1224 static BOOLEAN SourceSoldierPointerIsValidAndReachableForGive(SOLDIERTYPE* pGiver)
1225 {
1226 INT16 sAdjGridNo;
1227
1228 if ( !gpSrcSoldier )
1229 {
1230 return( FALSE );
1231 }
1232 if ( !gpSrcSoldier->bActive || !gpSrcSoldier->bInSector )
1233 {
1234 return( FALSE );
1235 }
1236 if ( gpSrcSoldier->bLife < OKLIFE || ( gpSrcSoldier->bBreath < OKBREATH && gpSrcSoldier->bCollapsed ) )
1237 {
1238 return( FALSE );
1239 }
1240
1241 if ( !pGiver )
1242 {
1243 return( FALSE );
1244 }
1245
1246 // pointer should always be valid anyhow
1247 if ( PythSpacesAway( pGiver->sGridNo, gpSrcSoldier->sGridNo ) > MaxDistanceVisible() )
1248 {
1249 return( FALSE );
1250 }
1251
1252 sAdjGridNo = FindAdjacentGridEx( pGiver, gpSrcSoldier->sGridNo, NULL, NULL, FALSE, FALSE );
1253 if ( sAdjGridNo == -1 )
1254 {
1255 return( FALSE );
1256 }
1257
1258 return( TRUE );
1259 }
1260
1261
HandleNPCItemGiven(UINT8 ubNPC,OBJECTTYPE * pObject,INT8 bInvPos)1262 void HandleNPCItemGiven( UINT8 ubNPC, OBJECTTYPE *pObject, INT8 bInvPos )
1263 {
1264
1265 // Give it to the NPC soldier
1266 //AutoPlaceObject( gpDestSoldier, pObject, FALSE );
1267
1268 // OK< if the timer is < 5000, use who was last in the talk panel box.
1269 if ( !SourceSoldierPointerIsValidAndReachableForGive( gpDestSoldier ) )
1270 {
1271 // just drop it
1272
1273 // have to walk up to the merc closest to ubNPC
1274 SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubNPC);
1275 if ( pNPC )
1276 {
1277 AddItemToPool(pNPC->sGridNo, &pNPC->inv[bInvPos], VISIBLE, 0, 0, 0);
1278 TriggerNPCWithGivenApproach(ubNPC, APPROACH_DONE_GIVING_ITEM);
1279 }
1280 }
1281 else
1282 {
1283 // Remove dialogue!
1284 DeleteTalkingMenu( );
1285
1286 // Give this to buddy!
1287 SoldierGiveItem( gpDestSoldier, gpSrcSoldier, pObject, bInvPos );
1288 }
1289
1290 }
1291
1292
1293 static void HandleNPCTrigger(void);
1294
1295
HandleNPCTriggerNPC(UINT8 const ubTargetNPC,UINT8 const ubTargetRecord,BOOLEAN const fShowDialogueMenu,Approach const ubTargetApproach)1296 void HandleNPCTriggerNPC(UINT8 const ubTargetNPC, UINT8 const ubTargetRecord, BOOLEAN const fShowDialogueMenu, Approach const ubTargetApproach)
1297 {
1298 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1299 if ( pSoldier == NULL )
1300 {
1301 return;
1302 }
1303
1304 // Save values for trigger function
1305 gubTargetNPC = ubTargetNPC;
1306 gubTargetRecord = ubTargetRecord;
1307 gubTargetApproach = ubTargetApproach;
1308 gfShowDialogueMenu = fShowDialogueMenu;
1309
1310
1311 if ( pSoldier->bTeam == OUR_TEAM )
1312 {
1313 // make sure they are in the right alert status to receive orders (it's a bug that
1314 // this could be set for the player...)
1315 pSoldier->bAlertStatus = STATUS_GREEN;
1316 pSoldier->uiStatusFlags &= (~SOLDIER_ENGAGEDINACTION );
1317 }
1318
1319 // OH BOY, CHECK IF THIS IS THE SAME PERSON WHO IS ON THE MENU
1320 // RIGHT NOW, IF SO , HANDLE DIRECTLY WITHOUT THIS DELAY!
1321 // IF WE ARE TO DISPLAY MENU AS WELL....
1322 if ( gfInTalkPanel )
1323 {
1324 if ( gpDestSoldier == pSoldier && fShowDialogueMenu )
1325 {
1326 HandleNPCTrigger( );
1327 return;
1328 }
1329 }
1330
1331 if ( fShowDialogueMenu )
1332 {
1333
1334 // ALRIGHTY, NOW DO THIS!
1335 // WAIT IN NON-INTERACTIVE MODE UNTIL TIMER IS DONE
1336 // SO WE CAN SEE NPC RADIO LOCATOR
1337 // THEN AFTER THIS IS DONE, DO THE STUFF
1338 // Setup timer!
1339 gfWaitingForTriggerTimer = TRUE;
1340 guiWaitingForTriggerTime = GetJA2Clock( );
1341
1342 // Setup locator!
1343 ShowRadioLocator(pSoldier, SHOW_LOCATOR_FAST);
1344
1345 // If he's visible, locate...
1346 if (pSoldier->bVisible != -1) LocateSoldier(pSoldier, FALSE);
1347
1348 guiPendingOverrideEvent = LU_BEGINUILOCK;
1349
1350 }
1351 else
1352 {
1353 HandleNPCTrigger( );
1354 }
1355
1356 // Being already here, we know that this is not the guy whose panel is up.
1357 // Only close the panel if dialogue is involved in the new record.
1358 if ( RecordHasDialogue( ubTargetNPC, ubTargetRecord ) )
1359 {
1360 // Shutdown any panel we had up...
1361 DeleteTalkingMenu( );
1362 }
1363 }
1364
1365
HandleNPCTrigger(void)1366 static void HandleNPCTrigger(void)
1367 {
1368 INT16 sPlayerGridNo;
1369
1370 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(gubTargetNPC);
1371 if (!pSoldier)
1372 {
1373 return;
1374 }
1375
1376 if ( gfInTalkPanel )
1377 {
1378 if ( pSoldier == gpDestSoldier )
1379 {
1380 if ( gfShowDialogueMenu )
1381 {
1382 // Converse another way!
1383 ConverseFull(gubTargetNPC, NO_PROFILE, gubTargetApproach, gubTargetRecord, 0);
1384 }
1385 else if (gpSrcSoldier != NULL) // if we can, reinitialize menu
1386 {
1387 InitiateConversationFull(gpDestSoldier, gpSrcSoldier, gubTargetApproach, gubTargetRecord, 0);
1388 }
1389 }
1390 else
1391 {
1392 ConverseFull(gubTargetNPC, NO_PROFILE, gubTargetApproach, gubTargetRecord, 0);
1393 }
1394 }
1395 else
1396 {
1397 // Now start new one...
1398 if ( gfShowDialogueMenu )
1399 {
1400 if ( SourceSoldierPointerIsValidAndReachableForGive( pSoldier ) )
1401 {
1402 InitiateConversationFull(pSoldier, gpSrcSoldier, gubTargetApproach, gubTargetRecord, 0);
1403 return;
1404 }
1405 else
1406 {
1407 sPlayerGridNo = ClosestPC( pSoldier, NULL );
1408 if (sPlayerGridNo != NOWHERE )
1409 {
1410 SOLDIERTYPE* const player = WhoIsThere2(sPlayerGridNo, 0);
1411 if (player != NULL)
1412 {
1413 InitiateConversationFull(pSoldier, player, gubTargetApproach, gubTargetRecord, 0);
1414 return;
1415 }
1416 }
1417 }
1418 // else
1419 InitiateConversationFull(pSoldier, pSoldier, gubTargetApproach, gubTargetRecord, 0);
1420 }
1421 else
1422 {
1423 // Converse another way!
1424 ConverseFull(gubTargetNPC, NO_PROFILE, gubTargetApproach, gubTargetRecord, 0);
1425 }
1426 }
1427
1428 }
1429
1430
HandleWaitTimerForNPCTrigger()1431 void HandleWaitTimerForNPCTrigger( )
1432 {
1433 if ( gfWaitingForTriggerTimer )
1434 {
1435 if ( ( GetJA2Clock( ) - guiWaitingForTriggerTime ) > 500 )
1436 {
1437 guiPendingOverrideEvent = LU_ENDUILOCK;
1438
1439 UIHandleLUIEndLock( NULL );
1440
1441 HandleNPCTrigger( );
1442
1443 gfWaitingForTriggerTimer = FALSE;
1444 }
1445
1446 }
1447
1448 }
1449
1450
HandleNPCGotoGridNo(UINT8 const ubTargetNPC,UINT16 const usGridNo,UINT8 const ubQuoteNum)1451 static void HandleNPCGotoGridNo(UINT8 const ubTargetNPC, UINT16 const usGridNo, UINT8 const ubQuoteNum)
1452 {
1453 // OK, Move to gridNo!
1454
1455 // Shotdown any panel we had up...
1456 DeleteTalkingMenu( );
1457
1458 // Get merc id for NPC
1459 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1460 if ( !pSoldier )
1461 {
1462 return;
1463 }
1464
1465 // zap any delay in this soldier
1466 ZEROTIMECOUNTER( pSoldier->AICounter );
1467 if (pSoldier->bNextAction == AI_ACTION_WAIT)
1468 {
1469 pSoldier->bNextAction = AI_ACTION_NONE;
1470 pSoldier->usNextActionData = 0;
1471 }
1472
1473 // if player controlled, set under AI control flag
1474 if (pSoldier->bTeam == OUR_TEAM)
1475 {
1476 pSoldier->uiStatusFlags |= SOLDIER_PCUNDERAICONTROL;
1477 }
1478
1479 // OK, set in motion!
1480 pSoldier->bNextAction = AI_ACTION_WALK;
1481
1482 // Set dest!
1483 pSoldier->usNextActionData = usGridNo;
1484
1485 // UNless he's has a pending action, delete what he was doing!
1486 // Cancel anything he was doing
1487 if ( pSoldier->bAction != AI_ACTION_PENDING_ACTION )
1488 {
1489 CancelAIAction(pSoldier);
1490 }
1491 // Go for it!
1492
1493 // Set flags to do stuff once there...
1494 pSoldier->ubQuoteRecord = ( ubQuoteNum + 1 );
1495 pSoldier->ubQuoteActionID = ActionIDForMovementRecord( ubTargetNPC, ubQuoteNum );
1496
1497 // Set absolute dest
1498 pSoldier->sAbsoluteFinalDestination = usGridNo;
1499
1500 // handle this guy's AI right away so that we can get him moving
1501 pSoldier->fAIFlags |= AI_HANDLE_EVERY_FRAME;
1502 }
1503
1504
HandleStuffForNPCEscorted(UINT8 ubNPC)1505 static void HandleStuffForNPCEscorted(UINT8 ubNPC)
1506 {
1507 switch( ubNPC )
1508 {
1509 case MARIA:
1510 break;
1511 case JOEY:
1512 break;
1513 case SKYRIDER:
1514 SetFactTrue( FACT_SKYRIDER_EVER_ESCORTED );
1515 if ( gubQuest[ QUEST_ESCORT_SKYRIDER ] == QUESTNOTSTARTED )
1516 {
1517 StartQuest( QUEST_ESCORT_SKYRIDER, gWorldSectorX, gWorldSectorY );
1518 }
1519 break;
1520 case JOHN:
1521 {
1522 // recruit Mary as well
1523 RecruitEPC( MARY );
1524
1525 SOLDIERTYPE* const pSoldier = FindSoldierByProfileIDOnPlayerTeam(MARY);
1526 if ( pSoldier )
1527 {
1528 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[ NOW_BING_ESCORTED_STR ], gMercProfiles[ MARY ].zNickname, ( pSoldier->bAssignment + 1 )) );
1529 }
1530
1531 if ( gubQuest[ QUEST_ESCORT_TOURISTS ] == QUESTNOTSTARTED )
1532 {
1533 StartQuest( QUEST_ESCORT_TOURISTS, gWorldSectorX, gWorldSectorY );
1534 }
1535 break;
1536 }
1537
1538 case MARY:
1539 {
1540 // recruit John as well
1541 RecruitEPC( JOHN );
1542
1543 SOLDIERTYPE* const pSoldier = FindSoldierByProfileIDOnPlayerTeam(JOHN);
1544 if ( pSoldier )
1545 {
1546 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[ NOW_BING_ESCORTED_STR ], gMercProfiles[ JOHN ].zNickname, ( pSoldier->bAssignment + 1 )) );
1547 }
1548
1549 if ( gubQuest[ QUEST_ESCORT_TOURISTS ] == QUESTNOTSTARTED )
1550 {
1551 StartQuest( QUEST_ESCORT_TOURISTS, gWorldSectorX, gWorldSectorY );
1552 }
1553 break;
1554 }
1555 }
1556 }
1557
1558
1559 static void StartDialogueMessageBox(UINT8 ubProfileID, UINT16 usMessageBoxType);
1560 static BOOLEAN NPCOpenThing(SOLDIERTYPE* pSoldier, BOOLEAN fDoor);
1561 static void DoneFadeOutActionBasement(void);
1562 static void DoneFadeOutActionLeaveBasement(void);
1563 static void CarmenLeavesSectorCallback(void);
1564
1565
HandleNPCDoAction(UINT8 ubTargetNPC,UINT16 usActionCode,UINT8 ubQuoteNum)1566 void HandleNPCDoAction( UINT8 ubTargetNPC, UINT16 usActionCode, UINT8 ubQuoteNum )
1567 {
1568 //ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, ST::format("Handling {}, action {} at {}", gMercProfiles[ubTargetNPC].zNickname, usActionCode, GetJA2Clock());
1569
1570 // Switch on action code!
1571 if (usActionCode > NPC_ACTION_TURN_TO_FACE_NEAREST_MERC && usActionCode < NPC_ACTION_LAST_TURN_TO_FACE_PROFILE)
1572 {
1573 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1574 SOLDIERTYPE* const pSoldier2 = FindSoldierByProfileID((UINT8)(usActionCode - NPC_ACTION_TURN_TO_FACE_NEAREST_MERC));
1575 if (pSoldier && pSoldier2)
1576 {
1577 // see if we are facing this person
1578 const UINT8 ubDesiredMercDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, pSoldier2->sGridNo);
1579 // if not already facing in that direction,
1580 if (pSoldier->bDirection != ubDesiredMercDir)
1581 {
1582 EVENT_SetSoldierDesiredDirection( pSoldier, ubDesiredMercDir );
1583 }
1584 }
1585 }
1586 else
1587 {
1588 auto params = GCM->getNpcActionParams(usActionCode);
1589 switch( usActionCode )
1590 {
1591 case NPC_ACTION_DONT_ACCEPT_ITEM:
1592 // do nothing; this is just to skip annoying debug msgs
1593 break;
1594
1595 case NPC_ACTION_GOTO_HIDEOUT:
1596 {
1597 // OK, we want to goto the basement level!
1598
1599 //DEF: First thing, Add the exit grid to the map temps file
1600 EXITGRID ExitGrid;
1601 ExitGrid.ubGotoSectorX = 10;
1602 ExitGrid.ubGotoSectorY = 1;
1603 ExitGrid.ubGotoSectorZ = 1;
1604 ExitGrid.usGridNo = 12722;
1605
1606 { ApplyMapChangesToMapTempFile app;
1607 AddExitGridToWorld( params->getGridNo(7887), &ExitGrid );
1608 }
1609
1610 // For one, loop through our current squad and move them over
1611 //ATE:Alrighty, instead of being a dufuss here, let's actually use the current
1612 // Squad here to search for...
1613
1614 INT8 bNumDone = 0;
1615 FOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
1616 {
1617 // Are we in this sector, On the current squad?
1618 if (pSoldier->bLife >= OKLIFE && pSoldier->bInSector && pSoldier->bAssignment == CurrentSquad())
1619 {
1620 gfTacticalTraversal = TRUE;
1621 SetGroupSectorValue(10, 1, 1, *GetGroup(pSoldier->ubGroupID));
1622
1623 // Set insertion gridno
1624 if ( bNumDone < 6 )
1625 {
1626 // Set next sectore
1627 pSoldier->sSectorX = 10;
1628 pSoldier->sSectorY = 1;
1629 pSoldier->bSectorZ = 1;
1630
1631 // Set gridno
1632 pSoldier->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
1633 pSoldier->usStrategicInsertionData = sBasementEnterGridNos[ bNumDone ];
1634 }
1635 bNumDone++;
1636 }
1637 }
1638
1639 // MOVE NPCS!
1640 ChangeNpcToDifferentSector(GetProfile(FATIMA), 10, 1, 1);
1641 ChangeNpcToDifferentSector(GetProfile(DIMITRI), 10, 1, 1);
1642
1643 gFadeOutDoneCallback = DoneFadeOutActionBasement;
1644
1645 FadeOutGameScreen( );
1646 break;
1647 }
1648
1649 case NPC_ACTION_FATIMA_GIVE_LETTER:
1650 {
1651 // Delete menu, give item to megual
1652 DeleteTalkingMenu( );
1653
1654 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(FATIMA);
1655 SOLDIERTYPE* const pSoldier2 = FindSoldierByProfileID(MIGUEL);
1656
1657 // Give item!
1658 if ( !pSoldier || !pSoldier2 )
1659 {
1660 return;
1661 }
1662
1663 // Look for letter....
1664 {
1665 INT8 bInvPos = FindObj(pSoldier, LETTER);
1666
1667 AssertMsg( bInvPos != NO_SLOT, "Interface Dialogue.C: Gift item does not exist in NPC." );
1668
1669 SoldierGiveItem( pSoldier, pSoldier2, &(pSoldier->inv[ bInvPos ] ), bInvPos );
1670 }
1671 break;
1672 }
1673
1674 case NPC_ACTION_FACE_CLOSEST_PLAYER:
1675 {
1676 // Get pointer for player
1677 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1678 if ( !pSoldier )
1679 {
1680 return;
1681 }
1682
1683 pSoldier->ubQuoteRecord = ubQuoteNum;
1684 pSoldier->ubQuoteActionID = QUOTE_ACTION_ID_TURNTOWARDSPLAYER;
1685
1686 // handle AI for this person every frame until a player merc is near
1687 pSoldier->fAIFlags |= AI_HANDLE_EVERY_FRAME;
1688 break;
1689 }
1690
1691 case NPC_ACTION_OPEN_CLOSEST_DOOR:
1692 {
1693 // Delete menu
1694 DeleteTalkingMenu( );
1695
1696 // Get pointer to NPC
1697 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1698 if ( !pSoldier )
1699 {
1700 return;
1701 }
1702
1703
1704 // FInd, open Closest Door
1705 NPCOpenThing( pSoldier, TRUE );
1706
1707 break;
1708 }
1709
1710 case NPC_ACTION_LOWER_GUN:
1711 {
1712 // Get pointer for player
1713 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1714 if ( !pSoldier )
1715 {
1716 return;
1717 }
1718
1719 if (pSoldier->inv[HANDPOS].usItem != NOTHING)
1720 {
1721 UINT16 usGun;
1722 INT8 bNewSlot;
1723
1724 usGun = pSoldier->inv[HANDPOS].usItem;
1725
1726 ReLoadSoldierAnimationDueToHandItemChange( pSoldier, pSoldier->inv[HANDPOS].usItem, NOTHING );
1727 AutoPlaceObject( pSoldier, &(pSoldier->inv[HANDPOS]), FALSE );
1728
1729 bNewSlot = FindObj( pSoldier, usGun );
1730 if (bNewSlot != NO_SLOT)
1731 {
1732 MERCPROFILESTRUCT& p = GetProfile(ubTargetNPC);
1733 if (p.inv[bNewSlot] == NOTHING)
1734 {
1735 // find where the gun is stored in the profile data and
1736 // move it to the new location
1737 INT8 const bOldSlot = FindObjectInSoldierProfile(p, usGun);
1738 if ( bOldSlot != NO_SLOT )
1739 {
1740 // rearrange profile... NB # of guns can only be 1 so this is easy
1741 p.inv[bOldSlot] = NOTHING;
1742 p.bInvNumber[bOldSlot] = 0;
1743
1744 p.inv[bNewSlot] = usGun;
1745 p.bInvNumber[bNewSlot] = 1;
1746 }
1747 }
1748 }
1749 }
1750 break;
1751 }
1752
1753 case NPC_ACTION_READY_GUN:
1754 {
1755 // Get pointer for player
1756 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1757 if (pSoldier && pSoldier->inv[HANDPOS].usItem != NOTHING)
1758 {
1759 const INT16 sGridNo = pSoldier->sGridNo + DirectionInc(pSoldier->bDirection);
1760 SoldierReadyWeapon(pSoldier, sGridNo, FALSE);
1761 }
1762 break;
1763 }
1764
1765 case NPC_ACTION_START_RUNNING:
1766 {
1767 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1768 if (pSoldier)
1769 {
1770 pSoldier->fUIMovementFast = TRUE;
1771 }
1772 break;
1773 }
1774
1775 case NPC_ACTION_STOP_RUNNING:
1776 {
1777 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1778 if (pSoldier)
1779 {
1780 pSoldier->fUIMovementFast = FALSE;
1781 }
1782 break;
1783 }
1784
1785 case NPC_ACTION_BOOST_TOWN_LOYALTY:
1786 HandleLoyaltyChangeForNPCAction( ubTargetNPC );
1787 break;
1788
1789 // case NPC_ACTION_PENALIZE_TOWN_LOYALTY:
1790 // break;
1791
1792 case NPC_ACTION_STOP_PLAYER_GIVING_FIRST_AID:
1793 {
1794 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1795 if (pSoldier)
1796 {
1797 ReceivingSoldierCancelServices( pSoldier );
1798 }
1799 break;
1800 }
1801
1802 case NPC_ACTION_FACE_NORTH:
1803 {
1804 // handle this separately to keep the code clean...
1805 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1806 if (pSoldier)
1807 {
1808 SendSoldierSetDesiredDirectionEvent( pSoldier, NORTHWEST );
1809 }
1810 break;
1811 }
1812
1813 case NPC_ACTION_FACE_NORTH_EAST:
1814 case NPC_ACTION_FACE_EAST:
1815 case NPC_ACTION_FACE_SOUTH_EAST:
1816 case NPC_ACTION_FACE_SOUTH:
1817 case NPC_ACTION_FACE_SOUTH_WEST:
1818 case NPC_ACTION_FACE_WEST:
1819 case NPC_ACTION_FACE_NORTH_WEST:
1820 {
1821 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1822 if (pSoldier)
1823 {
1824 // screen NORTHEAST corresponds to in-game NORTH
1825 SendSoldierSetDesiredDirectionEvent( pSoldier, (UINT16) (NORTH + (usActionCode - NPC_ACTION_FACE_NORTH_EAST)) );
1826 }
1827 break;
1828 }
1829
1830 case NPC_ACTION_RECRUIT:
1831 // gonna work for free!
1832 gMercProfiles[ ubTargetNPC ].sSalary = 0;
1833 // fallthrough
1834
1835 case NPC_ACTION_RECRUIT_WITH_SALARY:
1836
1837 // Delete menu
1838 DeleteTalkingMenu( );
1839
1840 if ( PlayerTeamFull( ) )
1841 {
1842 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ CANNOT_RECRUIT_TEAM_FULL ] );
1843 }
1844 else
1845 {
1846 RecruitRPC( ubTargetNPC );
1847 // OK, update UI with message that we have been recruited
1848 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[ HAS_BEEN_RECRUITED_STR ], gMercProfiles[ ubTargetNPC ].zNickname) );
1849 }
1850 break;
1851
1852 case NPC_ACTION_BECOME_ENEMY:
1853 {
1854 // CJC: disable because of new system?
1855 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1856 if ( !pSoldier )
1857 {
1858 return;
1859 }
1860
1861 if (pSoldier->ubCivilianGroup)
1862 {
1863 CivilianGroupMemberChangesSides( pSoldier );
1864 }
1865 else
1866 {
1867 //MakeCivHostile( pSoldier, 2 );
1868 }
1869 if (pSoldier->ubProfile != NO_PROFILE && pSoldier->bLife >= OKLIFE)
1870 {
1871 // trigger quote!
1872 //TriggerNPCWithIHateYouQuote( pSoldier->ubProfile );
1873 AddToShouldBecomeHostileOrSayQuoteList(pSoldier);
1874 }
1875 break;
1876 }
1877
1878 case NPC_ACTION_CLOSE_DIALOGUE_PANEL:
1879 DeleteTalkingMenu();
1880 break;
1881
1882 case NPC_ACTION_TRIGGER_FRIEND_WITH_HOSTILE_QUOTE:
1883 // CJC: disabled because of new system
1884 //TriggerFriendWithHostileQuote( ubTargetNPC );
1885 DeleteTalkingMenu();
1886 break;
1887
1888 case NPC_ACTION_LEAVE_HIDEOUT:
1889 {
1890 // Delete menu, give item to megual
1891 DeleteTalkingMenu( );
1892
1893 //if we are already leaving the sector, dont leave the sector again
1894 if( gubWaitingForAllMercsToExitCode != 0 )
1895 return;
1896
1897 // OK, we want to goto the basement level!
1898 // For one, loop through our current squad and move them over
1899 INT8 bNumDone = 0;
1900 FOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
1901 {
1902 // Are we in this sector, On the current squad?
1903 if (pSoldier->bLife >= OKLIFE && pSoldier->bInSector)
1904 {
1905 gfTacticalTraversal = TRUE;
1906 SetGroupSectorValue(10, 1, 0, *GetGroup(pSoldier->ubGroupID));
1907
1908 // Set insertion gridno
1909 if ( bNumDone < 8 )
1910 {
1911 // Set next sectore
1912 pSoldier->sSectorX = 10;
1913 pSoldier->sSectorY = 1;
1914 pSoldier->bSectorZ = 0;
1915
1916 // Set gridno
1917 pSoldier->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
1918 pSoldier->usStrategicInsertionData = sBasementExitGridNos[ bNumDone ];
1919 }
1920 bNumDone++;
1921 }
1922 }
1923
1924 gFadeOutDoneCallback = DoneFadeOutActionLeaveBasement;
1925
1926 FadeOutGameScreen( );
1927
1928 // turn off engaged in conv stuff
1929 gTacticalStatus.uiFlags &= ~ENGAGED_IN_CONV;
1930 // Decrement refrence count...
1931 giNPCReferenceCount = 0;
1932 break;
1933 }
1934
1935 case NPC_ACTION_TRAVERSE_MAP_EAST:
1936 {
1937 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1938 if ( !pSoldier )
1939 {
1940 return;
1941 }
1942
1943 pSoldier->ubQuoteActionID = QUOTE_ACTION_ID_TRAVERSE_EAST;
1944 break;
1945 }
1946
1947 case NPC_ACTION_TRAVERSE_MAP_SOUTH:
1948 {
1949 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1950 if ( !pSoldier )
1951 {
1952 return;
1953 }
1954
1955 pSoldier->ubQuoteActionID = QUOTE_ACTION_ID_TRAVERSE_SOUTH;
1956 break;
1957 }
1958
1959 case NPC_ACTION_TRAVERSE_MAP_WEST:
1960 {
1961 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1962 if ( !pSoldier )
1963 {
1964 return;
1965 }
1966
1967 pSoldier->ubQuoteActionID = QUOTE_ACTION_ID_TRAVERSE_WEST;
1968 break;
1969 }
1970
1971 case NPC_ACTION_TRAVERSE_MAP_NORTH:
1972 {
1973 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
1974 if ( !pSoldier )
1975 {
1976 return;
1977 }
1978
1979 pSoldier->ubQuoteActionID = QUOTE_ACTION_ID_TRAVERSE_NORTH;
1980 break;
1981 }
1982
1983 case NPC_ACTION_REPORT_SHIPMENT_SIZE:
1984 // for now, hard-coded to small shipment...
1985 // medium is quote 14, large is 15
1986 if (CheckFact( FACT_MEDIUM_SIZED_SHIPMENT_WAITING, 0 ))
1987 {
1988 TalkingMenuDialogue( 14 );
1989 }
1990 else if (CheckFact( FACT_LARGE_SIZED_SHIPMENT_WAITING, 0 ))
1991 {
1992 TalkingMenuDialogue( 15 );
1993 }
1994 else
1995 {
1996 TalkingMenuDialogue( 13 );
1997 }
1998 if (CheckFact( FACT_PABLOS_BRIBED, 0 ) && Random( 100 ) < 75)
1999 {
2000 TalkingMenuDialogue( 16 );
2001 if (Random( 100 ) < 75)
2002 {
2003 TalkingMenuDialogue( 21 );
2004 }
2005 }
2006 break;
2007
2008 case NPC_ACTION_RETURN_STOLEN_SHIPMENT_ITEMS:
2009 {
2010 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, 480 + Random( 60 ), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_RETURN_STOLEN_SHIPMENT_ITEMS, 1 );
2011 // also make Pablo neutral again and exit combat if we're in combat
2012 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2013 if ( !pSoldier )
2014 {
2015 return;
2016 }
2017 if ( !pSoldier->bNeutral )
2018 {
2019 DeleteTalkingMenu();
2020 SetSoldierNeutral( pSoldier );
2021 pSoldier->bBleeding = 0; // make sure he doesn't bleed now...
2022 RecalculateOppCntsDueToBecomingNeutral( pSoldier );
2023 if ( gTacticalStatus.uiFlags & INCOMBAT )
2024 {
2025 CheckForEndOfCombatMode( FALSE );
2026 }
2027 }
2028 break;
2029 }
2030
2031 case NPC_ACTION_THREATENINGLY_RAISE_GUN:
2032 {
2033 // Get pointer for NPC
2034 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2035 if ( !pSoldier )
2036 {
2037 return;
2038 }
2039
2040 const INT8 bItemIn = FindAIUsableObjClass(pSoldier, IC_GUN);
2041 if (bItemIn != NO_SLOT && bItemIn != HANDPOS)
2042 {
2043 SwapObjs( &(pSoldier->inv[HANDPOS]), &(pSoldier->inv[bItemIn]) );
2044 const INT16 sGridNo = pSoldier->sGridNo + DirectionInc(pSoldier->bDirection);
2045 SoldierReadyWeapon(pSoldier, sGridNo, FALSE);
2046 }
2047 // and face the nearest merc!
2048 }
2049 // fallthrough
2050
2051 case NPC_ACTION_TURN_TO_FACE_NEAREST_MERC:
2052 {
2053 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2054 if (pSoldier)
2055 {
2056 const INT16 sGridNo = ClosestPC(pSoldier, NULL);
2057 if (sGridNo != NOWHERE)
2058 {
2059 // see if we are facing this person
2060 const UINT8 ubDesiredMercDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sGridNo);
2061 // if not already facing in that direction,
2062 if (pSoldier->bDirection != ubDesiredMercDir)
2063 {
2064 EVENT_SetSoldierDesiredDirection( pSoldier, ubDesiredMercDir );
2065 }
2066 }
2067 }
2068 break;
2069 }
2070
2071 case NPC_ACTION_SEND_PACOS_INTO_HIDING:
2072 {
2073 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(PACOS);
2074 const INT16 sGridNo = params->getGridNo(16028);
2075 if (pSoldier)
2076 {
2077 if (NewOKDestination( pSoldier, sGridNo, TRUE, 0 ) )
2078 {
2079 // go for it!
2080 NPCGotoGridNo(PACOS, sGridNo, ubQuoteNum);
2081 }
2082 else
2083 {
2084 const INT16 sAdjustedGridNo = FindAdjacentGridEx(pSoldier, sGridNo, NULL, NULL, FALSE, FALSE);
2085 if (sAdjustedGridNo != -1)
2086 {
2087 NPCGotoGridNo(PACOS, sAdjustedGridNo, ubQuoteNum);
2088 }
2089 }
2090 }
2091 break;
2092 }
2093
2094 case NPC_ACTION_HAVE_PACOS_FOLLOW:
2095 {
2096 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(PACOS);
2097 const INT16 sGridNo = params->getGridNo(18193);
2098 if (pSoldier)
2099 {
2100 if (NewOKDestination( pSoldier, sGridNo, TRUE, 0 ) )
2101 {
2102 // go for it!
2103 NPCGotoGridNo(PACOS, sGridNo, ubQuoteNum);
2104 }
2105 else
2106 {
2107 const INT16 sAdjustedGridNo = FindAdjacentGridEx(pSoldier, sGridNo, NULL, NULL, FALSE, FALSE);
2108 if (sAdjustedGridNo != -1)
2109 {
2110 NPCGotoGridNo(PACOS, sAdjustedGridNo, ubQuoteNum);
2111 }
2112 }
2113 }
2114 break;
2115 }
2116
2117 case NPC_ACTION_SET_DELAYED_PACKAGE_TIMER:
2118 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), FACT_SHIPMENT_DELAYED_24_HOURS, 1 );
2119 break;
2120
2121 case NPC_ACTION_SET_RANDOM_PACKAGE_DAMAGE_TIMER:
2122 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_SET_RANDOM_PACKAGE_DAMAGE_TIMER, 1 );
2123 break;
2124
2125 case NPC_ACTION_ENABLE_CAMBRIA_DOCTOR_BONUS:
2126 {
2127 // add event in a
2128 const INT32 iRandom = Random(24);
2129 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, ( GetWorldMinutesInDay() + ( ( 24 + iRandom ) * 60 ) ), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_ENABLE_CAMBRIA_DOCTOR_BONUS, 1 );
2130 break;
2131 }
2132
2133 case NPC_ACTION_TRIGGER_END_OF_FOOD_QUEST:
2134 AddHistoryToPlayersLog( HISTORY_TALKED_TO_FATHER_WALKER, 0, GetWorldTotalMin(), gWorldSectorX, gWorldSectorY );
2135 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_TRIGGER_END_OF_FOOD_QUEST, 1 );
2136 break;
2137
2138 case NPC_ACTION_REMOVE_CONRAD:
2139 break;
2140
2141 case NPC_ACTION_REDUCE_CONRAD_SALARY_CONDITIONS:
2142 gMercProfiles[ CONRAD ].sSalary = 3300;
2143 // fallthrough
2144 case NPC_ACTION_ASK_ABOUT_ESCORTING_EPC:
2145 // Confirm if we want to start escorting or not....
2146 case NPC_ACTION_ASK_ABOUT_PAYING_RPC:
2147 case NPC_ACTION_ASK_ABOUT_PAYING_RPC_WITH_DAILY_SALARY:
2148 // Ask whether the player will pay the RPC
2149 case NPC_ACTION_DARREN_REQUESTOR:
2150 // Darren asks if the player wants to fight
2151 case NPC_ACTION_FIGHT_AGAIN_REQUESTOR:
2152 // Darren asks if the player wants to fight again
2153 case NPC_ACTION_BUY_LEATHER_KEVLAR_VEST: // from Angel
2154 case NPC_ACTION_MEDICAL_REQUESTOR: // at hospital
2155 case NPC_ACTION_MEDICAL_REQUESTOR_2: // at hospital
2156 case NPC_ACTION_BUY_VEHICLE_REQUESTOR: // from Dave
2157 case NPC_ACTION_KROTT_REQUESTOR:
2158 // Vince or Willis asks about payment? for medical attention
2159 if (ubTargetNPC != gpDestSoldier->ubProfile)
2160 {
2161 SLOGE("Inconsistency between HandleNPCDoAction and target profile IDs" );
2162 }
2163 else
2164 {
2165 DeleteTalkingMenu();
2166 StartDialogueMessageBox( ubTargetNPC, usActionCode );
2167 }
2168 break;
2169
2170 case NPC_ACTION_REPORT_BALANCE:
2171 ScreenMsg( FONT_YELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[ BALANCE_OWED_STR ], gMercProfiles[ubTargetNPC].zNickname, -gMercProfiles[ubTargetNPC].iBalance) );
2172 break;
2173
2174 case NPC_ACTION_DELAYED_MAKE_BRENDA_LEAVE:
2175 // set event to invoke Brenda's (#85) record 9 a minute from
2176 // now
2177 DeleteTalkingMenu();
2178 AddSameDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay() + 360, NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_DELAYED_MAKE_BRENDA_LEAVE );
2179 break;
2180
2181 case NPC_ACTION_SEX:
2182 // Delete menu
2183 //DeleteTalkingMenu( );
2184 //gFadeOutDoneCallback = DoneFadeOutActionSex;
2185 //FadeOutGameScreen( );
2186 SetPendingNewScreen( SEX_SCREEN );
2187 break;
2188
2189 case NPC_ACTION_KYLE_GETS_MONEY:
2190 {
2191 // add a money item with $10000 to the tile in front of Kyle
2192 // and then have him pick it up
2193 {
2194 OBJECTTYPE Object;
2195 INT16 sGridNo = params->getGridNo(14952);
2196 UINT32 uiAmount = params->getAmount(10000);
2197 STLOGI("add a money item with ${} to tile {} in front of Kyle", uiAmount, sGridNo);
2198
2199 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2200 if (pSoldier)
2201 {
2202 CreateMoney(uiAmount, &Object);
2203 INT32 const iWorldItem = AddItemToPool(sGridNo, &Object, INVISIBLE, pSoldier->bLevel, 0, 0);
2204
2205 // shouldn't have any current action but make sure everything
2206 // is clear... and set pending action so the guy won't move
2207 // until the pickup is completed
2208 CancelAIAction(pSoldier);
2209 pSoldier->bAction = AI_ACTION_PENDING_ACTION;
2210 pSoldier->ubQuoteRecord = NPC_ACTION_KYLE_GETS_MONEY;
2211
2212 SoldierPickupItem( pSoldier, iWorldItem, sGridNo, ITEM_IGNORE_Z_LEVEL );
2213 }
2214 }
2215 break;
2216 }
2217
2218 case NPC_ACTION_DRINK_DRINK_DRINK:
2219 gMercProfiles[ ubTargetNPC ].bNPCData++;
2220 break;
2221
2222 case NPC_ACTION_MARTHA_DIES:
2223 {
2224 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(MARTHA);
2225 if (pSoldier)
2226 {
2227 DeleteTalkingMenu();
2228 EVENT_SoldierGotHit(pSoldier, 1, 100, 10, pSoldier->bDirection, 320, NULL, FIRE_WEAPON_NO_SPECIAL, AIM_SHOT_TORSO, NOWHERE);
2229 }
2230 break;
2231 }
2232
2233 //case NPC_ACTION_DARREN_GIVEN_CASH:
2234 // Handled in NPC.c
2235 // break;
2236
2237 case NPC_ACTION_START_BOXING_MATCH:
2238 if ( CheckOnBoxers() )
2239 {
2240 DeleteTalkingMenu();
2241 PlayLocationJA2Sample(11237, BOXING_BELL, MIDVOLUME, 5);
2242 ClearAllBoxerFlags();
2243 SetBoxingState( BOXING_WAITING_FOR_PLAYER );
2244 }
2245 break;
2246
2247 case NPC_ACTION_ADD_JOEY_TO_WORLD:
2248 gMercProfiles[ JOEY ].sSectorX = 4;
2249 gMercProfiles[ JOEY ].sSectorY = MAP_ROW_D;
2250 gMercProfiles[ JOEY ].bSectorZ = 1;
2251 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_ADD_JOEY_TO_WORLD, 3 );
2252 break;
2253
2254 case NPC_ACTION_MARK_KINGPIN_QUOTE_0_USED:
2255 // set Kingpin's quote 0 as used so he doesn't introduce himself
2256 GetProfile(KINGPIN).ubLastDateSpokenTo = GetWorldDay();
2257 break;
2258
2259 case NPC_ACTION_TRIGGER_LAYLA_13_14_OR_15:
2260 if (CheckFact( FACT_CARLA_AVAILABLE, 0 ))
2261 {
2262 TriggerNPCRecord(MADAME, 13);
2263 }
2264 else if (CheckFact( FACT_CINDY_AVAILABLE, 0))
2265 {
2266 TriggerNPCRecord(MADAME, 14);
2267 }
2268 else if (CheckFact( FACT_BAMBI_AVAILABLE, 0))
2269 {
2270 TriggerNPCRecord(MADAME, 15);
2271 }
2272 break;
2273
2274 {
2275 case NPC_ACTION_OPEN_CARLAS_DOOR:
2276 case NPC_ACTION_OPEN_CINDYS_DOOR:
2277 case NPC_ACTION_OPEN_BAMBIS_DOOR:
2278 case NPC_ACTION_OPEN_MARIAS_DOOR:
2279 INT16 sGridNo = params->getGridNo(10852);
2280
2281 // JA3Gold: unlock the doors instead of opening them
2282 {
2283 DOOR* pDoor;
2284
2285 pDoor = FindDoorInfoAtGridNo( sGridNo );
2286 if (pDoor)
2287 {
2288 pDoor->fLocked = FALSE;
2289 }
2290 }
2291 /*
2292 // handle door opening here
2293 {
2294 STRUCTURE * pStructure;
2295 pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
2296 if (pStructure)
2297 {
2298
2299
2300
2301 pStructure->
2302
2303 if (pStructure->fFlags & STRUCTURE_OPEN)
2304 {
2305 // it's already open - this MIGHT be an error but probably not
2306 // because we are basically just ensuring that the door is open
2307 }
2308 else
2309 {
2310 if (pStructure->fFlags & STRUCTURE_BASE_TILE)
2311 {
2312 HandleDoorChangeFromGridNo( NULL, sGridNo, FALSE );
2313 }
2314 else
2315 {
2316 HandleDoorChangeFromGridNo( NULL, pStructure->sBaseGridNo, FALSE );
2317 }
2318 }
2319 }
2320 }*/
2321 break;
2322 }
2323
2324 case NPC_ACTION_POSSIBLY_ADVERTISE_CINDY:
2325 if ( CheckFact( FACT_CINDY_AVAILABLE, 0 ) )
2326 {
2327 TriggerNPCRecord( MADAME, 14 );
2328 }
2329 // else fall through
2330 case NPC_ACTION_POSSIBLY_ADVERTISE_BAMBI:
2331 if ( CheckFact( FACT_BAMBI_AVAILABLE, 0 ) )
2332 {
2333 TriggerNPCRecord( MADAME, 15 );
2334 }
2335 break;
2336
2337 case NPC_ACTION_LAYLA_GIVEN_WRONG_AMOUNT_OF_CASH:
2338 if ( CheckFact( FACT_NO_GIRLS_AVAILABLE, 0 ) )
2339 {
2340 TriggerNPCRecord( MADAME, 27 );
2341 }
2342 // else mention girls available...
2343 else if ( CheckFact( FACT_CARLA_AVAILABLE, 0 ) )
2344 {
2345 // Mention Carla
2346 TriggerNPCRecord( MADAME, 28 );
2347 break;
2348 }
2349 // else
2350 // fallthrough
2351 case NPC_ACTION_LAYLAS_NEXT_LINE_AFTER_CARLA:
2352 if ( CheckFact( FACT_CINDY_AVAILABLE, 0 ) )
2353 {
2354 // Mention Cindy
2355 TriggerNPCRecord( MADAME, 29 );
2356 break;
2357 }
2358 // else
2359 // fallthrough
2360 case NPC_ACTION_LAYLAS_NEXT_LINE_AFTER_CINDY:
2361 if ( CheckFact( FACT_BAMBI_AVAILABLE, 0 ) )
2362 {
2363 // Mention Bambi
2364 TriggerNPCRecord( MADAME, 30 );
2365 break;
2366 }
2367 // else
2368 // fallthrough
2369 case NPC_ACTION_LAYLAS_NEXT_LINE_AFTER_BAMBI:
2370 if ( gubQuest[ QUEST_RESCUE_MARIA ] == QUESTINPROGRESS )
2371 {
2372 // Mention Maria
2373 TriggerNPCRecord( MADAME, 31);
2374 break;
2375 }
2376 // else
2377 // fallthrough
2378 case NPC_ACTION_LAYLAS_NEXT_LINE_AFTER_MARIA:
2379 if ( CheckFact( FACT_MULTIPLE_MERCS_CLOSE, MADAME ) )
2380 {
2381 // if more than 1 merc nearby, say comment about 2 guys pergirl
2382 TriggerNPCRecord( MADAME, 32 );
2383 }
2384
2385 break;
2386
2387 case NPC_ACTION_PROMPT_PLAYER_TO_LIE:
2388 //
2389 ubRecordThatTriggeredLiePrompt = ubQuoteNum;
2390 DeleteTalkingMenu();
2391
2392 StartDialogueMessageBox( ubTargetNPC, usActionCode );
2393 break;
2394
2395 case NPC_ACTION_REMOVE_JOE_QUEEN:
2396 {
2397 // Find queen and joe and remove from sector...
2398 SOLDIERTYPE* const queen = FindSoldierByProfileID(QUEEN);
2399 if (queen) TacticalRemoveSoldier(*queen);
2400
2401 SOLDIERTYPE* const joe = FindSoldierByProfileID(JOE);
2402 if (joe) TacticalRemoveSoldier(*joe);
2403 break;
2404 }
2405
2406 case NPC_ACTION_REMOVE_ELLIOT_END_MEANWHILE:
2407 {
2408 SOLDIERTYPE* const elliot = FindSoldierByProfileID(ELLIOT);
2409 if (elliot) TacticalRemoveSoldier(*elliot);
2410
2411 // End meanwhile....
2412 // End meanwhile....
2413 DeleteTalkingMenu();
2414 EndMeanwhile( );
2415 break;
2416 }
2417
2418 case NPC_ACTION_NO_SCI_FI_END_MEANWHILE:
2419
2420 if ( !( gGameOptions.fSciFi ) )
2421 {
2422 // End meanwhile....
2423 // End meanwhile....
2424 DeleteTalkingMenu();
2425 EndMeanwhile( );
2426 }
2427 else
2428 {
2429 TriggerNPCRecord( QUEEN, 8 );
2430 }
2431 break;
2432 case NPC_ACTION_TRIGGER_MARRY_DARYL_PROMPT:
2433 DeleteTalkingMenu();
2434 StartDialogueMessageBox( ubTargetNPC, usActionCode );
2435 break;
2436
2437 case NPC_ACTION_HAVE_MARRIED_NPC_LEAVE_TEAM:
2438 {
2439 // get the soldier
2440 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(gMercProfiles[DARYL].bNPCData);
2441 SOLDIERTYPE* const pSoldier2 = gpDestSoldier;
2442
2443 if ( !pSoldier || !pSoldier2 )
2444 {
2445 return;
2446 }
2447
2448 //set the fact that the merc is being married ( used in the personnel screen )
2449 gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags2 |= PROFILE_MISC_FLAG2_MARRIED_TO_HICKS;
2450
2451 AddHistoryToPlayersLog( HISTORY_MERC_MARRIED_OFF, pSoldier->ubProfile, GetWorldTotalMin(), gWorldSectorX, gWorldSectorY );
2452
2453 // if Flo is going off with Daryl, then set that fact true
2454 if (pSoldier->ubProfile == FLO)
2455 {
2456 SetFactTrue( FACT_PC_MARRYING_DARYL_IS_FLO );
2457 }
2458
2459 HandleMoraleEvent( pSoldier, MORALE_MERC_MARRIED, gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
2460
2461 UpdateDarrelScriptToGoTo( pSoldier );
2462 TriggerNPCRecord( DARREL, 10 );
2463
2464 //Since the married merc is leaving the team, add them to the departed list for the personnel screen
2465 AddCharacterToOtherList( pSoldier );
2466 break;
2467 }
2468
2469 case NPC_ACTION_TRIGGER_ANGEL_17_OR_18:
2470 if (CheckFact( FACT_ANGEL_SOLD_VEST, ANGEL ))
2471 {
2472 TriggerNPCRecord( ANGEL, 18 );
2473 }
2474 else
2475 {
2476 TriggerNPCRecord( ANGEL, 17 );
2477 }
2478 break;
2479
2480 case NPC_ACTION_TRIGGER_ANGEL_16_OR_19:
2481 if (CheckFact( FACT_PLAYER_IN_SAME_ROOM, ANGEL ) )
2482 {
2483 TriggerNPCRecord( ANGEL, 16 );
2484 }
2485 else
2486 {
2487 TriggerNPCRecord( ANGEL, 19 );
2488 }
2489 break;
2490
2491 case NPC_ACTION_TRIGGER_ANGEL_21_OR_22:
2492 if (CheckFact( FACT_PLAYER_IN_SAME_ROOM, ANGEL ) )
2493 {
2494 TriggerNPCRecord( ANGEL, 22 );
2495 }
2496 else
2497 {
2498 TriggerNPCRecord( ANGEL, 21 );
2499 }
2500 break;
2501
2502 case NPC_ACTION_TRIGGER_MARIA:
2503 if (!CheckFact(FACT_MARIA_ESCAPE_NOTICED, MARIA))
2504 {
2505 TriggerNPCRecord( MARIA, 8 );
2506 }
2507 else
2508 {
2509 if ( CheckFact( FACT_PLAYER_IN_SAME_ROOM, MARIA ) )
2510 {
2511 TriggerNPCRecord( MARIA, 9 );
2512 }
2513 else
2514 {
2515 TriggerNPCRecord( MARIA, 10 );
2516 }
2517 }
2518 break;
2519
2520 case NPC_ACTION_ANGEL_LEAVES_DEED:
2521 {
2522 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2523 if (pSoldier)
2524 {
2525 const INT8 bItemIn = FindObj(pSoldier, DEED);
2526 if (bItemIn != NO_SLOT)
2527 {
2528 AddItemToPool(params->getGridNo(12541), &pSoldier->inv[bItemIn], INVISIBLE, 0, 0, 0);
2529 DeleteObj( &(pSoldier->inv[ bItemIn ]) );
2530 RemoveObjectFromSoldierProfile( ubTargetNPC, DEED );
2531 }
2532 }
2533 DeleteTalkingMenu();
2534 NPCDoAction( ubTargetNPC, NPC_ACTION_UN_RECRUIT_EPC, 0 );
2535 NPCDoAction( ubTargetNPC, NPC_ACTION_GRANT_EXPERIENCE_3, 0 );
2536 TriggerNPCRecord( ANGEL, 24 );
2537 break;
2538 }
2539
2540 case NPC_ACTION_SET_GIRLS_AVAILABLE:
2541
2542 // Initially assume none available
2543 SetFactTrue( FACT_NO_GIRLS_AVAILABLE );
2544
2545 // Carla... available 66% of the time
2546 if ( Random( 3 ) )
2547 {
2548 SetFactTrue( FACT_CARLA_AVAILABLE );
2549 SetFactFalse( FACT_NO_GIRLS_AVAILABLE );
2550 }
2551 else
2552 {
2553 SetFactFalse( FACT_CARLA_AVAILABLE );
2554 }
2555 // Cindy... available 50% of time
2556 if ( Random( 2 ) )
2557 {
2558 SetFactTrue( FACT_CINDY_AVAILABLE );
2559 SetFactFalse( FACT_NO_GIRLS_AVAILABLE );
2560 }
2561 else
2562 {
2563 SetFactFalse( FACT_CINDY_AVAILABLE );
2564 }
2565 // Bambi... available 50% of time
2566 if ( Random( 2 ) )
2567 {
2568 SetFactTrue( FACT_BAMBI_AVAILABLE );
2569 SetFactFalse( FACT_NO_GIRLS_AVAILABLE );
2570 }
2571 else
2572 {
2573 SetFactFalse( FACT_BAMBI_AVAILABLE );
2574 }
2575 break;
2576
2577 case NPC_ACTION_SET_DELAY_TILL_GIRLS_AVAILABLE:
2578 AddSameDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay() + 1 + Random( 2 ), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_SET_DELAY_TILL_GIRLS_AVAILABLE );
2579 break;
2580
2581 case NPC_ACTION_SET_WAITED_FOR_GIRL_FALSE:
2582 SetFactFalse( FACT_PLAYER_WAITED_FOR_GIRL );
2583 break;
2584
2585 case NPC_ACTION_UN_RECRUIT_EPC:
2586 case NPC_ACTION_SET_EPC_TO_NPC:
2587 if ( ubTargetNPC == ANGEL )
2588 {
2589 // hack to Mari!
2590 ubTargetNPC = MARIA;
2591 }
2592 UnRecruitEPC( ubTargetNPC );
2593 switch( ubTargetNPC )
2594 {
2595 case MARY:
2596 UnRecruitEPC( JOHN );
2597 break;
2598 case JOHN:
2599 UnRecruitEPC( MARY );
2600 break;
2601 }
2602 break;
2603
2604 case NPC_ACTION_REMOVE_DOREEN:
2605 // make Doreen disappear next time we do a sector traversal
2606 gMercProfiles[ DOREEN ].sSectorX = 0;
2607 gMercProfiles[ DOREEN ].sSectorY = 0;
2608 gMercProfiles[ DOREEN ].bSectorZ = 0;
2609 break;
2610
2611 case NPC_ACTION_FREE_KIDS:
2612 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, 420, NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_FREE_KIDS, 1 );
2613 break;
2614
2615 case NPC_ACTION_TRIGGER_FATHER_18_20_OR_15:
2616 if (!CheckFact(FACT_132, FATHER))
2617 {
2618 TriggerNPCRecord( FATHER, 18 );
2619 }
2620 else if (!CheckFact(FACT_133, FATHER))
2621 {
2622 TriggerNPCRecord( FATHER, 20 );
2623 }
2624 else if (!CheckFact(FACT_134, FATHER))
2625 {
2626 TriggerNPCRecord( FATHER, 15 );
2627 }
2628 else
2629 {
2630 TriggerNPCRecord( FATHER, 26 );
2631 }
2632 break;
2633
2634 case NPC_ACTION_ENTER_COMBAT:
2635 {
2636 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2637 if (pSoldier )
2638 {
2639 if ( pSoldier->ubCivilianGroup != NON_CIV_GROUP )
2640 {
2641 if ( gTacticalStatus.fCivGroupHostile[ pSoldier->ubCivilianGroup ] == CIV_GROUP_NEUTRAL )
2642 {
2643 CivilianGroupMemberChangesSides( pSoldier );
2644 }
2645 }
2646 else
2647 {
2648 // make hostile
2649 MakeCivHostile( pSoldier, 2 );
2650 }
2651 DeleteTalkingMenu();
2652 if ( ! ( gTacticalStatus.uiFlags & INCOMBAT ) )
2653 {
2654 EnterCombatMode( pSoldier->bTeam );
2655 }
2656 }
2657 break;
2658 }
2659
2660 case NPC_ACTION_DECIDE_ACTIVE_TERRORISTS:
2661 // only (now) add all terrorist files to laptop
2662 AddFilesAboutTerrorists();
2663
2664 // Carmen has received 0 terrorist heads recently
2665 GetProfile(CARMEN).bNPCData2 = 0;
2666 break;
2667
2668 case NPC_ACTION_CHECK_LAST_TERRORIST_HEAD:
2669 {
2670 MERCPROFILESTRUCT& carmen = GetProfile(CARMEN);
2671 --carmen.bNPCData; // decrement head count
2672 ++carmen.bNPCData2; // increment number of heads on hand
2673
2674 if (gWorldSectorX == 13 && gWorldSectorY == MAP_ROW_C && gbWorldSectorZ == 0)
2675 {
2676 TriggerNPCRecord(CARMEN, 20);
2677 }
2678 else
2679 {
2680 TriggerNPCRecord(CARMEN, 21);
2681 }
2682 // CJC Nov 28 2002 - fixed history record which didn't have location specified
2683 AddHistoryToPlayersLog( HISTORY_GAVE_CARMEN_HEAD, 0, GetWorldTotalMin(), gWorldSectorX, gWorldSectorY );
2684 break;
2685 }
2686
2687 case NPC_ACTION_CARMEN_LEAVES_FOR_GOOD:
2688 gMercProfiles[ ubTargetNPC ].ubMiscFlags2 |= PROFILE_MISC_FLAG2_LEFT_COUNTRY;
2689 // fall through!
2690 case NPC_ACTION_CARMEN_LEAVES_FOR_C13:
2691 // set "don't add to sector" cause he'll only appear after an event...
2692 gMercProfiles[ ubTargetNPC ].ubMiscFlags2 |= PROFILE_MISC_FLAG2_DONT_ADD_TO_SECTOR;
2693
2694 SetCustomizableTimerCallbackAndDelay(params->getAmount(10000), CarmenLeavesSectorCallback, TRUE );
2695 break;
2696
2697 case NPC_ACTION_CARMEN_LEAVES_ON_NEXT_SECTOR_LOAD:
2698 if (gMercProfiles[ CARMEN ].bNPCData == 0)
2699 {
2700 SetFactTrue( FACT_ALL_TERRORISTS_KILLED );
2701 // the next time we change sectors, quest 2 will be set to done
2702
2703 IncrementTownLoyaltyEverywhere( LOYALTY_BONUS_TERRORISTS_DEALT_WITH );
2704 }
2705 // anyhow Carmen has given the player money so reset his balance to 0.
2706 gMercProfiles[ CARMEN ].uiMoney = 0;
2707 break;
2708
2709 case NPC_ACTION_END_COMBAT:
2710 ExitCombatMode();
2711 break;
2712
2713 case NPC_ACTION_BECOME_FRIENDLY_END_COMBAT:
2714 {
2715 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2716 if (pSoldier )
2717 {
2718 DeleteTalkingMenu();
2719
2720 SetSoldierNeutral( pSoldier );
2721 RecalculateOppCntsDueToBecomingNeutral( pSoldier );
2722 if ( gTacticalStatus.bNumFoughtInBattle[ CIV_TEAM ] == 0 )
2723 {
2724 gTacticalStatus.fEnemyInSector = FALSE;
2725 }
2726 if ( !gTacticalStatus.fEnemyInSector )
2727 {
2728 ExitCombatMode();
2729 }
2730
2731 CancelAIAction(pSoldier);
2732 // make stand up if not standing already
2733 if ( ubTargetNPC == SLAY && pSoldier->ubBodyType == CRIPPLECIV )
2734 {
2735 HandleNPCDoAction( SLAY, NPC_ACTION_GET_OUT_OF_WHEELCHAIR, ubQuoteNum );
2736 }
2737 else if (!PTR_STANDING)
2738 {
2739 pSoldier->bNextAction = AI_ACTION_CHANGE_STANCE;
2740 pSoldier->usNextActionData = ANIM_STAND;
2741 }
2742 }
2743 break;
2744 }
2745
2746 case NPC_ACTION_TERRORIST_REVEALS_SELF:
2747 // WAS swap names in profile
2748 // NOW overwrite name with true name in profile
2749 // copy new nickname into soldier structure
2750 {
2751 gMercProfiles[ubTargetNPC].zNickname = gMercProfiles[ubTargetNPC].zName;
2752 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2753 if ( pSoldier )
2754 {
2755 pSoldier->name = gMercProfiles[ubTargetNPC].zNickname;
2756 }
2757 }
2758 break;
2759
2760 case NPC_ACTION_END_MEANWHILE:
2761
2762 // End meanwhile....
2763 DeleteTalkingMenu();
2764 EndMeanwhile( );
2765 break;
2766
2767 case NPC_ACTION_START_BLOODCAT_QUEST:
2768 StartQuest( QUEST_BLOODCATS, gWorldSectorX, gWorldSectorY );
2769 break;
2770
2771 case NPC_ACTION_START_MINE:
2772 {
2773 const UINT8 ubMineIndex = GetHeadMinersMineIndex(ubTargetNPC);
2774
2775 // record the fact that player has spoken with this head miner so he can now get production from that mine
2776 PlayerSpokeToHeadMiner( ubTargetNPC );
2777
2778 if ( IsMineShutDown( ubMineIndex ) )
2779 {
2780 RestartMineProduction( ubMineIndex );
2781 }
2782 break;
2783 }
2784
2785 case NPC_ACTION_STOP_MINE:
2786 {
2787 const UINT8 ubMineIndex = GetHeadMinersMineIndex(ubTargetNPC);
2788
2789 if ( !IsMineShutDown( ubMineIndex ) )
2790 {
2791 ShutOffMineProduction( ubMineIndex );
2792 }
2793 break;
2794 }
2795
2796 case NPC_ACTION_RESET_MINE_CAPTURED:
2797 ResetQueenRetookMine( ubTargetNPC );
2798 break;
2799
2800 case NPC_ACTION_SET_OSWALD_RECORD_13_USED:
2801 case NPC_ACTION_SET_CALVIN_RECORD_13_USED:
2802 case NPC_ACTION_SET_CARL_RECORD_13_USED:
2803 case NPC_ACTION_SET_FRED_RECORD_13_USED:
2804 case NPC_ACTION_SET_MATT_RECORD_13_USED:
2805 SetQuoteRecordAsUsed( ubTargetNPC, 13 );
2806 break;
2807
2808 case NPC_ACTION_TRIGGER_MATT:
2809 if ( CheckFact( FACT_DYNAMO_IN_J9, MATT ) )
2810 {
2811 TriggerNPCRecord( MATT, 15 );
2812 }
2813 else if ( CheckFact( FACT_DYNAMO_ALIVE, MATT ) )
2814 {
2815 TriggerNPCRecord( MATT, 16 );
2816 }
2817 break;
2818
2819 case NPC_ACTION_MADLAB_GIVEN_GUN:
2820 SetFactFalse( FACT_MADLAB_EXPECTING_FIREARM );
2821 if ( CheckFact( FACT_MADLAB_EXPECTING_VIDEO_CAMERA, 0 ) )
2822 {
2823 TriggerNPCRecord( ubTargetNPC, 14 );
2824 }
2825 else
2826 {
2827 TriggerNPCRecord( ubTargetNPC, 18 );
2828 }
2829 break;
2830
2831 case NPC_ACTION_MADLAB_GIVEN_CAMERA:
2832 SetFactFalse( FACT_MADLAB_EXPECTING_VIDEO_CAMERA );
2833 if ( CheckFact( FACT_MADLAB_EXPECTING_FIREARM, 0 ) )
2834 {
2835 TriggerNPCRecord( ubTargetNPC, 17 );
2836 }
2837 else
2838 {
2839 TriggerNPCRecord( ubTargetNPC, 18 );
2840 }
2841 break;
2842
2843 case NPC_ACTION_MADLAB_ATTACHES_GOOD_CAMERA:
2844 {
2845 SetFactFalse( FACT_MADLAB_HAS_GOOD_CAMERA );
2846 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(MADLAB);
2847 SOLDIERTYPE* const pSoldier2 = FindSoldierByProfileID(ROBOT);
2848 if ( pSoldier && pSoldier2 )
2849 {
2850 // Give weapon to robot
2851 INT8 bSlot;
2852
2853 bSlot = FindObjClass( pSoldier, IC_GUN );
2854 if ( bSlot != NO_SLOT )
2855 {
2856 AutoPlaceObject( pSoldier2, &( pSoldier->inv[ bSlot ] ) , FALSE );
2857 }
2858 }
2859 // Allow robot to be controlled by remote!
2860 RecruitEPC( ROBOT );
2861 break;
2862 }
2863
2864 case NPC_ACTION_READY_ROBOT:
2865 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, 420, NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_READY_ROBOT, 1 );
2866 break;
2867
2868 case NPC_ACTION_HANDLE_END_OF_FIGHT:
2869 ExitBoxing();
2870 DeleteTalkingMenu();
2871 /*
2872 switch( gTacticalStatus.bBoxingState )
2873 {
2874 case WON_ROUND:
2875 TriggerNPCRecord( DARREN, 23 );
2876 break;
2877 case LOST_ROUND:
2878 TriggerNPCRecord( DARREN, 24 );
2879 break;
2880 default:
2881 break;
2882 }*/
2883 break;
2884
2885 case NPC_ACTION_DARREN_PAYS_PLAYER:
2886 {
2887 // should change to split up cash
2888 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2889 if ( pSoldier )
2890 {
2891 INT16 sNearestPC;
2892 INT8 bMoneySlot;
2893 INT8 bEmptySlot;
2894
2895 sNearestPC = ClosestPC( pSoldier, NULL );
2896 SOLDIERTYPE* const pSoldier2 = (sNearestPC != NOWHERE ? WhoIsThere2(sNearestPC, 0) : NULL);
2897
2898 if (pSoldier2)
2899 {
2900 bMoneySlot = FindObjClass( pSoldier, IC_MONEY );
2901 bEmptySlot = FindObj( pSoldier, NOTHING );
2902
2903 // have to separate out money from Darren's stash equal to the amount
2904 // of the bet times 2 (returning the player's money, after all!)
2905 if (bMoneySlot != NO_SLOT && bEmptySlot != NO_SLOT)
2906 {
2907 CreateMoney( gMercProfiles[ ubTargetNPC ].iBalance * 2, &(pSoldier->inv[ bEmptySlot ] ) );
2908 pSoldier->inv[ bMoneySlot ].uiMoneyAmount -= gMercProfiles[ ubTargetNPC ].iBalance * 2;
2909 if (bMoneySlot < bEmptySlot)
2910 {
2911 // move main stash to later in inventory!
2912 SwapObjs( &(pSoldier->inv[ bEmptySlot ] ), &(pSoldier->inv[ bMoneySlot ] ) );
2913 SoldierGiveItem( pSoldier, pSoldier2, &(pSoldier->inv[ bMoneySlot ] ), bMoneySlot );
2914 }
2915 else
2916 {
2917 SoldierGiveItem( pSoldier, pSoldier2, &(pSoldier->inv[ bEmptySlot ] ), bEmptySlot );
2918 }
2919 }
2920 }
2921 }
2922 break;
2923 }
2924
2925 case NPC_ACTION_TRIGGER_SPIKE_OR_DARREN:
2926 {
2927 const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(KINGPIN);
2928 if (pSoldier)
2929 {
2930 UINT8 const room = GetRoom(pSoldier->sGridNo);
2931 if (1 <= room && room <= 3)
2932 { // Kingpin is in the club
2933 TriggerNPCRecord( DARREN, 31 );
2934 break;
2935 }
2936 }
2937 // Kingpin not in club
2938 TriggerNPCRecord( SPIKE, 11 );
2939 break;
2940 }
2941
2942 case NPC_ACTION_OPEN_CLOSEST_CABINET:
2943 {
2944 // Delete menu
2945 DeleteTalkingMenu( );
2946
2947 // Get pointer to NPC
2948 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2949 if ( !pSoldier )
2950 {
2951 return;
2952 }
2953
2954 // FInd, open Closest non-door
2955 NPCOpenThing( pSoldier, FALSE );
2956 break;
2957 }
2958
2959 case NPC_ACTION_GET_ITEMS_FROM_CLOSEST_CABINET:
2960 {
2961 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
2962 if (pSoldier)
2963 {
2964 // shouldn't have any current action but make sure everything
2965 // is clear... and set pending action so the guy won't move
2966 // until the pickup is completed
2967 CancelAIAction(pSoldier);
2968 pSoldier->bAction = AI_ACTION_PENDING_ACTION;
2969
2970 // make sure the pickup starts dammit!
2971 pSoldier->fInNonintAnim = FALSE;
2972 pSoldier->fRTInNonintAnim = FALSE;
2973
2974 INT16 sGridNo;
2975 if ( pSoldier->ubProfile == ARMAND )
2976 {
2977 sGridNo = 6968;
2978 }
2979 else
2980 {
2981 sGridNo = FindNearestOpenableNonDoor( pSoldier->sGridNo );
2982 }
2983
2984 SoldierPickupItem( pSoldier, ITEM_PICKUP_ACTION_ALL, sGridNo, ITEM_IGNORE_Z_LEVEL );
2985 }
2986 break;
2987 }
2988
2989 case NPC_ACTION_SHOW_TIXA:
2990 SetTownAsFound(TIXA);
2991 AddHistoryToPlayersLog( HISTORY_DISCOVERED_TIXA, 0, GetWorldTotalMin(), gWorldSectorX, gWorldSectorY );
2992 break;
2993 case NPC_ACTION_SHOW_ORTA:
2994 SetTownAsFound(ORTA);
2995 AddHistoryToPlayersLog( HISTORY_DISCOVERED_ORTA, 0, GetWorldTotalMin(), gWorldSectorX, gWorldSectorY );
2996 break;
2997
2998 case NPC_ACTION_SLAP:
2999 {
3000 // OK, LET'S FIND THE QUEEN AND MAKE HER DO SLAP ANIMATION
3001 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(QUEEN);
3002 if (pSoldier)
3003 {
3004 EVENT_InitNewSoldierAnim( pSoldier, QUEEN_SLAP, 0 , FALSE );
3005 }
3006 break;
3007 }
3008
3009 {
3010 INT16 sGridNo;
3011 Approach approach;
3012
3013 case NPC_ACTION_PUNCH_PC_SLOT_0:
3014 sGridNo = gsInterrogationGridNo[0];
3015 approach = APPROACH_DONE_PUNCH_0;
3016 goto action_punch_pc;
3017
3018 case NPC_ACTION_PUNCH_PC_SLOT_1:
3019 sGridNo = gsInterrogationGridNo[1];
3020 approach = APPROACH_DONE_PUNCH_1;
3021 goto action_punch_pc;
3022
3023 case NPC_ACTION_PUNCH_PC_SLOT_2:
3024 sGridNo = gsInterrogationGridNo[2];
3025 approach = APPROACH_DONE_PUNCH_2;
3026 goto action_punch_pc;
3027
3028 action_punch_pc:
3029 DeleteTalkingMenu( );
3030 // OK, LET'S FIND THE QUEEN AND MAKE HER DO SLAP ANIMATION
3031 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3032 if (pSoldier)
3033 {
3034 // Target a different merc....
3035 const SOLDIERTYPE* const pTarget = WhoIsThere2(sGridNo, 0);
3036
3037 // Use a different approach....
3038 pSoldier->uiPendingActionData4 = approach;
3039
3040 if ( pTarget && pTarget->bActive && pTarget->bInSector && pTarget->bLife != 0 )
3041 {
3042 pSoldier->bNextAction = AI_ACTION_KNIFE_MOVE;
3043 pSoldier->usNextActionData = pTarget->sGridNo;
3044 pSoldier->fAIFlags |= AI_HANDLE_EVERY_FRAME;
3045
3046 // UNless he's has a pending action, delete what he was doing!
3047 // Cancel anything he was doing
3048 if ( pSoldier->bAction != AI_ACTION_PENDING_ACTION )
3049 {
3050 CancelAIAction(pSoldier);
3051 }
3052
3053 //HandleItem( pSoldier, pTarget->sGridNo, 0, NOTHING, FALSE );
3054
3055 pSoldier->uiStatusFlags |= SOLDIER_NPC_DOING_PUNCH;
3056 }
3057 else
3058 {
3059 TriggerNPCWithGivenApproach(pSoldier->ubProfile, approach);
3060 }
3061 }
3062 break;
3063 }
3064
3065 case NPC_ACTION_SHOOT_ELLIOT:
3066 {
3067 DeleteTalkingMenu( );
3068
3069 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3070 if (pSoldier)
3071 {
3072 SOLDIERTYPE* const pTarget = FindSoldierByProfileID(ELLIOT);
3073 if ( pTarget )
3074 {
3075 // Set special flag....
3076 pTarget->uiStatusFlags |= SOLDIER_NPC_SHOOTING;
3077 pSoldier->uiStatusFlags |= SOLDIER_NPC_SHOOTING;
3078
3079 pSoldier->bAimShotLocation = AIM_SHOT_HEAD;
3080
3081 // Add gun to inventory.....
3082 CreateItem( (UINT16) (DESERTEAGLE), 100, &( pSoldier->inv[ HANDPOS ] ) );
3083
3084 // Make shoot
3085 pSoldier->bNextAction = AI_ACTION_FIRE_GUN;
3086 pSoldier->usNextActionData = pTarget->sGridNo;
3087 pSoldier->fAIFlags |= AI_HANDLE_EVERY_FRAME;
3088
3089 // UNless he's has a pending action, delete what he was doing!
3090 // Cancel anything he was doing
3091 if ( pSoldier->bAction != AI_ACTION_PENDING_ACTION )
3092 {
3093 CancelAIAction(pSoldier);
3094 }
3095
3096 // change elliot portrait...
3097 gMercProfiles[ ELLIOT ].bNPCData = 17;
3098 }
3099 }
3100 break;
3101 }
3102
3103 //Emmons: is this line of code part of something missing
3104 //or no longer necessary? CJC
3105 //if ( pSoldier->uiStatusFlags & SOLDIER_NPC_SHOOTING )
3106
3107 case NPC_ACTION_PUNCH_FIRST_LIVING_PC:
3108 {
3109 // Punch first living pc....
3110 // OK, LET'S FIND THE QUEEN AND MAKE HER DO SLAP ANIMATION
3111 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3112 if (pSoldier)
3113 {
3114 INT32 cnt;
3115 BOOLEAN fGoodTarget = FALSE;
3116
3117 for ( cnt = 0; cnt < 3; cnt++ )
3118 {
3119 const SOLDIERTYPE* const pTarget = WhoIsThere2(gsInterrogationGridNo[cnt], 0);
3120 if (pTarget == NULL) continue;
3121
3122 pSoldier->uiPendingActionData4 = APPROACH_DONE_PUNCH_1;
3123
3124 if (pTarget->bActive && pTarget->bInSector)
3125 {
3126 // If we are elliot, we can't do unconocious guys....
3127 if (pSoldier->ubProfile == ELLIOT)
3128 {
3129 if (pTarget->bLife >= OKLIFE) fGoodTarget = TRUE;
3130 }
3131 else
3132 {
3133 if (pTarget->bLife != 0) fGoodTarget = TRUE;
3134 }
3135 }
3136
3137 if ( fGoodTarget )
3138 {
3139 pSoldier->bNextAction = AI_ACTION_KNIFE_MOVE;
3140 pSoldier->usNextActionData = pTarget->sGridNo;
3141 pSoldier->fAIFlags |= AI_HANDLE_EVERY_FRAME;
3142
3143 // UNless he's has a pending action, delete what he was doing!
3144 // Cancel anything he was doing
3145 if ( pSoldier->bAction != AI_ACTION_PENDING_ACTION )
3146 {
3147 CancelAIAction(pSoldier);
3148 }
3149
3150 pSoldier->uiStatusFlags |= SOLDIER_NPC_DOING_PUNCH;
3151 break;
3152 }
3153 }
3154
3155 if ( cnt == 3 )
3156 {
3157 // If here, nobody was found...
3158 TriggerNPCWithGivenApproach(pSoldier->ubProfile, APPROACH_DONE_PUNCH_1);
3159 }
3160 }
3161 break;
3162 }
3163
3164 case NPC_ACTION_FRUSTRATED_SLAP:
3165 {
3166 // OK, LET'S FIND THE QUEEN AND MAKE HER DO SLAP ANIMATION
3167 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3168 if (pSoldier)
3169 {
3170 EVENT_InitNewSoldierAnim( pSoldier, QUEEN_FRUSTRATED_SLAP, 0 , FALSE );
3171 }
3172 break;
3173 }
3174
3175 case NPC_ACTION_START_TIMER_ON_KEITH_GOING_OUT_OF_BUSINESS:
3176 // start timer to place keith out of business
3177 AddStrategicEvent( EVENT_KEITH_GOING_OUT_OF_BUSINESS, GetWorldTotalMin() + ( 6 * 24 * 60 ), 0 );
3178 break;
3179
3180 case NPC_ACTION_KEITH_GOING_BACK_IN_BUSINESS:
3181 // keith is back in business
3182 SetFactFalse( FACT_KEITH_OUT_OF_BUSINESS );
3183 break;
3184 case NPC_ACTION_TRIGGER_QUEEN_BY_CITIES_CONTROLLED:
3185 switch ( GetNumberOfWholeTownsUnderControlButExcludeCity( OMERTA ) )
3186 {
3187 case 0:
3188 case 1:
3189 TriggerNPCRecord( QUEEN, 11 );
3190 break;
3191 case 2:
3192 TriggerNPCRecord( QUEEN, 7 );
3193 break;
3194 case 3:
3195 TriggerNPCRecord( QUEEN, 8 );
3196 break;
3197 case 4:
3198 TriggerNPCRecord( QUEEN, 9 );
3199 break;
3200 case 5:
3201 TriggerNPCRecord( QUEEN, 10 );
3202 break;
3203 default:
3204 TriggerNPCRecord( QUEEN, 11 );
3205 break;
3206 }
3207 break;
3208
3209 case NPC_ACTION_SEND_SOLDIERS_TO_DRASSEN:
3210 case NPC_ACTION_SEND_SOLDIERS_TO_BATTLE_LOCATION:
3211 case NPC_ACTION_SEND_SOLDIERS_TO_OMERTA:
3212 case NPC_ACTION_ADD_MORE_ELITES:
3213 case NPC_ACTION_GIVE_KNOWLEDGE_OF_ALL_MERCS:
3214 break;
3215
3216 case NPC_ACTION_TRIGGER_QUEEN_BY_SAM_SITES_CONTROLLED:
3217 switch ( GetNumberOfSAMSitesUnderPlayerControl() )
3218 {
3219 case 0:
3220 case 1:
3221 TriggerNPCRecord( QUEEN, 7 );
3222 break;
3223 case 2:
3224 TriggerNPCRecord( QUEEN, 8 );
3225 break;
3226 case 3:
3227 TriggerNPCRecord( QUEEN, 9 );
3228 break;
3229 default:
3230 break;
3231 }
3232 break;
3233 case NPC_ACTION_TRIGGER_ELLIOT_BY_BATTLE_RESULT:
3234 if ( gTacticalStatus.fLastBattleWon )
3235 {
3236 TriggerNPCRecord( ELLIOT, 6 );
3237 }
3238 else
3239 {
3240 TriggerNPCRecord( ELLIOT, 8 );
3241 }
3242 break;
3243
3244 case NPC_ACTION_TRIGGER_ELLIOT_BY_SAM_DISABLED:
3245 if ( IsThereAFunctionalSAMSiteInSector( gTacticalStatus.ubLastBattleSectorX, gTacticalStatus.ubLastBattleSectorY, 0 ) )
3246 {
3247 TriggerNPCRecord( QUEEN, 6 );
3248 }
3249 else
3250 {
3251 TriggerNPCRecord( ELLIOT, 2 );
3252 }
3253 break;
3254
3255 case NPC_ACTION_TRIGGER_VINCE_BY_LOYALTY:
3256 if( CheckFact( FACT_NPC_OWED_MONEY, ubTargetNPC ) && ( ubTargetNPC == VINCE ) )
3257 {
3258 // check if we owe vince cash
3259 TriggerNPCRecord( ubTargetNPC, 21 );
3260 }
3261 else if (!CheckFact(FACT_VINCE_EXPLAINED_HAS_TO_CHARGE, ubTargetNPC))
3262 {
3263 TriggerNPCRecord( ubTargetNPC, 15 );
3264 }
3265 else
3266 {
3267 if ( CheckFact( FACT_LOYALTY_LOW, ubTargetNPC ) )
3268 {
3269 gbHospitalPriceModifier = HOSPITAL_NORMAL;
3270 TriggerNPCRecord( ubTargetNPC, 16 );
3271 }
3272 else if ( CheckFact( FACT_LOYALTY_OKAY, ubTargetNPC ) )
3273 {
3274 gbHospitalPriceModifier = HOSPITAL_BREAK;
3275 TriggerNPCRecord( ubTargetNPC, 17 );
3276 }
3277 else
3278 {
3279 const INT32 iRandom = PreRandom(100);
3280 if( ubTargetNPC == VINCE )
3281 {
3282 if (gbHospitalPriceModifier == HOSPITAL_RANDOM_FREEBIE ||
3283 gbHospitalPriceModifier == HOSPITAL_FREEBIE ||
3284 (!CheckFact(FACT_HOSPITAL_FREEBIE_DECISION_MADE, 0) && iRandom < CHANCE_FOR_DOCTOR_TO_SAY_RANDOM_QUOTE))
3285 {
3286 SetFactTrue( FACT_HOSPITAL_FREEBIE_DECISION_MADE );
3287 if ( CheckFact( FACT_LOYALTY_HIGH, ubTargetNPC ) && CheckFact( FACT_24_HOURS_SINCE_DOCTOR_TALKED_TO, ubTargetNPC ) )
3288 {
3289 // loyalty high... freebie
3290 gbHospitalPriceModifier = HOSPITAL_FREEBIE;
3291 TriggerNPCRecord( ubTargetNPC, 19 );
3292 }
3293 else
3294 {
3295 // random vince quote
3296 gbHospitalPriceModifier = HOSPITAL_RANDOM_FREEBIE;
3297 TriggerNPCRecord( ubTargetNPC, 20 );
3298 }
3299 }
3300 else // loyalty good
3301 {
3302 gbHospitalPriceModifier = HOSPITAL_COST;
3303 TriggerNPCRecord( ubTargetNPC, 18 );
3304 }
3305 }
3306 else
3307 {
3308 // it's steve willis
3309
3310 // in discount mode?
3311 if ( CheckFact( FACT_WILLIS_GIVES_DISCOUNT, ubTargetNPC ) )
3312 {
3313 // yep inform player
3314 gbHospitalPriceModifier = HOSPITAL_COST;
3315 TriggerNPCRecord( ubTargetNPC, 21 );
3316 }
3317 else if ( CheckFact( FACT_WILLIS_HEARD_ABOUT_JOEY_RESCUE, ubTargetNPC ) )
3318 {
3319 // joey rescued.... note this will set FACT_WILLIS_GIVES_DISCOUNT so this will only ever be handled once
3320 gbHospitalPriceModifier = HOSPITAL_FREEBIE;
3321 TriggerNPCRecord( ubTargetNPC, 20 );
3322 }
3323 else if (CheckFact(FACT_LOYALTY_HIGH, ubTargetNPC) &&
3324 CheckFact(FACT_24_HOURS_SINCE_DOCTOR_TALKED_TO, ubTargetNPC) && (
3325 gbHospitalPriceModifier == HOSPITAL_FREEBIE ||
3326 (!CheckFact(FACT_HOSPITAL_FREEBIE_DECISION_MADE, 0) && iRandom < CHANCE_FOR_DOCTOR_TO_SAY_RANDOM_QUOTE)))
3327 {
3328 // loyalty high... freebie
3329 gbHospitalPriceModifier = HOSPITAL_FREEBIE;
3330 TriggerNPCRecord( ubTargetNPC, 19 );
3331 }
3332 else // loyalty good
3333 {
3334 gbHospitalPriceModifier = HOSPITAL_COST;
3335 TriggerNPCRecord( ubTargetNPC, 18 );
3336 }
3337 }
3338 }
3339 }
3340 break;
3341 case NPC_ACTION_CHECK_DOCTORING_MONEY_GIVEN:
3342 break;
3343
3344 case NPC_ACTION_DOCTOR_ESCORT_PATIENTS:
3345 {
3346 // find anyone in sector who is wounded; set to hospital patient
3347 const SOLDIERTYPE* const pSoldier2 = FindSoldierByProfileID(ubTargetNPC);
3348 if ( pSoldier2 )
3349 {
3350 //HOSPITAL_PATIENT_DISTANCE
3351 FOR_EACH_IN_TEAM(i, OUR_TEAM)
3352 {
3353 SOLDIERTYPE& s = *i;
3354 // Are we in this sector, On the current squad?
3355 if (s.bInSector &&
3356 0 < s.bLife && s.bLife < s.bLifeMax &&
3357 s.bAssignment != ASSIGNMENT_HOSPITAL &&
3358 PythSpacesAway(s.sGridNo, pSoldier2->sGridNo) < HOSPITAL_PATIENT_DISTANCE)
3359 {
3360 SetSoldierAssignmentHospital(s);
3361 TriggerNPCRecord(s.ubProfile, 2);
3362 s.bHospitalPriceModifier = gbHospitalPriceModifier;
3363 // make sure this person doesn't have an absolute dest any more
3364 s.sAbsoluteFinalDestination = NOWHERE;
3365 }
3366 }
3367
3368 SetFactFalse( FACT_HOSPITAL_FREEBIE_DECISION_MADE );
3369 gbHospitalPriceModifier = HOSPITAL_NORMAL;
3370 }
3371 break;
3372 }
3373
3374 case NPC_ACTION_START_DOCTORING:
3375 {
3376
3377 // reset fact he is expecting money fromt he player
3378 SetFactFalse( FACT_VINCE_EXPECTING_MONEY );
3379
3380 // check fact
3381 if( CheckFact( FACT_PLAYER_STOLE_MEDICAL_SUPPLIES, ubTargetNPC ) )
3382 {
3383 TriggerNPCRecord( ubTargetNPC, 28 );
3384 }
3385 else
3386 {
3387 TriggerNPCRecord( ubTargetNPC, 35 );
3388 }
3389 }
3390 break;
3391
3392 case NPC_ACTION_VINCE_UNRECRUITABLE:
3393 SetFactFalse( FACT_VINCE_RECRUITABLE );
3394 break;
3395 case NPC_ACTION_ELLIOT_DECIDE_WHICH_QUOTE_FOR_PLAYER_ATTACK:
3396 if( DidFirstBattleTakePlaceInThisTown( DRASSEN ) )
3397 {
3398 TriggerNPCRecord( ELLIOT, 2 );
3399 }
3400 else if( DidFirstBattleTakePlaceInThisTown( CAMBRIA ) )
3401 {
3402 TriggerNPCRecord( ELLIOT, 3 );
3403 }
3404 else
3405 {
3406 TriggerNPCRecord( ELLIOT, 1 );
3407 }
3408 break;
3409 case NPC_ACTION_QUEEN_DECIDE_WHICH_QUOTE_FOR_PLAYER_ATTACK:
3410 if( DidFirstBattleTakePlaceInThisTown( DRASSEN ) )
3411 {
3412 TriggerNPCRecord( QUEEN, 8 );
3413 }
3414 else
3415 {
3416 TriggerNPCRecord( QUEEN, 9 );
3417 }
3418
3419 break;
3420 case NPC_ACTION_KROTT_ALIVE_LOYALTY_BOOST:
3421 if ( gMercProfiles[ SERGEANT ].bMercStatus != MERC_IS_DEAD )
3422 {
3423 // give loyalty bonus to Alma
3424 IncrementTownLoyalty( ALMA, LOYALTY_BONUS_FOR_SERGEANT_KROTT );
3425 }
3426 break;
3427
3428 case NPC_ACTION_MAKE_NPC_FIRST_BARTENDER:
3429 gMercProfiles[ ubTargetNPC ].bNPCData = 1;
3430 break;
3431 case NPC_ACTION_MAKE_NPC_SECOND_BARTENDER:
3432 gMercProfiles[ ubTargetNPC ].bNPCData = 2;
3433 break;
3434 case NPC_ACTION_MAKE_NPC_THIRD_BARTENDER:
3435 gMercProfiles[ ubTargetNPC ].bNPCData = 3;
3436 break;
3437 case NPC_ACTION_MAKE_NPC_FOURTH_BARTENDER:
3438 gMercProfiles[ ubTargetNPC ].bNPCData = 4;
3439 break;
3440
3441 case NPC_ACTION_END_DEMO:
3442 DeleteTalkingMenu();
3443 // hack!!
3444 EndGameMessageBoxCallBack( MSG_BOX_RETURN_YES );
3445 break;
3446
3447 case NPC_ACTION_SEND_ENRICO_MIGUEL_EMAIL:
3448 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, 390 + Random( 60 ), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_SEND_ENRICO_MIGUEL_EMAIL, 1 );
3449 break;
3450
3451 case NPC_ACTION_PLAYER_SAYS_NICE_LATER:
3452 SetFactTrue( FACT_NEED_TO_SAY_SOMETHING );
3453 gubNiceNPCProfile = gpDestSoldier->ubProfile;
3454 break;
3455
3456 case NPC_ACTION_PLAYER_SAYS_NASTY_LATER:
3457 SetFactTrue( FACT_NEED_TO_SAY_SOMETHING );
3458 gubNastyNPCProfile = gpDestSoldier->ubProfile;
3459 break;
3460
3461 case NPC_ACTION_CHOOSE_DOCTOR:
3462 {
3463 // find doctors available and trigger record 12 or 13
3464 SOLDIERTYPE* pSoldier = FindSoldierByProfileID(STEVE); // Steve Willis, 80
3465 if (pSoldier)
3466 {
3467 if (!pSoldier->bInSector || pSoldier->bTeam != CIV_TEAM || !pSoldier->bNeutral || pSoldier->bLife < OKLIFE)
3468 {
3469 pSoldier = NULL;
3470 }
3471 }
3472
3473 SOLDIERTYPE* pSoldier2 = FindSoldierByProfileID(VINCE); // Vince, 69
3474 if (pSoldier2)
3475 {
3476 if (!pSoldier2->bInSector || pSoldier2->bTeam != CIV_TEAM || !pSoldier2->bNeutral || pSoldier2->bLife < OKLIFE)
3477 {
3478 pSoldier2 = NULL;
3479 }
3480 }
3481
3482 if ( pSoldier == NULL && pSoldier2 == NULL )
3483 {
3484 // uh oh
3485 break;
3486 }
3487
3488 if( ( pSoldier ) && ( pSoldier2 ) )
3489 {
3490 if( pSoldier->sGridNo == params->getGridNo(10343) )
3491 {
3492 pSoldier2 = NULL;
3493 }
3494 else if( pSoldier2->sGridNo == params->getGridNo(10343) )
3495 {
3496 pSoldier = NULL;
3497 }
3498 }
3499
3500 if ( pSoldier && pSoldier2 )
3501 {
3502 // both doctors available... turn one off randomly
3503 if ( Random( 2 ) )
3504 {
3505 pSoldier = NULL;
3506 }
3507 else
3508 {
3509 pSoldier2 = NULL;
3510 }
3511 }
3512
3513 // only one pointer left...
3514
3515 if ( pSoldier )
3516 {
3517 TriggerNPCRecord( ubTargetNPC, 12 );
3518 }
3519 else
3520 {
3521 TriggerNPCRecord( ubTargetNPC, 13 );
3522 }
3523 break;
3524 }
3525
3526 case NPC_ACTION_INVOKE_CONVERSATION_MODE:
3527 if ( !gfInTalkPanel )
3528 {
3529 INT16 sNearestPC;
3530
3531 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3532 if ( pSoldier )
3533 {
3534 SOLDIERTYPE* pSoldier2 = NULL;
3535
3536 sNearestPC = ClosestPC( pSoldier, NULL );
3537 if ( sNearestPC != NOWHERE )
3538 {
3539 pSoldier2 = WhoIsThere2(sNearestPC, 0);
3540 }
3541
3542 if ( pSoldier2 )
3543 {
3544 InitiateConversation(pSoldier, pSoldier2, NPC_INITIAL_QUOTE);
3545 }
3546 }
3547 }
3548 break;
3549
3550 case NPC_ACTION_KINGPIN_TRIGGER_25_OR_14:
3551 if ( CheckFact( FACT_KINGPIN_INTRODUCED_SELF, ubTargetNPC ) == TRUE)
3552 {
3553 TriggerNPCRecord( ubTargetNPC, 25 );
3554 }
3555 else
3556 {
3557 TriggerNPCRecord( ubTargetNPC, 14 );
3558 }
3559 break;
3560
3561 case NPC_ACTION_INITIATE_SHOPKEEPER_INTERFACE:
3562 // check if this is a shopkeeper who has been shutdown
3563 if (!HandleShopKeepHasBeenShutDown(gTalkPanel.ubCharNum))
3564 {
3565 DeleteTalkingMenu( );
3566
3567 EnterShopKeeperInterfaceScreen( gTalkPanel.ubCharNum );
3568 }
3569 break;
3570
3571 case NPC_ACTION_DRINK_WINE:
3572 gMercProfiles[ ubTargetNPC ].bNPCData += 2;
3573 break;
3574
3575 case NPC_ACTION_DRINK_BOOZE:
3576 gMercProfiles[ ubTargetNPC ].bNPCData += 4;
3577 break;
3578
3579 case NPC_ACTION_TRIGGER_ANGEL_22_OR_24:
3580 if (CheckFact(FACT_ANGEL_LEFT_DEED, ubTargetNPC))
3581 {
3582 TriggerNPCRecord( ubTargetNPC, 22 );
3583 }
3584 else
3585 {
3586 TriggerNPCRecord( ubTargetNPC, 24 );
3587 }
3588 break;
3589
3590 case NPC_ACTION_GRANT_EXPERIENCE_1:
3591 AwardExperienceBonusToActiveSquad( EXP_BONUS_MINIMUM );
3592 break;
3593 case NPC_ACTION_GRANT_EXPERIENCE_2:
3594 AwardExperienceBonusToActiveSquad( EXP_BONUS_SMALL );
3595 break;
3596 case NPC_ACTION_GRANT_EXPERIENCE_3:
3597 AwardExperienceBonusToActiveSquad( EXP_BONUS_AVERAGE );
3598 break;
3599 case NPC_ACTION_GRANT_EXPERIENCE_4:
3600 AwardExperienceBonusToActiveSquad( EXP_BONUS_LARGE );
3601 break;
3602 case NPC_ACTION_GRANT_EXPERIENCE_5:
3603 AwardExperienceBonusToActiveSquad( EXP_BONUS_MAXIMUM );
3604 break;
3605 case NPC_ACTION_TRIGGER_YANNI:
3606 if ( CheckFact( FACT_CHALICE_STOLEN, 0 ) == TRUE )
3607 {
3608 TriggerNPCRecord( YANNI, 8 );
3609 }
3610 else
3611 {
3612 TriggerNPCRecord( YANNI, 7 );
3613 }
3614 break;
3615 case NPC_ACTION_TRIGGER_MARY_OR_JOHN_RECORD_9:
3616 {
3617 const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(MARY);
3618 if ( pSoldier && pSoldier->bLife >= OKLIFE )
3619 {
3620 TriggerNPCRecord( MARY, 9 );
3621 }
3622 else
3623 {
3624 TriggerNPCRecord( JOHN, 9 );
3625 }
3626 break;
3627 }
3628
3629 case NPC_ACTION_TRIGGER_MARY_OR_JOHN_RECORD_10:
3630 {
3631 const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(MARY);
3632 if ( pSoldier && pSoldier->bLife >= OKLIFE )
3633 {
3634 TriggerNPCRecord( MARY, 10 );
3635 }
3636 else
3637 {
3638 TriggerNPCRecord( JOHN, 10 );
3639 }
3640 break;
3641 }
3642
3643 case NPC_ACTION_GET_OUT_OF_WHEELCHAIR:
3644 {
3645 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3646 if ( pSoldier && pSoldier->ubBodyType == CRIPPLECIV )
3647 {
3648 DeleteTalkingMenu();
3649 EVENT_InitNewSoldierAnim( pSoldier, CRIPPLE_KICKOUT, 0, TRUE );
3650 }
3651 break;
3652 }
3653
3654 case NPC_ACTION_GET_OUT_OF_WHEELCHAIR_AND_BECOME_HOSTILE:
3655 {
3656 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3657 if ( pSoldier && pSoldier->ubBodyType == CRIPPLECIV )
3658 {
3659 DeleteTalkingMenu();
3660 EVENT_InitNewSoldierAnim( pSoldier, CRIPPLE_KICKOUT, 0, TRUE );
3661 }
3662 TriggerFriendWithHostileQuote( ubTargetNPC );
3663 break;
3664 }
3665
3666 case NPC_ACTION_ADD_JOHNS_GUN_SHIPMENT:
3667 AddJohnsGunShipment();
3668 // also close panel
3669 DeleteTalkingMenu();
3670 break;
3671
3672 case NPC_ACTION_SET_FACT_105_FALSE:
3673 SetFactFalse( FACT_FRANK_HAS_BEEN_BRIBED );
3674 break;
3675
3676 case NPC_ACTION_CANCEL_WAYPOINTS:
3677 {
3678 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3679 if ( pSoldier )
3680 {
3681 pSoldier->bOrders = ONGUARD;
3682 }
3683 break;
3684 }
3685
3686 case NPC_ACTION_MAKE_RAT_DISAPPEAR:
3687 // hack a flag to determine when RAT should leave
3688 gMercProfiles[ RAT ].bNPCData = 1;
3689 break;
3690
3691 case NPC_ACTION_TRIGGER_MICKY_BY_SCI_FI:
3692 if ( gGameOptions.fSciFi )
3693 {
3694 TriggerNPCRecord( MICKY, 7 );
3695 }
3696 else
3697 {
3698 TriggerNPCRecord( MICKY, 8 );
3699 }
3700 break;
3701 case NPC_ACTION_TRIGGER_KROTT_11_OR_12:
3702 if ( CheckFact( FACT_KROTT_GOT_ANSWER_NO, SERGEANT ) )
3703 {
3704 TriggerNPCRecord( SERGEANT, 11 );
3705 }
3706 else
3707 {
3708 TriggerNPCRecord( SERGEANT, 12 );
3709 }
3710 break;
3711
3712 case NPC_ACTION_CHANGE_MANNY_POSITION:
3713 {
3714 gMercProfiles[ MANNY ].ubMiscFlags3 |= PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE;
3715 gMercProfiles[ MANNY ].ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
3716 gMercProfiles[ MANNY ].usStrategicInsertionData = params->getGridNo(19904);
3717 gMercProfiles[ MANNY ].fUseProfileInsertionInfo = TRUE;
3718 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(MANNY);
3719 if ( pSoldier )
3720 {
3721 pSoldier->bOrders = STATIONARY;
3722 }
3723 // close his panel too
3724 DeleteTalkingMenu();
3725 break;
3726 }
3727
3728 case NPC_ACTION_MAKE_BRENDA_STATIONARY:
3729 {
3730 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(BRENDA);
3731 if ( pSoldier )
3732 {
3733 gMercProfiles[ BRENDA ].ubMiscFlags3 |= PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE;
3734 gMercProfiles[ BRENDA ].ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
3735 gMercProfiles[ BRENDA ].usStrategicInsertionData = pSoldier->sGridNo;
3736 gMercProfiles[ BRENDA ].fUseProfileInsertionInfo = TRUE;
3737 pSoldier->bOrders = STATIONARY;
3738 }
3739 break;
3740 }
3741
3742 case NPC_ACTION_MAKE_MIGUEL_STATIONARY:
3743 {
3744 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(MIGUEL);
3745 if ( pSoldier )
3746 {
3747 gMercProfiles[ MIGUEL ].ubMiscFlags3 |= PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE;
3748 gMercProfiles[ MIGUEL ].ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
3749 gMercProfiles[ MIGUEL ].usStrategicInsertionData = pSoldier->sGridNo;
3750 gMercProfiles[ MIGUEL ].fUseProfileInsertionInfo = TRUE;
3751 pSoldier->bOrders = STATIONARY;
3752 }
3753 break;
3754 }
3755
3756 case NPC_ACTION_TRIGGER_DARREN_OR_KINGPIN_IMPRESSED:
3757 if ( BoxerAvailable() )
3758 {
3759 TriggerNPCRecord( DARREN, 25 );
3760 }
3761 // else check for Kingpin being impressed
3762 // fallthrough
3763 case NPC_ACTION_TRIGGER_KINGPIN_IMPRESSED:
3764 if ( gfLastBoxingMatchWonByPlayer && !BoxerAvailable() )
3765 {
3766 TriggerNPCRecord( KINGPIN, 15 );
3767 }
3768 break;
3769
3770 case NPC_ACTION_ADD_RAT:
3771 // add Rat
3772 gMercProfiles[ RAT ].sSectorX = 9;
3773 gMercProfiles[ RAT ].sSectorY = MAP_ROW_G;
3774 gMercProfiles[ RAT ].bSectorZ = 0;
3775 break;
3776
3777 case NPC_ACTION_ENDGAME_STATE_1:
3778
3779 // What is the status of fact for creatures?
3780 if ( gubQuest[ 15 ] == QUESTINPROGRESS )
3781 {
3782 // This is not the end, 'cause momma creature is still alive
3783 TriggerNPCRecordImmediately( 136, 8 );
3784 EndQueenDeathEndgame( );
3785 }
3786 else
3787 {
3788 // Continue with endgame cimematic..
3789 DeleteTalkingMenu( );
3790 EndQueenDeathEndgameBeginEndCimenatic( );
3791 }
3792 break;
3793
3794 case NPC_ACTION_ENDGAME_STATE_2:
3795
3796 // Just end queen killed dequence.......
3797 DeleteTalkingMenu( );
3798 EndQueenDeathEndgame( );
3799 break;
3800
3801 case NPC_ACTION_MAKE_ESTONI_A_FUEL_SITE:
3802 // Jake's script also sets the fact, but we need to be sure it happens before updating availability
3803 SetFactTrue( FACT_ESTONI_REFUELLING_POSSIBLE );
3804 UpdateRefuelSiteAvailability();
3805 break;
3806
3807 case NPC_ACTION_24_HOURS_SINCE_JOEY_RESCUED:
3808 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), FACT_24_HOURS_SINCE_JOEY_RESCUED, 1 );
3809 break;
3810
3811 case NPC_ACTION_24_HOURS_SINCE_DOCTORS_TALKED_TO:
3812 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), FACT_24_HOURS_SINCE_DOCTOR_TALKED_TO, 1 );
3813 break;
3814
3815 case NPC_ACTION_REMOVE_MERC_FOR_MARRIAGE:
3816 {
3817 SOLDIERTYPE* pSoldier = FindSoldierByProfileID(ubTargetNPC);
3818 if ( pSoldier )
3819 {
3820 pSoldier = ChangeSoldierTeam( pSoldier, CIV_TEAM );
3821 }
3822 // remove profile from map
3823 gMercProfiles[ pSoldier->ubProfile ].sSectorX = 0;
3824 gMercProfiles[ pSoldier->ubProfile ].sSectorY = 0;
3825 pSoldier->ubProfile = NO_PROFILE;
3826 // set to 0 civ group
3827 pSoldier->ubCivilianGroup = 0;
3828 break;
3829 }
3830
3831 case NPC_ACTION_TIMER_FOR_VEHICLE:
3832 AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_TIMER_FOR_VEHICLE, 1 );
3833 break;
3834
3835 case NPC_ACTION_RESET_SHIPMENT_ARRIVAL_STUFF:
3836 break;
3837
3838 case NPC_ACTION_TRIGGER_JOE_32_OR_33:
3839 if ( gbWorldSectorZ > 0 )
3840 {
3841 TriggerNPCRecord( JOE, 32 );
3842 }
3843 else
3844 {
3845 TriggerNPCRecord( JOE, 33 );
3846 }
3847 break;
3848 case NPC_ACTION_REMOVE_NPC:
3849 {
3850 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubTargetNPC);
3851 if ( pSoldier )
3852 {
3853 EndAIGuysTurn(*pSoldier);
3854 RemoveManAsTarget( pSoldier );
3855 TacticalRemoveSoldier(*pSoldier);
3856 gMercProfiles[ ubTargetNPC ].sSectorX = 0;
3857 gMercProfiles[ ubTargetNPC ].sSectorY = 0;
3858 CheckForEndOfBattle( TRUE );
3859 }
3860 break;
3861 }
3862
3863 {
3864 UINT8 code;
3865 case NPC_ACTION_HISTORY_GOT_ROCKET_RIFLES:
3866 code = HISTORY_GOT_ROCKET_RIFLES;
3867 goto add_log;
3868 case NPC_ACTION_HISTORY_DEIDRANNA_DEAD_BODIES:
3869 code = HISTORY_DEIDRANNA_DEAD_BODIES;
3870 goto add_log;
3871 case NPC_ACTION_HISTORY_BOXING_MATCHES:
3872 code = HISTORY_BOXING_MATCHES;
3873 goto add_log;
3874 case NPC_ACTION_HISTORY_SOMETHING_IN_MINES:
3875 code = HISTORY_SOMETHING_IN_MINES;
3876 goto add_log;
3877 case NPC_ACTION_HISTORY_DEVIN:
3878 code = HISTORY_DEVIN;
3879 goto add_log;
3880 case NPC_ACTION_HISTORY_MIKE:
3881 code = HISTORY_MIKE;
3882 goto add_log;
3883 case NPC_ACTION_HISTORY_TONY:
3884 code = HISTORY_TONY;
3885 goto add_log;
3886 case NPC_ACTION_HISTORY_KROTT:
3887 code = HISTORY_KROTT;
3888 goto add_log;
3889 case NPC_ACTION_HISTORY_KYLE:
3890 code = HISTORY_KYLE;
3891 goto add_log;
3892 case NPC_ACTION_HISTORY_MADLAB:
3893 code = HISTORY_MADLAB;
3894 goto add_log;
3895 case NPC_ACTION_HISTORY_GABBY:
3896 code = HISTORY_GABBY;
3897 goto add_log;
3898 case NPC_ACTION_HISTORY_KEITH_OUT_OF_BUSINESS:
3899 code = HISTORY_KEITH_OUT_OF_BUSINESS;
3900 goto add_log;
3901 case NPC_ACTION_HISTORY_HOWARD_CYANIDE:
3902 code = HISTORY_HOWARD_CYANIDE;
3903 goto add_log;
3904 case NPC_ACTION_HISTORY_KEITH:
3905 code = HISTORY_KEITH;
3906 goto add_log;
3907 case NPC_ACTION_HISTORY_HOWARD:
3908 code = HISTORY_HOWARD;
3909 goto add_log;
3910 case NPC_ACTION_HISTORY_PERKO:
3911 code = HISTORY_PERKO;
3912 goto add_log;
3913 case NPC_ACTION_HISTORY_SAM:
3914 code = HISTORY_SAM;
3915 goto add_log;
3916 case NPC_ACTION_HISTORY_FRANZ:
3917 code = HISTORY_FRANZ;
3918 goto add_log;
3919 case NPC_ACTION_HISTORY_ARNOLD:
3920 code = HISTORY_ARNOLD;
3921 goto add_log;
3922 case NPC_ACTION_HISTORY_FREDO:
3923 code = HISTORY_FREDO;
3924 goto add_log;
3925 case NPC_ACTION_HISTORY_RICHGUY_BALIME:
3926 code = HISTORY_RICHGUY_BALIME;
3927 goto add_log;
3928 case NPC_ACTION_HISTORY_JAKE:
3929 code = HISTORY_JAKE;
3930 goto add_log;
3931 case NPC_ACTION_HISTORY_BUM_KEYCARD:
3932 code = HISTORY_BUM_KEYCARD;
3933 goto add_log;
3934 case NPC_ACTION_HISTORY_WALTER:
3935 code = HISTORY_WALTER;
3936 goto add_log;
3937 case NPC_ACTION_HISTORY_DAVE:
3938 code = HISTORY_DAVE;
3939 goto add_log;
3940 case NPC_ACTION_HISTORY_PABLO:
3941 code = HISTORY_PABLO;
3942 goto add_log;
3943 case NPC_ACTION_HISTORY_KINGPIN_MONEY:
3944 code = HISTORY_KINGPIN_MONEY;
3945 goto add_log;
3946 add_log:
3947 AddHistoryToPlayersLog(code, 0, GetWorldTotalMin(),
3948 gWorldSectorX, gWorldSectorY);
3949 break;
3950 }
3951
3952 case NPC_ACTION_SEND_TROOPS_TO_SAM:
3953 break;
3954 case NPC_ACTION_PUT_PACOS_IN_BASEMENT:
3955 gMercProfiles[ PACOS ].sSectorX = 10;
3956 gMercProfiles[ PACOS ].sSectorY = MAP_ROW_A;
3957 gMercProfiles[ PACOS ].bSectorZ = 0;
3958 break;
3959 case NPC_ACTION_HISTORY_ASSASSIN:
3960 AddHistoryToPlayersLog(HISTORY_ASSASSIN, 0, GetWorldTotalMin(),
3961 gWorldSectorX, gWorldSectorY);
3962 break;
3963 case NPC_ACTION_TRIGGER_HANS_BY_ROOM:
3964 {
3965 if ( gpSrcSoldier )
3966 {
3967 if (GetRoom(gpSrcSoldier->sGridNo) == 49)
3968 {
3969 TriggerNPCRecord( HANS, 18 );
3970 }
3971 else
3972 {
3973 TriggerNPCRecord( HANS, 14 );
3974 }
3975
3976 }
3977 }
3978 break;
3979 case NPC_ACTION_TRIGGER_MADLAB_31:
3980 if ( PlayerTeamFull( ) )
3981 {
3982 DeleteTalkingMenu( );
3983 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ CANNOT_RECRUIT_TEAM_FULL ] );
3984 }
3985 else
3986 {
3987 TriggerNPCRecord( MADLAB, 31 );
3988 }
3989 break;
3990 case NPC_ACTION_TRIGGER_MADLAB_32:
3991 if ( PlayerTeamFull( ) )
3992 {
3993 DeleteTalkingMenu( );
3994 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ CANNOT_RECRUIT_TEAM_FULL ] );
3995 }
3996 else
3997 {
3998 TriggerNPCRecord( MADLAB, 32 );
3999 }
4000 break;
4001 case NPC_ACTION_TRIGGER_BREWSTER_BY_WARDEN_PROXIMITY:
4002 {
4003 const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(BREWSTER);
4004 if ( pSoldier )
4005 {
4006 const INT16 sGridNo = GetGridNoOfCorpseGivenProfileID(WARDEN);
4007 if ( sGridNo != NOWHERE && PythSpacesAway( pSoldier->sGridNo, sGridNo ) <= 10 )
4008 {
4009 TriggerNPCRecord( BREWSTER, 16 );
4010 }
4011 else
4012 {
4013 TriggerNPCRecord( BREWSTER, 17 );
4014 }
4015 }
4016 break;
4017 }
4018
4019 case NPC_ACTION_FILL_UP_CAR:
4020 {
4021 SOLDIERTYPE* pSoldier = FindSoldierByProfileIDOnPlayerTeam(PROF_HUMMER);
4022 if ( !pSoldier )
4023 {
4024 // ice cream truck?
4025 pSoldier = FindSoldierByProfileIDOnPlayerTeam(PROF_ICECREAM);
4026 }
4027 else if ( pSoldier->sBreathRed == 10000 )
4028 {
4029 SOLDIERTYPE* const pSoldier2 = FindSoldierByProfileIDOnPlayerTeam(PROF_ICECREAM);
4030 if ( pSoldier2 )
4031 {
4032 // try refueling this vehicle instead
4033 pSoldier = pSoldier2;
4034 }
4035 }
4036 if ( pSoldier )
4037 {
4038 pSoldier->sBreathRed = 10000;
4039 pSoldier->bBreath = 100;
4040 ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, gzLateLocalizedString[STR_LATE_50]);
4041 }
4042 break;
4043 }
4044
4045 case NPC_ACTION_WALTER_GIVEN_MONEY_INITIALLY:
4046 if ( gMercProfiles[ WALTER ].iBalance >= params->getGridNo(WALTER_BRIBE_AMOUNT) )
4047 {
4048 TriggerNPCRecord( WALTER, 16 );
4049 }
4050 else
4051 {
4052 TriggerNPCRecord( WALTER, 14 );
4053 }
4054 break;
4055 case NPC_ACTION_WALTER_GIVEN_MONEY:
4056 if ( gMercProfiles[ WALTER ].iBalance >= params->getGridNo(WALTER_BRIBE_AMOUNT) )
4057 {
4058 TriggerNPCRecord( WALTER, 16 );
4059 }
4060 else
4061 {
4062 TriggerNPCRecord( WALTER, 15 );
4063 }
4064 break;
4065 default:
4066 STLOGD("No code support for NPC action {}", usActionCode);
4067 break;
4068 }
4069 }
4070 }
4071
4072
CalcPatientMedicalCost(const SOLDIERTYPE * const pSoldier)4073 UINT32 CalcPatientMedicalCost(const SOLDIERTYPE* const pSoldier)
4074 {
4075 UINT32 uiCost;
4076
4077 if ( !pSoldier )
4078 {
4079 return( 0 );
4080 }
4081
4082 if ( pSoldier->bHospitalPriceModifier == HOSPITAL_FREEBIE || pSoldier->bHospitalPriceModifier == HOSPITAL_RANDOM_FREEBIE )
4083 {
4084 return( 0 );
4085 }
4086
4087 uiCost = 10 * ( pSoldier->bLifeMax - pSoldier->bLife );
4088
4089 if ( pSoldier->bLife < OKLIFE )
4090 {
4091 // charge additional $25 for every point below OKLIFE he is
4092 uiCost += ( 25 * ( OKLIFE - pSoldier->bLife ) );
4093 }
4094
4095 // also charge $2 for each point of bleeding that must be stopped
4096 if ( pSoldier->bBleeding > 0 )
4097 {
4098 uiCost += ( 2 * pSoldier->bBleeding );
4099 }
4100
4101 if ( pSoldier->bHospitalPriceModifier == HOSPITAL_BREAK )
4102 {
4103 uiCost = (uiCost * 85) / 100;
4104 }
4105 else if ( pSoldier->bHospitalPriceModifier == HOSPITAL_COST )
4106 {
4107 // 30% discount
4108 uiCost = (uiCost * 70) / 100;
4109 }
4110 else if ( pSoldier->bHospitalPriceModifier == HOSPITAL_UNSET )
4111 {
4112 if ( gbHospitalPriceModifier == HOSPITAL_BREAK )
4113 {
4114 // 15% discount
4115 uiCost = (uiCost * 85) / 100;
4116 }
4117 else if ( gbHospitalPriceModifier == HOSPITAL_COST )
4118 {
4119 // 30% discount
4120 uiCost = (uiCost * 70) / 100;
4121 }
4122 }
4123
4124 return( uiCost );
4125 }
4126
CalcMedicalCost(UINT8 ubId)4127 UINT32 CalcMedicalCost( UINT8 ubId )
4128 {
4129 UINT32 uiCostSoFar;
4130 INT16 sGridNo = 0;
4131
4132 uiCostSoFar = 0;
4133
4134 // find the doctor's soldiertype to get his position
4135 const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubId);
4136 if (!pNPC)
4137 {
4138 return( 0 );
4139 }
4140
4141 sGridNo = pNPC->sGridNo;
4142
4143 CFOR_EACH_IN_TEAM(s, OUR_TEAM)
4144 {
4145 if (s->bInSector &&
4146 0 < s->bLife && s->bLife < s->bLifeMax &&
4147 s->bAssignment != ASSIGNMENT_HOSPITAL &&
4148 PythSpacesAway(sGridNo, s->sGridNo) <= HOSPITAL_PATIENT_DISTANCE)
4149 {
4150 uiCostSoFar += CalcPatientMedicalCost(s);
4151 }
4152 }
4153
4154 // round up to nearest 10 dollars
4155 uiCostSoFar = ( (uiCostSoFar + 9) / 10 );
4156 uiCostSoFar *= 10;
4157
4158 // always ask for at least $10
4159 uiCostSoFar = __max( 10, uiCostSoFar );
4160
4161 return( uiCostSoFar );
4162 }
4163
4164
4165 static UINT16 gusDialogueMessageBoxType;
4166
4167
4168 static void DialogueMessageBoxCallBack(MessageBoxReturnValue);
4169
4170
StartDialogueMessageBox(UINT8 ubProfileID,UINT16 usMessageBoxType)4171 static void StartDialogueMessageBox(UINT8 ubProfileID, UINT16 usMessageBoxType)
4172 {
4173 INT32 iTemp;
4174 ST::string zTemp;
4175 ST::string zTemp2;
4176
4177 gusDialogueMessageBoxType = usMessageBoxType;
4178 switch( gusDialogueMessageBoxType )
4179 {
4180 case NPC_ACTION_ASK_ABOUT_ESCORTING_EPC:
4181 if ((ubProfileID == JOHN && gMercProfiles[MARY].bMercStatus != MERC_IS_DEAD) ||
4182 (ubProfileID == MARY && gMercProfiles[JOHN].bMercStatus != MERC_IS_DEAD) )
4183 {
4184 zTemp = gzLateLocalizedString[STR_LATE_57];
4185 }
4186 else
4187 {
4188 zTemp = st_format_printf(TacticalStr[ ESCORT_PROMPT ], gMercProfiles[ubProfileID].zNickname);
4189 }
4190 DoMessageBox(MSG_BOX_BASIC_STYLE, zTemp, GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4191 break;
4192 case NPC_ACTION_ASK_ABOUT_PAYING_RPC:
4193 case NPC_ACTION_ASK_ABOUT_PAYING_RPC_WITH_DAILY_SALARY:
4194 case NPC_ACTION_REDUCE_CONRAD_SALARY_CONDITIONS:
4195 zTemp2 = ST::format("${}", gMercProfiles[ubProfileID].sSalary);
4196 zTemp = st_format_printf(TacticalStr[ HIRE_PROMPT ], gMercProfiles[ubProfileID].zNickname, zTemp2);
4197 DoMessageBox(MSG_BOX_BASIC_STYLE, zTemp, GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4198 break;
4199 case NPC_ACTION_DARREN_REQUESTOR:
4200 case NPC_ACTION_FIGHT_AGAIN_REQUESTOR:
4201 DoMessageBox(MSG_BOX_BASIC_STYLE, TacticalStr[BOXING_PROMPT], GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4202 break;
4203 case NPC_ACTION_BUY_LEATHER_KEVLAR_VEST:
4204 zTemp2 = ST::format("${}", GCM->getItem(LEATHER_JACKET_W_KEVLAR)->getPrice());
4205 zTemp = st_format_printf(TacticalStr[ BUY_VEST_PROMPT ], ItemNames[LEATHER_JACKET_W_KEVLAR], zTemp2);
4206 DoMessageBox(MSG_BOX_BASIC_STYLE, zTemp, GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4207 break;
4208 case NPC_ACTION_PROMPT_PLAYER_TO_LIE:
4209 DoMessageBox(MSG_BOX_BASIC_STYLE, TacticalStr[YESNOLIE_STR], GAME_SCREEN, MSG_BOX_FLAG_YESNOLIE, DialogueMessageBoxCallBack, NULL);
4210 break;
4211 case NPC_ACTION_MEDICAL_REQUESTOR_2:
4212 DoMessageBox(MSG_BOX_BASIC_STYLE, TacticalStr[FREE_MEDICAL_PROMPT], GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4213 break;
4214 case NPC_ACTION_MEDICAL_REQUESTOR:
4215 iTemp = (INT32) CalcMedicalCost( ubProfileID );
4216 if ( giHospitalRefund > iTemp )
4217 {
4218 iTemp = 10;
4219 }
4220 else
4221 {
4222 iTemp -= giHospitalRefund;
4223 }
4224 zTemp2 = ST::format("${}", iTemp);
4225 zTemp = st_format_printf(TacticalStr[ PAY_MONEY_PROMPT ], zTemp2);
4226
4227 DoMessageBox(MSG_BOX_BASIC_STYLE, zTemp, GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4228 break;
4229 case NPC_ACTION_BUY_VEHICLE_REQUESTOR:
4230 zTemp = st_format_printf(TacticalStr[PAY_MONEY_PROMPT], "$10000");
4231 DoMessageBox(MSG_BOX_BASIC_STYLE, zTemp, GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4232 break;
4233 case NPC_ACTION_TRIGGER_MARRY_DARYL_PROMPT:
4234 DoMessageBox(MSG_BOX_BASIC_STYLE, TacticalStr[MARRY_DARYL_PROMPT], GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4235 break;
4236 case NPC_ACTION_KROTT_REQUESTOR:
4237 DoMessageBox(MSG_BOX_BASIC_STYLE, TacticalStr[SPARE_KROTT_PROMPT], GAME_SCREEN, MSG_BOX_FLAG_YESNO, DialogueMessageBoxCallBack, NULL);
4238 break;
4239 default:
4240 break;
4241 }
4242 }
4243
4244
DialogueMessageBoxCallBack(MessageBoxReturnValue const ubExitValue)4245 static void DialogueMessageBoxCallBack(MessageBoxReturnValue const ubExitValue)
4246 {
4247 UINT8 ubProfile;
4248 SOLDIERTYPE *pSoldier;
4249
4250 ubProfile = gpDestSoldier->ubProfile;
4251
4252 switch( gusDialogueMessageBoxType )
4253 {
4254 case NPC_ACTION_ASK_ABOUT_ESCORTING_EPC:
4255 if ( ubExitValue == MSG_BOX_RETURN_YES )
4256 {
4257 // Delete menu
4258 DeleteTalkingMenu( );
4259
4260 if ( PlayerTeamFull() )
4261 {
4262 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ CANNOT_RECRUIT_TEAM_FULL ] );
4263 }
4264 else
4265 {
4266 if ( ubProfile == JOHN )
4267 {
4268 // Mary might be alive, and if so we need to ensure two places
4269 pSoldier = FindSoldierByProfileID(MARY);
4270 if (pSoldier != NULL &&
4271 NumberOfMercsOnPlayerTeam() > gTacticalStatus.Team[OUR_TEAM].bLastID - 3u)
4272 {
4273 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ CANNOT_RECRUIT_TEAM_FULL ] );
4274 break;
4275 }
4276 }
4277
4278 RecruitEPC( ubProfile );
4279
4280 // Get soldier pointer
4281 pSoldier = FindSoldierByProfileID(ubProfile);
4282 if ( !pSoldier )
4283 {
4284 return;
4285 }
4286
4287 // OK, update UI with message that we have been recruited
4288 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[ NOW_BING_ESCORTED_STR ], gMercProfiles[ ubProfile ].zNickname, ( pSoldier->bAssignment + 1 )) );
4289
4290 // Change Squads....
4291 SetCurrentSquad( pSoldier ->bAssignment, FALSE );
4292
4293 HandleStuffForNPCEscorted( ubProfile );
4294 }
4295 }
4296 break;
4297 case NPC_ACTION_ASK_ABOUT_PAYING_RPC:
4298 case NPC_ACTION_ASK_ABOUT_PAYING_RPC_WITH_DAILY_SALARY:
4299 if ( ubExitValue == MSG_BOX_RETURN_YES )
4300 {
4301 TriggerNPCRecord( ubProfile, 1 );
4302 }
4303 else
4304 {
4305 TriggerNPCRecord( ubProfile, 0 );
4306 }
4307 break;
4308 case NPC_ACTION_REDUCE_CONRAD_SALARY_CONDITIONS:
4309 if ( ubExitValue == MSG_BOX_RETURN_YES )
4310 {
4311 TriggerNPCRecord( ubProfile, 1 );
4312 }
4313 else
4314 {
4315 TriggerNPCRecord( ubProfile, 2 );
4316 }
4317 break;
4318
4319 case NPC_ACTION_DARREN_REQUESTOR:
4320 if ( ubExitValue == MSG_BOX_RETURN_YES )
4321 {
4322 TriggerNPCRecord( ubProfile, 13 );
4323 }
4324 else
4325 {
4326 TriggerNPCRecord( ubProfile, 12 );
4327 }
4328 break;
4329 case NPC_ACTION_FIGHT_AGAIN_REQUESTOR:
4330 if ( ubExitValue == MSG_BOX_RETURN_YES )
4331 {
4332 TriggerNPCRecord( ubProfile, 30 );
4333 }
4334 else
4335 {
4336 TriggerNPCRecord( ubProfile, 12 );
4337 }
4338 break;
4339 case NPC_ACTION_BUY_LEATHER_KEVLAR_VEST:
4340 if ( ubExitValue == MSG_BOX_RETURN_YES )
4341 {
4342 TriggerNPCRecord( ubProfile, 27 );
4343 }
4344 else
4345 {
4346 TriggerNPCRecord( ubProfile, 28 );
4347 }
4348 break;
4349 case NPC_ACTION_PROMPT_PLAYER_TO_LIE:
4350 if ( ubExitValue == MSG_BOX_RETURN_YES )
4351 {
4352 TriggerNPCRecord( ubProfile, (UINT8)( ubRecordThatTriggeredLiePrompt + 1 ) );
4353 }
4354 else if ( ubExitValue == MSG_BOX_RETURN_NO )
4355 {
4356 TriggerNPCRecord( ubProfile, (UINT8)( ubRecordThatTriggeredLiePrompt + 2 ) );
4357 }
4358 else
4359 {
4360 // He tried to lie.....
4361 // Find the best conscious merc with a chance....
4362 SOLDIERTYPE* pLier = NULL;
4363 FOR_EACH_IN_TEAM(s, OUR_TEAM)
4364 {
4365 if (s->bInSector &&
4366 s->bLife >= OKLIFE &&
4367 s->bBreath >= OKBREATH &&
4368 (!pLier || (EffectiveWisdom(s) + EffectiveLeadership(s) > EffectiveWisdom(pLier) + EffectiveLeadership(s))))
4369 {
4370 pLier = s;
4371 }
4372 }
4373
4374 if ( pLier && SkillCheck( pLier, LIE_TO_QUEEN_CHECK, 0 ) >= 0 )
4375 {
4376 // SUCCESS..
4377 TriggerNPCRecord( ubProfile, (UINT8)( ubRecordThatTriggeredLiePrompt + 4 ) );
4378 }
4379 else
4380 {
4381 // NAUGHY BOY
4382 TriggerNPCRecord( ubProfile, (UINT8)( ubRecordThatTriggeredLiePrompt + 3 ) );
4383 }
4384 }
4385 break;
4386 case NPC_ACTION_MEDICAL_REQUESTOR:
4387 case NPC_ACTION_MEDICAL_REQUESTOR_2:
4388 if (ubProfile == VINCE)
4389 {
4390 if ( gusDialogueMessageBoxType == NPC_ACTION_MEDICAL_REQUESTOR )
4391 {
4392 if ( ubExitValue == MSG_BOX_RETURN_YES )
4393 {
4394 // give the guy the cash
4395 TriggerNPCRecord( VINCE, 23 );
4396 }
4397 else
4398 {
4399 // no cash, no help
4400 TriggerNPCRecord( VINCE, 24 );
4401 }
4402 }
4403 else
4404 {
4405 if ( ubExitValue == MSG_BOX_RETURN_YES )
4406 {
4407 //HandleNPCDoAction( VINCE, NPC_ACTION_CHECK_DOCTORING_MONEY_GIVEN, 0 );
4408 if ( CheckFact( FACT_WOUNDED_MERCS_NEARBY, VINCE) )
4409 {
4410 TriggerNPCRecord( VINCE, 26 );
4411 }
4412 else if( CheckFact( FACT_ONE_WOUNDED_MERC_NEARBY, VINCE ) )
4413 {
4414 TriggerNPCRecord( VINCE, 25 );
4415 }
4416 giHospitalTempBalance = 0;
4417 }
4418 else
4419 {
4420 // just don't want the help
4421 TriggerNPCRecord( VINCE, 34 );
4422 }
4423 }
4424
4425 DeleteTalkingMenu();
4426 }
4427 else // Steven Willis
4428 {
4429 if( gusDialogueMessageBoxType == NPC_ACTION_MEDICAL_REQUESTOR )
4430 {
4431 if ( ubExitValue == MSG_BOX_RETURN_YES )
4432 {
4433 // give the guy the cash
4434 TriggerNPCRecord( STEVE, 23 );
4435 }
4436 else
4437 {
4438 // no cahs, no help
4439 TriggerNPCRecord( STEVE, 24 );
4440 }
4441 }
4442 else
4443 {
4444 if ( ubExitValue == MSG_BOX_RETURN_YES )
4445 {
4446 //HandleNPCDoAction( STEVE, NPC_ACTION_CHECK_DOCTORING_MONEY_GIVEN, 0 );
4447 if ( CheckFact( FACT_WOUNDED_MERCS_NEARBY, STEVE) )
4448 {
4449 TriggerNPCRecord( STEVE, 26 );
4450 }
4451 else if( CheckFact( FACT_ONE_WOUNDED_MERC_NEARBY, STEVE ) )
4452 {
4453 TriggerNPCRecord( STEVE, 25 );
4454 }
4455 gMercProfiles[ VINCE ].iBalance = 0;
4456 }
4457 else
4458 {
4459 // just don't want the help
4460 TriggerNPCRecord( STEVE, 30 );
4461 }
4462 }
4463
4464 DeleteTalkingMenu();
4465 }
4466 break;
4467 case NPC_ACTION_BUY_VEHICLE_REQUESTOR:
4468 if ( ubExitValue == MSG_BOX_RETURN_YES )
4469 {
4470 TriggerNPCRecord( GERARD, 9 ); // Dave Gerard
4471 }
4472 else
4473 {
4474 TriggerNPCRecord( GERARD, 8 );
4475 }
4476 break;
4477 case NPC_ACTION_KROTT_REQUESTOR:
4478 if ( ubExitValue == MSG_BOX_RETURN_YES )
4479 {
4480 TriggerNPCRecord( SERGEANT, 7 );
4481 }
4482 else
4483 {
4484 TriggerNPCRecord( SERGEANT, 6 );
4485 }
4486 break;
4487 case NPC_ACTION_TRIGGER_MARRY_DARYL_PROMPT:
4488 gMercProfiles[ gpSrcSoldier->ubProfile ].ubMiscFlags2 |= PROFILE_MISC_FLAG2_ASKED_BY_HICKS;
4489 if ( ubExitValue == MSG_BOX_RETURN_YES )
4490 {
4491 gMercProfiles[ DARYL ].bNPCData = (INT8) gpSrcSoldier->ubProfile;
4492
4493 // create key for Daryl to give to player
4494 pSoldier = FindSoldierByProfileID(DARYL);
4495 if ( pSoldier )
4496 {
4497 OBJECTTYPE Key;
4498
4499 CreateKeyObject( &Key, 1, 38 );
4500 AutoPlaceObject( pSoldier, &Key, FALSE );
4501 }
4502 TriggerNPCRecord( DARYL, 11 );
4503 }
4504 else
4505 {
4506 TriggerNPCRecord( DARYL, 12 );
4507 }
4508 break;
4509 default:
4510 break;
4511 }
4512 }
4513
4514
4515 static void DoneFadeInActionBasement(void);
4516
4517
DoneFadeOutActionBasement(void)4518 static void DoneFadeOutActionBasement(void)
4519 {
4520 // OK, insertion data found, enter sector!
4521 SetCurrentWorldSector( 10, 1, 1 );
4522
4523 // OK, once down here, adjust the above map with crate info....
4524 gfTacticalTraversal = FALSE;
4525 gpTacticalTraversalGroup = NULL;
4526 gpTacticalTraversalChosenSoldier = NULL;
4527
4528 // Remove crate
4529 RemoveStructFromUnLoadedMapTempFile( 7887, SECONDOSTRUCT1, 10, 1, 0 );
4530 // Add crate
4531 AddStructToUnLoadedMapTempFile( 8207, SECONDOSTRUCT1, 10, 1, 0 );
4532
4533 // Add trapdoor
4534 AddStructToUnLoadedMapTempFile( 7887, DEBRIS2MISC1, 10, 1, 0 );
4535
4536
4537 gFadeInDoneCallback = DoneFadeInActionBasement;
4538
4539 FadeInGameScreen( );
4540
4541 }
4542
DoneFadeInActionBasement(void)4543 static void DoneFadeInActionBasement(void)
4544 {
4545 // Start conversation, etc
4546
4547 // Look for someone to talk to
4548 CFOR_EACH_IN_TEAM(s, OUR_TEAM)
4549 {
4550 // Are we in this sector, On the current squad?
4551 if (s->bLife >= OKLIFE && s->bInSector && s->bAssignment == CurrentSquad())
4552 {
4553 break;
4554 }
4555 }
4556
4557 const SOLDIERTYPE* const pNPCSoldier = FindSoldierByProfileID(CARLOS);
4558 if ( !pNPCSoldier )
4559 {
4560 return;
4561 }
4562
4563 TriggerNPCRecordImmediately( pNPCSoldier->ubProfile, 1 );
4564 }
4565
4566
4567 static void DoneFadeInActionLeaveBasement(void);
4568
4569
DoneFadeOutActionLeaveBasement(void)4570 static void DoneFadeOutActionLeaveBasement(void)
4571 {
4572 // OK, insertion data found, enter sector!
4573 SetCurrentWorldSector( 10, 1, 0 );
4574
4575 gfTacticalTraversal = FALSE;
4576 gpTacticalTraversalGroup = NULL;
4577 gpTacticalTraversalChosenSoldier = NULL;
4578
4579 gFadeInDoneCallback = DoneFadeInActionLeaveBasement;
4580
4581 FadeInGameScreen( );
4582
4583 }
4584
4585
DoneFadeInActionLeaveBasement(void)4586 static void DoneFadeInActionLeaveBasement(void)
4587 {
4588 // Start conversation, etc
4589 }
4590
4591
NPCOpenThing(SOLDIERTYPE * pSoldier,BOOLEAN fDoor)4592 static BOOLEAN NPCOpenThing(SOLDIERTYPE* pSoldier, BOOLEAN fDoor)
4593 {
4594 STRUCTURE *pStructure;
4595 INT16 sStructGridNo;
4596 INT16 sActionGridNo;
4597 UINT8 ubDirection;
4598 INT16 sGridNo;
4599 DOOR *pDoor;
4600
4601 // Find closest door and get struct data for it!
4602 if ( fDoor )
4603 {
4604 sStructGridNo = FindClosestDoor( pSoldier );
4605
4606 if ( sStructGridNo == NOWHERE )
4607 {
4608 return( FALSE );
4609 }
4610
4611 pStructure = FindStructure( sStructGridNo, STRUCTURE_ANYDOOR );
4612 }
4613 else
4614 {
4615 // for Armand, hard code to tile 6968
4616 if ( pSoldier->ubProfile == ARMAND )
4617 {
4618 sStructGridNo = 6968;
4619 }
4620 else
4621 {
4622 sStructGridNo = FindNearestOpenableNonDoor( pSoldier->sGridNo );
4623 }
4624
4625 if ( sStructGridNo == NOWHERE )
4626 {
4627 return( FALSE );
4628 }
4629
4630 pStructure = FindStructure( sStructGridNo, STRUCTURE_OPENABLE );
4631 }
4632
4633 if ( pStructure == NULL )
4634 {
4635 return( FALSE );
4636 }
4637
4638 if ( pStructure->fFlags & STRUCTURE_OPEN )
4639 {
4640 // it's already open!
4641 TriggerNPCWithGivenApproach(pSoldier->ubProfile, APPROACH_DONE_OPEN_STRUCTURE);
4642 return( FALSE );
4643 }
4644
4645 // anything that an NPC opens this way should become unlocked!
4646
4647 pDoor = FindDoorInfoAtGridNo( sStructGridNo );
4648 if ( pDoor )
4649 {
4650 pDoor->fLocked = FALSE;
4651 }
4652
4653 sActionGridNo = FindAdjacentGridEx( pSoldier, sStructGridNo, &ubDirection, NULL, FALSE, TRUE );
4654 if ( sActionGridNo == -1 )
4655 {
4656 return( FALSE );
4657 }
4658
4659 // Set dest gridno
4660 sGridNo = sActionGridNo;
4661
4662 StartInteractiveObject(sStructGridNo, *pStructure, *pSoldier, ubDirection);
4663
4664 // check if we are at this location
4665 if ( pSoldier->sGridNo == sGridNo )
4666 {
4667 InteractWithOpenableStruct(*pSoldier, *pStructure, ubDirection);
4668 }
4669 else
4670 {
4671 SendGetNewSoldierPathEvent(pSoldier, sGridNo);
4672 }
4673
4674 pSoldier->bAction = AI_ACTION_PENDING_ACTION;
4675
4676 return( TRUE );
4677
4678 }
4679
4680
TextRegionClickCallback(MOUSE_REGION * pRegion,INT32 iReason)4681 static void TextRegionClickCallback(MOUSE_REGION* pRegion, INT32 iReason)
4682 {
4683 static BOOLEAN fLButtonDown = FALSE;
4684
4685 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_DWN )
4686 {
4687 fLButtonDown = TRUE;
4688 }
4689
4690 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP && fLButtonDown )
4691 {
4692 InternalShutupaYoFace(gTalkPanel.face, FALSE);
4693 }
4694 else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
4695 {
4696 fLButtonDown = FALSE;
4697 }
4698 }
4699
4700
CarmenLeavesSectorCallback(void)4701 static void CarmenLeavesSectorCallback(void)
4702 {
4703 if (gWorldSectorX == 13 && gWorldSectorY == MAP_ROW_C && gbWorldSectorZ == 0)
4704 {
4705 TriggerNPCRecord(CARMEN, 34);
4706 }
4707 else if (gWorldSectorX == 9 && gWorldSectorY == MAP_ROW_G && gbWorldSectorZ == 0)
4708 {
4709 TriggerNPCRecord(CARMEN, 35);
4710 }
4711 else if (gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_C && gbWorldSectorZ == 0)
4712 {
4713 TriggerNPCRecord(CARMEN, 36);
4714 }
4715
4716 }
4717