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