1 #include "MapScreen.h"
2 #include "Strategic_Event_Handler.h"
3 #include "MemMan.h"
4 #include "Item_Types.h"
5 #include "Items.h"
6 #include "Handle_Items.h"
7 #include "LaptopSave.h"
8 #include "Tactical_Save.h"
9 #include "StrategicMap.h"
10 #include "Quests.h"
11 #include "Soldier_Profile.h"
12 #include "Game_Event_Hook.h"
13 #include "Game_Clock.h"
14 #include "Interface_Dialogue.h"
15 #include "Random.h"
16 #include "Overhead.h"
17 #include "Strategic_Town_Loyalty.h"
18 #include "Soldier_Init_List.h"
19 #include "SaveLoadMap.h"
20 #include "Soldier_Create.h"
21 #include "Soldier_Add.h"
22 #include "OppList.h"
23 #include "EMail.h"
24 #include "Structure_Wrap.h"
25 #include "History.h"
26 #include "BobbyRMailOrder.h"
27 #include "ShippingDestinationModel.h"
28 #include "ContentManager.h"
29 #include "GameInstance.h"
30 #include "policy/GamePolicy.h"
31 #include "strategic/NpcPlacementModel.h"
32 #include "MercProfile.h"
33 #include "MercProfileInfo.h"
34 
35 
36 UINT32 guiPabloExtraDaysBribed = 0;
37 
38 UINT8		gubCambriaMedicalObjects;
39 
40 
CloseCrate(const INT16 x,const INT16 y,const INT8 z,const GridNo grid_no)41 static BOOLEAN CloseCrate(const INT16 x, const INT16 y, const INT8 z, const GridNo grid_no)
42 {
43 	// Determine if the sector is loaded
44 	if (gWorldSectorX == x && gWorldSectorY == y && gbWorldSectorZ == z)
45 	{
46 		SetOpenableStructureToClosed(grid_no, 0);
47 		return TRUE;
48 	}
49 	else
50 	{
51 		ChangeStatusOfOpenableStructInUnloadedSector(x, y, z, grid_no, FALSE);
52 		return FALSE;
53 	}
54 }
55 
56 
57 static void DropOffItemsInDestination(UINT8 ubOrderNum, const ShippingDestinationModel* shippingDest);
58 
59 
BobbyRayPurchaseEventCallback(const UINT8 ubOrderID)60 void BobbyRayPurchaseEventCallback(const UINT8 ubOrderID)
61 {
62 	static UINT8 ubShipmentsSinceNoBribes = 0;
63 
64 	NewBobbyRayOrderStruct* const shipment = &gpNewBobbyrShipments[ubOrderID];
65 	auto dest = GCM->getShippingDestination(shipment->ubDeliveryLoc);
66 
67 	if (shipment->fActive && dest->canDeliver && !dest->isPrimary)
68 	{
69 		// the delivery is not for Drassen, use a simple logic (reliable delivery) to handle delivery arrival there
70 		DropOffItemsInDestination(ubOrderID, dest);
71 		return;
72 	}
73 
74 	UINT16 usStandardMapPos = dest->deliverySectorGridNo;
75 	auto destSector = StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(dest->getDeliverySector())];
76 	if (CheckFact(FACT_NEXT_PACKAGE_CAN_BE_LOST, 0))
77 	{
78 		SetFactFalse(FACT_NEXT_PACKAGE_CAN_BE_LOST);
79 		if (Random(100) < 50)
80 		{
81 			// lose the whole shipment!
82 			shipment->fActive = FALSE;
83 			SetFactTrue(FACT_LAST_SHIPMENT_CRASHED);
84 			return;
85 		}
86 	}
87 	else if (CheckFact(FACT_NEXT_PACKAGE_CAN_BE_DELAYED, 0))
88 	{
89 		// shipment went to wrong airport... reroute all items to a temporary
90 		// gridno to represent the other airport (and damage them)
91 		SetFactTrue(FACT_LAST_SHIPMENT_WENT_TO_WRONG_AIRPORT);
92 		SetFactFalse(FACT_NEXT_PACKAGE_CAN_BE_DELAYED);
93 		usStandardMapPos = LOST_SHIPMENT_GRIDNO;
94 	}
95 	else if (gTownLoyalty[destSector.bNameId].ubRating < 20 || destSector.fEnemyControlled)
96 	{
97 		// loss of the whole shipment
98 		shipment->fActive = FALSE;
99 		SetFactTrue(FACT_AGENTS_PREVENTED_SHIPMENT);
100 		return;
101 	}
102 
103 	//Must get the total number of items ( all item types plus how many of each item type ordered )
104 	BOOLEAN fThisShipmentIsFromJohnKulba = FALSE; //if it is, dont add an email
105 	UINT16  usNumberOfItems              = 0;
106 	for (UINT8 i = 0; i < shipment->ubNumberPurchases; ++i)
107 	{
108 		const BobbyRayPurchaseStruct* const purchase = &shipment->BobbyRayPurchase[i];
109 
110 		// Count how many items were purchased
111 		usNumberOfItems += purchase->ubNumberPurchased;
112 
113 		//if any items are AutoMags
114 		if (purchase->usItemIndex == AUTOMAG_III)
115 		{
116 			//This shipment is from John Kulba, dont add an email from bobby ray
117 			fThisShipmentIsFromJohnKulba = TRUE;
118 		}
119 	}
120 
121 	const BOOLEAN fSectorLoaded =
122 		CloseCrate(dest->deliverySectorX, dest->deliverySectorY, dest->deliverySectorZ, dest->deliverySectorGridNo);
123 
124 	OBJECTTYPE* pObject       = NULL;
125 	OBJECTTYPE* pStolenObject = NULL;
126 	if (!fSectorLoaded) // if we are NOT currently in the right sector
127 	{
128 		//build an array of objects to be added
129 		pObject       = new OBJECTTYPE[usNumberOfItems]{};
130 		pStolenObject = new OBJECTTYPE[usNumberOfItems]{};
131 	}
132 
133 	// check for potential theft
134 	UINT32 uiChanceOfTheft;
135 	if (CheckFact(FACT_PABLO_WONT_STEAL, 0) || gamepolicy(pablo_wont_steal))
136 	{
137 		uiChanceOfTheft = 0;
138 	}
139 	else if (CheckFact(FACT_PABLOS_BRIBED, 0))
140 	{
141 		// Since Pacos has some money, reduce record of # of shipments since last bribed...
142 		ubShipmentsSinceNoBribes /= 2;
143 		uiChanceOfTheft = 0;
144 	}
145 	else
146 	{
147 		++ubShipmentsSinceNoBribes;
148 		// this chance might seem high but it's only applied at most to every second item
149 		uiChanceOfTheft = 12 + Random(4 * ubShipmentsSinceNoBribes);
150 	}
151 
152 	UINT32  uiCount               = 0;
153 	UINT32  uiStolenCount         = 0;
154 	BOOLEAN fPablosStoleSomething = FALSE;
155 	BOOLEAN fPablosStoleLastItem  = FALSE;
156 	for (UINT8 i = 0; i < shipment->ubNumberPurchases; ++i)
157 	{
158 		const BobbyRayPurchaseStruct* const purchase = &shipment->BobbyRayPurchase[i];
159 		UINT16                        const usItem   = purchase->usItemIndex;
160 
161 		OBJECTTYPE Object;
162 		CreateItem(usItem, purchase->bItemQuality, &Object);
163 
164 		if (GCM->getItem(usItem)->getItemClass() == IC_GUN)
165 		{
166 			/* Empty out the bullets put in by CreateItem().  We now sell all guns
167 			 * empty of bullets.  This is done for BobbyR simply to be consistent with
168 			 * the dealers in Arulco, who must sell guns empty to prevent ammo cheats
169 			 * by players. */
170 			Object.ubGunShotsLeft = 0;
171 		}
172 
173 		//add all the items that were purchased
174 		UINT8 const ubItemsPurchased = purchase->ubNumberPurchased;
175 		UINT8       ubItemsDelivered = 0;
176 		for (UINT8 j = 0; j < ubItemsPurchased; ++j)
177 		{
178 			// Pablos might steal stuff but only:
179 			// - if it's one of a group of items
180 			// - if he didn't steal the previous item in the group (so he never steals > 50%)
181 			// - if he has been bribed, he only sneaks out stuff which is cheap
182 			if (fSectorLoaded)
183 			{
184 				// add ubItemsPurchased to the chance of theft so the chance increases when there are more items of a kind being ordered
185 				if (!fPablosStoleLastItem && uiChanceOfTheft > 0 && Random(100) < uiChanceOfTheft + ubItemsPurchased)
186 				{
187 					++uiStolenCount;
188 					fPablosStoleSomething = TRUE;
189 					fPablosStoleLastItem  = TRUE;
190 				}
191 				else
192 				{
193 					fPablosStoleLastItem = FALSE;
194 
195 					if (usStandardMapPos == LOST_SHIPMENT_GRIDNO)
196 					{
197 						// damage the item a random amount!
198 						const INT8 status = (70 + Random(11)) * (INT32)Object.bStatus[0] / 100;
199 						Object.bStatus[0] = MAX(1, status);
200 						AddItemToPool(usStandardMapPos, &Object, INVISIBLE, 0, 0, 0);
201 					}
202 					else
203 					{
204 						// record # delivered for later addition...
205 						++ubItemsDelivered;
206 					}
207 				}
208 			}
209 			else
210 			{
211 				if (j > 1 && !fPablosStoleLastItem && uiChanceOfTheft > 0 && Random(100) < uiChanceOfTheft + j)
212 				{
213 					pStolenObject[uiStolenCount] = Object;
214 					++uiStolenCount;
215 					fPablosStoleSomething = TRUE;
216 					fPablosStoleLastItem  = TRUE;
217 				}
218 				else
219 				{
220 					fPablosStoleLastItem = FALSE;
221 
222 					/* else we are not currently in the sector, so we build an array of
223 					 * items to add in one lump add the item to the item array */
224 					if (usStandardMapPos == LOST_SHIPMENT_GRIDNO)
225 					{
226 						// damage the item a random amount!
227 						const INT8 status = (70 + Random(11)) * (INT32)Object.bStatus[0] / 100;
228 						Object.bStatus[0] = MAX(1, status);
229 						pObject[uiCount++] = Object;
230 					}
231 					else
232 					{
233 						++ubItemsDelivered;
234 					}
235 				}
236 			}
237 		}
238 
239 		if (purchase->ubNumberPurchased == 1 && ubItemsDelivered == 1)
240 		{
241 			// the item in Object will be the item to deliver
242 			if (fSectorLoaded)
243 			{
244 				AddItemToPool(usStandardMapPos, &Object, INVISIBLE, 0, 0, 0);
245 			}
246 			else
247 			{
248 				pObject[uiCount++] = Object;
249 			}
250 		}
251 		else
252 		{
253 			while (ubItemsDelivered)
254 			{
255 				// treat 0s as 1s :-)
256 				const UINT8 ubTempNumItems = __min(ubItemsDelivered, __max(1, GCM->getItem(usItem)->getPerPocket()));
257 				CreateItems(usItem, purchase->bItemQuality, ubTempNumItems, &Object);
258 
259 				// stack as many as possible
260 				if (fSectorLoaded)
261 				{
262 					AddItemToPool(usStandardMapPos, &Object, INVISIBLE, 0, 0, 0);
263 				}
264 				else
265 				{
266 					pObject[uiCount++] = Object;
267 				}
268 
269 				ubItemsDelivered -= ubTempNumItems;
270 			}
271 		}
272 	}
273 
274 	//if we are NOT currently in the sector
275 	if (!fSectorLoaded)
276 	{
277 		//add all the items from the array that was built above
278 		//The item are to be added to the Top part of Drassen, grid loc's  10112, 9950
279 		AddItemsToUnLoadedSector(dest->deliverySectorX, dest->deliverySectorY, dest->deliverySectorZ, usStandardMapPos, uiCount, pObject, 0, 0, 0, INVISIBLE);
280 		if (uiStolenCount > 0)
281 		{
282 			AddItemsToUnLoadedSector(dest->deliverySectorX, dest->deliverySectorY, dest->deliverySectorZ, PABLOS_STOLEN_DEST_GRIDNO, uiStolenCount, pStolenObject, 0, 0, 0, INVISIBLE);
283 		}
284 		delete[] pObject;
285 		delete[] pStolenObject;
286 	}
287 
288 	if (fPablosStoleSomething)
289 	{
290 		SetFactTrue(FACT_PABLOS_STOLE_FROM_LATEST_SHIPMENT);
291 	}
292 	else
293 	{
294 		SetFactFalse(FACT_PABLOS_STOLE_FROM_LATEST_SHIPMENT);
295 	}
296 
297 	SetFactFalse(FACT_LARGE_SIZED_OLD_SHIPMENT_WAITING);
298 
299 	if (CheckFact(FACT_NEXT_PACKAGE_CAN_BE_DELAYED, 0))
300 	{
301 		SetFactFalse(FACT_MEDIUM_SIZED_SHIPMENT_WAITING);
302 		SetFactFalse(FACT_LARGE_SIZED_SHIPMENT_WAITING);
303 		SetFactFalse(FACT_REALLY_NEW_BOBBYRAY_SHIPMENT_WAITING);
304 	}
305 	else
306 	{
307 		if (usNumberOfItems - uiStolenCount <= 5)
308 		{
309 			SetFactFalse(FACT_MEDIUM_SIZED_SHIPMENT_WAITING);
310 			SetFactFalse(FACT_LARGE_SIZED_SHIPMENT_WAITING);
311 		}
312 		else if (usNumberOfItems - uiStolenCount <= 15)
313 		{
314 			SetFactTrue(FACT_MEDIUM_SIZED_SHIPMENT_WAITING);
315 			SetFactFalse(FACT_LARGE_SIZED_SHIPMENT_WAITING);
316 		}
317 		else
318 		{
319 			SetFactFalse(FACT_MEDIUM_SIZED_SHIPMENT_WAITING);
320 			SetFactTrue(FACT_LARGE_SIZED_SHIPMENT_WAITING);
321 		}
322 
323 		// this shipment isn't old yet...
324 		SetFactTrue(FACT_REALLY_NEW_BOBBYRAY_SHIPMENT_WAITING);
325 
326 		// set up even to make shipment "old"
327 		AddSameDayStrategicEvent(EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay() + 120, FACT_REALLY_NEW_BOBBYRAY_SHIPMENT_WAITING);
328 	}
329 
330 	//We have received the shipment so fActice becomes fALSE
331 	shipment->fActive = FALSE;
332 
333 	//Stop time compression the game
334 	StopTimeCompression();
335 
336 	//if the shipment is NOT from John Kulba, send an email
337 	if (!fThisShipmentIsFromJohnKulba)
338 	{
339 		//Add an email from Bobby r telling the user the shipment 'Should' be there
340 		AddEmail(dest->emailOffset, dest->emailLength, BOBBY_R, GetWorldTotalMin());
341 	}
342 	else
343 	{
344 		//if the shipment is from John Kulba
345 		//Add an email from kulba telling the user the shipment is there
346 		AddEmail(JOHN_KULBA_GIFT_IN_DRASSEN, JOHN_KULBA_GIFT_IN_DRASSEN_LENGTH, JOHN_KULBA, GetWorldTotalMin());
347 	}
348 }
349 
350 
HandleDelayedItemsArrival(UINT32 uiReason)351 static void HandleDelayedItemsArrival(UINT32 uiReason)
352 {
353 	// This function moves all the items that Pablos has stolen
354 	// (or items that were delayed) to the arrival location for new shipments,
355 	INT16			sStartGridNo;
356 	UINT8			ubLoop;
357 	OBJECTTYPE Object;
358 	auto shippingDest = GCM->getPrimaryShippingDestination();
359 
360 	if (uiReason == NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_RETURN_STOLEN_SHIPMENT_ITEMS )
361 	{
362 		if ( gMercProfiles[ PABLO ].bMercStatus == MERC_IS_DEAD )
363 		{
364 			// nothing arrives then!
365 			return;
366 		}
367 		// update some facts...
368 		SetFactTrue( FACT_PABLO_RETURNED_GOODS );
369 		SetFactFalse( FACT_PABLO_PUNISHED_BY_PLAYER );
370 		sStartGridNo = PABLOS_STOLEN_DEST_GRIDNO;
371 
372 		// add random items
373 
374 		for (ubLoop = 0; ubLoop < 2; ubLoop++)
375 		{
376 			switch( Random( 10 ) )
377 			{
378 				case 0:
379 					// 1 in 10 chance of a badly damaged gas mask
380 					CreateItem( GASMASK, (INT8) (20 + Random( 10 )), &Object );
381 					break;
382 				case 1:
383 				case 2:
384 					// 2 in 10 chance of a battered Desert Eagle
385 					CreateItem( DESERTEAGLE, (INT8) (40 + Random( 10 )), &Object );
386 					break;
387 				case 3:
388 				case 4:
389 				case 5:
390 					// 3 in 10 chance of a stun grenade
391 					CreateItem( STUN_GRENADE, (INT8) (70 + Random( 10 )), &Object );
392 					break;
393 				case 6:
394 				case 7:
395 				case 8:
396 				case 9:
397 					// 4 in 10 chance of two 38s!
398 					CreateItems( SW38, (INT8) (90 + Random( 10 )), 2, &Object );
399 					break;
400 			}
401 			if ( ( gWorldSectorX == shippingDest->deliverySectorX) && ( gWorldSectorY == shippingDest->deliverySectorY) && ( gbWorldSectorZ == shippingDest->deliverySectorZ ) )
402 			{
403 				AddItemToPool(shippingDest->deliverySectorGridNo, &Object, INVISIBLE, 0, 0, 0);
404 			}
405 			else
406 			{
407 				AddItemsToUnLoadedSector(shippingDest->deliverySectorX, shippingDest->deliverySectorY, shippingDest->deliverySectorZ, shippingDest->deliverySectorGridNo, 1, &Object, 0, 0, 0, INVISIBLE);
408 			}
409 		}
410 	}
411 	else if (uiReason == FACT_PACKAGE_DAMAGED)
412 	{
413 		sStartGridNo = LOST_SHIPMENT_GRIDNO;
414 	}
415 	else
416 	{
417 		return;
418 	}
419 
420 	// If the Drassen airport sector is already loaded, move the item pools...
421 	if ( ( gWorldSectorX == shippingDest->deliverySectorX ) && ( gWorldSectorY == shippingDest->deliverySectorY ) && ( gbWorldSectorZ == shippingDest->deliverySectorZ ) )
422 	{
423 		// sector is loaded!
424 		// just move the hidden item pool
425 		MoveItemPools( sStartGridNo, shippingDest->deliverySectorGridNo );
426 	}
427 	else
428 	{
429 		// otherwise load the saved items from the item file and change the records of their locations
430 		std::vector<WORLDITEM> pTemp = LoadWorldItemsFromTempItemFile(shippingDest->deliverySectorX, shippingDest->deliverySectorY, shippingDest->deliverySectorZ);
431 
432 		for (WORLDITEM& wi : pTemp)
433 		{
434 			if (wi.sGridNo == PABLOS_STOLEN_DEST_GRIDNO)
435 			{
436 				wi.sGridNo = shippingDest->deliverySectorGridNo;
437 			}
438 		}
439 		SaveWorldItemsToTempItemFile(shippingDest->deliverySectorX, shippingDest->deliverySectorY, shippingDest->deliverySectorZ, pTemp);
440 	}
441 }
442 
443 
AddSecondAirportAttendant(void)444 void AddSecondAirportAttendant( void )
445 {
446 	// add the second airport attendant to the Drassen airport...
447 	MERCPROFILESTRUCT& sal = GetProfile(SAL);
448 	auto shippingDest = GCM->getPrimaryShippingDestination();
449 
450 	sal.sSectorX = shippingDest->deliverySectorX;
451 	sal.sSectorY = shippingDest->deliverySectorY;
452 	sal.bSectorZ = shippingDest->deliverySectorZ;
453 }
454 
455 
SetPabloToUnbribed(void)456 static void SetPabloToUnbribed(void)
457 {
458 	if (guiPabloExtraDaysBribed > 0)
459 	{
460 		// set new event for later on, because the player gave Pablo more money!
461 		AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay(), FACT_PABLOS_BRIBED, guiPabloExtraDaysBribed );
462 		guiPabloExtraDaysBribed = 0;
463 	}
464 	else
465 	{
466 		SetFactFalse( FACT_PABLOS_BRIBED );
467 	}
468 }
469 
470 
HandlePossiblyDamagedPackage(void)471 static void HandlePossiblyDamagedPackage(void)
472 {
473 	if (Random( 100 ) < 70)
474 	{
475 		SetFactTrue( FACT_PACKAGE_DAMAGED );
476 		HandleDelayedItemsArrival( FACT_PACKAGE_DAMAGED );
477 	}
478 	else
479 	{
480 		// shipment lost forever!
481 		SetFactTrue( FACT_PACKAGE_LOST_PERMANENTLY );
482 	}
483 	// whatever happened, the shipment is no longer delayed
484 	SetFactFalse( FACT_SHIPMENT_DELAYED_24_HOURS );
485 }
486 
CheckForKingpinsMoneyMissing(BOOLEAN fFirstCheck)487 void CheckForKingpinsMoneyMissing( BOOLEAN fFirstCheck )
488 {
489 	UINT32				uiTotalCash = 0;
490 	BOOLEAN				fKingpinWillDiscover = FALSE, fKingpinDiscovers = FALSE;
491 
492 	// money in D5b1 must be less than 30k
493 	CFOR_EACH_WORLD_ITEM(wi)
494 	{
495 		OBJECTTYPE const& o = wi.o;
496 		if (o.usItem == MONEY) uiTotalCash += o.uiMoneyAmount;
497 	}
498 
499 	// This function should be called every time sector D5/B1 is unloaded!
500 	if ( fFirstCheck )
501 	{
502 		if ( CheckFact( FACT_KINGPIN_WILL_LEARN_OF_MONEY_GONE, 0 ) == TRUE )
503 		{
504 			// unnecessary
505 			return;
506 		}
507 
508 		if ( uiTotalCash < 30000 )
509 		{
510 			// add history log here
511 			AddHistoryToPlayersLog( HISTORY_FOUND_MONEY, 0, GetWorldTotalMin(), gWorldSectorX, gWorldSectorY );
512 
513 			SetFactTrue( FACT_KINGPIN_WILL_LEARN_OF_MONEY_GONE );
514 		}
515 	}
516 
517 	if ( CheckFact( FACT_KINGPIN_DEAD, 0 ) == TRUE )
518 	{
519 		return;
520 	}
521 
522 	if ( uiTotalCash < 30000 )
523 	{
524 		if ( fFirstCheck )
525 		{
526 			// add event to make Kingpin aware, two days from now
527 			fKingpinWillDiscover = TRUE;
528 		}
529 		else
530 		{
531 			fKingpinDiscovers = TRUE;
532 		}
533 	}
534 
535 	if ( fKingpinWillDiscover )
536 	{
537 		// set event for next day to check for real
538 		AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, Random( 120 ), FACT_KINGPIN_KNOWS_MONEY_GONE, 1 );
539 
540 		// the sector is unloaded NOW so set Kingpin's balance and remove the cash
541 		gMercProfiles[ KINGPIN ].iBalance = - (30000 - (INT32) uiTotalCash);
542 		// remove all money from map
543 		FOR_EACH_WORLD_ITEM(wi)
544 		{
545 			if (wi.o.usItem == MONEY) wi.fExists = FALSE; // remove!
546 		}
547 	}
548 	else if ( fKingpinDiscovers )
549 	{
550 		// ok start things up here!
551 		SetFactTrue( FACT_KINGPIN_KNOWS_MONEY_GONE );
552 
553 		// set event 2 days from now that if the player has not given Kingpin his money back,
554 		// he sends email to the player
555 		AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, Random( 120 ), FACT_KINGPIN_KNOWS_MONEY_GONE, 2 );
556 	}
557 
558 }
559 
HandleNPCSystemEvent(UINT32 uiEvent)560 void HandleNPCSystemEvent( UINT32 uiEvent )
561 {
562 	if (uiEvent < NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS)
563 	{
564 		switch( uiEvent )
565 		{
566 			case FACT_PABLOS_BRIBED:
567 				// set Pacos to unbribed
568 				SetPabloToUnbribed();
569 				break;
570 
571 			case FACT_REALLY_NEW_BOBBYRAY_SHIPMENT_WAITING:
572 				// the shipment is no longer really new
573 				SetFactFalse( FACT_REALLY_NEW_BOBBYRAY_SHIPMENT_WAITING );
574 				if (CheckFact( FACT_LARGE_SIZED_SHIPMENT_WAITING, 0 ))
575 				{
576 					// set "really heavy old shipment" fact
577 					SetFactTrue( FACT_LARGE_SIZED_OLD_SHIPMENT_WAITING );
578 				}
579 				break;
580 
581 			case FACT_SHIPMENT_DELAYED_24_HOURS:
582 			case FACT_24_HOURS_SINCE_DOCTOR_TALKED_TO:
583 			case FACT_24_HOURS_SINCE_JOEY_RESCUED:
584 				SetFactTrue((Fact)uiEvent);
585 				break;
586 
587 			case FACT_KINGPIN_KNOWS_MONEY_GONE:
588 				// more generally events for kingpin quest
589 				if (!CheckFact(FACT_KINGPIN_KNOWS_MONEY_GONE, 0))
590 				{
591 					// check for real whether to start quest
592 					CheckForKingpinsMoneyMissing( FALSE );
593 				}
594 				else if (!CheckFact(FACT_KINGPIN_DEAD, 0))
595 				{
596 					if ( gubQuest[ QUEST_KINGPIN_MONEY ] == QUESTNOTSTARTED )
597 					{
598 						// KP knows money is gone, hasn't told player, if this event is called then the 2
599 						// days are up... send email
600 						AddEmail( KING_PIN_LETTER, KING_PIN_LETTER_LENGTH, KING_PIN, GetWorldTotalMin() );
601 						StartQuest( QUEST_KINGPIN_MONEY, 5, MAP_ROW_D );
602 						// add event to send terrorists two days from now
603 						AddFutureDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, Random( 120 ), FACT_KINGPIN_KNOWS_MONEY_GONE, 2 );
604 					}
605 					else if ( gubQuest[ QUEST_KINGPIN_MONEY ] == QUESTINPROGRESS )
606 					{
607 						// knows money gone, quest is still in progress
608 						// event indicates Kingpin can start to send terrorists
609 						SetFactTrue( FACT_KINGPIN_CAN_SEND_ASSASSINS );
610 						gMercProfiles[ SPIKE ].sSectorX = 5;
611 						gMercProfiles[ SPIKE ].sSectorY = MAP_ROW_C;
612 						gTacticalStatus.fCivGroupHostile[ KINGPIN_CIV_GROUP ] = CIV_GROUP_WILL_BECOME_HOSTILE;
613 					}
614 				}
615 				break;
616 		}
617 	}
618 	else
619 	{
620 		switch( uiEvent - NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS )
621 		{
622 			case NPC_ACTION_RETURN_STOLEN_SHIPMENT_ITEMS:
623 				HandleDelayedItemsArrival( uiEvent );
624 				break;
625 			case NPC_ACTION_SET_RANDOM_PACKAGE_DAMAGE_TIMER:
626 				HandlePossiblyDamagedPackage();
627 				break;
628 			case NPC_ACTION_ENABLE_CAMBRIA_DOCTOR_BONUS:
629 				SetFactTrue( FACT_WILLIS_HEARD_ABOUT_JOEY_RESCUE );
630 				break;
631 			case NPC_ACTION_TRIGGER_END_OF_FOOD_QUEST:
632 				if ( gMercProfiles[ FATHER ].bMercStatus != MERC_IS_DEAD )
633 				{
634 					EndQuest( QUEST_FOOD_ROUTE, 10, MAP_ROW_A );
635 					SetFactTrue( FACT_FOOD_QUEST_OVER );
636 				}
637 				break;
638 			case NPC_ACTION_DELAYED_MAKE_BRENDA_LEAVE:
639 				//IC:
640 				//TriggerNPCRecord(BRENDA, 9);
641 				SetFactTrue( FACT_BRENDA_PATIENCE_TIMER_EXPIRED );
642 				break;
643 			case NPC_ACTION_SET_DELAY_TILL_GIRLS_AVAILABLE:
644 				HandleNPCDoAction( 107, NPC_ACTION_SET_GIRLS_AVAILABLE, 0 );
645 				break;
646 
647 			case NPC_ACTION_READY_ROBOT:
648 				{
649 					if ( CheckFact( FACT_FIRST_ROBOT_DESTROYED, 0 ) )
650 					{
651 						// second robot ready
652 						SetFactTrue( FACT_ROBOT_READY_SECOND_TIME );
653 						// resurrect robot
654 						gMercProfiles[ ROBOT ].bLife = gMercProfiles[ ROBOT ].bLifeMax;
655 						gMercProfiles[ ROBOT ].bMercStatus = MERC_OK;
656 					}
657 					else
658 					{
659 						// first robot ready
660 						SetFactTrue( FACT_ROBOT_READY );
661 					}
662 
663 					gMercProfiles[ ROBOT ].sSectorX = gMercProfiles[ MADLAB ].sSectorX;
664 					gMercProfiles[ ROBOT ].sSectorY = gMercProfiles[ MADLAB ].sSectorY;
665 					gMercProfiles[ ROBOT ].bSectorZ = gMercProfiles[ MADLAB ].bSectorZ;
666 
667 
668 				}
669 				break;
670 
671 			case NPC_ACTION_ADD_JOEY_TO_WORLD:
672 				// If Joey is not dead, escorted, or already delivered
673 				if ( gMercProfiles[ JOEY ].bMercStatus != MERC_IS_DEAD && !CheckFact( FACT_JOEY_ESCORTED, 0 ) &&
674 					gMercProfiles[ JOEY ].sSectorX == 4 &&
675 					gMercProfiles[ JOEY ].sSectorY == MAP_ROW_D &&
676 					gMercProfiles[ JOEY ].bSectorZ == 1 )
677 				{
678 					const SOLDIERTYPE* const pJoey = FindSoldierByProfileID(JOEY);
679 					if (pJoey )
680 					{
681 						// he's in the currently loaded sector...delay this an hour!
682 						AddSameDayStrategicEvent( EVENT_SET_BY_NPC_SYSTEM, GetWorldMinutesInDay() + 60, NPC_SYSTEM_EVENT_ACTION_PARAM_BONUS + NPC_ACTION_ADD_JOEY_TO_WORLD );
683 					}
684 					else
685 					{
686 						// move Joey from caves to San Mona
687 						gMercProfiles[ JOEY ].sSectorX = 5;
688 						gMercProfiles[ JOEY ].sSectorY = MAP_ROW_C;
689 						gMercProfiles[ JOEY ].bSectorZ = 0;
690 					}
691 				}
692 				break;
693 
694 			case NPC_ACTION_SEND_ENRICO_MIGUEL_EMAIL:
695 				AddEmail( ENRICO_MIGUEL, ENRICO_MIGUEL_LENGTH, MAIL_ENRICO, GetWorldTotalMin() );
696 				break;
697 
698 			case NPC_ACTION_TIMER_FOR_VEHICLE:
699 				SetFactTrue( FACT_OK_USE_HUMMER );
700 				break;
701 
702 			case NPC_ACTION_FREE_KIDS:
703 				SetFactTrue( FACT_KIDS_ARE_FREE );
704 				break;
705 
706 			default:
707 				break;
708 		}
709 	}
710 }
711 
HandleEarlyMorningEvents(void)712 void HandleEarlyMorningEvents( void )
713 {
714 	UINT32					cnt;
715 	UINT32					uiAmount;
716 
717 	// loop through all *NPCs* and reset "default response used recently" flags
718 	for (const MercProfile* profile : GCM->listMercProfiles())
719 	{
720 		if (!profile->isNPCorRPC()) continue;
721 
722 		MERCPROFILESTRUCT& p = profile->getStruct();
723 		p.bFriendlyOrDirectDefaultResponseUsedRecently = FALSE;
724 		p.bRecruitDefaultResponseUsedRecently = FALSE;
725 		p.bThreatenDefaultResponseUsedRecently = FALSE;
726 		p.ubMiscFlags2 &= (~PROFILE_MISC_FLAG2_BANDAGED_TODAY);
727 	}
728 	// reset Father Walker's drunkenness level!
729 	gMercProfiles[ FATHER ].bNPCData = (INT8) Random( 4 );
730 	// set Walker's location
731 	if ( Random( 2 ) )
732 	{
733 		// move the father to the other sector, provided neither are loaded
734 		if ( ! ( ( gWorldSectorX == 13) && ( ( gWorldSectorY == MAP_ROW_C) || gWorldSectorY == MAP_ROW_D ) && ( gbWorldSectorZ == 0 ) ) )
735 		{
736 			gMercProfiles[ FATHER ].sSectorX = 13;
737 			// swap his location
738 			if (gMercProfiles[ FATHER ].sSectorY == MAP_ROW_C)
739 			{
740 				gMercProfiles[ FATHER ].sSectorY = MAP_ROW_D;
741 			}
742 			else
743 			{
744 				gMercProfiles[ FATHER ].sSectorY = MAP_ROW_C;
745 			}
746 		}
747 	}
748 
749 
750 	if( gMercProfiles[ TONY ].ubLastDateSpokenTo > 0 && !( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_C && gbWorldSectorZ == 0 ) )
751 	{
752 		// San Mona C5 is not loaded so make Tony possibly not available
753 		if (Random( 4 ))
754 		{
755 			// Tony IS available
756 			SetFactFalse( FACT_TONY_NOT_AVAILABLE );
757 			gMercProfiles[ TONY ].sSectorX = 5;
758 			gMercProfiles[ TONY ].sSectorY = MAP_ROW_C;
759 		}
760 		else
761 		{
762 			// Tony is NOT available
763 			SetFactTrue( FACT_TONY_NOT_AVAILABLE );
764 			gMercProfiles[ TONY ].sSectorX = 0;
765 			gMercProfiles[ TONY ].sSectorY = 0;
766 		}
767 	}
768 
769 
770 	if ( gMercProfiles[ DEVIN ].ubLastDateSpokenTo == 0 )
771 	{
772 		// Does Devin move?
773 		gMercProfiles[ DEVIN ].bNPCData++;
774 		if ( gMercProfiles[ DEVIN ].bNPCData > 3 )
775 		{
776 			if ( ! ( (gWorldSectorX == gMercProfiles[ DEVIN ].sSectorX) && (gWorldSectorY == gMercProfiles[DEVIN].sSectorY) && (gbWorldSectorZ == 0) ) )
777 			{
778 				// ok, Devin's sector not loaded, so time to move!
779 				// might be same sector as before, if so, oh well!
780 				auto placement = GCM->getNpcPlacement(DEVIN);
781 				UINT8 sector   = placement->pickPlacementSector();
782 				gMercProfiles[DEVIN].sSectorX = SECTORX(sector);
783 				gMercProfiles[DEVIN].sSectorY = SECTORY(sector);
784 			}
785 		}
786 	}
787 
788 	// Does Hamous move?
789 
790 	// stop moving the truck if Hamous is dead!!
791 	// stop moving them if the player has the truck or Hamous is hired!
792 	if (gMercProfiles[HAMOUS].bLife > 0 &&
793 			FindSoldierByProfileIDOnPlayerTeam(HAMOUS)        == NULL &&
794 			FindSoldierByProfileIDOnPlayerTeam(PROF_ICECREAM) == NULL &&
795 			(gWorldSectorX != gMercProfiles[HAMOUS].sSectorX || gWorldSectorY != gMercProfiles[HAMOUS].sSectorY || gbWorldSectorZ != 0))
796 	{
797 		// ok, HAMOUS's sector not loaded, so time to move!
798 		// might be same sector as before, if so, oh well!
799 		auto placement = GCM->getNpcPlacement(HAMOUS);
800 		UINT8 sector   = placement->pickPlacementSector();
801 		gMercProfiles[HAMOUS].sSectorX = SECTORX(sector);
802 		gMercProfiles[HAMOUS].sSectorY = SECTORY(sector);
803 		gMercProfiles[PROF_ICECREAM].sSectorX = SECTORX(sector);
804 		gMercProfiles[PROF_ICECREAM].sSectorY = SECTORY(sector);
805 	}
806 
807 	// Does Rat take off?
808 	if ( gMercProfiles[ RAT ].bNPCData != 0 )
809 	{
810 		gMercProfiles[ RAT ].sSectorX = 0;
811 		gMercProfiles[ RAT ].sSectorY = 0;
812 		gMercProfiles[ RAT ].bSectorZ = 0;
813 	}
814 
815 
816 	// Empty money from pockets of Vince 69, Willis 80, and Jenny 132
817 	SetMoneyInSoldierProfile( VINCE, 0 );
818 	SetMoneyInSoldierProfile( STEVE, 0 ); // Steven Willis
819 	SetMoneyInSoldierProfile( JENNY, 0 );
820 
821 	// Vince is no longer expecting money
822 	SetFactFalse( FACT_VINCE_EXPECTING_MONEY );
823 
824 	// Reset Darren's balance and money
825 	gMercProfiles[ DARREN ].iBalance = 0;
826 	SetMoneyInSoldierProfile( DARREN, 15000 );
827 
828 	// set Carmen to be placed on the map in case he moved and is waiting off screen
829 	if (gMercProfiles[ CARMEN ].ubMiscFlags2 & PROFILE_MISC_FLAG2_DONT_ADD_TO_SECTOR)
830 	{
831 		gMercProfiles[ CARMEN ].ubMiscFlags2 &= ~(PROFILE_MISC_FLAG2_DONT_ADD_TO_SECTOR);
832 		// move Carmen to C13
833 		gMercProfiles[ CARMEN ].sSectorX = 13;
834 		gMercProfiles[ CARMEN ].sSectorY = MAP_ROW_C;
835 		gMercProfiles[ CARMEN ].bSectorZ = 0;
836 
837 		// we should also reset # of terrorist heads and give him cash
838 		if (gMercProfiles[ CARMEN ].bNPCData2 > 0)
839 		{
840 			if (gMercProfiles[ CARMEN ].uiMoney < 10000)
841 			{
842 				uiAmount = 0;
843 			}
844 			else
845 			{
846 				uiAmount = gMercProfiles[ CARMEN ].uiMoney;
847 			}
848 			uiAmount += 10000 * gMercProfiles[ CARMEN ].bNPCData2;
849 			SetMoneyInSoldierProfile( CARMEN, uiAmount );
850 			gMercProfiles[ CARMEN ].bNPCData2 = 0;
851 
852 			for ( cnt = HEAD_1; cnt <= HEAD_7; cnt++ )
853 			{
854 				RemoveObjectFromSoldierProfile( CARMEN, (UINT8) cnt );
855 			}
856 
857 		}
858 	}
859 	else
860 	{
861 		// randomize where he'll be today... so long as his sector's not loaded
862 
863 		if ( gMercProfiles[ CARMEN ].sSectorX != gWorldSectorX || gMercProfiles[ CARMEN ].sSectorY != gWorldSectorY )
864 		{
865 			auto placement = GCM->getNpcPlacement(CARMEN);
866 			UINT8 sector   = placement->pickPlacementSector();
867 			gMercProfiles[CARMEN].sSectorX = SECTORX(sector);
868 			gMercProfiles[CARMEN].sSectorY = SECTORY(sector);
869 
870 			// he should have $5000... unless the player forgot to meet him
871 			if (gMercProfiles[ CARMEN ].uiMoney < 5000)
872 			{
873 				SetMoneyInSoldierProfile( CARMEN, 5000 );
874 			}
875 		}
876 	}
877 
878 	if ( PreRandom( 3 ) == 0 )
879 	{
880 		SetFactTrue( FACT_DAVE_HAS_GAS );
881 	}
882 	else
883 	{
884 		SetFactFalse( FACT_DAVE_HAS_GAS );
885 	}
886 
887 	if ( gWorldSectorX == HOSPITAL_SECTOR_X && gWorldSectorY == HOSPITAL_SECTOR_Y && gbWorldSectorZ == HOSPITAL_SECTOR_Z )
888 	{
889 		CheckForMissingHospitalSupplies();
890 	}
891 
892 }
893 
MakeCivGroupHostileOnNextSectorEntrance(UINT8 ubCivGroup)894 void MakeCivGroupHostileOnNextSectorEntrance( UINT8 ubCivGroup )
895 {
896 	// if it's the rebels that will become hostile, reduce town loyalties NOW, not later
897 	if ( ubCivGroup == REBEL_CIV_GROUP && gTacticalStatus.fCivGroupHostile[ ubCivGroup ] == CIV_GROUP_NEUTRAL )
898 	{
899 		ReduceLoyaltyForRebelsBetrayed();
900 	}
901 
902 	gTacticalStatus.fCivGroupHostile[ ubCivGroup ] = CIV_GROUP_WILL_BECOME_HOSTILE;
903 }
904 
RemoveAssassin(UINT8 ubProfile)905 void RemoveAssassin( UINT8 ubProfile )
906 {
907 	gMercProfiles[ ubProfile ].sSectorX = 0;
908 	gMercProfiles[ ubProfile ].sSectorY = 0;
909 	gMercProfiles[ ubProfile ].bLife = gMercProfiles[ ubProfile ].bLifeMax;
910 }
911 
CheckForMissingHospitalSupplies(void)912 void CheckForMissingHospitalSupplies( void )
913 {
914 	UINT8					ubMedicalObjects = 0;
915 
916 	CFOR_EACH_WORLD_ITEM(wi)
917 	{
918 		// loop through all items, look for ownership
919 		if (wi.o.usItem != OWNERSHIP || wi.o.ubOwnerCivGroup != DOCTORS_CIV_GROUP) continue;
920 
921 		const ITEM_POOL* pItemPool = GetItemPool(wi.sGridNo, 0);
922 		while( pItemPool )
923 		{
924 			OBJECTTYPE const& o = GetWorldItem(pItemPool->iItemIndex).o;
925 			if (o.bStatus[0] > 60)
926 			{
927 				if (o.usItem == FIRSTAIDKIT || o.usItem == MEDICKIT || o.usItem == REGEN_BOOSTER || o.usItem == ADRENALINE_BOOSTER)
928 				{
929 					ubMedicalObjects++;
930 				}
931 			}
932 
933 			pItemPool = pItemPool->pNext;
934 		}
935 	}
936 
937 	if ( CheckFact( FACT_PLAYER_STOLE_MEDICAL_SUPPLIES_AGAIN, 0 ) == TRUE )
938 	{
939 		// player returning stuff!  if back to full then can operate
940 		if ( ubMedicalObjects >= gubCambriaMedicalObjects )
941 		{
942 			SetFactFalse( FACT_PLAYER_STOLE_MEDICAL_SUPPLIES_AGAIN );
943 			SetFactFalse( FACT_PLAYER_STOLE_MEDICAL_SUPPLIES );
944 			return;
945 		}
946 	}
947 
948 	if ( ubMedicalObjects < gubCambriaMedicalObjects )
949 	{
950 		// player's stolen something!
951 		if (!CheckFact(FACT_PLAYER_STOLE_MEDICAL_SUPPLIES, 0))
952 		{
953 			SetFactTrue( FACT_PLAYER_STOLE_MEDICAL_SUPPLIES );
954 		}
955 
956 		// if only 1/5 or less left, give up the ghost
957 		if ( ubMedicalObjects * 5 <= gubCambriaMedicalObjects )
958 		{
959 			// run out!
960 			SetFactTrue( FACT_PLAYER_STOLE_MEDICAL_SUPPLIES_AGAIN );
961 		}
962 	}
963 
964 }
965 
966 
DropOffItemsInDestination(UINT8 ubOrderNum,const ShippingDestinationModel * shippingDest)967 static void DropOffItemsInDestination(UINT8 ubOrderNum, const ShippingDestinationModel* shippingDest)
968 {
969 	OBJECTTYPE		Object;
970 	UINT32	uiCount = 0;
971 	OBJECTTYPE	*pObject=NULL;
972 	UINT16	usNumberOfItems=0, usItem;
973 	UINT8		ubItemsDelivered, ubTempNumItems;
974 	UINT32	i;
975 
976 	//if the player doesnt "own" the sector,
977 	if (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(shippingDest->getDeliverySector())].fEnemyControlled)
978 	{
979 		//the items disappear
980 		gpNewBobbyrShipments[ ubOrderNum ].fActive = FALSE;
981 		return;
982 	}
983 
984 	const BOOLEAN fSectorLoaded =
985 		CloseCrate(shippingDest->deliverySectorX, shippingDest->deliverySectorY, shippingDest->deliverySectorZ, shippingDest->deliverySectorGridNo);
986 
987 	for(i=0; i<gpNewBobbyrShipments[ ubOrderNum ].ubNumberPurchases; i++)
988 	{
989 		// Count how many items were purchased
990 		usNumberOfItems += gpNewBobbyrShipments[ ubOrderNum ].BobbyRayPurchase[i].ubNumberPurchased;
991 	}
992 
993 	//if we are NOT currently in the right sector
994 	if( !fSectorLoaded )
995 	{
996 		//build an array of objects to be added
997 		pObject = new OBJECTTYPE[usNumberOfItems]{};
998 	}
999 
1000 
1001 	uiCount = 0;
1002 
1003 	//loop through the number of purchases
1004 	for (i = 0; i < gpNewBobbyrShipments[0].ubNumberPurchases; i++)// FIXME shipment ubOrderNum instead of 0
1005 	{
1006 		ubItemsDelivered = gpNewBobbyrShipments[ ubOrderNum ].BobbyRayPurchase[i].ubNumberPurchased;
1007 		usItem = gpNewBobbyrShipments[ ubOrderNum ].BobbyRayPurchase[i].usItemIndex;
1008 
1009 		while ( ubItemsDelivered )
1010 		{
1011 			// treat 0s as 1s :-)
1012 			ubTempNumItems = __min( ubItemsDelivered, __max( 1, GCM->getItem(usItem )->getPerPocket() ) );
1013 			CreateItems( usItem, gpNewBobbyrShipments[ ubOrderNum ].BobbyRayPurchase[i].bItemQuality, ubTempNumItems, &Object );
1014 
1015 			// stack as many as possible
1016 			if( fSectorLoaded )
1017 			{
1018 				AddItemToPool(shippingDest->deliverySectorGridNo, &Object, INVISIBLE, 0, 0, 0);
1019 			}
1020 			else
1021 			{
1022 				pObject[uiCount] = Object;
1023 				uiCount++;
1024 			}
1025 
1026 			ubItemsDelivered -= ubTempNumItems;
1027 		}
1028 	}
1029 
1030 	//if the sector WASNT loaded
1031 	if( !fSectorLoaded )
1032 	{
1033 		//add all the items from the array that was built above
1034 
1035 		//The item are to be added to the Top part of Drassen, grid loc's  10112, 9950
1036 		AddItemsToUnLoadedSector(shippingDest->deliverySectorX, shippingDest->deliverySectorY, shippingDest->deliverySectorZ, shippingDest->deliverySectorGridNo, uiCount, pObject, 0, 0, 0, INVISIBLE);
1037 		delete[] pObject;
1038 		pObject = NULL;
1039 	}
1040 
1041 	//mark that the shipment has arrived
1042 	gpNewBobbyrShipments[ ubOrderNum ].fActive = FALSE;
1043 
1044 	if (shippingDest->emailOffset) {
1045 		//Add an email telling the user the shipment is there
1046 		AddEmail(shippingDest->emailOffset, shippingDest->emailLength, BOBBY_R, GetWorldTotalMin());
1047 	}
1048 	else
1049 	{
1050 		SLOGW("Bobby Ray shipment arrived but no email template found.");
1051 	}
1052 }
1053