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