1 #include "MapScreen.h"
2 #include "MessageBoxScreen.h"
3 #include "Overhead.h"
4 #include "Types.h"
5 #include "Merc_Contract.h"
6 #include "Soldier_Profile.h"
7 #include "History.h"
8 #include "Finances.h"
9 #include "Game_Clock.h"
10 #include "Soldier_Add.h"
11 #include "Dialogue_Control.h"
12 #include "Soldier_Create.h"
13 #include "Personnel.h"
14 #include "LaptopSave.h"
15 #include "Map_Screen_Interface.h"
16 #include "StrategicMap.h"
17 #include "Quests.h"
18 #include "WorldDef.h"
19 #include "Rotting_Corpses.h"
20 #include "Strategic_Merc_Handler.h"
21 #include "GameScreen.h"
22 #include "JAScreens.h"
23 #include "Random.h"
24 #include "Assignments.h"
25 #include "Strategic_Movement.h"
26 #include "Squads.h"
27 #include "Text.h"
28 #include "Strategic_Status.h"
29 #include "Mercs.h"
30 #include "Insurance_Contract.h"
31 #include "Vehicles.h"
32 #include "EMail.h"
33 #include "Debug.h"
34 #include "ScreenIDs.h"
35 #include "FileMan.h"
36 #include "ContentManager.h"
37 #include "GameInstance.h"
38 #include "ShippingDestinationModel.h"
39 #include "MercProfile.h"
40
41 #include <string_theory/string>
42
43
44 struct CONTRACT_NEWAL_LIST_NODE
45 {
46 UINT8 ubProfileID;
47 UINT8 ubFiller[3]; // XXX HACK000B
48 };
49
50
51 static SOLDIERTYPE* pLeaveSoldier = NULL;
52
53 BOOLEAN fEnterMapDueToContract = FALSE;
54
55
56 SOLDIERTYPE *pContractReHireSoldier = NULL;
57
58 static UINT8 gubContractLength = 0; // Used when extending a mercs insurance contract
59 static SOLDIERTYPE* gpInsuranceSoldier = 0;
60
61 // The values need to be saved!
62 static CONTRACT_NEWAL_LIST_NODE ContractRenewalList[20];
63 static UINT8 ubNumContractRenewals = 0;
64 // end
65 static UINT8 ubCurrentContractRenewal = 0;
66 static UINT8 ubCurrentContractRenewalInProgress = FALSE;
67 BOOLEAN gfContractRenewalSquenceOn = FALSE;
68 BOOLEAN gfInContractMenuFromRenewSequence = FALSE;
69
70
71 // the airport sector
72 #define AIRPORT_SECTOR SEC_B13
73
74
SaveContractRenewalDataToSaveGameFile(HWFILE const hFile)75 void SaveContractRenewalDataToSaveGameFile(HWFILE const hFile)
76 {
77 FileWrite(hFile, ContractRenewalList, sizeof(ContractRenewalList));
78 FileWrite(hFile, &ubNumContractRenewals, sizeof(ubNumContractRenewals));
79 }
80
81
LoadContractRenewalDataFromSaveGameFile(HWFILE const hFile)82 void LoadContractRenewalDataFromSaveGameFile(HWFILE const hFile)
83 {
84 FileRead(hFile, ContractRenewalList, sizeof(ContractRenewalList));
85 FileRead(hFile, &ubNumContractRenewals, sizeof(ubNumContractRenewals));
86 }
87
88
89 static BOOLEAN ContractIsExpiring(SOLDIERTYPE* pSoldier);
90
91
BeginContractRenewalSequence()92 void BeginContractRenewalSequence()
93 {
94 for (CONTRACT_NEWAL_LIST_NODE const* i = ContractRenewalList, * const end = i + ubNumContractRenewals; i != end; ++i)
95 {
96 SOLDIERTYPE* const s = FindSoldierByProfileID(i->ubProfileID);
97 if (!s) continue;
98 if (s->bLife == 0) continue;
99 if (s->bAssignment == IN_TRANSIT) continue;
100 if (s->bAssignment == ASSIGNMENT_POW) continue;
101
102 // Double check there are valid people here that still want to renew
103 if (!ContractIsExpiring(s)) continue;
104 // The user hasn't renewed yet, and is still leaving today
105
106 gfContractRenewalSquenceOn = TRUE;
107 ubCurrentContractRenewal = 0;
108 ubCurrentContractRenewalInProgress = 0;
109 PauseGame();
110 LockPauseState(LOCK_PAUSE_CONTRACT_RENEWAL);
111 InterruptTime();
112 MakeDialogueEventEnterMapScreen();
113 break;
114 }
115 }
116
117
118 static void EndCurrentContractRenewal(void);
119
120
HandleContractRenewalSequence()121 void HandleContractRenewalSequence( )
122 {
123 if ( gfContractRenewalSquenceOn )
124 {
125 // Should we stop now?
126 if ( ubCurrentContractRenewal == ubNumContractRenewals )
127 {
128 // Stop and clear any on list...
129 ubNumContractRenewals = 0;
130 gfContractRenewalSquenceOn = FALSE;
131 UnLockPauseState();
132 }
133
134 // Get soldier - if there is none, adavance to next
135 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ContractRenewalList[ubCurrentContractRenewal].ubProfileID); // Steve Willis, 80
136
137 if ( pSoldier == NULL )
138 {
139 // Advance to next guy!
140 EndCurrentContractRenewal( );
141 return;
142 }
143
144 // OK, check if it's in progress...
145 if ( !ubCurrentContractRenewalInProgress )
146 {
147 // Double check contract situation....
148 if ( ContractIsExpiring( pSoldier ) )
149 {
150 // Set this one in motion!
151 ubCurrentContractRenewalInProgress = 1;
152
153 // Handle start here...
154
155 // Determine what quote to use....
156 bool const wants_to_renew = WillMercRenew(pSoldier, FALSE);
157 if (!wants_to_renew)
158 {
159 // OK, he does not want to renew.......
160 HandleImportantMercQuote( pSoldier, QUOTE_MERC_LEAVING_ALSUCO_SOON );
161 }
162 else
163 {
164 // OK check what dialogue to play
165 // If we have not used this one before....
166 if ( pSoldier->ubContractRenewalQuoteCode == SOLDIER_CONTRACT_RENEW_QUOTE_NOT_USED )
167 {
168 HandleImportantMercQuoteLocked(pSoldier, QUOTE_CONTRACTS_OVER);
169 }
170 // Else if we have said 89 already......
171 else if ( pSoldier->ubContractRenewalQuoteCode == SOLDIER_CONTRACT_RENEW_QUOTE_89_USED )
172 {
173 HandleImportantMercQuoteLocked(pSoldier, QUOTE_MERC_LEAVING_ALSUCO_SOON);
174 }
175 }
176
177 class DialogueEventContract : public CharacterDialogueEvent
178 {
179 public:
180 DialogueEventContract(SOLDIERTYPE& soldier, bool const wants_to_renew) :
181 CharacterDialogueEvent(soldier),
182 wants_to_renew_(wants_to_renew)
183 {}
184
185 bool Execute()
186 {
187 LockMapScreenInterface(true);
188 SOLDIERTYPE& s = soldier_;
189 if (wants_to_renew_) CheckIfSalaryIncreasedAndSayQuote(&s, FALSE);
190 gfInContractMenuFromRenewSequence = TRUE;
191 MakeDialogueEventShowContractMenu(s);
192 LockMapScreenInterface(false);
193 return false;
194 }
195
196 private:
197 bool const wants_to_renew_;
198 };
199
200 DialogueEvent::Add(new DialogueEventContract(*pSoldier, wants_to_renew));
201 }
202 else
203 {
204 // Skip to next guy!
205 EndCurrentContractRenewal( );
206 }
207 }
208 }
209 }
210
211
EndCurrentContractRenewal(void)212 static void EndCurrentContractRenewal(void)
213 {
214 // Are we in the requence?
215 if ( gfContractRenewalSquenceOn )
216 {
217 // OK stop this one and increment current one
218 ubCurrentContractRenewalInProgress = FALSE;
219 gfInContractMenuFromRenewSequence = FALSE;
220
221 ubCurrentContractRenewal++;
222
223 }
224 }
225
226
227 static void HandleNotifyPlayerCanAffordInsurance(SOLDIERTYPE* pSoldier, UINT8 ubLength, INT32 iCost);
228 static void HandleNotifyPlayerCantAffordInsurance(void);
229
230
231 // This is used only to EXTEND the contract of an AIM merc already on the team
MercContractHandling(SOLDIERTYPE * const s,UINT8 const ubDesiredAction)232 BOOLEAN MercContractHandling(SOLDIERTYPE* const s, UINT8 const ubDesiredAction)
233 {
234 /* Determine what kind of merc the contract is being extended for (only AIM
235 * mercs can extend contract) */
236 if (s->ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC) return FALSE;
237
238 // Set the contract length and the charge
239 INT32 contract_charge;
240 INT32 contract_length;
241 UINT8 history_contract_type;
242 UINT8 finances_contract_type;
243 MERCPROFILESTRUCT const& p = GetProfile(s->ubProfile);
244 switch (ubDesiredAction)
245 {
246 case CONTRACT_EXTEND_1_DAY:
247 contract_charge = p.sSalary;
248 contract_length = 1;
249 history_contract_type = HISTORY_EXTENDED_CONTRACT_1_DAY;
250 finances_contract_type = EXTENDED_CONTRACT_BY_1_DAY;
251 break;
252
253 case CONTRACT_EXTEND_1_WEEK:
254 contract_charge = p.uiWeeklySalary;
255 contract_length = 7;
256 history_contract_type = HISTORY_EXTENDED_CONTRACT_1_WEEK;
257 finances_contract_type = EXTENDED_CONTRACT_BY_1_WEEK;
258 break;
259
260 case CONTRACT_EXTEND_2_WEEK:
261 contract_charge = p.uiBiWeeklySalary;
262 contract_length = 14;
263 history_contract_type = HISTORY_EXTENDED_CONTRACT_2_WEEK;
264 finances_contract_type = EXTENDED_CONTRACT_BY_2_WEEKS;
265 break;
266
267 default:
268 return FALSE;
269 }
270
271 // Check if the merc has enough money
272 if (LaptopSaveInfo.iCurrentBalance < contract_charge) return FALSE;
273
274 if (!WillMercRenew(s, TRUE))
275 {
276 // Remove soldier (if this is setup because normal contract ending sequence)
277 if (ContractIsExpiring(s))
278 {
279 MakeCharacterDialogueEventContractEnding(*s, true);
280 }
281 return FALSE;
282 }
283
284 LockMapScreenInterface(true);
285
286 // These calcs need to be done before Getting/Calculating the insurance costs
287
288 // Set the contract length and the charge
289 s->iTotalContractLength += contract_length;
290 s->bTypeOfLastContract = ubDesiredAction;
291
292 // Determine the end of the contract
293 s->iEndofContractTime += contract_length * 1440;
294
295 if (s->usLifeInsurance && s->bAssignment != ASSIGNMENT_POW)
296 {
297 // Check if player can afford insurance, if not, tell them
298 INT32 const iCostOfInsurance = CalculateInsuranceContractCost(contract_length, s->ubProfile);
299
300 HandleImportantMercQuote(s, QUOTE_ACCEPT_CONTRACT_RENEWAL);
301
302 if (iCostOfInsurance > LaptopSaveInfo.iCurrentBalance - contract_charge)
303 {
304 HandleNotifyPlayerCantAffordInsurance();
305
306 // Handle ending of renew session
307 if (gfInContractMenuFromRenewSequence)
308 {
309 EndCurrentContractRenewal();
310 }
311 }
312 else
313 { // Can afford, ask if they want it
314 HandleNotifyPlayerCanAffordInsurance(s, contract_length, iCostOfInsurance);
315 }
316 }
317 else
318 { // No need to query for life insurance
319 HandleImportantMercQuote(s, QUOTE_ACCEPT_CONTRACT_RENEWAL);
320
321 // handle ending of renew session
322 if (gfInContractMenuFromRenewSequence)
323 {
324 EndCurrentContractRenewal();
325 }
326 }
327
328 LockMapScreenInterface(false);
329
330 /* ATE: Setup when they can be signed again! If they are 2-weeks this can be
331 * extended otherwise don't change from current */
332 if (ubDesiredAction == CONTRACT_EXTEND_2_WEEK)
333 {
334 s->iTimeCanSignElsewhere = s->iEndofContractTime;
335 }
336
337 /* Add entries in the finacial and history pages for the extending of the
338 * merc's contract */
339 UINT32 const now = GetWorldTotalMin();
340 AddTransactionToPlayersBook(finances_contract_type, s->ubProfile, now, -contract_charge);
341 AddHistoryToPlayersLog(history_contract_type, s->ubProfile, now, s->sSectorX, s->sSectorY);
342
343 return TRUE;
344 }
345
346
FindRefusalReason(SOLDIERTYPE const * const s)347 static UINT16 FindRefusalReason(SOLDIERTYPE const* const s)
348 {
349 /* Check for sources of unhappiness in order of importance, which is:
350 * 1) Hated Mercs (Highest), 2) Death Rate, 3) Morale (lowest) */
351 MERCPROFILESTRUCT const& p = GetProfile(s->ubProfile);
352
353 // see if someone the merc hates is on the team
354 for (UINT8 i = 0; i < 2; ++i)
355 {
356 INT8 const bMercID = p.bHated[i];
357 if (bMercID < 0) continue;
358
359 if (!IsMercOnTeamAndInOmertaAlreadyAndAlive(bMercID)) continue;
360
361 if (p.bHatedCount[i] != 0)
362 { // tolerance is > 0, only gripe if in same sector
363 SOLDIERTYPE const* const hated = FindSoldierByProfileIDOnPlayerTeam(bMercID);
364 if (!hated) continue;
365 if (hated->sSectorX != s->sSectorX) continue;
366 if (hated->sSectorY != s->sSectorY) continue;
367 if (hated->bSectorZ != s->bSectorZ) continue;
368 }
369
370 // our tolerance has run out!
371 // use first hated in case there are multiple
372 return
373 i == 0 ? QUOTE_HATE_MERC_1_ON_TEAM_WONT_RENEW :
374 QUOTE_HATE_MERC_2_ON_TEAM_WONT_RENEW;
375 }
376
377 // now check for learn to hate
378 INT8 const bMercID = p.bLearnToHate;
379 if (bMercID >= 0 && IsMercOnTeamAndInOmertaAlreadyAndAlive(bMercID))
380 {
381 if (p.bLearnToHateCount == 0)
382 {
383 // our tolerance has run out!
384 return QUOTE_LEARNED_TO_HATE_MERC_1_ON_TEAM_WONT_RENEW;
385 }
386 else if (p.bLearnToHateCount <= p.bLearnToHateTime / 2)
387 {
388 const SOLDIERTYPE* const pHated = FindSoldierByProfileIDOnPlayerTeam(bMercID);
389 if (pHated &&
390 pHated->sSectorX == s->sSectorX &&
391 pHated->sSectorY == s->sSectorY &&
392 pHated->bSectorZ == s->bSectorZ)
393 {
394 return QUOTE_LEARNED_TO_HATE_MERC_1_ON_TEAM_WONT_RENEW;
395 }
396 }
397 }
398
399 if (MercThinksDeathRateTooHigh(p)) return QUOTE_DEATH_RATE_RENEWAL;
400 if (MercThinksHisMoraleIsTooLow(s)) return QUOTE_REFUSAL_RENEW_DUE_TO_MORALE;
401 return QUOTE_NONE;
402 }
403
404
WillMercRenew(SOLDIERTYPE * const s,BOOLEAN const say_quote)405 BOOLEAN WillMercRenew(SOLDIERTYPE* const s, BOOLEAN const say_quote)
406 {
407 if (s->ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC) return FALSE;
408
409 // does the merc have another contract already lined up?
410 if (s->fSignedAnotherContract)
411 {
412 // NOTE: Having a buddy around will NOT stop a merc from leaving on another contract (IC's call)
413 if (say_quote)
414 {
415 HandleImportantMercQuoteLocked(s, QUOTE_WONT_RENEW_CONTRACT_LAME_REFUSAL);
416 }
417 return FALSE;
418 }
419
420 UINT16 const reason_quote = FindRefusalReason(s);
421 // happy? no problem
422 if (reason_quote == QUOTE_NONE) return TRUE;
423 MERCPROFILESTRUCT& p = GetProfile(s->ubProfile);
424
425 // find out if the merc has a buddy working for the player
426 UINT16 buddy_quote;
427 switch (GetFirstBuddyOnTeam(p))
428 {
429 case 0: buddy_quote = QUOTE_RENEWING_CAUSE_BUDDY_1_ON_TEAM; break;
430 case 1: buddy_quote = QUOTE_RENEWING_CAUSE_BUDDY_2_ON_TEAM; break;
431 case 2: buddy_quote = QUOTE_RENEWING_CAUSE_LEARNED_TO_LIKE_BUDDY_ON_TEAM; break;
432 default: buddy_quote = QUOTE_NONE; break;
433 }
434
435 if (say_quote)
436 {
437 // If a buddy is around, agree to renew, but tell us why we're doing it.
438 UINT16 const quote =
439 buddy_quote != QUOTE_NONE ? buddy_quote :
440 #if 0 // ARM: Delay quote too vague, no longer to be used
441 SoldierWantsToDelayRenewalOfContract(s) ? QUOTE_DELAY_CONTRACT_RENEWAL :
442 #endif
443 reason_quote;
444
445 // check if we say the precedent for merc
446 UINT8 const quote_bit = GetQuoteBitNumberFromQuoteID(quote);
447 if (GetMercPrecedentQuoteBitStatus(&p, quote_bit))
448 {
449 HandleImportantMercQuoteLocked(s, QUOTE_PRECEDENT_TO_REPEATING_ONESELF_RENEW);
450 }
451 else
452 {
453 SetMercPrecedentQuoteBitStatus(&p, quote_bit);
454 }
455
456 HandleImportantMercQuoteLocked(s, quote);
457 }
458
459 return buddy_quote != QUOTE_NONE;
460 }
461
462
HandleSoldierLeavingWithLowMorale(SOLDIERTYPE * pSoldier)463 static void HandleSoldierLeavingWithLowMorale(SOLDIERTYPE* pSoldier)
464 {
465 if( MercThinksHisMoraleIsTooLow( pSoldier ) )
466 {
467 // this will cause him give us lame excuses for a while until he gets over it
468 // 3-6 days (but the first 1-2 days of that are spent "returning" home)
469 gMercProfiles[ pSoldier->ubProfile ].ubDaysOfMoraleHangover = (UINT8) (3 + Random(4));
470 }
471 }
472
473
HandleSoldierLeavingForAnotherContract(SOLDIERTYPE & s)474 static void HandleSoldierLeavingForAnotherContract(SOLDIERTYPE& s)
475 {
476 if (!s.fSignedAnotherContract) return;
477 // Merc goes to work elsewhere
478 MERCPROFILESTRUCT& p = GetProfile(s.ubProfile);
479 p.bMercStatus = MERC_WORKING_ELSEWHERE;
480 p.uiDayBecomesAvailable += 1 + Random(6 + s.bExpLevel / 2); // 1-(6 to 11) days
481 }
482
483
484 /*
485 BOOLEAN SoldierWantsToDelayRenewalOfContract( SOLDIERTYPE *pSoldier )
486 {
487
488 INT8 bTypeOfCurrentContract = 0; // what kind of contract the merc has..1 day, week or 2 week
489 INT32 iLeftTimeOnContract = 0; // how much time til contract expires..in minutes
490 INT32 iToleranceLevelForContract = 0; // how much time before contract ends before merc actually speaks thier mind
491
492 // does the soldier want to delay renew of contract, possibly due to poor performance by player
493 if( pSoldier->ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC )
494 return( FALSE );
495
496 // type of contract the merc had
497 bTypeOfCurrentContract = pSoldier -> bTypeOfLastContract;
498 iLeftTimeOnContract = pSoldier->iEndofContractTime - GetWorldTotalMin();
499
500 // grab tolerance
501 switch( bTypeOfCurrentContract )
502 {
503 case( CONTRACT_EXTEND_1_DAY ):
504 // 20 hour tolerance on 24 hour contract
505 iToleranceLevelForContract = 20 * 60;
506 break;
507 case( CONTRACT_EXTEND_1_WEEK ):
508 // two day tolerance for 1 week
509 iToleranceLevelForContract = 2 * 24 * 60;
510 break;
511 case( CONTRACT_EXTEND_2_WEEK ):
512 // three day on 2 week contract
513 iToleranceLevelForContract = 3 * 24 * 60;
514 break;
515 }
516
517 if( iLeftTimeOnContract > iToleranceLevelForContract )
518 {
519 return( TRUE );
520 }
521 else
522 {
523 return( FALSE );
524 }
525
526 }
527 */
528
529
530 // this is called once a day (daily update) for every merc working for the player
CheckIfMercGetsAnotherContract(SOLDIERTYPE & s)531 void CheckIfMercGetsAnotherContract(SOLDIERTYPE& s)
532 {
533 // AIM merc?
534 if (s.ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC) return;
535
536 UINT32 const now = GetWorldTotalMin();
537 // ATE: Check time we have and see if we can accept new contracts
538 if (now <= (UINT32)s.iTimeCanSignElsewhere) return;
539
540 if (s.fSignedAnotherContract) return;
541 // He doesn't already have another contract
542
543 /* Chance depends on how much time he has left in his contract and his
544 * experience level (determines demand) */
545 UINT32 const full_days_remaining = (s.iEndofContractTime - now) / (24 * 60);
546 if (full_days_remaining >= 3) return;
547
548 UINT32 const chance = (3 - full_days_remaining) * s.bExpLevel;
549 if (Chance(chance)) s.fSignedAnotherContract = TRUE;
550 }
551
552
553 static void HandleUniqueEventWhenPlayerLeavesTeam(SOLDIERTYPE* pSoldier);
554 static void NotifyPlayerOfMercDepartureAndPromptEquipmentPlacement(SOLDIERTYPE&, bool add_rehire_button);
555
556
MakeCharacterDialogueEventContractEnding(SOLDIERTYPE & s,bool const add_rehire_button)557 void MakeCharacterDialogueEventContractEnding(SOLDIERTYPE& s, bool const add_rehire_button)
558 {
559 class CharacterDialogueEventContractEnding : public CharacterDialogueEvent
560 {
561 public:
562 CharacterDialogueEventContractEnding(SOLDIERTYPE& s, bool const add_rehire_button) :
563 CharacterDialogueEvent(s),
564 add_rehire_button_(add_rehire_button)
565 {}
566
567 bool Execute()
568 {
569 if (!MayExecute()) return true;
570
571 InterruptTime();
572 PauseGame();
573 LockPauseState(LOCK_PAUSE_CONTRACT_ENDING);
574
575 SOLDIERTYPE& s = soldier_;
576 // If the soldier may have some special action when he/she leaves the party, handle it
577 HandleUniqueEventWhenPlayerLeavesTeam(&s);
578
579 // If the soldier is an EPC, don't ask about equipment
580 if (s.ubWhatKindOfMercAmI == MERC_TYPE__EPC)
581 {
582 UnEscortEPC(&s);
583 }
584 else
585 {
586 NotifyPlayerOfMercDepartureAndPromptEquipmentPlacement(s, add_rehire_button_);
587 }
588 return false;
589 }
590
591 private:
592 bool const add_rehire_button_;
593 };
594
595 DialogueEvent::Add(new CharacterDialogueEventContractEnding(s, add_rehire_button));
596 }
597
598
MakeCharacterDialogueEventContractEndingNoAskEquip(SOLDIERTYPE & s)599 void MakeCharacterDialogueEventContractEndingNoAskEquip(SOLDIERTYPE& s)
600 {
601 class CharacterDialogueEventContractEndingNoAskEquip: public CharacterDialogueEvent
602 {
603 public:
604 CharacterDialogueEventContractEndingNoAskEquip(SOLDIERTYPE& s) : CharacterDialogueEvent(s) {}
605
606 bool Execute()
607 {
608 if (!MayExecute()) return true;
609
610 StrategicRemoveMerc(soldier_);
611 return false;
612 }
613 };
614
615 DialogueEvent::Add(new CharacterDialogueEventContractEndingNoAskEquip(s));
616 }
617
618
619 static void CalculateMedicalDepositRefund(SOLDIERTYPE const&);
620
621
StrategicRemoveMerc(SOLDIERTYPE & s)622 void StrategicRemoveMerc(SOLDIERTYPE& s)
623 {
624 if (gfInContractMenuFromRenewSequence)
625 {
626 EndCurrentContractRenewal();
627 }
628
629 // ATE: Determine which HISTORY ENTRY to use...
630 if (s.ubLeaveHistoryCode == 0)
631 {
632 // Default use contract expired reason...
633 s.ubLeaveHistoryCode = HISTORY_MERC_CONTRACT_EXPIRED;
634 }
635
636 UINT8 const ubHistoryCode = s.ubLeaveHistoryCode;
637
638 if (s.bLife <= 0)
639 { // The soldier is dead
640 AddCharacterToDeadList(&s);
641 }
642 else if (ubHistoryCode == HISTORY_MERC_FIRED || s.bAssignment == ASSIGNMENT_POW)
643 { // The merc was fired
644 AddCharacterToFiredList(&s);
645 }
646 else
647 { // The merc is leaving for some other reason
648 AddCharacterToOtherList(&s);
649 }
650
651 if (s.ubWhatKindOfMercAmI == MERC_TYPE__NPC)
652 {
653 SetupProfileInsertionDataForSoldier(&s);
654 }
655
656 if (s.bAssignment >= ON_DUTY && s.ubGroupID)
657 { // He/she is in a mvt group, remove and destroy the group
658 if (s.bAssignment != VEHICLE)
659 { //Can only remove groups if they aren't persistant (not in a squad or vehicle)
660 RemoveGroup(*GetGroup(s.ubGroupID));
661 }
662 else
663 {
664 TakeSoldierOutOfVehicle(&s);
665 }
666 }
667
668 MERCPROFILESTRUCT& p = GetProfile(s.ubProfile);
669 // if the merc is not dead
670 if (p.bMercStatus != MERC_IS_DEAD)
671 {
672 // Set the status to returning home (delay the merc for rehire)
673 p.bMercStatus = MERC_RETURNING_HOME;
674
675 // specify how long the merc will continue to be unavailable
676 p.uiDayBecomesAvailable = 1 + Random(2); // 1-2 days
677
678 HandleSoldierLeavingWithLowMorale(&s);
679 HandleSoldierLeavingForAnotherContract(s);
680 }
681
682 //add an entry in the history page for the firing/quiting of the merc
683 // ATE: Don't do this if they are already dead!
684 if (!(s.uiStatusFlags & SOLDIER_DEAD))
685 {
686 AddHistoryToPlayersLog(ubHistoryCode, s.ubProfile, GetWorldTotalMin(), s.sSectorX, s.sSectorY);
687 }
688
689 //if the merc was a POW, remember it becuase the merc cant show up in AIM or MERC anymore
690 if (s.bAssignment == ASSIGNMENT_POW)
691 {
692 p.bMercStatus = MERC_FIRED_AS_A_POW;
693 }
694 else //else the merc CAN get his medical deposit back
695 {
696 //Determine how much of a Medical deposit is going to be refunded to the player
697 CalculateMedicalDepositRefund(s);
698 }
699
700 TacticalRemoveSoldier(s);
701
702 CheckAndHandleUnloadingOfCurrentWorld();
703
704 if (fInMapMode)
705 {
706 ReBuildCharactersList();
707 }
708
709 fMapPanelDirty = TRUE;
710 fTeamPanelDirty = TRUE;
711 fCharacterInfoPanelDirty = TRUE;
712
713 // stop time compression so player can react to the departure
714 StopTimeCompression();
715
716 UpdateTeamPanelAssignments();
717 }
718
719
CalculateMedicalDepositRefund(SOLDIERTYPE const & s)720 static void CalculateMedicalDepositRefund(SOLDIERTYPE const& s)
721 {
722 ProfileID const pid = s.ubProfile;
723 // If the merc didnt have any medical deposit, exit
724 if (!GetProfile(pid).bMedicalDeposit) return;
725
726 UINT32 const now = GetWorldTotalMin();
727 // Use the medical deposit in soldier, not in profile, which goes up with leveling
728 INT32 refund = s.usMedicalDeposit;
729 INT32 msg_offset;
730 INT32 msg_length;
731 if (s.bLife == s.bLifeMax)
732 { // The merc is at full health, refund the full medical deposit
733 AddTransactionToPlayersBook(FULL_MEDICAL_REFUND, pid, now, refund);
734 msg_offset = AIM_MEDICAL_DEPOSIT_REFUND;
735 msg_length = AIM_MEDICAL_DEPOSIT_REFUND_LENGTH;
736 }
737 else if (s.bLife > 0)
738 { // The merc is injured, refund a partial amount
739 refund = (2 * refund * s.bLifeMax / s.bLifeMax + 1) / 2;
740 AddTransactionToPlayersBook(PARTIAL_MEDICAL_REFUND, pid, now, refund);
741 msg_offset = AIM_MEDICAL_DEPOSIT_PARTIAL_REFUND;
742 msg_length = AIM_MEDICAL_DEPOSIT_PARTIAL_REFUND_LENGTH;
743 }
744 else
745 { // The merc is dead, refund nothing
746 //AddTransactionToPlayersBook(NO_MEDICAL_REFUND, pid, now, 0);
747 msg_offset = AIM_MEDICAL_DEPOSIT_NO_REFUND;
748 msg_length = AIM_MEDICAL_DEPOSIT_NO_REFUND_LENGTH;
749 }
750 AddEmailWithSpecialData(msg_offset, msg_length, AIM_SITE, now, refund, pid);
751 }
752
753
754 static void MercDepartEquipmentBoxCallBack(MessageBoxReturnValue);
755
756
757 /* Tell player this character is leaving and ask where they want the equipment
758 * left */
NotifyPlayerOfMercDepartureAndPromptEquipmentPlacement(SOLDIERTYPE & s,bool add_rehire_button)759 static void NotifyPlayerOfMercDepartureAndPromptEquipmentPlacement(SOLDIERTYPE& s, bool add_rehire_button)
760 {
761 pLeaveSoldier = &s;
762
763 if (s.fSignedAnotherContract || s.ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC)
764 {
765 add_rehire_button = false;
766 }
767
768 INT16 const x = s.sSectorX;
769 INT16 const y = s.sSectorY;
770 INT8 const z = s.bSectorZ;
771
772 ST::string town_sector = GetShortSectorString(x, y);
773
774 ST::string msg;
775 MessageBoxFlags flags;
776 MercProfile profile(s.ubProfile);
777 INT8 const sex = GetProfile(s.ubProfile).bSex;
778 if (!profile.isRPC())
779 { // The character is not an RPC
780 INT16 const elsewhere =
781 !StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(AIRPORT_SECTOR)].fEnemyControlled ? AIRPORT_SECTOR :
782 START_SECTOR;
783 if (elsewhere == SECTOR(x, y) && z == 0) goto no_choice;
784
785 // Set strings for generic buttons
786 gzUserDefinedButton1 = town_sector;
787 gzUserDefinedButton2 = GetShortSectorString(SECTORX(elsewhere), SECTORY(elsewhere));
788
789 ST::string town = GCM->getTownLocative(GetTownIdForSector(elsewhere));
790 ST::string text = sex == MALE ? str_he_leaves_where_drop_equipment : str_she_leaves_where_drop_equipment;
791 msg = st_format_printf(text, s.name, town_sector, town, gzUserDefinedButton2);
792 flags = add_rehire_button ? MSG_BOX_FLAG_GENERICCONTRACT : MSG_BOX_FLAG_GENERIC;
793 }
794 else
795 {
796 no_choice:
797 ST::string text = sex == MALE ? str_he_leaves_drops_equipment : str_she_leaves_drops_equipment;
798 msg = st_format_printf(text, s.name, town_sector);
799 flags = add_rehire_button ? MSG_BOX_FLAG_OKCONTRACT : MSG_BOX_FLAG_OK;
800 }
801
802 if (fInMapMode)
803 {
804 DoMapMessageBox(MSG_BOX_BASIC_STYLE, msg, MAP_SCREEN, flags, MercDepartEquipmentBoxCallBack);
805 }
806 else
807 {
808 DoMessageBox(MSG_BOX_BASIC_STYLE, msg, guiCurrentScreen, flags, MercDepartEquipmentBoxCallBack, 0);
809 }
810 }
811
812
813 static void HandleExtendMercsContract(SOLDIERTYPE* pSoldier);
814
815
MercDepartEquipmentBoxCallBack(MessageBoxReturnValue const exit_value)816 static void MercDepartEquipmentBoxCallBack(MessageBoxReturnValue const exit_value)
817 {
818 if (!pLeaveSoldier) return;
819 SOLDIERTYPE& s = *pLeaveSoldier;
820
821 switch (exit_value)
822 {
823 case MSG_BOX_RETURN_OK:
824 case MSG_BOX_RETURN_YES:
825 HandleLeavingOfEquipmentInCurrentSector(s);
826 break;
827
828 case MSG_BOX_RETURN_CONTRACT:
829 HandleExtendMercsContract(&s);
830 return;
831
832 default:
833 {
834 auto primaryAirport = GCM->getPrimaryShippingDestination();
835 auto airportSectorIndex = SECTOR_INFO_TO_STRATEGIC_INDEX(primaryAirport->getDeliverySector());
836 bool const in_drassen = !StrategicMap[airportSectorIndex].fEnemyControlled;
837 HandleMercLeavingEquipment(s, in_drassen);
838 break;
839 }
840 }
841
842 StrategicRemoveMerc(s);
843 pLeaveSoldier = 0;
844 }
845
846
HandleExtendMercsContract(SOLDIERTYPE * pSoldier)847 static void HandleExtendMercsContract(SOLDIERTYPE* pSoldier)
848 {
849 if (!fInMapMode)
850 {
851 gfEnteringMapScreen = TRUE;
852
853 fEnterMapDueToContract = TRUE;
854 pContractReHireSoldier = pSoldier;
855 LeaveTacticalScreen( MAP_SCREEN );
856 }
857 else
858 {
859 FindAndSetThisContractSoldier( pSoldier );
860 pContractReHireSoldier = pSoldier;
861 }
862
863 fTeamPanelDirty = TRUE;
864 fCharacterInfoPanelDirty = TRUE;
865
866 LockMapScreenInterface(true);
867 CheckIfSalaryIncreasedAndSayQuote( pSoldier, TRUE );
868 LockMapScreenInterface(false);
869 }
870
871
872 static BOOLEAN ContractIsGoingToExpireSoon(SOLDIERTYPE* pSoldier);
873
874
FindOutIfAnyMercAboutToLeaveIsGonnaRenew(void)875 void FindOutIfAnyMercAboutToLeaveIsGonnaRenew(void)
876 {
877 /* Run through list of grunts whoose contract are up in the next 2 hours
878 * ATE: AND - build list THEN choose one!
879 * Make a list of mercs that will want to stay if offered. During that
880 * process, also check if there is any merc that does not want to stay and
881 * only display that quote if they are the only one here */
882 SOLDIERTYPE* soldier_who_will_quit = 0;
883 UINT8 n_mercs = 0;
884 SOLDIERTYPE* potential_mercs[20];
885 FOR_EACH_IN_TEAM(s, OUR_TEAM)
886 {
887 if (s->bLife == 0) continue;
888 if (s->bAssignment == IN_TRANSIT) continue;
889 if (s->bAssignment == ASSIGNMENT_POW) continue;
890 if (s->ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC) continue;
891
892 // If the user hasn't renewed yet, and is still leaving today
893 if (!ContractIsGoingToExpireSoon(s)) continue;
894
895 // default value for quote said
896 s->ubContractRenewalQuoteCode = SOLDIER_CONTRACT_RENEW_QUOTE_NOT_USED;
897
898 // Add this guy to the renewal list
899 ContractRenewalList[ubNumContractRenewals++].ubProfileID = s->ubProfile;
900
901 if (WillMercRenew(s, FALSE))
902 {
903 potential_mercs[n_mercs++] = s;
904 }
905 else
906 {
907 soldier_who_will_quit = s;
908 }
909
910 AddSoldierToWaitingListQueue(*s);
911 }
912
913 if (n_mercs != 0)
914 {
915 SOLDIERTYPE* const chosen = potential_mercs[Random(n_mercs)];
916 HandleImportantMercQuoteLocked(chosen, QUOTE_CONTRACTS_OVER);
917 AddReasonToWaitingListQueue(CONTRACT_EXPIRE_WARNING_REASON);
918 AddDisplayBoxToWaitingQueue();
919 chosen->ubContractRenewalQuoteCode = SOLDIER_CONTRACT_RENEW_QUOTE_89_USED;
920 }
921 else if (soldier_who_will_quit) // Check if we should display line for the guy who does not want to stay
922 {
923 HandleImportantMercQuote(soldier_who_will_quit, QUOTE_MERC_LEAVING_ALSUCO_SOON);
924 AddReasonToWaitingListQueue(CONTRACT_EXPIRE_WARNING_REASON);
925 AddDisplayBoxToWaitingQueue();
926 soldier_who_will_quit->ubContractRenewalQuoteCode = SOLDIER_CONTRACT_RENEW_QUOTE_115_USED;
927 }
928 }
929
930
HandleNotifyPlayerCantAffordInsurance(void)931 static void HandleNotifyPlayerCantAffordInsurance(void)
932 {
933 DoScreenIndependantMessageBox( zMarksMapScreenText[ 9 ], MSG_BOX_FLAG_OK, NULL );
934 }
935
936
937 static void ExtendMercInsuranceContractCallBack(MessageBoxReturnValue);
938
939
HandleNotifyPlayerCanAffordInsurance(SOLDIERTYPE * pSoldier,UINT8 ubLength,INT32 iCost)940 static void HandleNotifyPlayerCanAffordInsurance(SOLDIERTYPE* pSoldier, UINT8 ubLength, INT32 iCost)
941 {
942 ST::string sString;
943 ST::string sStringA;
944
945 sStringA = SPrintMoney(iCost);
946
947 sString = st_format_printf(zMarksMapScreenText[ 10 ], pSoldier->name, sStringA, ubLength);
948
949 //Set the length to the global variable ( so we know how long the contract is in the callback )
950 gubContractLength = ubLength;
951 gpInsuranceSoldier = pSoldier;
952
953 //Remember the soldier aswell
954 pContractReHireSoldier = pSoldier;
955
956 // now pop up the message box
957 DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_YESNO, ExtendMercInsuranceContractCallBack );
958 }
959
960
ExtendMercInsuranceContractCallBack(MessageBoxReturnValue const bExitValue)961 static void ExtendMercInsuranceContractCallBack(MessageBoxReturnValue const bExitValue)
962 {
963 if( bExitValue == MSG_BOX_RETURN_YES )
964 {
965 PurchaseOrExtendInsuranceForSoldier( gpInsuranceSoldier, gubContractLength );
966 }
967
968 // OK, handle ending of renew session
969 if ( gfInContractMenuFromRenewSequence )
970 {
971 EndCurrentContractRenewal( );
972 }
973
974 gpInsuranceSoldier = NULL;
975 }
976
977
HandleUniqueEventWhenPlayerLeavesTeam(SOLDIERTYPE * pSoldier)978 static void HandleUniqueEventWhenPlayerLeavesTeam(SOLDIERTYPE* pSoldier)
979 {
980 switch( pSoldier->ubProfile )
981 {
982 //When iggy leaves the players team,
983 case IGGY:
984 //if he is owed money ( ie the player didnt pay him )
985 if( gMercProfiles[ pSoldier->ubProfile ].iBalance < 0 )
986 {
987 //iggy is now available to be handled by the enemy
988 gubFact[ FACT_IGGY_AVAILABLE_TO_ARMY ] = TRUE;
989 }
990 break;
991 }
992 }
993
994
GetHourWhenContractDone(SOLDIERTYPE * pSoldier)995 UINT32 GetHourWhenContractDone( SOLDIERTYPE *pSoldier )
996 {
997 UINT32 uiArriveHour;
998
999 // Get the arrival hour - that will give us when they arrived....
1000 uiArriveHour = ( ( pSoldier->uiTimeSoldierWillArrive ) - ( ( ( pSoldier->uiTimeSoldierWillArrive ) / 1440 ) * 1440 ) ) / 60;
1001
1002 return( uiArriveHour );
1003 }
1004
1005
ContractIsExpiring(SOLDIERTYPE * pSoldier)1006 static BOOLEAN ContractIsExpiring(SOLDIERTYPE* pSoldier)
1007 {
1008 UINT32 uiCheckHour;
1009
1010 // First at least make sure same day....
1011 if( ( pSoldier->iEndofContractTime /1440 ) <= (INT32)GetWorldDay( ) )
1012 {
1013 uiCheckHour = GetHourWhenContractDone( pSoldier );
1014
1015 // See if the hour we are on is the same....
1016 if ( GetWorldHour( ) == uiCheckHour )
1017 {
1018 // All's good for go!
1019 return( TRUE );
1020 }
1021 }
1022
1023 return( FALSE );
1024 }
1025
1026
ContractIsGoingToExpireSoon(SOLDIERTYPE * pSoldier)1027 static BOOLEAN ContractIsGoingToExpireSoon(SOLDIERTYPE* pSoldier)
1028 {
1029 // get hour contract is going to expire....
1030 UINT32 uiCheckHour;
1031
1032 // First at least make sure same day....
1033 if( ( pSoldier->iEndofContractTime /1440 ) <= (INT32)GetWorldDay( ) )
1034 {
1035 uiCheckHour = GetHourWhenContractDone( pSoldier );
1036
1037 // If we are <= 2 hours from expiry.
1038 if ( GetWorldHour( ) >= ( uiCheckHour - 2 ) )
1039 {
1040 // All's good for go!
1041 return( TRUE );
1042 }
1043 }
1044
1045 return( FALSE );
1046
1047 }
1048
1049
1050 #ifdef WITH_UNITTESTS
1051 #include "gtest/gtest.h"
1052
TEST(MercContract,asserts)1053 TEST(MercContract, asserts)
1054 {
1055 EXPECT_EQ(sizeof(CONTRACT_NEWAL_LIST_NODE), 4u);
1056 }
1057
1058 #endif
1059