1 #include "NPC.h"
2 #include "AI.h"
3 #include "Animation_Control.h"
4 #include "Arms_Dealer.h"
5 #include "Arms_Dealer_Init.h"
6 #include "Buffer.h"
7 #include "ContentManager.h"
8 #include "Dialogue_Control.h"
9 #include "Directories.h"
10 #include "Faces.h"
11 #include "FileMan.h"
12 #include "Font_Control.h"
13 #include "Game_Clock.h"
14 #include "GameInstance.h"
15 #include "GameRes.h"
16 #include "Interface_Dialogue.h"
17 #include "Interface_Items.h"
18 #include "Isometric_Utils.h"
19 #include "Items.h"
20 #include "LoadSaveData.h"
21 #include "Meanwhile.h"
22 #include "MercProfile.h"
23 #include "Message.h"
24 #include "OppList.h"
25 #include "Overhead.h"
26 #include "Quests.h"
27 #include "QuestText.h"
28 #include "Render_Fun.h"
29 #include "Scheduling.h"
30 #include "SkillCheck.h"
31 #include "Soldier_Add.h"
32 #include "Soldier_Macros.h"
33 #include "Soldier_Tile.h"
34 #include "Strategic_Town_Loyalty.h"
35 #include "Tactical_Save.h"
36 #include "Text.h"
37 #include "Timer_Control.h"
38 #include "WeaponModels.h"
39 #include <string_theory/format>
40 #include <string_theory/string>
41 
42 #define NUM_NPC_QUOTE_RECORDS  50
43 #define NUM_CIVQUOTE_SECTORS   20
44 #define MINERS_CIV_QUOTE_INDEX 16
45 
46 static const INT16 gsCivQuoteSector[NUM_CIVQUOTE_SECTORS][2] =
47 {
48 	{  2, MAP_ROW_A },
49 	{  2, MAP_ROW_B },
50 	{ 13, MAP_ROW_B },
51 	{ 13, MAP_ROW_C },
52 	{ 13, MAP_ROW_D },
53 	{  8, MAP_ROW_F },
54 	{  9, MAP_ROW_F },
55 	{  8, MAP_ROW_G },
56 	{  9, MAP_ROW_G },
57 	{  1, MAP_ROW_H },
58 
59 	{  2, MAP_ROW_H },
60 	{  3, MAP_ROW_H },
61 	{  8, MAP_ROW_H },
62 	{ 13, MAP_ROW_H },
63 	{ 14, MAP_ROW_I },
64 	{ 11, MAP_ROW_L },
65 	{ 12, MAP_ROW_L },
66 	{  0, 0         }, // THIS ONE USED NOW - FOR bSectorZ > 0.....
67 	{  0, 0         },
68 	{  0, 0         },
69 };
70 
71 #define NO_FACT                 (MAX_FACTS - 1)
72 #define NO_QUEST                255
73 #define QUEST_NOT_STARTED_NUM   100
74 #define QUEST_DONE_NUM          200
75 #define NO_QUOTE                255
76 #define IRRELEVANT              255
77 #define MUST_BE_NEW_DAY         254
78 #define NO_MOVE                 65535
79 #define INITIATING_FACTOR       30
80 
81 #define TURN_FLAG_ON(a, b)  ((a) |= (b))
82 #define TURN_FLAG_OFF(a, b) ((a) &= ~(b))
83 #define CHECK_FLAG(a, b)    ((a) & (b))
84 
85 #define QUOTE_FLAG_SAID               0x0001
86 #define QUOTE_FLAG_ERASE_ONCE_SAID    0x0002
87 #define QUOTE_FLAG_SAY_ONCE_PER_CONVO 0x0004
88 
89 #define TURN_UI_OFF         65000
90 #define TURN_UI_ON          65001
91 #define SPECIAL_TURN_UI_OFF 65002
92 #define SPECIAL_TURN_UI_ON  65003
93 
94 #define LARGE_AMOUNT_MONEY 1000
95 
96 #define ACCEPT_ANY_ITEM 1000
97 #define ANY_RIFLE       1001
98 
99 #define NUM_REAL_APPROACHES APPROACH_RECRUIT
100 
101 
102 enum StandardQuoteIDs
103 {
104 	QUOTE_INTRO = 0,
105 	QUOTE_SUBS_INTRO,
106 	QUOTE_FRIENDLY_DEFAULT1,
107 	QUOTE_FRIENDLY_DEFAULT2,
108 	QUOTE_GIVEITEM_NO,
109 	QUOTE_DIRECT_DEFAULT,
110 	QUOTE_THREATEN_DEFAULT,
111 	QUOTE_RECRUIT_NO,
112 	QUOTE_BYE,
113 	QUOTE_GETLOST
114 };
115 
116 
117 struct NPCQuoteInfo
118 {
119 	UINT32  ubIdentifier;
120 
121 	UINT16  fFlags;
122 
123 	// conditions
124 	union
125 	{
126 		INT16 sRequiredItem;      // item NPC must have to say quote
127 		INT16 sRequiredGridno;    // location for NPC req'd to say quote
128 	};
129 	UINT16  usFactMustBeTrue;   // ...before saying quote
130 	UINT16  usFactMustBeFalse;  // ...before saying quote
131 	UINT8   ubQuest;            // quest must be current to say quote
132 	UINT8   ubFirstDay;         // first day quote can be said
133 	UINT8   ubLastDay;          // last day quote can be said
134 	UINT8   ubApproachRequired; // must use this approach to generate quote
135 	UINT8   ubOpinionRequired;  // opinion needed for this quote
136 
137 	// quote to say (if any)
138 	UINT8   ubQuoteNum;         // this is the quote to say
139 	UINT8   ubNumQuotes;        // total # of quotes to say
140 
141 	// actions
142 	UINT8   ubStartQuest;
143 	UINT8   ubEndQuest;
144 	UINT8   ubTriggerNPC;
145 	UINT8   ubTriggerNPCRec;
146 	UINT16  usSetFactTrue;
147 	UINT16  usGiftItem;         // item NPC gives to merc after saying quote
148 	UINT16  usGoToGridno;
149 	INT16   sActionData;        // special action value
150 };
151 
152 static NPCQuoteInfo* gpNPCQuoteInfoArray[NUM_PROFILES];
153 static NPCQuoteInfo* gpBackupNPCQuoteInfoArray[NUM_PROFILES];
154 static NPCQuoteInfo* gpCivQuoteInfoArray[NUM_CIVQUOTE_SECTORS];
155 
156 // Warning: cheap hack approaching
157 static BOOLEAN gfTriedToLoadQuoteInfoArray[NUM_PROFILES];
158 
159 INT8 const gbFirstApproachFlags[] = { 0x01, 0x02, 0x04, 0x08 };
160 
161 
162 static UINT8 const gubAlternateNPCFileNumsForQueenMeanwhiles[]  = { 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176 };
163 static UINT8 const gubAlternateNPCFileNumsForElliotMeanwhiles[] = { 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196 };
164 
ExtractNPCQuoteInfoArrayFromFile(HWFILE const f)165 static NPCQuoteInfo* ExtractNPCQuoteInfoArrayFromFile(HWFILE const f)
166 {
167 	SGP::Buffer<NPCQuoteInfo> buf(NUM_NPC_QUOTE_RECORDS);
168 	for (NPCQuoteInfo* i = buf; i != buf + NUM_NPC_QUOTE_RECORDS; ++i)
169 	{
170 		BYTE data[32];
171 		FileRead(f, data, sizeof(data));
172 
173 		DataReader d{data};
174 		if(isRussianVersion())
175 		{
176 			EXTR_U32( d, i->ubIdentifier);
177 		}
178 		else
179 		{
180 			i->ubIdentifier = 0;
181 		}
182 		EXTR_U16( d, i->fFlags)
183 		EXTR_I16( d, i->sRequiredItem)
184 		EXTR_U16( d, i->usFactMustBeTrue)
185 		EXTR_U16( d, i->usFactMustBeFalse)
186 		EXTR_U8(  d, i->ubQuest)
187 		EXTR_U8(  d, i->ubFirstDay)
188 		EXTR_U8(  d, i->ubLastDay)
189 		EXTR_U8(  d, i->ubApproachRequired)
190 		EXTR_U8(  d, i->ubOpinionRequired)
191 		EXTR_U8(  d, i->ubQuoteNum)
192 		EXTR_U8(  d, i->ubNumQuotes)
193 		EXTR_U8(  d, i->ubStartQuest)
194 		EXTR_U8(  d, i->ubEndQuest)
195 		EXTR_U8(  d, i->ubTriggerNPC)
196 		EXTR_U8(  d, i->ubTriggerNPCRec)
197 		EXTR_SKIP(d, 1)
198 		EXTR_U16( d, i->usSetFactTrue)
199 		EXTR_U16( d, i->usGiftItem)
200 		EXTR_U16( d, i->usGoToGridno)
201 		EXTR_I16( d, i->sActionData)
202 		if(!isRussianVersion())
203 		{
204 			EXTR_SKIP(d, 4);
205 		}
206 		Assert(d.getConsumed() == lengthof(data));
207 	}
208 	return buf.Release();
209 }
210 
211 
ConditionalExtractNPCQuoteInfoArrayFromFile(HWFILE const f,NPCQuoteInfo * & q)212 static void ConditionalExtractNPCQuoteInfoArrayFromFile(HWFILE const f, NPCQuoteInfo*& q)
213 {
214 	UINT8 present;
215 	FileRead(f, &present, sizeof(present));
216 	FreeNullArray(q);
217 	if (!present) return;
218 	q = ExtractNPCQuoteInfoArrayFromFile(f);
219 }
220 
221 
ConditionalInjectNPCQuoteInfoArrayIntoFile(HWFILE const f,NPCQuoteInfo const * const q)222 static void ConditionalInjectNPCQuoteInfoArrayIntoFile(HWFILE const f, NPCQuoteInfo const* const q)
223 {
224 	if (!q)
225 	{
226 		static UINT8 const zero = 0;
227 		FileWrite(f, &zero, sizeof(zero));
228 		return;
229 	}
230 
231 	static UINT8 const one = 1;
232 	FileWrite(f, &one, sizeof(one));
233 
234 	for (NPCQuoteInfo const* i = q; i != q + NUM_NPC_QUOTE_RECORDS; ++i)
235 	{
236 		BYTE  data[32];
237 		DataWriter d{data};
238 		if(isRussianVersion())
239 		{
240 			INJ_U32( d, i->ubIdentifier);
241 		}
242 		INJ_U16( d, i->fFlags)
243 		INJ_I16( d, i->sRequiredItem)
244 		INJ_U16( d, i->usFactMustBeTrue)
245 		INJ_U16( d, i->usFactMustBeFalse)
246 		INJ_U8(  d, i->ubQuest)
247 		INJ_U8(  d, i->ubFirstDay)
248 		INJ_U8(  d, i->ubLastDay)
249 		INJ_U8(  d, i->ubApproachRequired)
250 		INJ_U8(  d, i->ubOpinionRequired)
251 		INJ_U8(  d, i->ubQuoteNum)
252 		INJ_U8(  d, i->ubNumQuotes)
253 		INJ_U8(  d, i->ubStartQuest)
254 		INJ_U8(  d, i->ubEndQuest)
255 		INJ_U8(  d, i->ubTriggerNPC)
256 		INJ_U8(  d, i->ubTriggerNPCRec)
257 		INJ_SKIP(d, 1)
258 		INJ_U16( d, i->usSetFactTrue)
259 		INJ_U16( d, i->usGiftItem)
260 		INJ_U16( d, i->usGoToGridno)
261 		INJ_I16( d, i->sActionData)
262 		if(!isRussianVersion())
263 		{
264 			INJ_SKIP(d, 4);
265 		}
266 		Assert(d.getConsumed() == lengthof(data));
267 		FileWrite(f, data, sizeof(data));
268 	}
269 }
270 
271 
272 //
273 // NPC QUOTE LOW LEVEL ROUTINES
274 //
275 
276 
LoadQuoteFile(UINT8 ubNPC)277 static NPCQuoteInfo* LoadQuoteFile(UINT8 ubNPC)
278 try
279 {
280 	ST::string  zFileName;
281 	MercProfile profile(ubNPC);
282 
283 	if ( ubNPC == PETER || ubNPC == ALBERTO || ubNPC == CARLO )
284 	{
285 		// use a copy of Herve's data file instead!
286 		zFileName = ST::format("{}/{03d}.npc", NPCDATADIR, HERVE);
287 	}
288 	else if (profile.isPlayerMerc() || (profile.isRPC() && profile.isRecruited()))
289 	{
290 		zFileName = ST::format("{}/000.npc", NPCDATADIR);
291 	}
292 	else
293 	{
294 		zFileName = ST::format("{}/{03d}.npc", NPCDATADIR, ubNPC);
295 	}
296 
297 	// ATE: Put some stuff i here to use a different NPC file if we are in a meanwhile.....
298 	if ( AreInMeanwhile( ) )
299 	{
300 		// If we are the queen....
301 		if ( ubNPC == QUEEN )
302 		{
303 			zFileName = ST::format("{}/{03d}.npc", NPCDATADIR, gubAlternateNPCFileNumsForQueenMeanwhiles[GetMeanwhileID()]);
304 		}
305 
306 		// If we are elliot....
307 		if ( ubNPC == ELLIOT )
308 		{
309 			zFileName = ST::format("{}/{03d}.npc", NPCDATADIR, gubAlternateNPCFileNumsForElliotMeanwhiles[GetMeanwhileID()]);
310 		}
311 
312 	}
313 
314 	AutoSGPFile f(GCM->openGameResForReading(zFileName));
315 	return ExtractNPCQuoteInfoArrayFromFile(f);
316 }
317 catch (const std::exception& e)
318 {
319 	STLOGE("caught exception: {}", e.what());
320 	return 0;
321 }
322 catch (...) { return 0; }
323 
324 
RevertToOriginalQuoteFile(UINT8 ubNPC)325 static void RevertToOriginalQuoteFile(UINT8 ubNPC)
326 {
327 	if ( gpBackupNPCQuoteInfoArray[ ubNPC ] && gpNPCQuoteInfoArray[ubNPC] )
328 	{
329 		delete[] gpNPCQuoteInfoArray[ubNPC];
330 		gpNPCQuoteInfoArray[ubNPC] = gpBackupNPCQuoteInfoArray[ubNPC];
331 		gpBackupNPCQuoteInfoArray[ubNPC] = NULL;
332 	}
333 }
334 
335 
BackupOriginalQuoteFile(UINT8 ubNPC)336 static void BackupOriginalQuoteFile(UINT8 ubNPC)
337 {
338 	gpBackupNPCQuoteInfoArray[ ubNPC ] = gpNPCQuoteInfoArray[ ubNPC ];
339 	gpNPCQuoteInfoArray[ubNPC] = NULL;
340 }
341 
342 
EnsureQuoteFileLoaded(UINT8 const ubNPC)343 static NPCQuoteInfo* EnsureQuoteFileLoaded(UINT8 const ubNPC)
344 {
345 	if (ubNPC == ROBOT) return 0;
346 
347 	NPCQuoteInfo*& q         = gpNPCQuoteInfoArray[ubNPC];
348 	bool           load_file = !q;
349 
350 	if (MercProfile(ubNPC).isRPC())
351 	{
352 		if (GetProfile(ubNPC).ubMiscFlags & PROFILE_MISC_FLAG_RECRUITED)
353 		{ // recruited
354 			if (!gpBackupNPCQuoteInfoArray[ubNPC])
355 			{
356 				// no backup stored of current script, so need to backup
357 				load_file = true;
358 				// set pointer to back up script!
359 				BackupOriginalQuoteFile(ubNPC);
360 			}
361 			// else have backup, are recruited, nothing special
362 		}
363 		else
364 		{ // not recruited
365 			if (gpBackupNPCQuoteInfoArray[ubNPC])
366 			{
367 				// backup stored, restore backup
368 				RevertToOriginalQuoteFile(ubNPC);
369 			}
370 			// else are no backup, nothing special
371 		}
372 	}
373 
374 	if (load_file)
375 	{
376 		q = LoadQuoteFile(ubNPC);
377 		if (!q)
378 		{
379 			if (!gfTriedToLoadQuoteInfoArray[ubNPC]) // don't report the error a second time
380 			{
381 				SLOGE("NPC needs NPC file: %d.", ubNPC );
382 				gfTriedToLoadQuoteInfoArray[ubNPC] = TRUE;
383 			}
384 			// error message at this point!
385 		}
386 	}
387 
388 	return q;
389 }
390 
391 
ReloadQuoteFile(UINT8 const ubNPC)392 bool ReloadQuoteFile(UINT8 const ubNPC)
393 {
394 	FreeNullArray(gpNPCQuoteInfoArray[ubNPC]);
395 	FreeNullArray(gpBackupNPCQuoteInfoArray[ubNPC]);
396 	return EnsureQuoteFileLoaded(ubNPC);
397 }
398 
399 
ReloadQuoteFileIfLoaded(UINT8 const ubNPC)400 static bool ReloadQuoteFileIfLoaded(UINT8 const ubNPC)
401 {
402 	NPCQuoteInfo*& q = gpNPCQuoteInfoArray[ubNPC];
403 	if (!q) return TRUE;
404 	FreeNullArray(q);
405 	return EnsureQuoteFileLoaded(ubNPC);
406 }
407 
408 
RefreshNPCScriptRecord(UINT8 const ubNPC,UINT8 const record)409 static void RefreshNPCScriptRecord(UINT8 const ubNPC, UINT8 const record)
410 {
411 	if (ubNPC == NO_PROFILE)
412 	{
413 		// loop through all PCs, and refresh their copy of this record
414 		for (const MercProfile* p : GCM->listMercProfiles())
415 		{
416 			ProfileID ubMercID = p->getID();
417 			if (p->isPlayerMerc() ||
418 				(p->isRPC() && p->isRecruited() && gpBackupNPCQuoteInfoArray[ubMercID]))
419 			{
420 				RefreshNPCScriptRecord(ubMercID, record);
421 			}
422 		}
423 		return;
424 	}
425 
426 	NPCQuoteInfo* const quotes = gpNPCQuoteInfoArray[ubNPC];
427 	if (!quotes) return;
428 
429 	NPCQuoteInfo& q = quotes[record];
430 	// already used? so we don't have to refresh!
431 	if (CHECK_FLAG(q.fFlags, QUOTE_FLAG_SAID)) return;
432 
433 	SGP::Buffer<NPCQuoteInfo> new_quotes(LoadQuoteFile(ubNPC));
434 	if (!new_quotes) return;
435 	q = new_quotes[record];
436 }
437 
438 
439 //
440 // CIV QUOTE LOW LEVEL ROUTINES
441 //
442 
443 
LoadCivQuoteFile(UINT8 const idx)444 static NPCQuoteInfo* LoadCivQuoteFile(UINT8 const idx)
445 {
446 	char const* filename;
447 	char        buf[255];
448 	if (idx == MINERS_CIV_QUOTE_INDEX)
449 	{
450 		filename = NPCDATADIR "/miners.npc";
451 	}
452 	else
453 	{
454 		sprintf(buf, NPCDATADIR "/%c%d.npc", 'A' + gsCivQuoteSector[idx][1] - 1, gsCivQuoteSector[idx][0]);
455 		filename = buf;
456 	}
457 	AutoSGPFile f(GCM->openGameResForReading(filename));
458 	return ExtractNPCQuoteInfoArrayFromFile(f);
459 }
460 
461 
EnsureCivQuoteFileLoaded(UINT8 const idx)462 static NPCQuoteInfo* EnsureCivQuoteFileLoaded(UINT8 const idx)
463 try
464 {
465 	NPCQuoteInfo*& q = gpCivQuoteInfoArray[idx];
466 	if (!q) q = LoadCivQuoteFile(idx);
467 	return q;
468 }
469 catch (...) { return 0; }
470 
471 
ReloadCivQuoteFileIfLoaded(UINT8 const idx)472 static bool ReloadCivQuoteFileIfLoaded(UINT8 const idx)
473 try
474 {
475 	NPCQuoteInfo*& q = gpCivQuoteInfoArray[idx];
476 	if (!q) return true;
477 	FreeNullArray(q);
478 	q = LoadCivQuoteFile(idx);
479 	return true;
480 }
481 catch (...) { return false; }
482 
483 
ShutdownNPCQuotes()484 void ShutdownNPCQuotes()
485 {
486 	FOR_EACH(NPCQuoteInfo*, i, gpNPCQuoteInfoArray)       FreeNullArray(*i);
487 	FOR_EACH(NPCQuoteInfo*, i, gpBackupNPCQuoteInfoArray) FreeNullArray(*i);
488 	FOR_EACH(NPCQuoteInfo*, i, gpCivQuoteInfoArray)       FreeNullArray(*i);
489 }
490 
491 
492 //
493 // GENERAL LOW LEVEL ROUTINES
494 //
495 
ReloadAllQuoteFiles(void)496 void ReloadAllQuoteFiles(void)
497 {
498 	for (const MercProfile* p : GCM->listMercProfiles())
499 	{
500 		if (!p->isNPCorRPC()) continue;
501 
502 		// zap backup if any
503 		FreeNullArray(gpBackupNPCQuoteInfoArray[p->getID()]);
504 		ReloadQuoteFileIfLoaded(p->getID());
505 	}
506 	// reload all civ quote files
507 	for (UINT8 ubLoop = 0; ubLoop < NUM_CIVQUOTE_SECTORS; ubLoop++ )
508 	{
509 		ReloadCivQuoteFileIfLoaded( ubLoop );
510 	}
511 }
512 
513 
514 //
515 // THE REST
516 //
517 
518 
SetQuoteRecordAsUsed(UINT8 ubNPC,UINT8 ubRecord)519 void SetQuoteRecordAsUsed( UINT8 ubNPC, UINT8 ubRecord )
520 {
521 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
522 	if (!quotes) return;
523 	quotes[ubRecord].fFlags |= QUOTE_FLAG_SAID;
524 }
525 
526 
CalcThreateningEffectiveness(UINT8 const ubMerc)527 static INT32 CalcThreateningEffectiveness(UINT8 const ubMerc)
528 {
529 	// effective threat is 1/3 strength, 1/3 weapon deadliness, 1/3 leadership
530 
531 	SOLDIERTYPE* const s = FindSoldierByProfileIDOnPlayerTeam(ubMerc);
532 	if (!s) return 0;
533 
534 	UINT16 const item_idx = s->inv[HANDPOS].usItem;
535 	INT32 deadliness =
536 		GCM->getItem(item_idx)->isWeapon() ? GCM->getWeapon(item_idx)->ubDeadliness :
537 		0;
538 
539 	if (deadliness == 0) deadliness = -30; // penalize!
540 
541 	INT32 const strength = EffectiveStrength(s);
542 	return (EffectiveLeadership(s) + strength + deadliness) / 2;
543 }
544 
545 
CalcDesireToTalk(UINT8 const ubNPC,UINT8 const ubMerc,Approach const bApproach)546 UINT8 CalcDesireToTalk(UINT8 const ubNPC, UINT8 const ubMerc, Approach const bApproach)
547 {
548 	INT32 iWillingness;
549 	INT32	iPersonalVal, iTownVal, iApproachVal;
550 	INT32 iEffectiveLeadership;
551 
552 	MERCPROFILESTRUCT const& pNPCProfile  = GetProfile(ubNPC);
553 	MERCPROFILESTRUCT const& pMercProfile = GetProfile(ubMerc);
554 
555 	iPersonalVal = 50 + pNPCProfile.bMercOpinion[ubMerc];
556 
557 	// ARM: NOTE - for towns which don't use loyalty (San Mona, Estoni, Tixa, Orta )
558 	// loyalty will always remain 0 (this was OKed by Ian)
559 	iTownVal = gTownLoyalty[pNPCProfile.bTown].ubRating;
560 	iTownVal = iTownVal * pNPCProfile.bTownAttachment / 100;
561 
562 	if (bApproach == NPC_INITIATING_CONV || bApproach == APPROACH_GIVINGITEM )
563 	{
564 		iApproachVal = 100;
565 	}
566 	else if ( bApproach == APPROACH_THREATEN )
567 	{
568 		iEffectiveLeadership = CalcThreateningEffectiveness(ubMerc) * pMercProfile.usApproachFactor[bApproach - 1] / 100;
569 		iApproachVal = pNPCProfile.ubApproachVal[bApproach - 1] * iEffectiveLeadership / 50;
570 	}
571 	else
572 	{
573 		iEffectiveLeadership = (INT32)pMercProfile.bLeadership * pMercProfile.usApproachFactor[bApproach - 1] / 100;
574 		iApproachVal = pNPCProfile.ubApproachVal[bApproach - 1] * iEffectiveLeadership / 50;
575 	}
576 	// NB if town attachment is less than 100% then we should make personal value proportionately more important!
577 	if (pNPCProfile.bTownAttachment < 100)
578 	{
579 		iPersonalVal = iPersonalVal * (100 + (100 - pNPCProfile.bTownAttachment) ) / 100;
580 	}
581 	iWillingness = (iPersonalVal / 2 + iTownVal / 2) * iApproachVal	/ 100;
582 
583 	if (bApproach == NPC_INITIATING_CONV)
584 	{
585 		iWillingness -= INITIATING_FACTOR;
586 	}
587 
588 	if (iWillingness < 0)
589 	{
590 		iWillingness = 0;
591 	}
592 
593 	return( (UINT8) iWillingness );
594 }
595 
596 
ApproachedForFirstTime(MERCPROFILESTRUCT & pNPCProfile,Approach const bApproach)597 static void ApproachedForFirstTime(MERCPROFILESTRUCT& pNPCProfile, Approach const bApproach)
598 {
599 	UINT8		ubLoop;
600 	UINT32	uiTemp;
601 
602 	pNPCProfile.bApproached |= gbFirstApproachFlags[bApproach - 1];
603 	for (ubLoop = 1; ubLoop <= NUM_REAL_APPROACHES; ubLoop++)
604 	{
605 		uiTemp = (UINT32) pNPCProfile.ubApproachVal[ubLoop - 1] * (UINT32) pNPCProfile.ubApproachMod[bApproach - 1][ubLoop - 1] / 100;
606 		if (uiTemp > 255)
607 		{
608 			uiTemp = 255;
609 		}
610 		pNPCProfile.ubApproachVal[ubLoop-1] = (UINT8) uiTemp;
611 	}
612 }
613 
614 
615 static UINT8 NPCConsiderQuote(UINT8 ubNPC, UINT8 ubMerc, Approach, UINT8 ubQuoteNum, UINT8 ubTalkDesire, NPCQuoteInfo* pNPCQuoteInfoArray);
616 
617 
NPCConsiderTalking(UINT8 const ubNPC,UINT8 const ubMerc,Approach const approach,UINT8 const record,NPCQuoteInfo * const pNPCQuoteInfoArray,NPCQuoteInfo ** const ppResultQuoteInfo,UINT8 * const pubQuoteNum)618 static UINT8 NPCConsiderTalking(UINT8 const ubNPC, UINT8 const ubMerc, Approach const approach, UINT8 const record, NPCQuoteInfo* const pNPCQuoteInfoArray, NPCQuoteInfo** const ppResultQuoteInfo, UINT8* const pubQuoteNum)
619 {
620 	// This function returns the opinion level required of the "most difficult" quote
621 	// that the NPC is willing to say to the merc.  It can also provide the quote #.
622 	SOLDIERTYPE const* const s = FindSoldierByProfileID(ubNPC);
623 	if (!s) return 0;
624 
625 	if (ppResultQuoteInfo) *ppResultQuoteInfo = 0;
626 	if (pubQuoteNum)       *pubQuoteNum       = 0;
627 
628 	UINT8 talk_desire = 0;
629 	if (approach <= NUM_REAL_APPROACHES)
630 	{
631 		MERCPROFILESTRUCT& p = GetProfile(ubNPC);
632 		// What's our willingness to divulge?
633 		talk_desire = CalcDesireToTalk(ubNPC, ubMerc, approach);
634 		if (approach < NUM_REAL_APPROACHES && !(p.bApproached & gbFirstApproachFlags[approach - 1]))
635 		{
636 			ApproachedForFirstTime(p, approach);
637 		}
638 	}
639 	else if (ubNPC == PABLO && approach == APPROACH_SECTOR_NOT_SAFE) // for Pablo, consider as threaten
640 	{
641 		MERCPROFILESTRUCT& p = GetProfile(ubNPC);
642 		// What's our willingness to divulge?
643 		talk_desire = CalcDesireToTalk(ubNPC, ubMerc, APPROACH_THREATEN);
644 		if (p.bApproached & gbFirstApproachFlags[APPROACH_THREATEN - 1])
645 		{
646 			ApproachedForFirstTime(p, APPROACH_THREATEN);
647 		}
648 	}
649 
650 	UINT8 first_quote_record;
651 	UINT8 last_quote_record;
652 	switch (approach)
653 	{
654 		case TRIGGER_NPC:
655 			first_quote_record = record;
656 			last_quote_record  = record;
657 			break;
658 
659 		default:
660 			first_quote_record = 0;
661 			last_quote_record  = NUM_NPC_QUOTE_RECORDS - 1;
662 			break;
663 	}
664 
665 	bool          fQuoteFound              = false;
666 	UINT8         ubHighestOpinionRequired = 0;
667 	NPCQuoteInfo* pNPCQuoteInfo            = 0;
668 	UINT8         ubQuote                  = 0;
669 	for (UINT8 i = first_quote_record; i <= last_quote_record; ++i)
670 	{
671 		pNPCQuoteInfo = &pNPCQuoteInfoArray[i];
672 
673 		// Check if we have the item / are in right spot
674 		if (pNPCQuoteInfo->sRequiredItem > 0)
675 		{
676 			if (FindObjectInSoldierProfile(GetProfile(ubNPC), pNPCQuoteInfo->sRequiredItem) == NO_SLOT)
677 			{
678 				continue;
679 			}
680 		}
681 		else if (pNPCQuoteInfo->sRequiredGridno < 0)
682 		{
683 			if (s->sGridNo != -pNPCQuoteInfo->sRequiredGridno)
684 			{
685 				continue;
686 			}
687 		}
688 
689 		if (!NPCConsiderQuote(ubNPC, ubMerc, approach, i, talk_desire, pNPCQuoteInfoArray)) continue;
690 
691 		if (approach != NPC_INITIATING_CONV)
692 		{
693 			// we do have a quote to say, and we want to say this one right away!
694 			if (ppResultQuoteInfo) *ppResultQuoteInfo = pNPCQuoteInfo;
695 			if (pubQuoteNum)       *pubQuoteNum       = i;
696 			return pNPCQuoteInfo->ubOpinionRequired;
697 		}
698 
699 		// want to find the quote with the highest required opinion rating that we're willing
700 		// to say
701 		if (pNPCQuoteInfo->ubOpinionRequired > ubHighestOpinionRequired)
702 		{
703 			fQuoteFound              = true;
704 			ubHighestOpinionRequired = pNPCQuoteInfo->ubOpinionRequired;
705 			ubQuote                  = pNPCQuoteInfo->ubQuoteNum;
706 		}
707 	}
708 
709 	// Whew, checked them all.  If we found a quote, return the appropriate values.
710 	if (fQuoteFound)
711 	{
712 		if (ppResultQuoteInfo) *ppResultQuoteInfo = pNPCQuoteInfo;
713 		if (pubQuoteNum)       *pubQuoteNum       = ubQuote;
714 		return ubHighestOpinionRequired;
715 	}
716 	else
717 	{
718 		if (ppResultQuoteInfo) *ppResultQuoteInfo = 0;
719 		if (pubQuoteNum)       *pubQuoteNum       = 0;
720 		return 0;
721 	}
722 }
723 
724 
UseQuote(NPCQuoteInfo * const quotes,NPCQuoteInfo ** const quote,UINT8 * const quote_id,UINT8 const quote_to_use)725 static UINT8 UseQuote(NPCQuoteInfo* const quotes, NPCQuoteInfo** const quote, UINT8* const quote_id, UINT8 const quote_to_use)
726 {
727 	*quote    = &quotes[quote_to_use];
728 	*quote_id = quote_to_use;
729 	return quotes[quote_to_use].ubOpinionRequired;
730 }
731 
732 
733 static UINT8 HandleNPCBeingGivenMoneyByPlayer(UINT8 ubNPC, UINT32 uiMoneyAmount);
734 
735 
NPCConsiderReceivingItemFromMerc(UINT8 const ubNPC,UINT8 const ubMerc,OBJECTTYPE const * const o,NPCQuoteInfo * const pNPCQuoteInfoArray,NPCQuoteInfo ** const ppResultQuoteInfo,UINT8 * const pubQuoteNum)736 static UINT8 NPCConsiderReceivingItemFromMerc(UINT8 const ubNPC, UINT8 const ubMerc, OBJECTTYPE const* const o, NPCQuoteInfo* const pNPCQuoteInfoArray, NPCQuoteInfo** const ppResultQuoteInfo, UINT8* const pubQuoteNum)
737 {
738 	// This function returns the opinion level required of the "most difficult" quote
739 	// that the NPC is willing to say to the merc.  It can also provide the quote #.
740 	*ppResultQuoteInfo = 0;
741 	*pubQuoteNum       = 0;
742 
743 	// don't accept any items when we are the player's enemy
744 	if (CheckFact(FACT_NPC_IS_ENEMY, ubNPC) && ubNPC != JOE) return 0;
745 
746 	// How much do we want to talk with this merc?
747 	UINT8 const ubTalkDesire = CalcDesireToTalk(ubNPC, ubMerc, APPROACH_GIVINGITEM);
748 
749 	UINT16 item_to_consider = o->usItem;
750 	if (GCM->getItem(item_to_consider)->getItemClass() == IC_GUN && item_to_consider != ROCKET_LAUNCHER)
751 	{
752 		UINT8 const weapon_class = GCM->getWeapon(item_to_consider)->ubWeaponClass;
753 		if (weapon_class == RIFLECLASS || weapon_class == MGCLASS)
754 		{
755 			item_to_consider = ANY_RIFLE; // treat all rifles the same
756 		}
757 	}
758 	switch (item_to_consider)
759 	{
760 		case HEAD_2:
761 		case HEAD_3:
762 		//case HEAD_4: // NOT Slay's head; it's different
763 		case HEAD_5:
764 		case HEAD_6:
765 		case HEAD_7:
766 			// all treated the same in the NPC code
767 			item_to_consider = HEAD_2;
768 			break;
769 
770 		case MONEY:
771 		case SILVER:
772 		case GOLD:
773 		{
774 			Fact const fact =
775 				o->uiMoneyAmount < LARGE_AMOUNT_MONEY ? FACT_SMALL_AMOUNT_OF_MONEY :
776 				FACT_LARGE_AMOUNT_OF_MONEY;
777 			SetFactTrue(fact);
778 			item_to_consider = MONEY;
779 			break;
780 		}
781 
782 		case WINE:
783 		case BEER:
784 			item_to_consider = ALCOHOL;
785 			break;
786 
787 		default:
788 			break;
789 	}
790 
791 	if (o->bStatus[0] < 50)
792 	{
793 		SetFactTrue(FACT_ITEM_POOR_CONDITION);
794 	}
795 	else
796 	{
797 		SetFactFalse(FACT_ITEM_POOR_CONDITION);
798 	}
799 
800 	for (UINT8 i = 0; i != NUM_NPC_QUOTE_RECORDS; ++i)
801 	{
802 		NPCQuoteInfo& q = pNPCQuoteInfoArray[i];
803 
804 		// First see if we want that item....
805 		INT16 const req_item = q.sRequiredItem;
806 		if (req_item <= 0) continue;
807 		if (req_item != item_to_consider && req_item != ACCEPT_ANY_ITEM) continue;
808 
809 		// Now see if everyhting else is OK
810 		if (!NPCConsiderQuote(ubNPC, ubMerc, APPROACH_GIVINGITEM, i, ubTalkDesire, pNPCQuoteInfoArray)) continue;
811 
812 		switch (ubNPC)
813 		{
814 			case DARREN:
815 				if (item_to_consider == MONEY && q.sActionData == NPC_ACTION_DARREN_GIVEN_CASH)
816 				{
817 					if (o->uiMoneyAmount < 1000)
818 					{
819 						// refuse, bet too low - record 15
820 						return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 15);
821 					}
822 					else if (o->uiMoneyAmount > 5000)
823 					{
824 						// refuse, bet too high - record 16
825 						return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 16);
826 					}
827 					else
828 					{
829 						// record amount of bet
830 						gMercProfiles[DARREN].iBalance = o->uiMoneyAmount;
831 						SetFactFalse(FACT_DARREN_EXPECTING_MONEY);
832 
833 						// if never fought before, use record 17
834 						// if fought before, today, use record 31
835 						// else use record 18
836 						if (!(gpNPCQuoteInfoArray[DARREN][17].fFlags & QUOTE_FLAG_SAID)) // record 17 not used
837 						{
838 							return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 17);
839 						}
840 						else
841 						{
842 							// find Kingpin, if he's in his house, invoke the script to move him to the bar
843 							UINT8                    id      = 31;
844 							SOLDIERTYPE const* const kingpin = FindSoldierByProfileID(KINGPIN);
845 							if (kingpin)
846 							{
847 								UINT8 const room = GetRoom(kingpin->sGridNo);
848 								// first boxer, bring kingpin over
849 								if (IN_KINGPIN_HOUSE(room)) id = 18;
850 							}
851 
852 							return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, id);
853 						}
854 					}
855 				}
856 				break;
857 
858 			case ANGEL:
859 				if (item_to_consider == MONEY && q.sActionData == NPC_ACTION_ANGEL_GIVEN_CASH)
860 				{
861 					if (o->uiMoneyAmount < GCM->getItem(LEATHER_JACKET_W_KEVLAR)->getPrice())
862 					{ // refuse, bet too low - record 8
863 						return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 8);
864 					}
865 					else if (o->uiMoneyAmount > GCM->getItem(LEATHER_JACKET_W_KEVLAR)->getPrice())
866 					{ // refuse, bet too high - record 9
867 						return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 9);
868 					}
869 					else
870 					{ // accept - record 10
871 						return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 10);
872 					}
873 				}
874 				break;
875 
876 			case MADAME:
877 				if (item_to_consider == MONEY)
878 				{
879 					if (GetProfile(ubMerc).bSex == FEMALE)
880 					{
881 						// say quote about not catering to women!
882 						return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 5);
883 					}
884 
885 					switch (o->uiMoneyAmount)
886 					{
887 						case 100:
888 						case 200: // Carla
889 							if (!CheckFact(FACT_CARLA_AVAILABLE, 0)) goto madame_default;
890 							gMercProfiles[MADAME].bNPCData += (INT8)(o->uiMoneyAmount / 100);
891 							TriggerNPCRecord(MADAME, 16);
892 							break;
893 
894 						case 500:
895 						case 1000: // Cindy
896 							if (!CheckFact(FACT_CINDY_AVAILABLE, 0)) goto madame_default;
897 							gMercProfiles[MADAME].bNPCData += (INT8)(o->uiMoneyAmount / 500);
898 							TriggerNPCRecord(MADAME, 17);
899 							break;
900 
901 						case 300:
902 						case 600: // Bambi
903 							if (!CheckFact(FACT_BAMBI_AVAILABLE, 0)) goto madame_default;
904 							gMercProfiles[MADAME].bNPCData += (INT8)(o->uiMoneyAmount / 300);
905 							TriggerNPCRecord(MADAME, 18);
906 							break;
907 
908 						case 400:
909 						case 800: // Maria
910 							if (gubQuest[QUEST_RESCUE_MARIA] != QUESTINPROGRESS) goto madame_default;
911 							gMercProfiles[MADAME].bNPCData += (INT8)(o->uiMoneyAmount / 400);
912 							TriggerNPCRecord(MADAME, 19);
913 							break;
914 
915 						default:
916 madame_default:
917 							// play quotes 39-42 (plus 44 if quest 22 on) plus 43 if >1 PC
918 							// and return money
919 							return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, 25);
920 					}
921 				}
922 				break;
923 
924 			case JOE:
925 				if (item_to_consider == MONEY && q.sActionData != NPC_ACTION_JOE_GIVEN_CASH)
926 				{
927 					break;
928 				}
929 				goto check_give_money;
930 
931 			case GERARD:
932 				if (item_to_consider == MONEY && q.sActionData != NPC_ACTION_GERARD_GIVEN_CASH)
933 				{
934 					break;
935 				}
936 				goto check_give_money;
937 
938 			case STEVE:
939 			case VINCE:
940 			case WALTER:
941 			case FRANK:
942 check_give_money:
943 				if (item_to_consider == MONEY)
944 				{
945 					if (ubNPC == VINCE || ubNPC == STEVE)
946 					{
947 						if (!CheckFact(FACT_VINCE_EXPECTING_MONEY, ubNPC) &&
948 								gMercProfiles[ubNPC].iBalance < 0             &&
949 								q.sActionData != NPC_ACTION_DONT_ACCEPT_ITEM)
950 						{
951 							MERCPROFILESTRUCT& p = GetProfile(ubNPC);
952 							// increment balance
953 							p.iBalance          += (INT32)o->uiMoneyAmount;
954 							p.uiTotalCostToDate += o->uiMoneyAmount;
955 							if (p.iBalance > 0) p.iBalance = 0;
956 							ScreenMsg(FONT_YELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[BALANCE_OWED_STR], p.zNickname, -p.iBalance));
957 						}
958 						else if (!CheckFact(FACT_VINCE_EXPECTING_MONEY, ubNPC) &&
959 								q.sActionData != NPC_ACTION_DONT_ACCEPT_ITEM)
960 						{
961 							// just accept cash!
962 							if (ubNPC == VINCE)
963 							{
964 								*ppResultQuoteInfo = &pNPCQuoteInfoArray[8];
965 							}
966 							else
967 							{
968 								*ppResultQuoteInfo = &pNPCQuoteInfoArray[7];
969 							}
970 							return (*ppResultQuoteInfo)->ubOpinionRequired;
971 						}
972 						else
973 						{
974 							// handle the player giving NPC some money
975 							UINT8 const quote_id = HandleNPCBeingGivenMoneyByPlayer(ubNPC, o->uiMoneyAmount);
976 							return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, quote_id);
977 						}
978 					}
979 					else
980 					{
981 						// handle the player giving NPC some money
982 						UINT8 const quote_id = HandleNPCBeingGivenMoneyByPlayer(ubNPC, o->uiMoneyAmount);
983 						return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, quote_id);
984 					}
985 				}
986 				break;
987 
988 			case KINGPIN:
989 				if (item_to_consider == MONEY && gubQuest[QUEST_KINGPIN_MONEY] == QUESTINPROGRESS)
990 				{
991 					UINT8 const quote_id = HandleNPCBeingGivenMoneyByPlayer(ubNPC, o->uiMoneyAmount);
992 					return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, quote_id);
993 				}
994 				break;
995 
996 			default:
997 				if (item_to_consider == MONEY && (ubNPC == SKYRIDER || (ubNPC != NO_PROFILE && MercProfile(ubNPC).isRPC())))
998 				{
999 					MERCPROFILESTRUCT& p = GetProfile(ubNPC);
1000 					if (p.iBalance < 0 && q.sActionData != NPC_ACTION_DONT_ACCEPT_ITEM)
1001 					{
1002 						// increment balance
1003 						p.iBalance          += (INT32)o->uiMoneyAmount;
1004 						p.uiTotalCostToDate += o->uiMoneyAmount;
1005 						if (p.iBalance > 0) p.iBalance = 0;
1006 						ScreenMsg(FONT_YELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[BALANCE_OWED_STR], p.zNickname, -p.iBalance));
1007 					}
1008 				}
1009 				break;
1010 		}
1011 		// This is great!
1012 		// Return desire value
1013 		return UseQuote(pNPCQuoteInfoArray, ppResultQuoteInfo, pubQuoteNum, i);
1014 	}
1015 
1016 	return 0;
1017 }
1018 
1019 
1020 // handle money being npc being
HandleNPCBeingGivenMoneyByPlayer(UINT8 const ubNPC,UINT32 const uiMoneyAmount)1021 static UINT8 HandleNPCBeingGivenMoneyByPlayer(UINT8 const ubNPC, UINT32 const uiMoneyAmount)
1022 {
1023 	UINT8 quote_id=0; // Wmaybe-uninitialized
1024 	switch( ubNPC )
1025 	{
1026 		// handle for STEVE and VINCE
1027 		case STEVE:
1028 		case VINCE:
1029 			{
1030 				INT32		iCost;
1031 
1032 				iCost = (INT32) CalcMedicalCost( ubNPC );
1033 
1034 				// check amount of money
1035 				if ( (INT32)uiMoneyAmount + giHospitalTempBalance + giHospitalRefund >= iCost )
1036 				{
1037 					// enough cash, check how much help is needed
1038 					if( CheckFact( FACT_WOUNDED_MERCS_NEARBY , ubNPC) )
1039 					{
1040 						quote_id = 26;
1041 					}
1042 					else if( CheckFact( FACT_ONE_WOUNDED_MERC_NEARBY, ubNPC ) )
1043 					{
1044 						quote_id = 25;
1045 					}
1046 
1047 					if ( giHospitalRefund > 0 )
1048 					{
1049 						giHospitalRefund = __max( 0, giHospitalRefund - iCost + uiMoneyAmount );
1050 					}
1051 					giHospitalTempBalance = 0;
1052 				}
1053 				else
1054 				{
1055 					ST::string sTempString = ST::format("${}", iCost - uiMoneyAmount - giHospitalTempBalance);
1056 
1057 					// not enough cash
1058 					ScreenMsg( FONT_MCOLOR_LTYELLOW,
1059 						MSG_INTERFACE,
1060 						st_format_printf(g_langRes->Message[ STR_NEED_TO_GIVE_MONEY ],
1061 						gMercProfiles[ ubNPC ].zNickname,
1062 						sTempString) );
1063 					quote_id = 27;
1064 					giHospitalTempBalance += uiMoneyAmount;
1065 				}
1066 			}
1067 			break;
1068 		case KINGPIN:
1069 			if ( (INT32) uiMoneyAmount < -gMercProfiles[ KINGPIN ].iBalance )
1070 			{
1071 				quote_id = 9;
1072 			}
1073 			else
1074 			{
1075 				quote_id = 10;
1076 			}
1077 			gMercProfiles[ KINGPIN ].iBalance += (INT32) uiMoneyAmount;
1078 			break;
1079 		case WALTER:
1080 			if ( gMercProfiles[ WALTER ].iBalance == 0 )
1081 			{
1082 				quote_id = 12;
1083 			}
1084 			else
1085 			{
1086 				quote_id = 13;
1087 			}
1088 			gMercProfiles[ WALTER ].iBalance += uiMoneyAmount;
1089 			break;
1090 		case FRANK:
1091 			gArmsDealerStatus[ ARMS_DEALER_FRANK ].uiArmsDealersCash += uiMoneyAmount;
1092 			quote_id = 0;
1093 			break;
1094 
1095 		case GERARD:
1096 			gMercProfiles[ GERARD ].iBalance += uiMoneyAmount;
1097 			if ( (gMercProfiles[ GERARD ].iBalance) >= 10000 )
1098 			{
1099 				quote_id = 12;
1100 			}
1101 			else
1102 			{
1103 				quote_id = 11;
1104 			}
1105 			break;
1106 		case JOE:
1107 			gMercProfiles[ JOE ].iBalance += uiMoneyAmount;
1108 			if ( (gMercProfiles[ JOE ].iBalance) >= 10000 )
1109 			{
1110 				quote_id = 7;
1111 			}
1112 			else
1113 			{
1114 				quote_id = 6;
1115 			}
1116 			break;
1117 
1118 		default:
1119 			quote_id = 0;
1120 	}
1121 	return quote_id;
1122 }
1123 
1124 
NPCConsiderQuote(UINT8 const ubNPC,UINT8 const ubMerc,Approach const ubApproach,UINT8 const ubQuoteNum,UINT8 const ubTalkDesire,NPCQuoteInfo * const pNPCQuoteInfoArray)1125 static UINT8 NPCConsiderQuote(UINT8 const ubNPC, UINT8 const ubMerc, Approach const ubApproach, UINT8 const ubQuoteNum, UINT8 const ubTalkDesire, NPCQuoteInfo* const pNPCQuoteInfoArray)
1126 {
1127 	//This function looks at a quote and determines if conditions for it have been met.
1128 	// Returns 0 if none , 1 if one is found
1129 	NPCQuoteInfo *				pNPCQuoteInfo;
1130 	UINT32								uiDay;
1131 	BOOLEAN								fTrue;
1132 
1133 	MERCPROFILESTRUCT const* const pNPCProfile = ubNPC != NO_PROFILE ? &GetProfile(ubNPC) : 0;
1134 
1135 	// How much do we want to talk with this merc?
1136 	uiDay = GetWorldDay();
1137 
1138 	pNPCQuoteInfo = &(pNPCQuoteInfoArray[ubQuoteNum]);
1139 
1140 	if (ubApproach != NPC_INITIATING_CONV && ubMerc != NO_PROFILE)
1141 	{
1142 		SLOGD("New Approach for NPC ID: %d '%s' against Merc: %d '%s'\n\
1143 														\tTesting Record #: %d",
1144 					ubNPC, GetProfile(ubNPC).zNickname.c_str(), ubMerc, GetProfile(ubMerc).zNickname.c_str(), ubQuoteNum);
1145 	}
1146 
1147 	if (CHECK_FLAG( pNPCQuoteInfo->fFlags, QUOTE_FLAG_SAID ))
1148 	{
1149 		if (ubApproach != NPC_INITIATING_CONV)
1150 		{
1151 			SLOGD("Quote Already Said, leaving");
1152 		}
1153 		// skip quotes already said
1154 		return( FALSE );
1155 	}
1156 
1157 	// if the quote is quest-specific, is the player on that quest?
1158 	if (pNPCQuoteInfo->ubQuest != NO_QUEST)
1159 	{
1160 		if (pNPCQuoteInfo->ubQuest > QUEST_DONE_NUM && (pNPCQuoteInfo->ubQuest - QUEST_DONE_NUM) < MAX_QUESTS)
1161 		{
1162 			if (gubQuest[pNPCQuoteInfo->ubQuest - QUEST_DONE_NUM] != QUESTDONE)
1163 			{
1164 				return( FALSE );
1165 			}
1166 		}
1167 		else if (pNPCQuoteInfo->ubQuest > QUEST_NOT_STARTED_NUM && (pNPCQuoteInfo->ubQuest - QUEST_NOT_STARTED_NUM) < MAX_QUESTS)
1168 		{
1169 			if (gubQuest[pNPCQuoteInfo->ubQuest - QUEST_NOT_STARTED_NUM] != QUESTNOTSTARTED)
1170 			{
1171 				return( FALSE );
1172 			}
1173 		}
1174 		else if (pNPCQuoteInfo->ubQuest < MAX_QUESTS)
1175 		{
1176 			if (gubQuest[pNPCQuoteInfo->ubQuest] != QUESTINPROGRESS)
1177 			{
1178 				return( FALSE );
1179 			}
1180 		}
1181 		else
1182 		{
1183 			ST::string err = ST::format("invalid quest index: {}", pNPCQuoteInfo->ubQuest);
1184 			throw std::runtime_error(err.to_std_string());
1185 		}
1186 	}
1187 
1188 	// if there are facts to be checked, check them
1189 	if (pNPCQuoteInfo->usFactMustBeTrue != NO_FACT)
1190 	{
1191 		fTrue = CheckFact((Fact)pNPCQuoteInfo->usFactMustBeTrue, ubNPC);
1192 		if (ubApproach != NPC_INITIATING_CONV)
1193 		{
1194 			SLOGD("Fact (%d:'%s') Must be True, status is %s",
1195 						pNPCQuoteInfo->usFactMustBeTrue, FactDescText[pNPCQuoteInfo->usFactMustBeTrue].c_str(),
1196 						fTrue ? "True" : "False, returning");
1197 		}
1198 		if (!fTrue) return FALSE;
1199 	}
1200 
1201 	if (pNPCQuoteInfo->usFactMustBeFalse != NO_FACT)
1202 	{
1203 		fTrue = CheckFact((Fact)pNPCQuoteInfo->usFactMustBeFalse, ubNPC);
1204 		if (ubApproach != NPC_INITIATING_CONV)
1205 		{
1206 			SLOGD("Fact(%d:'%s') Must be False status is  %s",
1207 						pNPCQuoteInfo->usFactMustBeFalse, FactDescText[pNPCQuoteInfo->usFactMustBeFalse].c_str(),
1208 						(fTrue == TRUE) ? "True, return" : "FALSE" );
1209 		}
1210 		if (fTrue)	return( FALSE );
1211 	}
1212 
1213 	// check for required approach
1214 	// since the "I hate you" code triggers the record, triggering has to work properly
1215 	// with the other value that is stored!
1216 	if ( pNPCQuoteInfo->ubApproachRequired || !(ubApproach == APPROACH_FRIENDLY || ubApproach == APPROACH_DIRECT || ubApproach == TRIGGER_NPC ) )
1217 	{
1218 		if (ubApproach != NPC_INITIATING_CONV)
1219 		{
1220 			SLOGD("Approach Taken(%d) must equal required Approach(%d) = %s",
1221 						ubApproach, pNPCQuoteInfo->ubApproachRequired,
1222 						(ubApproach != pNPCQuoteInfo->ubApproachRequired) ? "TRUE, return" : "FALSE" );
1223 		}
1224 		if ( pNPCQuoteInfo->ubApproachRequired == APPROACH_ONE_OF_FOUR_STANDARD )
1225 		{
1226 			// friendly to recruit will match
1227 			if ( ubApproach < APPROACH_FRIENDLY || ubApproach > APPROACH_RECRUIT )
1228 			{
1229 				return( FALSE );
1230 			}
1231 		}
1232 		else if ( pNPCQuoteInfo->ubApproachRequired == APPROACH_FRIENDLY_DIRECT_OR_RECRUIT )
1233 		{
1234 			if ( ubApproach != APPROACH_FRIENDLY && ubApproach != APPROACH_DIRECT && ubApproach != APPROACH_RECRUIT )
1235 			{
1236 				return( FALSE );
1237 			}
1238 		}
1239 		else if (ubApproach != pNPCQuoteInfo->ubApproachRequired)
1240 		{
1241 			return( FALSE );
1242 		}
1243 	}
1244 
1245 	// check time constraints on the quotes
1246 	if (pNPCProfile != NULL && pNPCQuoteInfo->ubFirstDay == MUST_BE_NEW_DAY)
1247 	{
1248 		if (ubApproach != NPC_INITIATING_CONV)
1249 		{
1250 			SLOGD("Time constraints. Current Day(%d) must <= Day last spoken too (%d) : %s",
1251 						uiDay, pNPCProfile->ubLastDateSpokenTo,
1252 						(uiDay <= pNPCProfile->ubLastDateSpokenTo) ? "TRUE, return" : "FALSE" );
1253 		}
1254 
1255 		if (uiDay <= pNPCProfile->ubLastDateSpokenTo)
1256 		{
1257 			// too early!
1258 			return( FALSE );
1259 		}
1260 	}
1261 	else if (uiDay < pNPCQuoteInfo->ubFirstDay)
1262 	{
1263 		if (ubApproach != NPC_INITIATING_CONV)
1264 		{
1265 			SLOGD("Current Day(%d) is before Required first day(%d) = %s",
1266 						uiDay, pNPCQuoteInfo->ubFirstDay,
1267 						(uiDay < pNPCQuoteInfo->ubFirstDay) ? "False, returning" : "True" );
1268 		}
1269 		// too early!
1270 		return( FALSE );
1271 	}
1272 
1273 	if (uiDay > pNPCQuoteInfo->ubLastDay && uiDay < 255 )
1274 	{
1275 		if (ubApproach != NPC_INITIATING_CONV)
1276 		{
1277 			SLOGD("Current Day(%d) is after Required first day(%d) = %s",
1278 						uiDay, pNPCQuoteInfo->ubFirstDay,
1279 						(uiDay > pNPCQuoteInfo->ubLastDay) ? "TRUE, returning" : "FALSE" );
1280 		}
1281 		// too late!
1282 		return( FALSE );
1283 	}
1284 
1285 	// check opinion required
1286 	if ((pNPCQuoteInfo->ubOpinionRequired != IRRELEVANT) && (ubApproach != TRIGGER_NPC))
1287 	{
1288 		if (ubApproach != NPC_INITIATING_CONV)
1289 		{
1290 			SLOGD("Opinion Required.  Talk Desire (%d), Opinion Required(%d) : %s",
1291 						ubTalkDesire, pNPCQuoteInfo->ubOpinionRequired,
1292 						(ubTalkDesire < pNPCQuoteInfo->ubOpinionRequired) ? "False, return" : "False, continue" );
1293 		}
1294 
1295 		if (ubTalkDesire < pNPCQuoteInfo->ubOpinionRequired )
1296 		{
1297 			return( FALSE );
1298 		}
1299 	}
1300 
1301 	if (ubApproach != NPC_INITIATING_CONV)
1302 	{
1303 		SLOGD("Return the quote opinion value! = TRUE");
1304 	}
1305 	// Return the quote opinion value!
1306 	return( TRUE );
1307 
1308 }
1309 
1310 
ReplaceLocationInNPCData(NPCQuoteInfo * pNPCQuoteInfoArray,INT16 sOldGridNo,INT16 sNewGridNo)1311 static void ReplaceLocationInNPCData(NPCQuoteInfo* pNPCQuoteInfoArray, INT16 sOldGridNo, INT16 sNewGridNo)
1312 {
1313 	UINT8							ubFirstQuoteRecord, ubLastQuoteRecord, ubLoop;
1314 	NPCQuoteInfo *		pNPCQuoteInfo;
1315 
1316 	ubFirstQuoteRecord = 0;
1317 	ubLastQuoteRecord = NUM_NPC_QUOTE_RECORDS - 1;
1318 	for (ubLoop = ubFirstQuoteRecord; ubLoop <= ubLastQuoteRecord; ubLoop++)
1319 	{
1320 		pNPCQuoteInfo = &(pNPCQuoteInfoArray[ ubLoop ]);
1321 		if (sOldGridNo == -pNPCQuoteInfo->sRequiredGridno)
1322 		{
1323 			pNPCQuoteInfo->sRequiredGridno = -sNewGridNo;
1324 		}
1325 		if (sOldGridNo == pNPCQuoteInfo->usGoToGridno)
1326 		{
1327 			pNPCQuoteInfo->usGoToGridno = sNewGridNo;
1328 		}
1329 	}
1330 }
1331 
1332 
ReplaceLocationInNPCDataFromProfileID(UINT8 const ubNPC,INT16 const sOldGridNo,INT16 const sNewGridNo)1333 void ReplaceLocationInNPCDataFromProfileID(UINT8 const ubNPC, INT16 const sOldGridNo, INT16 const sNewGridNo)
1334 {
1335 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
1336 	if (!quotes) return; // error
1337 	ReplaceLocationInNPCData(quotes, sOldGridNo, sNewGridNo);
1338 }
1339 
1340 
ResetOncePerConvoRecords(NPCQuoteInfo * pNPCQuoteInfoArray)1341 static void ResetOncePerConvoRecords(NPCQuoteInfo* pNPCQuoteInfoArray)
1342 {
1343 	UINT8									ubLoop;
1344 
1345 	for ( ubLoop = 0; ubLoop < NUM_NPC_QUOTE_RECORDS; ubLoop++ )
1346 	{
1347 		if ( CHECK_FLAG(pNPCQuoteInfoArray[ubLoop].fFlags, QUOTE_FLAG_SAY_ONCE_PER_CONVO) )
1348 		{
1349 			TURN_FLAG_OFF( pNPCQuoteInfoArray[ubLoop].fFlags, QUOTE_FLAG_SAID );
1350 		}
1351 	}
1352 }
1353 
1354 
ResetOncePerConvoRecordsForNPC(UINT8 ubNPC)1355 void ResetOncePerConvoRecordsForNPC( UINT8 ubNPC )
1356 {
1357 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
1358 	if (!quotes) return; // error
1359 	ResetOncePerConvoRecords(quotes);
1360 }
1361 
1362 
ResetOncePerConvoRecordsForAllNPCsInLoadedSector(void)1363 void ResetOncePerConvoRecordsForAllNPCsInLoadedSector( void )
1364 {
1365 	if ( gWorldSectorX == 0 || gWorldSectorY == 0 )
1366 	{
1367 		return;
1368 	}
1369 
1370 	for (const MercProfile* p : GCM->listMercProfiles())
1371 	{
1372 		// only RPCs and NPCs
1373 		if (!p->isNPCorRPC()) continue;
1374 
1375 		ProfileID ubMercID = p->getID();
1376 		if ( gMercProfiles[ubMercID].sSectorX == gWorldSectorX &&
1377 				gMercProfiles[ubMercID].sSectorY == gWorldSectorY &&
1378 				gMercProfiles[ubMercID].bSectorZ == gbWorldSectorZ &&
1379 				gpNPCQuoteInfoArray[ubMercID] != NULL )
1380 		{
1381 			ResetOncePerConvoRecordsForNPC(ubMercID);
1382 		}
1383 	}
1384 }
1385 
1386 
NPCClosePanel()1387 static void NPCClosePanel()
1388 {
1389 	DialogueEvent::Add(new DialogueEventCallback<DeleteTalkingMenu>());
1390 }
1391 
1392 
TalkingMenuGiveItem(ProfileID const npc,OBJECTTYPE * const object,INT8 const inv_pos)1393 static void TalkingMenuGiveItem(ProfileID const npc, OBJECTTYPE* const object, INT8 const inv_pos)
1394 {
1395 	class DialogueEventGiveItem : public DialogueEvent
1396 	{
1397 		public:
1398 			DialogueEventGiveItem(ProfileID const npc, OBJECTTYPE* const object, INT8 const inv_pos) :
1399 				object_(object),
1400 				npc_(npc),
1401 				inv_pos_(inv_pos)
1402 			{}
1403 
1404 			bool Execute()
1405 			{
1406 				HandleNPCItemGiven(npc_, object_, inv_pos_);
1407 				return false;
1408 			}
1409 
1410 		private:
1411 			OBJECTTYPE* const object_;
1412 			ProfileID   const npc_;
1413 			INT8        const inv_pos_;
1414 	};
1415 
1416 	DialogueEvent::Add(new DialogueEventGiveItem(npc, object, inv_pos));
1417 }
1418 
1419 
ReturnItemToPlayer(ProfileID const merc,OBJECTTYPE * const o)1420 static void ReturnItemToPlayer(ProfileID const merc, OBJECTTYPE* const o)
1421 {
1422 	if (!o) return;
1423 
1424 	SOLDIERTYPE* const s = FindSoldierByProfileID(merc);
1425 
1426 	// Try to auto place object and then if it fails, put into cursor
1427 	if (!AutoPlaceObject(s, o, FALSE))
1428 	{
1429 		InternalBeginItemPointer(s, o, NO_SLOT);
1430 	}
1431 	DirtyMercPanelInterface(s, DIRTYLEVEL2);
1432 }
1433 
1434 
1435 static void TriggerClosestMercWhoCanSeeNPC(UINT8 ubNPC, NPCQuoteInfo* pQuotePtr);
1436 
1437 
ConverseFull(UINT8 const ubNPC,UINT8 const ubMerc,Approach bApproach,UINT8 const approach_record,OBJECTTYPE * const o)1438 void ConverseFull(UINT8 const ubNPC, UINT8 const ubMerc, Approach bApproach, UINT8 const approach_record, OBJECTTYPE* const o)
1439 {
1440 	static INT32 giNPCSpecialReferenceCount = 0;
1441 
1442 	NPCQuoteInfo					QuoteInfo;
1443 	NPCQuoteInfo *				pQuotePtr = &(QuoteInfo);
1444 	UINT8									ubLoop, ubQuoteNum, ubRecordNum;
1445 	UINT32								uiDay;
1446 
1447 	Assert((bApproach == APPROACH_GIVINGITEM) == (o != 0));
1448 
1449 	SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubNPC);
1450 	if ( pNPC )
1451 	{
1452 		// set delay for civ AI movement
1453 		pNPC->uiTimeSinceLastSpoke = GetJA2Clock();
1454 
1455 		if (!CheckFact(FACT_CURRENT_SECTOR_IS_SAFE, ubNPC))
1456 		{
1457 			if ( bApproach != TRIGGER_NPC && bApproach != APPROACH_GIVEFIRSTAID && bApproach != APPROACH_DECLARATION_OF_HOSTILITY && bApproach != APPROACH_ENEMY_NPC_QUOTE )
1458 			{
1459 				if ( NPCHasUnusedRecordWithGivenApproach( ubNPC, APPROACH_SECTOR_NOT_SAFE ) )
1460 				{
1461 					// override with sector-not-safe approach
1462 					bApproach = APPROACH_SECTOR_NOT_SAFE;
1463 				}
1464 			}
1465 		}
1466 
1467 		// make sure civ is awake now
1468 		pNPC->fAIFlags &= (~AI_ASLEEP);
1469 	}
1470 
1471 	NPCQuoteInfo* const pNPCQuoteInfoArray = EnsureQuoteFileLoaded(ubNPC);
1472 	if (!pNPCQuoteInfoArray)
1473 	{ // error!!!
1474 		ReturnItemToPlayer(ubMerc, o);
1475 		return;
1476 	}
1477 
1478 	MERCPROFILESTRUCT& p = GetProfile(ubNPC);
1479 	switch( bApproach )
1480 	{
1481 		case NPC_INITIAL_QUOTE:
1482 			// reset stuff
1483 			ResetOncePerConvoRecords( pNPCQuoteInfoArray );
1484 
1485 			// CHEAP HACK
1486 			// Since we don't have CONDITIONAL once-per-convo refreshes, do this in code
1487 			// NB fact 281 is 'Darren has explained boxing rules'
1488 			if (ubNPC == DARREN && !CheckFact(FACT_281, DARREN))
1489 			{
1490 				TURN_FLAG_OFF( pNPCQuoteInfoArray[11].fFlags, QUOTE_FLAG_SAID );
1491 			}
1492 
1493 			// turn the NPC to face us
1494 			// this '1' value is a dummy....
1495 			NPCDoAction( ubNPC, NPC_ACTION_TURN_TO_FACE_NEAREST_MERC, 1 );
1496 
1497 			if (p.ubLastDateSpokenTo > 0)
1498 			{
1499 				uiDay = GetWorldDay();
1500 				if (uiDay > p.ubLastDateSpokenTo)
1501 				{
1502 					NPCConsiderTalking( ubNPC, ubMerc, APPROACH_SPECIAL_INITIAL_QUOTE, 0, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum );
1503 					if (pQuotePtr != NULL)
1504 					{
1505 						// converse using this approach instead!
1506 						ReturnItemToPlayer(ubMerc, o);
1507 						Converse(ubNPC, ubMerc, APPROACH_SPECIAL_INITIAL_QUOTE);
1508 						return;
1509 					}
1510 					// subsequent times approached intro
1511 					ubQuoteNum = QUOTE_SUBS_INTRO;
1512 				}
1513 				else
1514 				{
1515 					// say nothing!
1516 					ReturnItemToPlayer(ubMerc, o);
1517 					return;
1518 				}
1519 			}
1520 			else
1521 			{
1522 				// try special initial quote first
1523 				NPCConsiderTalking( ubNPC, ubMerc, APPROACH_SPECIAL_INITIAL_QUOTE, 0, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum );
1524 				if (pQuotePtr != NULL)
1525 				{
1526 					// converse using this approach instead!
1527 					ReturnItemToPlayer(ubMerc, o);
1528 					Converse(ubNPC, ubMerc, APPROACH_SPECIAL_INITIAL_QUOTE);
1529 					return;
1530 				}
1531 
1532 				NPCConsiderTalking( ubNPC, ubMerc, APPROACH_INITIAL_QUOTE, 0, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum );
1533 				if (pQuotePtr != NULL)
1534 				{
1535 					// converse using this approach instead!
1536 					ReturnItemToPlayer(ubMerc, o);
1537 					Converse(ubNPC, ubMerc, APPROACH_INITIAL_QUOTE);
1538 					return;
1539 				}
1540 
1541 				// first time approached intro
1542 				ubQuoteNum = QUOTE_INTRO;
1543 			}
1544 			TalkingMenuDialogue( ubQuoteNum );
1545 			p.ubLastQuoteSaid          = ubQuoteNum;
1546 			p.bLastQuoteSaidWasSpecial = FALSE;
1547 			break;
1548 		case NPC_WHOAREYOU:
1549 			ubQuoteNum = QUOTE_INTRO;
1550 			TalkingMenuDialogue( ubQuoteNum );
1551 			// For now, DO NOT remember for 'Come again?'
1552 			break;
1553 		case APPROACH_REPEAT:
1554 			if (p.ubLastQuoteSaid == NO_QUOTE)
1555 			{
1556 				// this should never occur now!
1557 				TalkingMenuDialogue( QUOTE_INTRO );
1558 			}
1559 			else
1560 			{
1561 				if (p.bLastQuoteSaidWasSpecial)
1562 				{
1563 					pQuotePtr = &(pNPCQuoteInfoArray[p.ubLastQuoteSaid]);
1564 					// say quote and following consecutive quotes
1565 					for (ubLoop = 0; ubLoop < pQuotePtr->ubNumQuotes; ubLoop++)
1566 					{
1567 						// say quote #(pQuotePtr->ubQuoteNum + ubLoop)
1568 						TalkingMenuDialogue( (UINT8)( pQuotePtr->ubQuoteNum + ubLoop ) );
1569 					}
1570 				}
1571 				else
1572 				{
1573 					TalkingMenuDialogue(p.ubLastQuoteSaid);
1574 				}
1575 			}
1576 			break;
1577 		default:
1578 			switch( bApproach )
1579 			{
1580 				case APPROACH_GIVINGITEM:
1581 					// first start by triggering any introduction quote if there is one...
1582 					if (p.ubLastDateSpokenTo > 0)
1583 					{
1584 						uiDay = GetWorldDay();
1585 						if (uiDay > p.ubLastDateSpokenTo)
1586 						{
1587 							NPCConsiderTalking( ubNPC, ubMerc, APPROACH_SPECIAL_INITIAL_QUOTE, 0, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum );
1588 							if (pQuotePtr != NULL)
1589 							{
1590 								// converse using this approach instead!
1591 								Converse(ubNPC, ubMerc, APPROACH_SPECIAL_INITIAL_QUOTE);
1592 
1593 								if ( ubNPC == DARREN )
1594 								{
1595 									// then we have to make this give attempt fail
1596 									ReturnItemToPlayer(ubMerc, o);
1597 									return;
1598 								}
1599 							}
1600 						}
1601 					}
1602 					else
1603 					{
1604 						NPCConsiderTalking( ubNPC, ubMerc, APPROACH_INITIAL_QUOTE, 0, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum );
1605 						if (pQuotePtr != NULL)
1606 						{
1607 							// converse using this approach instead!
1608 							Converse(ubNPC, ubMerc, APPROACH_INITIAL_QUOTE);
1609 						}
1610 					}
1611 
1612 					// If we are approaching because we want to give an item, do something different
1613 					NPCConsiderReceivingItemFromMerc(ubNPC, ubMerc, o, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum);
1614 					break;
1615 				case TRIGGER_NPC:
1616 					// if triggering, pass in the approach data as the record to consider
1617 					STLOGD("Handling trigger {}/{} at {}", gMercProfiles[ubNPC].zNickname.c_str(), approach_record, GetJA2Clock());
1618 					NPCConsiderTalking(ubNPC, ubMerc, bApproach, approach_record, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum);
1619 					break;
1620 				default:
1621 					NPCConsiderTalking( ubNPC, ubMerc, bApproach, 0, pNPCQuoteInfoArray, &pQuotePtr, &ubRecordNum );
1622 					break;
1623 			}
1624 			if (pQuotePtr == NULL)
1625 			{
1626 				// say random everyday quote
1627 				// do NOT set last quote said!
1628 				switch( bApproach )
1629 				{
1630 					case APPROACH_FRIENDLY:
1631 						if (p.bFriendlyOrDirectDefaultResponseUsedRecently)
1632 						{
1633 							ubQuoteNum = QUOTE_GETLOST;
1634 						}
1635 						else
1636 						{
1637 							ubQuoteNum = QUOTE_FRIENDLY_DEFAULT1 + (UINT8) Random( 2 );
1638 							p.bFriendlyOrDirectDefaultResponseUsedRecently = TRUE;
1639 						}
1640 						break;
1641 					case APPROACH_DIRECT:
1642 						if (p.bFriendlyOrDirectDefaultResponseUsedRecently)
1643 						{
1644 							ubQuoteNum = QUOTE_GETLOST;
1645 						}
1646 						else
1647 						{
1648 							ubQuoteNum = QUOTE_DIRECT_DEFAULT;
1649 							p.bFriendlyOrDirectDefaultResponseUsedRecently = TRUE;
1650 						}
1651 						break;
1652 					case APPROACH_THREATEN:
1653 						if (p.bThreatenDefaultResponseUsedRecently)
1654 						{
1655 							ubQuoteNum = QUOTE_GETLOST;
1656 						}
1657 						else
1658 						{
1659 							ubQuoteNum = QUOTE_THREATEN_DEFAULT;
1660 							p.bThreatenDefaultResponseUsedRecently = TRUE;
1661 						}
1662 						break;
1663 					case APPROACH_RECRUIT:
1664 						if (p.bRecruitDefaultResponseUsedRecently)
1665 						{
1666 							ubQuoteNum = QUOTE_GETLOST;
1667 						}
1668 						else
1669 						{
1670 							ubQuoteNum = QUOTE_RECRUIT_NO;
1671 							p.bRecruitDefaultResponseUsedRecently = TRUE;
1672 						}
1673 						break;
1674 					case APPROACH_GIVINGITEM:
1675 						ubQuoteNum = QUOTE_GIVEITEM_NO;
1676 
1677 						/*
1678 						CC - now handled below
1679 						*/
1680 						break;
1681 					case TRIGGER_NPC:
1682 						// trigger did not succeed - abort!!
1683 						return;
1684 					default:
1685 						ubQuoteNum = QUOTE_INTRO;
1686 						break;
1687 				}
1688 				TalkingMenuDialogue( ubQuoteNum );
1689 				p.ubLastQuoteSaid          = ubQuoteNum;
1690 				p.bLastQuoteSaidWasSpecial = FALSE;
1691 				if (ubQuoteNum == QUOTE_GETLOST)
1692 				{
1693 					if (ubNPC == 70 || ubNPC == 120)
1694 					{
1695 						// becomes an enemy
1696 						NPCDoAction( ubNPC, NPC_ACTION_BECOME_ENEMY, 0 );
1697 					}
1698 					// close panel at end of speech
1699 					NPCClosePanel();
1700 				}
1701 				else if ( ubQuoteNum == QUOTE_GIVEITEM_NO )
1702 				{
1703 					// close panel at end of speech
1704 					NPCClosePanel();
1705 					if ( pNPC )
1706 					{
1707 						switch( ubNPC )
1708 						{
1709 							case JIM:
1710 							case JACK:
1711 							case OLAF:
1712 							case RAY:
1713 							case OLGA:
1714 							case TYRONE:
1715 								// Start combat etc
1716 								CancelAIAction(pNPC);
1717 								AddToShouldBecomeHostileOrSayQuoteList(pNPC);
1718 							default:
1719 								break;
1720 						}
1721 					}
1722 				}
1723 			}
1724 			else
1725 			{
1726 				// turn before speech?
1727 				if ( pQuotePtr->sActionData <= -NPC_ACTION_TURN_TO_FACE_NEAREST_MERC )
1728 				{
1729 					SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
1730 					ZEROTIMECOUNTER( pSoldier->AICounter );
1731 					if (pSoldier->bNextAction == AI_ACTION_WAIT)
1732 					{
1733 						pSoldier->bNextAction = AI_ACTION_NONE;
1734 						pSoldier->usNextActionData = 0;
1735 					}
1736 					NPCDoAction( ubNPC, (UINT16) -(pQuotePtr->sActionData), ubRecordNum );
1737 				}
1738 				if (pQuotePtr->ubQuoteNum != NO_QUOTE)
1739 				{
1740 					// say quote and following consecutive quotes
1741 					for (ubLoop = 0; ubLoop < pQuotePtr->ubNumQuotes; ubLoop++)
1742 					{
1743 						TalkingMenuDialogue( (UINT8)( pQuotePtr->ubQuoteNum + ubLoop  ) );
1744 					}
1745 					p.ubLastQuoteSaid          = ubRecordNum;
1746 					p.bLastQuoteSaidWasSpecial = TRUE;
1747 				}
1748 				// set to "said" if we should do so
1749 				if (pQuotePtr->fFlags & QUOTE_FLAG_ERASE_ONCE_SAID || pQuotePtr->fFlags & QUOTE_FLAG_SAY_ONCE_PER_CONVO)
1750 				{
1751 					TURN_FLAG_ON( pQuotePtr->fFlags, QUOTE_FLAG_SAID );
1752 				}
1753 
1754 				// Carry out implications (actions) of this record
1755 
1756 				// Give NPC item if appropriate
1757 				if (bApproach == APPROACH_GIVINGITEM )
1758 				{
1759 					if ( pQuotePtr->sActionData != NPC_ACTION_DONT_ACCEPT_ITEM )
1760 					{
1761 						PlaceObjectInSoldierProfile(ubNPC, o);
1762 
1763 						// Find the GIVER....
1764 						SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubMerc);
1765 
1766 						// Is this one of us?
1767 						if ( pSoldier->bTeam == OUR_TEAM )
1768 						{
1769 							INT8 const bSlot = FindExactObj(pSoldier, o);
1770 							if (bSlot != NO_SLOT)
1771 							{
1772 								RemoveObjs(&pSoldier->inv[bSlot], o->ubNumberOfObjects);
1773 								DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
1774 							}
1775 						}
1776 						else
1777 						{
1778 							RemoveObjectFromSoldierProfile(ubMerc, o->usItem);
1779 						}
1780 					}
1781 					// CC: now handled below
1782 					/*
1783 					else
1784 					{
1785 						// ATE: Here, put back into inventory or place on ground....
1786 						{
1787 							SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubMerc);
1788 
1789 							// Try to auto place object and then if it fails, put into cursor
1790 							if (!AutoPlaceObject(pSoldier, o, FALSE))
1791 							{
1792 								InternalBeginItemPointer(pSoldier, o, NO_SLOT);
1793 							}
1794 							DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
1795 
1796 						}
1797 					}
1798 					*/
1799 				}
1800 				else if (bApproach == APPROACH_RECRUIT)
1801 				{
1802 					// the guy just joined our party
1803 				}
1804 
1805 				// Set things
1806 				if (pQuotePtr->usSetFactTrue != NO_FACT)
1807 				{
1808 					SetFactTrue((Fact)pQuotePtr->usSetFactTrue);
1809 				}
1810 				if (pQuotePtr->ubEndQuest != NO_QUEST)
1811 				{
1812 					EndQuest( pQuotePtr->ubEndQuest, gWorldSectorX, gWorldSectorY );
1813 				}
1814 				if (pQuotePtr->ubStartQuest != NO_QUEST)
1815 				{
1816 					StartQuest( pQuotePtr->ubStartQuest, gWorldSectorX, gWorldSectorY );
1817 				}
1818 
1819 				// Give item to merc?
1820 				if ( pQuotePtr->usGiftItem >= TURN_UI_OFF )
1821 				{
1822 					switch ( pQuotePtr->usGiftItem )
1823 					{
1824 						case TURN_UI_OFF:
1825 							if ( !(gTacticalStatus.uiFlags & INCOMBAT) )
1826 							{
1827 								gTacticalStatus.uiFlags |= ENGAGED_IN_CONV;
1828 								// Increment refrence count...
1829 								giNPCReferenceCount = 1;
1830 							}
1831 							break;
1832 						case TURN_UI_ON:
1833 							// while the special ref count is set, ignore standard off
1834 							if ( giNPCSpecialReferenceCount == 0 )
1835 							{
1836 								gTacticalStatus.uiFlags &= ~ENGAGED_IN_CONV;
1837 								// Decrement refrence count...
1838 								giNPCReferenceCount = 0;
1839 							}
1840 							break;
1841 						case SPECIAL_TURN_UI_OFF:
1842 							if ( !(gTacticalStatus.uiFlags & INCOMBAT) )
1843 							{
1844 
1845 								gTacticalStatus.uiFlags |= ENGAGED_IN_CONV;
1846 								// Increment refrence count...
1847 								giNPCReferenceCount = 1;
1848 								if ( giNPCSpecialReferenceCount < 0 )
1849 								{
1850 									// ???
1851 									giNPCSpecialReferenceCount = 0;
1852 								}
1853 								// increment SPECIAL reference count
1854 								giNPCSpecialReferenceCount += 1;
1855 							}
1856 							break;
1857 						case SPECIAL_TURN_UI_ON:
1858 							// Decrement SPECIAL reference count
1859 							giNPCSpecialReferenceCount -= 1;
1860 							// if special count is now 0, turn reactivate UI
1861 							if ( giNPCSpecialReferenceCount == 0 )
1862 							{
1863 								gTacticalStatus.uiFlags &= ~ENGAGED_IN_CONV;
1864 								giNPCReferenceCount = 0;
1865 							}
1866 							else if ( giNPCSpecialReferenceCount < 0 )
1867 							{
1868 								// ???
1869 								giNPCSpecialReferenceCount = 0;
1870 							}
1871 							break;
1872 					}
1873 				}
1874 				else if ( pQuotePtr->usGiftItem != 0 )
1875 				{
1876 					{
1877 						INT8 bInvPos;
1878 
1879 						SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
1880 
1881 						// Look for item....
1882 						bInvPos = FindObj( pSoldier, pQuotePtr->usGiftItem );
1883 
1884 						AssertMsg( bInvPos != NO_SLOT, "NPC.C:  Gift item does not exist in NPC." );
1885 
1886 						TalkingMenuGiveItem( ubNPC, &(pSoldier->inv[ bInvPos ] ), bInvPos );
1887 					}
1888 				}
1889 				// Action before movement?
1890 				if ( pQuotePtr->sActionData < 0 && pQuotePtr->sActionData > -NPC_ACTION_TURN_TO_FACE_NEAREST_MERC )
1891 				{
1892 					SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
1893 					ZEROTIMECOUNTER( pSoldier->AICounter );
1894 					if (pSoldier->bNextAction == AI_ACTION_WAIT)
1895 					{
1896 						pSoldier->bNextAction = AI_ACTION_NONE;
1897 						pSoldier->usNextActionData = 0;
1898 					}
1899 					NPCDoAction( ubNPC, (UINT16) -(pQuotePtr->sActionData), ubRecordNum );
1900 				}
1901 				else if ( pQuotePtr->usGoToGridno == NO_MOVE && pQuotePtr->sActionData > 0 )
1902 				{
1903 					SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
1904 					ZEROTIMECOUNTER( pSoldier->AICounter );
1905 					if (pSoldier->bNextAction == AI_ACTION_WAIT)
1906 					{
1907 						pSoldier->bNextAction = AI_ACTION_NONE;
1908 						pSoldier->usNextActionData = 0;
1909 					}
1910 					NPCDoAction( ubNPC, (UINT16) (pQuotePtr->sActionData), ubRecordNum );
1911 				}
1912 
1913 				// Movement?
1914 				if ( pQuotePtr->usGoToGridno != NO_MOVE )
1915 				{
1916 					SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
1917 
1918 					// stupid hack CC
1919 					if (pSoldier && ubNPC == KYLE)
1920 					{
1921 						// make sure he has keys
1922 						pSoldier->bHasKeys = TRUE;
1923 					}
1924 					if (pSoldier && pSoldier->sGridNo == pQuotePtr->usGoToGridno )
1925 					{
1926 						// search for quotes to trigger immediately!
1927 						pSoldier->ubQuoteRecord = ubRecordNum + 1; // add 1 so that the value is guaranteed nonzero
1928 						NPCReachedDestination( pSoldier, TRUE );
1929 					}
1930 					else
1931 					{
1932 						// turn off cowering
1933 						if ( pNPC->uiStatusFlags & SOLDIER_COWERING)
1934 						{
1935 							//pNPC->uiStatusFlags &= ~SOLDIER_COWERING;
1936 							EVENT_InitNewSoldierAnim( pNPC, STANDING, 0 , FALSE );
1937 						}
1938 
1939 						pSoldier->ubQuoteRecord = ubRecordNum + 1; // add 1 so that the value is guaranteed nonzero
1940 
1941 						if (pQuotePtr->sActionData == NPC_ACTION_TELEPORT_NPC)
1942 						{
1943 							BumpAnyExistingMerc( pQuotePtr->usGoToGridno );
1944 							TeleportSoldier(*pSoldier, pQuotePtr->usGoToGridno, false);
1945 							// search for quotes to trigger immediately!
1946 							NPCReachedDestination( pSoldier, FALSE );
1947 						}
1948 						else
1949 						{
1950 							NPCGotoGridNo( ubNPC, pQuotePtr->usGoToGridno, ubRecordNum );
1951 						}
1952 					}
1953 				}
1954 
1955 				// Trigger other NPC?
1956 				// ATE: Do all triggers last!
1957 				if ( pQuotePtr->ubTriggerNPC != IRRELEVANT )
1958 				{
1959 					// Check for special NPC trigger codes
1960 					if ( pQuotePtr->ubTriggerNPC == 0 )
1961 					{
1962 						TriggerClosestMercWhoCanSeeNPC( ubNPC, pQuotePtr );
1963 					}
1964 					else if ( pQuotePtr->ubTriggerNPC == 1 )
1965 					{
1966 						// trigger self
1967 						TriggerNPCRecord( ubNPC, pQuotePtr->ubTriggerNPCRec );
1968 					}
1969 					else
1970 					{
1971 						TriggerNPCRecord( pQuotePtr->ubTriggerNPC, pQuotePtr->ubTriggerNPCRec );
1972 					}
1973 				}
1974 
1975 				// Ian says it is okay to take this out!
1976 				/*
1977 				if (bApproach == APPROACH_ENEMY_NPC_QUOTE)
1978 				{
1979 					NPCClosePanel();
1980 				}
1981 				*/
1982 
1983 			}
1984 			break;
1985 	}
1986 
1987 	// Set last day spoken!
1988 	switch( bApproach )
1989 	{
1990 		case APPROACH_FRIENDLY:
1991 		case APPROACH_DIRECT:
1992 		case APPROACH_THREATEN:
1993 		case APPROACH_RECRUIT:
1994 		case NPC_INITIATING_CONV:
1995 		case NPC_INITIAL_QUOTE:
1996 		case APPROACH_SPECIAL_INITIAL_QUOTE:
1997 		case APPROACH_DECLARATION_OF_HOSTILITY:
1998 		case APPROACH_INITIAL_QUOTE:
1999 		case APPROACH_GIVINGITEM:
2000 			p.ubLastDateSpokenTo = (UINT8)GetWorldDay();
2001 			break;
2002 		default:
2003 			break;
2004 	}
2005 
2006 	/* If the approach was changed, always return the item. Otherwise check to see
2007 	 * if the record in question specified refusal */
2008 	if (bApproach != APPROACH_GIVINGITEM || !pQuotePtr || pQuotePtr->sActionData == NPC_ACTION_DONT_ACCEPT_ITEM)
2009 	{
2010 		ReturnItemToPlayer(ubMerc, o);
2011 	}
2012 }
2013 
2014 
Converse(UINT8 const ubNPC,UINT8 const ubMerc,Approach const bApproach)2015 void Converse(UINT8 const ubNPC, UINT8 const ubMerc, Approach const bApproach)
2016 {
2017 	ConverseFull(ubNPC, ubMerc, bApproach, 0, 0);
2018 }
2019 
2020 
NPCConsiderInitiatingConv(const SOLDIERTYPE * const pNPC)2021 INT16 NPCConsiderInitiatingConv(const SOLDIERTYPE* const pNPC)
2022 {
2023 	UINT8         const ubNPC              = pNPC->ubProfile;
2024 	NPCQuoteInfo* const pNPCQuoteInfoArray = EnsureQuoteFileLoaded(ubNPC);
2025 	if (!pNPCQuoteInfoArray) return NOWHERE; // error
2026 
2027 	const INT16  sMyGridNo           = pNPC->sGridNo;
2028 	INT16        sDesiredMercDist    = 100;
2029 	UINT8        ubDesiredMerc       = NOBODY;
2030 	UINT8        ubHighestTalkDesire = 0;
2031 	SOLDIERTYPE* pDesiredMerc        = NULL; // XXX HACK000E
2032 	// loop through all mercs
2033 	for (UINT8 ubMerc = 0; ubMerc < guiNumMercSlots; ++ubMerc)
2034 	{
2035 		const SOLDIERTYPE* const pMerc = MercSlots[ubMerc];
2036 		if (pMerc == NULL) continue;
2037 
2038 		// only look for mercs on the side of the player
2039 		if (pMerc->bSide != OUR_TEAM) continue;
2040 
2041 		// only look for active mercs
2042 		if (pMerc->bAssignment >= ON_DUTY) continue;
2043 
2044 		// if they're not visible, don't think about it
2045 		if (pNPC->bOppList[ubMerc] != SEEN_CURRENTLY) continue;
2046 
2047 		/* what's the opinion required for the highest-opinion quote that we would
2048 		 * say to this merc */
2049 		const UINT8 ubTalkDesire = NPCConsiderTalking(pNPC->ubProfile, pMerc->ubProfile, NPC_INITIATING_CONV, 0, pNPCQuoteInfoArray, NULL, NULL);
2050 		if (ubTalkDesire == 0) continue;
2051 
2052 		if (ubTalkDesire > ubHighestTalkDesire)
2053 		{
2054 			ubHighestTalkDesire = ubTalkDesire;
2055 			ubDesiredMerc       = ubMerc;
2056 			pDesiredMerc        = &GetMan(ubMerc);
2057 			sDesiredMercDist    = PythSpacesAway(sMyGridNo, pDesiredMerc->sGridNo);
2058 		}
2059 		else if (ubTalkDesire == ubHighestTalkDesire)
2060 		{
2061 			INT16 const sDist = PythSpacesAway(sMyGridNo, GetMan(ubMerc).sGridNo);
2062 			if (sDist < sDesiredMercDist)
2063 			{
2064 				// we can say the same thing to this merc, and they're closer!
2065 				ubDesiredMerc    = ubMerc;
2066 				sDesiredMercDist = sDist;
2067 			}
2068 		}
2069 	}
2070 
2071 	return ubDesiredMerc == NOBODY ? NOWHERE : pDesiredMerc->sGridNo;
2072 }
2073 
2074 
NPCReachedDestination(SOLDIERTYPE * pNPC,BOOLEAN fAlreadyThere)2075 void NPCReachedDestination( SOLDIERTYPE * pNPC, BOOLEAN fAlreadyThere )
2076 {
2077 	// perform action or whatever after reaching our destination
2078 	UINT8		ubNPC;
2079 	NPCQuoteInfo *				pQuotePtr;
2080 	UINT8									ubLoop;
2081 	UINT8									ubQuoteRecord;
2082 
2083 	if ( pNPC->ubQuoteRecord == 0 )
2084 	{
2085 		ubQuoteRecord = 0;
2086 	}
2087 	else
2088 	{
2089 		ubQuoteRecord = (UINT8) (pNPC->ubQuoteRecord - 1);
2090 	}
2091 
2092 	// Clear values!
2093 	pNPC->ubQuoteRecord = 0;
2094 	if (pNPC->bTeam == OUR_TEAM)
2095 	{
2096 		// the "under ai control" flag was set temporarily; better turn it off now
2097 		pNPC->uiStatusFlags &= (~SOLDIER_PCUNDERAICONTROL);
2098 		// make damn sure the AI_HANDLE_EVERY_FRAME flag is turned off
2099 		pNPC->fAIFlags &= (AI_HANDLE_EVERY_FRAME);
2100 	}
2101 
2102 	ubNPC = pNPC->ubProfile;
2103 	NPCQuoteInfo* const pNPCQuoteInfoArray = EnsureQuoteFileLoaded(ubNPC);
2104 	if (!pNPCQuoteInfoArray) return; // error
2105 
2106 	pQuotePtr = &(pNPCQuoteInfoArray[ubQuoteRecord]);
2107 	// either we are supposed to consider a new quote record
2108 	// (indicated by a negative gridno in the has-item field)
2109 	// or an action to perform once we reached this gridno
2110 
2111 	if ( pNPC->sGridNo == pQuotePtr->usGoToGridno )
2112 	{
2113 		// check for an after-move action
2114 		if ( pQuotePtr->sActionData > 0)
2115 		{
2116 			NPCDoAction( ubNPC, (UINT16) pQuotePtr->sActionData, ubQuoteRecord );
2117 		}
2118 	}
2119 
2120 	for ( ubLoop = 0; ubLoop < NUM_NPC_QUOTE_RECORDS; ubLoop++ )
2121 	{
2122 		pQuotePtr = &(pNPCQuoteInfoArray[ubLoop]);
2123 		if ( pNPC->sGridNo == -(pQuotePtr->sRequiredGridno ) )
2124 		{
2125 			if ( NPCConsiderQuote( ubNPC, 0, TRIGGER_NPC, ubLoop, 0, pNPCQuoteInfoArray ) )
2126 			{
2127 				if (fAlreadyThere)
2128 				{
2129 					TriggerNPCRecord( ubNPC, ubLoop );
2130 				}
2131 				else
2132 				{
2133 					// trigger this quote
2134 					TriggerNPCRecordImmediately( ubNPC, ubLoop );
2135 				}
2136 				return;
2137 			}
2138 		}
2139 	}
2140 }
2141 
2142 
2143 // Trigger an NPC record
NPCTriggerNPC(UINT8 const npc,UINT8 const record,Approach const approach,bool const show_dialogue_menu)2144 static void NPCTriggerNPC(UINT8 const npc, UINT8 const record, Approach const approach, bool const show_dialogue_menu)
2145 {
2146 	class DialogueEventTriggerNPC : public DialogueEvent
2147 	{
2148 		public:
2149 			DialogueEventTriggerNPC(ProfileID const npc, UINT8 const record, Approach const approach, bool const show_dialogue_menu) :
2150 				npc_(npc),
2151 				record_(record),
2152 				show_dialogue_menu_(show_dialogue_menu),
2153 				approach_(approach)
2154 			{}
2155 
2156 			bool Execute()
2157 			{
2158 				HandleNPCTriggerNPC(npc_, record_, show_dialogue_menu_, approach_);
2159 				return false;
2160 			}
2161 
2162 		private:
2163 			ProfileID const npc_;
2164 			UINT8     const record_;
2165 			bool      const show_dialogue_menu_;
2166 			Approach  const approach_;
2167 	};
2168 
2169 	DialogueEvent::Add(new DialogueEventTriggerNPC(npc, record, approach, show_dialogue_menu));
2170 }
2171 
2172 
TriggerNPCRecord(UINT8 const ubTriggerNPC,UINT8 const record)2173 void TriggerNPCRecord(UINT8 const ubTriggerNPC, UINT8 const record)
2174 {
2175 	// Check if we have a quote to trigger...
2176 
2177 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubTriggerNPC);
2178 	if (!quotes) return; // error
2179 	NPCQuoteInfo const& q                = quotes[record];
2180 	bool         const  display_dialogue = q.ubQuoteNum != IRRELEVANT;
2181 
2182 	if (NPCConsiderQuote(ubTriggerNPC, 0, TRIGGER_NPC, record, 0, quotes))
2183 	{
2184 		NPCTriggerNPC(ubTriggerNPC, record, TRIGGER_NPC, display_dialogue);
2185 	}
2186 	else
2187 	{ // don't do anything
2188 		SLOGW("trigger of %d, record %d cannot proceed, possible error", ubTriggerNPC, record);
2189 	}
2190 }
2191 
2192 
TriggerNPCRecordImmediately(UINT8 const ubTriggerNPC,UINT8 const record)2193 void TriggerNPCRecordImmediately(UINT8 const ubTriggerNPC, UINT8 const record)
2194 {
2195 	// Check if we have a quote to trigger...
2196 
2197 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubTriggerNPC);
2198 	if (!quotes) return; // error
2199 	NPCQuoteInfo const& q                = quotes[record];
2200 	bool         const  display_dialogue = q.ubQuoteNum != IRRELEVANT;
2201 
2202 	if (NPCConsiderQuote(ubTriggerNPC, 0, TRIGGER_NPC, record, 0, quotes))
2203 	{ // trigger IMMEDIATELY
2204 		HandleNPCTriggerNPC(ubTriggerNPC, record, display_dialogue, TRIGGER_NPC);
2205 	}
2206 	else
2207 	{ // don't do anything
2208 		SLOGW("trigger of %d, record %d cannot proceed, possible error", ubTriggerNPC, record);
2209 	}
2210 }
2211 
2212 
PCsNearNPC(UINT8 ubNPC)2213 void PCsNearNPC( UINT8 ubNPC )
2214 {
2215 	UINT8									ubLoop;
2216 	NPCQuoteInfo *				pQuotePtr;
2217 
2218 	NPCQuoteInfo* const pNPCQuoteInfoArray = EnsureQuoteFileLoaded(ubNPC);
2219 	if (!pNPCQuoteInfoArray) return; // error
2220 
2221 	// see what this triggers...
2222 	SetFactTrue( FACT_PC_NEAR );
2223 
2224 	// Clear values!
2225 	// Get value for NPC
2226 	SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
2227 	pSoldier->ubQuoteRecord = 0;
2228 
2229 	for ( ubLoop = 0; ubLoop < NUM_NPC_QUOTE_RECORDS; ubLoop++ )
2230 	{
2231 		pQuotePtr = &(pNPCQuoteInfoArray[ubLoop]);
2232 		if ( pSoldier->sGridNo == -(pQuotePtr->sRequiredGridno ) )
2233 		{
2234 			if ( NPCConsiderQuote( ubNPC, 0, TRIGGER_NPC, ubLoop, 0, pNPCQuoteInfoArray ) )
2235 			{
2236 				// trigger this quote IMMEDIATELY!
2237 				TriggerNPCRecordImmediately( ubNPC, ubLoop );
2238 				break;
2239 			}
2240 		}
2241 	}
2242 
2243 	// reset fact
2244 	SetFactFalse( FACT_PC_NEAR );
2245 }
2246 
PCDoesFirstAidOnNPC(UINT8 ubNPC)2247 BOOLEAN PCDoesFirstAidOnNPC( UINT8 ubNPC )
2248 {
2249 	UINT8									ubLoop;
2250 	NPCQuoteInfo *				pQuotePtr;
2251 
2252 	NPCQuoteInfo* const pNPCQuoteInfoArray = EnsureQuoteFileLoaded(ubNPC);
2253 	if (!pNPCQuoteInfoArray) return FALSE; // error
2254 
2255 	SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
2256 	// Clear values!
2257 	pSoldier->ubQuoteRecord = 0;
2258 
2259 	// Set flag...
2260 	gMercProfiles[ ubNPC ].ubMiscFlags2 |= PROFILE_MISC_FLAG2_BANDAGED_TODAY;
2261 
2262 	for ( ubLoop = 0; ubLoop < NUM_NPC_QUOTE_RECORDS; ubLoop++ )
2263 	{
2264 		pQuotePtr = &(pNPCQuoteInfoArray[ubLoop]);
2265 		if ( pQuotePtr->ubApproachRequired == APPROACH_GIVEFIRSTAID )
2266 		{
2267 			if ( NPCConsiderQuote( ubNPC, 0, TRIGGER_NPC, ubLoop, 0, pNPCQuoteInfoArray ) )
2268 			{
2269 				// trigger this quote IMMEDIATELY!
2270 				TriggerNPCRecordImmediately( ubNPC, ubLoop );
2271 				return( TRUE );
2272 			}
2273 		}
2274 	}
2275 	return( FALSE );
2276 }
2277 
2278 
TriggerClosestMercWhoCanSeeNPC(UINT8 ubNPC,NPCQuoteInfo * pQuotePtr)2279 static void TriggerClosestMercWhoCanSeeNPC(UINT8 ubNPC, NPCQuoteInfo* pQuotePtr)
2280 {
2281 	// Loop through all mercs, gather closest mercs who can see and trigger one!
2282 	UINT8	ubNumMercs = 0;
2283 
2284 	const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
2285 
2286 	// Loop through all our guys and randomly say one from someone in our sector
2287 	SOLDIERTYPE* mercs_in_sector[40];
2288 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
2289 	{
2290 		// Add guy if he's a candidate...
2291 		if (OkControllableMerc(s) &&
2292 				s->bOppList[pSoldier->ubID] == SEEN_CURRENTLY)
2293 		{
2294 			mercs_in_sector[ubNumMercs++] = s;
2295 		}
2296 	}
2297 
2298 	// If we are > 0
2299 	if ( ubNumMercs > 0 )
2300 	{
2301 		SOLDIERTYPE* const chosen = mercs_in_sector[Random(ubNumMercs)];
2302 
2303 		// Post action to close panel
2304 		NPCClosePanel( );
2305 
2306 		// If 64, do something special
2307 		if ( pQuotePtr->ubTriggerNPCRec == QUOTE_RESPONSE_TO_MIGUEL_SLASH_QUOTE_MERC_OR_RPC_LETGO )
2308 		{
2309 			class CharacterDialogueEventPCTriggerNPC : public CharacterDialogueEvent
2310 			{
2311 				public:
2312 					CharacterDialogueEventPCTriggerNPC(SOLDIERTYPE& s) : CharacterDialogueEvent(s) {}
2313 
2314 					bool Execute()
2315 					{
2316 						if (!MayExecute()) return true;
2317 
2318 						SOLDIERTYPE const& s = soldier_;
2319 						ExecuteCharacterDialogue(s.ubProfile, QUOTE_RESPONSE_TO_MIGUEL_SLASH_QUOTE_MERC_OR_RPC_LETGO, s.face, DIALOGUE_TACTICAL_UI, TRUE, false);
2320 
2321 						// Setup face with data!
2322 						FACETYPE& f = *gpCurrentTalkingFace;
2323 						f.uiFlags          |= FACE_PCTRIGGER_NPC;
2324 						f.u.trigger.npc     = MIGUEL;
2325 						f.u.trigger.record  = 6;
2326 
2327 						return false;
2328 					}
2329 			};
2330 
2331 			DialogueEvent::Add(new CharacterDialogueEventPCTriggerNPC(*chosen));
2332 			++giNPCReferenceCount;
2333 		}
2334 		else
2335 		{
2336 			TacticalCharacterDialogue(chosen, pQuotePtr->ubTriggerNPCRec);
2337 		}
2338 	}
2339 
2340 }
2341 
TriggerNPCWithIHateYouQuote(UINT8 ubTriggerNPC)2342 BOOLEAN TriggerNPCWithIHateYouQuote( UINT8 ubTriggerNPC )
2343 {
2344 	UINT8					ubLoop;
2345 
2346 	NPCQuoteInfo* const pNPCQuoteInfoArray = EnsureQuoteFileLoaded(ubTriggerNPC);
2347 	if (!pNPCQuoteInfoArray) return FALSE; // error
2348 
2349 	for ( ubLoop = 0; ubLoop < NUM_NPC_QUOTE_RECORDS; ubLoop++ )
2350 	{
2351 		if ( NPCConsiderQuote( ubTriggerNPC, 0, APPROACH_DECLARATION_OF_HOSTILITY, ubLoop, 0, pNPCQuoteInfoArray ) )
2352 		{
2353 			// trigger this quote!
2354 			NPCTriggerNPC(ubTriggerNPC, ubLoop, APPROACH_DECLARATION_OF_HOSTILITY, true);
2355 			gMercProfiles[ ubTriggerNPC ].ubMiscFlags |= PROFILE_MISC_FLAG_SAID_HOSTILE_QUOTE;
2356 			return( TRUE );
2357 		}
2358 	}
2359 	return( FALSE );
2360 
2361 }
2362 
2363 
NPCHasUnusedRecordWithGivenApproach(UINT8 const ubNPC,Approach const approach)2364 BOOLEAN NPCHasUnusedRecordWithGivenApproach(UINT8 const ubNPC, Approach const approach)
2365 {
2366 	// Check if we have a quote that could be used
2367 
2368 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
2369 	if (!quotes) return FALSE; // error
2370 
2371 	for (UINT8 i = 0; i != NUM_NPC_QUOTE_RECORDS; ++i)
2372 	{
2373 		if (!NPCConsiderQuote(ubNPC, 0, approach, i, 0, quotes)) continue;
2374 		return TRUE;
2375 	}
2376 	return FALSE;
2377 }
2378 
2379 
NPCHasUnusedHostileRecord(UINT8 const ubNPC,Approach const approach)2380 BOOLEAN NPCHasUnusedHostileRecord(UINT8 const ubNPC, Approach const approach)
2381 {
2382 	/* this is just like the standard check BUT we must skip any records using
2383 	 * fact 289 and print debug msg for any records which can't be marked as used
2384 	 */
2385 	// Check if we have a quote that could be used
2386 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
2387 	if (!quotes) return FALSE; // error
2388 
2389 	for (UINT8 i = 0; i != NUM_NPC_QUOTE_RECORDS; ++i)
2390 	{
2391 		if (!NPCConsiderQuote(ubNPC, 0, approach, i, 0, quotes)) continue;
2392 		NPCQuoteInfo const& q = quotes[i];
2393 		if (q.usFactMustBeTrue == FACT_NPC_HOSTILE_OR_PISSED_OFF) continue;
2394 		return TRUE;
2395 	}
2396 	return FALSE;
2397 }
2398 
2399 
NPCWillingToAcceptItem(UINT8 const ubNPC,UINT8 const ubMerc,OBJECTTYPE * const o)2400 BOOLEAN NPCWillingToAcceptItem(UINT8 const ubNPC, UINT8 const ubMerc, OBJECTTYPE* const o)
2401 {
2402 	// Check if we have a quote that could be used, that applies to this item
2403 
2404 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
2405 	if (!quotes) return FALSE; // error
2406 
2407 	NPCQuoteInfo* q;
2408 	UINT8         ubQuoteNum;
2409 	NPCConsiderReceivingItemFromMerc(ubNPC, ubMerc, o, quotes, &q, &ubQuoteNum);
2410 	return q != 0;
2411 }
2412 
2413 
GetInfoForAbandoningEPC(UINT8 const ubNPC,UINT16 * const pusQuoteNum,Fact * const fact_to_set_true)2414 BOOLEAN GetInfoForAbandoningEPC(UINT8 const ubNPC, UINT16* const pusQuoteNum, Fact* const fact_to_set_true)
2415 {
2416 	// Check if we have a quote that could be used
2417 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
2418 	if (!quotes) return FALSE; // error
2419 
2420 	for (UINT8 i = 0; i != NUM_NPC_QUOTE_RECORDS; ++i)
2421 	{
2422 		if (!NPCConsiderQuote(ubNPC, 0, APPROACH_EPC_IN_WRONG_SECTOR, i, 0, quotes)) continue;
2423 		NPCQuoteInfo const& q = quotes[i];
2424 		*pusQuoteNum      = q.ubQuoteNum;
2425 		*fact_to_set_true = (Fact)q.usSetFactTrue;
2426 		return TRUE;
2427 	}
2428 	return FALSE;
2429 }
2430 
2431 
TriggerNPCWithGivenApproach(UINT8 const ubTriggerNPC,Approach const approach)2432 BOOLEAN TriggerNPCWithGivenApproach(UINT8 const ubTriggerNPC, Approach const approach)
2433 {
2434 	// Check if we have a quote to trigger...
2435 
2436 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubTriggerNPC);
2437 	if (!quotes) return FALSE; // error
2438 
2439 	for (UINT8 i = 0; i != NUM_NPC_QUOTE_RECORDS; ++i)
2440 	{
2441 		if (!NPCConsiderQuote(ubTriggerNPC, 0, approach, i, 0, quotes)) continue;
2442 
2443 		bool const show_panel = quotes[i].ubQuoteNum != IRRELEVANT;
2444 		NPCTriggerNPC(ubTriggerNPC, i, approach, show_panel);
2445 		return TRUE;
2446 	}
2447 	return FALSE;
2448 }
2449 
2450 
SaveNPCInfoToSaveGameFile(HWFILE const f)2451 void SaveNPCInfoToSaveGameFile(HWFILE const f)
2452 {
2453 	FOR_EACH(NPCQuoteInfo* const, i, gpNPCQuoteInfoArray)
2454 	{
2455 		ConditionalInjectNPCQuoteInfoArrayIntoFile(f, *i);
2456 	}
2457 
2458 	FOR_EACH(NPCQuoteInfo* const, i, gpCivQuoteInfoArray)
2459 	{
2460 		ConditionalInjectNPCQuoteInfoArrayIntoFile(f, *i);
2461 	}
2462 }
2463 
2464 
LoadNPCInfoFromSavedGameFile(HWFILE const f,UINT32 const uiSaveGameVersion)2465 void LoadNPCInfoFromSavedGameFile(HWFILE const f, UINT32 const uiSaveGameVersion)
2466 {
2467 	UINT32		cnt;
2468 	UINT32		uiNumberToLoad=0;
2469 
2470 	// If we are trying to restore a saved game prior to version 44, use the
2471 	// MAX_NUM_SOLDIERS, else use NUM_PROFILES.  Dave used the wrong define!
2472 	if( uiSaveGameVersion >= 44 )
2473 		uiNumberToLoad = NUM_PROFILES;
2474 	else
2475 		uiNumberToLoad = MAX_NUM_SOLDIERS;
2476 
2477 	//Loop through all the NPC quotes
2478 	for( cnt=0; cnt<uiNumberToLoad; cnt++ )
2479 	{
2480 		ConditionalExtractNPCQuoteInfoArrayFromFile(f, gpNPCQuoteInfoArray[cnt]);
2481 	}
2482 
2483 	if ( uiSaveGameVersion >= 56 )
2484 	{
2485 		for( cnt = 0; cnt < NUM_CIVQUOTE_SECTORS; cnt++)
2486 		{
2487 			ConditionalExtractNPCQuoteInfoArrayFromFile(f, gpCivQuoteInfoArray[cnt]);
2488 		}
2489 	}
2490 
2491 	if ( uiSaveGameVersion < 88 )
2492 	{
2493 		RefreshNPCScriptRecord( NO_PROFILE, 5 ); // special pass-in value for "replace PC scripts"
2494 		RefreshNPCScriptRecord( DARYL, 11 );
2495 		RefreshNPCScriptRecord( DARYL, 14 );
2496 		RefreshNPCScriptRecord( DARYL, 15 );
2497 	}
2498 	if ( uiSaveGameVersion < 89 )
2499 	{
2500 		RefreshNPCScriptRecord( KINGPIN, 23 );
2501 		RefreshNPCScriptRecord( KINGPIN, 27 );
2502 	}
2503 	if ( uiSaveGameVersion < 90 )
2504 	{
2505 		RefreshNPCScriptRecord( KINGPIN, 25 );
2506 		RefreshNPCScriptRecord( KINGPIN, 26 );
2507 	}
2508 	if ( uiSaveGameVersion < 92 )
2509 	{
2510 		RefreshNPCScriptRecord( MATT, 14 );
2511 		RefreshNPCScriptRecord( AUNTIE, 8 );
2512 	}
2513 	if ( uiSaveGameVersion < 93 )
2514 	{
2515 		RefreshNPCScriptRecord( JENNY, 7 );
2516 		RefreshNPCScriptRecord( JENNY, 8 );
2517 		RefreshNPCScriptRecord( FRANK, 7 );
2518 		RefreshNPCScriptRecord( FRANK, 8 );
2519 		RefreshNPCScriptRecord( FATHER, 12 );
2520 		RefreshNPCScriptRecord( FATHER, 13 );
2521 	}
2522 	if ( uiSaveGameVersion < 94 )
2523 	{
2524 		RefreshNPCScriptRecord( CONRAD, 0 );
2525 		RefreshNPCScriptRecord( CONRAD, 2 );
2526 		RefreshNPCScriptRecord( CONRAD, 9 );
2527 	}
2528 	if ( uiSaveGameVersion < 95 )
2529 	{
2530 		RefreshNPCScriptRecord( WALDO, 6 );
2531 		RefreshNPCScriptRecord( WALDO, 7 );
2532 		RefreshNPCScriptRecord( WALDO, 10 );
2533 		RefreshNPCScriptRecord( WALDO, 11 );
2534 		RefreshNPCScriptRecord( WALDO, 12 );
2535 	}
2536 	if ( uiSaveGameVersion < 96 )
2537 	{
2538 		RefreshNPCScriptRecord( HANS, 18 );
2539 		RefreshNPCScriptRecord( ARMAND, 13 );
2540 		RefreshNPCScriptRecord( DARREN, 4 );
2541 		RefreshNPCScriptRecord( DARREN, 5 );
2542 	}
2543 	if ( uiSaveGameVersion < 97 )
2544 	{
2545 		RefreshNPCScriptRecord( JOHN, 22 );
2546 		RefreshNPCScriptRecord( JOHN, 23 );
2547 		RefreshNPCScriptRecord( SKYRIDER, 19 );
2548 		RefreshNPCScriptRecord( SKYRIDER, 21 );
2549 		RefreshNPCScriptRecord( SKYRIDER, 22 );
2550 	}
2551 
2552 	if ( uiSaveGameVersion < 98 )
2553 	{
2554 		RefreshNPCScriptRecord( SKYRIDER, 19 );
2555 		RefreshNPCScriptRecord( SKYRIDER, 21 );
2556 		RefreshNPCScriptRecord( SKYRIDER, 22 );
2557 	}
2558 }
2559 
2560 
SaveBackupNPCInfoToSaveGameFile(HWFILE const f)2561 void SaveBackupNPCInfoToSaveGameFile(HWFILE const f)
2562 {
2563 	FOR_EACH(NPCQuoteInfo* const, i, gpBackupNPCQuoteInfoArray)
2564 	{
2565 		ConditionalInjectNPCQuoteInfoArrayIntoFile(f, *i);
2566 	}
2567 }
2568 
2569 
LoadBackupNPCInfoFromSavedGameFile(HWFILE const f)2570 void LoadBackupNPCInfoFromSavedGameFile(HWFILE const f)
2571 {
2572 	FOR_EACH(NPCQuoteInfo*, i, gpBackupNPCQuoteInfoArray)
2573 	{
2574 		ConditionalExtractNPCQuoteInfoArrayFromFile(f, *i);
2575 	}
2576 }
2577 
2578 
TriggerFriendWithHostileQuote(UINT8 ubNPC)2579 void TriggerFriendWithHostileQuote( UINT8 ubNPC )
2580 {
2581 	UINT8						ubMercsAvailable[ 40 ] = { 0 };
2582 	UINT8						ubNumMercsAvailable = 0, ubChosenMerc;
2583 
2584 	SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubNPC);
2585 	if (!pSoldier)
2586 	{
2587 		return;
2588 	}
2589 	const INT8 bTeam = pSoldier->bTeam;
2590 
2591 	// Loop through all our guys and find one to yell
2592 	CFOR_EACH_IN_TEAM(s, bTeam)
2593 	{
2594 		// Add guy if he's a candidate...
2595 		if (pSoldier->bInSector && s->bLife >= OKLIFE && s->bBreath >= OKBREATH && s->bOppCnt > 0 && s->ubProfile != NO_PROFILE)
2596 		{
2597 			if (bTeam == CIV_TEAM && pSoldier->ubCivilianGroup != NON_CIV_GROUP && s->ubCivilianGroup != pSoldier->ubCivilianGroup)
2598 			{
2599 				continue;
2600 			}
2601 
2602 			if (!(gMercProfiles[s->ubProfile].ubMiscFlags & PROFILE_MISC_FLAG_SAID_HOSTILE_QUOTE))
2603 			{
2604 				ubMercsAvailable[ubNumMercsAvailable] = s->ubProfile;
2605 				ubNumMercsAvailable++;
2606 			}
2607 		}
2608 	}
2609 
2610 	if ( bTeam == CIV_TEAM && pSoldier->ubCivilianGroup != NON_CIV_GROUP && gTacticalStatus.fCivGroupHostile[ pSoldier->ubCivilianGroup ] == CIV_GROUP_NEUTRAL )
2611 	{
2612 		CivilianGroupMemberChangesSides( pSoldier );
2613 	}
2614 
2615 	if (ubNumMercsAvailable > 0)
2616 	{
2617 		PauseAITemporarily();
2618 		ubChosenMerc = (UINT8) Random( ubNumMercsAvailable );
2619 		TriggerNPCWithIHateYouQuote( ubMercsAvailable[ ubChosenMerc ] );
2620 	}
2621 	else
2622 	{
2623 		// done... we should enter combat mode with this soldier's team starting,
2624 		// after all the dialogue is completed
2625 		NPCDoAction( ubNPC, NPC_ACTION_ENTER_COMBAT, 0 );
2626 	}
2627 }
2628 
2629 
ActionIDForMovementRecord(UINT8 const ubNPC,UINT8 const record)2630 UINT8 ActionIDForMovementRecord(UINT8 const ubNPC, UINT8 const record)
2631 {
2632 	// Check if we have a quote to trigger...
2633 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
2634 	if (!quotes) return FALSE; // error
2635 
2636 	switch (quotes[record].sActionData)
2637 	{
2638 		case NPC_ACTION_TRAVERSE_MAP_EAST:  return QUOTE_ACTION_ID_TRAVERSE_EAST;
2639 		case NPC_ACTION_TRAVERSE_MAP_SOUTH: return QUOTE_ACTION_ID_TRAVERSE_SOUTH;
2640 		case NPC_ACTION_TRAVERSE_MAP_WEST:  return QUOTE_ACTION_ID_TRAVERSE_WEST;
2641 		case NPC_ACTION_TRAVERSE_MAP_NORTH: return QUOTE_ACTION_ID_TRAVERSE_NORTH;
2642 		default:                            return QUOTE_ACTION_ID_CHECKFORDEST;
2643 	}
2644 }
2645 
2646 
HandleNPCChangesForTacticalTraversal(const SOLDIERTYPE * s)2647 void HandleNPCChangesForTacticalTraversal(const SOLDIERTYPE* s)
2648 {
2649 	if (!s || s->ubProfile == NO_PROFILE || s->fAIFlags & AI_CHECK_SCHEDULE)
2650 	{
2651 		return;
2652 	}
2653 
2654 	MERCPROFILESTRUCT& p = GetProfile(s->ubProfile);
2655 	switch (s->ubQuoteActionID)
2656 	{
2657 		case QUOTE_ACTION_ID_TRAVERSE_EAST:  p.sSectorX++; break;
2658 		case QUOTE_ACTION_ID_TRAVERSE_SOUTH: p.sSectorY++; break;
2659 		case QUOTE_ACTION_ID_TRAVERSE_WEST:  p.sSectorX--; break;
2660 		case QUOTE_ACTION_ID_TRAVERSE_NORTH: p.sSectorY--; break;
2661 		default: return;
2662 	}
2663 
2664 	// Call to change the NPC's Sector Location
2665 	ChangeNpcToDifferentSector(p, p.sSectorX, p.sSectorY, p.bSectorZ);
2666 }
2667 
2668 
HandleVictoryInNPCSector(INT16 const x,INT16 const y,INT16 const z)2669 void HandleVictoryInNPCSector(INT16 const x, INT16 const y, INT16 const z)
2670 {
2671 	// handle special cases of victory in certain sector
2672 
2673 	// not the surface?..leave
2674 	if (z != 0) return;
2675 
2676 	switch (SECTOR(x, y))
2677 	{
2678 		case SEC_F10:
2679 			FOR_EACH_IN_TEAM(s, CIV_TEAM)
2680 			{
2681 				// hillbilies are still alive?..leave
2682 				if (s->ubCivilianGroup == HICKS_CIV_GROUP && s->bLife > 0) return;
2683 			}
2684 
2685 			// we won over the hillbillies
2686 			// set fact they are dead
2687 			if (!CheckFact(FACT_HILLBILLIES_KILLED, KEITH))
2688 			{
2689 				SetFactTrue(FACT_HILLBILLIES_KILLED);
2690 			}
2691 
2692 			// check if keith is out of business
2693 			if (CheckFact(FACT_KEITH_OUT_OF_BUSINESS, KEITH) == TRUE)
2694 			{
2695 				SetFactFalse(FACT_KEITH_OUT_OF_BUSINESS);
2696 			}
2697 			break;
2698 	}
2699 }
2700 
2701 
HandleShopKeepHasBeenShutDown(UINT8 ubCharNum)2702 BOOLEAN HandleShopKeepHasBeenShutDown( UINT8 ubCharNum )
2703 {
2704 	// check if shopkeep has been shutdown, if so handle
2705 	switch( ubCharNum )
2706 	{
2707 		case( KEITH ):
2708 		{
2709 			// if keith out of business, do action and leave
2710 			if( CheckFact( FACT_KEITH_OUT_OF_BUSINESS, KEITH ) == TRUE )
2711 			{
2712 				TriggerNPCRecord( KEITH, 11 );
2713 
2714 				return( TRUE );
2715 			}
2716 			else if( CheckFact( FACT_LOYALTY_LOW, KEITH ) == TRUE )
2717 			{
2718 				// loyalty is too low
2719 				TriggerNPCRecord( KEITH, 7 );
2720 
2721 				return( TRUE );
2722 			}
2723 		}
2724 	}
2725 
2726 	return( FALSE );
2727 }
2728 
UpdateDarrelScriptToGoTo(SOLDIERTYPE * pSoldier)2729 void UpdateDarrelScriptToGoTo( SOLDIERTYPE * pSoldier )
2730 {
2731 	// change destination in Darrel record 10 to go to a gridno adjacent to the
2732 	// soldier's gridno, and destination in record 11
2733 	const SOLDIERTYPE* const pDarrel = FindSoldierByProfileID(DARREL);
2734 	if ( !pDarrel )
2735 	{
2736 		return;
2737 	}
2738 
2739 	// find a spot to an alternate location nearby
2740 	INT16 sAdjustedGridNo = FindGridNoFromSweetSpotExcludingSweetSpot(pDarrel, pSoldier->sGridNo, 5);
2741 	if (sAdjustedGridNo == NOWHERE)
2742 	{
2743 		// yikes! try again with a bigger radius!
2744 		sAdjustedGridNo = FindGridNoFromSweetSpotExcludingSweetSpot(pDarrel, pSoldier->sGridNo, 10);
2745 		if (sAdjustedGridNo == NOWHERE)
2746 		{
2747 			// ok, now we're completely foobar
2748 			return;
2749 		}
2750 	}
2751 
2752 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(DARREL);
2753 	quotes[10].usGoToGridno    = sAdjustedGridNo;
2754 	quotes[11].sRequiredGridno = -sAdjustedGridNo;
2755 	quotes[11].ubTriggerNPC    = pSoldier->ubProfile;
2756 }
2757 
2758 
RecordHasDialogue(UINT8 const ubNPC,UINT8 const ubRecord)2759 BOOLEAN RecordHasDialogue(UINT8 const ubNPC, UINT8 const ubRecord)
2760 {
2761 	NPCQuoteInfo* const quotes = EnsureQuoteFileLoaded(ubNPC);
2762 	if (!quotes) return FALSE; // error
2763 	NPCQuoteInfo const& q = quotes[ubRecord];
2764 	return q.ubQuoteNum != NO_QUOTE && q.ubQuoteNum != 0;
2765 }
2766 
2767 
FindCivQuoteFileIndex(INT16 const x,INT16 const y,INT16 const z)2768 static INT8 FindCivQuoteFileIndex(INT16 const x, INT16 const y, INT16 const z)
2769 {
2770 	if (z > 0) return MINERS_CIV_QUOTE_INDEX;
2771 
2772 	for (UINT8 i = 0; i != NUM_CIVQUOTE_SECTORS; ++i)
2773 	{
2774 		if (gsCivQuoteSector[i][0] != x) continue;
2775 		if (gsCivQuoteSector[i][1] != y) continue;
2776 		return i;
2777 	}
2778 	return -1;
2779 }
2780 
2781 
ConsiderCivilianQuotes(INT16 const x,INT16 const y,INT16 const z,BOOLEAN const set_as_used)2782 INT8 ConsiderCivilianQuotes(INT16 const x, INT16 const y, INT16 const z, BOOLEAN const set_as_used)
2783 {
2784 	INT8 const quote_file_idx = FindCivQuoteFileIndex(x, y, z);
2785 	if (quote_file_idx == -1) return -1; // no hints for this sector
2786 
2787 	NPCQuoteInfo* const quotes = EnsureCivQuoteFileLoaded(quote_file_idx);
2788 	if (!quotes) return -1; // error
2789 
2790 	for (INT8 i = 0; i != NUM_NPC_QUOTE_RECORDS; ++i)
2791 	{
2792 		if (!NPCConsiderQuote(NO_PROFILE, NO_PROFILE, APPROACH_NONE, i, 0, quotes)) continue;
2793 		NPCQuoteInfo& q = quotes[i];
2794 
2795 		if (set_as_used)
2796 		{
2797 			TURN_FLAG_ON(q.fFlags, QUOTE_FLAG_SAID);
2798 		}
2799 
2800 		if (q.ubStartQuest != NO_QUEST)
2801 		{
2802 			StartQuest(q.ubStartQuest, gWorldSectorX, gWorldSectorY);
2803 		}
2804 
2805 		return q.ubQuoteNum;
2806 	}
2807 
2808 	return -1;
2809 }
2810