1 #include "Font_Control.h"
2 #include "Debug.h"
3 #include "math.h"
4 #include "MapScreen.h"
5 #include "MessageBoxScreen.h"
6 #include "WorldDef.h"
7 #include "Assignments.h"
8 #include "Soldier_Control.h"
9 #include "Animation_Data.h"
10 #include "Render_Fun.h"
11 #include "Render_Dirty.h"
12 #include "MouseSystem.h"
13 #include "SysUtil.h"
14 #include "Points.h"
15 #include "Random.h"
16 #include "AI.h"
17 #include "Soldier_Ani.h"
18 #include "Overhead.h"
19 #include "Soldier_Profile.h"
20 #include "Game_Clock.h"
21 #include "Soldier_Create.h"
22 #include "Merc_Hiring.h"
23 #include "Game_Event_Hook.h"
24 #include "Message.h"
25 #include "StrategicMap.h"
26 #include "Strategic.h"
27 #include "Items.h"
28 #include "Soldier_Add.h"
29 #include "History.h"
30 #include "Squads.h"
31 #include "Strategic_Merc_Handler.h"
32 #include "Dialogue_Control.h"
33 #include "Map_Screen_Interface.h"
34 #include "Map_Screen_Interface_Map.h"
35 #include "ScreenIDs.h"
36 #include "JAScreens.h"
37 #include "Text.h"
38 #include "Merc_Contract.h"
39 #include "LaptopSave.h"
40 #include "Personnel.h"
41 #include "Auto_Resolve.h"
42 #include "Map_Screen_Interface_Bottom.h"
43 #include "Quests.h"
44 #include "Logger.h"
45 
46 #include <string_theory/string>
47 
48 
49 #define MIN_FLIGHT_PREP_TIME 6
50 
51 extern BOOLEAN gfTacticalDoHeliRun;
52 extern BOOLEAN gfFirstHeliRun;
53 
54 // ATE: Globals that dictate where the mercs will land once being hired
55 // Default to start sector
56 // Saved in general saved game structure
57 INT16 g_merc_arrive_sector = START_SECTOR;
58 
CreateSpecialItem(SOLDIERTYPE * const s,UINT16 item)59 void CreateSpecialItem(SOLDIERTYPE* const s, UINT16 item)
60 {
61 	OBJECTTYPE o;
62 	CreateItem(item, 100, &o);
63 	BOOLEAN fReturn = AutoPlaceObject(s, &o, FALSE);
64 	if (!fReturn) {
65 		// no space, so overwrite an existing item (can happen when importing IMPs)
66 		s->inv[SMALLPOCK8POS] = o;
67 	}
68 }
69 
HireMerc(MERC_HIRE_STRUCT & h)70 INT8 HireMerc(MERC_HIRE_STRUCT& h)
71 {
72 	ProfileID  const   pid = h.ubProfileID;
73 	MERCPROFILESTRUCT& p   = GetProfile(pid);
74 
75 	// If we are to disregard the status of the merc
76 	switch (p.bMercStatus)
77 	{
78 		case 0:
79 		case MERC_ANNOYED_BUT_CAN_STILL_CONTACT:
80 		case MERC_HIRED_BUT_NOT_ARRIVED_YET:
81 			break;
82 
83 		default:
84 			return MERC_HIRE_FAILED;
85 	}
86 
87 	if (NumberOfMercsOnPlayerTeam() >= 18) return MERC_HIRE_OVER_20_MERCS_HIRED;
88 
89 	// ATE: if we are to use landing zone, update to latest value
90 	// they will be updated again just before arrival...
91 	if (h.fUseLandingZoneForArrival)
92 	{
93 		h.sSectorX = SECTORX(g_merc_arrive_sector);
94 		h.sSectorY = SECTORY(g_merc_arrive_sector);
95 		h.bSectorZ = 0;
96 	}
97 
98 	SOLDIERCREATE_STRUCT MercCreateStruct;
99 	MercCreateStruct = SOLDIERCREATE_STRUCT{};
100 	MercCreateStruct.ubProfile             = pid;
101 	MercCreateStruct.sSectorX              = h.sSectorX;
102 	MercCreateStruct.sSectorY              = h.sSectorY;
103 	MercCreateStruct.bSectorZ              = h.bSectorZ;
104 	MercCreateStruct.bTeam                 = OUR_TEAM;
105 	MercCreateStruct.fCopyProfileItemsOver = h.fCopyProfileItemsOver;
106 	SOLDIERTYPE* const s = TacticalCreateSoldier(MercCreateStruct);
107 	if (s == NULL)
108 	{
109 		SLOGW("TacticalCreateSoldier in HireMerc():  Failed to Add Merc");
110 		return MERC_HIRE_FAILED;
111 	}
112 
113 	if (DidGameJustStart())
114 	{
115 		// OK, CHECK FOR FIRST GUY, GIVE HIM SPECIAL ITEM!
116 		if (s->ubID == 0)
117 		{
118 			// OK, give this item to our merc!
119 			CreateSpecialItem(s, LETTER);
120 		}
121 
122 		// Set insertion for first time in chopper
123 		h.ubInsertionCode = INSERTION_CODE_CHOPPER;
124 	}
125 
126 	// Record how long the merc will be gone for
127 	p.bMercStatus = (UINT8)h.iTotalContractLength;
128 
129 	// Copy over insertion data
130 	s->ubStrategicInsertionCode = h.ubInsertionCode;
131 	s->usStrategicInsertionData = h.usInsertionData;
132 	// ATE: Copy over value for using landing zone to soldier type
133 	s->fUseLandingZoneForArrival = h.fUseLandingZoneForArrival;
134 
135 	// Set assignment
136 	if (s->bAssignment != IN_TRANSIT) SetTimeOfAssignmentChangeForMerc(s);
137 	ChangeSoldiersAssignment(s, IN_TRANSIT);
138 
139 	// Set the contract length
140 	s->iTotalContractLength = h.iTotalContractLength;
141 
142 	// Reset the insurance values
143 	s->iStartOfInsuranceContract       = 0;
144 	s->iTotalLengthOfInsuranceContract = 0;
145 
146 	// Store arrival time in soldier structure so map screen can display it
147 	s->uiTimeSoldierWillArrive = h.uiTimeTillMercArrives;
148 
149 	if (DidGameJustStart())
150 	{
151 		// Set time of initial merc arrival in minutes
152 		h.uiTimeTillMercArrives = (STARTING_TIME + FIRST_ARRIVAL_DELAY) / NUM_SEC_IN_MIN;
153 
154 		//set when the merc's contract is finished
155 		s->iEndofContractTime = GetMidnightOfFutureDayInMinutes(s->iTotalContractLength) + GetHourWhenContractDone(s) * 60;
156 	}
157 	else
158 	{
159 		//set when the merc's contract is finished (+1 cause it takes a day for the merc to arrive)
160 		s->iEndofContractTime = GetMidnightOfFutureDayInMinutes(1 + s->iTotalContractLength) + GetHourWhenContractDone(s) * 60;
161 	}
162 
163 	// Set the time and ID of the last hired merc will arrive
164 	LaptopSaveInfo.sLastHiredMerc.iIdOfMerc     = pid;
165 	LaptopSaveInfo.sLastHiredMerc.uiArrivalTime = h.uiTimeTillMercArrives;
166 
167 	// If we are trying to hire a merc that should arrive later, put the merc in the queue
168 	if (h.uiTimeTillMercArrives != 0)
169 	{
170 		AddStrategicEvent(EVENT_DELAYED_HIRING_OF_MERC, h.uiTimeTillMercArrives, s->ubID);
171 		// Specify that the merc is hired but has not arrived yet
172 		p.bMercStatus = MERC_HIRED_BUT_NOT_ARRIVED_YET;
173 	}
174 
175 	// Set the type of merc
176 	if (h.bWhatKindOfMerc == MERC_TYPE__AIM_MERC)
177 	{
178 		s->ubWhatKindOfMercAmI = MERC_TYPE__AIM_MERC;
179 
180 		// Determine how much the contract is, and remember what type of contract he got
181 		switch (h.iTotalContractLength)
182 		{
183 			case 1:
184 				s->bTypeOfLastContract   = CONTRACT_EXTEND_1_DAY;
185 				s->iTimeCanSignElsewhere = GetWorldTotalMin();
186 				break;
187 
188 			case 7:
189 				s->bTypeOfLastContract   = CONTRACT_EXTEND_1_WEEK;
190 				s->iTimeCanSignElsewhere = GetWorldTotalMin();
191 				break;
192 
193 			case 14:
194 				s->bTypeOfLastContract   = CONTRACT_EXTEND_2_WEEK;
195 				// This fellow needs to stay the whole duration!
196 				s->iTimeCanSignElsewhere = s->iEndofContractTime;
197 				break;
198 		}
199 
200 		// remember the medical deposit we PAID.  The one in his profile can increase when he levels!
201 		s->usMedicalDeposit = p.sMedicalDepositAmount;
202 	}
203 	else if (h.bWhatKindOfMerc == MERC_TYPE__MERC)
204 	{
205 		s->ubWhatKindOfMercAmI = MERC_TYPE__MERC;
206 
207 		p.iMercMercContractLength = 1;
208 
209 		// Set starting conditions for the merc
210 		s->iStartContractTime = GetWorldDay();
211 
212 		AddHistoryToPlayersLog(HISTORY_HIRED_MERC_FROM_MERC, pid, GetWorldTotalMin(), -1, -1);
213 	}
214 	else if (h.bWhatKindOfMerc == MERC_TYPE__PLAYER_CHARACTER)
215 	{
216 		s->ubWhatKindOfMercAmI = MERC_TYPE__PLAYER_CHARACTER;
217 	}
218 	else
219 	{
220 		s->ubWhatKindOfMercAmI = MERC_TYPE__NPC;
221 	}
222 
223 	// remove the merc from the Personnel screens departed list (if they have never been hired before, its ok to call it)
224 	RemoveNewlyHiredMercFromPersonnelDepartedList(s->ubProfile);
225 
226 	gfAtLeastOneMercWasHired = TRUE;
227 	return MERC_HIRE_OK;
228 }
229 
230 
231 static void CheckForValidArrivalSector(void);
232 
233 
MercArrivesCallback(SOLDIERTYPE & s)234 void MercArrivesCallback(SOLDIERTYPE& s)
235 {
236 	UINT32 uiTimeOfPost;
237 
238 	if (!DidGameJustStart() && g_merc_arrive_sector == START_SECTOR)
239 	{
240 		// Mercs arriving in start sector. This sector has been deemed as the always
241 		// safe sector. Seeing we don't support entry into a hostile sector (except
242 		// for the beginning), we will nuke any enemies in this sector first.
243 		if (gWorldSectorX != SECTORX(START_SECTOR) ||
244 			gWorldSectorY != SECTORY(START_SECTOR) ||
245 			gbWorldSectorZ != 0)
246 		{
247 			EliminateAllEnemies(SECTORX(g_merc_arrive_sector), SECTORY(g_merc_arrive_sector));
248 		}
249 	}
250 
251 	// This will update ANY soldiers currently schedules to arrive too
252 	CheckForValidArrivalSector( );
253 
254 	// stop time compression until player restarts it
255 	StopTimeCompression();
256 
257 	MERCPROFILESTRUCT& p = GetProfile(s.ubProfile);
258 
259 	// add the guy to a squad
260 	AddCharacterToAnySquad(&s);
261 
262 	// ATE: Make sure we use global.....
263 	if (s.fUseLandingZoneForArrival)
264 	{
265 		s.sSectorX = SECTORX(g_merc_arrive_sector);
266 		s.sSectorY = SECTORY(g_merc_arrive_sector);
267 		s.bSectorZ = 0;
268 	}
269 
270 	// Add merc to sector ( if it's the current one )
271 	if (gWorldSectorX == s.sSectorX && gWorldSectorY == s.sSectorY && s.bSectorZ == gbWorldSectorZ)
272 	{
273 		// OK, If this sector is currently loaded, and guy does not have CHOPPER insertion code....
274 		// ( which means we are at beginning of game if so )
275 		// Setup chopper....
276 		if (s.ubStrategicInsertionCode != INSERTION_CODE_CHOPPER &&
277 				SECTOR(s.sSectorX, s.sSectorY) == START_SECTOR)
278 		{
279 			gfTacticalDoHeliRun = TRUE;
280 
281 			// OK, If we are in mapscreen, get out...
282 			if ( guiCurrentScreen == MAP_SCREEN )
283 			{
284 				// ATE: Make sure the current one is selected!
285 				ChangeSelectedMapSector( gWorldSectorX, gWorldSectorY, 0 );
286 
287 				RequestTriggerExitFromMapscreen( MAP_EXIT_TO_TACTICAL );
288 			}
289 
290 			s.ubStrategicInsertionCode = INSERTION_CODE_CHOPPER;
291 		}
292 
293 		UpdateMercInSector(s, s.sSectorX, s.sSectorY, s.bSectorZ);
294 	}
295 	else
296 	{
297 		// OK, otherwise, set them in north area, so once we load again, they are here.
298 		s.ubStrategicInsertionCode = INSERTION_CODE_NORTH;
299 	}
300 
301 
302 	if (s.ubStrategicInsertionCode != INSERTION_CODE_CHOPPER)
303 	{
304 		ScreenMsg(FONT_MCOLOR_WHITE, MSG_INTERFACE, st_format_printf(TacticalStr[MERC_HAS_ARRIVED_STR], s.name));
305 
306 		// ATE: He's going to say something, now that they've arrived...
307 		if (!gTacticalStatus.bMercArrivingQuoteBeingUsed && !gfFirstHeliRun)
308 		{
309 			gTacticalStatus.bMercArrivingQuoteBeingUsed = TRUE;
310 
311 			TacticalCharacterDialogue(&s, QUOTE_MERC_REACHED_DESTINATION);
312 
313 			class DialogueEventUnsetArrivesFlag : public DialogueEvent
314 			{
315 				public:
316 					bool Execute()
317 					{
318 						gTacticalStatus.bMercArrivingQuoteBeingUsed = FALSE;
319 						return false;
320 					}
321 			};
322 
323 			DialogueEvent::Add(new DialogueEventUnsetArrivesFlag());
324 		}
325 	}
326 
327 	//record how long the merc will be gone for
328 	p.bMercStatus = (UINT8)s.iTotalContractLength;
329 
330 	// remember when excatly he ARRIVED in Arulco, in case he gets fired early
331 	s.uiTimeOfLastContractUpdate = GetWorldTotalMin();
332 
333 	//set when the merc's contract is finished
334 	s.iEndofContractTime = GetMidnightOfFutureDayInMinutes(s.iTotalContractLength) + GetHourWhenContractDone(&s) * 60;
335 
336 	// Do initial check for bad items
337 	if (s.bTeam == OUR_TEAM)
338 	{
339 		//ATE: Try to see if our equipment sucks!
340 		if (SoldierHasWorseEquipmentThanUsedTo(&s))
341 		{
342 			// Randomly anytime between 9:00, and 10:00
343 			uiTimeOfPost =  540 + Random( 660 );
344 
345 			if ( GetWorldMinutesInDay() < uiTimeOfPost )
346 			{
347 				AddSameDayStrategicEvent(EVENT_MERC_COMPLAIN_EQUIPMENT, uiTimeOfPost, s.ubProfile);
348 			}
349 		}
350 	}
351 
352 	HandleMercArrivesQuotes(s);
353 
354 	fTeamPanelDirty = TRUE;
355 
356 	// if the currently selected sector has no one in it, select this one instead
357 	if ( !CanGoToTacticalInSector( sSelMapX, sSelMapY, ( UINT8 )iCurrentMapSectorZ ) )
358 	{
359 		ChangeSelectedMapSector(s.sSectorX, s.sSectorY, 0);
360 	}
361 }
362 
363 
IsMercHireable(MERCPROFILESTRUCT const & p)364 bool IsMercHireable(MERCPROFILESTRUCT const& p)
365 {
366 	// If the merc has an .edt file, is not away on assignment, and isn't already
367 	// hired (but not arrived yet), he is not dead and he isn't returning home
368 	return
369 		p.bMercStatus <= 0                              &&
370 		p.bMercStatus != MERC_HAS_NO_TEXT_FILE          &&
371 		p.bMercStatus != MERC_HIRED_BUT_NOT_ARRIVED_YET &&
372 		p.bMercStatus != MERC_IS_DEAD                   &&
373 		p.bMercStatus != MERC_RETURNING_HOME            &&
374 		p.bMercStatus != MERC_WORKING_ELSEWHERE         &&
375 		p.bMercStatus != MERC_FIRED_AS_A_POW            &&
376 		p.uiDayBecomesAvailable == 0;
377 }
378 
379 
IsMercDead(MERCPROFILESTRUCT const & p)380 bool IsMercDead(MERCPROFILESTRUCT const& p)
381 {
382 	return p.bMercStatus == MERC_IS_DEAD;
383 }
384 
385 
HandleMercArrivesQuotes(SOLDIERTYPE & s)386 void HandleMercArrivesQuotes(SOLDIERTYPE& s)
387 {
388 	// If we are approaching with helicopter, don't say any ( yet )
389 	if (s.ubStrategicInsertionCode == INSERTION_CODE_CHOPPER) return;
390 
391 	// Player-generated characters issue a comment about arriving in Omerta.
392 	if (s.ubWhatKindOfMercAmI == MERC_TYPE__PLAYER_CHARACTER &&
393 		gubQuest[QUEST_DELIVER_LETTER] == QUESTINPROGRESS)
394 	{
395 		TacticalCharacterDialogue(&s, QUOTE_PC_DROPPED_OMERTA);
396 	}
397 
398 	// Check to see if anyone hates this merc and will now complain
399 	FOR_EACH_IN_TEAM(other, OUR_TEAM)
400 	{
401 		if (other->ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC) continue;
402 
403 		// hates the merc who has arrived and is going to gripe about it!
404 		switch (WhichHated(other->ubProfile, s.ubProfile))
405 		{
406 			case 0:  TacticalCharacterDialogue(other, QUOTE_HATED_1_ARRIVES); break;
407 			case 1:  TacticalCharacterDialogue(other, QUOTE_HATED_2_ARRIVES); break;
408 			default: break;
409 		}
410 	}
411 }
412 
GetMercArrivalTimeOfDay()413 UINT32 GetMercArrivalTimeOfDay( )
414 {
415 	UINT32 uiCurrHour;
416 	UINT32 uiMinHour;
417 
418 	// Pick a time...
419 
420 	// First get the current time of day.....
421 	uiCurrHour = GetWorldHour( );
422 
423 	// Subtract the min time for any arrival....
424 	uiMinHour = uiCurrHour + MIN_FLIGHT_PREP_TIME;
425 
426 	// OK, first check if we need to advance a whole day's time...
427 	// See if we have missed the last flight for the day...
428 	if ( ( uiCurrHour ) > 13  ) // ( > 1:00 pm - too bad )
429 	{
430 		// 7:30 flight....
431 		return( GetMidnightOfFutureDayInMinutes( 1 ) + MERC_ARRIVE_TIME_SLOT_1 );
432 	}
433 
434 	// Well, now we can handle flights all in one day....
435 	// Find next possible flight
436 	if ( uiMinHour <= 7 )
437 	{
438 		return( GetWorldDayInMinutes() + MERC_ARRIVE_TIME_SLOT_1 ); // 7:30 am
439 	}
440 	else if ( uiMinHour <= 13 )
441 	{
442 		return( GetWorldDayInMinutes() + MERC_ARRIVE_TIME_SLOT_2 ); // 1:30 pm
443 	}
444 	else
445 	{
446 		return( GetWorldDayInMinutes() + MERC_ARRIVE_TIME_SLOT_3 ); // 7:30 pm
447 	}
448 }
449 
450 
UpdateAnyInTransitMercsWithGlobalArrivalSector()451 void UpdateAnyInTransitMercsWithGlobalArrivalSector( )
452 {
453 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
454 	{
455 		if (s->bAssignment == IN_TRANSIT && s->fUseLandingZoneForArrival)
456 		{
457 			s->sSectorX = SECTORX(g_merc_arrive_sector);
458 			s->sSectorY = SECTORY(g_merc_arrive_sector);
459 			s->bSectorZ = 0;
460 		}
461 	}
462 }
463 
464 
StrategicPythSpacesAway(INT16 sOrigin,INT16 sDest)465 static INT16 StrategicPythSpacesAway(INT16 sOrigin, INT16 sDest)
466 {
467 	INT16 sRows,sCols,sResult;
468 
469 	sRows = ABS((sOrigin / MAP_WORLD_X) - (sDest / MAP_WORLD_X));
470 	sCols = ABS((sOrigin % MAP_WORLD_X) - (sDest % MAP_WORLD_X));
471 
472 
473 	// apply Pythagoras's theorem for right-handed triangle:
474 	// dist^2 = rows^2 + cols^2, so use the square root to get the distance
475 	sResult = (INT16)sqrt((double)((sRows * sRows) + (sCols * sCols)));
476 
477 	return(sResult);
478 }
479 
480 
481 // ATE: This function will check if the current arrival sector
482 // is valid
483 // if there are enemies present, it's invalid
484 // if so, search around for nearest non-occupied sector.
CheckForValidArrivalSector(void)485 static void CheckForValidArrivalSector(void)
486 {
487 	INT16   sTop, sBottom;
488 	INT16   sLeft, sRight;
489 	INT16   cnt1, cnt2;
490 	UINT8   ubRadius = 4;
491 	INT32   leftmost;
492 	INT16   sSectorGridNo, sSectorGridNo2;
493 	INT32   uiRange, uiLowestRange = 999999;
494 	BOOLEAN fFound = FALSE;
495 	ST::string sString;
496 	ST::string zShortTownIDString1;
497 	ST::string zShortTownIDString2;
498 
499 	sSectorGridNo = SECTOR_INFO_TO_STRATEGIC_INDEX(g_merc_arrive_sector);
500 
501 	// Check if valid...
502 	if ( !StrategicMap[ sSectorGridNo ].fEnemyControlled )
503 	{
504 		return;
505 	}
506 
507 	zShortTownIDString1 = GetShortSectorString(SECTORX(g_merc_arrive_sector), SECTORY(g_merc_arrive_sector));
508 
509 
510 	// If here - we need to do a search!
511 	sTop    = ubRadius;
512 	sBottom = -ubRadius;
513 	sLeft   = - ubRadius;
514 	sRight  = ubRadius;
515 
516 	INT16 sGoodX = 0; // XXX HACK000E
517 	INT16 sGoodY = 0; // XXX HACK000E
518 	for( cnt1 = sBottom; cnt1 <= sTop; cnt1++ )
519 	{
520 		leftmost = ( ( sSectorGridNo + ( MAP_WORLD_X * cnt1 ) )/ MAP_WORLD_X ) * MAP_WORLD_X;
521 
522 		for( cnt2 = sLeft; cnt2 <= sRight; cnt2++ )
523 		{
524 			sSectorGridNo2 = sSectorGridNo + ( MAP_WORLD_X * cnt1 ) + cnt2;
525 
526 			if( sSectorGridNo2 >=1 && sSectorGridNo2 < ( ( MAP_WORLD_X - 1 ) * ( MAP_WORLD_X - 1 ) ) && sSectorGridNo2 >= leftmost && sSectorGridNo2 < ( leftmost + MAP_WORLD_X ) )
527 			{
528 				if ( !StrategicMap[ sSectorGridNo2 ].fEnemyControlled && !StrategicMap[ sSectorGridNo2 ].fEnemyAirControlled )
529 				{
530 					uiRange = StrategicPythSpacesAway( sSectorGridNo2, sSectorGridNo );
531 
532 					if ( uiRange < uiLowestRange )
533 					{
534 						sGoodY = cnt1;
535 						sGoodX = cnt2;
536 						uiLowestRange = uiRange;
537 						fFound = TRUE;
538 					}
539 				}
540 			}
541 		}
542 	}
543 
544 	if ( fFound )
545 	{
546 		g_merc_arrive_sector = SECTOR(SECTORX(g_merc_arrive_sector) + sGoodX, SECTORY(g_merc_arrive_sector) + sGoodY);
547 
548 		UpdateAnyInTransitMercsWithGlobalArrivalSector( );
549 
550 		zShortTownIDString2 = GetShortSectorString(SECTORX(g_merc_arrive_sector), SECTORY(g_merc_arrive_sector));
551 
552 		sString = st_format_printf(str_arrival_rerouted, zShortTownIDString2, zShortTownIDString1);
553 
554 		DoScreenIndependantMessageBox(  sString, MSG_BOX_FLAG_OK, NULL );
555 
556 	}
557 }
558