1 #include "StrategicMap.h"
2
3 #include "AI.h"
4 #include "Ambient_Control.h"
5 #include "Animated_ProgressBar.h"
6 #include "Animation_Control.h"
7 #include "Assignments.h"
8 #include "Auto_Resolve.h"
9 #include "Boxing.h"
10 #include "Bullets.h"
11 #include "Campaign_Types.h"
12 #include "ContentManager.h"
13 #include "ContentMusic.h"
14 #include "Creature_Spreading.h"
15 #include "Cursor_Control.h"
16 #include "Cursors.h"
17 #include "Debug.h"
18 #include "Dialogue_Control.h"
19 #include "Directories.h"
20 #include "Enemy_Soldier_Save.h"
21 #include "Environment.h"
22 #include "Event_Pump.h"
23 #include "Exit_Grids.h"
24 #include "Explosion_Control.h"
25 #include "Faces.h"
26 #include "Fade_Screen.h"
27 #include "FileMan.h"
28 #include "Font.h"
29 #include "Font_Control.h"
30 #include "GameInstance.h"
31 #include "GameLoop.h"
32 #include "GameScreen.h"
33 #include "GameSettings.h"
34 #include "Game_Clock.h"
35 #include "Game_Events.h"
36 #include "HImage.h"
37 #include "Handle_UI.h"
38 #include "History.h"
39 #include "Interface.h"
40 #include "Interface_Dialogue.h"
41 #include "Interface_Panels.h"
42 #include "Isometric_Utils.h"
43 #include "Items.h"
44 #include "JAScreens.h"
45 #include "Keys.h"
46 #include "LoadSaveSectorInfo.h"
47 #include "LoadSaveStrategicMapElement.h"
48 #include "Loading_Screen.h"
49 #include "Local.h"
50 #include "Logger.h"
51 #include "MapScreen.h"
52 #include "Map_Edgepoints.h"
53 #include "Map_Information.h"
54 #include "Map_Screen_Helicopter.h"
55 #include "Meanwhile.h"
56 #include "Merc_Contract.h"
57 #include "Merc_Entering.h"
58 #include "Merc_Hiring.h"
59 #include "Message.h"
60 #include "MessageBoxScreen.h"
61 #include "Militia_Control.h"
62 #include "MineModel.h"
63 #include "Music_Control.h"
64 #include "NPC.h"
65 #include "OppList.h"
66 #include "Overhead.h"
67 #include "PathAI.h"
68 #include "Physics.h"
69 #include "Player_Command.h"
70 #include "Points.h"
71 #include "PreBattle_Interface.h"
72 #include "Queen_Command.h"
73 #include "Quests.h"
74 #include "Radar_Screen.h"
75 #include "Random.h"
76 #include "RenderWorld.h"
77 #include "Render_Dirty.h"
78 #include "SamSiteModel.h"
79 #include "SaveLoadMap.h"
80 #include "Scheduling.h"
81 #include "ScreenIDs.h"
82 #include "Soldier_Add.h"
83 #include "Soldier_Control.h"
84 #include "Soldier_Create.h"
85 #include "Soldier_Init_List.h"
86 #include "Soldier_Macros.h"
87 #include "Sound_Control.h"
88 #include "Squads.h"
89 #include "Strategic.h"
90 #include "StrategicMap_Secrets.h"
91 #include "StrategicMapSecretModel.h"
92 #include "Strategic_Event_Handler.h"
93 #include "Strategic_Mines.h"
94 #include "Strategic_Movement.h"
95 #include "Strategic_Pathing.h"
96 #include "Strategic_Town_Loyalty.h"
97 #include "Strategic_Turns.h"
98 #include "SysUtil.h"
99 #include "Sys_Globals.h"
100 #include "Tactical_Placement_GUI.h"
101 #include "Tactical_Save.h"
102 #include "Tactical_Turns.h"
103 #include "Text.h"
104 #include "Timer.h"
105 #include "Timer_Control.h"
106 #include "TownModel.h"
107 #include "Town_Militia.h"
108 #include "Types.h"
109 #include "UILayout.h"
110 #include "VObject.h"
111 #include "VSurface.h"
112 #include "Video.h"
113 #include "WorldDat.h"
114 #include "WorldDef.h"
115 #include "WorldMan.h"
116
117 #include <string_theory/format>
118 #include <string_theory/string>
119
120 #include <algorithm>
121 #include <iterator>
122 #include <map>
123 #include <stdexcept>
124
125 //Used by PickGridNoToWalkIn
126 #define MAX_ATTEMPTS 200
127
128 #define QUEST_CHECK_EVENT_TIME ( 8 * 60 )
129 #define BOBBYRAY_UPDATE_TIME ( 9 * 60 )
130 #define INSURANCE_UPDATE_TIME 0
131 #define EARLY_MORNING_TIME ( 4 * 60 )
132 #define ENRICO_MAIL_TIME ( 7 * 60 )
133
134
135 extern INT16 gsRobotGridNo;
136
137 BOOLEAN gfGettingNameFromSaveLoadScreen;
138
139 INT16 gWorldSectorX = 0;
140 INT16 gWorldSectorY = 0;
141 INT8 gbWorldSectorZ = -1;
142
143 static INT16 gsAdjacentSectorX;
144 static INT16 gsAdjacentSectorY;
145 static INT8 gbAdjacentSectorZ;
146 static GROUP* gpAdjacentGroup = 0;
147 static UINT8 gubAdjacentJumpCode;
148 static UINT32 guiAdjacentTraverseTime;
149 UINT8 gubTacticalDirection;
150 static INT16 gsAdditionalData;
151
152 static BOOLEAN fUsingEdgePointsForStrategicEntry = FALSE;
153 BOOLEAN gfInvalidTraversal = FALSE;
154 BOOLEAN gfLoneEPCAttemptingTraversal = FALSE;
155 BOOLEAN gfRobotWithoutControllerAttemptingTraversal = FALSE;
156 BOOLEAN gubLoneMercAttemptingToAbandonEPCs = 0;
157 const SOLDIERTYPE* gPotentiallyAbandonedEPC = NULL;
158
159 INT8 gbGreenToElitePromotions = 0;
160 INT8 gbGreenToRegPromotions = 0;
161 INT8 gbRegToElitePromotions = 0;
162 INT8 gbMilitiaPromotions = 0;
163
164
165 BOOLEAN gfUseAlternateMap = FALSE;
166
167 static INT16 const DirXIncrementer[8] =
168 {
169 0, //N
170 1, //NE
171 1, //E
172 1, //SE
173 0, //S
174 -1, //SW
175 -1, //W
176 -1 //NW
177 };
178
179 static INT16 const DirYIncrementer[8] =
180 {
181 -1, //N
182 -1, //NE
183 0, //E
184 1, //SE
185 1, //S
186 1, //SW
187 0, //W
188 -1 //NW
189 };
190
191 Observable<> BeforePrepareSector = {};
192
193 extern BOOLEAN gfOverrideSector;
194
195
196 StrategicMapElement StrategicMap[MAP_WORLD_X * MAP_WORLD_Y];
197
198
199 //temp timer stuff -- to measure the time it takes to load a map.
200
UndergroundTacticalTraversalTime(INT8 const exit_direction)201 static UINT32 UndergroundTacticalTraversalTime(INT8 const exit_direction)
202 { /* We are attempting to traverse in an underground environment. We need to use
203 * a complete different method. When underground, all sectors are instantly
204 * adjacent. */
205 GridNo gridno;
206 switch (exit_direction)
207 {
208 case NORTH_STRATEGIC_MOVE: gridno = gMapInformation.sNorthGridNo; break;
209 case EAST_STRATEGIC_MOVE: gridno = gMapInformation.sEastGridNo; break;
210 case SOUTH_STRATEGIC_MOVE: gridno = gMapInformation.sSouthGridNo; break;
211 case WEST_STRATEGIC_MOVE: gridno = gMapInformation.sWestGridNo; break;
212 default: throw std::logic_error("invalid exit direction");
213 }
214 return gridno != -1 ? 0 : TRAVERSE_TIME_IMPOSSIBLE;
215 }
216
217
BeginLoadScreen()218 void BeginLoadScreen( )
219 {
220 UINT32 uiStartTime, uiCurrTime;
221 INT32 iPercentage, iFactor;
222 UINT32 uiTimeRange;
223
224 SetCurrentCursorFromDatabase( VIDEO_NO_CURSOR );
225
226 if( guiCurrentScreen == MAP_SCREEN && !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME) && !AreInMeanwhile() )
227 {
228 SGPBox const DstRect = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
229 uiTimeRange = 2000;
230 iPercentage = 0;
231 uiStartTime = GetClock();
232 BltVideoSurface(guiSAVEBUFFER, FRAME_BUFFER, 0, 0, NULL);
233 PlayJA2SampleFromFile(SOUNDSDIR "/final psionic blast 01 (16-44).wav", HIGHVOLUME, 1, MIDDLEPAN);
234 while( iPercentage < 100 )
235 {
236 uiCurrTime = GetClock();
237 iPercentage = (uiCurrTime-uiStartTime) * 100 / uiTimeRange;
238 iPercentage = MIN( iPercentage, 100 );
239
240 //Factor the percentage so that it is modified by a gravity falling acceleration effect.
241 iFactor = (iPercentage - 50) * 2;
242 if( iPercentage < 50 )
243 iPercentage = (UINT32)(iPercentage + iPercentage * iFactor * 0.01 + 0.5);
244 else
245 iPercentage = (UINT32)(iPercentage + (100-iPercentage) * iFactor * 0.01 + 0.05);
246
247 if( iPercentage > 50 )
248 {
249 guiSAVEBUFFER->ShadowRectUsingLowPercentTable(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
250 }
251
252 SGPBox const SrcRect =
253 {
254 (UINT16) (536 * iPercentage / 100),
255 (UINT16) (367 * iPercentage / 100),
256 (UINT16) (SCREEN_WIDTH - 541 * iPercentage / 100),
257 (UINT16) (SCREEN_HEIGHT - 406 * iPercentage / 100)
258 };
259 BltStretchVideoSurface(FRAME_BUFFER, guiSAVEBUFFER, &SrcRect, &DstRect);
260 InvalidateScreen();
261 RefreshScreen();
262 }
263 }
264 FRAME_BUFFER->Fill(Get16BPPColor(FROMRGB(0, 0, 0)));
265 InvalidateScreen( );
266 RefreshScreen();
267
268 //If we are loading a saved game, use the Loading screen we saved into the SavedGameHeader file
269 // ( which gets reloaded into gubLastLoadingScreenID )
270 if( !gfGotoSectorTransition )
271 {
272 UINT8 const id = gTacticalStatus.uiFlags & LOADING_SAVED_GAME ?
273 gubLastLoadingScreenID :
274 GetLoadScreenID(gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
275 DisplayLoadScreenWithID(id);
276 }
277 }
278
279 static void InitializeMapStructure(void);
280
281 static void HandleAirspaceControlUpdated();
282
InitStrategicEngine()283 void InitStrategicEngine()
284 {
285 // this runs every time we start the application, so don't put anything in here that's only supposed to run when a new
286 // *game* is started! Those belong in InitStrategicLayer() instead.
287
288 InitializeMapStructure();
289
290 // set up town stuff
291 BuildListOfTownSectors( );
292
293 OnAirspaceControlUpdated.addListener("default", HandleAirspaceControlUpdated);
294 }
295
296
GetTownIdForSector(UINT8 const sector)297 UINT8 GetTownIdForSector(UINT8 const sector)
298 {
299 // return the name value of the town in this sector
300 return StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(sector)].bNameId;
301 }
302
303
GetTownSectorSize(INT8 const town_id)304 UINT8 GetTownSectorSize(INT8 const town_id)
305 {
306 UINT8 n = 0;
307 FOR_EACH_SECTOR_IN_TOWN(i, town_id) ++n;
308 return n;
309 }
310
311
GetTownSectorsUnderControl(INT8 const town_id)312 UINT8 GetTownSectorsUnderControl(INT8 const town_id)
313 {
314 UINT8 n = 0;
315 FOR_EACH_SECTOR_IN_TOWN(i, town_id)
316 {
317 UINT32 const x = SECTORX(i->sector);
318 UINT32 const y = SECTORY(i->sector);
319 if (StrategicMap[CALCULATE_STRATEGIC_INDEX(x, y)].fEnemyControlled) continue;
320 if (NumEnemiesInSector(x, y) != 0) continue;
321 ++n;
322 }
323 return n;
324 }
325
326
327 static void InitializeStrategicMapSectorTownNames(void);
328
329
InitializeMapStructure(void)330 static void InitializeMapStructure(void)
331 {
332 std::fill(std::begin(StrategicMap), std::end(StrategicMap), StrategicMapElement{});
333
334 InitializeStrategicMapSectorTownNames( );
335 }
336
337 // get short sector name without town name
GetShortSectorString(INT16 sMapX,INT16 sMapY)338 ST::string GetShortSectorString(INT16 sMapX, INT16 sMapY)
339 {
340 // OK, build string id like J11
341 return ST::format("{c}{}", 'A' - 1 + sMapY, sMapX);
342 }
343
344
GetMapFileName(INT16 const x,INT16 const y,INT8 const z,char * const buf,BOOLEAN const add_alternate_map_letter)345 void GetMapFileName(INT16 const x, INT16 const y, INT8 const z, char* const buf, BOOLEAN const add_alternate_map_letter)
346 {
347 size_t n = sprintf(buf, "%c%d", 'A' - 1 + y, x);
348
349 if (z != 0) n += sprintf(buf + n, "_b%d", z);
350
351 /* The gfUseAlternateMap flag is set while loading saved games. When starting
352 * a new game the underground sector info has not been initialized, so we need
353 * the flag to load an alternate sector. */
354 if (GetSectorFlagStatus(x, y, z, SF_USE_ALTERNATE_MAP) || gfUseAlternateMap)
355 {
356 gfUseAlternateMap = FALSE;
357 if (add_alternate_map_letter) n += sprintf(buf + n, "_a");
358 }
359
360 if (AreInMeanwhile() && x == 3 && y == 16 && z == 0)
361 {
362 if (add_alternate_map_letter) n += sprintf(buf + n, "_m");
363 }
364
365 sprintf(buf + n, ".dat");
366 }
367
368
HandleRPCDescriptionOfSector(INT16 const x,INT16 const y,INT16 const z)369 static void HandleRPCDescriptionOfSector(INT16 const x, INT16 const y, INT16 const z)
370 {
371 struct SectorDescriptionInfo
372 {
373 UINT8 y;
374 UINT8 x;
375 UINT8 quote;
376 };
377
378 SectorDescriptionInfo const sector_description[] =
379 {
380 { 2, 13, 0 }, // B13 Drassen
381 { 3, 13, 1 }, // C13 Drassen
382 { 4, 13, 2 }, // D13 Drassen
383 { 8, 13, 3 }, // H13 Alma
384 { 8, 14, 4 }, // H14 Alma
385 { 9, 13, 5 }, // I13 Alma (extra quote 6 if Sci-fi)
386 { 9, 14, 7 }, // I14 Alma
387 { 6, 8, 8 }, // F8 Cambria
388 { 6, 9, 9 }, // F9 Cambria
389 { 7, 8, 10 }, // G8 Cambria
390
391 { 7, 9, 11 }, // G9 Cambria
392 { 3, 6, 12 }, // C6 San Mona
393 { 3, 5, 13 }, // C5 San Mona
394 { 4, 5, 14 }, // D5 San Mona
395 { 2, 2, 15 }, // B2 Chitzena
396 { 1, 2, 16 }, // A2 Chitzena
397 { 7, 1, 17 }, // G1 Grumm
398 { 8, 1, 18 }, // H1 Grumm
399 { 7, 2, 19 }, // G2 Grumm
400 { 8, 2, 20 }, // H2 Grumm
401
402 { 9, 6, 21 }, // I6 Estoni
403 { 11, 4, 22 }, // K4 Orta
404 { 12, 11, 23 }, // L11 Balime
405 { 12, 12, 24 }, // L12 Balime
406 { 15, 3, 25 }, // O3 Meduna
407 { 16, 3, 26 }, // P3 Meduna
408 { 14, 4, 27 }, // N4 Meduna
409 { 14, 3, 28 }, // N3 Meduna
410 { 15, 4, 30 }, // O4 Meduna
411 { 10, 9, 31 }, // J9 Tixa
412
413 { 4, 15, 32 }, // D15 NE SAM
414 { 4, 2, 33 }, // D2 NW SAM
415 { 9, 8, 34 } // I8 CENTRAL SAM
416 };
417
418 TacticalStatusType& ts = gTacticalStatus;
419 // Default to false
420 ts.fCountingDownForGuideDescription = FALSE;
421
422 if (GetSectorFlagStatus(x, y, z, SF_HAVE_USED_GUIDE_QUOTE)) return;
423 if (z != 0) return;
424
425 // Check if we are in a good sector
426 FOR_EACH(SectorDescriptionInfo const, i, sector_description)
427 {
428 if (x != i->x || y != i->y) continue;
429
430 // If we're not scifi, skip some
431 if (i == §or_description[3] && !gGameOptions.fSciFi) continue;
432
433 SetSectorFlag(x, y, z, SF_HAVE_USED_GUIDE_QUOTE);
434
435 ts.fCountingDownForGuideDescription = TRUE;
436 ts.bGuideDescriptionCountDown = 4 + Random(5); // 4 to 8 tactical turns
437 ts.ubGuideDescriptionToUse = i->quote;
438 ts.bGuideDescriptionSectorX = x;
439 ts.bGuideDescriptionSectorY = y;
440
441 // Handle guide description (will be needed if a SAM one)
442 HandleRPCDescription();
443 break;
444 }
445 }
446
447
448 static void EnterSector(INT16 x, INT16 y, INT8 z);
449 enum
450 {
451 ABOUT_TO_LOAD_NEW_MAP,
452 ABOUT_TO_TRASH_WORLD,
453 };
454 static void HandleDefiniteUnloadingOfWorld(UINT8 ubUnloadCode);
455
456
SetCurrentWorldSector(INT16 const x,INT16 const y,INT8 const z)457 void SetCurrentWorldSector(INT16 const x, INT16 const y, INT8 const z)
458 {
459 SyncStrategicTurnTimes();
460
461 // is the sector already loaded?
462 if (gWorldSectorX == x && y == gWorldSectorY && z == gbWorldSectorZ)
463 {
464 /* Insert the enemies into the newly loaded map based on the strategic
465 * information. Note, the flag will return TRUE only if enemies were added.
466 * The game may wish to do something else in a case where no enemies are
467 * present. */
468
469 SetPendingNewScreen(GAME_SCREEN);
470 if (NumEnemyInSector() == 0)
471 {
472 PrepareEnemyForSectorBattle();
473 }
474 if (gubNumCreaturesAttackingTown != 0 &&
475 z == 0 &&
476 gubSectorIDOfCreatureAttack == SECTOR(x, y))
477 {
478 PrepareCreaturesForBattle();
479 }
480
481 if (gfGotoSectorTransition)
482 {
483 BeginLoadScreen();
484 gfGotoSectorTransition = FALSE;
485 }
486
487 HandleHelicopterOnGround(true);
488
489 ResetMilitia();
490 AllTeamsLookForAll(TRUE);
491 return;
492 }
493
494 if (gWorldSectorX != 0 && gWorldSectorY != 0 && gbWorldSectorZ != -1)
495 {
496 HandleDefiniteUnloadingOfWorld(ABOUT_TO_LOAD_NEW_MAP);
497 }
498
499 // make this the currently loaded sector
500 gWorldSectorX = x;
501 gWorldSectorY = y;
502 gbWorldSectorZ = z;
503
504 // update currently selected map sector to match
505 ChangeSelectedMapSector(x, y, z);
506
507 bool const loading_savegame = gTacticalStatus.uiFlags & LOADING_SAVED_GAME;
508 if (loading_savegame)
509 {
510 SetMusicMode(MUSIC_MAIN_MENU);
511 }
512 else
513 {
514 StopAnyCurrentlyTalkingSpeech();
515
516 /* Check to see if the sector we are loading is the cave sector under Tixa.
517 * If so then we will set up the meanwhile scene to start the creature
518 * quest. */
519 if (x == 9 && y == 10 && z == 2)
520 {
521 InitCreatureQuest(); // Ignored if already active.
522 }
523
524 gTacticalStatus.uiTimeSinceLastInTactical = GetWorldTotalMin();
525 InitializeTacticalStatusAtBattleStart();
526 HandleHelicopterOnGround(false);
527 }
528
529 EnterSector(x, y, z);
530
531 if (!loading_savegame)
532 {
533 InitAI();
534 ExamineDoorsOnEnteringSector();
535 }
536
537 /* Update all the doors in the sector according to the temp file previously
538 * loaded, and any changes made by the schedules */
539 UpdateDoorGraphicsFromStatus();
540
541 // Set the fact we have visited the sector
542 SetSectorFlag(x, y, z, SF_ALREADY_LOADED);
543
544 // Check for helicopter being on the ground in this sector
545 HandleHelicopterOnGround(true);
546
547 if (!loading_savegame)
548 {
549 if (gubMusicMode == MUSIC_TACTICAL_ENEMYPRESENT ?
550 NumHostilesInSector(x, y, z) == 0 :
551 gubMusicMode != MUSIC_TACTICAL_BATTLE)
552 {
553 // ATE: Fade FAST
554 SetMusicFadeSpeed(5);
555 SetMusicMode(MUSIC_TACTICAL_NOTHING);
556 }
557
558 // ATE: Check what sector we are in, to show description if we have an RPC
559 HandleRPCDescriptionOfSector(x, y, z);
560
561 // ATE: Set Flag for being visited
562 SetSectorFlag(x, y, z, SF_HAS_ENTERED_TACTICAL);
563
564 ResetMultiSelection();
565
566 gTacticalStatus.fHasEnteredCombatModeSinceEntering = FALSE;
567 gTacticalStatus.fDontAddNewCrows = FALSE;
568
569 // Adjust delay for tense quote
570 gTacticalStatus.sCreatureTenseQuoteDelay = 10 + Random(20);
571
572 INT16 sWarpWorldX;
573 INT16 sWarpWorldY;
574 INT8 bWarpWorldZ;
575 INT16 sWarpGridNo;
576 if (z >= 2 && GetWarpOutOfMineCodes(&sWarpWorldX, &sWarpWorldY, &bWarpWorldZ, &sWarpGridNo))
577 {
578 gTacticalStatus.uiFlags |= IN_CREATURE_LAIR;
579 }
580 else
581 {
582 gTacticalStatus.uiFlags &= ~IN_CREATURE_LAIR;
583 }
584
585 gTacticalStatus.ubNumCrowsPossible = 5 + Random(5);
586 }
587 }
588
589
RemoveMercsInSector()590 void RemoveMercsInSector( )
591 {
592 // ATE: only for OUR guys.. the rest is taken care of in TrashWorld() when a new sector is added...
593 FOR_EACH_IN_TEAM(i, OUR_TEAM)
594 {
595 RemoveSoldierFromGridNo(*i);
596 }
597 }
598
599
PrepareLoadedSector()600 void PrepareLoadedSector()
601 {
602 BOOLEAN fAddCivs = TRUE;
603 INT8 bMineIndex = -1;
604
605 BeforePrepareSector();
606
607 if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
608 {
609 UpdateMercsInSector();
610 }
611
612 // Reset ambients!
613 HandleNewSectorAmbience( gTilesets[ giCurrentTilesetID ].ubAmbientID );
614
615 //if we are loading a 'pristine' map ( ie, not loading a saved game )
616 if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ))
617 {
618 if ( !AreReloadingFromMeanwhile( ) )
619 {
620 SetPendingNewScreen(GAME_SCREEN);
621
622 // Make interface the team panel always...
623 SetCurrentInterfacePanel(TEAM_PANEL);
624 }
625
626
627 //Check to see if civilians should be added. Always add civs to maps unless they are
628 //in a mine that is shutdown.
629 if( gbWorldSectorZ )
630 {
631 bMineIndex = GetIdOfMineForSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
632 if( bMineIndex != -1 )
633 {
634 if( !AreThereMinersInsideThisMine( (UINT8)bMineIndex ) )
635 {
636 fAddCivs = FALSE;
637 }
638 }
639 }
640 if( fAddCivs )
641 {
642 AddSoldierInitListTeamToWorld(CIV_TEAM);
643 }
644
645 AddSoldierInitListTeamToWorld(MILITIA_TEAM);
646 AddSoldierInitListBloodcats();
647 //Creatures are only added if there are actually some of them. It has to go through some
648 //additional checking.
649
650 PrepareCreaturesForBattle();
651
652 PrepareMilitiaForTactical();
653
654 // OK, set varibles for entring this new sector...
655 gTacticalStatus.fVirginSector = TRUE;
656
657 AddProfilesNotUsingProfileInsertionData();
658
659 if( !AreInMeanwhile() || GetMeanwhileID() == INTERROGATION )
660 { // Insert the enemies into the newly loaded map based on the strategic information.
661 PrepareEnemyForSectorBattle();
662 }
663
664
665 //Regardless whether or not this was set, clear it now.
666 gfRestoringEnemySoldiersFromTempFile = FALSE;
667
668 //@@@Evaluate
669 //Add profiles to world using strategic info, not editor placements.
670 AddProfilesUsingProfileInsertionData();
671
672 PostSchedules();
673 }
674
675 CallAvailableTeamEnemiesToAmbush(gMapInformation.sCenterGridNo);
676
677 if( !( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
678 {
679 // unpause game
680 UnPauseGame( );
681 }
682
683 gpBattleGroup = NULL;
684
685 if( gfTacticalTraversal )
686 {
687 CalculateNonPersistantPBIInfo();
688 }
689
690 SLOGD("Current Time is: %d", GetWorldTotalMin() );
691
692 AllTeamsLookForAll( TRUE );
693 }
694
695 #define RANDOM_HEAD_MINERS 4
HandleQuestCodeOnSectorEntry(INT16 sNewSectorX,INT16 sNewSectorY,INT8 bNewSectorZ)696 void HandleQuestCodeOnSectorEntry( INT16 sNewSectorX, INT16 sNewSectorY, INT8 bNewSectorZ )
697 {
698 UINT8 ubRandomMiner[RANDOM_HEAD_MINERS] = { 106, 156, 157, 158 };
699 UINT8 ubMiner, ubMinersPlaced;
700
701 if ( CheckFact( FACT_ALL_TERRORISTS_KILLED, 0 ) )
702 {
703 // end terrorist quest
704 EndQuest( QUEST_KILL_TERRORISTS, gMercProfiles[ CARMEN ].sSectorX, gMercProfiles[ CARMEN ].sSectorY );
705 // remove Carmen
706 gMercProfiles[ CARMEN ].sSectorX = 0;
707 gMercProfiles[ CARMEN ].sSectorY = 0;
708 gMercProfiles[ CARMEN ].bSectorZ = 0;
709 }
710
711 UINT8 const sector = SECTOR(sNewSectorX, sNewSectorY);
712 // are we in a mine sector, on the surface?
713 if (bNewSectorZ == 0)
714 {
715 INT8 const ubThisMine = GetMineIndexForSector(sector);
716 if (ubThisMine != -1 && !CheckFact(FACT_MINERS_PLACED, 0))
717 {
718 auto thisMine = GCM->getMine(ubThisMine);
719 // SET HEAD MINER LOCATIONS, unless mine is abandoned
720 if (!thisMine->isAbandoned())
721 {
722 ubMinersPlaced = 0;
723
724 if (!thisMine->headMinerAssigned)
725 {
726 // Fred Morris is always in the first mine sector we enter, unless head miner here has been pre-determined (then he's randomized, too)
727 MERCPROFILESTRUCT& fred = GetProfile(FRED);
728 fred.sSectorX = sNewSectorX;
729 fred.sSectorY = sNewSectorY;
730 fred.bSectorZ = 0;
731 fred.bTown = thisMine->associatedTownId;
732
733 // mark miner as placed
734 ubRandomMiner[ 0 ] = 0;
735 ubMinersPlaced++;
736 }
737
738 // assign the remaining (3) miners randomly
739 for (const MineModel* ubMine : GCM->getMines())
740 {
741 if ( ubMine->mineId == ubThisMine || ubMine->headMinerAssigned || ubMine->isAbandoned() )
742 {
743 // Alma always has Matt as a miner, and we have assigned Fred to the current mine
744 // and San Mona is abandoned
745 continue;
746 }
747
748 do
749 {
750 ubMiner = (UINT8) Random( RANDOM_HEAD_MINERS );
751 }
752 while( ubRandomMiner[ ubMiner ] == 0 );
753
754 MERCPROFILESTRUCT& p = GetProfile(ubRandomMiner[ubMiner]);
755 UINT8 const sector = ubMine->entranceSector;
756 p.sSectorX = SECTORX(sector);
757 p.sSectorY = SECTORY(sector);
758 p.bSectorZ = 0;
759 p.bTown = ubMine->associatedTownId;
760
761 // mark miner as placed
762 ubRandomMiner[ ubMiner ] = 0;
763 ubMinersPlaced++;
764
765 if (ubMinersPlaced == RANDOM_HEAD_MINERS)
766 {
767 break;
768 }
769 }
770
771 SetFactTrue( FACT_MINERS_PLACED );
772 }
773 }
774 }
775
776 if (!CheckFact(FACT_ROBOT_RECRUITED_AND_MOVED, 0))
777 {
778 const SOLDIERTYPE* const pRobot = FindSoldierByProfileIDOnPlayerTeam(ROBOT);
779 if (pRobot)
780 {
781 // robot is on our team and we have changed sectors, so we can
782 // replace the robot-under-construction in Madlab's sector
783 RemoveGraphicFromTempFile( gsRobotGridNo, SEVENTHISTRUCT1, gMercProfiles[MADLAB].sSectorX, gMercProfiles[MADLAB].sSectorY, gMercProfiles[MADLAB].bSectorZ );
784 SetFactTrue( FACT_ROBOT_RECRUITED_AND_MOVED );
785 }
786 }
787
788 // Check to see if any player merc has the Chalice; if so,
789 // note it as stolen
790 CFOR_EACH_IN_TEAM(s, OUR_TEAM)
791 {
792 if (FindObj(s, CHALICE) != ITEM_NOT_FOUND)
793 {
794 SetFactTrue(FACT_CHALICE_STOLEN);
795 }
796 }
797
798 if (gubQuest[QUEST_KINGPIN_MONEY] == QUESTINPROGRESS &&
799 CheckFact(FACT_KINGPIN_CAN_SEND_ASSASSINS, 0) &&
800 GetTownIdForSector(sector) != BLANK_SECTOR &&
801 Random(10 + GetNumberOfMilitiaInSector(sNewSectorX, sNewSectorY, bNewSectorZ)) < 3)
802 {
803 DecideOnAssassin();
804 }
805
806 /*
807 if (sector == SEC_C5)
808 {
809 // reset Madame Layla counters
810 gMercProfiles[ MADAME ].bNPCData = 0;
811 gMercProfiles[ MADAME ].bNPCData2 = 0;
812 }
813 */
814
815 if (sector == SEC_C6 && gubQuest[QUEST_RESCUE_MARIA] == QUESTDONE)
816 {
817 // make sure Maria and Angel are gone
818 gMercProfiles[ MARIA ].sSectorX = 0;
819 gMercProfiles[ MARIA ].sSectorY = 0;
820 gMercProfiles[ ANGEL ].sSectorX = 0;
821 gMercProfiles[ ANGEL ].sSectorY = 0;
822 }
823
824 if (sector == SEC_D5)
825 {
826 gBoxer[0] = NULL;
827 gBoxer[1] = NULL;
828 gBoxer[2] = NULL;
829 }
830
831 if (sector == SEC_P3)
832 {
833 // heal up Elliot if he's been hurt
834 if ( gMercProfiles[ ELLIOT ].bMercStatus != MERC_IS_DEAD )
835 {
836 gMercProfiles[ ELLIOT ].bLife = gMercProfiles[ ELLIOT ].bLifeMax;
837 }
838 }
839
840 ResetOncePerConvoRecordsForAllNPCsInLoadedSector();
841 }
842
843
HandleQuestCodeOnSectorExit(INT16 sOldSectorX,INT16 sOldSectorY,INT8 bOldSectorZ)844 static void HandleQuestCodeOnSectorExit(INT16 sOldSectorX, INT16 sOldSectorY, INT8 bOldSectorZ)
845 {
846 if ( sOldSectorX == KINGPIN_MONEY_SECTOR_X && sOldSectorY == KINGPIN_MONEY_SECTOR_Y && bOldSectorZ == KINGPIN_MONEY_SECTOR_Z )
847 {
848 CheckForKingpinsMoneyMissing( TRUE );
849 }
850
851 if ( sOldSectorX == 13 && sOldSectorY == MAP_ROW_H && bOldSectorZ == 0 && CheckFact( FACT_CONRAD_SHOULD_GO, 0 ) )
852 {
853 // remove Conrad from the map
854 gMercProfiles[ CONRAD ].sSectorX = 0;
855 gMercProfiles[ CONRAD ].sSectorY = 0;
856 }
857
858 if ( sOldSectorX == HOSPITAL_SECTOR_X && sOldSectorY == HOSPITAL_SECTOR_Y && bOldSectorZ == HOSPITAL_SECTOR_Z )
859 {
860 CheckForMissingHospitalSupplies();
861 }
862
863 // reset the state of the museum alarm for Eldin's quotes
864 SetFactFalse( FACT_MUSEUM_ALARM_WENT_OFF );
865 }
866
867
SetupProfileInsertionDataForCivilians(void)868 static void SetupProfileInsertionDataForCivilians(void)
869 {
870 FOR_EACH_IN_TEAM(s, CIV_TEAM)
871 {
872 if (s->bInSector) SetupProfileInsertionDataForSoldier(s);
873 }
874 }
875
876
EnterSector(INT16 const x,INT16 const y,INT8 const z)877 static void EnterSector(INT16 const x, INT16 const y, INT8 const z)
878 {
879 PauseGame();
880 // Stop time for this frame
881 InterruptTime();
882
883 /* Setup the tactical existance of RPCs and CIVs in the last sector before
884 * moving on to a new sector. */
885 //@@@Evaluate
886 if (gfWorldLoaded) SetupProfileInsertionDataForCivilians();
887
888 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
889 {
890 // Handle NPC stuff related to changing sectors
891 HandleQuestCodeOnSectorEntry(x, y, z);
892 }
893
894 BeginLoadScreen();
895
896 /* This has to be done before loadworld, as it will remmove old gridnos if
897 * present */
898 RemoveMercsInSector();
899
900 if (!AreInMeanwhile())
901 {
902 SetSectorFlag(x, y, z, SF_ALREADY_VISITED);
903 }
904
905 CreateLoadingScreenProgressBar();
906
907 char filename[50];
908 GetMapFileName(x, y, z, filename, TRUE);
909 LoadWorld(filename);
910 LoadRadarScreenBitmap(filename);
911 // We have to add the helicopter after the sector is fully loaded
912 // to prevent that the pathfinding doenst consider its collission-grids
913 HandleHelicopterOnGround(true);
914
915 /* ATE: Moved this form above, so that we can have the benefit of changing the
916 * world BEFORE adding guys to it. */
917 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
918 {
919 try
920 { // Load the current sectors Information From the temporary files
921 LoadCurrentSectorsInformationFromTempItemsFile();
922 }
923 catch (...)
924 { /* The integrity of the temp files have been compromised. Boot out of the
925 * game after warning message. */
926 InitExitGameDialogBecauseFileHackDetected();
927 return;
928 }
929 }
930
931 RemoveLoadingScreenProgressBar();
932
933 if (gfEnterTacticalPlacementGUI)
934 {
935 SetPendingNewScreen(GAME_SCREEN);
936 InitTacticalPlacementGUI();
937 }
938 else
939 {
940 EndMapScreen(FALSE);
941 PrepareLoadedSector();
942 }
943
944 /* This function will either hide or display the tree tops, depending on the
945 * game setting */
946 SetTreeTopStateForMap();
947 }
948
949
UpdateMercsInSector()950 void UpdateMercsInSector()
951 {
952 // Remove from interface slot
953 RemoveAllPlayersFromSlot();
954
955 // Remove tactical interface stuff
956 guiPendingOverrideEvent = I_CHANGE_TO_IDLE;
957
958 //If we are in this function during the loading of a sector
959 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
960 {
961 //DONT set these values
962 SetSelectedMan(NULL);
963 gfGameScreenLocateToSoldier = TRUE;
964 }
965
966 SetAllAutoFacesInactive();
967
968 if (fUsingEdgePointsForStrategicEntry)
969 {
970 BeginMapEdgepointSearch();
971 }
972
973 UINT8 pow_squad = NO_CURRENT_SQUAD;
974 UINT8 const first_enemy = gTacticalStatus.Team[ENEMY_TEAM].bFirstID;
975 UINT8 const last_enemy = gTacticalStatus.Team[CREATURE_TEAM].bLastID;
976 INT16 const sSectorX = gWorldSectorX;
977 INT16 const sSectorY = gWorldSectorY;
978 INT8 const bSectorZ = gbWorldSectorZ;
979 for (INT32 i = 0; i != MAX_NUM_SOLDIERS; ++i)
980 {
981 if (gfRestoringEnemySoldiersFromTempFile &&
982 first_enemy <= i && i <= last_enemy)
983 { /* Don't update enemies/creatures (consec. teams) if they were just
984 * restored via the temp map files */
985 continue;
986 }
987
988 SOLDIERTYPE& s = GetMan(i);
989 RemoveMercSlot(&s);
990
991 s.bInSector = FALSE;
992
993 if (!s.bActive) continue;
994 if (s.sSectorX != sSectorX) continue;
995 if (s.sSectorY != sSectorY) continue;
996 if (s.bSectorZ != bSectorZ) continue;
997 if (s.fBetweenSectors) continue;
998
999 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
1000 {
1001 if (gMapInformation.sCenterGridNo != -1 &&
1002 gfBlitBattleSectorLocator &&
1003 s.bTeam != CIV_TEAM &&
1004 (
1005 gubEnemyEncounterCode == ENEMY_AMBUSH_CODE ||
1006 gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE
1007 ))
1008 {
1009 s.ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
1010 s.usStrategicInsertionData = gMapInformation.sCenterGridNo;
1011 }
1012 else if (gfOverrideInsertionWithExitGrid)
1013 {
1014 s.ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
1015 s.usStrategicInsertionData = gExitGrid.usGridNo;
1016 }
1017 }
1018
1019 UpdateMercInSector(s, sSectorX, sSectorY, bSectorZ);
1020
1021 if (gTacticalStatus.uiFlags & LOADING_SAVED_GAME) continue;
1022 if (s.bAssignment != ASSIGNMENT_POW) continue;
1023
1024 if (pow_squad == NO_CURRENT_SQUAD)
1025 {
1026 // ATE: If we are in i13 - pop up message!
1027 if (sSectorY == MAP_ROW_I && sSectorX == 13)
1028 {
1029 DoMessageBox(MSG_BOX_BASIC_STYLE, TacticalStr[POW_MERCS_ARE_HERE], GAME_SCREEN, MSG_BOX_FLAG_OK, NULL, NULL);
1030 }
1031 else
1032 {
1033 AddCharacterToUniqueSquad(&s);
1034 pow_squad = s.bAssignment;
1035 s.bNeutral = FALSE;
1036 }
1037 }
1038 else
1039 {
1040 if (sSectorY != MAP_ROW_I && sSectorX != 13)
1041 {
1042 AddCharacterToSquad(&s, pow_squad);
1043 }
1044 }
1045
1046 // ATE: Call actions based on what POW we are on...
1047 if (gubQuest[QUEST_HELD_IN_ALMA] == QUESTINPROGRESS)
1048 {
1049 EndQuest(QUEST_HELD_IN_ALMA, sSectorX, sSectorY);
1050 HandleNPCDoAction(0, NPC_ACTION_GRANT_EXPERIENCE_3, 0);
1051 }
1052 }
1053
1054 if (fUsingEdgePointsForStrategicEntry)
1055 {
1056 EndMapEdgepointSearch();
1057 fUsingEdgePointsForStrategicEntry = FALSE;
1058 }
1059 }
1060
1061
1062 static ST::string GetLoadedSectorString();
1063
1064
UpdateMercInSector(SOLDIERTYPE & s,INT16 const x,INT16 const y,INT8 const z)1065 void UpdateMercInSector(SOLDIERTYPE& s, INT16 const x, INT16 const y, INT8 const z)
1066 {
1067 // Determine entrance direction and get sweetspot
1068 // Some checks here must be fleshed out
1069
1070 if (!s.bActive) return;
1071
1072 if (s.bAssignment == IN_TRANSIT) return;
1073
1074 if (s.ubProfile != NO_PROFILE && GetProfile(s.ubProfile).ubMiscFlags3 & PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE)
1075 { // Override orders
1076 s.bOrders = STATIONARY;
1077 }
1078
1079 if (s.ubStrategicInsertionCode == INSERTION_CODE_PRIMARY_EDGEINDEX ||
1080 s.ubStrategicInsertionCode == INSERTION_CODE_SECONDARY_EDGEINDEX)
1081 {
1082 if (!fUsingEdgePointsForStrategicEntry)
1083 { // If we are not supposed to use this now, pick something better
1084 s.ubStrategicInsertionCode = (UINT8)s.usStrategicInsertionData;
1085 }
1086 }
1087
1088 MAPEDGEPOINT_SEARCH_FAILED:
1089 // Use insertion direction from loaded map
1090 GridNo gridno;
1091 switch (s.ubStrategicInsertionCode)
1092 {
1093 case INSERTION_CODE_NORTH: gridno = gMapInformation.sNorthGridNo; goto check_entry;
1094 case INSERTION_CODE_SOUTH: gridno = gMapInformation.sSouthGridNo; goto check_entry;
1095 case INSERTION_CODE_EAST: gridno = gMapInformation.sEastGridNo; goto check_entry;
1096 case INSERTION_CODE_WEST: gridno = gMapInformation.sWestGridNo; goto check_entry;
1097 case INSERTION_CODE_CENTER: gridno = gMapInformation.sCenterGridNo; goto check_entry;
1098 check_entry:
1099 if (gridno == -1 && !gfEditMode)
1100 { /* Strategic insertion failed because it expected to find an entry
1101 * point. This is likely a missing part of the map or possible fault in
1102 * strategic movement costs, traversal logic, etc. */
1103 ST::string sector = GetLoadedSectorString();
1104
1105 ST::string entry;
1106 if (gMapInformation.sNorthGridNo != -1)
1107 {
1108 entry = "north";
1109 gridno = gMapInformation.sNorthGridNo;
1110 }
1111 else if (gMapInformation.sEastGridNo != -1)
1112 {
1113 entry = "east";
1114 gridno = gMapInformation.sEastGridNo;
1115 }
1116 else if (gMapInformation.sSouthGridNo != -1)
1117 {
1118 entry = "south";
1119 gridno = gMapInformation.sSouthGridNo;
1120 }
1121 else if (gMapInformation.sWestGridNo != -1)
1122 {
1123 entry = "west";
1124 gridno = gMapInformation.sWestGridNo;
1125 }
1126 else if (gMapInformation.sCenterGridNo != -1)
1127 {
1128 entry = "center";
1129 gridno = gMapInformation.sCenterGridNo;
1130 }
1131 else
1132 {
1133 SLOGD("Sector %s has NO entrypoints -- using precise center of map for %s.", sector.c_str(), s.name.c_str());
1134 goto place_in_center;
1135 }
1136 ST::string no_entry;
1137 switch (s.ubStrategicInsertionCode)
1138 {
1139 case INSERTION_CODE_NORTH: no_entry = "north"; break;
1140 case INSERTION_CODE_EAST: no_entry = "east"; break;
1141 case INSERTION_CODE_SOUTH: no_entry = "south"; break;
1142 case INSERTION_CODE_WEST: no_entry = "west"; break;
1143 case INSERTION_CODE_CENTER: no_entry = "center"; break;
1144 }
1145 if (!no_entry.empty())
1146 {
1147 SLOGD("Sector %s doesn't have a %s entrypoint -- substituting %s entrypoint for %s.", sector.c_str(), no_entry.c_str(), entry.c_str(), s.name.c_str());
1148 }
1149 }
1150 break;
1151
1152 case INSERTION_CODE_GRIDNO:
1153 gridno = s.usStrategicInsertionData;
1154 break;
1155
1156 case INSERTION_CODE_PRIMARY_EDGEINDEX:
1157 {
1158 gridno = SearchForClosestPrimaryMapEdgepoint(s.sPendingActionData2, (UINT8)s.usStrategicInsertionData);
1159 SLOGD("%s's primary insertion gridno is %d using %d as initial search gridno and %d insertion code.",
1160 s.name.c_str(), gridno, s.sPendingActionData2, s.usStrategicInsertionData);
1161 if (gridno == NOWHERE)
1162 {
1163 SLOGE("Main edgepoint search failed for %s -- substituting entrypoint.", s.name.c_str());
1164 s.ubStrategicInsertionCode = (UINT8)s.usStrategicInsertionData;
1165 goto MAPEDGEPOINT_SEARCH_FAILED;
1166 }
1167 break;
1168 }
1169
1170 case INSERTION_CODE_SECONDARY_EDGEINDEX:
1171 {
1172 gridno = SearchForClosestSecondaryMapEdgepoint(s.sPendingActionData2, (UINT8)s.usStrategicInsertionData);
1173 SLOGD("%s's isolated insertion gridno is %d using %d as initial search gridno and %d insertion code.",
1174 s.name.c_str(), gridno, s.sPendingActionData2, s.usStrategicInsertionData);
1175 if (gridno == NOWHERE)
1176 {
1177 SLOGE("Isolated edgepoint search failed for %s -- substituting entrypoint.", s.name.c_str());
1178 s.ubStrategicInsertionCode = (UINT8)s.usStrategicInsertionData;
1179 goto MAPEDGEPOINT_SEARCH_FAILED;
1180 }
1181 break;
1182 }
1183
1184 case INSERTION_CODE_ARRIVING_GAME:
1185 // Are we in the start sector?
1186 if (SECTOR(x, y) == START_SECTOR && z == 0 &&
1187 SECTOR(gWorldSectorX, gWorldSectorY) == START_SECTOR && gbWorldSectorZ == 0)
1188 { // Try another location and walk into map
1189 gridno = 4379;
1190 }
1191 else
1192 {
1193 s.ubStrategicInsertionCode = INSERTION_CODE_NORTH;
1194 gridno = gMapInformation.sNorthGridNo;
1195 }
1196 break;
1197
1198 case INSERTION_CODE_CHOPPER:
1199 AddMercToHeli(&s);
1200 return;
1201
1202 default:
1203 SLOGD("Improper insertion code %d given to UpdateMercsInSector", s.ubStrategicInsertionCode);
1204 goto place_in_center;
1205 }
1206
1207 // If no insertion direction exists, this is bad!
1208 if (gridno == -1)
1209 {
1210 SLOGW("Insertion gridno for direction %d not added to map sector %d %d", s.ubStrategicInsertionCode, x, y);
1211 place_in_center:
1212 gridno = WORLD_ROWS / 2 * WORLD_COLS + WORLD_COLS / 2;
1213 }
1214
1215 s.sInsertionGridNo = gridno;
1216 AddSoldierToSector(&s);
1217 }
1218
1219
InitializeStrategicMapSectorTownNames(void)1220 static void InitializeStrategicMapSectorTownNames(void)
1221 {
1222 for (auto& element: GCM->getTowns())
1223 {
1224 auto town = element.second;
1225 for (auto sector : town->sectorIDs)
1226 {
1227 StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX(sector) ].bNameId = town->townId;
1228 }
1229 }
1230 }
1231
GetSectorLandTypeString(UINT8 const ubSectorID,UINT8 const ubSectorZ,bool const fDetailed)1232 ST::string GetSectorLandTypeString(UINT8 const ubSectorID, UINT8 const ubSectorZ, bool const fDetailed)
1233 {
1234 // first consider map secrets and SAM sites
1235 auto secret = GetMapSecretBySectorID(ubSectorID);
1236 if (ubSectorZ == 0 && secret)
1237 {
1238 UINT8 ubLandType = secret->getLandType(IsSecretFoundAt(ubSectorID));
1239 if (ubLandType != TOWN) // we will handle town sectors separately
1240 {
1241 return (secret->isSAMSite && !fDetailed)
1242 ? GCM->getLandTypeString(SAM_SITE)
1243 : GCM->getLandTypeString(ubLandType);
1244 }
1245 }
1246
1247 // special facilities
1248 if (ubSectorZ > 0 || fDetailed)
1249 {
1250 int16_t landType = GCM->getSectorLandType(ubSectorID, ubSectorZ);
1251 if (landType >= 0)
1252 {
1253 return GCM->getLandTypeString(landType);
1254 }
1255 }
1256
1257 INT8 const town_name_id = StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(ubSectorID)].bNameId;
1258 if (town_name_id != BLANK_SECTOR)
1259 { // show town name
1260 return GCM->getTownName(town_name_id);
1261 }
1262
1263 if (ubSectorZ > 0)
1264 { // any other underground sectors (not facility, not part of a mine) are creature lair
1265 return GCM->getLandTypeString(CREATURE_LAIR);
1266 }
1267
1268 // finally consider the sector traversibility
1269 UINT8 ubTraversibility = SectorInfo[ubSectorID].ubTraversability[THROUGH_STRATEGIC_MOVE];
1270 return GCM->getLandTypeString(ubTraversibility);
1271 }
1272
GetSectorIDString(INT16 x,INT16 y,INT8 z,BOOLEAN detailed)1273 ST::string GetSectorIDString(INT16 x, INT16 y, INT8 z, BOOLEAN detailed)
1274 {
1275 if (x <= 0 || y <= 0 || z < 0) /* Empty? */
1276 {
1277 return ST::null;
1278 }
1279
1280 if (z != 0)
1281 {
1282 UNDERGROUND_SECTORINFO const* const u = FindUnderGroundSector(x, y, z);
1283 if (!u || (!(u->uiFlags & SF_ALREADY_VISITED) && !gfGettingNameFromSaveLoadScreen))
1284 { // Display nothing
1285 return ST::null;
1286 }
1287 }
1288
1289 INT8 const mine_index = GetIdOfMineForSector(x, y, z);
1290 ST::string add;
1291 if (mine_index != -1)
1292 {
1293 add = GCM->getTownName(GetTownAssociatedWithMine(mine_index));
1294 if (detailed && mine_index != -1)
1295 { // Append "Mine"
1296 add += ST::format(" {}", pwMineStrings[0]);
1297 }
1298 }
1299
1300 if (add.empty())
1301 {
1302 UINT8 const sector_id = SECTOR(x, y);
1303 add = GetSectorLandTypeString(sector_id, z, detailed);
1304 }
1305
1306 return ST::format("{c}{}: {}", 'A' + y - 1, x, add);
1307 }
1308
1309
SetInsertionDataFromAdjacentMoveDirection(SOLDIERTYPE & s,UINT8 const tactical_direction,INT16 const additional_data)1310 static void SetInsertionDataFromAdjacentMoveDirection(SOLDIERTYPE& s, UINT8 const tactical_direction, INT16 const additional_data)
1311 {
1312 // Set insertion code
1313 switch (tactical_direction)
1314 {
1315 case 255:
1316 // We are using an exit grid, set insertion values
1317 EXITGRID ExitGrid;
1318 if (!GetExitGrid(additional_data, &ExitGrid))
1319 {
1320 SLOGA("No valid Exit grid can be found when one was expected: SetInsertionDataFromAdjacentMoveDirection.");
1321 }
1322 s.ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
1323 s.usStrategicInsertionData = ExitGrid.usGridNo;
1324 s.bUseExitGridForReentryDirection = TRUE;
1325 break;
1326
1327 case NORTH: s.ubStrategicInsertionCode = INSERTION_CODE_SOUTH; break;
1328 case SOUTH: s.ubStrategicInsertionCode = INSERTION_CODE_NORTH; break;
1329 case EAST: s.ubStrategicInsertionCode = INSERTION_CODE_WEST; break;
1330 case WEST: s.ubStrategicInsertionCode = INSERTION_CODE_EAST; break;
1331
1332 default: // Wrong direction given
1333 SLOGD("Improper insertion direction %d given to SetInsertionDataFromAdjacentMoveDirection", tactical_direction);
1334 s.ubStrategicInsertionCode = INSERTION_CODE_WEST;
1335 break;
1336 }
1337 }
1338
1339
GetInsertionDataFromAdjacentMoveDirection(UINT8 ubTacticalDirection,INT16 sAdditionalData)1340 static UINT8 GetInsertionDataFromAdjacentMoveDirection(UINT8 ubTacticalDirection, INT16 sAdditionalData)
1341 {
1342 UINT8 ubDirection;
1343
1344
1345 // Set insertion code
1346 switch( ubTacticalDirection )
1347 {
1348 // OK, we are using an exit grid - set insertion values...
1349
1350 case 255:
1351
1352 ubDirection = 255;
1353 break;
1354
1355 case NORTH:
1356 ubDirection = NORTH_STRATEGIC_MOVE;
1357 break;
1358 case SOUTH:
1359 ubDirection = SOUTH_STRATEGIC_MOVE;
1360 break;
1361 case EAST:
1362 ubDirection = EAST_STRATEGIC_MOVE;
1363 break;
1364 case WEST:
1365 ubDirection = WEST_STRATEGIC_MOVE;
1366 break;
1367 default:
1368 // Wrong direction given!
1369 SLOGD("Improper insertion direction %d given to GetInsertionDataFromAdjacentMoveDirection", ubTacticalDirection);
1370 ubDirection = EAST_STRATEGIC_MOVE;
1371 }
1372
1373 return( ubDirection );
1374
1375 }
1376
1377
GetStrategicInsertionDataFromAdjacentMoveDirection(UINT8 ubTacticalDirection,INT16 sAdditionalData)1378 static UINT8 GetStrategicInsertionDataFromAdjacentMoveDirection(UINT8 ubTacticalDirection, INT16 sAdditionalData)
1379 {
1380 UINT8 ubDirection;
1381
1382
1383 // Set insertion code
1384 switch( ubTacticalDirection )
1385 {
1386 // OK, we are using an exit grid - set insertion values...
1387
1388 case 255:
1389
1390 ubDirection = 255;
1391 break;
1392
1393 case NORTH:
1394 ubDirection = INSERTION_CODE_SOUTH;
1395 break;
1396 case SOUTH:
1397 ubDirection = INSERTION_CODE_NORTH;
1398 break;
1399 case EAST:
1400 ubDirection = INSERTION_CODE_WEST;
1401 break;
1402 case WEST:
1403 ubDirection = INSERTION_CODE_EAST;
1404 break;
1405 default:
1406 // Wrong direction given!
1407 SLOGD("Improper insertion direction %d given to GetStrategicInsertionDataFromAdjacentMoveDirection", ubTacticalDirection);
1408 ubDirection = EAST_STRATEGIC_MOVE;
1409 }
1410 return( ubDirection );
1411 }
1412
1413
1414 static INT16 PickGridNoNearestEdge(SOLDIERTYPE* pSoldier, UINT8 ubTacticalDirection);
1415
1416
JumpIntoAdjacentSector(UINT8 ubTacticalDirection,UINT8 ubJumpCode,INT16 sAdditionalData)1417 void JumpIntoAdjacentSector( UINT8 ubTacticalDirection, UINT8 ubJumpCode, INT16 sAdditionalData )
1418 {
1419 SOLDIERTYPE *pValidSoldier = NULL;
1420 UINT32 uiTraverseTime=0;
1421 UINT8 ubDirection = (UINT8)-1; // XXX HACK000E
1422 EXITGRID ExitGrid;
1423
1424 // Set initial selected
1425 // ATE: moved this towards top...
1426 SOLDIERTYPE* const sel = GetSelectedMan();
1427 gPreferredInitialSelectedGuy = sel;
1428
1429 if ( ubJumpCode == JUMP_ALL_LOAD_NEW || ubJumpCode == JUMP_ALL_NO_LOAD )
1430 {
1431 // TODO: Check flags to see if we can jump!
1432 // Move controllable mercs!
1433 FOR_EACH_IN_TEAM(s, OUR_TEAM)
1434 {
1435 // If we are controllable
1436 if (OkControllableMerc(s) && s->bAssignment == CurrentSquad())
1437 {
1438 pValidSoldier = s;
1439 //This now gets handled by strategic movement. It is possible that the
1440 //group won't move instantaneously.
1441 //s->sSectorX = sNewX;
1442 //s->sSectorY = sNewY;
1443
1444 ubDirection = GetInsertionDataFromAdjacentMoveDirection( ubTacticalDirection, sAdditionalData );
1445 break;
1446 }
1447 }
1448 }
1449 else if ( ( ubJumpCode == JUMP_SINGLE_LOAD_NEW || ubJumpCode == JUMP_SINGLE_NO_LOAD ) )
1450 {
1451 // Use selected soldier...
1452 // This guy should always be 1 ) selected and 2 ) close enough to exit sector to leave
1453 if (sel != NULL)
1454 {
1455 pValidSoldier = sel;
1456 ubDirection = GetInsertionDataFromAdjacentMoveDirection( ubTacticalDirection, sAdditionalData );
1457 }
1458
1459 if( ubJumpCode == JUMP_SINGLE_NO_LOAD )
1460 { // handle soldier moving by themselves
1461 HandleSoldierLeavingSectorByThemSelf( pValidSoldier );
1462 }
1463 else
1464 { // now add char to a squad all their own
1465 AddCharacterToUniqueSquad( pValidSoldier );
1466 }
1467 }
1468 else
1469 {
1470 // OK, no jump code here given...
1471 SLOGD("Improper jump code %d given to JumpIntoAdjacentSector", ubJumpCode);
1472 }
1473
1474 Assert( pValidSoldier );
1475
1476 //Now, determine the traversal time.
1477 GROUP* const pGroup = GetGroup(pValidSoldier->ubGroupID);
1478 AssertMsg( pGroup, String( "%s is not in a valid group (pSoldier->ubGroupID is %d)", pValidSoldier->name.c_str(), pValidSoldier->ubGroupID) );
1479
1480 // If we are going through an exit grid, don't get traversal direction!
1481 if ( ubTacticalDirection != 255 )
1482 {
1483 if( !gbWorldSectorZ )
1484 {
1485 uiTraverseTime = GetSectorMvtTimeForGroup( (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ), ubDirection, pGroup );
1486 }
1487 else if( gbWorldSectorZ > 0 )
1488 { //We are attempting to traverse in an underground environment. We need to use a complete different
1489 //method. When underground, all sectors are instantly adjacent.
1490 uiTraverseTime = UndergroundTacticalTraversalTime( ubDirection );
1491 }
1492 AssertMsg(uiTraverseTime != TRAVERSE_TIME_IMPOSSIBLE, "Attempting to tactically traverse to adjacent sector, despite being unable to do so.");
1493 }
1494
1495 // Alrighty, we want to do whatever our omnipotent player asked us to do
1496 // this is what the ubJumpCode is for.
1497 // Regardless of that we were asked to do, we MUST walk OFF ( Ian loves this... )
1498 // So..... let's setup our people to walk off...
1499 // We deal with a pGroup here... if an all move or a group...
1500
1501 // Setup some globals so our callback that deals when guys go off screen is handled....
1502 // Look in the handler function AllMercsHaveWalkedOffSector() below...
1503 gpAdjacentGroup = pGroup;
1504 gubAdjacentJumpCode = ubJumpCode;
1505 guiAdjacentTraverseTime = uiTraverseTime;
1506 gubTacticalDirection = ubTacticalDirection;
1507 gsAdditionalData = sAdditionalData;
1508
1509 // If normal direction, use it!
1510 if ( ubTacticalDirection != 255 )
1511 {
1512 gsAdjacentSectorX = (INT16)(gWorldSectorX + DirXIncrementer[ ubTacticalDirection ]);
1513 gsAdjacentSectorY = (INT16)(gWorldSectorY + DirYIncrementer[ ubTacticalDirection ]);
1514 gbAdjacentSectorZ = pValidSoldier->bSectorZ;
1515 }
1516 else
1517 {
1518 // Take directions from exit grid info!
1519 if ( !GetExitGrid( sAdditionalData, &ExitGrid ) )
1520 {
1521 SLOGA("Told to use exit grid at %d but one does not exist", sAdditionalData);
1522 }
1523
1524 gsAdjacentSectorX = ExitGrid.ubGotoSectorX;
1525 gsAdjacentSectorY = ExitGrid.ubGotoSectorY;
1526 gbAdjacentSectorZ = ExitGrid.ubGotoSectorZ;
1527 }
1528
1529 // Give guy(s) orders to walk off sector...
1530 if( pGroup->fPlayer )
1531 { //For player groups, update the soldier information
1532 UINT8 ubNum = 0;
1533
1534 CFOR_EACH_PLAYER_IN_GROUP(curr, pGroup)
1535 {
1536 if ( OK_CONTROLLABLE_MERC( curr->pSoldier) )
1537 {
1538 if ( ubTacticalDirection != 255 )
1539 {
1540 const INT16 sGridNo = PickGridNoNearestEdge(curr->pSoldier, ubTacticalDirection);
1541
1542 curr->pSoldier->sPreTraversalGridNo = curr->pSoldier->sGridNo;
1543
1544 if ( sGridNo != NOWHERE )
1545 {
1546 // Save wait code - this will make buddy walk off screen into oblivion
1547 curr->pSoldier->ubWaitActionToDo = 2;
1548 // This will set the direction so we know now to move into oblivion
1549 curr->pSoldier->uiPendingActionData1 = ubTacticalDirection;
1550 }
1551 else
1552 {
1553 SLOGA("Failed to get good exit location for adjacentmove");
1554 }
1555
1556 EVENT_GetNewSoldierPath( curr->pSoldier, sGridNo, WALKING );
1557
1558 }
1559 else
1560 {
1561 // Here, get closest location for exit grid....
1562 const INT16 sGridNo = FindGridNoFromSweetSpotCloseToExitGrid(curr->pSoldier, sAdditionalData, 10);
1563
1564 //curr->pSoldier->
1565 if ( sGridNo != NOWHERE )
1566 {
1567 // Save wait code - this will make buddy walk off screen into oblivion
1568 //curr->pSoldier->ubWaitActionToDo = 2;
1569 }
1570 else
1571 {
1572 SLOGA("Failed to get good exit location for adjacentmove");
1573 }
1574
1575 // Don't worry about walk off screen, just stay at gridno...
1576 curr->pSoldier->ubWaitActionToDo = 1;
1577
1578 // Set buddy go!
1579 gfPlotPathToExitGrid = TRUE;
1580 EVENT_GetNewSoldierPath( curr->pSoldier, sGridNo, WALKING );
1581 gfPlotPathToExitGrid = FALSE;
1582
1583 }
1584 ubNum++;
1585 }
1586 else
1587 {
1588 // We will remove them later....
1589 }
1590 }
1591
1592 // ATE: Do another round, removing guys from group that can't go on...
1593 BEGINNING_LOOP:
1594 CFOR_EACH_PLAYER_IN_GROUP(curr, pGroup)
1595 {
1596 if ( !OK_CONTROLLABLE_MERC( curr->pSoldier ) )
1597 {
1598 RemoveCharacterFromSquads( curr->pSoldier );
1599 goto BEGINNING_LOOP;
1600 }
1601 }
1602
1603 // OK, setup TacticalOverhead polling system that will notify us once everybody
1604 // has made it to our destination.
1605 const UINT8 action = (ubTacticalDirection == 255 ?
1606 WAIT_FOR_MERCS_TO_WALK_TO_GRIDNO : WAIT_FOR_MERCS_TO_WALKOFF_SCREEN);
1607 SetActionToDoOnceMercsGetToLocation(action, ubNum);
1608
1609 // Lock UI!
1610 guiPendingOverrideEvent = LU_BEGINUILOCK;
1611 HandleTacticalUI( );
1612 }
1613 }
1614
1615
HandleSoldierLeavingSectorByThemSelf(SOLDIERTYPE * pSoldier)1616 void HandleSoldierLeavingSectorByThemSelf( SOLDIERTYPE *pSoldier )
1617 {
1618 // soldier leaving thier squad behind, will rejoin later
1619 // if soldier in a squad, set the fact they want to return here
1620
1621 if( pSoldier->bAssignment < ON_DUTY )
1622 {
1623 RemoveCharacterFromSquads( pSoldier ); // REDUNDANT AddCharacterToUniqueSquad()
1624
1625 // are they in a group?..remove from group
1626 if( pSoldier->ubGroupID != 0 )
1627 {
1628 // remove from group
1629 RemovePlayerFromGroup(*pSoldier);
1630 pSoldier->ubGroupID = 0;
1631 }
1632 }
1633 else
1634 {
1635 // otherwise, they are on thier own, not in a squad, simply remove mvt group
1636 if( pSoldier->ubGroupID && pSoldier->bAssignment != VEHICLE )
1637 { //Can only remove groups if they aren't persistant (not in a squad or vehicle)
1638 // delete group
1639 RemoveGroup(*GetGroup(pSoldier->ubGroupID));
1640 pSoldier->ubGroupID = 0;
1641 }
1642 }
1643
1644 // set to guard
1645 AddCharacterToUniqueSquad( pSoldier );
1646
1647 if( pSoldier->ubGroupID == 0 )
1648 {
1649 // create independant group
1650 GROUP& g = *CreateNewPlayerGroupDepartingFromSector(pSoldier->sSectorX, pSoldier->sSectorY);
1651 AddPlayerToGroup(g, *pSoldier);
1652 }
1653 }
1654
1655
1656 static void DoneFadeOutExitGridSector(void);
1657 static void HandlePotentialMoraleHitForSkimmingSectors(GROUP* pGroup);
1658
1659
AllMercsWalkedToExitGrid()1660 void AllMercsWalkedToExitGrid()
1661 {
1662 BOOLEAN fDone;
1663
1664 HandlePotentialMoraleHitForSkimmingSectors( gpAdjacentGroup );
1665
1666 if( gubAdjacentJumpCode == JUMP_ALL_NO_LOAD || gubAdjacentJumpCode == JUMP_SINGLE_NO_LOAD )
1667 {
1668 Assert( gpAdjacentGroup );
1669 CFOR_EACH_PLAYER_IN_GROUP(pPlayer, gpAdjacentGroup)
1670 {
1671 SOLDIERTYPE& s = *pPlayer->pSoldier;
1672 SetInsertionDataFromAdjacentMoveDirection(s, gubTacticalDirection, gsAdditionalData);
1673 RemoveSoldierFromTacticalSector(s);
1674 }
1675
1676 SetGroupSectorValue(gsAdjacentSectorX, gsAdjacentSectorY, gbAdjacentSectorZ, *gpAdjacentGroup);
1677
1678 SetDefaultSquadOnSectorEntry( TRUE );
1679
1680 }
1681 else
1682 {
1683 //Because we are actually loading the new map, and we are physically traversing, we don't want
1684 //to bring up the prebattle interface when we arrive if there are enemies there. This flag
1685 //ignores the initialization of the prebattle interface and clears the flag.
1686 gfTacticalTraversal = TRUE;
1687 gpTacticalTraversalGroup = gpAdjacentGroup;
1688
1689 //Check for any unconcious and/or dead merc and remove them from the current squad, so that they
1690 //don't get moved to the new sector.
1691 fDone = FALSE;
1692 while( !fDone )
1693 {
1694 fDone = FALSE;
1695 const PLAYERGROUP* pPlayer = gpAdjacentGroup->pPlayerList;
1696 while( pPlayer )
1697 {
1698 if( pPlayer->pSoldier->bLife < OKLIFE )
1699 {
1700 AddCharacterToUniqueSquad( pPlayer->pSoldier );
1701 break;
1702 }
1703 pPlayer = pPlayer->next;
1704 }
1705 if( !pPlayer )
1706 {
1707 fDone = TRUE;
1708 }
1709 }
1710
1711 // OK, Set insertion direction for all these guys....
1712 Assert( gpAdjacentGroup );
1713 CFOR_EACH_PLAYER_IN_GROUP(pPlayer, gpAdjacentGroup)
1714 {
1715 SetInsertionDataFromAdjacentMoveDirection(*pPlayer->pSoldier, gubTacticalDirection, gsAdditionalData);
1716 }
1717 SetGroupSectorValue(gsAdjacentSectorX, gsAdjacentSectorY, gbAdjacentSectorZ, *gpAdjacentGroup);
1718
1719 gFadeOutDoneCallback = DoneFadeOutExitGridSector;
1720 FadeOutGameScreen( );
1721 }
1722 if( !PlayerMercsInSector( (UINT8)gsAdjacentSectorX, (UINT8)gsAdjacentSectorY, (UINT8)gbAdjacentSectorZ ) )
1723 {
1724 HandleLoyaltyImplicationsOfMercRetreat( RETREAT_TACTICAL_TRAVERSAL, gsAdjacentSectorX, gsAdjacentSectorY, gbAdjacentSectorZ );
1725 }
1726 if( gubAdjacentJumpCode == JUMP_ALL_NO_LOAD || gubAdjacentJumpCode == JUMP_SINGLE_NO_LOAD )
1727 {
1728 gfTacticalTraversal = FALSE;
1729 gpTacticalTraversalGroup = NULL;
1730 gpTacticalTraversalChosenSoldier = NULL;
1731 }
1732 }
1733
1734
SetupTacticalTraversalInformation(void)1735 static void SetupTacticalTraversalInformation(void)
1736 {
1737 INT16 sScreenX, sScreenY;
1738
1739 Assert( gpAdjacentGroup );
1740 CFOR_EACH_PLAYER_IN_GROUP(pPlayer, gpAdjacentGroup)
1741 {
1742 SOLDIERTYPE& s = *pPlayer->pSoldier;
1743
1744 SetInsertionDataFromAdjacentMoveDirection(s, gubTacticalDirection, gsAdditionalData);
1745
1746 // pass flag that this is a tactical traversal, the path built MUST go in the traversed direction even if longer!
1747 PlotPathForCharacter(s, gsAdjacentSectorX, gsAdjacentSectorY, true);
1748
1749 if( guiAdjacentTraverseTime <= 5 )
1750 {
1751 // Determine 'mirror' gridno...
1752 // Convert to absolute xy
1753 GetAbsoluteScreenXYFromMapPos(GETWORLDINDEXFROMWORLDCOORDS(s.sY, s.sX), &sScreenX, &sScreenY);
1754
1755 // Get 'mirror', depending on what direction...
1756 switch( gubTacticalDirection )
1757 {
1758 case NORTH: sScreenY = 1520; break;
1759 case SOUTH: sScreenY = 0; break;
1760 case EAST: sScreenX = 0; break;
1761 case WEST: sScreenX = 3160; break;
1762 }
1763
1764 // Convert into a gridno again.....
1765 const GridNo sNewGridNo = GetMapPosFromAbsoluteScreenXY(sScreenX, sScreenY);
1766
1767 // Save this gridNo....
1768 s.sPendingActionData2 = sNewGridNo;
1769 // Copy CODe computed earlier into data
1770 s.usStrategicInsertionData = s.ubStrategicInsertionCode;
1771 // Now use NEW code....
1772
1773 s.ubStrategicInsertionCode = CalcMapEdgepointClassInsertionCode(s.sPreTraversalGridNo);
1774
1775 if( gubAdjacentJumpCode == JUMP_SINGLE_LOAD_NEW || gubAdjacentJumpCode == JUMP_ALL_LOAD_NEW )
1776 {
1777 fUsingEdgePointsForStrategicEntry = TRUE;
1778 }
1779 }
1780 }
1781 if( gubAdjacentJumpCode == JUMP_ALL_NO_LOAD || gubAdjacentJumpCode == JUMP_SINGLE_NO_LOAD )
1782 {
1783 gfTacticalTraversal = FALSE;
1784 gpTacticalTraversalGroup = NULL;
1785 gpTacticalTraversalChosenSoldier = NULL;
1786 }
1787 }
1788
1789
1790 static void DoneFadeOutAdjacentSector(void);
1791
1792
AllMercsHaveWalkedOffSector()1793 void AllMercsHaveWalkedOffSector( )
1794 {
1795 BOOLEAN fEnemiesInLoadedSector = FALSE;
1796
1797 if( NumEnemiesInAnySector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ) )
1798 {
1799 fEnemiesInLoadedSector = TRUE;
1800 }
1801
1802 if (fEnemiesInLoadedSector)
1803 {
1804 HandleLoyaltyImplicationsOfMercRetreat( RETREAT_TACTICAL_TRAVERSAL, gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
1805 }
1806
1807 //Setup strategic traversal information
1808 if( guiAdjacentTraverseTime <= 5 )
1809 {
1810 gfTacticalTraversal = TRUE;
1811 gpTacticalTraversalGroup = gpAdjacentGroup;
1812
1813 if( gbAdjacentSectorZ > 0 && guiAdjacentTraverseTime <= 5 )
1814 { //Nasty strategic movement logic doesn't like underground sectors!
1815 gfUndergroundTacticalTraversal = TRUE;
1816 }
1817 }
1818 ClearMercPathsAndWaypointsForAllInGroup(*gpAdjacentGroup);
1819 AddWaypointToPGroup( gpAdjacentGroup, (UINT8)gsAdjacentSectorX, (UINT8)gsAdjacentSectorY );
1820 if( gbAdjacentSectorZ > 0 && guiAdjacentTraverseTime <= 5 )
1821 { //Nasty strategic movement logic doesn't like underground sectors!
1822 gfUndergroundTacticalTraversal = TRUE;
1823 }
1824
1825 SetupTacticalTraversalInformation();
1826
1827 // ATE: Added here: donot load another screen if we were told not to....
1828 if( ( gubAdjacentJumpCode == JUMP_ALL_NO_LOAD || gubAdjacentJumpCode == JUMP_SINGLE_NO_LOAD ) )
1829 { //Case 1: Group is leaving sector, but there are other mercs in sector and player wants to stay, or
1830 // there are other mercs in sector while a battle is in progress.
1831 CFOR_EACH_PLAYER_IN_GROUP(pPlayer, gpAdjacentGroup)
1832 {
1833 RemoveSoldierFromTacticalSector(*pPlayer->pSoldier);
1834 }
1835 SetDefaultSquadOnSectorEntry( TRUE );
1836 }
1837 else
1838 {
1839 if( fEnemiesInLoadedSector )
1840 { //We are retreating from a sector with enemies in it and there are no mercs left so
1841 //warp the game time by 5 minutes to simulate the actual retreat. This restricts the
1842 //player from immediately coming back to the same sector they left to perhaps take advantage
1843 //of the tactical placement gui to get into better position. Additionally, if there are any
1844 //enemies in this sector that are part of a movement group, reset that movement group so that they
1845 //are "in" the sector rather than 75% of the way to the next sector if that is the case.
1846 ResetMovementForEnemyGroupsInLocation( (UINT8)gWorldSectorX, (UINT8)gWorldSectorY );
1847
1848 if( guiAdjacentTraverseTime > 5 )
1849 {
1850 //Because this final group is retreating, simulate extra time to retreat, so they can't immediately come back.
1851 WarpGameTime( 300, WARPTIME_NO_PROCESSING_OF_EVENTS );
1852 }
1853 }
1854 if( guiAdjacentTraverseTime <= 5 )
1855 {
1856 //Case 2: Immediatly loading the next sector
1857 if( !gbAdjacentSectorZ )
1858 {
1859 UINT32 uiWarpTime;
1860 uiWarpTime = (GetWorldTotalMin() + 5) * 60 - GetWorldTotalSeconds();
1861 WarpGameTime( uiWarpTime, WARPTIME_PROCESS_TARGET_TIME_FIRST );
1862 }
1863 else if( gbAdjacentSectorZ > 0 )
1864 {
1865 UINT32 uiWarpTime;
1866 uiWarpTime = (GetWorldTotalMin() + 1) * 60 - GetWorldTotalSeconds();
1867 WarpGameTime( uiWarpTime, WARPTIME_PROCESS_TARGET_TIME_FIRST );
1868 }
1869
1870 //Because we are actually loading the new map, and we are physically traversing, we don't want
1871 //to bring up the prebattle interface when we arrive if there are enemies there. This flag
1872 //ignores the initialization of the prebattle interface and clears the flag.
1873 gFadeOutDoneCallback = DoneFadeOutAdjacentSector;
1874 FadeOutGameScreen( );
1875 }
1876 else
1877 { //Case 3: Going directly to mapscreen
1878
1879 //Lock game into mapscreen mode, but after the fade is done.
1880 gfEnteringMapScreen = TRUE;
1881
1882 // ATE; Fade FAST....
1883 SetMusicFadeSpeed( 5 );
1884 SetMusicMode( MUSIC_TACTICAL_NOTHING );
1885 }
1886 }
1887 }
1888
1889
DoneFadeOutExitGridSector(void)1890 static void DoneFadeOutExitGridSector(void)
1891 {
1892 SetCurrentWorldSector( gsAdjacentSectorX, gsAdjacentSectorY, gbAdjacentSectorZ );
1893 if( gfTacticalTraversal && gpTacticalTraversalGroup && gpTacticalTraversalChosenSoldier )
1894 {
1895 if( gTacticalStatus.fEnemyInSector )
1896 {
1897 TacticalCharacterDialogue(gpTacticalTraversalChosenSoldier, QUOTE_ENEMY_PRESENCE);
1898 }
1899 }
1900 gfTacticalTraversal = FALSE;
1901 gpTacticalTraversalGroup = NULL;
1902 gpTacticalTraversalChosenSoldier = NULL;
1903 FadeInGameScreen( );
1904 }
1905
1906
1907 static INT16 PickGridNoToWalkIn(SOLDIERTYPE* pSoldier, UINT8 ubInsertionDirection, UINT32* puiNumAttempts);
1908
1909
DoneFadeOutAdjacentSector(void)1910 static void DoneFadeOutAdjacentSector(void)
1911 {
1912 UINT8 ubDirection;
1913 SetCurrentWorldSector( gsAdjacentSectorX, gsAdjacentSectorY, gbAdjacentSectorZ );
1914
1915 ubDirection = GetStrategicInsertionDataFromAdjacentMoveDirection( gubTacticalDirection, gsAdditionalData );
1916 if( gfTacticalTraversal && gpTacticalTraversalGroup && gpTacticalTraversalChosenSoldier )
1917 {
1918 if( gTacticalStatus.fEnemyInSector )
1919 {
1920 TacticalCharacterDialogue(gpTacticalTraversalChosenSoldier, QUOTE_ENEMY_PRESENCE);
1921 }
1922 }
1923 gfTacticalTraversal = FALSE;
1924 gpTacticalTraversalGroup = NULL;
1925 gpTacticalTraversalChosenSoldier = NULL;
1926
1927 if ( gfCaves )
1928 {
1929 // ATE; Set tactical status flag...
1930 gTacticalStatus.uiFlags |= IGNORE_ALL_OBSTACLES;
1931 // Set pathing flag to path through anything....
1932 gfPathAroundObstacles = FALSE;
1933 }
1934
1935 // OK, give our guys new orders...
1936 if( gpAdjacentGroup->fPlayer )
1937 {
1938 //For player groups, update the soldier information
1939 UINT32 uiAttempts;
1940 INT16 sGridNo, sOldGridNo;
1941 UINT8 ubNum = 0;
1942 CFOR_EACH_PLAYER_IN_GROUP(curr, gpAdjacentGroup)
1943 {
1944 if (curr->pSoldier->sGridNo != NOWHERE)
1945 {
1946 sGridNo = PickGridNoToWalkIn(curr->pSoldier, ubDirection, &uiAttempts);
1947
1948 //If the search algorithm failed due to too many attempts, simply reset the
1949 //the gridno as the destination is a reserved gridno and we will place the
1950 //merc there without walking into the sector.
1951 if (sGridNo == NOWHERE && uiAttempts == MAX_ATTEMPTS)
1952 {
1953 sGridNo = curr->pSoldier->sGridNo;
1954 }
1955
1956 if (sGridNo != NOWHERE)
1957 {
1958 curr->pSoldier->ubWaitActionToDo = 1;
1959 // OK, here we have been given a position, a gridno has been given to use as well....
1960 sOldGridNo = curr->pSoldier->sGridNo;
1961 EVENT_SetSoldierPosition(curr->pSoldier, sGridNo, SSP_NONE);
1962 if (sGridNo != sOldGridNo)
1963 {
1964 EVENT_GetNewSoldierPath(curr->pSoldier, sOldGridNo, WALKING);
1965 }
1966 ubNum++;
1967 }
1968 }
1969 else
1970 {
1971 SLOGW("%s's gridno is NOWHERE, and is attempting to walk into sector.", curr->pSoldier->name.c_str());
1972 }
1973 }
1974 SetActionToDoOnceMercsGetToLocation(WAIT_FOR_MERCS_TO_WALKON_SCREEN, ubNum);
1975 guiPendingOverrideEvent = LU_BEGINUILOCK;
1976 HandleTacticalUI( );
1977
1978 // Unset flag here.....
1979 gfPathAroundObstacles = TRUE;
1980
1981 }
1982 FadeInGameScreen( );
1983 }
1984
1985
SoldierOKForSectorExit(SOLDIERTYPE * pSoldier,INT8 bExitDirection,UINT16 usAdditionalData)1986 static BOOLEAN SoldierOKForSectorExit(SOLDIERTYPE* pSoldier, INT8 bExitDirection, UINT16 usAdditionalData)
1987 {
1988 INT16 sWorldX;
1989 INT16 sWorldY;
1990
1991 // if the soldiers gridno is not NOWHERE
1992 if( pSoldier->sGridNo == NOWHERE )
1993 return( FALSE );
1994
1995 // OK, anyone on roofs cannot!
1996 if ( pSoldier->bLevel > 0 )
1997 return( FALSE );
1998
1999 // Get screen coordinates for current position of soldier
2000 GetAbsoluteScreenXYFromMapPos(pSoldier->sGridNo, &sWorldX, &sWorldY);
2001
2002 // Check direction
2003 switch( bExitDirection )
2004 {
2005 case EAST_STRATEGIC_MOVE:
2006
2007 if ( sWorldX < ( ( gsRightX - gsLeftX ) - CHECK_DIR_X_DELTA ) )
2008 {
2009 // NOT OK, return FALSE
2010 return( FALSE );
2011 }
2012 break;
2013
2014 case WEST_STRATEGIC_MOVE:
2015
2016 if ( sWorldX > CHECK_DIR_X_DELTA )
2017 {
2018 // NOT OK, return FALSE
2019 return( FALSE );
2020 }
2021 break;
2022
2023 case SOUTH_STRATEGIC_MOVE:
2024
2025 if ( sWorldY < ( ( gsBottomY - gsTopY ) - CHECK_DIR_Y_DELTA ) )
2026 {
2027 // NOT OK, return FALSE
2028 return( FALSE );
2029 }
2030 break;
2031
2032 case NORTH_STRATEGIC_MOVE:
2033
2034 if ( sWorldY > CHECK_DIR_Y_DELTA )
2035 {
2036 // NOT OK, return FALSE
2037 return( FALSE );
2038 }
2039 break;
2040
2041 // This case is for an exit grid....
2042 // check if we are close enough.....
2043
2044 case -1:
2045
2046
2047 // FOR REALTIME - DO MOVEMENT BASED ON STANCE!
2048 if (!(gTacticalStatus.uiFlags & INCOMBAT))
2049 {
2050 pSoldier->usUIMovementMode = GetMoveStateBasedOnStance( pSoldier, gAnimControl[ pSoldier->usAnimState ].ubEndHeight );
2051 }
2052
2053 const INT16 sGridNo = FindGridNoFromSweetSpotCloseToExitGrid(pSoldier, usAdditionalData, 10);
2054 if ( sGridNo == NOWHERE )
2055 {
2056 return( FALSE );
2057 }
2058
2059 // ATE: if we are in combat, get cost to move here....
2060 if ( gTacticalStatus.uiFlags & INCOMBAT )
2061 {
2062 // Turn off at end of function...
2063 const INT16 sAPs = PlotPath(pSoldier, sGridNo, NO_COPYROUTE, NO_PLOT, pSoldier->usUIMovementMode, pSoldier->bActionPoints);
2064 if ( !EnoughPoints( pSoldier, sAPs, 0, FALSE ) )
2065 {
2066 return( FALSE );
2067 }
2068 }
2069 break;
2070
2071 }
2072 return( TRUE );
2073 }
2074
2075 //ATE: Returns FALSE if NOBODY is close enough, 1 if ONLY selected guy is and 2 if all on squad are...
OKForSectorExit(INT8 bExitDirection,UINT16 usAdditionalData,UINT32 * puiTraverseTimeInMinutes)2076 BOOLEAN OKForSectorExit( INT8 bExitDirection, UINT16 usAdditionalData, UINT32 *puiTraverseTimeInMinutes )
2077 {
2078 BOOLEAN fAtLeastOneMercControllable = FALSE;
2079 BOOLEAN fOnlySelectedGuy = FALSE;
2080 SOLDIERTYPE *pValidSoldier = NULL;
2081 UINT8 ubReturnVal = FALSE;
2082 UINT8 ubNumControllableMercs = 0;
2083 UINT8 ubNumMercs = 0, ubNumEPCs = 0;
2084 UINT8 ubPlayerControllableMercsInSquad = 0;
2085
2086 const SOLDIERTYPE* const sel = GetSelectedMan();
2087 // must have a selected soldier to be allowed to tactically traverse.
2088 if (sel == NULL) return FALSE;
2089
2090 /*
2091 //Exception code for the two sectors in San Mona that are separated by a cliff. We want to allow strategic
2092 //traversal, but NOT tactical traversal. The only way to tactically go from D4 to D5 (or viceversa) is to enter
2093 //the cave entrance.
2094 if( gWorldSectorX == 4 && gWorldSectorY == 4 && !gbWorldSectorZ && bExitDirection == EAST_STRATEGIC_MOVE )
2095 {
2096 gfInvalidTraversal = TRUE;
2097 return FALSE;
2098 }
2099 if( gWorldSectorX == 5 && gWorldSectorY == 4 && !gbWorldSectorZ && bExitDirection == WEST_STRATEGIC_MOVE )
2100 {
2101 gfInvalidTraversal = TRUE;
2102 return FALSE;
2103 }
2104 */
2105
2106 gfInvalidTraversal = FALSE;
2107 gfLoneEPCAttemptingTraversal = FALSE;
2108 gubLoneMercAttemptingToAbandonEPCs = 0;
2109 gPotentiallyAbandonedEPC = NULL;
2110
2111 // Look through all mercs and check if they are within range of east end....
2112 FOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
2113 {
2114 // If we are controllable
2115 if (OkControllableMerc(pSoldier) && pSoldier->bAssignment == CurrentSquad())
2116 {
2117 //Need to keep a copy of a good soldier, so we can access it later, and
2118 //not more than once.
2119 pValidSoldier = pSoldier;
2120
2121 ubNumControllableMercs++;
2122
2123 //We need to keep track of the number of EPCs and mercs in this squad. If we have
2124 //only one merc and one or more EPCs, then we can't allow the merc to tactically traverse,
2125 //if he is the only merc near enough to traverse.
2126 if( AM_AN_EPC( pSoldier ) )
2127 {
2128 ubNumEPCs++;
2129 //Also record the EPC's slot ID incase we later build a string using the EPC's name.
2130 gPotentiallyAbandonedEPC = pSoldier;
2131 if( AM_A_ROBOT( pSoldier ) && !CanRobotBeControlled( pSoldier ) )
2132 {
2133 gfRobotWithoutControllerAttemptingTraversal = TRUE;
2134 ubNumControllableMercs--;
2135 continue;
2136 }
2137 }
2138 else
2139 {
2140 ubNumMercs++;
2141 }
2142
2143 if ( SoldierOKForSectorExit( pSoldier, bExitDirection, usAdditionalData ) )
2144 {
2145 fAtLeastOneMercControllable++;
2146
2147 if (pSoldier == sel) fOnlySelectedGuy = TRUE;
2148 }
2149 else
2150 {
2151 GROUP *pGroup;
2152
2153 // ATE: Dont's assume exit grids here...
2154 if ( bExitDirection != -1 )
2155 {
2156 //Now, determine if this is a valid path.
2157 pGroup = GetGroup( pValidSoldier->ubGroupID );
2158 AssertMsg( pGroup, String( "%s is not in a valid group (pSoldier->ubGroupID is %d)", pValidSoldier->name.c_str(), pValidSoldier->ubGroupID) );
2159 UINT32 traverse_time = TRAVERSE_TIME_IMPOSSIBLE;
2160 if( !gbWorldSectorZ )
2161 {
2162 traverse_time = GetSectorMvtTimeForGroup((UINT8)SECTOR(pGroup->ubSectorX, pGroup->ubSectorY), bExitDirection, pGroup);
2163 }
2164 else if( gbWorldSectorZ > 1 )
2165 { //We are attempting to traverse in an underground environment. We need to use a complete different
2166 //method. When underground, all sectors are instantly adjacent.
2167 traverse_time = UndergroundTacticalTraversalTime(bExitDirection);
2168 }
2169 if (puiTraverseTimeInMinutes) *puiTraverseTimeInMinutes = traverse_time;
2170 if (traverse_time == TRAVERSE_TIME_IMPOSSIBLE)
2171 {
2172 gfInvalidTraversal = TRUE;
2173 return FALSE;
2174 }
2175 }
2176 else
2177 {
2178 // Exit grid travel is instantaneous
2179 if (puiTraverseTimeInMinutes) *puiTraverseTimeInMinutes = 0;
2180 }
2181 }
2182 }
2183 }
2184
2185 // If we are here, at least one guy is controllable in this sector, at least he can go!
2186 if( fAtLeastOneMercControllable )
2187 {
2188 ubPlayerControllableMercsInSquad = (UINT8)NumberOfPlayerControllableMercsInSquad(sel->bAssignment);
2189 if( fAtLeastOneMercControllable <= ubPlayerControllableMercsInSquad )
2190 { //if the selected merc is an EPC and we can only leave with that merc, then prevent it
2191 //as EPCs aren't allowed to leave by themselves. Instead of restricting this in the
2192 //exiting sector gui, we restrict it by explaining it with a message box.
2193 if (AM_AN_EPC(sel))
2194 {
2195 if (fAtLeastOneMercControllable < ubPlayerControllableMercsInSquad || fAtLeastOneMercControllable == 1)
2196 {
2197 gfLoneEPCAttemptingTraversal = TRUE;
2198 return FALSE;
2199 }
2200 }
2201 else
2202 { //We previously counted the number of EPCs and mercs, and if the selected merc is not an EPC and there are no
2203 //other mercs in the squad able to escort the EPCs, we will prohibit this merc from tactically traversing.
2204 if( ubNumEPCs && ubNumMercs == 1 && fAtLeastOneMercControllable < ubPlayerControllableMercsInSquad )
2205 {
2206 gubLoneMercAttemptingToAbandonEPCs = ubNumEPCs;
2207 return FALSE;
2208 }
2209 }
2210 }
2211 if ( bExitDirection != -1 )
2212 {
2213 GROUP *pGroup;
2214 //Now, determine if this is a valid path.
2215 pGroup = GetGroup( pValidSoldier->ubGroupID );
2216 AssertMsg( pGroup, String( "%s is not in a valid group (pSoldier->ubGroupID is %d)", pValidSoldier->name.c_str(), pValidSoldier->ubGroupID) );
2217 UINT32 traverse_time = TRAVERSE_TIME_IMPOSSIBLE; // -XXX Wmaybe-uninitialized : valid default?
2218 if( !gbWorldSectorZ )
2219 {
2220 traverse_time = GetSectorMvtTimeForGroup((UINT8)SECTOR(pGroup->ubSectorX, pGroup->ubSectorY), bExitDirection, pGroup);
2221 }
2222 else if( gbWorldSectorZ > 0 )
2223 { //We are attempting to traverse in an underground environment. We need to use a complete different
2224 //method. When underground, all sectors are instantly adjacent.
2225 traverse_time = UndergroundTacticalTraversalTime(bExitDirection);
2226 }
2227 if (puiTraverseTimeInMinutes) *puiTraverseTimeInMinutes = traverse_time;
2228 if (traverse_time == TRAVERSE_TIME_IMPOSSIBLE)
2229 {
2230 gfInvalidTraversal = TRUE;
2231 ubReturnVal = FALSE;
2232 }
2233 else
2234 {
2235 ubReturnVal = TRUE;
2236 }
2237 }
2238 else
2239 {
2240 ubReturnVal = TRUE;
2241 // Exit grid travel is instantaneous
2242 if (puiTraverseTimeInMinutes) *puiTraverseTimeInMinutes = 0;
2243 }
2244 }
2245
2246 if ( ubReturnVal )
2247 {
2248 // Default to FALSE again, until we see that we have
2249 ubReturnVal = FALSE;
2250
2251 if ( fAtLeastOneMercControllable )
2252 {
2253 // Do we contain the selected guy?
2254 if ( fOnlySelectedGuy )
2255 {
2256 ubReturnVal = 1;
2257 }
2258 // Is the whole squad able to go here?
2259 if ( fAtLeastOneMercControllable == ubPlayerControllableMercsInSquad )
2260 {
2261 ubReturnVal = 2;
2262 }
2263 }
2264 }
2265
2266 return( ubReturnVal );
2267 }
2268
2269
SetupNewStrategicGame()2270 void SetupNewStrategicGame()
2271 {
2272 // Set all sectors as enemy controlled.
2273 FOR_EACH(StrategicMapElement, i, StrategicMap)
2274 {
2275 i->fEnemyControlled = TRUE;
2276 }
2277
2278 InitNewGameClock();
2279 DeleteAllStrategicEvents();
2280
2281 // Set up all events that get processed daily.
2282 BuildDayLightLevels();
2283 // Check for quests each morning.
2284 AddEveryDayStrategicEvent(EVENT_CHECKFORQUESTS, QUEST_CHECK_EVENT_TIME, 0);
2285 // Some things get updated in the very early morning.
2286 AddEveryDayStrategicEvent(EVENT_DAILY_EARLY_MORNING_EVENTS, EARLY_MORNING_TIME, 0);
2287 // Daily update BobbyRay Inventory.
2288 AddEveryDayStrategicEvent(EVENT_DAILY_UPDATE_BOBBY_RAY_INVENTORY, BOBBYRAY_UPDATE_TIME, 0);
2289 // Daily update of the M.E.R.C. site..
2290 AddEveryDayStrategicEvent(EVENT_DAILY_UPDATE_OF_MERC_SITE, 0, 0);
2291 // Daily update of insured mercs.
2292 AddEveryDayStrategicEvent(EVENT_HANDLE_INSURED_MERCS, INSURANCE_UPDATE_TIME, 0);
2293 // Daily update of mercs.
2294 AddEveryDayStrategicEvent(EVENT_MERC_DAILY_UPDATE, 0, 0);
2295 // Daily mine production processing events.
2296 AddEveryDayStrategicEvent(EVENT_SETUP_MINE_INCOME, 0, 0);
2297 // Daily checks for E-mail from Enrico.
2298 AddEveryDayStrategicEvent(EVENT_ENRICO_MAIL, ENRICO_MAIL_TIME, 0);
2299
2300 // Hourly update of all sorts of things
2301 AddPeriodStrategicEvent(EVENT_HOURLY_UPDATE, 60, 0);
2302 AddPeriodStrategicEvent(EVENT_QUARTER_HOUR_UPDATE, 15, 0);
2303
2304 // Clear any possible battle locator.
2305 gfBlitBattleSectorLocator = FALSE;
2306
2307 StrategicTurnsNewGame();
2308
2309 // Move the landing zone over to the start sector.
2310 g_merc_arrive_sector = START_SECTOR;
2311 }
2312
2313
CanGoToTacticalInSector(INT16 const x,INT16 const y,UINT8 const z)2314 bool CanGoToTacticalInSector(INT16 const x, INT16 const y, UINT8 const z)
2315 {
2316 // If not a valid sector
2317 if (x < 1 || 16 < x) return false;
2318 if (y < 1 || 16 < x) return false;
2319 if ( 3 < z) return false;
2320
2321 /* Look for all living, fighting mercs on player's team. Robot and EPCs
2322 * qualify! */
2323 CFOR_EACH_IN_TEAM(i, OUR_TEAM)
2324 {
2325 SOLDIERTYPE const& s = *i;
2326 /* ARM: Now allows loading of sector with all mercs below OKLIFE as long as
2327 * they're alive */
2328 if (s.bLife == 0) continue;
2329 if (s.uiStatusFlags & SOLDIER_VEHICLE) continue;
2330 if (s.bAssignment == IN_TRANSIT) continue;
2331 if (s.bAssignment == ASSIGNMENT_POW) continue;
2332 if (s.bAssignment == ASSIGNMENT_DEAD) continue;
2333 if (SoldierAboardAirborneHeli(s)) continue;
2334 if (s.fBetweenSectors) continue;
2335 if (s.sSectorX != x) continue;
2336 if (s.sSectorY != y) continue;
2337 if (s.bSectorZ != z) continue;
2338 return true;
2339 }
2340
2341 return false;
2342 }
2343
HandleAirspaceControlUpdated()2344 static void HandleAirspaceControlUpdated()
2345 {
2346 // check if currently selected arrival sector still has secure airspace
2347
2348 // if it's not enemy air controlled
2349 if (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(g_merc_arrive_sector)].fEnemyAirControlled)
2350 {
2351 // get the name of the old sector
2352 ST::string sMsgSubString1 = GetSectorIDString(SECTORX(g_merc_arrive_sector), SECTORY(g_merc_arrive_sector), 0, FALSE);
2353
2354 // Move the landing zone over to the start sector.
2355 g_merc_arrive_sector = START_SECTOR;
2356
2357 // get the name of the new sector
2358 ST::string sMsgSubString2 = GetSectorIDString(SECTORX(g_merc_arrive_sector), SECTORY(g_merc_arrive_sector), 0, FALSE);
2359
2360 // now build the string
2361 ST::string sMsgString = st_format_printf(pBullseyeStrings[ 4 ], sMsgSubString1, sMsgSubString2);
2362
2363 // confirm the change with overlay message
2364 DoScreenIndependantMessageBox(sMsgString, MSG_BOX_FLAG_OK, NULL);
2365
2366 // update position of bullseye
2367 fMapPanelDirty = TRUE;
2368
2369 // update destination column for any mercs in transit
2370 fTeamPanelDirty = TRUE;
2371 }
2372
2373
2374 // ARM: airspace control now affects refueling site availability, so update that too with every change!
2375 UpdateRefuelSiteAvailability();
2376 }
2377
SaveStrategicInfoToSavedFile(HWFILE const f)2378 void SaveStrategicInfoToSavedFile(HWFILE const f)
2379 {
2380 // Save the strategic map information
2381 FOR_EACH(StrategicMapElement const, i, StrategicMap)
2382 {
2383 InjectStrategicMapElementIntoFile(f, *i);
2384 }
2385
2386 // Save the Sector Info
2387 FOR_EACH(SECTORINFO const, i, SectorInfo)
2388 {
2389 InjectSectorInfoIntoFile(f, *i);
2390 }
2391
2392 // Skip the SAM controlled sector information
2393 FileSeek(f, MAP_WORLD_X * MAP_WORLD_Y, FILE_SEEK_FROM_CURRENT);
2394
2395 // Save the state of the 2nd map secret (fFoundOrta)
2396 BOOLEAN fFound = GetMapSecretStateForSave(1);
2397 FileWrite(f, &fFound, sizeof(BOOLEAN));
2398 }
2399
2400
LoadStrategicInfoFromSavedFile(HWFILE const f)2401 void LoadStrategicInfoFromSavedFile(HWFILE const f)
2402 {
2403 // Load the strategic map information
2404 FOR_EACH (StrategicMapElement, i, StrategicMap)
2405 {
2406 ExtractStrategicMapElementFromFile(f, *i);
2407 }
2408
2409 // Load the Sector Info
2410 FOR_EACH(SECTORINFO, i, SectorInfo)
2411 {
2412 ExtractSectorInfoFromFile(f, *i);
2413 }
2414
2415 // Skip the SAM controlled sector information
2416 FileSeek(f, MAP_WORLD_X * MAP_WORLD_Y, FILE_SEEK_FROM_CURRENT);
2417
2418 // Load state of the 2nd map secret (fFoundOrta)
2419 BOOLEAN fFound;
2420 FileRead(f, &fFound, sizeof(BOOLEAN));
2421 SetMapSecretStateFromSave(1, fFound);
2422 }
2423
2424
PickGridNoNearestEdge(SOLDIERTYPE * pSoldier,UINT8 ubTacticalDirection)2425 static INT16 PickGridNoNearestEdge(SOLDIERTYPE* pSoldier, UINT8 ubTacticalDirection)
2426 {
2427 INT16 sGridNo, sStartGridNo, sOldGridNo;
2428 INT8 bOdd = 1, bOdd2 = 1;
2429 UINT8 bAdjustedDist = 0;
2430 UINT32 cnt;
2431
2432 switch( ubTacticalDirection )
2433 {
2434
2435 case EAST:
2436
2437 sGridNo = pSoldier->sGridNo;
2438 sStartGridNo = pSoldier->sGridNo;
2439 sOldGridNo = pSoldier->sGridNo;
2440
2441 // Move directly to the right!
2442 while( GridNoOnVisibleWorldTile( sGridNo ) )
2443 {
2444 sOldGridNo = sGridNo;
2445
2446 if ( bOdd )
2447 {
2448 sGridNo -= WORLD_COLS;
2449 }
2450 else
2451 {
2452 sGridNo++;
2453 }
2454
2455 bOdd = (INT8)!bOdd;
2456 }
2457
2458 sGridNo = sOldGridNo;
2459 sStartGridNo = sOldGridNo;
2460
2461 do
2462 {
2463 // OK, here we go back one, check for OK destination...
2464 if ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) )
2465 {
2466 return( sGridNo );
2467 }
2468
2469 // If here, try another place!
2470 // ( alternate up/down )
2471 if ( bOdd2 )
2472 {
2473 bAdjustedDist++;
2474
2475 sGridNo = sStartGridNo;
2476
2477 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2478 {
2479 sGridNo = (INT16)(sGridNo - WORLD_COLS - 1);
2480 }
2481 }
2482 else
2483 {
2484 sGridNo = sStartGridNo;
2485
2486 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2487 {
2488 sGridNo = (INT16)(sGridNo + WORLD_COLS + 1);
2489 }
2490 }
2491
2492 bOdd2 = (INT8)(!bOdd2);
2493
2494 } while( TRUE );
2495
2496 case WEST:
2497
2498 sGridNo = pSoldier->sGridNo;
2499 sStartGridNo = pSoldier->sGridNo;
2500 sOldGridNo = pSoldier->sGridNo;
2501
2502 // Move directly to the left!
2503 while( GridNoOnVisibleWorldTile( sGridNo ) )
2504 {
2505 sOldGridNo = sGridNo;
2506
2507 if ( bOdd )
2508 {
2509 sGridNo += WORLD_COLS;
2510 }
2511 else
2512 {
2513 sGridNo--;
2514 }
2515
2516 bOdd = (INT8)!bOdd;
2517 }
2518
2519 sGridNo = sOldGridNo;
2520 sStartGridNo = sOldGridNo;
2521
2522 do
2523 {
2524 // OK, here we go back one, check for OK destination...
2525 if ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) )
2526 {
2527 return( sGridNo );
2528 }
2529
2530 // If here, try another place!
2531 // ( alternate up/down )
2532 if ( bOdd2 )
2533 {
2534 bAdjustedDist++;
2535
2536 sGridNo = sStartGridNo;
2537
2538 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2539 {
2540 sGridNo = (INT16)(sGridNo - WORLD_COLS - 1);
2541 }
2542 }
2543 else
2544 {
2545 sGridNo = sStartGridNo;
2546
2547 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2548 {
2549 sGridNo = (INT16)(sGridNo + WORLD_COLS + 1);
2550 }
2551 }
2552
2553 bOdd2 = (INT8)(!bOdd2);
2554
2555 } while( TRUE );
2556
2557 case NORTH:
2558
2559 sGridNo = pSoldier->sGridNo;
2560 sStartGridNo = pSoldier->sGridNo;
2561 sOldGridNo = pSoldier->sGridNo;
2562
2563 // Move directly to the left!
2564 while( GridNoOnVisibleWorldTile( sGridNo ) )
2565 {
2566 sOldGridNo = sGridNo;
2567
2568 if ( bOdd )
2569 {
2570 sGridNo -= WORLD_COLS;
2571 }
2572 else
2573 {
2574 sGridNo--;
2575 }
2576
2577 bOdd = (INT8)(!bOdd);
2578 }
2579
2580 sGridNo = sOldGridNo;
2581 sStartGridNo = sOldGridNo;
2582
2583 do
2584 {
2585 // OK, here we go back one, check for OK destination...
2586 if ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) )
2587 {
2588 return( sGridNo );
2589 }
2590
2591 // If here, try another place!
2592 // ( alternate left/right )
2593 if ( bOdd2 )
2594 {
2595 bAdjustedDist++;
2596
2597 sGridNo = sStartGridNo;
2598
2599 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2600 {
2601 sGridNo = (INT16)(sGridNo + WORLD_COLS - 1);
2602 }
2603 }
2604 else
2605 {
2606 sGridNo = sStartGridNo;
2607
2608 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2609 {
2610 sGridNo = (INT16)(sGridNo - WORLD_COLS + 1);
2611 }
2612 }
2613
2614 bOdd2 = (INT8)(!bOdd2);
2615
2616 } while( TRUE );
2617
2618 case SOUTH:
2619
2620 sGridNo = pSoldier->sGridNo;
2621 sStartGridNo = pSoldier->sGridNo;
2622 sOldGridNo = pSoldier->sGridNo;
2623
2624 // Move directly to the left!
2625 while( GridNoOnVisibleWorldTile( sGridNo ) )
2626 {
2627 sOldGridNo = sGridNo;
2628
2629 if ( bOdd )
2630 {
2631 sGridNo += WORLD_COLS;
2632 }
2633 else
2634 {
2635 sGridNo++;
2636 }
2637
2638 bOdd = (INT8)(!bOdd);
2639 }
2640
2641 sGridNo = sOldGridNo;
2642 sStartGridNo = sOldGridNo;
2643
2644 do
2645 {
2646 // OK, here we go back one, check for OK destination...
2647 if ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) )
2648 {
2649 return( sGridNo );
2650 }
2651
2652 // If here, try another place!
2653 // ( alternate left/right )
2654 if ( bOdd2 )
2655 {
2656 bAdjustedDist++;
2657
2658 sGridNo = sStartGridNo;
2659
2660 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2661 {
2662 sGridNo = (INT16)(sGridNo + WORLD_COLS - 1);
2663 }
2664 }
2665 else
2666 {
2667 sGridNo = sStartGridNo;
2668
2669 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2670 {
2671 sGridNo = (INT16)(sGridNo - WORLD_COLS + 1);
2672 }
2673 }
2674
2675 bOdd2 = (INT8)(!bOdd2);
2676
2677 } while( TRUE );
2678 }
2679
2680 return( NOWHERE );
2681 }
2682
2683
AdjustSoldierPathToGoOffEdge(SOLDIERTYPE * pSoldier,INT16 sEndGridNo,UINT8 ubTacticalDirection)2684 void AdjustSoldierPathToGoOffEdge( SOLDIERTYPE *pSoldier, INT16 sEndGridNo, UINT8 ubTacticalDirection )
2685 {
2686 INT16 sNewGridNo, sTempGridNo;
2687 INT32 iLoop;
2688
2689 // will this path segment actually take us to our desired destination in the first place?
2690 if (pSoldier->ubPathDataSize + 2 > MAX_PATH_LIST_SIZE)
2691 {
2692
2693 sTempGridNo = pSoldier->sGridNo;
2694
2695 for (iLoop = 0; iLoop < pSoldier->ubPathDataSize; iLoop++)
2696 {
2697 sTempGridNo += DirectionInc( pSoldier->ubPathingData[ iLoop ] );
2698 }
2699
2700 if (sTempGridNo == sEndGridNo)
2701 {
2702 // we can make it, but there isn't enough path room for the two steps required.
2703 // truncate our path so there's guaranteed the merc will have to generate another
2704 // path later on...
2705 pSoldier->ubPathDataSize -= 4;
2706 return;
2707 }
2708 else
2709 {
2710 // can't even make it there with these 30 tiles of path, abort...
2711 return;
2712 }
2713 }
2714
2715 switch( ubTacticalDirection )
2716 {
2717 case EAST:
2718
2719 sNewGridNo = NewGridNo( (UINT16)sEndGridNo, DirectionInc( NORTHEAST ) );
2720
2721 if ( OutOfBounds( sEndGridNo, sNewGridNo ) )
2722 {
2723 return;
2724 }
2725
2726 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = NORTHEAST;
2727 pSoldier->ubPathDataSize++;
2728 pSoldier->sFinalDestination = sNewGridNo;
2729 pSoldier->usActionData = sNewGridNo;
2730
2731 sTempGridNo = NewGridNo( (UINT16)sNewGridNo, DirectionInc( NORTHEAST ) );
2732
2733 if ( OutOfBounds( sNewGridNo, sTempGridNo ) )
2734 {
2735 return;
2736 }
2737 sNewGridNo = sTempGridNo;
2738
2739 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = NORTHEAST;
2740 pSoldier->ubPathDataSize++;
2741 pSoldier->sFinalDestination = sNewGridNo;
2742 pSoldier->usActionData = sNewGridNo;
2743
2744 break;
2745
2746 case WEST:
2747
2748 sNewGridNo = NewGridNo( (UINT16)sEndGridNo, DirectionInc( SOUTHWEST ) );
2749
2750 if ( OutOfBounds( sEndGridNo, sNewGridNo ) )
2751 {
2752 return;
2753 }
2754
2755 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = SOUTHWEST;
2756 pSoldier->ubPathDataSize++;
2757 pSoldier->sFinalDestination = sNewGridNo;
2758 pSoldier->usActionData = sNewGridNo;
2759
2760 sTempGridNo = NewGridNo( (UINT16)sNewGridNo, DirectionInc( SOUTHWEST ) );
2761
2762 if ( OutOfBounds( sNewGridNo, sTempGridNo ) )
2763 {
2764 return;
2765 }
2766 sNewGridNo = sTempGridNo;
2767
2768 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = SOUTHWEST;
2769 pSoldier->ubPathDataSize++;
2770 pSoldier->sFinalDestination = sNewGridNo;
2771 pSoldier->usActionData = sNewGridNo;
2772 break;
2773
2774 case NORTH:
2775
2776 sNewGridNo = NewGridNo( (UINT16)sEndGridNo, (UINT16)DirectionInc( (UINT8)NORTHWEST ) );
2777
2778 if ( OutOfBounds( sEndGridNo, sNewGridNo ) )
2779 {
2780 return;
2781 }
2782
2783 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = NORTHWEST;
2784 pSoldier->ubPathDataSize++;
2785 pSoldier->sFinalDestination = sNewGridNo;
2786 pSoldier->usActionData = sNewGridNo;
2787
2788 sTempGridNo = NewGridNo( (UINT16)sNewGridNo, DirectionInc( NORTHWEST ) );
2789
2790 if ( OutOfBounds( sNewGridNo, sTempGridNo ) )
2791 {
2792 return;
2793 }
2794 sNewGridNo = sTempGridNo;
2795
2796 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = NORTHWEST;
2797 pSoldier->ubPathDataSize++;
2798 pSoldier->sFinalDestination = sNewGridNo;
2799 pSoldier->usActionData = sNewGridNo;
2800
2801 break;
2802
2803 case SOUTH:
2804
2805 sNewGridNo = NewGridNo( (UINT16)sEndGridNo, DirectionInc( SOUTHEAST ) );
2806
2807 if ( OutOfBounds( sEndGridNo, sNewGridNo ) )
2808 {
2809 return;
2810 }
2811
2812 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = SOUTHEAST;
2813 pSoldier->ubPathDataSize++;
2814 pSoldier->sFinalDestination = sNewGridNo;
2815 pSoldier->usActionData = sNewGridNo;
2816
2817 sTempGridNo = NewGridNo( (UINT16)sNewGridNo, DirectionInc( SOUTHEAST ) );
2818
2819 if ( OutOfBounds( sNewGridNo, sTempGridNo ) )
2820 {
2821 return;
2822 }
2823 sNewGridNo = sTempGridNo;
2824
2825 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = SOUTHEAST;
2826 pSoldier->ubPathDataSize++;
2827 pSoldier->sFinalDestination = sNewGridNo;
2828 pSoldier->usActionData = sNewGridNo;
2829 break;
2830
2831 }
2832 }
2833
2834
PickGridNoToWalkIn(SOLDIERTYPE * pSoldier,UINT8 ubInsertionDirection,UINT32 * puiNumAttempts)2835 static INT16 PickGridNoToWalkIn(SOLDIERTYPE* pSoldier, UINT8 ubInsertionDirection, UINT32* puiNumAttempts)
2836 {
2837 INT16 sGridNo, sStartGridNo, sOldGridNo;
2838 INT8 bOdd = 1, bOdd2 = 1;
2839 UINT8 bAdjustedDist = 0;
2840 UINT32 cnt;
2841
2842 *puiNumAttempts = 0;
2843
2844 switch( ubInsertionDirection )
2845 {
2846 // OK, we're given a direction on visible map, let's look for the first oone
2847 // we find that is just on the start of visible map...
2848 case INSERTION_CODE_WEST:
2849
2850 sGridNo = (INT16)pSoldier->sGridNo;
2851 sStartGridNo = (INT16)pSoldier->sGridNo;
2852 sOldGridNo = (INT16)pSoldier->sGridNo;
2853
2854 // Move directly to the left!
2855 while( GridNoOnVisibleWorldTile( sGridNo ) )
2856 {
2857 sOldGridNo = sGridNo;
2858
2859 if ( bOdd )
2860 {
2861 sGridNo += WORLD_COLS;
2862 }
2863 else
2864 {
2865 sGridNo--;
2866 }
2867
2868 bOdd = (INT8)(!bOdd);
2869 }
2870
2871 sGridNo = sOldGridNo;
2872 sStartGridNo = sOldGridNo;
2873
2874 while( *puiNumAttempts < MAX_ATTEMPTS )
2875 {
2876 (*puiNumAttempts)++;
2877 // OK, here we go back one, check for OK destination...
2878 if ( ( gTacticalStatus.uiFlags & IGNORE_ALL_OBSTACLES ) || ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) ) )
2879 {
2880 return( sGridNo );
2881 }
2882
2883 // If here, try another place!
2884 // ( alternate up/down )
2885 if ( bOdd2 )
2886 {
2887 bAdjustedDist++;
2888
2889 sGridNo = sStartGridNo;
2890
2891 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2892 {
2893 sGridNo = (INT16)(sGridNo - WORLD_COLS - 1);
2894 }
2895 }
2896 else
2897 {
2898 sGridNo = sStartGridNo;
2899
2900 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2901 {
2902 sGridNo = (INT16)(sGridNo + WORLD_COLS + 1);
2903 }
2904 }
2905
2906 bOdd2 = (INT8)(!bOdd2);
2907
2908 }
2909 return NOWHERE;
2910
2911 case INSERTION_CODE_EAST:
2912
2913 sGridNo = (INT16)pSoldier->sGridNo;
2914 sStartGridNo = (INT16)pSoldier->sGridNo;
2915 sOldGridNo = (INT16)pSoldier->sGridNo;
2916
2917 // Move directly to the right!
2918 while( GridNoOnVisibleWorldTile( sGridNo ) )
2919 {
2920 sOldGridNo = sGridNo;
2921
2922 if ( bOdd )
2923 {
2924 sGridNo -= WORLD_COLS;
2925 }
2926 else
2927 {
2928 sGridNo++;
2929 }
2930
2931 bOdd = (INT8)(!bOdd);
2932 }
2933
2934 sGridNo = sOldGridNo;
2935 sStartGridNo = sOldGridNo;
2936
2937 while( *puiNumAttempts < MAX_ATTEMPTS )
2938 {
2939 (*puiNumAttempts)++;
2940 // OK, here we go back one, check for OK destination...
2941 if ( ( gTacticalStatus.uiFlags & IGNORE_ALL_OBSTACLES ) || ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) ) )
2942 {
2943 return( sGridNo );
2944 }
2945
2946 // If here, try another place!
2947 // ( alternate up/down )
2948 if ( bOdd2 )
2949 {
2950 bAdjustedDist++;
2951
2952 sGridNo = sStartGridNo;
2953
2954 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2955 {
2956 sGridNo = (INT16)(sGridNo - WORLD_COLS - 1);
2957 }
2958 }
2959 else
2960 {
2961 sGridNo = sStartGridNo;
2962
2963 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
2964 {
2965 sGridNo = (INT16)(sGridNo + WORLD_COLS + 1);
2966 }
2967 }
2968
2969 bOdd2 = (INT8)(!bOdd2);
2970
2971 }
2972 return NOWHERE;
2973
2974 case INSERTION_CODE_NORTH:
2975
2976 sGridNo = (INT16)pSoldier->sGridNo;
2977 sStartGridNo = (INT16)pSoldier->sGridNo;
2978 sOldGridNo = (INT16)pSoldier->sGridNo;
2979
2980 // Move directly to the up!
2981 while( GridNoOnVisibleWorldTile( sGridNo ) )
2982 {
2983 sOldGridNo = sGridNo;
2984
2985 if ( bOdd )
2986 {
2987 sGridNo -= WORLD_COLS;
2988 }
2989 else
2990 {
2991 sGridNo--;
2992 }
2993
2994 bOdd = (INT8)(!bOdd);
2995 }
2996
2997 sGridNo = sOldGridNo;
2998 sStartGridNo = sOldGridNo;
2999
3000 while( *puiNumAttempts < MAX_ATTEMPTS )
3001 {
3002 (*puiNumAttempts)++;
3003 // OK, here we go back one, check for OK destination...
3004 if ( ( gTacticalStatus.uiFlags & IGNORE_ALL_OBSTACLES ) || ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) ) )
3005 {
3006 return( sGridNo );
3007 }
3008
3009 // If here, try another place!
3010 // ( alternate left/right )
3011 if ( bOdd2 )
3012 {
3013 bAdjustedDist++;
3014
3015 sGridNo = sStartGridNo;
3016
3017 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
3018 {
3019 sGridNo = (INT16)(sGridNo - WORLD_COLS + 1);
3020 }
3021 }
3022 else
3023 {
3024 sGridNo = sStartGridNo;
3025
3026 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
3027 {
3028 sGridNo = (INT16)(sGridNo + WORLD_COLS - 1);
3029 }
3030 }
3031
3032 bOdd2 = (INT8)(!bOdd2);
3033
3034 }
3035 return NOWHERE;
3036
3037 case INSERTION_CODE_SOUTH:
3038
3039 sGridNo = (INT16)pSoldier->sGridNo;
3040 sStartGridNo = (INT16)pSoldier->sGridNo;
3041 sOldGridNo = (INT16)pSoldier->sGridNo;
3042
3043 // Move directly to the down!
3044 while( GridNoOnVisibleWorldTile( sGridNo ) )
3045 {
3046 sOldGridNo = sGridNo;
3047
3048 if ( bOdd )
3049 {
3050 sGridNo += WORLD_COLS;
3051 }
3052 else
3053 {
3054 sGridNo++;
3055 }
3056
3057 bOdd = (INT8)(!bOdd);
3058 }
3059
3060 sGridNo = sOldGridNo;
3061 sStartGridNo = sOldGridNo;
3062
3063 while( *puiNumAttempts < MAX_ATTEMPTS )
3064 {
3065 (*puiNumAttempts)++;
3066 // OK, here we go back one, check for OK destination...
3067 if ( ( gTacticalStatus.uiFlags & IGNORE_ALL_OBSTACLES ) || ( NewOKDestination( pSoldier, sGridNo, TRUE, pSoldier->bLevel ) && FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) ) )
3068 {
3069 return( sGridNo );
3070 }
3071
3072 // If here, try another place!
3073 // ( alternate left/right )
3074 if ( bOdd2 )
3075 {
3076 bAdjustedDist++;
3077
3078 sGridNo = sStartGridNo;
3079
3080 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
3081 {
3082 sGridNo = (INT16)(sGridNo - WORLD_COLS + 1);
3083 }
3084 }
3085 else
3086 {
3087 sGridNo = sStartGridNo;
3088
3089 for ( cnt = 0; cnt < bAdjustedDist; cnt++ )
3090 {
3091 sGridNo = (INT16)(sGridNo + WORLD_COLS - 1);
3092 }
3093 }
3094
3095 bOdd2 = (INT8)(!bOdd2);
3096
3097 }
3098 return NOWHERE;
3099
3100 }
3101
3102 //Unhandled exit
3103 *puiNumAttempts = 0;
3104
3105 return( NOWHERE );
3106 }
3107
3108
3109 //NEW!
3110 //Calculates the name of the sector based on the loaded sector values.
3111 //Examples: A9
3112 // A10_B1
3113 // J9_B2_A ( >= BETAVERSION ) else J9_B2 (release equivalent)
GetLoadedSectorString()3114 static ST::string GetLoadedSectorString()
3115 {
3116 if (!gfWorldLoaded)
3117 {
3118 return ST::null;
3119 }
3120 else if (gbWorldSectorZ == 0)
3121 {
3122 return ST::format("{c}{}", gWorldSectorY + 'A' - 1, gWorldSectorX);
3123 }
3124 else
3125 {
3126 return ST::format("{c}{}_b{}", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ);
3127 }
3128 }
3129
3130
HandleSlayDailyEvent(void)3131 void HandleSlayDailyEvent( void )
3132 {
3133 SOLDIERTYPE* const pSoldier = FindSoldierByProfileIDOnPlayerTeam(SLAY);
3134 if( pSoldier == NULL )
3135 {
3136 return;
3137 }
3138
3139 // valid soldier?
3140 if (pSoldier->bLife == 0 || pSoldier->bAssignment == IN_TRANSIT || pSoldier->bAssignment == ASSIGNMENT_POW)
3141 {
3142 // no
3143 return;
3144 }
3145
3146 // ATE: This function is used to check for the ultimate last day SLAY can stay for
3147 // he may decide to leave randomly while asleep...
3148 //if the user hasnt renewed yet, and is still leaving today
3149 if( ( pSoldier->iEndofContractTime /1440 ) <= (INT32)GetWorldDay( ) )
3150 {
3151 pSoldier->ubLeaveHistoryCode = HISTORY_SLAY_MYSTERIOUSLY_LEFT;
3152 MakeCharacterDialogueEventContractEndingNoAskEquip(*pSoldier);
3153 }
3154 }
3155
3156
IsSectorDesert(INT16 const x,INT16 const y)3157 bool IsSectorDesert(INT16 const x, INT16 const y)
3158 {
3159 return SectorInfo[SECTOR(x, y)].ubTraversability[THROUGH_STRATEGIC_MOVE] == SAND;
3160 }
3161
3162
HandleDefiniteUnloadingOfWorld(UINT8 const ubUnloadCode)3163 static void HandleDefiniteUnloadingOfWorld(UINT8 const ubUnloadCode)
3164 {
3165 // clear tactical queue
3166 ClearEventQueue();
3167
3168 // ATE: End all bullets....
3169 DeleteAllBullets();
3170
3171 // End all physics objects...
3172 RemoveAllPhysicsObjects();
3173
3174 RemoveAllActiveTimedBombs();
3175
3176 // handle any quest stuff here so world items can be affected
3177 HandleQuestCodeOnSectorExit( gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
3178
3179 //if we arent loading a saved game
3180 if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
3181 {
3182 //Clear any potential battle flags. They will be set if necessary.
3183 gTacticalStatus.fEnemyInSector = FALSE;
3184 gTacticalStatus.uiFlags &= ~INCOMBAT;
3185 }
3186
3187 if ( ubUnloadCode == ABOUT_TO_LOAD_NEW_MAP )
3188 {
3189 //if we arent loading a saved game
3190 if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
3191 {
3192
3193 // Save the current sectors Item list to a temporary file, if its not the first time in
3194 SaveCurrentSectorsInformationToTempItemFile();
3195
3196 // Update any mercs currently in sector, their profile info...
3197 UpdateSoldierPointerDataIntoProfile();
3198 }
3199 }
3200 else if( ubUnloadCode == ABOUT_TO_TRASH_WORLD )
3201 {
3202 //Save the current sectors open temp files to the disk
3203 SaveCurrentSectorsInformationToTempItemFile();
3204
3205 //Setup the tactical existance of the current soldier.
3206 //@@@Evaluate
3207 SetupProfileInsertionDataForCivilians();
3208
3209 gfBlitBattleSectorLocator = FALSE;
3210 }
3211
3212 //Handle cases for both types of unloading
3213 HandleMilitiaStatusInCurrentMapBeforeLoadingNewMap();
3214 }
3215
3216
HandlePotentialBringUpAutoresolveToFinishBattle()3217 BOOLEAN HandlePotentialBringUpAutoresolveToFinishBattle( )
3218 {
3219 INT32 i;
3220
3221 //We don't have mercs in the sector. Now, we check to see if there are BOTH enemies and militia. If both
3222 //co-exist in the sector, then make them fight for control of the sector via autoresolve.
3223 for( i = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; i <= gTacticalStatus.Team[ CREATURE_TEAM ].bLastID; i++ )
3224 {
3225 SOLDIERTYPE const& creature = GetMan(i);
3226 if (creature.bActive &&
3227 creature.bLife != 0 &&
3228 creature.sSectorX == gWorldSectorX &&
3229 creature.sSectorY == gWorldSectorY &&
3230 creature.bSectorZ == gbWorldSectorZ)
3231 { //We have enemies, now look for militia!
3232 for( i = gTacticalStatus.Team[ MILITIA_TEAM ].bFirstID; i <= gTacticalStatus.Team[ MILITIA_TEAM ].bLastID; i++ )
3233 {
3234 SOLDIERTYPE const& milita = GetMan(i);
3235 if (milita.bActive &&
3236 milita.bLife != 0 &&
3237 milita.bSide == OUR_TEAM &&
3238 milita.sSectorX == gWorldSectorX &&
3239 milita.sSectorY == gWorldSectorY &&
3240 milita.bSectorZ == gbWorldSectorZ)
3241 { //We have militia and enemies and no mercs! Let's finish this battle in autoresolve.
3242 gfEnteringMapScreen = TRUE;
3243 gfEnteringMapScreenToEnterPreBattleInterface = TRUE;
3244 gfAutomaticallyStartAutoResolve = TRUE;
3245 gfUsePersistantPBI = FALSE;
3246 gubPBSectorX = (UINT8)gWorldSectorX;
3247 gubPBSectorY = (UINT8)gWorldSectorY;
3248 gubPBSectorZ = (UINT8)gbWorldSectorZ;
3249 gfBlitBattleSectorLocator = TRUE;
3250 gfTransferTacticalOppositionToAutoResolve = TRUE;
3251 if( gubEnemyEncounterCode != CREATURE_ATTACK_CODE )
3252 {
3253 gubEnemyEncounterCode = ENEMY_INVASION_CODE; //has to be, if militia are here.
3254 }
3255 else
3256 {
3257 //DoScreenIndependantMessageBox(gzLateLocalizedString[STR_LATE_39], MSG_BOX_FLAG_OK, MapScreenDefaultOkBoxCallback);
3258 }
3259
3260 return( TRUE );
3261 }
3262 }
3263 }
3264 }
3265
3266 return( FALSE );
3267 }
3268
3269
CheckAndHandleUnloadingOfCurrentWorld()3270 BOOLEAN CheckAndHandleUnloadingOfCurrentWorld()
3271 try
3272 {
3273 INT16 sBattleSectorX, sBattleSectorY, sBattleSectorZ;
3274
3275 //Don't bother checking this if we don't have a world loaded.
3276 if( !gfWorldLoaded )
3277 {
3278 return FALSE;
3279 }
3280
3281 if (DidGameJustStart() && SECTOR(gWorldSectorX, gWorldSectorY) == START_SECTOR && gbWorldSectorZ == 0)
3282 {
3283 return FALSE;
3284 }
3285
3286 GetCurrentBattleSectorXYZ( &sBattleSectorX, &sBattleSectorY, &sBattleSectorZ );
3287
3288 if( guiCurrentScreen == AUTORESOLVE_SCREEN )
3289 { //The user has decided to let the game autoresolve the current battle.
3290 if( gWorldSectorX == sBattleSectorX && gWorldSectorY == sBattleSectorY && gbWorldSectorZ == sBattleSectorZ )
3291 {
3292 FOR_EACH_IN_TEAM(i, OUR_TEAM)
3293 { //If we have a live and valid soldier
3294 SOLDIERTYPE& s = *i;
3295 if (s.bLife == 0) continue;
3296 if (s.fBetweenSectors) continue;
3297 if (IsMechanical(s)) continue;
3298 if (AM_AN_EPC(&s)) continue;
3299 if (s.sSectorX != gWorldSectorX) continue;
3300 if (s.sSectorY != gWorldSectorY) continue;
3301 if (s.bSectorZ != gbWorldSectorZ) continue;
3302 RemoveSoldierFromGridNo(s);
3303 InitSoldierOppList(s);
3304 }
3305 }
3306 }
3307 else
3308 { //Check and see if we have any live mercs in the sector.
3309 CFOR_EACH_IN_TEAM(s, OUR_TEAM)
3310 { //If we have a live and valid soldier
3311 if (s->bLife != 0 &&
3312 !s->fBetweenSectors &&
3313 !IsMechanical(*s) &&
3314 !AM_AN_EPC(s) &&
3315 s->sSectorX == gWorldSectorX &&
3316 s->sSectorY == gWorldSectorY &&
3317 s->bSectorZ == gbWorldSectorZ)
3318 {
3319 return FALSE;
3320 }
3321 }
3322 //KM : August 6, 1999 Patch fix
3323 // Added logic to prevent a crash when player mercs would retreat from a battle involving militia and enemies.
3324 // Without the return here, it would proceed to trash the world, and then when autoresolve would come up to
3325 // finish the tactical battle, it would fail to find the existing soldier information (because it was trashed).
3326 if( HandlePotentialBringUpAutoresolveToFinishBattle( ) )
3327 {
3328 return FALSE;
3329 }
3330 //end
3331
3332 //HandlePotentialBringUpAutoresolveToFinishBattle( ); //prior patch logic
3333 }
3334
3335
3336 CheckForEndOfCombatMode( FALSE );
3337 EndTacticalBattleForEnemy();
3338
3339 // ATE: Change cursor to wait cursor for duration of frame.....
3340 // save old cursor ID....
3341 SetCurrentCursorFromDatabase( CURSOR_WAIT_NODELAY );
3342 RefreshScreen();
3343
3344 // JA2Gold: Leaving sector, so get rid of ambients!
3345 DeleteAllAmbients();
3346
3347 if( guiCurrentScreen == GAME_SCREEN )
3348 {
3349 if( !gfTacticalTraversal )
3350 { //if we are in tactical and don't intend on going to another sector immediately, then
3351 gfEnteringMapScreen = TRUE;
3352 }
3353 else
3354 { //The trashing of the world will be handled automatically.
3355 return FALSE;
3356 }
3357 }
3358
3359 //We have passed all the checks and can Trash the world.
3360 HandleDefiniteUnloadingOfWorld(ABOUT_TO_TRASH_WORLD);
3361
3362 if( guiCurrentScreen == AUTORESOLVE_SCREEN )
3363 {
3364 if( gWorldSectorX == sBattleSectorX && gWorldSectorY == sBattleSectorY && gbWorldSectorZ == sBattleSectorZ )
3365 {
3366 /* Yes, this is and looks like a hack. The conditions of this if
3367 * statement doesn't work inside TrashWorld() or more specifically,
3368 * TacticalRemoveSoldier() from within TrashWorld(). Because we are in
3369 * the autoresolve screen, soldiers are internally created different (from
3370 * pointers instead of Menptr[]). It keys on the fact that we are in the
3371 * autoresolve screen. So, by switching the screen, it'll delete the
3372 * soldiers in the loaded world properly, then later on, once autoresolve
3373 * is complete, it'll delete the autoresolve soldiers properly. As you
3374 * can now see, the above if conditions don't change throughout this whole
3375 * process which makes it necessary to do it this way. */
3376 guiCurrentScreen = MAP_SCREEN;
3377 TrashWorld();
3378 guiCurrentScreen = AUTORESOLVE_SCREEN;
3379 }
3380 }
3381 else
3382 {
3383 TrashWorld();
3384 }
3385
3386 //Clear all combat related flags.
3387 gTacticalStatus.fEnemyInSector = FALSE;
3388 gTacticalStatus.uiFlags &= ~INCOMBAT;
3389 EndTopMessage( );
3390
3391
3392 //Clear the world sector values.
3393 SetWorldSectorInvalid();
3394
3395 //Clear the flags regarding.
3396 gfCaves = FALSE;
3397 gfBasement = FALSE;
3398
3399 return TRUE;
3400 }
3401 catch (...) { return FALSE; }
3402
3403
3404 /* This is called just before the world is unloaded to preserve location
3405 * information for RPCs and NPCs either in the sector or strategically in the
3406 * sector (such as firing an NPC in a sector that isn't yet loaded.) When
3407 * loading that sector, the RPC would be added. */
3408 //@@@Evaluate
SetupProfileInsertionDataForSoldier(const SOLDIERTYPE * const s)3409 void SetupProfileInsertionDataForSoldier(const SOLDIERTYPE* const s)
3410 {
3411 if (s->ubProfile == NO_PROFILE) return;
3412 MERCPROFILESTRUCT& p = GetProfile(s->ubProfile);
3413
3414 // can't be changed?
3415 if (p.ubMiscFlags3 & PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE) return;
3416
3417 if (gfWorldLoaded && s->bActive && s->bInSector)
3418 {
3419 // This soldier is currently in the sector
3420
3421 //@@@Evaluate -- insert code here
3422 //SAMPLE CODE: There are multiple situations that I didn't code. The gridno should be the final destination
3423 //or reset???
3424
3425 if (s->ubQuoteRecord && s->ubQuoteActionID)
3426 {
3427 // if moving to traverse
3428 if (s->ubQuoteActionID >= QUOTE_ACTION_ID_TRAVERSE_EAST && s->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH)
3429 {
3430 // Handle traversal. This NPC's sector will NOT already be set correctly, so we have to call for that too
3431 HandleNPCChangesForTacticalTraversal(s);
3432 p.fUseProfileInsertionInfo = FALSE;
3433 if (s->ubProfile != NO_PROFILE && NPCHasUnusedRecordWithGivenApproach(s->ubProfile, APPROACH_DONE_TRAVERSAL))
3434 {
3435 p.ubMiscFlags3 |= PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL;
3436 }
3437 }
3438 else
3439 {
3440 if (s->sFinalDestination == s->sGridNo)
3441 {
3442 p.usStrategicInsertionData = s->sGridNo;
3443 }
3444 else if (s->sAbsoluteFinalDestination != NOWHERE)
3445 {
3446 p.usStrategicInsertionData = s->sAbsoluteFinalDestination;
3447 }
3448 else
3449 {
3450 p.usStrategicInsertionData = s->sFinalDestination;
3451 }
3452
3453 p.fUseProfileInsertionInfo = TRUE;
3454 p.ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
3455 p.ubQuoteActionID = s->ubQuoteActionID;
3456 p.ubQuoteRecord = s->ubQuoteActionID;
3457 }
3458 }
3459 else
3460 {
3461 p.fUseProfileInsertionInfo = FALSE;
3462 }
3463 }
3464 else
3465 {
3466 //use strategic information
3467 /* It appears to set the soldier's strategic insertion code everytime a
3468 * group arrives in a new sector. The insertion data isn't needed for these
3469 * cases as the code is a direction only. */
3470 p.ubStrategicInsertionCode = s->ubStrategicInsertionCode;
3471 p.usStrategicInsertionData = 0;
3472
3473 //Strategic system should now work.
3474 p.fUseProfileInsertionInfo = TRUE;
3475 }
3476 }
3477
3478
HandlePotentialMoraleHitForSkimmingSectors(GROUP * pGroup)3479 static void HandlePotentialMoraleHitForSkimmingSectors(GROUP* pGroup)
3480 {
3481 if ( !gTacticalStatus.fHasEnteredCombatModeSinceEntering && gTacticalStatus.fEnemyInSector )
3482 {
3483 //Flag is set so if "wilderness" enemies are in the adjacent sector of this group, the group has
3484 //a 90% chance of ambush. Because this typically doesn't happen very often, the chance is high.
3485 //This reflects the enemies radioing ahead to other enemies of the group's arrival, so they have
3486 //time to setup a good ambush!
3487 pGroup->uiFlags |= GROUPFLAG_HIGH_POTENTIAL_FOR_AMBUSH;
3488
3489 CFOR_EACH_PLAYER_IN_GROUP(pPlayer, pGroup)
3490 {
3491 // Do morale hit...
3492 // CC look here!
3493 // pPlayer->pSoldier
3494 }
3495 }
3496 }
3497
3498
GetWorldSector()3499 UINT GetWorldSector()
3500 {
3501 if (gWorldSectorX == 0 || gWorldSectorY == 0) return NO_SECTOR;
3502 return SECTOR(gWorldSectorX, gWorldSectorY);
3503 }
3504
3505