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