1 #include "Quests.h"
2 
3 #include "Arms_Dealer_Init.h"
4 #include "Boxing.h"
5 #include "Campaign.h"
6 #include "ContentManager.h"
7 #include "FactParamsModel.h"
8 #include "FileMan.h"
9 #include "Game_Clock.h"
10 #include "GameInstance.h"
11 #include "GameSettings.h"
12 #include "History.h"
13 #include "Interface_Dialogue.h"
14 #include "Isometric_Utils.h"
15 #include "Items.h"
16 #include "Map_Screen_Helicopter.h"
17 #include "MapScreen.h"
18 #include "Overhead.h"
19 #include "Queen_Command.h"
20 #include "Render_Fun.h"
21 #include "ShippingDestinationModel.h"
22 #include "Soldier_Profile.h"
23 #include "Strategic_Event_Handler.h"
24 #include "Strategic_Mines.h"
25 #include "Strategic_Town_Loyalty.h"
26 #include "StrategicMap.h"
27 #include "Tactical_Save.h"
28 #include "Town_Militia.h"
29 
30 #include <algorithm>
31 #include <iterator>
32 
33 #define TESTQUESTS
34 
35 extern SOLDIERTYPE * gpSrcSoldier;
36 extern SOLDIERTYPE * gpDestSoldier;
37 
38 UINT8 gubQuest[MAX_QUESTS];
39 UINT8 gubFact[ NUM_FACTS ]; // this has to be updated when we figure out how many facts we have
40 
41 
SetFactTrue(Fact const usFact)42 void SetFactTrue(Fact const usFact)
43 {
44 	// This function is here just for control flow purposes (debug breakpoints)
45 	// and code is more readable that way
46 
47 	// must intercept when Jake is first trigered to start selling fuel
48 	if (usFact == FACT_ESTONI_REFUELLING_POSSIBLE && !CheckFact(usFact, 0))
49 	{
50 		// give him some gas...
51 		GuaranteeAtLeastXItemsOfIndex( ARMS_DEALER_JAKE, GAS_CAN, ( UINT8 ) ( 4 + Random( 3 ) ) );
52 	}
53 
54 	gubFact[usFact] = TRUE;
55 }
56 
57 
SetFactFalse(Fact const usFact)58 void SetFactFalse(Fact const usFact)
59 {
60 	gubFact[usFact] = FALSE;
61 }
62 
63 
CheckForNewShipment(void)64 static bool CheckForNewShipment(void)
65 {
66 	auto shippingDest = GCM->getPrimaryShippingDestination();
67 	if (gWorldSectorX  != shippingDest->deliverySectorX) return false;
68 	if (gWorldSectorY  != shippingDest->deliverySectorY) return false;
69 	if (gbWorldSectorZ != shippingDest->deliverySectorZ) return false;
70 
71 	ITEM_POOL const* const ip = GetItemPool(shippingDest->deliverySectorGridNo, 0);
72 	return ip && !IsItemPoolVisible(ip);
73 }
74 
75 
CheckNPCWounded(UINT8 const ubProfileID,BOOLEAN const fByPlayerOnly)76 static BOOLEAN CheckNPCWounded(UINT8 const ubProfileID, BOOLEAN const fByPlayerOnly)
77 {
78 	SOLDIERTYPE const* const s = FindSoldierByProfileID(ubProfileID);
79 	return
80 		s &&
81 		s->bLife < s->bLifeMax && // is the NPC is wounded at all?
82 		(
83 			!fByPlayerOnly ||
84 			GetProfile(ubProfileID).ubMiscFlags & PROFILE_MISC_FLAG_WOUNDEDBYPLAYER
85 		);
86 }
87 
88 
CheckNPCInOkayHealth(UINT8 ubProfileID)89 static BOOLEAN CheckNPCInOkayHealth(UINT8 ubProfileID)
90 {
91 	// is the NPC at better than half health?
92 	const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubProfileID);
93 	if (pSoldier && pSoldier->bLife > (pSoldier->bLifeMax / 2) && pSoldier->bLife > 30)
94 	{
95 		return( TRUE );
96 	}
97 	else
98 	{
99 		return( FALSE );
100 	}
101 }
102 
103 
CheckNPCBleeding(UINT8 ubProfileID)104 static BOOLEAN CheckNPCBleeding(UINT8 ubProfileID)
105 {
106 	// the NPC is wounded...
107 	const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubProfileID);
108 	if (pSoldier && pSoldier->bLife > 0 && pSoldier->bBleeding > 0)
109 	{
110 		return( TRUE );
111 	}
112 	else
113 	{
114 		return( FALSE );
115 	}
116 
117 }
118 
119 
CheckNPCWithin(UINT8 ubFirstNPC,UINT8 ubSecondNPC,UINT8 ubMaxDistance)120 static BOOLEAN CheckNPCWithin(UINT8 ubFirstNPC, UINT8 ubSecondNPC, UINT8 ubMaxDistance)
121 {
122 	const SOLDIERTYPE* const pFirstNPC  = FindSoldierByProfileID(ubFirstNPC);
123 	const SOLDIERTYPE* const pSecondNPC = FindSoldierByProfileID(ubSecondNPC);
124 	if (!pFirstNPC || !pSecondNPC)
125 	{
126 		return( FALSE );
127 	}
128 	return( PythSpacesAway( pFirstNPC->sGridNo, pSecondNPC->sGridNo ) <= ubMaxDistance );
129 }
130 
131 
CheckGuyVisible(UINT8 ubNPC,UINT8 ubGuy)132 static BOOLEAN CheckGuyVisible(UINT8 ubNPC, UINT8 ubGuy)
133 {
134 	// NB ONLY WORKS IF ON DIFFERENT TEAMS
135 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubNPC);
136 	const SOLDIERTYPE* const pGuy = FindSoldierByProfileID(ubGuy);
137 	if (!pNPC || !pGuy)
138 	{
139 		return( FALSE );
140 	}
141 	if (pNPC->bOppList[ pGuy->ubID ] == SEEN_CURRENTLY )
142 	{
143 		return( TRUE );
144 	}
145 	else
146 	{
147 		return( FALSE );
148 	}
149 }
150 
151 
CheckNPCAt(UINT8 ubNPC,INT16 sGridNo)152 static BOOLEAN CheckNPCAt(UINT8 ubNPC, INT16 sGridNo)
153 {
154 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubNPC);
155 	if (!pNPC)
156 	{
157 		return( FALSE );
158 	}
159 	return( pNPC->sGridNo == sGridNo );
160 }
161 
162 
CheckNPCIsEnemy(UINT8 ubProfileID)163 static BOOLEAN CheckNPCIsEnemy(UINT8 ubProfileID)
164 {
165 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubProfileID);
166 	if (!pNPC)
167 	{
168 		return( FALSE );
169 	}
170 	if (pNPC->bSide == OUR_TEAM || pNPC->bNeutral)
171 	{
172 		if (pNPC->ubCivilianGroup != NON_CIV_GROUP)
173 		{
174 			// although the soldier is NOW the same side, this civ group could be set to "will become hostile"
175 			return( gTacticalStatus.fCivGroupHostile[ pNPC->ubCivilianGroup ] >= CIV_GROUP_WILL_BECOME_HOSTILE );
176 		}
177 		else
178 		{
179 			return( FALSE );
180 		}
181 	}
182 	else
183 	{
184 		return( TRUE );
185 	}
186 }
187 
188 
NumWoundedMercsNearby(ProfileID const pid)189 static INT8 NumWoundedMercsNearby(ProfileID const pid)
190 {
191 	SOLDIERTYPE const* const npc = FindSoldierByProfileID(pid);
192 	if (!npc) return 0;
193 
194 	INT8         n      = 0;
195 	GridNo const gridno = npc->sGridNo;
196 	FOR_EACH_MERC(i)
197 	{
198 		SOLDIERTYPE const& s = **i;
199 		if (s.bTeam != OUR_TEAM)                                        continue;
200 		if (s.bLife <= 0 || s.bLifeMax <= s.bLife)                         continue;
201 		if (s.bAssignment == ASSIGNMENT_HOSPITAL)                          continue;
202 		if (PythSpacesAway(gridno, s.sGridNo) > HOSPITAL_PATIENT_DISTANCE) continue;
203 		++n;
204 	}
205 	return n;
206 }
207 
208 
NumMercsNear(ProfileID const pid,UINT8 const max_dist)209 static INT8 NumMercsNear(ProfileID const pid, UINT8 const max_dist)
210 {
211 	SOLDIERTYPE const* const npc = FindSoldierByProfileID(pid);
212 	if (!npc) return 0;
213 
214 	INT8         n      = 0;
215 	GridNo const gridno = npc->sGridNo;
216 	FOR_EACH_MERC(i)
217 	{
218 		SOLDIERTYPE const& s = **i;
219 		if (s.bTeam != OUR_TEAM)                        continue;
220 		if (s.bLife <  OKLIFE)                             continue;
221 		if (PythSpacesAway(gridno, s.sGridNo) <= max_dist) continue;
222 		++n;
223 	}
224 	return n;
225 }
226 
227 
CheckNPCIsEPC(UINT8 ubProfileID)228 static BOOLEAN CheckNPCIsEPC(UINT8 ubProfileID)
229 {
230 	if ( gMercProfiles[ ubProfileID ].bMercStatus == MERC_IS_DEAD )
231 	{
232 		return( FALSE );
233 	}
234 
235 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileIDOnPlayerTeam(ubProfileID);
236 	if (!pNPC)
237 	{
238 		return( FALSE );
239 	}
240 	return( (pNPC->ubWhatKindOfMercAmI == MERC_TYPE__EPC ) );
241 }
242 
243 
NPCInRoom(UINT8 ubProfileID,UINT8 ubRoomID)244 BOOLEAN NPCInRoom(UINT8 ubProfileID, UINT8 ubRoomID)
245 {
246 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubProfileID);
247 	if ( !pNPC || (gubWorldRoomInfo[ pNPC->sGridNo ] != ubRoomID) )
248 	{
249 		return( FALSE );
250 	}
251 	return( TRUE );
252 }
253 
254 
NPCInRoomRange(UINT8 ubProfileID,UINT8 ubRoomID1,UINT8 ubRoomID2)255 static BOOLEAN NPCInRoomRange(UINT8 ubProfileID, UINT8 ubRoomID1, UINT8 ubRoomID2)
256 {
257 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubProfileID);
258 	if ( !pNPC || (gubWorldRoomInfo[ pNPC->sGridNo ] < ubRoomID1) || (gubWorldRoomInfo[ pNPC->sGridNo ] > ubRoomID2) )
259 	{
260 		return( FALSE );
261 	}
262 	return( TRUE );
263 }
264 
265 
PCInSameRoom(UINT8 ubProfileID)266 static BOOLEAN PCInSameRoom(UINT8 ubProfileID)
267 {
268 	UINT8						ubRoom;
269 
270 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubProfileID);
271 	if ( !pNPC )
272 	{
273 		return( FALSE );
274 	}
275 	ubRoom = gubWorldRoomInfo[ pNPC->sGridNo ];
276 
277 	CFOR_EACH_IN_TEAM(s, OUR_TEAM)
278 	{
279 		if (s->bInSector && gubWorldRoomInfo[s->sGridNo] == ubRoom)
280 		{
281 			return TRUE;
282 		}
283 	}
284 
285 	return( FALSE );
286 }
287 
288 
CheckTalkerStrong(void)289 static BOOLEAN CheckTalkerStrong(void)
290 {
291 	if (gpSrcSoldier && gpSrcSoldier->bTeam == OUR_TEAM)
292 	{
293 		return( gpSrcSoldier->bStrength >= 84 );
294 	}
295 	else if (gpDestSoldier && gpDestSoldier->bTeam == OUR_TEAM)
296 	{
297 		return( gpDestSoldier->bStrength >= 84 );
298 	}
299 	return( FALSE );
300 }
301 
302 
CheckTalkerFemale(void)303 static BOOLEAN CheckTalkerFemale(void)
304 {
305 	if (gpSrcSoldier && gpSrcSoldier->bTeam == OUR_TEAM && gpSrcSoldier->ubProfile != NO_PROFILE)
306 	{
307 		return( gMercProfiles[ gpSrcSoldier->ubProfile ].bSex == FEMALE );
308 	}
309 	else if (gpDestSoldier && gpDestSoldier->bTeam == OUR_TEAM && gpDestSoldier->ubProfile != NO_PROFILE)
310 	{
311 		return( gMercProfiles[ gpDestSoldier->ubProfile ].bSex == FEMALE );
312 	}
313 	return( FALSE );
314 }
315 
316 
CheckTalkerUnpropositionedFemale(void)317 static BOOLEAN CheckTalkerUnpropositionedFemale(void)
318 {
319 	if (gpSrcSoldier && gpSrcSoldier->bTeam == OUR_TEAM && gpSrcSoldier->ubProfile != NO_PROFILE)
320 	{
321 		if ( !(gMercProfiles[ gpSrcSoldier->ubProfile ].ubMiscFlags2 & PROFILE_MISC_FLAG2_ASKED_BY_HICKS) )
322 		{
323 			return( gMercProfiles[ gpSrcSoldier->ubProfile ].bSex == FEMALE );
324 		}
325 	}
326 	else if (gpDestSoldier && gpDestSoldier->bTeam == OUR_TEAM && gpDestSoldier->ubProfile != NO_PROFILE)
327 	{
328 		if ( !(gMercProfiles[ gpDestSoldier->ubProfile ].ubMiscFlags2 & PROFILE_MISC_FLAG2_ASKED_BY_HICKS) )
329 		{
330 			return( gMercProfiles[ gpDestSoldier->ubProfile ].bSex == FEMALE );
331 		}
332 	}
333 	return( FALSE );
334 }
335 
336 
NumMalesPresent(ProfileID const pid)337 static INT8 NumMalesPresent(ProfileID const pid)
338 {
339 	SOLDIERTYPE const* const npc = FindSoldierByProfileID(pid);
340 	if (!npc) return 0;
341 
342 	INT8         n      = 0;
343 	GridNo const gridno = npc->sGridNo;
344 	FOR_EACH_MERC(i)
345 	{
346 		SOLDIERTYPE const& s = **i;
347 		if (s.bTeam     != OUR_TEAM)            continue;
348 		if (s.bLife     <  OKLIFE)                 continue;
349 		if (s.ubProfile == NO_PROFILE)             continue;
350 		if (GetProfile(s.ubProfile).bSex != MALE)  continue;
351 		if (PythSpacesAway(gridno, s.sGridNo) > 8) continue;
352 		++n;
353 	}
354 	return n;
355 }
356 
357 
FemalePresent(ProfileID const pid)358 static bool FemalePresent(ProfileID const pid)
359 {
360 	SOLDIERTYPE const* const npc = FindSoldierByProfileID(pid);
361 	if (!npc) return false;
362 
363 	GridNo const gridno = npc->sGridNo;
364 	FOR_EACH_MERC(i)
365 	{
366 		SOLDIERTYPE const& s = **i;
367 		if (s.bTeam     != OUR_TEAM)             continue;
368 		if (s.bLife     <  OKLIFE)                  continue;
369 		if (s.ubProfile == NO_PROFILE)              continue;
370 		if (GetProfile(s.ubProfile).bSex != FEMALE) continue;
371 		if (PythSpacesAway(gridno, s.sGridNo) > 10) continue;
372 		return true;
373 	}
374 	return false;
375 }
376 
377 
CheckPlayerHasHead(void)378 static BOOLEAN CheckPlayerHasHead(void)
379 {
380 	CFOR_EACH_IN_TEAM(s, OUR_TEAM)
381 	{
382 		if (s->bLife > 0 && FindObjInObjRange(s, HEAD_2, HEAD_7) != NO_SLOT)
383 		{
384 			return TRUE;
385 		}
386 	}
387 	return FALSE;
388 }
389 
390 
CheckNPCSector(UINT8 ubProfileID,INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)391 static BOOLEAN CheckNPCSector(UINT8 ubProfileID, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ)
392 {
393 	const SOLDIERTYPE* const pSoldier = FindSoldierByProfileIDOnPlayerTeam(ubProfileID);
394 	if( pSoldier )
395 	{
396 		if (pSoldier->sSectorX == sSectorX &&
397 			pSoldier->sSectorY == sSectorY &&
398 			pSoldier->bSectorZ == bSectorZ )
399 		{
400 			return( TRUE );
401 		}
402 	}
403 	else if (gMercProfiles[ubProfileID].sSectorX == sSectorX &&
404 		gMercProfiles[ubProfileID].sSectorY == sSectorY &&
405 		gMercProfiles[ubProfileID].bSectorZ == bSectorZ )
406 	{
407 		return( TRUE );
408 	}
409 
410 	return( FALSE );
411 
412 }
413 
414 
AIMMercWithin(GridNo const gridno,INT16 const distance)415 static bool AIMMercWithin(GridNo const gridno, INT16 const distance)
416 {
417 	FOR_EACH_MERC(i)
418 	{
419 		SOLDIERTYPE const& s = **i;
420 		if (s.bTeam               != OUR_TEAM)         continue;
421 		if (s.bLife               <  OKLIFE)              continue;
422 		if (s.ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC) continue;
423 		if (PythSpacesAway(gridno, s.sGridNo) > distance) continue;
424 		return true;
425 	}
426 	return false;
427 }
428 
429 
CheckNPCCowering(UINT8 ubProfileID)430 static BOOLEAN CheckNPCCowering(UINT8 ubProfileID)
431 {
432 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubProfileID);
433 	if ( !pNPC )
434 	{
435 		return( FALSE );
436 	}
437 	return( ( (pNPC->uiStatusFlags & SOLDIER_COWERING) != 0) );
438 }
439 
440 
CountBartenders(void)441 static UINT8 CountBartenders(void)
442 {
443 	UINT8		ubLoop;
444 	UINT8		ubBartenders = 0;
445 
446 	for( ubLoop = HERVE; ubLoop <= CARLO; ubLoop++ )
447 	{
448 		if (gMercProfiles[ ubLoop ].bNPCData != 0 )
449 		{
450 			ubBartenders++;
451 		}
452 	}
453 	return( ubBartenders );
454 }
455 
456 
CheckNPCIsUnderFire(UINT8 ubProfileID)457 static BOOLEAN CheckNPCIsUnderFire(UINT8 ubProfileID)
458 {
459 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubProfileID);
460 	if ( !pNPC )
461 	{
462 		return( FALSE );
463 	}
464 	return( pNPC->bUnderFire != 0 );
465 }
466 
467 
NPCHeardShot(UINT8 ubProfileID)468 static BOOLEAN NPCHeardShot(UINT8 ubProfileID)
469 {
470 	const SOLDIERTYPE* const pNPC = FindSoldierByProfileID(ubProfileID);
471 	if ( !pNPC )
472 	{
473 		return( FALSE );
474 	}
475 	return( pNPC->ubMiscSoldierFlags & SOLDIER_MISC_HEARD_GUNSHOT );
476 }
477 
478 
InTownSectorWithTrainingLoyalty(UINT8 const sector)479 static bool InTownSectorWithTrainingLoyalty(UINT8 const sector)
480 {
481 	UINT8 const town = GetTownIdForSector(sector);
482 	return
483 		town != BLANK_SECTOR        &&
484 		gfTownUsesLoyalty[town]     &&
485 		gTownLoyalty[town].fStarted &&
486 		gTownLoyalty[town].ubRating >= MIN_RATING_TO_TRAIN_TOWN;
487 }
488 
489 
CheckFact(Fact const usFact,UINT8 const ubProfileID)490 BOOLEAN CheckFact(Fact const usFact, UINT8 const ubProfileID)
491 {
492 	INT8 bTown = -1;
493 	auto factParams = GCM->getFactParams(usFact);
494 
495 	switch( usFact )
496 	{
497 		case FACT_DIMITRI_DEAD:
498 			gubFact[ usFact ] = (gMercProfiles[ DIMITRI ].bMercStatus == MERC_IS_DEAD );
499 			break;
500 		case FACT_CURRENT_SECTOR_IS_SAFE:
501 			gubFact[FACT_CURRENT_SECTOR_IS_SAFE] = !( ( (gTacticalStatus.fEnemyInSector && NPCHeardShot( ubProfileID ) ) || gTacticalStatus.uiFlags & INCOMBAT ) );
502 			break;
503 		case FACT_BOBBYRAY_SHIPMENT_IN_TRANSIT:
504 		case FACT_NEW_BOBBYRAY_SHIPMENT_WAITING:
505 			if (gubFact[FACT_PABLO_PUNISHED_BY_PLAYER] == TRUE &&
506 					!gubFact[FACT_PABLO_RETURNED_GOODS] &&
507 					gMercProfiles[PABLO].bMercStatus != MERC_IS_DEAD)
508 			{
509 				gubFact[FACT_BOBBYRAY_SHIPMENT_IN_TRANSIT] = FALSE;
510 				gubFact[FACT_NEW_BOBBYRAY_SHIPMENT_WAITING] = FALSE;
511 			}
512 			else
513 			{
514 				if (CheckForNewShipment()) // if new stuff waiting unseen in Drassen
515 				{
516 					gubFact[FACT_BOBBYRAY_SHIPMENT_IN_TRANSIT] = FALSE;
517 					gubFact[FACT_NEW_BOBBYRAY_SHIPMENT_WAITING] = TRUE;
518 				}
519 				else if ( CountNumberOfBobbyPurchasesThatAreInTransit() > 0) // if stuff in transit
520 				{
521 					if ( gubFact[ FACT_PACKAGE_DAMAGED ] == TRUE )
522 					{
523 						gubFact[FACT_BOBBYRAY_SHIPMENT_IN_TRANSIT] = FALSE;
524 					}
525 					else
526 					{
527 						gubFact[FACT_BOBBYRAY_SHIPMENT_IN_TRANSIT] = TRUE;
528 					}
529 					gubFact[FACT_NEW_BOBBYRAY_SHIPMENT_WAITING] = FALSE;
530 				}
531 				else
532 				{
533 					gubFact[FACT_BOBBYRAY_SHIPMENT_IN_TRANSIT] = FALSE;
534 					gubFact[FACT_NEW_BOBBYRAY_SHIPMENT_WAITING] = FALSE;
535 				}
536 			}
537 			break;
538 		case FACT_NPC_WOUNDED:
539 			gubFact[FACT_NPC_WOUNDED] = CheckNPCWounded( ubProfileID, FALSE );
540 			break;
541 		case FACT_NPC_WOUNDED_BY_PLAYER:
542 			gubFact[FACT_NPC_WOUNDED_BY_PLAYER] = CheckNPCWounded( ubProfileID, TRUE );
543 			break;
544 		case FACT_IRA_NOT_PRESENT:
545 			gubFact[FACT_IRA_NOT_PRESENT] = !CheckNPCWithin( ubProfileID, IRA, 10 );
546 			break;
547 		case FACT_IRA_TALKING:
548 			gubFact[FACT_IRA_TALKING] = (gubSrcSoldierProfile == IRA);
549 			break;
550 		case FACT_IRA_UNHIRED_AND_ALIVE:
551 			if ( gMercProfiles[ IRA ].bMercStatus != MERC_IS_DEAD && CheckNPCSector( IRA, 10, 1, 1) && !(gMercProfiles[IRA].ubMiscFlags & PROFILE_MISC_FLAG_RECRUITED) )
552 			{
553 				gubFact[FACT_IRA_UNHIRED_AND_ALIVE] = TRUE;
554 			}
555 			else
556 			{
557 				gubFact[FACT_IRA_UNHIRED_AND_ALIVE] = FALSE;
558 			}
559 			break;
560 		case FACT_NPC_BLEEDING:
561 			gubFact[FACT_NPC_BLEEDING] = CheckNPCBleeding( ubProfileID );
562 			break;
563 		case FACT_NPC_BLEEDING_BUT_OKAY:
564 			if ( CheckNPCBleeding( ubProfileID ) && CheckNPCInOkayHealth( ubProfileID ) )
565 			{
566 				gubFact[FACT_NPC_BLEEDING_BUT_OKAY] = TRUE;
567 			}
568 			else
569 			{
570 				gubFact[FACT_NPC_BLEEDING_BUT_OKAY] = FALSE;
571 			}
572 			break;
573 
574 		case FACT_PLAYER_HAS_HEAD_AND_CARMEN_IN_SAN_MONA:
575 			gubFact[usFact] = (CheckNPCSector( CARMEN, 5, MAP_ROW_C, 0 ) && CheckPlayerHasHead() );
576 			break;
577 
578 		case FACT_PLAYER_HAS_HEAD_AND_CARMEN_IN_CAMBRIA:
579 			gubFact[usFact] = (CheckNPCSector( CARMEN, 9, MAP_ROW_G, 0 ) && CheckPlayerHasHead() );
580 			break;
581 
582 		case FACT_PLAYER_HAS_HEAD_AND_CARMEN_IN_DRASSEN:
583 			gubFact[usFact] = (CheckNPCSector( CARMEN, 13, MAP_ROW_C, 0 ) && CheckPlayerHasHead() );
584 			break;
585 
586 		case FACT_NPC_OWED_MONEY:
587 			gubFact[FACT_NPC_OWED_MONEY] = (ubProfileID != NO_PROFILE && gMercProfiles[ubProfileID].iBalance < 0);
588 			break;
589 
590 		case FACT_FATHER_DRUNK:
591 			gubFact[FACT_FATHER_DRUNK] = ( gMercProfiles[ FATHER ].bNPCData >= 5 );
592 			break;
593 
594 		case FACT_MICKY_DRUNK:
595 			gubFact[FACT_MICKY_DRUNK] = ( gMercProfiles[ MICKY ].bNPCData >= 5 );
596 			break;
597 
598 		case FACT_BRENDA_IN_STORE_AND_ALIVE:
599 			// ensure alive
600 			if (GetProfile(BRENDA).bMercStatus == MERC_IS_DEAD)
601 			{
602 				gubFact[FACT_BRENDA_IN_STORE_AND_ALIVE] = FALSE;
603 			}
604 			// ensure in a building and nearby
605 			else if (!(NPCInRoom(BRENDA, 47)))
606 			{
607 				gubFact[FACT_BRENDA_IN_STORE_AND_ALIVE] = FALSE;
608 			}
609 			else
610 			{
611 				gubFact[FACT_BRENDA_IN_STORE_AND_ALIVE] = CheckNPCWithin(ubProfileID, BRENDA, 12);
612 			}
613 			break;
614 		case FACT_BRENDA_DEAD:
615 			gubFact[FACT_BRENDA_DEAD] = (GetProfile(BRENDA).bMercStatus == MERC_IS_DEAD);
616 			break;
617 		case FACT_NPC_IS_ENEMY:
618 			gubFact[FACT_NPC_IS_ENEMY] = CheckNPCIsEnemy( ubProfileID ) || gMercProfiles[ ubProfileID ].ubMiscFlags2 & PROFILE_MISC_FLAG2_NEEDS_TO_SAY_HOSTILE_QUOTE;
619 			break;
620 			/*
621 		case FACT_SKYRIDER_CLOSE_TO_CHOPPER:
622 			SetUpHelicopterForPlayer( 13, MAP_ROW_B );
623 			break;
624 			*/
625 		case FACT_SPIKE_AT_DOOR:
626 			gubFact[FACT_SPIKE_AT_DOOR] = CheckNPCAt(SPIKE, factParams->getGridNo(9817));
627 			break;
628 		case FACT_WOUNDED_MERCS_NEARBY:
629 			gubFact[usFact] = (NumWoundedMercsNearby( ubProfileID ) > 0);
630 			break;
631 		case FACT_ONE_WOUNDED_MERC_NEARBY:
632 			gubFact[usFact] = (NumWoundedMercsNearby( ubProfileID ) == 1);
633 			break;
634 		case FACT_MULTIPLE_WOUNDED_MERCS_NEARBY:
635 			gubFact[usFact] = (NumWoundedMercsNearby( ubProfileID ) > 1);
636 			break;
637 		case FACT_HANS_AT_SPOT:
638 			gubFact[usFact] = CheckNPCAt(HANS, factParams->getGridNo(13523));
639 			break;
640 		case FACT_MULTIPLE_MERCS_CLOSE:
641 			gubFact[usFact] = ( NumMercsNear( ubProfileID, 3 ) > 1 );
642 			break;
643 		case FACT_SOME_MERCS_CLOSE:
644 			gubFact[usFact] = ( NumMercsNear( ubProfileID, 3 ) > 0 );
645 			break;
646 		case FACT_MARIA_ESCORTED:
647 			gubFact[usFact] = CheckNPCIsEPC( MARIA );
648 			break;
649 		case FACT_JOEY_ESCORTED:
650 			gubFact[usFact] = CheckNPCIsEPC( JOEY );
651 			break;
652 		case FACT_ESCORTING_SKYRIDER:
653 			gubFact[usFact] = CheckNPCIsEPC( SKYRIDER );
654 			break;
655 		case FACT_MARIA_ESCORTED_AT_LEATHER_SHOP:
656 			gubFact[usFact] = ( CheckNPCIsEPC( MARIA ) && (NPCInRoom( MARIA, 2 )) );
657 			break;
658 		case FACT_PC_STRONG_AND_LESS_THAN_3_MALES_PRESENT:
659 			gubFact[usFact] = ( CheckTalkerStrong() && (NumMalesPresent( ubProfileID ) < 3) );
660 			break;
661 		case FACT_PC_STRONG_AND_3_PLUS_MALES_PRESENT:
662 			gubFact[usFact] = ( CheckTalkerStrong() && (NumMalesPresent( ubProfileID ) >= 3) );
663 			break;
664 		case FACT_FEMALE_SPEAKING_TO_NPC:
665 			gubFact[usFact] = CheckTalkerFemale();
666 			break;
667 		case FACT_CARMEN_IN_C5:
668 			gubFact[usFact] = CheckNPCSector(CARMEN, 5, MAP_ROW_C, 0);
669 			break;
670 		case FACT_JOEY_IN_C5:
671 			gubFact[usFact] = CheckNPCSector(JOEY, 5, MAP_ROW_C, 0);
672 			break;
673 		case FACT_JOEY_NEAR_MARTHA:
674 			gubFact[usFact] = CheckNPCWithin(JOEY, MARTHA, 5) && (CheckGuyVisible(MARTHA, JOEY) || CheckGuyVisible(JOEY, MARTHA));
675 			break;
676 		case FACT_JOEY_DEAD:
677 			gubFact[usFact] = gMercProfiles[ JOEY ].bMercStatus == MERC_IS_DEAD;
678 			break;
679 		case FACT_MERC_NEAR_MARTHA:
680 			gubFact[usFact] = ( NumMercsNear( ubProfileID, 5 ) > 0 );
681 			break;
682 		case FACT_REBELS_HATE_PLAYER:
683 			gubFact[usFact] = (gTacticalStatus.fCivGroupHostile[ REBEL_CIV_GROUP ] == CIV_GROUP_HOSTILE);
684 			break;
685 		case FACT_CURRENT_SECTOR_G9:
686 			gubFact[usFact] = ( gWorldSectorX == 9 && gWorldSectorY == MAP_ROW_G && gbWorldSectorZ == 0 );
687 			break;
688 		case FACT_CURRENT_SECTOR_C5:
689 			gubFact[usFact] = ( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_C && gbWorldSectorZ == 0 );
690 			break;
691 		case FACT_CURRENT_SECTOR_C13:
692 			gubFact[usFact] = ( gWorldSectorX == 13 && gWorldSectorY == MAP_ROW_C && gbWorldSectorZ == 0 );
693 			break;
694 		case FACT_CARMEN_HAS_TEN_THOUSAND:
695 			gubFact[usFact] = (GetProfile(CARMEN).uiMoney >= 10000);
696 			break;
697 		case FACT_SLAY_IN_SECTOR:
698 			gubFact[usFact] = (gMercProfiles[ SLAY ].sSectorX == gWorldSectorX && gMercProfiles[ SLAY ].sSectorY == gWorldSectorY && gMercProfiles[ SLAY ].bSectorZ == gbWorldSectorZ );
699 			break;
700 		case FACT_SLAY_HIRED_AND_WORKED_FOR_48_HOURS:
701 			gubFact[usFact] = ( ( gMercProfiles[ SLAY ].ubMiscFlags & PROFILE_MISC_FLAG_RECRUITED ) && ( gMercProfiles[ SLAY ].usTotalDaysServed > 1 ) );
702 			break;
703 
704 		case FACT_SHANK_IN_SQUAD_BUT_NOT_SPEAKING:
705 			gubFact[usFact] =
706 				FindSoldierByProfileIDOnPlayerTeam(SHANK) != NULL              &&
707 				gMercProfiles[SHANK].ubMiscFlags & PROFILE_MISC_FLAG_RECRUITED &&
708 				(gpSrcSoldier == NULL || gpSrcSoldier->ubProfile != SHANK);
709 			break;
710 
711 		case FACT_SHANK_NOT_IN_SECTOR:
712 			gubFact[usFact] = FindSoldierByProfileID(SHANK) == NULL;
713 			break;
714 		case FACT_QUEEN_DEAD:
715 			gubFact[usFact] = (gMercProfiles[ QUEEN ].bMercStatus == MERC_IS_DEAD);
716 			break;
717 		case FACT_MINE_EMPTY:
718 			gubFact[usFact] = IsHisMineEmpty( ubProfileID );
719 			break;
720 		case FACT_MINE_RUNNING_OUT:
721 			gubFact[usFact] = IsHisMineRunningOut( ubProfileID );
722 			break;
723 		case FACT_MINE_PRODUCING_BUT_LOYALTY_LOW:
724 			gubFact[usFact] = HasHisMineBeenProducingForPlayerForSomeTime( ubProfileID ) && IsHisMineDisloyal( ubProfileID );
725 			break;
726 		case FACT_CREATURES_IN_MINE:
727 			gubFact[usFact] = IsHisMineInfested( ubProfileID );
728 			break;
729 		case FACT_PLAYER_LOST_MINE:
730 			gubFact[usFact] = IsHisMineLostAndRegained( ubProfileID );
731 			break;
732 		case FACT_MINE_AT_FULL_PRODUCTION:
733 			gubFact[usFact] = IsHisMineAtMaxProduction( ubProfileID );
734 			break;
735 		case FACT_DYNAMO_IN_J9:
736 			gubFact[usFact] = CheckNPCSector( DYNAMO, 9, MAP_ROW_J, 0 ) && NumEnemiesInAnySector( 9, 10, 0 );
737 			break;
738 		case FACT_DYNAMO_ALIVE:
739 			gubFact[usFact] = ( gMercProfiles[ DYNAMO ].bMercStatus != MERC_IS_DEAD );
740 			break;
741 		case FACT_DYNAMO_SPEAKING_OR_NEARBY:
742 			gubFact[usFact] = ( gpSrcSoldier != NULL && (gpSrcSoldier->ubProfile == DYNAMO || ( CheckNPCWithin( gpSrcSoldier->ubProfile, DYNAMO, 10 ) && CheckGuyVisible( gpSrcSoldier->ubProfile, DYNAMO ) ) ) );
743 			break;
744 		case FACT_JOHN_EPC:
745 			gubFact[usFact] = CheckNPCIsEPC( JOHN );
746 			break;
747 		case FACT_MARY_EPC:
748 			gubFact[usFact] = CheckNPCIsEPC( MARY );
749 			break;
750 		case FACT_JOHN_AND_MARY_EPCS:
751 			gubFact[usFact] = CheckNPCIsEPC( JOHN ) && CheckNPCIsEPC( MARY );
752 			break;
753 		case FACT_MARY_ALIVE:
754 			gubFact[usFact] = ( gMercProfiles[ MARY ].bMercStatus != MERC_IS_DEAD );
755 			break;
756 		case FACT_MARY_BLEEDING:
757 			gubFact[usFact] = CheckNPCBleeding( MARY );
758 			break;
759 		case FACT_JOHN_ALIVE:
760 			gubFact[usFact] = ( gMercProfiles[ JOHN ].bMercStatus != MERC_IS_DEAD );
761 			break;
762 		case FACT_JOHN_BLEEDING:
763 			gubFact[usFact] = CheckNPCBleeding( JOHN );
764 			break;
765 		case FACT_MARY_DEAD:
766 			gubFact[usFact] = ( gMercProfiles[ MARY ].bMercStatus == MERC_IS_DEAD );
767 			break;
768 
769 		case FACT_ANOTHER_FIGHT_POSSIBLE:
770 			gubFact[usFact] = AnotherFightPossible();
771 			break;
772 
773 		case FACT_RECEIVING_INCOME_FROM_DCAC:
774 			gubFact[usFact] = (
775 				( PredictDailyIncomeFromAMine( MINE_DRASSEN ) > 0 ) &&
776 				( PredictDailyIncomeFromAMine( MINE_ALMA ) > 0 ) &&
777 				( PredictDailyIncomeFromAMine( MINE_CAMBRIA ) > 0 ) &&
778 				( PredictDailyIncomeFromAMine( MINE_CHITZENA ) > 0 ) );
779 			break;
780 
781 		case FACT_PLAYER_BEEN_TO_K4:
782 			gubFact[usFact] = GetSectorFlagStatus(4, MAP_ROW_K, 1, SF_ALREADY_VISITED);
783 			break;
784 
785 		case FACT_WARDEN_DEAD:
786 			gubFact[usFact] = ( gMercProfiles[ WARDEN ].bMercStatus == MERC_IS_DEAD );
787 			break;
788 
789 		case FACT_PLAYER_PAID_FOR_TWO_IN_BROTHEL:
790 			gubFact[usFact] = (gMercProfiles[ MADAME ].bNPCData > 1);
791 			break;
792 
793 		case FACT_LOYALTY_OKAY:
794 			bTown = ubProfileID != NO_PROFILE ? gMercProfiles[ubProfileID].bTown : BLANK_SECTOR;
795 			if( ( bTown != BLANK_SECTOR ) && gTownLoyalty[ bTown ].fStarted && gfTownUsesLoyalty[ bTown ])
796 			{
797 				gubFact[usFact] = ( (gTownLoyalty[ bTown ].ubRating >= LOYALTY_LOW_THRESHOLD ) && (gTownLoyalty[ bTown ].ubRating < LOYALTY_OK_THRESHOLD ) );
798 			}
799 			else
800 			{
801 				gubFact[usFact] = FALSE;
802 			}
803 			break;
804 
805 		case FACT_LOYALTY_LOW:
806 			bTown = ubProfileID != NO_PROFILE ? gMercProfiles[ubProfileID].bTown : BLANK_SECTOR;
807 			if( ( bTown != BLANK_SECTOR ) && gTownLoyalty[ bTown ].fStarted && gfTownUsesLoyalty[ bTown ])
808 			{
809 				// if Skyrider, ignore low loyalty until he has monologues, and wait at least a day since the latest monologue to avoid a hot/cold attitude
810 				if ( ( ubProfileID == SKYRIDER ) &&
811 					( ( guiHelicopterSkyriderTalkState == 0 ) || ( ( GetWorldTotalMin() - guiTimeOfLastSkyriderMonologue ) < ( 24 * 60 ) ) ) )
812 				{
813 					gubFact[usFact] = FALSE;
814 				}
815 				else
816 				{
817 					gubFact[usFact] = (gTownLoyalty[ bTown ].ubRating < LOYALTY_LOW_THRESHOLD );
818 				}
819 			}
820 			else
821 			{
822 				gubFact[usFact] = FALSE;
823 			}
824 			break;
825 
826 		case FACT_LOYALTY_HIGH:
827 			bTown = ubProfileID != NO_PROFILE ? gMercProfiles[ubProfileID].bTown : BLANK_SECTOR;
828 			if( ( bTown != BLANK_SECTOR ) && gTownLoyalty[ bTown ].fStarted && gfTownUsesLoyalty[ bTown ])
829 			{
830 				gubFact[usFact] = (gTownLoyalty[ gMercProfiles[ ubProfileID ].bTown ].ubRating >= LOYALTY_HIGH_THRESHOLD );
831 			}
832 			else
833 			{
834 				gubFact[usFact] = FALSE;
835 			}
836 			break;
837 
838 		case FACT_ELGIN_ALIVE:
839 			gubFact[usFact] = ( gMercProfiles[ DRUGGIST ].bMercStatus != MERC_IS_DEAD );
840 			break;
841 
842 		case FACT_SPEAKER_AIM_OR_AIM_NEARBY:
843 			gubFact[usFact] = gpDestSoldier && AIMMercWithin( gpDestSoldier->sGridNo, 10 );
844 			break;
845 
846 		case FACT_MALE_SPEAKING_FEMALE_PRESENT:
847 			gubFact[usFact] = ( !CheckTalkerFemale() && FemalePresent( ubProfileID ) );
848 			break;
849 
850 		case FACT_PLAYER_OWNS_2_TOWNS_INCLUDING_OMERTA:
851 			gubFact[usFact] = ( ( GetNumberOfWholeTownsUnderControl() == 3 ) && IsTownUnderCompleteControlByPlayer( OMERTA ) );
852 			break;
853 
854 		case FACT_PLAYER_OWNS_3_TOWNS_INCLUDING_OMERTA:
855 			gubFact[usFact] = ( ( GetNumberOfWholeTownsUnderControl() == 5 ) && IsTownUnderCompleteControlByPlayer( OMERTA ) );
856 			break;
857 
858 		case FACT_PLAYER_OWNS_4_TOWNS_INCLUDING_OMERTA:
859 			gubFact[usFact] = ( ( GetNumberOfWholeTownsUnderControl() >= 6 ) && IsTownUnderCompleteControlByPlayer( OMERTA ) );
860 			break;
861 
862 		case FACT_PLAYER_FOUGHT_THREE_TIMES_TODAY:
863 			gubFact[usFact] = !BoxerAvailable();
864 			break;
865 
866 		case FACT_PLAYER_DOING_POORLY:
867 			gubFact[usFact] = ( CurrentPlayerProgressPercentage() < 20 );
868 			break;
869 
870 		case FACT_PLAYER_DOING_WELL:
871 			gubFact[usFact] = ( CurrentPlayerProgressPercentage() > 50 );
872 			break;
873 
874 		case FACT_PLAYER_DOING_VERY_WELL:
875 			gubFact[usFact] = ( CurrentPlayerProgressPercentage() > 80 );
876 			break;
877 
878 		case FACT_FATHER_DRUNK_AND_SCIFI_OPTION_ON:
879 			gubFact[usFact] = ( ( gMercProfiles[ FATHER ].bNPCData >= 5 ) && gGameOptions.fSciFi );
880 			break;
881 
882 		case FACT_BLOODCAT_QUEST_STARTED_TWO_DAYS_AGO:
883 			gubFact[usFact] = ( (gubQuest[ QUEST_BLOODCATS ] != QUESTNOTSTARTED) && (GetWorldTotalMin() - GetTimeQuestWasStarted( QUEST_BLOODCATS ) > 2 * NUM_SEC_IN_DAY / NUM_SEC_IN_MIN) );
884 			break;
885 
886 		case FACT_NOTHING_REPAIRED_YET:
887 			gubFact[usFact] = RepairmanIsFixingItemsButNoneAreDoneYet( ubProfileID );
888 			break;
889 
890 		case FACT_NPC_COWERING:
891 			gubFact[usFact] = CheckNPCCowering( ubProfileID );
892 			break;
893 
894 		case FACT_TOP_AND_BOTTOM_LEVELS_CLEARED:
895 			gubFact[usFact] = ( gubFact[ FACT_TOP_LEVEL_CLEARED ] && gubFact[ FACT_BOTTOM_LEVEL_CLEARED ] );
896 			break;
897 
898 		case FACT_FIRST_BARTENDER:
899 			gubFact[ usFact ] = ubProfileID != NO_PROFILE && (gMercProfiles[ubProfileID].bNPCData == 1 || (gMercProfiles[ubProfileID].bNPCData == 0 && CountBartenders() == 0));
900 			break;
901 
902 		case FACT_SECOND_BARTENDER:
903 			gubFact[ usFact ] = ubProfileID != NO_PROFILE && (gMercProfiles[ubProfileID].bNPCData == 2 || (gMercProfiles[ubProfileID].bNPCData == 0 && CountBartenders() == 1));
904 			break;
905 
906 		case FACT_THIRD_BARTENDER:
907 			gubFact[ usFact ] = ubProfileID != NO_PROFILE && (gMercProfiles[ubProfileID].bNPCData == 3 || (gMercProfiles[ubProfileID].bNPCData == 0 && CountBartenders() == 2));
908 			break;
909 
910 		case FACT_FOURTH_BARTENDER:
911 			gubFact[ usFact ] = ubProfileID != NO_PROFILE && (gMercProfiles[ubProfileID].bNPCData == 4 || (gMercProfiles[ubProfileID].bNPCData == 0 && CountBartenders() == 3));
912 			break;
913 
914 		case FACT_NPC_NOT_UNDER_FIRE:
915 			gubFact[usFact] = !CheckNPCIsUnderFire( ubProfileID );
916 			break;
917 
918 		case FACT_KINGPIN_NOT_IN_OFFICE:
919 			gubFact[usFact] = !( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_D && NPCInRoomRange( KINGPIN, 30, 39 ) );
920 			// 30 to 39
921 			break;
922 
923 		case FACT_DONT_OWE_KINGPIN_MONEY:
924 			gubFact[usFact] = (gubQuest[ QUEST_KINGPIN_MONEY ] != QUESTINPROGRESS);
925 			break;
926 
927 		case FACT_NO_CLUB_FIGHTING_ALLOWED:
928 			gubFact[usFact] = ( gubQuest[ QUEST_KINGPIN_MONEY ] == QUESTINPROGRESS || gfBoxersResting );// plus other conditions
929 			break;
930 
931 		case FACT_MADDOG_IS_SPEAKER:
932 			gubFact[usFact] = ( gubSrcSoldierProfile == MADDOG );
933 			break;
934 
935 		case FACT_PC_HAS_CONRADS_RECRUIT_OPINION:
936 			gubFact[usFact] = ( gpDestSoldier && (CalcDesireToTalk( gpDestSoldier->ubProfile, gubSrcSoldierProfile, APPROACH_RECRUIT ) >= 50) );
937 			break;
938 
939 		case FACT_NPC_HOSTILE_OR_PISSED_OFF:
940 			gubFact[usFact] = CheckNPCIsEnemy( ubProfileID ) || (gMercProfiles[ ubProfileID ].ubMiscFlags3 & PROFILE_MISC_FLAG3_NPC_PISSED_OFF);
941 			break;
942 
943 		case FACT_TONY_IN_BUILDING:
944 			gubFact[usFact] = CheckNPCSector( TONY, 5, MAP_ROW_C, 0 ) && NPCInRoom( TONY, 50 );
945 			break;
946 
947 		case FACT_SHANK_SPEAKING:
948 			gubFact[usFact] = ( gpSrcSoldier && gpSrcSoldier->ubProfile == SHANK );
949 			break;
950 
951 		case FACT_ROCKET_RIFLE_EXISTS:
952 			gubFact[usFact] = ItemTypeExistsAtLocation( 10472, ROCKET_RIFLE, 0, NULL );
953 			break;
954 
955 		case FACT_DOREEN_ALIVE:
956 			gubFact[usFact] = gMercProfiles[ DOREEN ].bMercStatus != MERC_IS_DEAD;
957 			break;
958 
959 		case FACT_WALDO_ALIVE:
960 			gubFact[usFact] = gMercProfiles[ WALDO ].bMercStatus != MERC_IS_DEAD;
961 			break;
962 
963 		case FACT_PERKO_ALIVE:
964 			gubFact[usFact] = gMercProfiles[ PERKO ].bMercStatus != MERC_IS_DEAD;
965 			break;
966 
967 		case FACT_TONY_ALIVE:
968 			gubFact[usFact] = gMercProfiles[ TONY ].bMercStatus != MERC_IS_DEAD;
969 			break;
970 
971 		case FACT_VINCE_ALIVE:
972 			gubFact[usFact] = gMercProfiles[ VINCE ].bMercStatus != MERC_IS_DEAD;
973 			break;
974 
975 		case FACT_JENNY_ALIVE:
976 			gubFact[usFact] = gMercProfiles[ JENNY ].bMercStatus != MERC_IS_DEAD;
977 			break;
978 
979 		case FACT_ARNOLD_ALIVE:
980 			gubFact[usFact] = gMercProfiles[ ARNIE ].bMercStatus != MERC_IS_DEAD;
981 			break;
982 
983 		case FACT_I16_BLOODCATS_KILLED:
984 			gubFact[usFact] = (SectorInfo[ SEC_I16 ].bBloodCats == 0);
985 			break;
986 
987 		case FACT_NPC_BANDAGED_TODAY:
988 			gubFact[usFact] = ubProfileID != NO_PROFILE && (gMercProfiles[ ubProfileID ].ubMiscFlags2 & PROFILE_MISC_FLAG2_BANDAGED_TODAY) != 0;
989 			break;
990 
991 		case FACT_PLAYER_IN_SAME_ROOM:
992 			gubFact[usFact] = PCInSameRoom( ubProfileID );
993 			break;
994 
995 		case FACT_PLAYER_SPOKE_TO_DRASSEN_MINER:
996 			gubFact[usFact] = SpokenToHeadMiner( MINE_DRASSEN );
997 			break;
998 		case FACT_PLAYER_IN_CONTROLLED_DRASSEN_MINE:
999 			gubFact[usFact] = ( GetIdOfMineForSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ) == MINE_DRASSEN && !(StrategicMap[ gWorldSectorX + MAP_WORLD_X * gWorldSectorY ].fEnemyControlled) );
1000 			break;
1001 		case FACT_PLAYER_SPOKE_TO_CAMBRIA_MINER:
1002 			gubFact[usFact] = SpokenToHeadMiner( MINE_CAMBRIA );
1003 			break;
1004 		case FACT_PLAYER_IN_CONTROLLED_CAMBRIA_MINE:
1005 			gubFact[usFact] = ( GetIdOfMineForSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ) == MINE_CAMBRIA && !(StrategicMap[ gWorldSectorX + MAP_WORLD_X * gWorldSectorY ].fEnemyControlled) );
1006 			break;
1007 		case FACT_PLAYER_SPOKE_TO_CHITZENA_MINER:
1008 			gubFact[usFact] = SpokenToHeadMiner( MINE_CHITZENA );
1009 			break;
1010 		case FACT_PLAYER_IN_CONTROLLED_CHITZENA_MINE:
1011 			gubFact[usFact] = ( GetIdOfMineForSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ) == MINE_CHITZENA && !(StrategicMap[ gWorldSectorX + MAP_WORLD_X * gWorldSectorY ].fEnemyControlled) );
1012 			break;
1013 		case FACT_PLAYER_SPOKE_TO_ALMA_MINER:
1014 			gubFact[usFact] = SpokenToHeadMiner( MINE_ALMA );
1015 			break;
1016 		case FACT_PLAYER_IN_CONTROLLED_ALMA_MINE:
1017 			gubFact[usFact] = ( GetIdOfMineForSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ) == MINE_ALMA && !(StrategicMap[ gWorldSectorX + MAP_WORLD_X * gWorldSectorY ].fEnemyControlled) );
1018 			break;
1019 		case FACT_PLAYER_SPOKE_TO_GRUMM_MINER:
1020 			gubFact[usFact] = SpokenToHeadMiner( MINE_GRUMM );
1021 			break;
1022 		case FACT_PLAYER_IN_CONTROLLED_GRUMM_MINE:
1023 			gubFact[usFact] = ( GetIdOfMineForSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ) == MINE_GRUMM && !(StrategicMap[ gWorldSectorX + MAP_WORLD_X * gWorldSectorY ].fEnemyControlled) );
1024 			break;
1025 
1026 		case FACT_ENOUGH_LOYALTY_TO_TRAIN_MILITIA:
1027 			gubFact[usFact] = InTownSectorWithTrainingLoyalty(SECTOR(gWorldSectorX, gWorldSectorY));
1028 			break;
1029 
1030 		case FACT_WALKER_AT_BAR:
1031 			gubFact[usFact] = (gMercProfiles[ FATHER ].sSectorX == 13 && gMercProfiles[ FATHER ].sSectorY == MAP_ROW_C);
1032 			break;
1033 
1034 		case FACT_JOEY_ALIVE:
1035 			gubFact[usFact] = gMercProfiles[ JOEY ].bMercStatus != MERC_IS_DEAD;
1036 			break;
1037 
1038 		case FACT_UNPROPOSITIONED_FEMALE_SPEAKING_TO_NPC:
1039 			gubFact[usFact] = CheckTalkerUnpropositionedFemale();
1040 			break;
1041 
1042 		case FACT_84_AND_85_TRUE:
1043 			gubFact[usFact] = CheckFact(FACT_84, ubProfileID) && CheckFact(FACT_HANS_AT_SPOT, ubProfileID);
1044 			break;
1045 
1046 		case FACT_SKYRIDER_IN_B15:
1047 			gubFact[ usFact ] = CheckNPCSector( SKYRIDER, 15, MAP_ROW_B, 0 );
1048 			break;
1049 
1050 		case FACT_SKYRIDER_IN_C16:
1051 			gubFact[ usFact ] = CheckNPCSector( SKYRIDER, 16, MAP_ROW_C, 0 );
1052 			break;
1053 		case FACT_SKYRIDER_IN_E14:
1054 			gubFact[ usFact ] = CheckNPCSector( SKYRIDER, 14, MAP_ROW_E, 0 );
1055 			break;
1056 		case FACT_SKYRIDER_IN_D12:
1057 			gubFact[ usFact ] = CheckNPCSector( SKYRIDER, 12, MAP_ROW_D, 0 );
1058 			break;
1059 
1060 		case FACT_KINGPIN_IS_ENEMY:
1061 			gubFact[usFact] = (gTacticalStatus.fCivGroupHostile[ KINGPIN_CIV_GROUP ] >= CIV_GROUP_WILL_BECOME_HOSTILE);
1062 			break;
1063 
1064 		case FACT_DYNAMO_NOT_SPEAKER:
1065 			gubFact[usFact] = !( gpSrcSoldier != NULL && (gpSrcSoldier->ubProfile == DYNAMO ) );
1066 			break;
1067 
1068 		case FACT_PABLO_BRIBED:
1069 			gubFact[usFact] = !CheckFact( FACT_PABLOS_BRIBED, ubProfileID );
1070 			break;
1071 
1072 		case FACT_VEHICLE_PRESENT:
1073 			gubFact[usFact] =
1074 				CheckFact(FACT_OK_USE_HUMMER, ubProfileID) &&
1075 				(
1076 					FindSoldierByProfileIDOnPlayerTeam(PROF_HUMMER)   != NULL ||
1077 					FindSoldierByProfileIDOnPlayerTeam(PROF_ICECREAM) != NULL
1078 				);
1079 			break;
1080 
1081 		case FACT_PLAYER_KILLED_BOXERS:
1082 			gubFact[usFact] = !BoxerExists();
1083 			break;
1084 
1085 		case FACT_245: // Can dimitri be recruited? should be true if already true, OR if Miguel has been recruited already
1086 			gubFact[usFact] = gubFact[usFact] || FindSoldierByProfileIDOnPlayerTeam(MIGUEL);
1087 /*
1088 		case FACT_:
1089 			gubFact[usFact] = ;
1090 			break;
1091 */
1092 
1093 		default:
1094 			break;
1095 	}
1096 	return( gubFact[usFact] );
1097 }
1098 
StartQuest(UINT8 ubQuest,INT16 sSectorX,INT16 sSectorY)1099 void StartQuest( UINT8 ubQuest, INT16 sSectorX, INT16 sSectorY )
1100 {
1101 	InternalStartQuest( ubQuest, sSectorX, sSectorY, TRUE );
1102 }
1103 
1104 
InternalStartQuest(UINT8 ubQuest,INT16 sSectorX,INT16 sSectorY,BOOLEAN fUpdateHistory)1105 void InternalStartQuest( UINT8 ubQuest, INT16 sSectorX, INT16 sSectorY, BOOLEAN fUpdateHistory )
1106 {
1107 	if ( gubQuest[ubQuest ] == QUESTNOTSTARTED )
1108 	{
1109 		gubQuest[ubQuest] = QUESTINPROGRESS;
1110 
1111 		if ( fUpdateHistory )
1112 		{
1113 			AddHistoryToPlayersLog(HISTORY_QUEST_STARTED, ubQuest, GetWorldTotalMin(), sSectorX, sSectorY);
1114 		}
1115 	}
1116 	else
1117 	{
1118 		gubQuest[ubQuest] = QUESTINPROGRESS;
1119 	}
1120 }
1121 
EndQuest(UINT8 ubQuest,INT16 sSectorX,INT16 sSectorY)1122 void EndQuest( UINT8 ubQuest, INT16 sSectorX, INT16 sSectorY )
1123 {
1124 	InternalEndQuest( ubQuest, sSectorX, sSectorY, TRUE );
1125 }
1126 
InternalEndQuest(UINT8 ubQuest,INT16 sSectorX,INT16 sSectorY,BOOLEAN fUpdateHistory)1127 void InternalEndQuest( UINT8 ubQuest, INT16 sSectorX, INT16 sSectorY, BOOLEAN fUpdateHistory )
1128 {
1129 	if ( gubQuest[ubQuest ] == QUESTINPROGRESS )
1130 	{
1131 		gubQuest[ubQuest] = QUESTDONE;
1132 
1133 		if ( fUpdateHistory )
1134 		{
1135 			AddHistoryToPlayersLog(HISTORY_QUEST_FINISHED, ubQuest, GetWorldTotalMin(), sSectorX, sSectorY);
1136 		}
1137 	}
1138 	else
1139 	{
1140 		gubQuest[ubQuest] = QUESTDONE;
1141 	}
1142 
1143 	if ( ubQuest == QUEST_RESCUE_MARIA )
1144 	{
1145 		// cheap hack to try to prevent Madame Layla from thinking that you are
1146 		// still in the brothel with Maria...
1147 		gMercProfiles[ MADAME ].bNPCData = 0;
1148 		gMercProfiles[ MADAME ].bNPCData2 = 0;
1149 	}
1150 }
1151 
1152 
InitQuestEngine()1153 void InitQuestEngine()
1154 {
1155 	std::fill(std::begin(gubQuest), std::end(gubQuest), 0);
1156 	std::fill(std::begin(gubFact), std::end(gubFact), 0);
1157 
1158 	// semi-hack to make the letter quest start right away
1159 	CheckForQuests( 1 );
1160 
1161 	if ( gGameOptions.fSciFi )
1162 	{
1163 		// 3 medical boosters
1164 		gubCambriaMedicalObjects = 21;
1165 	}
1166 	else
1167 	{
1168 		gubCambriaMedicalObjects = 18;
1169 	}
1170 
1171 	gubBoxingMatchesWon = 0;
1172 	gubBoxersRests = 0;
1173 	gfBoxersResting = FALSE;
1174 }
1175 
1176 
1177 
CheckForQuests(UINT32 uiDay)1178 void CheckForQuests( UINT32 uiDay )
1179 {
1180 	// This function gets called at 8:00 AM time of the day
1181 
1182 	SLOGD("Checking For Quests, Day %d", uiDay );
1183 
1184 	// -------------------------------------------------------------------------------
1185 	// QUEST 0 : DELIVER LETTER
1186 	// -------------------------------------------------------------------------------
1187 	// The game always starts with DELIVER LETTER quest, so turn it on if it hasn't
1188 	// already started
1189 	if (gubQuest[QUEST_DELIVER_LETTER] == QUESTNOTSTARTED)
1190 	{
1191 		AddHistoryToPlayersLog(HISTORY_ACCEPTED_ASSIGNMENT_FROM_ENRICO, 0, GetWorldTotalMin(), -1, -1);
1192 		StartQuest( QUEST_DELIVER_LETTER, -1, -1 );
1193 		SLOGD("Started DELIVER LETTER quest");
1194 	}
1195 
1196 	// This quest gets turned OFF through conversation with Miguel - when user hands
1197 	// Miguel the letter
1198 }
1199 
1200 
SaveQuestInfoToSavedGameFile(HWFILE const hFile)1201 void SaveQuestInfoToSavedGameFile(HWFILE const hFile)
1202 {
1203 	//Save all the states if the Quests
1204 	FileWrite(hFile, gubQuest, MAX_QUESTS);
1205 
1206 	//Save all the states for the facts
1207 	FileWrite(hFile, gubFact, NUM_FACTS);
1208 }
1209 
1210 
LoadQuestInfoFromSavedGameFile(HWFILE const hFile)1211 void LoadQuestInfoFromSavedGameFile(HWFILE const hFile)
1212 {
1213 	//Save all the states if the Quests
1214 	FileRead(hFile, gubQuest, MAX_QUESTS);
1215 
1216 	//Save all the states for the facts
1217 	FileRead(hFile, gubFact, NUM_FACTS);
1218 }
1219