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