1 #include "Map_Screen_Helicopter.h"
2 
3 #include "Assignments.h"
4 #include "ContentManager.h"
5 #include "Debug.h"
6 #include "Dialogue_Control.h"
7 #include "Directories.h"
8 #include "Finances.h"
9 #include "Font_Control.h"
10 #include "GameInstance.h"
11 #include "Game_Clock.h"
12 #include "Game_Event_Hook.h"
13 #include "Isometric_Utils.h"
14 #include "LaptopSave.h"
15 #include "MapScreen.h"
16 #include "Map_Screen_Interface.h"
17 #include "Map_Screen_Interface_Border.h"
18 #include "Meanwhile.h"
19 #include "Message.h"
20 #include "MessageBoxScreen.h"
21 #include "Overhead.h"
22 #include "Player_Command.h"
23 #include "PreBattle_Interface.h"
24 #include "Queen_Command.h"
25 #include "Quests.h"
26 #include "Random.h"
27 #include "RenderWorld.h"
28 #include "SamSiteModel.h"
29 #include "Scheduling.h"
30 #include "Soldier_Create.h"
31 #include "Soldier_Profile.h"
32 #include "SoundMan.h"
33 #include "Sound_Control.h"
34 #include "Squads.h"
35 #include "StrategicMap.h"
36 #include "StrategicMap_Secrets.h"
37 #include "Strategic_Event_Handler.h"
38 #include "Strategic_Movement.h"
39 #include "Strategic_Pathing.h"
40 #include "Text.h"
41 #include "TileDat.h"
42 #include "UILayout.h"
43 #include "Vehicles.h"
44 #include "WorldDef.h"
45 #include "WorldMan.h"
46 
47 // the amounts of time to wait for hover stuff
48 #define TIME_DELAY_FOR_HOVER_WAIT						10		// minutes
49 #define TIME_DELAY_FOR_HOVER_WAIT_TOO_LONG	20		// mintues
50 #define MIN_DAYS_BETWEEN_SKYRIDER_MONOLOGUES 1
51 
52 // maximum chance out of a hundred per unsafe sector that a SAM site in decent working condition will hit Skyrider
53 #define MAX_SAM_SITE_ACCURACY		33
54 
55 // current temp path for dest char
56 extern PathSt* pTempHelicopterPath;
57 
58 // whether helicopted variables have been set up
59 BOOLEAN fSkyRiderSetUp = FALSE;
60 
61 // plotting for a helicopter
62 BOOLEAN fPlotForHelicopter = FALSE;
63 
64 // helicopter vehicle id
65 INT32 iHelicopterVehicleId = -1;
66 
67 // total owed to player
68 INT32 iTotalAccumulatedCostByPlayer = 0;
69 
70 // helicopter destroyed
71 BOOLEAN fHelicopterDestroyed = FALSE;
72 
73 struct RefuelSite
74 {
75 	INT16  sector;
76 	GridNo grid_no;
77 	INT16  heli_ostruct;
78 };
79 
80 // list of sector locations where SkyRider can be refueled
81 static RefuelSite const g_refuel_site[] =
82 {
83 	{ CALCULATE_STRATEGIC_INDEX(13, 2),  9001, FIRSTOSTRUCT1  }, // Drassen airport
84 	{ CALCULATE_STRATEGIC_INDEX(6,  9), 13067, FOURTHOSTRUCT1 }  // Estoni
85 };
86 
87 enum SkyriderMonologueEvent
88 {
89 	SKYRIDER_MONOLOGUE_EVENT_DRASSEN_SAM_SITE = 0,
90 	SKYRIDER_MONOLOGUE_EVENT_OTHER_SAM_SITES,
91 	SKYRIDER_MONOLOGUE_EVENT_ESTONI_REFUEL,
92 	SKYRIDER_MONOLOGUE_EVENT_CAMBRIA_HOSPITAL
93 };
94 
95 // whether or not helicopter can refuel at this site
96 BOOLEAN fRefuelingSiteAvailable[ NUMBER_OF_REFUEL_SITES ] = { FALSE, FALSE };
97 
98 // is the heli in the air?
99 BOOLEAN fHelicopterIsAirBorne = FALSE;
100 
101 // is the pilot returning straight to base?
102 BOOLEAN fHeliReturnStraightToBase = FALSE;
103 
104 // heli hovering
105 BOOLEAN fHoveringHelicopter = FALSE;
106 
107 // time started hovering
108 UINT32 uiStartHoverTime = 0;
109 
110 // what state are skyrider's monologues in in?
111 UINT32 guiHelicopterSkyriderTalkState = 0;
112 
113 // the flags for skyrider events
114 BOOLEAN fShowEstoniRefuelHighLight = FALSE;
115 BOOLEAN fShowOtherSAMHighLight = FALSE;
116 BOOLEAN fShowDrassenSAMHighLight = FALSE;
117 BOOLEAN fShowCambriaHospitalHighLight = FALSE;
118 UINT32 guiTimeOfLastSkyriderMonologue = 0;
119 UINT8 gubHelicopterHitsTaken = 0;
120 BOOLEAN gfSkyriderSaidCongratsOnTakingSAM = FALSE;
121 UINT8 gubPlayerProgressSkyriderLastCommentedOn = 0;
122 
123 static BOOLEAN DoesSkyriderNoticeEnemiesInSector(UINT8 ubNumEnemies);
124 static BOOLEAN HandleSAMSiteAttackOfHelicopterInSector(INT16 sSectorX, INT16 sSectorY);
125 static void HeliCharacterDialogue(UINT16 usQuoteNum);
126 static void PaySkyriderBill(void);
127 static void StartHoverTime(void);
128 static RefuelSite const* FindClosestRefuelSite(bool must_be_available);
129 static void LandHelicopter(void);
130 static void MakeHeliReturnToBase(void);
131 static void HandleSkyRiderMonologueEvent(SkyriderMonologueEvent, UINT32 uiSpecialCode);
132 static void HandleSkyRiderMonologueAboutCambriaHospital(UINT32 uiSpecialCode);
133 static void HandleSkyRiderMonologueAboutDrassenSAMSite(UINT32 uiSpecialCode);
134 static void HandleSkyRiderMonologueAboutEstoniRefuel(UINT32 uiSpecialCode);
135 static void HandleSkyRiderMonologueAboutOtherSAMSites(UINT32 uiSpecialCode);
136 static void AddHelicopterToMaps(bool add, RefuelSite const&);
137 static bool IsHelicopterOnGroundAtRefuelingSite(RefuelSite const&);
138 
InitializeHelicopter(void)139 void InitializeHelicopter( void )
140 {
141 	// must be called whenever a new game starts up!
142 	iHelicopterVehicleId = -1;
143 
144 	fSkyRiderSetUp = FALSE;
145 
146 	fHelicopterIsAirBorne = FALSE;
147 	fHeliReturnStraightToBase = FALSE;
148 
149 	fHoveringHelicopter = FALSE;
150 	uiStartHoverTime = 0;
151 
152 	fPlotForHelicopter = FALSE;
153 	pTempHelicopterPath = NULL;
154 
155 	iTotalAccumulatedCostByPlayer = 0;
156 
157 	fHelicopterDestroyed = FALSE;
158 
159 	guiHelicopterSkyriderTalkState = 0;
160 	guiTimeOfLastSkyriderMonologue = 0;
161 
162 	fShowEstoniRefuelHighLight = FALSE;
163 	fShowOtherSAMHighLight = FALSE;
164 	fShowDrassenSAMHighLight = FALSE;
165 	fShowCambriaHospitalHighLight = FALSE;
166 
167 	gfSkyriderEmptyHelpGiven = FALSE;
168 
169 	gubHelicopterHitsTaken = 0;
170 
171 	gfSkyriderSaidCongratsOnTakingSAM = FALSE;
172 	gubPlayerProgressSkyriderLastCommentedOn = 0;
173 }
174 
RemoveSoldierFromHelicopter(SOLDIERTYPE * pSoldier)175 BOOLEAN RemoveSoldierFromHelicopter( SOLDIERTYPE *pSoldier )
176 {
177 	// attempt to add soldier to helicopter
178 	if( iHelicopterVehicleId == -1 )
179 	{
180 		// no heli yet
181 		return( FALSE );
182 	}
183 
184 	// check if heli is in motion or if on the ground
185 	if (fHelicopterIsAirBorne && !fHoveringHelicopter) return FALSE;
186 
187 	// is the heli returning to base?..he ain't waiting if so
188 	if (fHeliReturnStraightToBase) return FALSE;
189 
190 	VEHICLETYPE const& v = GetHelicopter();
191 	pSoldier->sSectorX = v.sSectorX;
192 	pSoldier->sSectorY = v.sSectorY;
193 	pSoldier->bSectorZ = 0;
194 
195 	// reset between sectors
196 	pSoldier->fBetweenSectors = FALSE;
197 
198 	// remove from the vehicle
199 	return( TakeSoldierOutOfVehicle( pSoldier ) );
200 }
201 
HandleHeliEnteringSector(INT16 sX,INT16 sY)202 BOOLEAN HandleHeliEnteringSector( INT16 sX, INT16 sY )
203 {
204 	UINT8 ubNumEnemies;
205 	BOOLEAN endOfHelicoptersPath;
206 
207 	VEHICLETYPE const& v = GetHelicopter();
208 	endOfHelicoptersPath = (!v.pMercPath || !v.pMercPath->pNext);
209 
210 	// check for SAM attack upon the chopper.  If it's destroyed by the attack, do nothing else here
211 	if (HandleSAMSiteAttackOfHelicopterInSector(sX, sY))
212 	{
213 		// destroyed
214 		return( TRUE );
215 	}
216 
217 	// count how many enemies are camped there or passing through
218 	ubNumEnemies = NumEnemiesInSector( sX, sY );
219 
220 	// any baddies?
221 	if( ubNumEnemies > 0 )
222 	{
223 		// if the player didn't know about these prior to the chopper's arrival
224 		if( WhatPlayerKnowsAboutEnemiesInSector( sX, sY ) == KNOWS_NOTHING )
225 		{
226 			// but Skyrider notices them
227 			if (DoesSkyriderNoticeEnemiesInSector(ubNumEnemies))
228 			{
229 				// if just passing through (different quotes are used below if it's his final destination)
230 				if( !endOfHelicoptersPath )
231 				{
232 					// stop time compression and inform player that there are enemies in the sector below
233 					StopTimeCompression();
234 
235 					if( Random( 2 ) )
236 					{
237 						HeliCharacterDialogue(ENEMIES_SPOTTED_EN_ROUTE_IN_FRIENDLY_SECTOR_A);
238 					}
239 					else
240 					{
241 						HeliCharacterDialogue(ENEMIES_SPOTTED_EN_ROUTE_IN_FRIENDLY_SECTOR_B);
242 					}
243 				}
244 				// make their presence appear on the map while Skyrider remains in the sector
245 				SectorInfo[ SECTOR( sX, sY ) ].uiFlags |= SF_SKYRIDER_NOTICED_ENEMIES_HERE;
246 			}
247 		}
248 	}
249 
250 	// player pays for travel if Skyrider is NOT returning to base (even if empty while scouting/going for pickup)
251 	if (!fHeliReturnStraightToBase)
252 	{
253 		// charge cost for flying another sector
254 		INT32 iCost;
255 		if( !StrategicMap[CALCULATE_STRATEGIC_INDEX(sX, sY)].fEnemyAirControlled)
256 			iCost = COST_AIRSPACE_SAFE;
257 		else
258 			iCost = COST_AIRSPACE_UNSAFE;
259 		iTotalAccumulatedCostByPlayer += iCost;
260 	}
261 
262 	// check if heli has any real path left
263 	if( endOfHelicoptersPath )
264 	{
265 		// start hovering
266 		StartHoverTime( );
267 
268 		// if sector is safe, or Skyrider MUST land anyway (returning to base)
269 		if ( ( ubNumEnemies == 0 ) || fHeliReturnStraightToBase )
270 		{
271 			// if he has passengers, or he's not going straight to base, tell player he's arrived
272 			// (i.e. don't say anything nor stop time compression if he's empty and just returning to base)
273 			if ( ( GetNumberInVehicle(v) > 0 ) || !fHeliReturnStraightToBase )
274 			{
275 				// arrived at destination
276 				HeliCharacterDialogue(ARRIVED_IN_NON_HOSTILE_SECTOR);
277 				StopTimeCompression();
278 			}
279 
280 			// destination reached, payment due.  If player can't pay, mercs get kicked off and heli flies to base!
281 			PaySkyriderBill();
282 		}
283 		else
284 		{
285 			// Say quote: "Gonna have to abort.  Enemies below"
286 			HeliCharacterDialogue(ARRIVED_IN_HOSTILE_SECTOR);
287 			StopTimeCompression();
288 		}
289 
290 		if (IsRefuelAvailableInSector(CALCULATE_STRATEGIC_INDEX(sX, sY)))
291 		{
292 			LandHelicopter();
293 		}
294 	}
295 	return( FALSE );
296 }
297 
NearestRefuelPoint(bool const fNotifyPlayerIfNoSafeLZ)298 static RefuelSite const& NearestRefuelPoint(bool const fNotifyPlayerIfNoSafeLZ)
299 {
300 	// Try to find one, any one under the players control
301 	RefuelSite const* closest_site = FindClosestRefuelSite(TRUE);
302 	if (closest_site) return *closest_site;
303 
304 	if (fNotifyPlayerIfNoSafeLZ)
305 	{ // No refueling sites available, might wanna warn player about this
306 		ScreenMsg(FONT_MCOLOR_DKRED, MSG_INTERFACE, pHelicopterEtaStrings[5]);
307 	}
308 
309 	// Find the closest location regardless
310 	closest_site = FindClosestRefuelSite(FALSE);
311 
312 	// Always returns a valid refuel point, picking a hostile one if unavoidable
313 	Assert(closest_site);
314 	return *closest_site;
315 }
316 
317 // find the location sector of closest refuel point for heli..and the criteria if the sector must be under the players control
FindClosestRefuelSite(bool const must_be_available)318 static RefuelSite const* FindClosestRefuelSite(bool const must_be_available)
319 {
320 	INT32             shortest_distance = 9999;
321 	RefuelSite const* closest_site      = 0;
322 
323 	VEHICLETYPE const& v        				= GetHelicopter();
324 	INT16 sectorID											= CALCULATE_STRATEGIC_INDEX(v.sSectorX , v.sSectorY);
325 	GROUP& g														= *GetGroup(v.ubMovementGroup);
326 	// find shortest distance to refuel site
327 	for (INT32 i = 0; i < NUMBER_OF_REFUEL_SITES; ++i)
328 	{
329 		// if this refuelling site is available
330 		if (!fRefuelingSiteAvailable[i] && must_be_available) continue;
331 
332 		// find if sector is under control, find distance from heli to it
333 		RefuelSite  const& r        = g_refuel_site[i];
334 		INT16       const  dest     = r.sector;
335 		INT32       const  distance = FindStratPath( sectorID, dest, g, FALSE);
336 		if (distance >= shortest_distance) continue;
337 
338 		// shorter, copy over
339 		shortest_distance = distance;
340 		closest_site      = &r;
341 	}
342 	return closest_site;
343 }
344 
345 // helicopter shot down, kill all on board
SkyriderDestroyed(void)346 static void SkyriderDestroyed(void)
347 {
348 	// remove any arrival events for the helicopter's group
349 	VEHICLETYPE& v = GetHelicopter();
350 	DeleteStrategicEvent(EVENT_GROUP_ARRIVAL, v.ubMovementGroup);
351 	KillAllInVehicle(v);
352 
353 	// kill skyrider
354 	gMercProfiles[ SKYRIDER ].bLife = 0;
355 
356 	// destroy helicopter
357 	fHelicopterDestroyed = TRUE;
358 
359 	// zero out balance due
360 	gMercProfiles[ SKYRIDER ].iBalance = 0;
361 	iTotalAccumulatedCostByPlayer = 0;
362 
363 	// remove vehicle and reset
364 	RemoveVehicleFromList(v);
365 	iHelicopterVehicleId = -1;
366 }
367 
CanHelicopterFly(void)368 BOOLEAN CanHelicopterFly( void )
369 {
370 	// check if heli is available for flight?
371 
372 	// is the heli available
373 	if (iHelicopterVehicleId == -1) return FALSE;
374 
375 	// is the pilot alive, well, and willing to help us?
376 	if (!IsHelicopterPilotAvailable()) return FALSE;
377 
378 	if (fHeliReturnStraightToBase) return FALSE;
379 
380 	// grounded by enemies in sector?
381 	if (!CanHelicopterTakeOff()) return FALSE;
382 
383 	// everything A-OK!
384 	return( TRUE );
385 }
386 
IsHelicopterPilotAvailable(void)387 BOOLEAN IsHelicopterPilotAvailable( void )
388 {
389 	// what is state of skyrider?
390 	if (iHelicopterVehicleId == -1) return FALSE;
391 
392 	// owe any money to skyrider?
393 	if( gMercProfiles[ SKYRIDER ].iBalance < 0 ) return( FALSE );
394 
395 	// last case: Drassen too disloyal to wanna help player? if not, return true
396 	return ( !CheckFact( FACT_LOYALTY_LOW, SKYRIDER ) );
397 }
398 
399 // land the helicopter here
LandHelicopter(void)400 static void LandHelicopter(void)
401 {
402 	// set the helictoper down, call arrive callback for this mvt group
403 	fHelicopterIsAirBorne = FALSE;
404 
405 	// no longer hovering
406 	fHoveringHelicopter = FALSE;
407 
408 	// reset fact that we might have returned straight here
409 	fHeliReturnStraightToBase = FALSE;
410 	HandleHelicopterOnGround(true);
411 
412 	// if we'll be unable to take off again (because there are enemies in the sector, or we owe pilot money)
413 	if (!CanHelicopterFly())
414 	{
415 		// kick everyone out!
416 		MoveAllInHelicopterToFootMovementGroup( );
417 	}
418 	else
419 	{
420 		// play meanwhile scene if it hasn't been used yet
421 		HandleKillChopperMeanwhileScene();
422 	}
423 }
424 
TakeOffHelicopter(void)425 void TakeOffHelicopter( void )
426 {
427 	// heli in the air
428 	fHelicopterIsAirBorne = TRUE;
429 
430 	// no longer hovering
431 	fHoveringHelicopter = FALSE;
432 	HandleHelicopterOnGround(true);
433 }
434 
435 // start the heli hover time
StartHoverTime(void)436 static void StartHoverTime(void)
437 {
438 	// start hover in this sector
439 	fHoveringHelicopter = TRUE;
440 
441 	// post event for x mins in future, save start time, if event time - delay = start time, then hover has gone on too long
442 	uiStartHoverTime = GetWorldTotalMin( );
443 
444 	// post event..to call handle hover
445 	AddStrategicEvent( EVENT_HELICOPTER_HOVER_TOO_LONG, GetWorldTotalMin() + TIME_DELAY_FOR_HOVER_WAIT, 0 );
446 }
447 
HandleHeliHoverLong(void)448 void HandleHeliHoverLong( void )
449 {
450 	// post message about hovering too long
451 	if( fHoveringHelicopter )
452 	{
453 		// proper event, post next one
454 		AddStrategicEvent( EVENT_HELICOPTER_HOVER_WAY_TOO_LONG, uiStartHoverTime + TIME_DELAY_FOR_HOVER_WAIT_TOO_LONG, 0 );
455 
456 		// inform player
457 		HeliCharacterDialogue(HOVERING_A_WHILE);
458 
459 		// stop time compression if it's on
460 		StopTimeCompression( );
461 	}
462 	else
463 	{
464 		// reset
465 		uiStartHoverTime = 0;
466 	}
467 }
468 
HandleHeliHoverTooLong(void)469 void HandleHeliHoverTooLong( void )
470 {
471 	// reset hover time
472 	uiStartHoverTime = 0;
473 	if (!fHoveringHelicopter) return;
474 
475 	// hovered too long, inform player heli is returning to base
476 	HeliCharacterDialogue(RETURN_TO_BASE);
477 	VEHICLETYPE const& v = GetHelicopter();
478 	// If the sector is safe
479 	if (NumEnemiesInSector(v.sSectorX, v.sSectorY) == 0)
480 	{
481 		// kick everyone out!
482 		MoveAllInHelicopterToFootMovementGroup( );
483 	}
484 	MakeHeliReturnToBase();
485 }
486 
487 // check if anyone in the chopper sees any baddies in sector
DoesSkyriderNoticeEnemiesInSector(UINT8 ubNumEnemies)488 static BOOLEAN DoesSkyriderNoticeEnemiesInSector(UINT8 ubNumEnemies)
489 {
490 	UINT8 ubChance;
491 
492 	// is the pilot and heli around?
493 	if (!CanHelicopterFly()) return FALSE;
494 
495 	// if there aren't any, he obviously won't see them
496 	if( ubNumEnemies == 0 )
497 	{
498 		return( FALSE );
499 	}
500 
501 	// figure out what the chance is of seeing them
502 	// make this relatively accurate most of the time, to encourage helicopter scouting by making it useful
503 	ubChance = 60 + ubNumEnemies;
504 	if( PreRandom( 100 ) < ubChance )
505 	{
506 		return( TRUE );
507 	}
508 	return( FALSE );
509 }
510 
511 // if the heli is on the move, what is the distance it will move..the length of the merc path, less the first node
DistanceOfIntendedHelicopterPath(void)512 INT32 DistanceOfIntendedHelicopterPath( void )
513 {
514 	INT32 iLength = 0;
515 	if (!CanHelicopterFly())
516 	{
517 		// big number, no go
518 		return( 9999 );
519 	}
520 
521 	PathSt const* pNode = GetHelicopter().pMercPath;
522 
523 	// any path yet?
524 	if( pNode != NULL )
525 	{
526 		while( pNode -> pNext )
527 		{
528 			iLength++;
529 			pNode = pNode ->pNext;
530 		}
531 	}
532 
533 	pNode = pTempHelicopterPath;
534 
535 	// any path yet?
536 	if( pNode != NULL )
537 	{
538 		while( pNode -> pNext )
539 		{
540 			iLength++;
541 			pNode = pNode ->pNext;
542 		}
543 	}
544 	return( iLength );
545 }
546 
SetUpHelicopterForMovement(void)547 void SetUpHelicopterForMovement( void )
548 {
549 	// check if helicopter vehicle has a mvt group, if not, assign one in this sector
550 	VEHICLETYPE& v = GetHelicopter();
551 
552 	// if no group, create one for vehicle
553 	if (v.ubMovementGroup == 0)
554 	{
555 		// get the vehicle a mvt group
556 		GROUP& g = *CreateNewVehicleGroupDepartingFromSector(v.sSectorX, v.sSectorY);
557 		v.ubMovementGroup = g.ubGroupID;
558 
559 		// add everyone in vehicle to this mvt group
560 		CFOR_EACH_PASSENGER(v, i)
561 		{
562 			AddPlayerToGroup(g, **i);
563 		}
564 	}
565 }
566 
SkyriderDialogue(UINT16 const quote)567 static void SkyriderDialogue(UINT16 const quote)
568 {
569 	CharacterDialogue(SKYRIDER, quote, GetExternalNPCFace(SKYRIDER), DIALOGUE_EXTERNAL_NPC_UI, FALSE);
570 }
571 
SkyriderDialogueWithSpecialEvent(SkyriderMonologueEvent const event,UINT32 const special_code)572 static void SkyriderDialogueWithSpecialEvent(SkyriderMonologueEvent const event, UINT32 const special_code)
573 {
574 	class DialogueEventSkyriderMapScreenEvent : public DialogueEvent
575 	{
576 		public:
577 			DialogueEventSkyriderMapScreenEvent(SkyriderMonologueEvent const event, UINT32 const special_code) :
578 				event_(event),
579 				special_code_(special_code)
580 			{}
581 
582 			bool Execute()
583 			{
584 				HandleSkyRiderMonologueEvent(event_, special_code_);
585 				return false;
586 			}
587 
588 		private:
589 			SkyriderMonologueEvent const event_;
590 			UINT32                 const special_code_;
591 	};
592 	DialogueEvent::Add(new DialogueEventSkyriderMapScreenEvent(event, special_code));
593 }
594 
HeliCharacterDialogue(UINT16 const usQuoteNum)595 static void HeliCharacterDialogue(UINT16 const usQuoteNum)
596 {
597 	// ARM: we could just return, but since various flags are often being set it's safer to honk so it gets fixed right!
598 	Assert(iHelicopterVehicleId != -1);
599 	SkyriderDialogue(usQuoteNum);
600 }
601 
IsRefuelSiteInSector(INT16 const sector)602 bool IsRefuelSiteInSector(INT16 const sector)
603 {
604 	FOR_EACH(RefuelSite const, i, g_refuel_site)
605 	{
606 		if (i->sector == sector) return true;
607 	}
608 	return false;
609 }
610 
IsRefuelAvailableInSector(INT16 const sector)611 bool IsRefuelAvailableInSector(INT16 const sector)
612 {
613 	return NearestRefuelPoint(false).sector == sector;
614 }
615 
UpdateRefuelSiteAvailability(void)616 void UpdateRefuelSiteAvailability( void )
617 {
618 	INT32 iCounter = 0;
619 
620 	// Generally, only Drassen is initially available for refuelling
621 	// Estoni must first be captured (although player may already have it when he gets Skyrider!)
622 
623 	for( iCounter = 0; iCounter < NUMBER_OF_REFUEL_SITES; iCounter++ )
624 	{
625 		// if enemy controlled sector (ground OR air, don't want to fly into enemy air territory)
626 		StrategicMapElement const& m = StrategicMap[g_refuel_site[iCounter].sector];
627 		if (m.fEnemyControlled    ||
628 				m.fEnemyAirControlled ||
629 				(iCounter == ESTONI_REFUELING_SITE && !CheckFact(FACT_ESTONI_REFUELLING_POSSIBLE, 0)))
630 		{
631 			// mark refueling site as unavailable
632 			fRefuelingSiteAvailable[ iCounter ] = FALSE;
633 		}
634 		else
635 		{
636 			// mark refueling site as available
637 			fRefuelingSiteAvailable[ iCounter ] = TRUE;
638 		}
639 	}
640 }
641 
SetUpHelicopterForPlayer(INT16 sX,INT16 sY)642 void SetUpHelicopterForPlayer( INT16 sX, INT16 sY )
643 {
644 	if (!fSkyRiderSetUp)
645 	{
646 		iHelicopterVehicleId = AddVehicleToList( sX, sY, 0, HELICOPTER );
647 
648 		// set up for movement
649 		SetUpHelicopterForMovement( );
650 		UpdateRefuelSiteAvailability( );
651 		fSkyRiderSetUp = TRUE;
652 		gMercProfiles[ SKYRIDER ].fUseProfileInsertionInfo = FALSE;
653 	}
654 }
655 
MoveAllInHelicopterToFootMovementGroup(void)656 void MoveAllInHelicopterToFootMovementGroup(void)
657 {
658 	// take everyone out of heli and add to movement group
659 	INT8 bNewSquad;
660 	BOOLEAN fSuccess;
661 	UINT8   ubInsertionCode = (UINT8)-1; // XXX HACK000E
662 	BOOLEAN fInsertionCodeSet = FALSE;
663 	UINT16  usInsertionData = (UINT16)-1; // XXX HACK000E
664 
665 	// put these guys on their own squad (we need to return their group ID, and can only return one, so they need a unique one
666 	bNewSquad = GetFirstEmptySquad();
667 
668 	// go through list of everyone in helicopter
669 	VEHICLETYPE const& v = GetHelicopter();
670 	CFOR_EACH_PASSENGER(v, i)
671 	{
672 		SOLDIERTYPE* const pSoldier = *i;
673 		Assert(InHelicopter(*pSoldier));
674 		fSuccess = RemoveSoldierFromHelicopter( pSoldier );
675 		Assert( fSuccess );
676 		AddCharacterToSquad( pSoldier, bNewSquad );
677 
678 		// ATE: OK - the ubStrategicInsertionCode is set 'cause groupArrivesInsector has been
679 		// called when buddy is added to a squad. However, the insertion code onlt sets set for
680 		// the first merc, so the rest are going to use whatever they had previously....
681 		if ( !fInsertionCodeSet )
682 		{
683 			ubInsertionCode = pSoldier->ubStrategicInsertionCode;
684 			usInsertionData = pSoldier->usStrategicInsertionData;
685 			fInsertionCodeSet = TRUE;
686 		}
687 		else
688 		{
689 			pSoldier->ubStrategicInsertionCode = ubInsertionCode;
690 			pSoldier->usStrategicInsertionData = usInsertionData;
691 		}
692 	}
693 }
694 
SkyRiderTalk(UINT16 usQuoteNum)695 void SkyRiderTalk( UINT16 usQuoteNum )
696 {
697 	// have skyrider talk to player
698 	HeliCharacterDialogue(usQuoteNum);
699 	fTeamPanelDirty = TRUE;
700 }
701 
702 // Skyrider monlogue events for mapscreen
HandleSkyRiderMonologueEvent(SkyriderMonologueEvent const uiEventCode,UINT32 const uiSpecialCode)703 static void HandleSkyRiderMonologueEvent(SkyriderMonologueEvent const uiEventCode, UINT32 const uiSpecialCode)
704 {
705 	// will handle the skyrider monologue about where the SAM sites are and what not
706 	TurnOnAirSpaceMode();
707 
708 	switch( uiEventCode )
709 	{
710 		case( SKYRIDER_MONOLOGUE_EVENT_DRASSEN_SAM_SITE ):
711 			SetExternMapscreenSpeechPanelXY( DEFAULT_EXTERN_PANEL_X_POS, STD_SCREEN_Y + 117 );
712 			HandleSkyRiderMonologueAboutDrassenSAMSite( uiSpecialCode );
713 			break;
714 		case SKYRIDER_MONOLOGUE_EVENT_CAMBRIA_HOSPITAL:
715 			SetExternMapscreenSpeechPanelXY( DEFAULT_EXTERN_PANEL_X_POS, STD_SCREEN_Y + 172 );
716 			HandleSkyRiderMonologueAboutCambriaHospital( uiSpecialCode );
717 			break;
718 		case( SKYRIDER_MONOLOGUE_EVENT_OTHER_SAM_SITES ):
719 			SetExternMapscreenSpeechPanelXY( STD_SCREEN_X + 335, DEFAULT_EXTERN_PANEL_Y_POS );
720 			HandleSkyRiderMonologueAboutOtherSAMSites( uiSpecialCode );
721 			break;
722 		case( SKYRIDER_MONOLOGUE_EVENT_ESTONI_REFUEL ):
723 			SetExternMapscreenSpeechPanelXY( DEFAULT_EXTERN_PANEL_X_POS, DEFAULT_EXTERN_PANEL_Y_POS );
724 			HandleSkyRiderMonologueAboutEstoniRefuel( uiSpecialCode );
725 			break;
726 	}
727 
728 	// update time
729 	guiTimeOfLastSkyriderMonologue = GetWorldTotalMin();
730 }
731 
HandleSkyRiderMonologueAboutEstoniRefuel(UINT32 const uiSpecialCode)732 static void HandleSkyRiderMonologueAboutEstoniRefuel(UINT32 const uiSpecialCode)
733 {
734 	// Once Estoni is free tell player about refueling
735 	switch (uiSpecialCode)
736 	{
737 		case 0:
738 		{
739 			SkyriderDialogueWithSpecialEvent(SKYRIDER_MONOLOGUE_EVENT_ESTONI_REFUEL, 1);
740 			// if special event data 2 is true, then do dialogue, else this is just a trigger for an event
741 			SkyriderDialogue(SPIEL_ABOUT_ESTONI_AIRSPACE);
742 			SkyriderDialogueWithSpecialEvent(SKYRIDER_MONOLOGUE_EVENT_ESTONI_REFUEL, 2);
743 			break;
744 		}
745 
746 		case 1: // Highlight Estoni
747 			fShowEstoniRefuelHighLight = TRUE;
748 			break;
749 
750 		case 2:
751 			fShowEstoniRefuelHighLight = FALSE;
752 			break;
753 	}
754 }
755 
HandleSkyRiderMonologueAboutDrassenSAMSite(UINT32 const uiSpecialCode)756 static void HandleSkyRiderMonologueAboutDrassenSAMSite(UINT32 const uiSpecialCode)
757 {
758 	switch (uiSpecialCode)
759 	{
760 		case 0:
761 		{
762 			// if special event data 2 is true, then do dialogue, else this is just a trigger for an event
763 			SkyriderDialogue(MENTION_DRASSEN_SAM_SITE);
764 			SkyriderDialogueWithSpecialEvent(SKYRIDER_MONOLOGUE_EVENT_DRASSEN_SAM_SITE, 1);
765 
766 			auto samList = GCM->getSamSites();
767 			if (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(samList[SAM_SITE_TWO]->sectorId)].fEnemyControlled)
768 			{
769 				SkyriderDialogue(SECOND_HALF_OF_MENTION_DRASSEN_SAM_SITE);
770 			}
771 			else if (CheckFact(FACT_SKYRIDER_USED_IN_MAPSCREEN, SKYRIDER))
772 			{ // Ian says don't use the SAM site quote unless player has tried flying already
773 				SkyriderDialogue(SAM_SITE_TAKEN);
774 				gfSkyriderSaidCongratsOnTakingSAM = TRUE;
775 			}
776 			SkyriderDialogueWithSpecialEvent(SKYRIDER_MONOLOGUE_EVENT_DRASSEN_SAM_SITE, 2);
777 			break;
778 		}
779 
780 		case 1: // Highlight Drassen SAM site sector
781 			fShowDrassenSAMHighLight = TRUE;
782 			SetSAMSiteAsFound(SAM_SITE_TWO);
783 			break;
784 
785 		case 2:
786 			fShowDrassenSAMHighLight = FALSE;
787 			break;
788 	}
789 }
790 
HandleSkyRiderMonologueAboutCambriaHospital(UINT32 const uiSpecialCode)791 static void HandleSkyRiderMonologueAboutCambriaHospital(UINT32 const uiSpecialCode)
792 {
793 	switch (uiSpecialCode)
794 	{
795 		case 0:
796 		{
797 			// if special event data 2 is true, then do dialogue, else this is just a trigger for an event
798 			SkyriderDialogue(MENTION_HOSPITAL_IN_CAMBRIA);
799 			SkyriderDialogueWithSpecialEvent(SKYRIDER_MONOLOGUE_EVENT_CAMBRIA_HOSPITAL, 1);
800 
801 			// Highlight Cambria hospital sector
802 			fShowCambriaHospitalHighLight = TRUE;
803 			break;
804 		}
805 
806 		case 1:
807 			fShowCambriaHospitalHighLight = FALSE;
808 			break;
809 	}
810 }
811 
HandleSkyRiderMonologueAboutOtherSAMSites(UINT32 const uiSpecialCode)812 static void HandleSkyRiderMonologueAboutOtherSAMSites(UINT32 const uiSpecialCode)
813 {
814 	/* Handle skyrider telling player about other SAM sites on fifth hiring or
815 	 * after one near drassen is taken out */
816 	switch (uiSpecialCode)
817 	{
818 		case 0:
819 		{
820 			// if special event data 2 is true, then do dialogue, else this is just a trigger for an event
821 			SkyriderDialogue(SPIEL_ABOUT_OTHER_SAM_SITES);
822 			SkyriderDialogueWithSpecialEvent(SKYRIDER_MONOLOGUE_EVENT_OTHER_SAM_SITES, 1);
823 
824 			SkyriderDialogue(SECOND_HALF_OF_SPIEL_ABOUT_OTHER_SAM_SITES);
825 			SkyriderDialogueWithSpecialEvent(SKYRIDER_MONOLOGUE_EVENT_OTHER_SAM_SITES, 2);
826 			break;
827 		}
828 
829 		case 1: // Highlight other SAMs
830 			fShowOtherSAMHighLight = TRUE;
831 			// Reveal other 3 SAM sites
832 			SetSAMSiteAsFound(SAM_SITE_ONE);
833 			SetSAMSiteAsFound(SAM_SITE_THREE);
834 			SetSAMSiteAsFound(SAM_SITE_FOUR);
835 			break;
836 
837 		case 2:
838 			fShowOtherSAMHighLight = FALSE;
839 			break;
840 	}
841 }
842 
CheckAndHandleSkyriderMonologues(void)843 void CheckAndHandleSkyriderMonologues( void )
844 {
845 	// wait at least this many days between Skyrider monologues
846 	if ( ( GetWorldTotalMin() - guiTimeOfLastSkyriderMonologue ) >= ( MIN_DAYS_BETWEEN_SKYRIDER_MONOLOGUES * 24 * 60 ) )
847 	{
848 		if( guiHelicopterSkyriderTalkState == 0 )
849 		{
850 			HandleSkyRiderMonologueEvent( SKYRIDER_MONOLOGUE_EVENT_DRASSEN_SAM_SITE, 0 );
851 			guiHelicopterSkyriderTalkState = 1;
852 		}
853 		else if( guiHelicopterSkyriderTalkState == 1 )
854 		{
855 			// if enemy still controls the Cambria hospital sector
856 			if( StrategicMap[ CALCULATE_STRATEGIC_INDEX( HOSPITAL_SECTOR_X, HOSPITAL_SECTOR_Y ) ].fEnemyControlled )
857 			{
858 				HandleSkyRiderMonologueEvent( SKYRIDER_MONOLOGUE_EVENT_CAMBRIA_HOSPITAL, 0 );
859 			}
860 			// advance state even if player already has Cambria's hospital sector!!!
861 			guiHelicopterSkyriderTalkState = 2;
862 		}
863 		else if( guiHelicopterSkyriderTalkState == 2 )
864 		{
865 			// wait until player has taken over a SAM site before saying this and advancing state
866 			if ( gfSkyriderSaidCongratsOnTakingSAM )
867 			{
868 				HandleSkyRiderMonologueEvent( SKYRIDER_MONOLOGUE_EVENT_OTHER_SAM_SITES, 0 );
869 				guiHelicopterSkyriderTalkState = 3;
870 			}
871 		}
872 		else if( guiHelicopterSkyriderTalkState == 3 )
873 		{
874 			// wait until Estoni refuelling site becomes available
875 			if ( fRefuelingSiteAvailable[ ESTONI_REFUELING_SITE ] )
876 			{
877 				HandleSkyRiderMonologueEvent( SKYRIDER_MONOLOGUE_EVENT_ESTONI_REFUEL, 0 );
878 				guiHelicopterSkyriderTalkState = 4;
879 			}
880 		}
881 	}
882 }
883 
884 
HandleBlitOfSectorLocatorIcon(UINT8 const sector,UINT8 const locator)885 static void HandleBlitOfSectorLocatorIcon(UINT8 const sector, UINT8 const locator)
886 {
887 	HandleBlitOfSectorLocatorIcon(SECTORX(sector), SECTORY(sector), 0, locator);
888 }
889 
890 
HandleAnimationOfSectors(void)891 void HandleAnimationOfSectors( void )
892 {
893 	BOOLEAN fSkipSpeakersLocator = FALSE;
894 	// these don't need to be saved, they merely turn off the highlights after they stop flashing
895 	static BOOLEAN fOldShowDrassenSAMHighLight = FALSE;
896 	static BOOLEAN fOldShowCambriaHospitalHighLight = FALSE;
897 	static BOOLEAN fOldShowEstoniRefuelHighLight = FALSE;
898 	static BOOLEAN fOldShowOtherSAMHighLight = FALSE;
899 
900 	auto samList = GCM->getSamSites();
901 
902 	// find out which mode we are in and animate for that mode
903 
904 	// Drassen SAM site
905 	if( fShowDrassenSAMHighLight )
906 	{
907 		fOldShowDrassenSAMHighLight = TRUE;
908 		// Drassen's SAM site is #2
909 		HandleBlitOfSectorLocatorIcon(samList[SAM_SITE_TWO]->sectorId, LOCATOR_COLOR_RED);
910 		fSkipSpeakersLocator = TRUE;
911 	}
912 	else if( fOldShowDrassenSAMHighLight )
913 	{
914 		fOldShowDrassenSAMHighLight = FALSE;
915 		fMapPanelDirty = TRUE;
916 	}
917 
918 	// Cambria hospital
919 	if( fShowCambriaHospitalHighLight )
920 	{
921 		fOldShowCambriaHospitalHighLight = TRUE;
922 		HandleBlitOfSectorLocatorIcon( HOSPITAL_SECTOR_X, HOSPITAL_SECTOR_Y, 0, LOCATOR_COLOR_RED );
923 		fSkipSpeakersLocator = TRUE;
924 	}
925 	else if( fOldShowCambriaHospitalHighLight )
926 	{
927 		fOldShowCambriaHospitalHighLight = FALSE;
928 		fMapPanelDirty = TRUE;
929 	}
930 
931 	// show other SAM sites
932 	if( fShowOtherSAMHighLight )
933 	{
934 		fOldShowOtherSAMHighLight = TRUE;
935 		HandleBlitOfSectorLocatorIcon(samList[SAM_SITE_ONE]->sectorId,   LOCATOR_COLOR_RED);
936 		HandleBlitOfSectorLocatorIcon(samList[SAM_SITE_THREE]->sectorId, LOCATOR_COLOR_RED);
937 		HandleBlitOfSectorLocatorIcon(samList[SAM_SITE_FOUR]->sectorId,  LOCATOR_COLOR_RED);
938 		fSkipSpeakersLocator = TRUE;
939 	}
940 	else if( fOldShowOtherSAMHighLight )
941 	{
942 		fOldShowOtherSAMHighLight = FALSE;
943 		fMapPanelDirty = TRUE;
944 	}
945 
946 	// show Estoni site
947 	if( fShowEstoniRefuelHighLight )
948 	{
949 		fOldShowEstoniRefuelHighLight = TRUE;
950 		INT16 const sec = g_refuel_site[ESTONI_REFUELING_SITE].sector;
951 		HandleBlitOfSectorLocatorIcon(GET_X_FROM_STRATEGIC_INDEX(sec), GET_Y_FROM_STRATEGIC_INDEX(sec), 0, LOCATOR_COLOR_RED);
952 		fSkipSpeakersLocator = TRUE;
953 	}
954 	else if( fOldShowEstoniRefuelHighLight )
955 	{
956 		fOldShowEstoniRefuelHighLight = FALSE;
957 		fMapPanelDirty = TRUE;
958 	}
959 
960 	// don't show sector locator over the speaker's sector if he is talking about another sector - it's confusing
961 	if ( !fSkipSpeakersLocator )
962 	{
963 		switch( gubBlitSectorLocatorCode )
964 		{
965 			case LOCATOR_COLOR_RED: // normal one used for mines (will now be overriden with yellow)
966 				HandleBlitOfSectorLocatorIcon( gsSectorLocatorX, gsSectorLocatorY, 0, LOCATOR_COLOR_RED );
967 				break;
968 			case LOCATOR_COLOR_YELLOW: // used for all other dialogues
969 				HandleBlitOfSectorLocatorIcon( gsSectorLocatorX, gsSectorLocatorY, 0, LOCATOR_COLOR_YELLOW );
970 				break;
971 		}
972 	}
973 }
974 
HandleHelicopterOnGround(BOOLEAN handleGraphicToo)975 void HandleHelicopterOnGround(BOOLEAN handleGraphicToo)
976 {
977 	// No worries if underground
978 	if (gbWorldSectorZ != 0) return;
979 
980 	for (UINT8 site = 0; site != NUMBER_OF_REFUEL_SITES; ++site)
981 	{
982 		RefuelSite const& r = g_refuel_site[site];
983 		// Is this refueling site sector the loaded sector?
984 		if (CALCULATE_STRATEGIC_INDEX(gWorldSectorX, gWorldSectorY) != r.sector) continue;
985 
986 		// YES, so find out if the chopper is landed here
987 		if (IsHelicopterOnGroundAtRefuelingSite(r))
988 		{
989 			if(handleGraphicToo)
990 			{
991 				AddHelicopterToMaps(true, r);
992 			}
993 			// ATE: Add Skyrider too
994 			// ATE: only if hired
995 			if (iHelicopterVehicleId != -1)
996 			{
997 				MERCPROFILESTRUCT& p = GetProfile(SKYRIDER);
998 				p.sSectorX = gWorldSectorX;
999 				p.sSectorY = gWorldSectorY;
1000 			}
1001 		}
1002 		else
1003 		{
1004 			if(handleGraphicToo)
1005 			{
1006 				AddHelicopterToMaps(false, r);
1007 			}
1008 			// ATE: Remove Skyrider
1009 			if (iHelicopterVehicleId != -1)
1010 			{
1011 				MERCPROFILESTRUCT& p = GetProfile(SKYRIDER);
1012 				p.sSectorX = 0;
1013 				p.sSectorY = 0;
1014 
1015 				// See if we can find him and remove him if so
1016 				// ATE: Don't do this if buddy is on our team!
1017 				SOLDIERTYPE* const s = FindSoldierByProfileID(SKYRIDER);
1018 				if (s && s->bTeam != OUR_TEAM) TacticalRemoveSoldier(*s);
1019 			}
1020 		}
1021 		if(handleGraphicToo)
1022 		{
1023 			InvalidateWorldRedundency();
1024 		}
1025 		break;
1026 	}
1027 }
1028 
IsHelicopterOnGroundAtRefuelingSite(RefuelSite const & r)1029 static bool IsHelicopterOnGroundAtRefuelingSite(RefuelSite const& r)
1030 {
1031 	if (fHelicopterDestroyed)  return false;
1032 	if (fHelicopterIsAirBorne) return false;
1033 
1034 	// if we haven't even met SkyRider
1035 	if (!fSkyRiderSetUp)
1036 	{ // Then it's always at Drassen
1037 		return &r == &g_refuel_site[DRASSEN_REFUELING_SITE];
1038 	}
1039 
1040 	// on the ground, but is it at this site or at another one?
1041 	VEHICLETYPE const& v = GetHelicopter();
1042 	return CALCULATE_STRATEGIC_INDEX(v.sSectorX, v.sSectorY) == r.sector;
1043 }
1044 
HeliCrashSoundStopCallback(void * pData)1045 static void HeliCrashSoundStopCallback(void* pData)
1046 {
1047 	SkyriderDestroyed( );
1048 }
1049 
HandleSAMSiteAttackOfHelicopterInSector(INT16 sSectorX,INT16 sSectorY)1050 static BOOLEAN HandleSAMSiteAttackOfHelicopterInSector(INT16 sSectorX, INT16 sSectorY)
1051 {
1052 	INT8 bSamSiteID = -1;
1053 	INT8 bSAMCondition;
1054 	UINT8 ubChance;
1055 
1056 	// if this sector is in friendly airspace, we're safe
1057 	if (!StrategicMap[CALCULATE_STRATEGIC_INDEX(sSectorX, sSectorY)].fEnemyAirControlled)
1058 	{
1059 		// no problem, friendly airspace
1060 		return( FALSE );
1061 	}
1062 
1063 	// which SAM controls this sector?
1064 	bSamSiteID = GCM->getControllingSamSite(SECTOR(sSectorX, sSectorY));
1065 
1066 	// if none of them (-1 means the sector is not covered by a SAM)
1067 	if (bSamSiteID < 0)
1068 	{
1069 		return FALSE;
1070 	}
1071 
1072 	// get the condition of that SAM site (NOTE: SAM IDs are 0-3)
1073 	Assert(bSamSiteID < NUMBER_OF_SAMS );
1074 	UINT8 ubSAMSectorID = GCM->getSamSites()[bSamSiteID]->sectorId;
1075 	bSAMCondition = StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX(ubSAMSectorID) ].bSAMCondition;
1076 
1077 	// if the SAM site is too damaged to be a threat
1078 	if( bSAMCondition < MIN_CONDITION_FOR_SAM_SITE_TO_WORK )
1079 	{
1080 		// no problem, SAM site not working
1081 		return( FALSE );
1082 	}
1083 
1084 	// Hostile airspace controlled by a working SAM site, so SAM site fires a SAM at Skyrider!!!
1085 	// calc chance that chopper will be shot down
1086 	ubChance = bSAMCondition;
1087 
1088 	// there's a fair chance of a miss even if the SAM site is in perfect working order
1089 	if (ubChance > MAX_SAM_SITE_ACCURACY)
1090 	{
1091 		ubChance = MAX_SAM_SITE_ACCURACY;
1092 	}
1093 
1094 	if( PreRandom( 100 ) < ubChance)
1095 	{
1096 		// another hit!
1097 		gubHelicopterHitsTaken++;
1098 
1099 		// Took a hit!  Pause time so player can reconsider
1100 		StopTimeCompression();
1101 
1102 		// first hit?
1103 		if ( gubHelicopterHitsTaken == 1 )
1104 		{
1105 			HeliCharacterDialogue(HELI_TOOK_MINOR_DAMAGE);
1106 		}
1107 		// second hit?
1108 		else if ( gubHelicopterHitsTaken == 2 )
1109 		{
1110 			// going back to base (no choice, dialogue says so)
1111 			HeliCharacterDialogue(HELI_TOOK_MAJOR_DAMAGE);
1112 			MakeHeliReturnToBase();
1113 		}
1114 		// third hit!
1115 		else
1116 		{
1117 			// Important: Skyrider must still be alive when he talks, so must do this before heli is destroyed!
1118 			HeliCharacterDialogue(HELI_GOING_DOWN);
1119 
1120 			// everyone die die die
1121 			// play sound
1122 			if (PlayJA2StreamingSampleFromFile(STSOUNDSDIR "/blah2.wav", HIGHVOLUME, 1, MIDDLEPAN, HeliCrashSoundStopCallback) == SOUND_ERROR)
1123 			{
1124 				// Destroy here if we cannot play streamed sound sample....
1125 				SkyriderDestroyed( );
1126 			}
1127 			else
1128 			{
1129 				// otherwise it's handled in the callback
1130 				// remove any arrival events for the helicopter's group
1131 				DeleteStrategicEvent(EVENT_GROUP_ARRIVAL, GetHelicopter().ubMovementGroup);
1132 			}
1133 
1134 			// special return code indicating heli was destroyed
1135 			return( TRUE );
1136 		}
1137 	}
1138 	// still flying
1139 	return( FALSE );
1140 }
1141 
1142 // check if helicopter can take off?
CanHelicopterTakeOff(void)1143 BOOLEAN CanHelicopterTakeOff( void )
1144 {
1145 	// if it's already in the air
1146 	if (fHelicopterIsAirBorne) return TRUE;
1147 
1148 	VEHICLETYPE const& v = GetHelicopter();
1149 	// grab location
1150 	INT16 const sHelicopterSector = CALCULATE_STRATEGIC_INDEX(v.sSectorX, v.sSectorY);
1151 	// if it's not in enemy control, we can take off
1152 	if (!StrategicMap[sHelicopterSector].fEnemyControlled)
1153 	{
1154 		return( TRUE );
1155 	}
1156 	return( FALSE );
1157 }
1158 
AddHeliPiece(INT16 const sGridNo,UINT16 const sOStruct)1159 static void AddHeliPiece(INT16 const sGridNo, UINT16 const sOStruct)
1160 {
1161 	if (IndexExistsInStructLayer(sGridNo, sOStruct)) return;
1162 	AddStructToTail(sGridNo, sOStruct);
1163 }
1164 
AddHelicopterToMaps(bool const add,RefuelSite const & r)1165 static void AddHelicopterToMaps(bool const add, RefuelSite const& r)
1166 {
1167 	GridNo const grid_no = r.grid_no;
1168 	INT16  const ostruct = r.heli_ostruct;
1169 
1170 	// are we adding or taking away
1171 	if (add)
1172 	{
1173 		AddHeliPiece(grid_no,       ostruct    );
1174 		AddHeliPiece(grid_no,       ostruct + 1);
1175 		AddHeliPiece(grid_no - 800, ostruct + 2);
1176 		AddHeliPiece(grid_no,       ostruct + 3);
1177 		AddHeliPiece(grid_no,       ostruct + 4);
1178 		AddHeliPiece(grid_no - 800, ostruct + 5);
1179 
1180 		// ATE: If any mercs here, bump them off!
1181 		INT16	sCentreGridX;
1182 		INT16 sCentreGridY;
1183 		ConvertGridNoToXY(grid_no, &sCentreGridX, &sCentreGridY);
1184 
1185 		for (INT16 y = sCentreGridY - 5; y < sCentreGridY + 5; ++y)
1186 		{
1187 			for (INT16 x = sCentreGridX - 5; x < sCentreGridX + 5; ++x)
1188 			{
1189 				BumpAnyExistingMerc(MAPROWCOLTOPOS(y, x));
1190 			}
1191 		}
1192 	}
1193 	else
1194 	{
1195 		// remove from the world
1196 		RemoveStruct(grid_no,       ostruct    );
1197 		RemoveStruct(grid_no,       ostruct + 1);
1198 		RemoveStruct(grid_no - 800, ostruct + 2);
1199 		RemoveStruct(grid_no,       ostruct + 3);
1200 		RemoveStruct(grid_no,       ostruct + 4);
1201 		RemoveStruct(grid_no - 800, ostruct + 5);
1202 	}
1203 	InvalidateWorldRedundency();
1204 	SetRenderFlags(RENDER_FLAG_FULL);
1205 }
1206 
IsSkyriderFlyingInSector(INT16 const x,INT16 const y)1207 bool IsSkyriderFlyingInSector(INT16 const x, INT16 const y)
1208 {
1209 	// up and about?
1210 	if (iHelicopterVehicleId == -1) return false;
1211 	if (!CanHelicopterFly())        return false;
1212 	if (!fHelicopterIsAirBorne)     return false;
1213 	VEHICLETYPE const& v = GetHelicopter();
1214 	// the right sector?
1215 	return x == v.sSectorX && y == v.sSectorY;
1216 }
1217 
IsGroupTheHelicopterGroup(GROUP const & g)1218 bool IsGroupTheHelicopterGroup(GROUP const& g)
1219 {
1220 	if (iHelicopterVehicleId == -1) return false;
1221 	VEHICLETYPE const& v = GetHelicopter();
1222 	return
1223 		v.ubMovementGroup != 0 &&
1224 		v.ubMovementGroup == g.ubGroupID;
1225 }
1226 
GetNumSafeSectorsInPath()1227 INT16 GetNumSafeSectorsInPath()
1228 {
1229 	if (!CanHelicopterFly()) return 0;
1230 
1231 	VEHICLETYPE const& v      = GetHelicopter();
1232 	INT32       const  sector = CALCULATE_STRATEGIC_INDEX(v.sSectorX, v.sSectorY);
1233 	GROUP*      const  g      = GetGroup(v.ubMovementGroup);
1234 	UINT32             n      = 0;
1235 
1236 	if (PathSt const* i = v.pMercPath)
1237 	{
1238 		/* First node: Skip it if that's the sector the chopper is currently in, AND
1239 		 * we're NOT gonna be changing directions (not actually performed until
1240 		 * waypoints are rebuilt AFTER plotting is done) */
1241 		if ((INT32)i->uiSectorId == sector &&
1242 				i->pNext &&
1243 				!GroupBetweenSectorsAndSectorXYIsInDifferentDirection(g, GET_X_FROM_STRATEGIC_INDEX(i->pNext->uiSectorId), GET_Y_FROM_STRATEGIC_INDEX(i->pNext->uiSectorId)))
1244 		{
1245 			i = i->pNext;
1246 		}
1247 
1248 		for (; i; i = i->pNext)
1249 		{
1250 			if (StrategicMap[i->uiSectorId].fEnemyAirControlled) continue;
1251 			++n;
1252 		}
1253 	}
1254 
1255 	if (PathSt const* i = pTempHelicopterPath)
1256 	{
1257 		/* First node: Skip it if that's the sector the chopper is currently in, AND
1258 		 * we're NOT gonna be changing directions (not actually performed until
1259 		 * waypoints are rebuilt AFTER plotting is done) OR if the chopper has a
1260 		 * mercpath, in which case this a continuation of it that would count the
1261 		 * sector twice */
1262 		if ((
1263 					(INT32)i->uiSectorId == sector &&
1264 					i->pNext &&
1265 					!GroupBetweenSectorsAndSectorXYIsInDifferentDirection(g, GET_X_FROM_STRATEGIC_INDEX(i->pNext->uiSectorId), GET_Y_FROM_STRATEGIC_INDEX(i->pNext->uiSectorId))
1266 				) ||
1267 				GetLengthOfPath(v.pMercPath) > 0)
1268 		{
1269 			i = i->pNext;
1270 		}
1271 
1272 		for (; i; i = i->pNext)
1273 		{
1274 			if (StrategicMap[i->uiSectorId].fEnemyAirControlled) continue;
1275 			++n;
1276 		}
1277 	}
1278 	return n;
1279 }
1280 
GetNumUnSafeSectorsInPath(void)1281 INT16 GetNumUnSafeSectorsInPath( void )
1282 {
1283 	// get the last sector value in the helictoper's path
1284 	UINT32 uiLocation = 0;
1285 	UINT32  uiCount = 0;
1286 
1287 	// if the heli is on the move, what is the distance it will move..the length of the merc path, less the first node
1288 	if (!CanHelicopterFly()) return 0;
1289 
1290 	VEHICLETYPE const& v = GetHelicopter();
1291 	// may need to skip the sector the chopper is currently in
1292 	INT32 const iHeliSector = CALCULATE_STRATEGIC_INDEX(v.sSectorX, v.sSectorY);
1293 
1294 	// get chopper's group ptr
1295 	GROUP* const pGroup = GetGroup(v.ubMovementGroup);
1296 
1297 	const PathSt* pNode = v.pMercPath;
1298 
1299 	// any path yet?
1300 	if( pNode != NULL )
1301 	{
1302 		// first node: skip it if that's the sector the chopper is currently in, AND
1303 		// we're NOT gonna be changing directions (not actually performed until waypoints are rebuilt AFTER plotting is done)
1304 		if ( ( ( INT32 ) pNode->uiSectorId == iHeliSector ) && ( pNode->pNext != NULL ) &&
1305 				!GroupBetweenSectorsAndSectorXYIsInDifferentDirection( pGroup, ( UINT8 ) GET_X_FROM_STRATEGIC_INDEX( pNode->pNext->uiSectorId ), ( UINT8 ) GET_Y_FROM_STRATEGIC_INDEX( pNode->pNext->uiSectorId ) ) )
1306 		{
1307 			pNode = pNode->pNext;
1308 		}
1309 
1310 		while( pNode)
1311 		{
1312 			uiLocation = pNode -> uiSectorId;
1313 
1314 			if ( StrategicMap[ uiLocation ].fEnemyAirControlled )
1315 			{
1316 				uiCount++;
1317 			}
1318 
1319 			pNode = pNode ->pNext;
1320 		}
1321 	}
1322 
1323 	pNode = pTempHelicopterPath;
1324 	// any path yet?
1325 	if( pNode != NULL )
1326 	{
1327 		// first node: skip it if that's the sector the chopper is currently in, AND
1328 		// we're NOT gonna be changing directions (not actually performed until waypoints are rebuilt AFTER plotting is done)
1329 		// OR if the chopper has a mercpath, in which case this a continuation of it that would count the sector twice
1330 		if ( ( ( ( INT32 ) pNode->uiSectorId == iHeliSector ) && ( pNode->pNext != NULL ) &&
1331 				!GroupBetweenSectorsAndSectorXYIsInDifferentDirection( pGroup, ( UINT8 ) GET_X_FROM_STRATEGIC_INDEX( pNode->pNext->uiSectorId ), ( UINT8 ) GET_Y_FROM_STRATEGIC_INDEX( pNode->pNext->uiSectorId ) ) ) ||
1332 				GetLengthOfPath(v.pMercPath) > 0)
1333 		{
1334 			pNode = pNode->pNext;
1335 		}
1336 
1337 		while( pNode)
1338 		{
1339 			uiLocation = pNode -> uiSectorId;
1340 
1341 			if ( StrategicMap[ uiLocation ].fEnemyAirControlled )
1342 			{
1343 				uiCount++;
1344 			}
1345 
1346 			pNode = pNode ->pNext;
1347 		}
1348 	}
1349 	return( (INT16)uiCount );
1350 }
1351 
PaySkyriderBill(void)1352 static void PaySkyriderBill(void)
1353 {
1354 	// if we owe anything for the trip
1355 	if ( iTotalAccumulatedCostByPlayer > 0 )
1356 	{
1357 		// if player can afford to pay the Skyrider bill
1358 		if( LaptopSaveInfo.iCurrentBalance >= iTotalAccumulatedCostByPlayer )
1359 		{
1360 			// no problem, pay the man
1361 			// add the transaction
1362 			AddTransactionToPlayersBook( PAYMENT_TO_NPC, SKYRIDER, GetWorldTotalMin( ), -iTotalAccumulatedCostByPlayer );
1363 			ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, st_format_printf(pSkyriderText[ 0 ], iTotalAccumulatedCostByPlayer) );
1364 		}
1365 		else
1366 		{
1367 			// money owed
1368 			if( LaptopSaveInfo.iCurrentBalance > 0 )
1369 			{
1370 				ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, st_format_printf(pSkyriderText[ 0 ], LaptopSaveInfo.iCurrentBalance) );
1371 				gMercProfiles[ SKYRIDER ].iBalance = LaptopSaveInfo.iCurrentBalance - iTotalAccumulatedCostByPlayer;
1372 				// add the transaction
1373 				AddTransactionToPlayersBook( PAYMENT_TO_NPC, SKYRIDER, GetWorldTotalMin( ), -LaptopSaveInfo.iCurrentBalance );
1374 			}
1375 			else
1376 			{
1377 				gMercProfiles[ SKYRIDER ].iBalance = - iTotalAccumulatedCostByPlayer;
1378 			}
1379 
1380 			HeliCharacterDialogue(OWED_MONEY_TO_SKYRIDER);
1381 			ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, st_format_printf(pSkyriderText[ 1 ], -gMercProfiles[ SKYRIDER ].iBalance) );
1382 
1383 			// kick everyone out! (we know we're in a safe sector if we're paying)
1384 			MoveAllInHelicopterToFootMovementGroup( );
1385 			MakeHeliReturnToBase();
1386 		}
1387 		iTotalAccumulatedCostByPlayer = 0;
1388 	}
1389 }
1390 
PayOffSkyriderDebtIfAny()1391 void PayOffSkyriderDebtIfAny( )
1392 {
1393 	INT32 iAmountOwed;
1394 	INT32 iPayAmount;
1395 
1396 	iAmountOwed = - gMercProfiles[ SKYRIDER ].iBalance;
1397 
1398 	// if we owe him anything, and have any money
1399 	if ( ( iAmountOwed > 0 ) && ( LaptopSaveInfo.iCurrentBalance > 0 ) )
1400 	{
1401 		iPayAmount = MIN( iAmountOwed, LaptopSaveInfo.iCurrentBalance );
1402 
1403 		// pay the man what we can
1404 		gMercProfiles[ SKYRIDER ].iBalance += iPayAmount;
1405 		// add the transaction
1406 		AddTransactionToPlayersBook( PAYMENT_TO_NPC, SKYRIDER, GetWorldTotalMin( ), -iPayAmount );
1407 		// tell player
1408 		ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, st_format_printf(pSkyriderText[ 0 ], iPayAmount) );
1409 		// now whaddawe owe?
1410 		iAmountOwed = - gMercProfiles[ SKYRIDER ].iBalance;
1411 
1412 		// if it wasn't enough
1413 		if ( iAmountOwed > 0 )
1414 		{
1415 			ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, st_format_printf(pSkyriderText[ 1 ], iAmountOwed) );
1416 			HeliCharacterDialogue(OWED_MONEY_TO_SKYRIDER);
1417 		}
1418 	}
1419 }
1420 
MakeHeliReturnToBase(void)1421 static void MakeHeliReturnToBase(void)
1422 {
1423 	VEHICLETYPE& v = GetHelicopter();
1424 	INT16 sectorID;
1425 
1426 	sectorID = CALCULATE_STRATEGIC_INDEX(v.sSectorX, v.sSectorY);
1427 	// if already at a refueling point
1428 	if (IsRefuelAvailableInSector(sectorID))
1429 	{
1430 		LandHelicopter();
1431 	}
1432 	else
1433 	{
1434 		// choose destination (closest refueling sector)
1435 		RefuelSite const& refuel_site = NearestRefuelPoint(true);
1436 
1437 		ClearStrategicPathList(v.pMercPath, v.ubMovementGroup);
1438 		GROUP& g = *GetGroup(v.ubMovementGroup);
1439 		v.pMercPath = BuildAStrategicPath( sectorID, refuel_site.sector, g, FALSE);
1440 		RebuildWayPointsForGroupPath(v.pMercPath, g);
1441 
1442 		fHeliReturnStraightToBase = TRUE;
1443 		fHoveringHelicopter       = FALSE;
1444 	}
1445 	// stop time compression if it's on so player can digest this
1446 	StopTimeCompression();
1447 }
1448 
SoldierAboardAirborneHeli(SOLDIERTYPE const & s)1449 bool SoldierAboardAirborneHeli(SOLDIERTYPE const& s)
1450 {
1451 	return InHelicopter(s) && fHelicopterIsAirBorne;
1452 }
1453