1 #include "Map_Screen_Interface_Map.h"
2
3 #include "Assignments.h"
4 #include "Button_System.h"
5 #include "Campaign_Types.h"
6 #include "ContentManager.h"
7 #include "Debug.h"
8 #include "Directories.h"
9 #include "Finances.h"
10 #include "Font.h"
11 #include "Font_Control.h"
12 #include "Game_Clock.h"
13 #include "GameInstance.h"
14 #include "HImage.h"
15 #include "Interface.h"
16 #include "Line.h"
17 #include "Map_Information.h"
18 #include "Map_Screen_Helicopter.h"
19 #include "Map_Screen_Interface.h"
20 #include "Map_Screen_Interface_Border.h"
21 #include "MapScreen.h"
22 #include "Merc_Hiring.h"
23 #include "Message.h"
24 #include "Militia_Control.h"
25 #include "MineModel.h"
26 #include "MouseSystem.h"
27 #include "Overhead.h"
28 #include "Overhead_Types.h"
29 #include "PreBattle_Interface.h"
30 #include "Queen_Command.h"
31 #include "Render_Dirty.h"
32 #include "SamSiteModel.h"
33 #include "SGPStrings.h"
34 #include "Soldier_Control.h"
35 #include "Squads.h"
36 #include "StrategicMap_Secrets.h"
37 #include "StrategicMapSecretModel.h"
38 #include "Strategic_Mines.h"
39 #include "Strategic_Movement.h"
40 #include "Strategic_Pathing.h"
41 #include "Strategic_Town_Loyalty.h"
42 #include "StrategicMap.h"
43 #include "SysUtil.h"
44 #include "Tactical_Save.h"
45 #include "Text.h"
46 #include "Timer_Control.h"
47 #include "Town_Militia.h"
48 #include "TownModel.h"
49 #include "Vehicles.h"
50 #include "Video.h"
51 #include "VObject.h"
52 #include "VObject_Blitters.h"
53 #include "VSurface.h"
54 #include <memory>
55 #include <stdexcept>
56 #include <string_theory/format>
57 #include <string_theory/string>
58 #include <vector>
59
60 // // Scroll region width
61 // #define SCROLL_REGION 4
62
63 // // The Map/Mouse Scroll defines
64 // #define EAST_DIR 0
65 // #define WEST_DIR 1
66 // #define NORTH_DIR 2
67 // #define SOUTH_DIR 3
68 // #define TOP_NORTH 2
69 // #define TOP_SOUTH 13
70 // #define RIGHT_WEST 250
71 // #define RIGHT_EAST 260
72 // #define LEFT_EAST 640
73 // #define LEFT_WEST 630
74 // #define BOTTOM_NORTH 320
75 // #define BOTTOM_SOUTH 330
76
77 // // Map Scroll Defines
78 // #define SCROLL_EAST 0
79 // #define SCROLL_WEST 1
80 // #define SCROLL_NORTH 2
81 // #define SCROLL_SOUTH 3
82 // #define SCROLL_DELAY 50
83 // #define HORT_SCROLL 14
84 // #define VERT_SCROLL 10
85
86 // the pop up for helicopter stuff
87 #define MAP_HELICOPTER_ETA_POPUP_X (STD_SCREEN_X + 400)
88 #define MAP_HELICOPTER_ETA_POPUP_Y (STD_SCREEN_Y + 250)
89 #define MAP_HELICOPTER_UPPER_ETA_POPUP_Y (STD_SCREEN_Y + 50)
90 #define MAP_HELICOPTER_ETA_POPUP_WIDTH 120
91 #define MAP_HELICOPTER_ETA_POPUP_HEIGHT 68
92
93 #define MAP_LEVEL_STRING_X (STD_SCREEN_X + 432)
94 #define MAP_LEVEL_STRING_Y (STD_SCREEN_Y + 305)
95
96 // font
97 #define MAP_FONT BLOCKFONT2
98
99 // index color
100 #define MAP_INDEX_COLOR 32*4-9
101
102 // max number of sectors viewable
103 #define MAX_VIEW_SECTORS 16
104
105
106 //Map Location index regions
107
108 // x start of hort index
109 #define MAP_HORT_INDEX_X (STD_SCREEN_X + 292)
110
111 // y position of hort index
112 #define MAP_HORT_INDEX_Y (STD_SCREEN_Y + 10)
113
114 // height of hort index
115 #define MAP_HORT_HEIGHT GetFontHeight(MAP_FONT)
116
117 // vert index start x
118 #define MAP_VERT_INDEX_X (STD_SCREEN_X + 273)
119
120 // vert index start y
121 #define MAP_VERT_INDEX_Y (STD_SCREEN_Y + 31)
122
123 // vert width
124 #define MAP_VERT_WIDTH GetFontHeight(MAP_FONT)
125
126 // "Boxes" Icons
127 #define SMALL_YELLOW_BOX 0
128 #define BIG_YELLOW_BOX 1
129 #define SMALL_DULL_YELLOW_BOX 2
130 #define BIG_DULL_YELLOW_BOX 3
131 #define SMALL_WHITE_BOX 4
132 #define BIG_WHITE_BOX 5
133 #define SMALL_RED_BOX 6
134 #define BIG_RED_BOX 7
135 #define SMALL_QUESTION_MARK 8
136 #define BIG_QUESTION_MARK 9
137
138
139 #define MERC_ICONS_PER_LINE 6
140 #define ROWS_PER_SECTOR 5
141
142 #define MAP_X_ICON_OFFSET 2
143 #define MAP_Y_ICON_OFFSET 1
144
145 // Arrow Offsets
146 #define UP_X 13
147 #define UP_Y 7
148 #define DOWN_X 0
149 #define DOWN_Y -2
150 #define RIGHT_X -2
151 #define RIGHT_Y 11
152 #define LEFT_X 2
153 #define LEFT_Y 5
154
155
156 // The Path Lines
157 #define NORTH_LINE 1
158 #define SOUTH_LINE 0
159 #define WEST_LINE 3
160 #define EAST_LINE 2
161 #define N_TO_E_LINE 4
162 #define E_TO_S_LINE 5
163 #define W_TO_N_LINE 6
164 #define S_TO_W_LINE 7
165 #define W_TO_S_LINE 8
166 #define N_TO_W_LINE 9
167 #define S_TO_E_LINE 10
168 #define E_TO_N_LINE 11
169 #define W_TO_E_LINE 12
170 #define N_TO_S_LINE 13
171 #define E_TO_W_LINE 14
172 #define S_TO_N_LINE 15
173 #define W_TO_E_PART1_LINE 16
174 #define W_TO_E_PART2_LINE 17
175 #define E_TO_W_PART1_LINE 18
176 #define E_TO_W_PART2_LINE 19
177 #define N_TO_S_PART1_LINE 20
178 #define N_TO_S_PART2_LINE 21
179 #define S_TO_N_PART1_LINE 22
180 #define S_TO_N_PART2_LINE 23
181 #define GREEN_X_WEST 36
182 #define GREEN_X_EAST 37
183 #define GREEN_X_NORTH 38
184 #define GREEN_X_SOUTH 39
185 #define RED_X_WEST 40
186 #define RED_X_EAST 41
187 #define RED_X_NORTH 42
188 #define RED_X_SOUTH 43
189
190 // The arrows
191 #define Y_NORTH_ARROW 24
192 #define Y_SOUTH_ARROW 25
193 #define Y_EAST_ARROW 26
194 #define Y_WEST_ARROW 27
195 #define W_NORTH_ARROW 28
196 #define W_SOUTH_ARROW 29
197 #define W_EAST_ARROW 30
198 #define W_WEST_ARROW 31
199 #define NORTH_ARROW 32
200 #define SOUTH_ARROW 33
201 #define EAST_ARROW 34
202 #define WEST_ARROW 35
203
204 #define ARROW_DELAY 20
205 #define PAUSE_DELAY 1000
206
207
208 #define CHAR_FONT_COLOR 32*4-9
209
210 // Arrow Offsets
211 #define EAST_OFFSET_X 11
212 #define EAST_OFFSET_Y 0
213 #define NORTH_OFFSET_X 9
214 #define NORTH_OFFSET_Y -9
215 #define SOUTH_OFFSET_X -9
216 #define SOUTH_OFFSET_Y 9
217 #define WEST_OFFSET_X -11
218 #define WEST_OFFSET_Y 0
219 #define WEST_TO_SOUTH_OFFSET_Y 0
220 #define EAST_TO_NORTH_OFFSET_Y 0
221 #define RED_WEST_OFF_X -MAP_GRID_X
222 #define RED_EAST_OFF_X MAP_GRID_X
223 #define RED_NORTH_OFF_Y -21
224 #define RED_SOUTH_OFF_Y 21
225
226 // the font use on the mvt icons for mapscreen
227 #define MAP_MVT_ICON_FONT SMALLCOMPFONT
228
229
230 // map shading colors
231
232 enum{
233 MAP_SHADE_BLACK =0,
234 MAP_SHADE_LT_GREEN,
235 MAP_SHADE_DK_GREEN,
236 MAP_SHADE_LT_RED,
237 MAP_SHADE_DK_RED,
238 };
239 // the big map .pcx
240 static SGPVSurface* guiBIGMAP;
241
242 // boxes for characters on the map
243 static SGPVObject* guiCHARICONS;
244
245 // the merc arrival sector landing zone icon
246 static SGPVObject* guiBULLSEYE;
247
248
249 // the max allowable towns militia in a sector
250 #define MAP_MILITIA_MAP_X 4
251 #define MAP_MILITIA_MAP_Y 20
252 #define MAP_MILITIA_LOWER_ROW_Y 142
253 #define NUMBER_OF_MILITIA_ICONS_PER_LOWER_ROW 25
254 #define MILITIA_BOX_ROWS 3
255 #define MILITIA_BOX_BOX_HEIGHT 36
256 #define MILITIA_BOX_BOX_WIDTH 42
257 #define MAP_MILITIA_BOX_POS_X (STD_SCREEN_X + 400)
258 #define MAP_MILITIA_BOX_POS_Y (STD_SCREEN_Y + 125)
259
260 #define POPUP_MILITIA_ICONS_PER_ROW 5 // max 6 rows gives the limit of 30 militia
261 #define MEDIUM_MILITIA_ICON_SPACING 5
262 #define LARGE_MILITIA_ICON_SPACING 6
263
264 #define MILITIA_BTN_OFFSET_X 26
265 #define MILITIA_BTN_HEIGHT 11
266 #define MILITIA_BOX_WIDTH 133
267 #define MILITIA_BOX_TEXT_OFFSET_Y 4
268 #define MILITIA_BOX_UNASSIGNED_TEXT_OFFSET_Y 132
269 #define MILITIA_BOX_TEXT_TITLE_HEIGHT 13
270
271 #define MAP_MILITIA_BOX_AUTO_BOX_X 4
272 #define MAP_MILITIA_BOX_AUTO_BOX_Y 167
273 #define MAP_MILITIA_BOX_DONE_BOX_X 67
274
275 #define HELI_ICON 0
276 #define HELI_SHADOW_ICON 1
277
278 #define HELI_ICON_WIDTH 20
279 #define HELI_ICON_HEIGHT 10
280 #define HELI_SHADOW_ICON_WIDTH 19
281 #define HELI_SHADOW_ICON_HEIGHT 11
282
283
284 // the militia box buttons
285 static GUIButtonRef giMapMilitiaButton[5];
286
287
288 static INT16 const gsMilitiaSectorButtonColors[] =
289 {
290 FONT_LTGREEN,
291 FONT_LTBLUE,
292 16
293 };
294
295 // track number of townspeople picked up
296 INT16 sGreensOnCursor = 0;
297 INT16 sRegularsOnCursor = 0;
298 INT16 sElitesOnCursor = 0;
299
300 // the current militia town id
301 INT16 sSelectedMilitiaTown = 0;
302
303
304 // sublevel graphics
305 static SGPVObject* guiSubLevel1;
306 static SGPVObject* guiSubLevel2;
307 static SGPVObject* guiSubLevel3;
308
309 // the between sector icons
310 static SGPVObject* guiCHARBETWEENSECTORICONS;
311 static SGPVObject* guiCHARBETWEENSECTORICONSCLOSE;
312
313 // selected sector
314 UINT16 sSelMapX = 9;
315 UINT16 sSelMapY = 1;
316
317 // highlighted sector
318 INT16 gsHighlightSectorX=-1;
319 INT16 gsHighlightSectorY=-1;
320
321 // the current sector Z value of the map being displayed
322 INT32 iCurrentMapSectorZ = 0;
323
324 // the palettes
325 static UINT16* pMapLTRedPalette;
326 static UINT16* pMapDKRedPalette;
327 static UINT16* pMapLTGreenPalette;
328 static UINT16* pMapDKGreenPalette;
329
330
331 // heli pop up
332 static SGPVObject* guiMapBorderHeliSectors;
333
334 // base sectors (sector value for the upper left corner) of towns. List start at zero, indexed by (townId - 1)
335 static std::vector<INT16> sBaseSectorList;
336
337 // position of town names on the map (list by townId, starting at 1)
338 // these are no longer PIXELS, but 10 * the X,Y position in SECTORS (fractions possible) to the X-CENTER of the town
339 static std::vector<SGPPoint> pTownPoints;
340
341
342 // map region
343 SGPRect MapScreenRect;
344
345 static SGPRect gOldClipRect;
346
347 // temp helicopter path
348 PathSt* pTempHelicopterPath = NULL;
349
350 // character temp path
351 PathSt* pTempCharacterPath = NULL;
352
353 // draw temp path?
354 BOOLEAN fDrawTempHeliPath = FALSE;
355
356 // the map arrows graphics
357 static SGPVObject* guiMAPCURSORS;
358
359 // destination plotting character
360 INT8 bSelectedDestChar = -1;
361
362 // assignment selection character
363 INT8 bSelectedAssignChar=-1;
364
365 // current contract char
366 INT8 bSelectedContractChar = -1;
367
368
369 // has the temp path for character or helicopter been already drawn?
370 BOOLEAN fTempPathAlreadyDrawn = FALSE;
371
372 // the regions for the mapscreen militia box
373 static MOUSE_REGION gMapScreenMilitiaBoxRegions[9];
374
375 // the mine icon
376 static SGPVObject* guiMINEICON;
377
378 // militia graphics
379 static SGPVObject* guiMilitia;
380 static SGPVObject* guiMilitiaMaps;
381 static SGPVObject* guiMilitiaSectorHighLight;
382 static SGPVObject* guiMilitiaSectorOutline;
383
384 // the sector that is highlighted on the militia map
385 static INT16 sSectorMilitiaMapSector = -1;
386 static bool fMilitiaMapButtonsCreated = false;
387 static INT16 sSectorMilitiaMapSectorOutline = -1;
388
389
390 // have any nodes in the current path list been deleted?
391 BOOLEAN fDeletedNode = FALSE;
392
393 static UINT16 gusUndergroundNearBlack;
394
395 BOOLEAN gfMilitiaPopupCreated = FALSE;
396
397 INT32 giAnimateRouteBaseTime = 0;
398 INT32 giPotHeliPathBaseTime = 0;
399
400 // sam and mine icons
401 static SGPVObject* guiSAMICON;
402
403 // helicopter icon
404 static SGPVObject* guiHelicopterIcon;
405
406 // map secret icons
407 static std::map<ST::string, SGPVObject*> gSecretSiteIcons;
408
409
InitMapScreenInterfaceMap()410 void InitMapScreenInterfaceMap()
411 {
412 sBaseSectorList.clear();
413 pTownPoints.clear();
414 pTownPoints.push_back(SGPPoint());
415
416 auto towns = GCM->getTowns();
417 for (auto& pair : GCM->getTowns())
418 {
419 auto town = pair.second;
420 sBaseSectorList.push_back(town->getBaseSector());
421 pTownPoints.push_back(town->townPoint);
422 }
423
424 MapScreenRect.set((MAP_VIEW_START_X+MAP_GRID_X - 2), ( MAP_VIEW_START_Y+MAP_GRID_Y - 1),
425 MAP_VIEW_START_X + MAP_VIEW_WIDTH - 1 + MAP_GRID_X , MAP_VIEW_START_Y+MAP_VIEW_HEIGHT-10+MAP_GRID_Y);
426 }
427
DrawMapIndexBigMap(BOOLEAN fSelectedCursorIsYellow)428 void DrawMapIndexBigMap(BOOLEAN fSelectedCursorIsYellow)
429 {
430 // this procedure will draw the coord indexes on the zoomed out map
431 SetFontDestBuffer(FRAME_BUFFER);
432 SetFont(MAP_FONT);
433 SetFontBackground(FONT_MCOLOR_BLACK);
434
435 bool const draw_cursors = CanDrawSectorCursor();
436 bool const sel_candidate = bSelectedDestChar == -1 && !fPlotForHelicopter;
437 UINT8 const sel_colour = fSelectedCursorIsYellow ? FONT_YELLOW : FONT_WHITE;
438 for (INT32 i = 1; i <= MAX_VIEW_SECTORS; ++i)
439 {
440 INT16 usX;
441 INT16 usY;
442
443 UINT8 const colour_x =
444 !draw_cursors ? MAP_INDEX_COLOR :
445 i == sSelMapX && sel_candidate ? sel_colour :
446 i == gsHighlightSectorX ? FONT_WHITE :
447 MAP_INDEX_COLOR;
448 SetFontForeground(colour_x);
449 FindFontCenterCoordinates(MAP_HORT_INDEX_X + (i - 1) * MAP_GRID_X, MAP_HORT_INDEX_Y, MAP_GRID_X, MAP_HORT_HEIGHT, pMapHortIndex[i], MAP_FONT, &usX, &usY);
450 MPrint(usX, usY, pMapHortIndex[i]);
451
452 UINT8 const colour_y =
453 !draw_cursors ? MAP_INDEX_COLOR :
454 i == sSelMapY && sel_candidate ? sel_colour :
455 i == gsHighlightSectorY ? FONT_WHITE :
456 MAP_INDEX_COLOR;
457 SetFontForeground(colour_y);
458 FindFontCenterCoordinates(MAP_VERT_INDEX_X, MAP_VERT_INDEX_Y + (i - 1) * MAP_GRID_Y, MAP_HORT_HEIGHT, MAP_GRID_Y, pMapVertIndex[i], MAP_FONT, &usX, &usY);
459 MPrint(usX, usY, pMapVertIndex[i]);
460 }
461
462 InvalidateRegion(MAP_VERT_INDEX_X, MAP_VERT_INDEX_Y, MAP_VERT_INDEX_X + MAP_HORT_HEIGHT, MAP_VERT_INDEX_Y + MAX_VIEW_SECTORS * MAP_GRID_Y);
463 InvalidateRegion(MAP_HORT_INDEX_X, MAP_HORT_INDEX_Y, MAP_HORT_INDEX_X + MAX_VIEW_SECTORS * MAP_GRID_X, MAP_HORT_INDEX_Y + MAP_HORT_HEIGHT);
464 }
465
466 static void HandleShowingOfEnemyForcesInSector(INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ, UINT8 ubIconPosition);
467
468
HandleShowingOfEnemiesWithMilitiaOn(void)469 static void HandleShowingOfEnemiesWithMilitiaOn(void)
470 {
471 for (INT16 sX = 1; sX < MAP_WORLD_X - 1; ++sX)
472 {
473 for (INT16 sY = 1; sY < MAP_WORLD_Y - 1; ++sY)
474 {
475 HandleShowingOfEnemyForcesInSector(sX, sY, iCurrentMapSectorZ, CountAllMilitiaInSector(sX, sY));
476 }
477 }
478 }
479
480
481 static void BlitMineGridMarkers(void);
482 static void BlitMineIcon(INT16 sMapX, INT16 sMapY);
483 static void BlitMineText(UINT8 mine_idx, INT16 sMapX, INT16 sMapY);
484 static void BlitTownGridMarkers(void);
485 static void DisplayLevelString(void);
486 static void DrawBullseye(void);
487 static void DrawSecretSite(const StrategicMapSecretModel*);
488 static void DrawTownMilitiaForcesOnMap();
489 static void HandleLowerLevelMapBlit(void);
490 static void ShadeMapElem(INT16 sMapX, INT16 sMapY, INT32 iColor);
491 static void ShowItemsOnMap(void);
492 static void ShowSAMSitesOnStrategicMap();
493 static void ShowTeamAndVehicles();
494 static void ShowTownText(void);
495
496
DrawMap(void)497 void DrawMap(void)
498 {
499 if (!iCurrentMapSectorZ)
500 {
501 BltVideoSurfaceHalf(guiSAVEBUFFER, guiBIGMAP, MAP_VIEW_START_X + 1, MAP_VIEW_START_Y, NULL);
502
503 // shade map sectors (must be done after Tixa/Orta/Mine icons have been blitted, but before icons!)
504 for (INT16 cnt = 1; cnt < MAP_WORLD_X - 1; ++cnt)
505 {
506 for (INT16 cnt2 = 1; cnt2 < MAP_WORLD_Y - 1; ++cnt2)
507 {
508 if (!GetSectorFlagStatus(cnt, cnt2, iCurrentMapSectorZ, SF_ALREADY_VISITED))
509 {
510 INT32 color;
511 if (fShowAircraftFlag)
512 {
513 if (!StrategicMap[cnt + cnt2 * WORLD_MAP_X].fEnemyAirControlled)
514 {
515 // sector not visited, not air controlled
516 color = MAP_SHADE_DK_GREEN;
517 }
518 else
519 {
520 // sector not visited, controlled and air not
521 color = MAP_SHADE_DK_RED;
522 }
523 }
524 else
525 {
526 // not visited
527 color = MAP_SHADE_BLACK;
528 }
529 ShadeMapElem(cnt, cnt2, color);
530 }
531 else
532 {
533 if (fShowAircraftFlag)
534 {
535 INT32 color;
536 if (!StrategicMap[cnt + cnt2 * WORLD_MAP_X].fEnemyAirControlled)
537 {
538 // sector visited and air controlled
539 color = MAP_SHADE_LT_GREEN;
540 }
541 else
542 {
543 // sector visited but not air controlled
544 color = MAP_SHADE_LT_RED;
545 }
546 ShadeMapElem(cnt, cnt2, color);
547 }
548 }
549 }
550 }
551
552 /* unfortunately, we can't shade these icons as part of shading the map,
553 * because for airspace, the shade function doesn't merely shade the
554 * existing map surface, but instead grabs the original graphics from
555 * bigmap, and changes their palette. blitting icons prior to shading would
556 * mean they don't show up in airspace view at all. */
557
558 for (auto s : GCM->getMapSecrets())
559 {
560 if (!s->isSAMSite // we have other handling for SAM sites
561 && IsSecretFoundAt(s->sectorID))
562 {
563 DrawSecretSite(s);
564 }
565 }
566
567 ShowSAMSitesOnStrategicMap();
568
569 // draw mine icons and descriptive text
570 auto mines = GCM->getMines();
571 for (UINT32 i = 0; i < mines.size(); ++i)
572 {
573 UINT8 const sector = mines[i]->entranceSector;
574 INT16 const x = SECTORX(sector);
575 INT16 const y = SECTORY(sector);
576 BlitMineIcon(x, y);
577 // show mine name/production text
578 if (fShowMineFlag) BlitMineText(i, x, y);
579 }
580
581 // draw towns names & loyalty ratings, and grey town limit borders
582 if (fShowTownFlag)
583 {
584 BlitTownGridMarkers();
585 ShowTownText();
586 }
587
588 if (fShowMilitia) DrawTownMilitiaForcesOnMap();
589
590 if (fShowAircraftFlag && !gfInChangeArrivalSectorMode) DrawBullseye();
591 }
592 else
593 {
594 HandleLowerLevelMapBlit();
595 }
596
597 /* show mine outlines even when viewing underground sublevels - they indicate
598 * where the mine entrances are */
599 if (fShowMineFlag) BlitMineGridMarkers();
600
601 if (fShowTeamFlag)
602 {
603 ShowTeamAndVehicles();
604 }
605 else if (fShowMilitia)
606 {
607 HandleShowingOfEnemiesWithMilitiaOn();
608 }
609
610 if (fShowItemsFlag) ShowItemsOnMap();
611
612 DisplayLevelString();
613 }
614
615
GetScreenXYFromMapXY(INT16 sMapX,INT16 sMapY,INT16 * psX,INT16 * psY)616 void GetScreenXYFromMapXY( INT16 sMapX, INT16 sMapY, INT16 *psX, INT16 *psY )
617 {
618 *psX = ( sMapX * MAP_GRID_X ) + MAP_VIEW_START_X;
619 *psY = ( sMapY * MAP_GRID_Y ) + MAP_VIEW_START_Y;
620 }
621
622 // display the town names and loyalty on the screen
ShowTownText(void)623 static void ShowTownText(void)
624 {
625 SetFont(MAP_FONT);
626 SetFontBackground(FONT_MCOLOR_BLACK);
627 SetFontDestBuffer(guiSAVEBUFFER, MapScreenRect.iLeft + 2, MapScreenRect.iTop, MapScreenRect.iRight, MapScreenRect.iBottom);
628 ClipBlitsToMapViewRegion();
629
630 for (INT8 town = FIRST_TOWN; town < NUM_TOWNS; ++town)
631 {
632 // skip Orta/Tixa until found
633 if (!IsTownFound(town)) continue;
634
635 UINT16 x = MAP_VIEW_START_X + MAP_GRID_X;
636 UINT16 y = MAP_VIEW_START_Y + MAP_GRID_Y;
637 x += pTownPoints[town].iX * MAP_GRID_X / 10;
638 y += pTownPoints[town].iY * MAP_GRID_Y / 10 + 1;
639
640 // if within view region...render, else don't
641 if (x < MAP_VIEW_START_X || MAP_VIEW_START_X + MAP_VIEW_WIDTH < x) continue;
642 if (y < MAP_VIEW_START_Y || MAP_VIEW_START_Y + MAP_VIEW_HEIGHT < y) continue;
643
644 // don't show loyalty string until loyalty tracking for that town has been started
645 if (gTownLoyalty[town].fStarted && gfTownUsesLoyalty[town])
646 {
647 // if loyalty is too low to train militia, and militia training is allowed here
648 UINT8 const colour =
649 gTownLoyalty[town].ubRating < MIN_RATING_TO_TRAIN_TOWN &&
650 MilitiaTrainingAllowedInTown(town) ?
651 FONT_MCOLOR_RED : FONT_MCOLOR_LTGREEN;
652 SetFontForeground(colour);
653
654 ST::string loyalty_str = st_format_printf(gsLoyalString, gTownLoyalty[town].ubRating);
655
656 INT16 loyalty_x = x - StringPixLength(loyalty_str, MAP_FONT) / 2;
657
658 // make sure we don't go past left edge (Grumm)
659 INT16 const min_x = MAP_VIEW_START_X + 23;
660 if (loyalty_x < min_x) loyalty_x = min_x;
661
662 GDirtyPrint(loyalty_x, y + GetFontHeight(MAP_FONT), loyalty_str);
663 }
664 else
665 {
666 SetFontForeground(FONT_MCOLOR_LTGREEN);
667 }
668
669 ST::string name = GCM->getTownName(town);
670 INT16 const name_x = x - StringPixLength(name, MAP_FONT) / 2;
671 GDirtyPrint(name_x, y, name);
672 }
673
674 RestoreClipRegionToFullScreen();
675 }
676
677
678 static void DrawMapBoxIcon(HVOBJECT, UINT16 icon, INT16 sec_x, INT16 sec_y, UINT8 icon_pos);
679
680
681 // "on duty" includes mercs inside vehicles
ShowOnDutyTeam(INT16 const x,INT16 const y)682 static INT32 ShowOnDutyTeam(INT16 const x, INT16 const y)
683 {
684 UINT8 icon_pos = 0;
685 CFOR_EACH_IN_CHAR_LIST(c)
686 {
687 SOLDIERTYPE const& s = *c->merc;
688 if (s.uiStatusFlags & SOLDIER_VEHICLE) continue;
689 if (s.sSectorX != x) continue;
690 if (s.sSectorY != y) continue;
691 if (s.bSectorZ != iCurrentMapSectorZ) continue;
692 if (s.bLife <= 0) continue;
693 if (s.bAssignment >= ON_DUTY && s.bAssignment != VEHICLE) continue;
694 if (InHelicopter(s)) continue;
695 if (PlayerIDGroupInMotion(s.ubGroupID)) continue;
696 DrawMapBoxIcon(guiCHARICONS, SMALL_YELLOW_BOX, x, y, icon_pos++);
697 }
698 return icon_pos;
699 }
700
701
ShowAssignedTeam(INT16 const x,INT16 const y,INT32 icon_pos)702 static INT32 ShowAssignedTeam(INT16 const x, INT16 const y, INT32 icon_pos)
703 {
704 CFOR_EACH_IN_CHAR_LIST(c)
705 {
706 SOLDIERTYPE const& s = *c->merc;
707 // given number of on duty members, find number of assigned chars
708 // start at beginning of list, look for people who are in sector and assigned
709 if (s.uiStatusFlags & SOLDIER_VEHICLE) continue;
710 if (s.sSectorX != x) continue;
711 if (s.sSectorY != y) continue;
712 if (s.bSectorZ != iCurrentMapSectorZ) continue;
713 if (s.bAssignment < ON_DUTY) continue;
714 if (s.bAssignment == VEHICLE) continue;
715 if (s.bAssignment == IN_TRANSIT) continue;
716 if (s.bAssignment == ASSIGNMENT_POW) continue;
717 if (s.bLife <= 0) continue;
718 if (PlayerIDGroupInMotion(s.ubGroupID)) continue;
719
720 DrawMapBoxIcon(guiCHARICONS, SMALL_DULL_YELLOW_BOX, x, y, icon_pos++);
721 }
722 return icon_pos;
723 }
724
725
ShowVehicles(INT16 const x,INT16 const y,INT32 icon_pos)726 static INT32 ShowVehicles(INT16 const x, INT16 const y, INT32 icon_pos)
727 {
728 CFOR_EACH_VEHICLE(v)
729 {
730 // skip the chopper, it has its own icon and displays in airspace mode
731 if (IsHelicopter(v)) continue;
732 if (v.sSectorX != x) continue;
733 if (v.sSectorY != y) continue;
734 if (v.sSectorZ != iCurrentMapSectorZ) continue;
735 if (PlayerIDGroupInMotion(v.ubMovementGroup)) continue;
736
737 SOLDIERTYPE const& vs = GetSoldierStructureForVehicle(v);
738 if (vs.bTeam != OUR_TEAM) continue;
739
740 DrawMapBoxIcon(guiCHARICONS, SMALL_WHITE_BOX, x, y, icon_pos++);
741 }
742 return icon_pos;
743 }
744
745
ShowEnemiesInSector(INT16 const x,INT16 const y,INT16 n_enemies,UINT8 icon_pos)746 static void ShowEnemiesInSector(INT16 const x, INT16 const y, INT16 n_enemies, UINT8 icon_pos)
747 {
748 while (n_enemies-- != 0)
749 {
750 DrawMapBoxIcon(guiCHARICONS, SMALL_RED_BOX, x, y, icon_pos++);
751 }
752 }
753
754
ShowUncertainNumberEnemiesInSector(INT16 const sec_x,INT16 const sec_y)755 static void ShowUncertainNumberEnemiesInSector(INT16 const sec_x, INT16 const sec_y)
756 {
757 INT16 const x = MAP_VIEW_START_X + sec_x * MAP_GRID_X + MAP_X_ICON_OFFSET;
758 INT16 const y = MAP_VIEW_START_Y + sec_y * MAP_GRID_Y - 1;
759 BltVideoObject(guiSAVEBUFFER, guiCHARICONS, SMALL_QUESTION_MARK, x, y);
760 InvalidateRegion(x, y, x + DMAP_GRID_X, y + DMAP_GRID_Y);
761 }
762
763
764 static void ShowPeopleInMotion(INT16 sX, INT16 sY);
765
766
ShowTeamAndVehicles()767 static void ShowTeamAndVehicles()
768 {
769 // Go through each sector, display the on duty, assigned, and vehicles
770 INT32 icon_pos = 0;
771 GROUP const* const g = gfDisplayPotentialRetreatPaths ? gpBattleGroup : 0;
772 for (INT16 x = 1; x != MAP_WORLD_X - 1; ++x)
773 {
774 for (INT16 y = 1; y != MAP_WORLD_Y - 1; ++y)
775 {
776 /* Don't show mercs/vehicles currently in this sector if player is
777 * contemplating retreating from THIS sector */
778 if (!g || x != g->ubSectorX || y != g->ubSectorY)
779 {
780 icon_pos = ShowOnDutyTeam(x, y);
781 icon_pos = ShowAssignedTeam(x, y, icon_pos);
782 icon_pos = ShowVehicles(x, y, icon_pos);
783 }
784
785 HandleShowingOfEnemyForcesInSector(x, y, iCurrentMapSectorZ, icon_pos);
786 ShowPeopleInMotion(x, y);
787 }
788 }
789 }
790
791
ShadeMapElem(const INT16 sMapX,const INT16 sMapY,const INT32 iColor)792 static void ShadeMapElem(const INT16 sMapX, const INT16 sMapY, const INT32 iColor)
793 {
794 INT16 sScreenX;
795 INT16 sScreenY;
796 GetScreenXYFromMapXY(sMapX, sMapY, &sScreenX, &sScreenY);
797
798 // compensate for original BIG_MAP blit being done at MAP_VIEW_START_X + 1
799 sScreenX += 1;
800
801 SGPBox const clip =
802 {
803 (UINT16)(2 * (sScreenX - (MAP_VIEW_START_X + 1))),
804 (UINT16)(2 * (sScreenY - MAP_VIEW_START_Y)),
805 2 * MAP_GRID_X,
806 2 * MAP_GRID_Y
807 };
808
809 // non-airspace
810 if (iColor == MAP_SHADE_BLACK) sScreenY -= 1;
811
812 UINT16* pal;
813 switch (iColor)
814 {
815 case MAP_SHADE_BLACK:
816 // simply shade darker
817 guiSAVEBUFFER->ShadowRect(sScreenX, sScreenY, sScreenX + MAP_GRID_X - 1, sScreenY + MAP_GRID_Y - 1);
818 return;
819
820 case MAP_SHADE_LT_GREEN: pal = pMapLTGreenPalette; break;
821 case MAP_SHADE_DK_GREEN: pal = pMapDKGreenPalette; break;
822 case MAP_SHADE_LT_RED: pal = pMapLTRedPalette; break;
823 case MAP_SHADE_DK_RED: pal = pMapDKRedPalette; break;
824
825 default: return;
826 }
827
828 // get original video surface palette
829 SGPVSurface* const map = guiBIGMAP;
830
831 UINT16* const org_pal = map->p16BPPPalette;
832 map->p16BPPPalette = pal;
833 BltVideoSurfaceHalf(guiSAVEBUFFER, guiBIGMAP, sScreenX, sScreenY, &clip);
834 map->p16BPPPalette = org_pal;
835 }
836
InitializePalettesForMap(void)837 void InitializePalettesForMap(void)
838 {
839 std::unique_ptr<SGPVSurfaceAuto> uiTempMap(AddVideoSurfaceFromFile(INTERFACEDIR "/b_map.pcx"));
840
841 SGPPaletteEntry const* const pal = uiTempMap->GetPalette();
842
843 pMapLTRedPalette = Create16BPPPaletteShaded(pal, 400, 0, 0, TRUE);
844 pMapDKRedPalette = Create16BPPPaletteShaded(pal, 200, 0, 0, TRUE);
845 pMapLTGreenPalette = Create16BPPPaletteShaded(pal, 0, 400, 0, TRUE);
846 pMapDKGreenPalette = Create16BPPPaletteShaded(pal, 0, 200, 0, TRUE);
847 }
848
849
ShutDownPalettesForMap(void)850 void ShutDownPalettesForMap(void)
851 {
852 delete[] pMapLTRedPalette;
853 delete[] pMapDKRedPalette;
854 delete[] pMapLTGreenPalette;
855 delete[] pMapDKGreenPalette;
856
857 pMapLTRedPalette = NULL;
858 pMapDKRedPalette = NULL;
859 pMapLTGreenPalette = NULL;
860 pMapDKGreenPalette = NULL;
861 }
862
863
864 static void CopyPathToCharactersSquadIfInOne(SOLDIERTYPE* pCharacter);
865
866
PlotPathForCharacter(SOLDIERTYPE & s,INT16 const x,INT16 const y,bool const tactical_traversal)867 void PlotPathForCharacter(SOLDIERTYPE& s, INT16 const x, INT16 const y, bool const tactical_traversal)
868 {
869 // Don't build path, if cursor isn't allowed here
870 if (!IsTheCursorAllowedToHighLightThisSector(x, y)) return;
871 // Leave if the character is in transit
872 if (s.bAssignment == IN_TRANSIT) return;
873
874 if (s.bSectorZ != 0)
875 { /* Not on the surface, character won't move until they reach surface, inform
876 * player of this fact */
877 ST::string who =
878 s.bAssignment >= ON_DUTY ? s.name :
879 pLongAssignmentStrings[s.bAssignment];
880 MapScreenMessage(FONT_MCOLOR_DKRED, MSG_INTERFACE, ST::format("{} {}", who, gsUndergroundString));
881 return;
882 }
883
884 bool const vehicle = s.bAssignment == VEHICLE || s.uiStatusFlags & SOLDIER_VEHICLE;
885 if (vehicle) SetUpMvtGroupForVehicle(&s);
886
887 /* Plot a path from current position to x, y: Get last sector in characters
888 * list, build new path, remove tail section, and append onto old list */
889 INT16 const start = GetLastSectorIdInCharactersPath(&s);
890 INT16 const end = x + y * MAP_WORLD_X;
891 PathSt* const path = BuildAStrategicPath(start, end, *GetSoldierGroup(s), tactical_traversal);
892 s.pMercPath = AppendStrategicPath(path, s.pMercPath);
893
894 if (vehicle)
895 {
896 MoveCharactersPathToVehicle(&s);
897 }
898 else
899 {
900 CopyPathToCharactersSquadIfInOne(&s);
901 }
902 }
903
904
PlotATemporaryPathForCharacter(const SOLDIERTYPE * const pCharacter,const INT16 sX,const INT16 sY)905 void PlotATemporaryPathForCharacter(const SOLDIERTYPE* const pCharacter, const INT16 sX, const INT16 sY)
906 {
907 // clear old temp path
908 pTempCharacterPath = ClearStrategicPathList( pTempCharacterPath, -1 );
909
910 // is cursor allowed here?..if not..don't build temp path
911 if( !IsTheCursorAllowedToHighLightThisSector( sX, sY ) )
912 {
913 return;
914 }
915
916 pTempCharacterPath = BuildAStrategicPath(GetLastSectorIdInCharactersPath(pCharacter), sX + sY * MAP_WORLD_X, *GetSoldierGroup(*pCharacter), FALSE);
917 }
918
919
920
921 // clear out character path list, after and including this sector
ClearPathAfterThisSectorForCharacter(SOLDIERTYPE * pCharacter,INT16 sX,INT16 sY)922 UINT32 ClearPathAfterThisSectorForCharacter( SOLDIERTYPE *pCharacter, INT16 sX, INT16 sY )
923 {
924 INT32 iOrigLength = 0;
925 VEHICLETYPE *pVehicle = NULL;
926
927
928 iOrigLength = GetLengthOfMercPath( pCharacter );
929
930 if( !iOrigLength )
931 {
932 // no previous path, nothing to do
933 return( ABORT_PLOTTING );
934 }
935
936
937 // if we're clearing everything beyond the current sector, that's quite different. Since we're basically cancelling
938 // his movement completely, we must also make sure his next X,Y are changed and he officially "returns" to his sector
939 if ( ( sX == pCharacter->sSectorX ) && ( sY == pCharacter->sSectorY ) )
940 {
941 // if we're in confirm map move mode, cancel that (before new UI messages are issued)
942 EndConfirmMapMoveMode( );
943
944 CancelPathsOfAllSelectedCharacters();
945 return( PATH_CLEARED );
946 }
947 else // click not in the current sector
948 {
949 // if the clicked sector is along current route, this will repath only as far as it. If not, the entire path will
950 // be canceled.
951
952 // if a vehicle
953 if( pCharacter->uiStatusFlags & SOLDIER_VEHICLE )
954 {
955 pVehicle = &( pVehicleList[ pCharacter->bVehicleID ] );
956 }
957 // or in a vehicle
958 else if( pCharacter->bAssignment == VEHICLE )
959 {
960 pVehicle = &( pVehicleList[ pCharacter->iVehicleId ] );
961 }
962 else
963 {
964 // foot soldier
965 pCharacter->pMercPath = ClearStrategicPathListAfterThisSector( pCharacter->pMercPath, sX, sY, pCharacter->ubGroupID );
966 }
967
968 // if there's an associated vehicle structure
969 if ( pVehicle != NULL )
970 {
971 // do it for the vehicle, too
972 pVehicle->pMercPath = ClearStrategicPathListAfterThisSector( pVehicle->pMercPath, sX, sY, pVehicle->ubMovementGroup );
973 }
974
975 if( GetLengthOfMercPath( pCharacter ) < iOrigLength )
976 {
977 CopyPathToAllSelectedCharacters( pCharacter->pMercPath );
978 // path WAS actually shortened
979 return( PATH_SHORTENED );
980 }
981 else
982 {
983 // same path as before - it's not any shorter
984 return ( ABORT_PLOTTING );
985 }
986 }
987 }
988
989
CancelPathForCharacter(SOLDIERTYPE * pCharacter)990 void CancelPathForCharacter( SOLDIERTYPE *pCharacter )
991 {
992 // clear out character's entire path list, he and his squad will stay/return to his current sector.
993 pCharacter->pMercPath = ClearStrategicPathList( pCharacter->pMercPath, pCharacter->ubGroupID );
994 // NOTE: This automatically calls RemoveGroupWaypoints() internally for valid movement groups
995
996 // This causes the group to effectively reverse directions (even if they've never actually left), so handle that.
997 // They are going to return to their current X,Y sector.
998 RebuildWayPointsForGroupPath(pCharacter->pMercPath, *GetGroup(pCharacter->ubGroupID));
999 // GroupReversingDirectionsBetweenSectors( GetGroup( pCharacter->ubGroupID ), ( UINT8 )( pCharacter->sSectorX ), ( UINT8 )( pCharacter->sSectorY ), FALSE );
1000
1001
1002 // if he's in a vehicle, clear out the vehicle, too
1003 if( pCharacter->bAssignment == VEHICLE )
1004 {
1005 CancelPathForVehicle(pVehicleList[pCharacter->iVehicleId], TRUE);
1006 }
1007 else
1008 {
1009 // display "travel route canceled" message
1010 BeginMapUIMessage(0, pMapPlotStrings[3]);
1011 }
1012
1013
1014 CopyPathToCharactersSquadIfInOne( pCharacter );
1015
1016 fMapPanelDirty = TRUE;
1017 fTeamPanelDirty = TRUE;
1018 fCharacterInfoPanelDirty = TRUE; // to update ETA
1019 }
1020
1021
CancelPathForVehicle(VEHICLETYPE & v,BOOLEAN const fAlreadyReversed)1022 void CancelPathForVehicle(VEHICLETYPE& v, BOOLEAN const fAlreadyReversed)
1023 {
1024 // we're clearing everything beyond the *current* sector, that's quite different. Since we're basically cancelling
1025 // his movement completely, we must also make sure his next X,Y are changed and he officially "returns" to his sector
1026 v.pMercPath = ClearStrategicPathList(v.pMercPath, v.ubMovementGroup);
1027 // NOTE: This automatically calls RemoveGroupWaypoints() internally for valid movement groups
1028
1029 // if we already reversed one of the passengers, flag will be TRUE,
1030 // don't do it again or we're headed back where we came from!
1031 if ( !fAlreadyReversed )
1032 {
1033 // This causes the group to effectively reverse directions (even if they've never actually left), so handle that.
1034 // They are going to return to their current X,Y sector.
1035 RebuildWayPointsForGroupPath(v.pMercPath, *GetGroup(v.ubMovementGroup));
1036 }
1037
1038 // display "travel route canceled" message
1039 BeginMapUIMessage(0, pMapPlotStrings[3]);
1040
1041 // turn the helicopter flag off here, this prevents the "route aborted" msg from coming up
1042 fPlotForHelicopter = FALSE;
1043
1044 fTeamPanelDirty = TRUE;
1045 fMapPanelDirty = TRUE;
1046 fCharacterInfoPanelDirty = TRUE; // to update ETA
1047 }
1048
1049
CopyPathToCharactersSquadIfInOne(SOLDIERTYPE * const s)1050 static void CopyPathToCharactersSquadIfInOne(SOLDIERTYPE* const s)
1051 {
1052 // check if on a squad, if so, do same thing for all characters
1053
1054 // check to see if character is on a squad, if so, copy path to squad
1055 if (s->bAssignment < ON_DUTY)
1056 {
1057 CopyPathOfCharacterToSquad(s, s->bAssignment);
1058 }
1059 }
1060
1061
1062 static void AnimateRoute(PathSt* pPath);
1063 static void TracePathRoute(PathSt*);
1064
1065
DisplaySoldierPath(SOLDIERTYPE * pCharacter)1066 void DisplaySoldierPath( SOLDIERTYPE *pCharacter )
1067 {
1068 PathSt* const pPath = GetSoldierMercPathPtr(pCharacter);
1069 // trace real route
1070 TracePathRoute(pPath);
1071 AnimateRoute( pPath );
1072 }
1073
1074
DisplaySoldierTempPath(void)1075 void DisplaySoldierTempPath(void)
1076 {
1077 // now render temp route
1078 TracePathRoute(pTempCharacterPath);
1079 }
1080
1081
1082
DisplayHelicopterPath(void)1083 void DisplayHelicopterPath( void )
1084 {
1085 // clip to map
1086 ClipBlitsToMapViewRegion( );
1087
1088 VEHICLETYPE const& v = GetHelicopter();
1089 // trace both lists..temp is conditional if cursor has sat in same sector grid long enough
1090 TracePathRoute(v.pMercPath);
1091 AnimateRoute(v.pMercPath);
1092
1093 // restore
1094 RestoreClipRegionToFullScreen( );
1095 }
1096
1097
DisplayHelicopterTempPath(void)1098 void DisplayHelicopterTempPath( void )
1099 {
1100 //should we draw temp path?
1101 if (fDrawTempHeliPath) TracePathRoute(pTempHelicopterPath);
1102 }
1103
1104
PlotPathForHelicopter(const INT16 sX,const INT16 sY)1105 void PlotPathForHelicopter(const INT16 sX, const INT16 sY)
1106 {
1107 // will plot the path for the helicopter
1108
1109 // no heli...go back
1110 if (!fShowAircraftFlag || iHelicopterVehicleId == -1) return;
1111
1112 // is cursor allowed here?..if not..don't build path
1113 if (!IsTheCursorAllowedToHighLightThisSector(sX, sY)) return;
1114
1115 // set up mvt group for helicopter
1116 SetUpHelicopterForMovement();
1117
1118 VEHICLETYPE& v = GetHelicopter();
1119 // will plot a path from current position to sX, sY
1120 // get last sector in helicopters list, build new path, remove tail section, move to beginning of list, and append onto old list
1121 v.pMercPath = AppendStrategicPath(BuildAStrategicPath(GetLastSectorOfHelicoptersPath(), (INT16)(sX + sY * MAP_WORLD_X), *GetGroup(v.ubMovementGroup), FALSE), v.pMercPath);
1122
1123 fMapPanelDirty = TRUE;
1124 }
1125
1126
PlotATemporaryPathForHelicopter(INT16 sX,INT16 sY)1127 void PlotATemporaryPathForHelicopter( INT16 sX, INT16 sY )
1128 {
1129 // clear old temp path
1130 pTempHelicopterPath = ClearStrategicPathList(pTempHelicopterPath, 0);
1131
1132 // is cursor allowed here?..if not..don't build temp path
1133 if( !IsTheCursorAllowedToHighLightThisSector( sX, sY ) )
1134 {
1135 return;
1136 }
1137
1138 // build path
1139 pTempHelicopterPath = BuildAStrategicPath(GetLastSectorOfHelicoptersPath(), sX + sY * MAP_WORLD_X, *GetGroup(GetHelicopter().ubMovementGroup), FALSE);
1140 }
1141
1142
1143 // clear out helicopter path list, after and including this sector
ClearPathAfterThisSectorForHelicopter(INT16 sX,INT16 sY)1144 UINT32 ClearPathAfterThisSectorForHelicopter( INT16 sX, INT16 sY )
1145 {
1146 INT32 iOrigLength = 0;
1147
1148 // clear out helicopter path list, after and including this sector
1149 if( iHelicopterVehicleId == -1 || !CanHelicopterFly() )
1150 {
1151 // abort plotting, shouldn't even be here
1152 return( ABORT_PLOTTING );
1153 }
1154
1155 VEHICLETYPE& v = GetHelicopter();
1156
1157 iOrigLength = GetLengthOfPath(v.pMercPath);
1158 if( !iOrigLength )
1159 {
1160 // no previous path, nothing to do, and we didn't shorten it
1161 return( ABORT_PLOTTING );
1162 }
1163
1164
1165 // are we clearing everything beyond the helicopter's CURRENT sector?
1166 if (sX == v.sSectorX && sY == v.sSectorY)
1167 {
1168 // if we're in confirm map move mode, cancel that (before new UI messages are issued)
1169 EndConfirmMapMoveMode( );
1170
1171 CancelPathForVehicle(v, FALSE);
1172 return( PATH_CLEARED );
1173 }
1174 else // click not in the current sector
1175 {
1176 // if the clicked sector is along current route, this will repath only as far as it. If not, the entire path will
1177 // be canceled.
1178 v.pMercPath = ClearStrategicPathListAfterThisSector(v.pMercPath, sX, sY, v.ubMovementGroup);
1179
1180 if (GetLengthOfPath(v.pMercPath) < iOrigLength)
1181 {
1182 // really shortened!
1183 return( PATH_SHORTENED );
1184 }
1185 else
1186 {
1187 // same path as before - it's not any shorter
1188 return ( ABORT_PLOTTING );
1189 }
1190 }
1191 }
1192
1193
1194
GetLastSectorOfHelicoptersPath(void)1195 INT16 GetLastSectorOfHelicoptersPath( void )
1196 {
1197 VEHICLETYPE const& v = GetHelicopter();
1198 // will return the last sector of the helicopter's current path
1199 INT16 sLastSector = v.sSectorX + v.sSectorY * MAP_WORLD_X;
1200 PathSt* pNode = v.pMercPath;
1201
1202 while( pNode )
1203 {
1204 sLastSector = ( INT16 ) ( pNode->uiSectorId );
1205 pNode = pNode->pNext;
1206 }
1207
1208 return sLastSector;
1209 }
1210
1211
1212 // trace a route for a passed path...doesn't require dest char - most more general
TracePathRoute(PathSt * const pPath)1213 static void TracePathRoute(PathSt* const pPath)
1214 {
1215 if (pPath == NULL) return;
1216 Assert(pPath->pPrev == NULL);
1217
1218 INT32 iDirection = -1;
1219 INT32 iArrow = -1;
1220 const PathSt* prev = NULL;
1221 const PathSt* next;
1222 for (const PathSt* node = pPath; node != NULL; prev = node, node = next)
1223 {
1224 next = node->pNext;
1225
1226 BOOLEAN fUTurnFlag = FALSE;
1227 INT32 iArrowX;
1228 INT32 iArrowY;
1229 INT32 iX;
1230 INT32 iY;
1231 if (prev && next)
1232 {
1233 const INT32 iDeltaA = (INT16)node->uiSectorId - (INT16)prev->uiSectorId;
1234 const INT32 iDeltaB = (INT16)node->uiSectorId - (INT16)next->uiSectorId;
1235 if (iDeltaA == 0) return;
1236
1237 iX = node->uiSectorId % MAP_WORLD_X;
1238 iY = node->uiSectorId / MAP_WORLD_X;
1239 iX = iX * MAP_GRID_X + MAP_VIEW_START_X;
1240 iY = iY * MAP_GRID_Y + MAP_VIEW_START_Y;
1241
1242 iArrowX = iX;
1243 iArrowY = iY;
1244 if (prev->pPrev && next->pNext)
1245 {
1246 // check to see if out-of sector U-turn
1247 // for placement of arrows
1248 const INT32 iDeltaB1 = next->uiSectorId - next->pNext->uiSectorId;
1249 fUTurnFlag =
1250 (iDeltaB1 == -WORLD_MAP_X && iDeltaA == -WORLD_MAP_X && iDeltaB == -1) ||
1251 (iDeltaB1 == -WORLD_MAP_X && iDeltaA == -WORLD_MAP_X && iDeltaB == 1) ||
1252 (iDeltaB1 == WORLD_MAP_X && iDeltaA == WORLD_MAP_X && iDeltaB == -1) ||
1253 (iDeltaB1 == WORLD_MAP_X && iDeltaA == WORLD_MAP_X && iDeltaB == 1) ||
1254 (iDeltaB1 == -1 && iDeltaA == -1 && iDeltaB == -WORLD_MAP_X) ||
1255 (iDeltaB1 == -1 && iDeltaA == -1 && iDeltaB == WORLD_MAP_X) ||
1256 (iDeltaB1 == 1 && iDeltaA == 1 && iDeltaB == -WORLD_MAP_X) ||
1257 (iDeltaB1 == 1 && iDeltaA == 1 && iDeltaB == WORLD_MAP_X);
1258 }
1259
1260 if (prev->uiSectorId == next->uiSectorId)
1261 {
1262 if (prev->uiSectorId + WORLD_MAP_X == node->uiSectorId)
1263 {
1264 iDirection = S_TO_N_LINE;
1265 iArrow = NORTH_ARROW;
1266 iArrowX += NORTH_OFFSET_X;
1267 iArrowY += NORTH_OFFSET_Y;
1268 }
1269 else if (prev->uiSectorId - WORLD_MAP_X == node->uiSectorId)
1270 {
1271 iDirection = N_TO_S_LINE;
1272 iArrow = SOUTH_ARROW;
1273 iArrowX += SOUTH_OFFSET_X;
1274 iArrowY += SOUTH_OFFSET_Y;
1275 }
1276 else if (prev->uiSectorId + 1 == node->uiSectorId)
1277 {
1278 iDirection = E_TO_W_LINE;
1279 iArrow = WEST_ARROW;
1280 iArrowX += WEST_OFFSET_X;
1281 iArrowY += WEST_OFFSET_Y;
1282 }
1283 else
1284 {
1285 iDirection = W_TO_E_LINE;
1286 iArrow = EAST_ARROW;
1287 iArrowX += EAST_OFFSET_X;
1288 iArrowY += EAST_OFFSET_Y;
1289 }
1290 }
1291 else
1292 {
1293 if (iDeltaA == -1 && iDeltaB == 1)
1294 {
1295 iDirection = WEST_LINE;
1296 iArrow = WEST_ARROW;
1297 iArrowX += WEST_OFFSET_X;
1298 iArrowY += WEST_OFFSET_Y;
1299 }
1300 else if (iDeltaA == 1 && iDeltaB == -1)
1301 {
1302 iDirection = EAST_LINE;
1303 iArrow = EAST_ARROW;
1304 iArrowX += EAST_OFFSET_X;
1305 iArrowY += EAST_OFFSET_Y;
1306 }
1307 else if (iDeltaA == -WORLD_MAP_X && iDeltaB == WORLD_MAP_X)
1308 {
1309 iDirection = NORTH_LINE;
1310 iArrow = NORTH_ARROW;
1311 iArrowX += NORTH_OFFSET_X;
1312 iArrowY += NORTH_OFFSET_Y;
1313 }
1314 else if (iDeltaA == WORLD_MAP_X && iDeltaB == -WORLD_MAP_X)
1315 {
1316 iDirection = SOUTH_LINE;
1317 iArrow = SOUTH_ARROW;
1318 iArrowX += SOUTH_OFFSET_X;
1319 iArrowY += SOUTH_OFFSET_Y;
1320 }
1321 else if (iDeltaA == -WORLD_MAP_X && iDeltaB == -1)
1322 {
1323 iDirection = N_TO_E_LINE;
1324 iArrow = EAST_ARROW;
1325 iArrowX += EAST_OFFSET_X;
1326 iArrowY += EAST_OFFSET_Y;
1327 }
1328 else if (iDeltaA == WORLD_MAP_X && iDeltaB == 1)
1329 {
1330 iDirection = S_TO_W_LINE;
1331 iArrow = WEST_ARROW;
1332 iArrowX += WEST_OFFSET_X;
1333 iArrowY += WEST_OFFSET_Y;
1334 }
1335 else if (iDeltaA == 1 && iDeltaB == -WORLD_MAP_X)
1336 {
1337 iDirection = E_TO_S_LINE;
1338 iArrow = SOUTH_ARROW;
1339 iArrowX += SOUTH_OFFSET_X;
1340 iArrowY += SOUTH_OFFSET_Y;
1341 }
1342 else if (iDeltaA == -1 && iDeltaB == WORLD_MAP_X)
1343 {
1344 iDirection = W_TO_N_LINE;
1345 iArrow = NORTH_ARROW;
1346 iArrowX += NORTH_OFFSET_X;
1347 iArrowY += NORTH_OFFSET_Y;
1348 }
1349 else if (iDeltaA == -1 && iDeltaB == -WORLD_MAP_X)
1350 {
1351 iDirection = W_TO_S_LINE;
1352 iArrow = SOUTH_ARROW;
1353 iArrowX += SOUTH_OFFSET_X;
1354 iArrowY += SOUTH_OFFSET_Y + WEST_TO_SOUTH_OFFSET_Y;
1355 }
1356 else if (iDeltaA == -WORLD_MAP_X && iDeltaB == 1)
1357 {
1358 iDirection = N_TO_W_LINE;
1359 iArrow = WEST_ARROW;
1360 iArrowX += WEST_OFFSET_X;
1361 iArrowY += WEST_OFFSET_Y;
1362 }
1363 else if (iDeltaA == WORLD_MAP_X && iDeltaB == -1)
1364 {
1365 iDirection = S_TO_E_LINE;
1366 iArrow = EAST_ARROW;
1367 iArrowX += EAST_OFFSET_X;
1368 iArrowY += EAST_OFFSET_Y;
1369 }
1370 else if (iDeltaA == 1 && iDeltaB == WORLD_MAP_X)
1371 {
1372 iDirection = E_TO_N_LINE;
1373 iArrow = NORTH_ARROW;
1374 iArrowX += NORTH_OFFSET_X;
1375 iArrowY += NORTH_OFFSET_Y + EAST_TO_NORTH_OFFSET_Y;
1376 }
1377 }
1378 }
1379 else
1380 {
1381 iX = node->uiSectorId % MAP_WORLD_X;
1382 iY = node->uiSectorId / MAP_WORLD_X;
1383 iX = iX * MAP_GRID_X + MAP_VIEW_START_X;
1384 iY = iY * MAP_GRID_Y + MAP_VIEW_START_Y;
1385
1386 iArrowX = iX;
1387 iArrowY = iY;
1388 // display enter and exit 'X's
1389 if (prev)
1390 {
1391 fUTurnFlag = TRUE;
1392 const INT32 iDeltaA = (INT16)node->uiSectorId - (INT16)prev->uiSectorId;
1393 if (iDeltaA == -1)
1394 {
1395 iDirection = RED_X_WEST;
1396 }
1397 else if (iDeltaA == 1)
1398 {
1399 iDirection = RED_X_EAST;
1400 }
1401 else if (iDeltaA == -WORLD_MAP_X)
1402 {
1403 iDirection = RED_X_NORTH;
1404 }
1405 else
1406 {
1407 iDirection = RED_X_SOUTH;
1408 }
1409 }
1410 if (next)
1411 {
1412 fUTurnFlag = FALSE;
1413 const INT32 iDeltaB = (INT16)node->uiSectorId - (INT16)next->uiSectorId;
1414 if (iDeltaB == -1)
1415 {
1416 iDirection = GREEN_X_EAST;
1417 iArrow = EAST_ARROW;
1418 iArrowX += EAST_OFFSET_X;
1419 iArrowY += EAST_OFFSET_Y;
1420 }
1421 else if (iDeltaB == 1)
1422 {
1423 iDirection = GREEN_X_WEST;
1424 iArrow = WEST_ARROW;
1425 iArrowX += WEST_OFFSET_X;
1426 iArrowY += WEST_OFFSET_Y;
1427 }
1428 else if (iDeltaB == WORLD_MAP_X)
1429 {
1430 iDirection = GREEN_X_NORTH;
1431 iArrow = NORTH_ARROW;
1432 iArrowX += NORTH_OFFSET_X;
1433 iArrowY += NORTH_OFFSET_Y;
1434 }
1435 else
1436 {
1437 iDirection = GREEN_X_SOUTH;
1438 iArrow = SOUTH_ARROW;
1439 iArrowX += SOUTH_OFFSET_X;
1440 iArrowY += SOUTH_OFFSET_Y;
1441 }
1442 }
1443 }
1444
1445 if (iDirection == -1) continue;
1446
1447 if (MAP_VIEW_START_X < iX && iX < SCREEN_WIDTH - MAP_GRID_X * 2 &&
1448 MAP_VIEW_START_Y < iY && iY < MAP_VIEW_START_Y + MAP_VIEW_HEIGHT
1449 )
1450 {
1451 BltVideoObject(FRAME_BUFFER, guiMAPCURSORS, iDirection, iX, iY);
1452
1453 if (!fUTurnFlag)
1454 {
1455 BltVideoObject(FRAME_BUFFER, guiMAPCURSORS, (UINT16)iArrow, iArrowX, iArrowY);
1456 InvalidateRegion(iArrowX, iArrowY, iArrowX + 2 * MAP_GRID_X, iArrowY + 2 * MAP_GRID_Y);
1457 }
1458
1459 InvalidateRegion(iX, iY, iX + 2 * MAP_GRID_X, iY + 2 * MAP_GRID_Y);
1460 }
1461 }
1462 }
1463
1464
1465 static BOOLEAN TraceCharAnimatedRoute(PathSt* pPath, BOOLEAN fForceUpDate);
1466
1467
AnimateRoute(PathSt * pPath)1468 static void AnimateRoute(PathSt* pPath)
1469 {
1470 SetFontDestBuffer(FRAME_BUFFER);
1471
1472 // the animated path
1473 if (TraceCharAnimatedRoute(pPath, FALSE))
1474 {
1475 // ARM? Huh? Why the same thing twice more?
1476 TraceCharAnimatedRoute(pPath, TRUE);
1477 TraceCharAnimatedRoute(pPath, TRUE);
1478 }
1479 }
1480
1481
TraceCharAnimatedRoute(PathSt * const pPath,const BOOLEAN fForceUpDate)1482 static BOOLEAN TraceCharAnimatedRoute(PathSt* const pPath, const BOOLEAN fForceUpDate)
1483 {
1484 static PathSt* pCurrentNode = NULL;
1485 static BOOLEAN fUpDateFlag=FALSE;
1486 static BOOLEAN fPauseFlag=TRUE;
1487 static UINT8 ubCounter=1;
1488
1489 INT32 iDifference=0;
1490 INT32 iArrow=-1;
1491 INT32 iX = 0, iY = 0;
1492 INT32 iPastX, iPastY;
1493 INT32 iArrowX, iArrowY;
1494 INT32 iDeltaA, iDeltaB, iDeltaB1;
1495 INT32 iDirection = -1;
1496 BOOLEAN fUTurnFlag=FALSE;
1497 BOOLEAN fNextNode=FALSE;
1498 PathSt* pTempNode = NULL;
1499 PathSt* pNode = NULL;
1500 PathSt* pPastNode = NULL;
1501 PathSt* pNextNode = NULL;
1502
1503
1504 // must be plotting movement
1505 if (bSelectedDestChar == -1 && !fPlotForHelicopter)
1506 {
1507 return FALSE;
1508 }
1509
1510 // if any nodes have been deleted, reset current node to beginning of the list
1511 if( fDeletedNode )
1512 {
1513 fDeletedNode = FALSE;
1514 pCurrentNode = NULL;
1515 }
1516
1517
1518 // Valid path?
1519 if ( pPath == NULL )
1520 {
1521 return FALSE;
1522 }
1523 else
1524 {
1525 if(pCurrentNode==NULL)
1526 {
1527 pCurrentNode = pPath;
1528 }
1529 }
1530
1531 // Check Timer
1532 if (giAnimateRouteBaseTime==0)
1533 {
1534 giAnimateRouteBaseTime=GetJA2Clock();
1535 return FALSE;
1536 }
1537
1538 // check difference in time
1539 iDifference=GetJA2Clock()-giAnimateRouteBaseTime;
1540
1541 // if pause flag, check time, if time passed, reset, continue on, else return
1542 if(fPauseFlag)
1543 {
1544 if(iDifference < PAUSE_DELAY)
1545 {
1546 return FALSE;
1547 }
1548 else
1549 {
1550 fPauseFlag=FALSE;
1551 giAnimateRouteBaseTime=GetJA2Clock();
1552 }
1553 }
1554
1555
1556 // if is checkflag and change in status, return TRUE;
1557 if(!fForceUpDate)
1558 {
1559 if(iDifference < ARROW_DELAY)
1560 {
1561 if (!fUpDateFlag)
1562 return FALSE;
1563 }
1564 else
1565 {
1566 // sufficient time, update base time
1567 giAnimateRouteBaseTime=GetJA2Clock();
1568 fUpDateFlag=!fUpDateFlag;
1569
1570 fNextNode=TRUE;
1571 }
1572 }
1573
1574 // check to see if Current node has not been deleted
1575 pTempNode = pPath;
1576
1577 while(pTempNode)
1578 {
1579 if(pTempNode==pCurrentNode)
1580 {
1581 //not deleted
1582 //reset pause flag
1583 break;
1584 }
1585 else
1586 pTempNode=pTempNode->pNext;
1587 }
1588
1589 // if deleted, restart at beginnning
1590 if(pTempNode==NULL)
1591 {
1592 pCurrentNode = pPath;
1593
1594 // set pause flag
1595 if(!pCurrentNode)
1596 return FALSE;
1597
1598 }
1599
1600 // Handle drawing of arrow
1601 pNode=pCurrentNode;
1602 if((!pNode->pPrev)&&(ubCounter==1)&&(fForceUpDate))
1603 {
1604 ubCounter=0;
1605 return FALSE;
1606 }
1607 else if((ubCounter==1)&&(fForceUpDate))
1608 {
1609 pNode=pCurrentNode->pPrev;
1610 }
1611 if (pNode->pNext)
1612 pNextNode=pNode->pNext;
1613 else
1614 pNextNode=NULL;
1615
1616 if (pNode->pPrev)
1617 pPastNode=pNode->pPrev;
1618 else
1619 pPastNode=NULL;
1620
1621 // go through characters list and display arrows for path
1622 fUTurnFlag=FALSE;
1623 if ((pPastNode)&&(pNextNode))
1624 {
1625 iDeltaA=(INT16)pNode->uiSectorId-(INT16)pPastNode->uiSectorId;
1626 iDeltaB=(INT16)pNode->uiSectorId-(INT16)pNextNode->uiSectorId;
1627 if (iDeltaA ==0)
1628 return FALSE;
1629
1630 iX=(pNode->uiSectorId%MAP_WORLD_X);
1631 iY=(pNode->uiSectorId/MAP_WORLD_X);
1632 iX=(iX*MAP_GRID_X)+MAP_VIEW_START_X;
1633 iY=(iY*MAP_GRID_Y)+MAP_VIEW_START_Y;
1634
1635 iArrowX=iX;
1636 iArrowY=iY;
1637 if ((pPastNode->pPrev)&&(pNextNode->pNext))
1638 {
1639 fUTurnFlag=FALSE;
1640 // check to see if out-of sector U-turn
1641 // for placement of arrows
1642 iDeltaB1=pNextNode->uiSectorId-pNextNode->pNext->uiSectorId;
1643 if ((iDeltaB1==-WORLD_MAP_X)&&(iDeltaA==-WORLD_MAP_X)&&(iDeltaB==-1))
1644 {
1645 fUTurnFlag=TRUE;
1646 }
1647 else if((iDeltaB1==-WORLD_MAP_X)&&(iDeltaA==-WORLD_MAP_X)&&(iDeltaB==1))
1648 {
1649 fUTurnFlag=TRUE;
1650 }
1651 else if((iDeltaB1==WORLD_MAP_X)&&(iDeltaA==WORLD_MAP_X)&&(iDeltaB==-1))
1652 {
1653 fUTurnFlag=TRUE;
1654 }
1655 else if((iDeltaB1==WORLD_MAP_X)&&(iDeltaA==WORLD_MAP_X)&&(iDeltaB==1))
1656 {
1657 fUTurnFlag=TRUE;
1658 }
1659 else if((iDeltaB1==-1)&&(iDeltaA==-1)&&(iDeltaB==-WORLD_MAP_X))
1660 {
1661 fUTurnFlag=TRUE;
1662 }
1663 else if((iDeltaB1==-1)&&(iDeltaA==-1)&&(iDeltaB==WORLD_MAP_X))
1664 {
1665 fUTurnFlag=TRUE;
1666 }
1667 else if((iDeltaB1==1)&&(iDeltaA==1)&&(iDeltaB==-WORLD_MAP_X))
1668 {
1669 fUTurnFlag=TRUE;
1670 }
1671 else if((iDeltaB1==1)&&(iDeltaA==1)&&(iDeltaB==WORLD_MAP_X))
1672 {
1673 fUTurnFlag=TRUE;
1674 }
1675 else
1676 fUTurnFlag=FALSE;
1677 }
1678
1679
1680 if (pPastNode->uiSectorId==pNextNode->uiSectorId)
1681 {
1682 if (pPastNode->uiSectorId + WORLD_MAP_X == pNode->uiSectorId)
1683 {
1684 iDirection = S_TO_N_LINE;
1685 if (!ubCounter)
1686 iArrow = W_NORTH_ARROW;
1687 else
1688 iArrow = NORTH_ARROW;
1689
1690 iArrowX += NORTH_OFFSET_X;
1691 iArrowY += NORTH_OFFSET_Y;
1692 }
1693 else if(pPastNode->uiSectorId-WORLD_MAP_X==pNode->uiSectorId)
1694 {
1695 iDirection=N_TO_S_LINE;
1696 if(!ubCounter)
1697 iArrow=W_SOUTH_ARROW;
1698 else
1699 iArrow=SOUTH_ARROW;
1700 iArrowX+=SOUTH_OFFSET_X;
1701 iArrowY+=SOUTH_OFFSET_Y;
1702 }
1703 else if (pPastNode->uiSectorId+1==pNode->uiSectorId)
1704 {
1705 iDirection=E_TO_W_LINE;
1706 if(!ubCounter)
1707 iArrow=W_WEST_ARROW;
1708 else
1709 iArrow=WEST_ARROW;
1710 iArrowX+=WEST_OFFSET_X;
1711 iArrowY+=WEST_OFFSET_Y;
1712 }
1713 else
1714 {
1715 iDirection=W_TO_E_LINE;
1716 if(!ubCounter)
1717 iArrow=W_EAST_ARROW;
1718 else
1719 iArrow=EAST_ARROW;
1720 iArrowX+=EAST_OFFSET_X;
1721 iArrowY+=EAST_OFFSET_Y;
1722 }
1723 }
1724 else
1725 {
1726 if ((iDeltaA==-1)&&(iDeltaB==1))
1727 {
1728 iDirection = WEST_LINE;
1729 if (!ubCounter)
1730 iArrow = W_WEST_ARROW;
1731 else
1732 iArrow = WEST_ARROW;
1733
1734
1735 iArrowX += WEST_OFFSET_X;
1736 iArrowY += WEST_OFFSET_Y;
1737 }
1738 else if((iDeltaA==1)&&(iDeltaB==-1))
1739 {
1740 iDirection = EAST_LINE;
1741 if (!ubCounter)
1742 iArrow = W_EAST_ARROW;
1743 else
1744 iArrow = EAST_ARROW;
1745
1746 iArrowX += EAST_OFFSET_X;
1747 iArrowY += EAST_OFFSET_Y;
1748 }
1749 else if((iDeltaA==1)&&(iDeltaB==-1))
1750 {
1751 iDirection=EAST_LINE;
1752 if(!ubCounter)
1753 iArrow=W_EAST_ARROW;
1754 else
1755 iArrow=EAST_ARROW;
1756
1757 iArrowX+=EAST_OFFSET_X;
1758 iArrowY+=EAST_OFFSET_Y;
1759 }
1760 else if((iDeltaA==-WORLD_MAP_X)&&(iDeltaB==WORLD_MAP_X))
1761 {
1762 iDirection=NORTH_LINE;
1763 if(!ubCounter)
1764 iArrow=W_NORTH_ARROW;
1765 else
1766 iArrow=NORTH_ARROW;
1767
1768 iArrowX+=NORTH_OFFSET_X;
1769 iArrowY+=NORTH_OFFSET_Y;
1770 }
1771 else if((iDeltaA==WORLD_MAP_X)&&(iDeltaB==-WORLD_MAP_X))
1772 {
1773 iDirection=SOUTH_LINE;
1774 if(!ubCounter)
1775 iArrow=W_SOUTH_ARROW;
1776 else
1777 iArrow=SOUTH_ARROW;
1778
1779 iArrowX+=SOUTH_OFFSET_X;
1780 iArrowY+=SOUTH_OFFSET_Y;
1781 }
1782 else if((iDeltaA==-WORLD_MAP_X)&&(iDeltaB==-1))
1783 {
1784 iDirection=N_TO_E_LINE;
1785 if(!ubCounter)
1786 iArrow=W_EAST_ARROW;
1787 else
1788 iArrow=EAST_ARROW;
1789
1790 iArrowX+=EAST_OFFSET_X;
1791 iArrowY+=EAST_OFFSET_Y;
1792 }
1793 else if((iDeltaA==WORLD_MAP_X)&&(iDeltaB==1))
1794 {
1795 iDirection=S_TO_W_LINE;
1796 if(!ubCounter)
1797 iArrow=W_WEST_ARROW;
1798 else
1799 iArrow=WEST_ARROW;
1800
1801
1802 iArrowX+=WEST_OFFSET_X;
1803 iArrowY+=WEST_OFFSET_Y;
1804 }
1805 else if((iDeltaA==1)&&(iDeltaB==-WORLD_MAP_X))
1806 {
1807 iDirection=E_TO_S_LINE;
1808 if(!ubCounter)
1809 iArrow=W_SOUTH_ARROW;
1810 else
1811 iArrow=SOUTH_ARROW;
1812
1813 iArrowX+=SOUTH_OFFSET_X;
1814 iArrowY+=SOUTH_OFFSET_Y;
1815 }
1816 else if ((iDeltaA==-1)&&(iDeltaB==WORLD_MAP_X))
1817 {
1818 iDirection=W_TO_N_LINE;
1819 if(!ubCounter)
1820 iArrow=W_NORTH_ARROW;
1821 else
1822 iArrow=NORTH_ARROW;
1823
1824 iArrowX+=NORTH_OFFSET_X;
1825 iArrowY+=NORTH_OFFSET_Y;
1826 }
1827 else if ((iDeltaA==-1)&&(iDeltaB==-WORLD_MAP_X))
1828 {
1829 iDirection=W_TO_S_LINE;
1830 if(!ubCounter)
1831 iArrow=W_SOUTH_ARROW;
1832 else
1833 iArrow=SOUTH_ARROW;
1834 iArrowX+=SOUTH_OFFSET_X;
1835 iArrowY+=(SOUTH_OFFSET_Y+WEST_TO_SOUTH_OFFSET_Y);
1836 }
1837 else if ((iDeltaA==-WORLD_MAP_X)&&(iDeltaB==1))
1838 {
1839 iDirection=N_TO_W_LINE;
1840 if(!ubCounter)
1841 iArrow=W_WEST_ARROW;
1842 else
1843 iArrow=WEST_ARROW;
1844
1845 iArrowX+=WEST_OFFSET_X;
1846 iArrowY+=WEST_OFFSET_Y;
1847 }
1848 else if ((iDeltaA==WORLD_MAP_X)&&(iDeltaB==-1))
1849 {
1850 iDirection=S_TO_E_LINE;
1851 if(!ubCounter)
1852 iArrow=W_EAST_ARROW;
1853 else
1854 iArrow=EAST_ARROW;
1855 iArrowX+=EAST_OFFSET_X;
1856 iArrowY+=EAST_OFFSET_Y;
1857 }
1858 else if ((iDeltaA==1)&&(iDeltaB==WORLD_MAP_X))
1859 {
1860 iDirection=E_TO_N_LINE;
1861 if(!ubCounter)
1862 iArrow=W_NORTH_ARROW;
1863 else
1864 iArrow=NORTH_ARROW;
1865
1866 iArrowX+=NORTH_OFFSET_X;
1867 iArrowY+=NORTH_OFFSET_Y+EAST_TO_NORTH_OFFSET_Y;
1868 }
1869 }
1870 }
1871 else
1872 {
1873 iX=(pNode->uiSectorId%MAP_WORLD_X);
1874 iY=(pNode->uiSectorId/MAP_WORLD_X);
1875 iX=(iX*MAP_GRID_X)+MAP_VIEW_START_X;
1876 iY=(iY*MAP_GRID_Y)+MAP_VIEW_START_Y;
1877 if(pPastNode)
1878 {
1879 iPastX=(pPastNode->uiSectorId%MAP_WORLD_X);
1880 iPastY=(pPastNode->uiSectorId/MAP_WORLD_X);
1881 iPastX=(iPastX*MAP_GRID_X)+MAP_VIEW_START_X;
1882 iPastY=(iPastY*MAP_GRID_Y)+MAP_VIEW_START_Y;
1883 }
1884 iArrowX=iX;
1885 iArrowY=iY;
1886 // display enter and exit 'X's
1887 if (pPastNode)
1888 {
1889 // red 'X'
1890 fUTurnFlag=TRUE;
1891 iDeltaA=(INT16)pNode->uiSectorId-(INT16)pPastNode->uiSectorId;
1892 if (iDeltaA==-1)
1893 {
1894 iDirection=RED_X_WEST;
1895 //iX+=RED_WEST_OFF_X;
1896 }
1897 else if (iDeltaA==1)
1898 {
1899 iDirection=RED_X_EAST;
1900 //iX+=RED_EAST_OFF_X;
1901 }
1902 else if(iDeltaA==-WORLD_MAP_X)
1903 {
1904 iDirection=RED_X_NORTH;
1905 //iY+=RED_NORTH_OFF_Y;
1906 }
1907 else
1908 {
1909 iDirection=RED_X_SOUTH;
1910 //iY+=RED_SOUTH_OFF_Y;
1911 }
1912 }
1913 if (pNextNode)
1914 {
1915 fUTurnFlag=FALSE;
1916 iDeltaB=(INT16)pNode->uiSectorId-(INT16)pNextNode->uiSectorId;
1917 if (iDeltaB==-1)
1918 {
1919 iDirection=GREEN_X_EAST;
1920 if(!ubCounter)
1921 iArrow=W_EAST_ARROW;
1922 else
1923 iArrow=EAST_ARROW;
1924
1925 iArrowX+=EAST_OFFSET_X;
1926 iArrowY+=EAST_OFFSET_Y;
1927 //iX+=RED_EAST_OFF_X;
1928 }
1929 else if (iDeltaB==1)
1930 {
1931 iDirection=GREEN_X_WEST;
1932 if(!ubCounter)
1933 iArrow=W_WEST_ARROW;
1934 else
1935 iArrow=WEST_ARROW;
1936
1937 iArrowX+=WEST_OFFSET_X;
1938 iArrowY+=WEST_OFFSET_Y;
1939 //iX+=RED_WEST_OFF_X;
1940 }
1941 else if(iDeltaB==WORLD_MAP_X)
1942 {
1943 iDirection=GREEN_X_NORTH;
1944 if(!ubCounter)
1945 iArrow=W_NORTH_ARROW;
1946 else
1947 iArrow=NORTH_ARROW;
1948
1949 iArrowX+=NORTH_OFFSET_X;
1950 iArrowY+=NORTH_OFFSET_Y;
1951 //iY+=RED_NORTH_OFF_Y;
1952 }
1953 else
1954 {
1955 iDirection=GREEN_X_SOUTH;
1956 if(!ubCounter)
1957 iArrow=W_SOUTH_ARROW;
1958 else
1959 iArrow=SOUTH_ARROW;
1960 iArrowX+=SOUTH_OFFSET_X;
1961 iArrowY+=SOUTH_OFFSET_Y;
1962 //iY+=RED_SOUTH_OFF_Y;
1963 }
1964
1965
1966 }
1967 }
1968 if(fNextNode)
1969 {
1970 if(!ubCounter)
1971 {
1972 pCurrentNode=pCurrentNode->pNext;
1973 if(!pCurrentNode)
1974 fPauseFlag=TRUE;
1975 }
1976 }
1977 if ((iDirection !=-1)&&(iArrow!=-1))
1978 {
1979
1980 if(!fUTurnFlag)
1981 {
1982 if ((MAP_VIEW_START_X < iX && iX < SCREEN_WIDTH - MAP_GRID_X * 2 && MAP_VIEW_START_Y < iY && iY < MAP_VIEW_START_Y + MAP_VIEW_HEIGHT))
1983 {
1984 if( pNode != pPath )
1985 {
1986 BltVideoObject(FRAME_BUFFER, guiMAPCURSORS, (UINT16)iArrow, iArrowX, iArrowY);
1987 InvalidateRegion( iArrowX, iArrowY, iArrowX + 2 * MAP_GRID_X, iArrowY + 2 * MAP_GRID_Y );
1988 }
1989 }
1990 if(ubCounter==1)
1991 ubCounter=0;
1992 else
1993 ubCounter=1;
1994 return TRUE;
1995 }
1996 if(ubCounter==1)
1997 ubCounter=0;
1998 else
1999 ubCounter=1;
2000
2001
2002 }
2003 // move to next arrow
2004
2005
2006 //ARM who knows what it should return here?
2007 return FALSE;
2008 }
2009
2010
2011 // display potential path, yes or no?
DisplayThePotentialPathForHelicopter(INT16 sMapX,INT16 sMapY)2012 void DisplayThePotentialPathForHelicopter(INT16 sMapX, INT16 sMapY )
2013 {
2014 // simply check if we want to refresh the screen to display path
2015 static BOOLEAN fOldShowAirCraft = FALSE;
2016 static INT16 sOldMapX, sOldMapY;
2017 INT32 iDifference = 0;
2018
2019
2020 if( fOldShowAirCraft != fShowAircraftFlag )
2021 {
2022 fOldShowAirCraft = fShowAircraftFlag;
2023 giPotHeliPathBaseTime = GetJA2Clock( );
2024
2025 sOldMapX = sMapX;
2026 sOldMapY = sMapY;
2027 fTempPathAlreadyDrawn = FALSE;
2028 fDrawTempHeliPath = FALSE;
2029
2030 }
2031
2032 if( ( sMapX != sOldMapX) || ( sMapY != sOldMapY ) )
2033 {
2034 giPotHeliPathBaseTime = GetJA2Clock( );
2035
2036 sOldMapX = sMapX;
2037 sOldMapY = sMapY;
2038
2039 // path was plotted and we moved, re draw map..to clean up mess
2040 if( fTempPathAlreadyDrawn )
2041 {
2042 fMapPanelDirty = TRUE;
2043 }
2044
2045 fTempPathAlreadyDrawn = FALSE;
2046 fDrawTempHeliPath = FALSE;
2047 }
2048
2049 iDifference = GetJA2Clock( ) - giPotHeliPathBaseTime ;
2050
2051 if( fTempPathAlreadyDrawn )
2052 {
2053 return;
2054 }
2055
2056 if( iDifference > MIN_WAIT_TIME_FOR_TEMP_PATH )
2057 {
2058 fDrawTempHeliPath = TRUE;
2059 giPotHeliPathBaseTime = GetJA2Clock( );
2060 fTempPathAlreadyDrawn = TRUE;
2061 }
2062 }
2063
2064
IsTheCursorAllowedToHighLightThisSector(INT16 const x,INT16 const y)2065 bool IsTheCursorAllowedToHighLightThisSector(INT16 const x, INT16 const y)
2066 {
2067 return SectorInfo[SECTOR(x, y)].ubTraversability[THROUGH_STRATEGIC_MOVE] != EDGEOFWORLD;
2068 }
2069
2070
RestoreBackgroundForMapGrid(INT16 sMapX,INT16 sMapY)2071 void RestoreBackgroundForMapGrid( INT16 sMapX, INT16 sMapY )
2072 {
2073 INT16 sX, sY;
2074
2075 // screen values
2076 sX=(sMapX * MAP_GRID_X ) + MAP_VIEW_START_X;
2077 sY=(sMapY * MAP_GRID_Y ) + MAP_VIEW_START_Y;
2078
2079 // restore background
2080 RestoreExternBackgroundRect( sX, sY ,DMAP_GRID_X ,DMAP_GRID_Y );
2081 }
2082
2083
ClipBlitsToMapViewRegion(void)2084 void ClipBlitsToMapViewRegion( void )
2085 {
2086 SGPRect *pRectToUse = &MapScreenRect;
2087
2088 SetClippingRect( pRectToUse );
2089 gOldClipRect = gDirtyClipRect;
2090 gDirtyClipRect = *pRectToUse;
2091 }
2092
RestoreClipRegionToFullScreen(void)2093 void RestoreClipRegionToFullScreen( void )
2094 {
2095 SGPRect FullScreenRect = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
2096 SetClippingRect( &FullScreenRect );
2097 gDirtyClipRect = gOldClipRect;
2098 }
2099
2100
ClipBlitsToMapViewRegionForRectangleAndABit(UINT32 uiDestPitchBYTES)2101 void ClipBlitsToMapViewRegionForRectangleAndABit( UINT32 uiDestPitchBYTES )
2102 {
2103 // clip blits to map view region
2104 // because MC's map coordinates system is so screwy, these had to be hand-tuned to work right... ARM
2105 SetClippingRegionAndImageWidth( uiDestPitchBYTES, MapScreenRect.iLeft - 1, MapScreenRect.iTop - 1, MapScreenRect.iRight - MapScreenRect.iLeft + 3, MapScreenRect.iBottom - MapScreenRect.iTop + 2 );
2106 }
2107
RestoreClipRegionToFullScreenForRectangle(UINT32 uiDestPitchBYTES)2108 void RestoreClipRegionToFullScreenForRectangle( UINT32 uiDestPitchBYTES )
2109 {
2110 // clip blits to map view region
2111 SetClippingRegionAndImageWidth(uiDestPitchBYTES, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
2112 }
2113
2114
2115 // mvt icon offset defines
2116 #define SOUTH_Y_MVT_OFFSET +10
2117 #define SOUTH_X_MVT_OFFSET 0
2118 #define NORTH_Y_MVT_OFFSET -10
2119 #define NORTH_X_MVT_OFFSET +10
2120 #define NORTH_SOUTH_CENTER_OFFSET +5
2121
2122
2123 #define EAST_Y_MVT_OFFSET + 8
2124 #define EAST_X_MVT_OFFSET 15
2125 #define WEST_Y_MVT_OFFSET -2
2126 #define WEST_X_MVT_OFFSET -8
2127 #define EAST_WEST_CENTER_OFFSET +2
2128
2129 #define NORTH_TEXT_X_OFFSET +1
2130 #define NORTH_TEXT_Y_OFFSET +4
2131 #define SOUTH_TEXT_X_OFFSET +1
2132 #define SOUTH_TEXT_Y_OFFSET +2
2133
2134 #define EAST_TEXT_X_OFFSET + 2
2135 #define EAST_TEXT_Y_OFFSET 0
2136 #define WEST_TEXT_X_OFFSET + 4
2137 #define WEST_TEXT_Y_OFFSET 0
2138
2139
2140 #define ICON_WIDTH 8
2141
2142
2143 // show the icons for people in motion
ShowPeopleInMotion(INT16 const sX,INT16 const sY)2144 static void ShowPeopleInMotion(INT16 const sX, INT16 const sY)
2145 {
2146 if (iCurrentMapSectorZ != 0) return;
2147
2148 // show the icons for people in motion from this sector to the next guy over
2149 INT16 const sSource = CALCULATE_STRATEGIC_INDEX(sX, sY);
2150 for (INT32 dir = 0; dir != 4; ++dir)
2151 { // find how many people are coming and going in this sector
2152 INT16 sDest = sSource;
2153 switch (dir)
2154 {
2155 case 0: if (sY <= 1) continue; sDest += NORTH_MOVE; break;
2156 case 1: if (sX >= MAP_WORLD_X - 2) continue; sDest += EAST_MOVE; break;
2157 case 2: if (sY >= MAP_WORLD_Y - 2) continue; sDest += SOUTH_MOVE; break;
2158 case 3: if (sX <= 1) continue; sDest += WEST_MOVE; break;
2159
2160 default: abort();
2161 }
2162
2163 INT32 sExiting;
2164 INT32 sEntering;
2165 BOOLEAN fAboutToEnter;
2166 INT16 const sec_src = SECTOR(sSource % MAP_WORLD_X, sSource / MAP_WORLD_X);
2167 INT16 const sec_dst = SECTOR(sDest % MAP_WORLD_X, sDest / MAP_WORLD_X);
2168 if (!PlayersBetweenTheseSectors(sec_src, sec_dst, &sExiting, &sEntering, &fAboutToEnter)) continue;
2169 // someone is leaving
2170
2171 // now find position
2172 INT16 sOffsetX;
2173 INT16 sOffsetY;
2174 if (dir % 2 == 0)
2175 { // guys going north or south
2176 if (sEntering > 0)
2177 { // more than one coming in, offset from middle
2178 sOffsetX = dir == 0 ?
2179 (NORTH_X_MVT_OFFSET) :
2180 (SOUTH_X_MVT_OFFSET);
2181 }
2182 else
2183 {
2184 sOffsetX = NORTH_SOUTH_CENTER_OFFSET;
2185 }
2186
2187 if (dir == 0)
2188 { // going north
2189 sOffsetY = NORTH_Y_MVT_OFFSET;
2190 }
2191 else
2192 { // going south
2193 sOffsetY = SOUTH_Y_MVT_OFFSET;
2194 }
2195 }
2196 else
2197 { // going east/west
2198 if (sEntering > 0)
2199 { // people also entering, offset from middle
2200 sOffsetY = dir == 1 ?
2201 (EAST_Y_MVT_OFFSET) :
2202 (WEST_Y_MVT_OFFSET);
2203 }
2204 else
2205 {
2206 sOffsetY = EAST_WEST_CENTER_OFFSET;
2207 }
2208
2209 if (dir == 1)
2210 { // going east
2211 sOffsetX = EAST_X_MVT_OFFSET;
2212 }
2213 else
2214 { // going west
2215 sOffsetX = WEST_X_MVT_OFFSET;
2216 }
2217 }
2218
2219 INT16 sTextXOffset;
2220 INT16 sTextYOffset;
2221 switch (dir)
2222 {
2223 case 0: sTextXOffset = NORTH_TEXT_X_OFFSET; sTextYOffset = NORTH_TEXT_Y_OFFSET; break;
2224 case 1: sTextXOffset = EAST_TEXT_X_OFFSET; sTextYOffset = EAST_TEXT_Y_OFFSET; break;
2225 case 2: sTextXOffset = SOUTH_TEXT_X_OFFSET; sTextYOffset = SOUTH_TEXT_Y_OFFSET; break;
2226 case 3: sTextXOffset = WEST_TEXT_X_OFFSET; sTextYOffset = WEST_TEXT_Y_OFFSET; break;
2227
2228 default: abort();
2229 }
2230
2231 // if about to enter, draw yellow arrows, blue otherwise
2232 SGPVObject const* const hIconHandle = fAboutToEnter ? guiCHARBETWEENSECTORICONSCLOSE : guiCHARBETWEENSECTORICONS;
2233
2234 INT16 iX;
2235 INT16 iY;
2236
2237 iX = MAP_VIEW_START_X + sX * MAP_GRID_X + sOffsetX;
2238 iY = MAP_Y_ICON_OFFSET + MAP_VIEW_START_Y + sY * MAP_GRID_Y + sOffsetY;
2239 BltVideoObject(guiSAVEBUFFER, hIconHandle, dir, iX, iY);
2240
2241 // blit the text
2242 UINT8 const foreground = fAboutToEnter ? FONT_BLACK : FONT_WHITE;
2243 SetFontAttributes(MAP_MVT_ICON_FONT, foreground);
2244 SetFontDestBuffer(guiSAVEBUFFER);
2245
2246 ST::string buf = ST::format("{}", sExiting);
2247
2248 INT16 usX;
2249 INT16 usY;
2250 FindFontCenterCoordinates(iX + sTextXOffset, 0, ICON_WIDTH, 0, buf, MAP_FONT, &usX, &usY);
2251 MPrint(usX, iY + sTextYOffset, buf);
2252
2253 INT32 iWidth;
2254 INT32 iHeight;
2255 if (dir % 2 == 0)
2256 { // north south
2257 iWidth = 10;
2258 iHeight = 12;
2259 }
2260 else
2261 { // east west
2262 iWidth = 12;
2263 iHeight = 7;
2264 }
2265
2266 // error correction for scrolling with people on the move
2267 if (iX < 0)
2268 {
2269 iWidth += iX;
2270 iX = 0;
2271 }
2272
2273 if (iY < 0)
2274 {
2275 iHeight += iY;
2276 iY = 0;
2277 }
2278
2279 if (iWidth > 0 && iHeight > 0)
2280 {
2281 RestoreExternBackgroundRect(iX, iY, (INT16)iWidth, (INT16)iHeight);
2282 }
2283 }
2284
2285 // restore buffer
2286 SetFontDestBuffer(FRAME_BUFFER);
2287 }
2288
2289
2290 /* calculate the distance travelled, the proposed distance, and total distance
2291 * one can go and display these on screen */
DisplayDistancesForHelicopter()2292 void DisplayDistancesForHelicopter()
2293 {
2294 static INT16 sOldYPosition = 0;
2295
2296 INT16 sMapX;
2297 INT16 sMapY;
2298 INT16 const sYPosition = GetMouseMapXY(&sMapX, &sMapY) && sMapY >= 13 ?
2299 MAP_HELICOPTER_UPPER_ETA_POPUP_Y : MAP_HELICOPTER_ETA_POPUP_Y;
2300
2301 if (sOldYPosition != 0 && sOldYPosition != sYPosition)
2302 {
2303 RestoreExternBackgroundRect(MAP_HELICOPTER_ETA_POPUP_X, sOldYPosition, MAP_HELICOPTER_ETA_POPUP_WIDTH + 20, MAP_HELICOPTER_ETA_POPUP_HEIGHT);
2304 }
2305 sOldYPosition = sYPosition;
2306
2307 BltVideoObject(FRAME_BUFFER, guiMapBorderHeliSectors, 0, MAP_HELICOPTER_ETA_POPUP_X, sYPosition);
2308
2309 SetFontAttributes(MAP_FONT, FONT_LTGREEN);
2310
2311 INT32 const x = MAP_HELICOPTER_ETA_POPUP_X + 5;
2312 INT32 y = sYPosition + 5;
2313 INT32 const w = MAP_HELICOPTER_ETA_POPUP_WIDTH;
2314 INT32 const h = GetFontHeight(MAP_FONT);
2315 ST::string sString;
2316 INT16 sX;
2317 INT16 sY;
2318
2319 MPrint(x, y, pHelicopterEtaStrings[0]);
2320 INT32 const total_distance = DistanceOfIntendedHelicopterPath();
2321 sString = ST::format("{}", total_distance);
2322 FindFontRightCoordinates(x, y, w, 0, sString, MAP_FONT, &sX, &sY);
2323 MPrint(sX, y, sString);
2324 y += h;
2325
2326 MPrint(x, y, pHelicopterEtaStrings[1]);
2327 INT16 const n_safe_sectors = GetNumSafeSectorsInPath();
2328 sString = ST::format("{}", n_safe_sectors);
2329 FindFontRightCoordinates(x, y, w, 0, sString, MAP_FONT, &sX, &sY);
2330 MPrint(sX, y, sString);
2331 y += h;
2332
2333 MPrint(x, y, pHelicopterEtaStrings[2]);
2334 INT16 const n_unsafe_sectors = GetNumUnSafeSectorsInPath();
2335 sString = ST::format("{}", n_unsafe_sectors);
2336 FindFontRightCoordinates(x, y, w, 0, sString, MAP_FONT, &sX, &sY);
2337 MPrint(sX, y, sString);
2338 y += h;
2339
2340 // calculate the cost of the trip based on the number of safe and unsafe sectors it will pass through
2341 MPrint(x, y, pHelicopterEtaStrings[3]);
2342 UINT32 const uiTripCost = n_safe_sectors * COST_AIRSPACE_SAFE + n_unsafe_sectors * COST_AIRSPACE_UNSAFE;
2343 sString = SPrintMoney(uiTripCost);
2344 FindFontRightCoordinates(x, y, w, 0, sString, MAP_FONT, &sX, &sY);
2345 MPrint(sX, y, sString);
2346 y += h;
2347
2348 MPrint(x, y, pHelicopterEtaStrings[4]);
2349 // get travel time for the last path segment
2350 INT32 iTime = GetPathTravelTimeDuringPlotting(pTempHelicopterPath);
2351 // add travel time for any prior path segments (stored in the helicopter's mercpath, but waypoints aren't built)
2352 iTime += GetPathTravelTimeDuringPlotting(GetHelicopter().pMercPath);
2353 sString = ST::format("{}{} {}{}", iTime / 60, gsTimeStrings[0], iTime % 60, gsTimeStrings[1]);
2354 FindFontRightCoordinates(x, y, w, 0, sString, MAP_FONT, &sX, &sY);
2355 MPrint(sX, y, sString);
2356 y += h;
2357
2358 // show # of passengers aboard the chopper
2359 MPrint(x, y, pHelicopterEtaStrings[6]);
2360 sString = ST::format("{}", GetNumberInVehicle(GetHelicopter()));
2361 FindFontRightCoordinates(x, y, w, 0, sString, MAP_FONT, &sX, &sY);
2362 MPrint(sX, y, sString);
2363
2364 InvalidateRegion(MAP_HELICOPTER_ETA_POPUP_X, sOldYPosition, MAP_HELICOPTER_ETA_POPUP_X + MAP_HELICOPTER_ETA_POPUP_WIDTH + 20, sOldYPosition + MAP_HELICOPTER_ETA_POPUP_HEIGHT);
2365 }
2366
2367
2368 static void DisplayDestinationOfHelicopter(void);
2369
2370
2371 // grab position of helicopter and blt to screen
DisplayPositionOfHelicopter(void)2372 void DisplayPositionOfHelicopter( void )
2373 {
2374 static INT16 sOldMapX = 0, sOldMapY = 0;
2375 //INT16 sX =0, sY = 0;
2376 FLOAT flRatio = 0.0;
2377 UINT32 x,y;
2378 UINT16 minX, minY, maxX, maxY;
2379
2380 AssertMsg(0 <= sOldMapX && sOldMapX < SCREEN_WIDTH, String("DisplayPositionOfHelicopter: Invalid sOldMapX = %d", sOldMapX));
2381 AssertMsg(0 <= sOldMapY && sOldMapY < SCREEN_HEIGHT, String("DisplayPositionOfHelicopter: Invalid sOldMapY = %d", sOldMapY));
2382
2383 // restore background on map where it is
2384 if( sOldMapX != 0 )
2385 {
2386 RestoreExternBackgroundRect( sOldMapX, sOldMapY, HELI_ICON_WIDTH, HELI_ICON_HEIGHT );
2387 sOldMapX = 0;
2388 }
2389
2390
2391 if( iHelicopterVehicleId != -1 )
2392 {
2393 // draw the destination icon first, so when they overlap, the real one is on top!
2394 DisplayDestinationOfHelicopter( );
2395
2396 VEHICLETYPE const& v = GetHelicopter();
2397 // check if mvt group
2398 if (v.ubMovementGroup != 0)
2399 {
2400 const GROUP* const pGroup = GetGroup(v.ubMovementGroup);
2401
2402 if (pGroup->uiTraverseTime > 0 && pGroup->uiTraverseTime != TRAVERSE_TIME_IMPOSSIBLE)
2403 {
2404 flRatio = ( ( pGroup->uiTraverseTime + GetWorldTotalMin() ) - pGroup->uiArrivalTime ) / ( float ) pGroup->uiTraverseTime;
2405 }
2406
2407 /*
2408 AssertMsg( ( flRatio >= 0 ) && ( flRatio <= 100 ), String( "DisplayPositionOfHelicopter: Invalid flRatio = %6.2f, trav %d, arr %d, time %d",
2409 flRatio, pGroup->uiTraverseTime, pGroup->uiArrivalTime, GetWorldTotalMin() ) );*/
2410
2411 if ( flRatio < 0 )
2412 {
2413 flRatio = 0;
2414 }
2415 else if ( flRatio > 100 )
2416 {
2417 flRatio = 100;
2418 }
2419
2420 // grab min and max locations to interpolate sub sector position
2421 minX = MAP_VIEW_START_X + MAP_GRID_X * ( pGroup->ubSectorX );
2422 maxX = MAP_VIEW_START_X + MAP_GRID_X * ( pGroup->ubNextX );
2423 minY = MAP_VIEW_START_Y + MAP_GRID_Y * ( pGroup->ubSectorY );
2424 maxY = MAP_VIEW_START_Y + MAP_GRID_Y * ( pGroup->ubNextY );
2425
2426 AssertMsg(minX < SCREEN_WIDTH, String("DisplayPositionOfHelicopter: Invalid minX = %d", minX));
2427 AssertMsg(maxX < SCREEN_WIDTH, String("DisplayPositionOfHelicopter: Invalid maxX = %d", maxX));
2428 AssertMsg(minY < SCREEN_HEIGHT, String("DisplayPositionOfHelicopter: Invalid minY = %d", minY));
2429 AssertMsg(maxY < SCREEN_HEIGHT, String("DisplayPositionOfHelicopter: Invalid maxY = %d", maxY));
2430
2431 // IMPORTANT: Since min can easily be larger than max, we gotta cast to as signed value
2432 x = ( UINT32 )( minX + flRatio * ( ( INT16 ) maxX - ( INT16 ) minX ) );
2433 y = ( UINT32 )( minY + flRatio * ( ( INT16 ) maxY - ( INT16 ) minY ) );
2434 x += 1;
2435 y += 3;
2436
2437 AssertMsg(x < SCREEN_WIDTH, String("DisplayPositionOfHelicopter: Invalid x = %d. At %d,%d. Next %d,%d. Min/Max X = %d/%d",
2438 x, pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubNextX, pGroup->ubNextY, minX, maxX ) );
2439
2440 AssertMsg(y < SCREEN_HEIGHT, String("DisplayPositionOfHelicopter: Invalid y = %d. At %d,%d. Next %d,%d. Min/Max Y = %d/%d",
2441 y, pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubNextX, pGroup->ubNextY, minY, maxY ) );
2442
2443
2444 // clip blits to mapscreen region
2445 ClipBlitsToMapViewRegion( );
2446
2447 BltVideoObject(FRAME_BUFFER, guiHelicopterIcon, HELI_ICON, x, y);
2448
2449 SetFontAttributes(MAP_MVT_ICON_FONT, FONT_WHITE);
2450 MPrint(x + 5, y + 1, ST::format("{}", GetNumberInVehicle(v)));
2451
2452 InvalidateRegion( x, y, x + HELI_ICON_WIDTH, y + HELI_ICON_HEIGHT );
2453
2454 RestoreClipRegionToFullScreen( );
2455
2456 // now store the old stuff
2457 sOldMapX = ( INT16 ) x;
2458 sOldMapY = ( INT16 ) y;
2459 }
2460 }
2461 }
2462
2463
DisplayDestinationOfHelicopter(void)2464 static void DisplayDestinationOfHelicopter(void)
2465 {
2466 static INT16 sOldMapX = 0, sOldMapY = 0;
2467 INT16 sMapX, sMapY;
2468 UINT32 x,y;
2469
2470 AssertMsg(0 <= sOldMapX && sOldMapX < SCREEN_WIDTH, String("DisplayDestinationOfHelicopter: Invalid sOldMapX = %d", sOldMapX));
2471 AssertMsg(0 <= sOldMapY && sOldMapY < SCREEN_HEIGHT, String("DisplayDestinationOfHelicopter: Invalid sOldMapY = %d", sOldMapY));
2472
2473 // restore background on map where it is
2474 if( sOldMapX != 0 )
2475 {
2476 RestoreExternBackgroundRect( sOldMapX, sOldMapY, HELI_SHADOW_ICON_WIDTH, HELI_SHADOW_ICON_HEIGHT );
2477 sOldMapX = 0;
2478 }
2479
2480 // if helicopter is going somewhere
2481 if (GetLengthOfPath(GetHelicopter().pMercPath) > 1)
2482 {
2483 // get destination
2484 const INT16 sSector = GetLastSectorOfHelicoptersPath();
2485 sMapX = sSector % MAP_WORLD_X;
2486 sMapY = sSector / MAP_WORLD_X;
2487
2488 x = MAP_VIEW_START_X + ( MAP_GRID_X * sMapX ) + 1;
2489 y = MAP_VIEW_START_Y + ( MAP_GRID_Y * sMapY ) + 3;
2490
2491 AssertMsg( x < SCREEN_WIDTH, String("DisplayDestinationOfHelicopter: Invalid x = %d. Dest %d,%d", x, sMapX, sMapY));
2492 AssertMsg( y < SCREEN_HEIGHT, String("DisplayDestinationOfHelicopter: Invalid y = %d. Dest %d,%d", y, sMapX, sMapY));
2493
2494 // clip blits to mapscreen region
2495 ClipBlitsToMapViewRegion( );
2496
2497 BltVideoObject(FRAME_BUFFER, guiHelicopterIcon, HELI_SHADOW_ICON, x, y);
2498 InvalidateRegion( x, y, x + HELI_SHADOW_ICON_WIDTH, y + HELI_SHADOW_ICON_HEIGHT );
2499
2500 RestoreClipRegionToFullScreen( );
2501
2502 // now store the old stuff
2503 sOldMapX = ( INT16 ) x;
2504 sOldMapY = ( INT16 ) y;
2505 }
2506 }
2507
2508
2509
CheckForClickOverHelicopterIcon(INT16 sClickedSectorX,INT16 sClickedSectorY)2510 BOOLEAN CheckForClickOverHelicopterIcon( INT16 sClickedSectorX, INT16 sClickedSectorY )
2511 {
2512 BOOLEAN fHelicopterOverNextSector = FALSE;
2513 FLOAT flRatio = 0.0;
2514 INT16 sSectorX;
2515 INT16 sSectorY;
2516
2517 if (!fShowAircraftFlag) return FALSE;
2518 if (iHelicopterVehicleId == -1) return FALSE;
2519
2520 VEHICLETYPE const& v = GetHelicopter();
2521
2522 // figure out over which sector the helicopter APPEARS to be to the player (because we slide it smoothly across the
2523 // map, unlike groups travelling on the ground, it can appear over its next sector while it's not there yet.
2524 GROUP const* const pGroup = GetGroup(v.ubMovementGroup);
2525 Assert( pGroup );
2526
2527 if ( pGroup->fBetweenSectors )
2528 {
2529 if (pGroup->uiTraverseTime > 0 && pGroup->uiTraverseTime != TRAVERSE_TIME_IMPOSSIBLE)
2530 {
2531 flRatio = ( pGroup->uiTraverseTime - pGroup->uiArrivalTime + GetWorldTotalMin() ) / ( float ) pGroup->uiTraverseTime;
2532 }
2533
2534 // if more than halfway there, the chopper appears more over the next sector, not over its current one(!)
2535 if ( flRatio > 0.5 )
2536 {
2537 fHelicopterOverNextSector = TRUE;
2538 }
2539 }
2540
2541
2542 if ( fHelicopterOverNextSector )
2543 {
2544 // use the next sector's coordinates
2545 sSectorX = pGroup->ubNextX;
2546 sSectorY = pGroup->ubNextY;
2547 }
2548 else
2549 {
2550 // use current sector's coordinates
2551 sSectorX = v.sSectorX;
2552 sSectorY = v.sSectorY;
2553 }
2554
2555 // check if helicopter appears where he clicked
2556 if( ( sSectorX != sClickedSectorX ) || ( sSectorY != sClickedSectorY ) )
2557 {
2558 return( FALSE );
2559 }
2560
2561 return( TRUE );
2562 }
2563
2564
DrawSite(const INT16 sector_x,const INT16 sector_y,const SGPVObject * const icon)2565 static void DrawSite(const INT16 sector_x, const INT16 sector_y, const SGPVObject* const icon)
2566 {
2567 INT16 x;
2568 INT16 y;
2569 UINT16 max_w;
2570 UINT16 max_h;
2571 UINT8 vo_idx;
2572
2573 GetScreenXYFromMapXY(sector_x, sector_y, &x, &y);
2574 ++x;
2575 max_w = MAP_GRID_X - 1;
2576 max_h = MAP_GRID_Y - 1;
2577 vo_idx = 1;
2578
2579 ETRLEObject const& ETRLEProps = icon->SubregionProperties(vo_idx);
2580 UINT16 const w = ETRLEProps.usWidth;
2581 UINT16 const h = ETRLEProps.usHeight;
2582 x += (max_w - w) / 2;
2583 /* If the icon is higher than a map cell, align with the bottom of the cell */
2584 y += (h > max_h ? max_h - h : (max_h - h) / 2);
2585
2586 BltVideoObject(guiSAVEBUFFER, icon, vo_idx, x, y);
2587 }
2588
2589
BlitMineIcon(INT16 sMapX,INT16 sMapY)2590 static void BlitMineIcon(INT16 sMapX, INT16 sMapY)
2591 {
2592 DrawSite(sMapX, sMapY, guiMINEICON);
2593 }
2594
2595
PrintStringCenteredBoxed(INT32 x,INT32 y,const ST::string & string)2596 static void PrintStringCenteredBoxed(INT32 x, INT32 y, const ST::string& string)
2597 {
2598 x -= StringPixLength(string, MAP_FONT) / 2;
2599 if (x < MAP_VIEW_START_X + 23) x = MAP_VIEW_START_X + 23;
2600
2601 MPrint(x, y, string);
2602 }
2603
2604
BlitMineText(UINT8 const mine_idx,INT16 const sMapX,INT16 const sMapY)2605 static void BlitMineText(UINT8 const mine_idx, INT16 const sMapX, INT16 const sMapY)
2606 {
2607 // set coordinates for start of mine text
2608 INT16 sScreenX;
2609 INT16 sScreenY;
2610
2611 GetScreenXYFromMapXY(sMapX, sMapY, &sScreenX, &sScreenY);
2612 sScreenX += MAP_GRID_X / 2; // centered around middle of mine square
2613 sScreenY += MAP_GRID_Y + 1; // slightly below
2614
2615 // show detailed mine info (name, production rate, daily production)
2616
2617 SetFontDestBuffer(guiSAVEBUFFER, MAP_VIEW_START_X, MAP_VIEW_START_Y, MAP_VIEW_START_X + MAP_VIEW_WIDTH + MAP_GRID_X, MAP_VIEW_START_Y + MAP_VIEW_HEIGHT + 7);
2618 SetFontAttributes(MAP_FONT, FONT_LTGREEN);
2619
2620 INT32 const x = sScreenX;
2621 INT32 y = sScreenY;
2622 INT32 const h = GetFontHeight(MAP_FONT);
2623 ST::string buf;
2624
2625 // display associated town name, followed by "mine"
2626 buf = ST::format("{} {}", GCM->getTownName(GetTownAssociatedWithMine(mine_idx)), pwMineStrings[0]);
2627 PrintStringCenteredBoxed(x, y, buf);
2628 y += h;
2629
2630 // check if mine is empty (abandoned) or running out
2631 if (gMineStatus[mine_idx].fEmpty)
2632 {
2633 PrintStringCenteredBoxed(x, y, pwMineStrings[5]);
2634 y += h;
2635 }
2636 else if (gMineStatus[mine_idx].fShutDown)
2637 {
2638 PrintStringCenteredBoxed(x, y, pwMineStrings[6]);
2639 y += h;
2640 }
2641 else if (gMineStatus[mine_idx].fRunningOut)
2642 {
2643 PrintStringCenteredBoxed(x, y, pwMineStrings[7]);
2644 y += h;
2645 }
2646
2647 // only show production if player controls it and it's actually producing
2648 if (PlayerControlsMine(mine_idx) && !gMineStatus[mine_idx].fEmpty)
2649 {
2650 // show current production
2651 buf = SPrintMoney(PredictDailyIncomeFromAMine(mine_idx));
2652
2653 // if potential is not nil, show percentage of the two
2654 UINT32 maxIncome = GetMaxDailyRemovalFromMine(mine_idx);
2655 if (maxIncome > 0)
2656 {
2657 UINT32 predictedIncome = PredictDailyIncomeFromAMine(mine_idx);
2658 buf += ST::format(" ({}%)", 100 * predictedIncome / maxIncome);
2659 }
2660
2661 PrintStringCenteredBoxed(x, y, buf);
2662 }
2663
2664 SetFontDestBuffer(FRAME_BUFFER, MAP_VIEW_START_X, MAP_VIEW_START_Y, MAP_VIEW_START_X + MAP_VIEW_WIDTH + MAP_GRID_X, MAP_VIEW_START_Y + MAP_VIEW_HEIGHT + 7);
2665 }
2666
2667
BlitTownGridMarkers(void)2668 static void BlitTownGridMarkers(void)
2669 {
2670 SGPVSurface::Lock l(guiSAVEBUFFER);
2671 UINT16* const buf = l.Buffer<UINT16>();
2672 UINT32 const pitch = l.Pitch();
2673
2674 ClipBlitsToMapViewRegionForRectangleAndABit(pitch);
2675
2676 // Go through list of towns and place on screen
2677 UINT16 const color = Get16BPPColor(FROMRGB(100, 100, 100));
2678 FOR_EACH_TOWN_SECTOR(i)
2679 {
2680 // skip Orta/Tixa until found
2681 if (!IsTownFound(i->town)) continue;
2682
2683 INT32 const sector = i->sector;
2684 INT16 x;
2685 INT16 y;
2686 INT16 w;
2687 INT16 h;
2688 // Get location on screen
2689 GetScreenXYFromMapXY(SECTORX(sector), SECTORY(sector), &x, &y);
2690 w = MAP_GRID_X - 1;
2691 h = MAP_GRID_Y;
2692 x += 2;
2693
2694 INT32 const loc = SECTOR_INFO_TO_STRATEGIC_INDEX(sector);
2695 if (StrategicMap[loc - MAP_WORLD_X].bNameId == BLANK_SECTOR)
2696 {
2697 LineDraw(TRUE, x - 1, y - 1, x + w - 1, y - 1, color, buf);
2698 }
2699
2700 if (StrategicMap[loc + MAP_WORLD_X].bNameId == BLANK_SECTOR)
2701 {
2702 LineDraw(TRUE, x - 1, y + h - 1, x + w - 1, y + h - 1, color, buf);
2703 }
2704
2705 if (StrategicMap[loc - 1].bNameId == BLANK_SECTOR)
2706 {
2707 LineDraw(TRUE, x - 2, y - 1, x - 2, y + h - 1, color, buf);
2708 }
2709
2710 if (StrategicMap[loc + 1].bNameId == BLANK_SECTOR)
2711 {
2712 LineDraw(TRUE, x + w - 1, y - 1, x + w - 1, y + h - 1, color, buf);
2713 }
2714 }
2715
2716 RestoreClipRegionToFullScreenForRectangle(pitch);
2717 }
2718
2719
BlitMineGridMarkers(void)2720 static void BlitMineGridMarkers(void)
2721 {
2722 SGPVSurface::Lock l(guiSAVEBUFFER);
2723 UINT32 const pitch = l.Pitch();
2724
2725 ClipBlitsToMapViewRegionForRectangleAndABit(pitch);
2726
2727 UINT16 const color = Get16BPPColor(FROMRGB(100, 100, 100));
2728 for (auto m : GCM->getMines())
2729 {
2730 INT16 x;
2731 INT16 y;
2732 INT16 w;
2733 INT16 h;
2734 INT16 const mx = SECTORX(m->entranceSector);
2735 INT16 const my = SECTORY(m->entranceSector);
2736
2737 // Get location on screen
2738 GetScreenXYFromMapXY(mx, my, &x, &y);
2739 w = MAP_GRID_X;
2740 h = MAP_GRID_Y;
2741
2742 RectangleDraw(TRUE, x, y - 1, x + w, y + h - 1, color, l.Buffer<UINT16>());
2743 }
2744
2745 RestoreClipRegionToFullScreenForRectangle(pitch);
2746 }
2747
2748
DisplayLevelString(void)2749 static void DisplayLevelString(void)
2750 {
2751 // given the current level being displayed on the map, show a sub level message
2752
2753 // at the surface
2754 if( !iCurrentMapSectorZ )
2755 {
2756 return;
2757 }
2758
2759 // otherwise we will have to display the string with the level number
2760
2761 SetFontDestBuffer(guiSAVEBUFFER, MAP_VIEW_START_X, MAP_VIEW_START_Y, MAP_VIEW_START_X + MAP_VIEW_WIDTH + MAP_GRID_X, MAP_VIEW_START_Y + MAP_VIEW_HEIGHT + 7);
2762 SetFontAttributes(MAP_FONT, MAP_INDEX_COLOR);
2763 MPrint(MAP_LEVEL_STRING_X, MAP_LEVEL_STRING_Y, ST::format("{} {}", sMapLevelString, iCurrentMapSectorZ));
2764 SetFontDestBuffer(FRAME_BUFFER);
2765 }
2766
2767
GetPickedUpMilitia(UINT8 const type)2768 static INT16& GetPickedUpMilitia(UINT8 const type)
2769 {
2770 switch (type)
2771 {
2772 case GREEN_MILITIA: return sGreensOnCursor;
2773 case REGULAR_MILITIA: return sRegularsOnCursor;
2774 case ELITE_MILITIA: return sElitesOnCursor;
2775 default: throw std::logic_error("invalid militia type");
2776 }
2777 }
2778
2779
2780 // function to manipulate the number of towns people on the cursor
PickUpATownPersonFromSector(UINT8 const type,UINT8 const sector)2781 static void PickUpATownPersonFromSector(UINT8 const type, UINT8 const sector)
2782 {
2783 // Are they in the same town as they were picked up from?
2784 if (GetTownIdForSector(sector) != sSelectedMilitiaTown) return;
2785
2786 if (!SectorOursAndPeaceful(SECTORX(sector), SECTORY(sector), 0)) return;
2787
2788 UINT8& n_type = SectorInfo[sector].ubNumberOfCivsAtLevel[type];
2789 // See if there are any militia of this type in this sector
2790 if (n_type == 0) return;
2791
2792 --n_type; // Reduce number in this sector
2793 ++GetPickedUpMilitia(type); // Pick this guy up
2794 fMapPanelDirty = TRUE;
2795 if (sector == GetWorldSector()) gfStrategicMilitiaChangesMade = TRUE;
2796 }
2797
2798
DropAPersonInASector(UINT8 const type,UINT8 const sector)2799 static void DropAPersonInASector(UINT8 const type, UINT8 const sector)
2800 {
2801 // Are they in the same town as they were picked up from?
2802 if (GetTownIdForSector(sector) != sSelectedMilitiaTown) return;
2803
2804 if (!SectorOursAndPeaceful(SECTORX(sector), SECTORY(sector), 0)) return;
2805
2806 UINT8 (&n_milita)[MAX_MILITIA_LEVELS] = SectorInfo[sector].ubNumberOfCivsAtLevel;
2807 if (n_milita[GREEN_MILITIA] + n_milita[REGULAR_MILITIA] + n_milita[ELITE_MILITIA] >= MAX_ALLOWABLE_MILITIA_PER_SECTOR) return;
2808
2809 // Drop the guy into this sector
2810 INT16& n_type = GetPickedUpMilitia(type);
2811 if (n_type == 0) return;
2812
2813 --n_type;
2814 ++n_milita[type]; // Up the number in this sector of this type of militia
2815 fMapPanelDirty = TRUE;
2816 if (sector == GetWorldSector()) gfStrategicMilitiaChangesMade = TRUE;
2817 }
2818
2819
LoadMapScreenInterfaceMapGraphics()2820 void LoadMapScreenInterfaceMapGraphics()
2821 {
2822 guiBIGMAP = AddVideoSurfaceFromFile(INTERFACEDIR "/b_map.pcx");
2823 guiBULLSEYE = AddVideoObjectFromFile(INTERFACEDIR "/bullseye.sti");
2824 guiSAMICON = AddVideoObjectFromFile(INTERFACEDIR "/sam.sti");
2825 guiCHARBETWEENSECTORICONS = AddVideoObjectFromFile(INTERFACEDIR "/merc_between_sector_icons.sti");
2826 guiCHARBETWEENSECTORICONSCLOSE = AddVideoObjectFromFile(INTERFACEDIR "/merc_mvt_green_arrows.sti");
2827 guiCHARICONS = AddVideoObjectFromFile(INTERFACEDIR "/boxes.sti");
2828 guiHelicopterIcon = AddVideoObjectFromFile(INTERFACEDIR "/helicop.sti");
2829 guiMAPCURSORS = AddVideoObjectFromFile(INTERFACEDIR "/mapcursr.sti");
2830 guiMINEICON = AddVideoObjectFromFile(INTERFACEDIR "/mine.sti");
2831 guiMapBorderHeliSectors = AddVideoObjectFromFile(INTERFACEDIR "/pos2.sti");
2832 guiMilitia = AddVideoObjectFromFile(INTERFACEDIR "/militia.sti");
2833 guiMilitiaMaps = AddVideoObjectFromFile(INTERFACEDIR "/militiamaps.sti");
2834 guiMilitiaSectorHighLight = AddVideoObjectFromFile(INTERFACEDIR "/militiamapsectoroutline2.sti");
2835 guiMilitiaSectorOutline = AddVideoObjectFromFile(INTERFACEDIR "/militiamapsectoroutline.sti");
2836 guiSubLevel1 = AddVideoObjectFromFile(INTERFACEDIR "/mine_1.sti");
2837 guiSubLevel2 = AddVideoObjectFromFile(INTERFACEDIR "/mine_2.sti");
2838 guiSubLevel3 = AddVideoObjectFromFile(INTERFACEDIR "/mine_3.sti");
2839
2840 for (auto s : GCM->getMapSecrets())
2841 {
2842 const ST::string& path = s->secretMapIcon;
2843 if (!path.empty() && gSecretSiteIcons.find(path) == gSecretSiteIcons.end())
2844 {
2845 gSecretSiteIcons[path] = AddVideoObjectFromFile(path.c_str());
2846 }
2847 }
2848 }
2849
2850
DeleteMapScreenInterfaceMapGraphics()2851 void DeleteMapScreenInterfaceMapGraphics()
2852 {
2853 DeleteVideoSurface(guiBIGMAP);
2854 DeleteVideoObject(guiBULLSEYE);
2855 DeleteVideoObject(guiSAMICON);
2856 DeleteVideoObject(guiCHARBETWEENSECTORICONS);
2857 DeleteVideoObject(guiCHARBETWEENSECTORICONSCLOSE);
2858 DeleteVideoObject(guiCHARICONS);
2859 DeleteVideoObject(guiHelicopterIcon);
2860 DeleteVideoObject(guiMAPCURSORS);
2861 DeleteVideoObject(guiMINEICON);
2862 DeleteVideoObject(guiMapBorderHeliSectors);
2863 DeleteVideoObject(guiMilitia);
2864 DeleteVideoObject(guiMilitiaMaps);
2865 DeleteVideoObject(guiMilitiaSectorHighLight);
2866 DeleteVideoObject(guiMilitiaSectorOutline);
2867 DeleteVideoObject(guiSubLevel1);
2868 DeleteVideoObject(guiSubLevel2);
2869 DeleteVideoObject(guiSubLevel3);
2870
2871 for (auto& pair : gSecretSiteIcons)
2872 {
2873 DeleteVideoObject(pair.second);
2874 }
2875 gSecretSiteIcons.clear();
2876 }
2877
2878
2879 static void CheckAndUpdateStatesOfSelectedMilitiaSectorButtons();
2880 static void DisplayUnallocatedMilitia(void);
2881 static void DrawTownMilitiaName();
2882 static void RenderIconsPerSectorForSelectedTown(void);
2883 static void RenderShadingForUnControlledSectors(void);
2884 static void SetMilitiaMapButtonsText(void);
2885 static void ShowHighLightedSectorOnMilitiaMap(void);
2886
2887
DrawMilitiaPopUpBox()2888 void DrawMilitiaPopUpBox()
2889 {
2890 if( !fShowMilitia )
2891 {
2892 sSelectedMilitiaTown = 0;
2893 }
2894
2895 // create buttons
2896 CreateDestroyMilitiaSectorButtons( );
2897
2898 // create mouse regions if we need to
2899 CreateDestroyMilitiaPopUPRegions( );
2900
2901 if (!sSelectedMilitiaTown) return;
2902
2903 // update states of militia selected sector buttons
2904 CheckAndUpdateStatesOfSelectedMilitiaSectorButtons();
2905
2906 BltVideoObject(FRAME_BUFFER, guiMilitia, 0, MAP_MILITIA_BOX_POS_X, MAP_MILITIA_BOX_POS_Y);
2907 BltVideoObject(FRAME_BUFFER, guiMilitiaMaps, sSelectedMilitiaTown - 1, MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X, MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y);
2908
2909 // set font color for labels and "total militia" counts
2910 SetFontForeground(FONT_WHITE);
2911
2912 // draw name of town, and the "unassigned" label
2913 DrawTownMilitiaName();
2914
2915 // render the icons for each sector in the town
2916 RenderIconsPerSectorForSelectedTown( );
2917
2918 // shade any sectors not under our control
2919 RenderShadingForUnControlledSectors( );
2920
2921 // display anyone picked up
2922 DisplayUnallocatedMilitia( );
2923
2924 // draw the highlight last
2925 ShowHighLightedSectorOnMilitiaMap( );
2926
2927 ETRLEObject const& pTrav = guiMilitia->SubregionProperties(0);
2928 InvalidateRegion(MAP_MILITIA_BOX_POS_X, MAP_MILITIA_BOX_POS_Y, MAP_MILITIA_BOX_POS_X + pTrav.usWidth, MAP_MILITIA_BOX_POS_Y + pTrav.usHeight);
2929
2930 // set the text for the militia map sector info buttons
2931 SetMilitiaMapButtonsText( );
2932
2933 // render buttons
2934 MarkButtonsDirty( );
2935 }
2936
2937
2938 static void CreateMilitiaPanelBottomButton(void);
2939 static void DeleteMilitiaPanelBottomButton(void);
2940 static void HandleShutDownOfMilitiaPanelIfPeopleOnTheCursor(INT16 sTownValue);
2941 static void MilitiaRegionClickCallback(MOUSE_REGION*, INT32 reason);
2942 static void MilitiaRegionMoveCallback(MOUSE_REGION*, INT32 reason);
2943
2944
CreateDestroyMilitiaPopUPRegions(void)2945 void CreateDestroyMilitiaPopUPRegions(void)
2946 {
2947 static INT16 sOldTown = 0;
2948
2949 // create destroy militia pop up regions for mapscreen militia pop up box
2950 if (sSelectedMilitiaTown != 0)
2951 {
2952 sOldTown = sSelectedMilitiaTown;
2953 }
2954
2955 if (fShowMilitia && sSelectedMilitiaTown && !gfMilitiaPopupCreated)
2956 {
2957 for (INT32 i = 0; i < 9; ++i)
2958 {
2959 MOUSE_REGION* const r = &gMapScreenMilitiaBoxRegions[i];
2960 UINT16 const x = MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + i % MILITIA_BOX_ROWS * MILITIA_BOX_BOX_WIDTH;
2961 UINT16 const y = MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y + i / MILITIA_BOX_ROWS * MILITIA_BOX_BOX_HEIGHT;
2962 MSYS_DefineRegion(r, x, y, x + MILITIA_BOX_BOX_WIDTH, y + MILITIA_BOX_BOX_HEIGHT, MSYS_PRIORITY_HIGHEST - 3, MSYS_NO_CURSOR, MilitiaRegionMoveCallback, MilitiaRegionClickCallback);
2963 MSYS_SetRegionUserData(r, 0, i);
2964 }
2965
2966 CreateMilitiaPanelBottomButton();
2967 gfMilitiaPopupCreated = TRUE;
2968 }
2969 else if (gfMilitiaPopupCreated && (!fShowMilitia || !sSelectedMilitiaTown))
2970 {
2971 for (INT32 i = 0; i < 9; ++i)
2972 {
2973 MSYS_RemoveRegion(&gMapScreenMilitiaBoxRegions[i]);
2974 }
2975
2976 // handle the shutdown of the panel...there maybe people on the cursor, distribute them evenly over all the sectors
2977 HandleShutDownOfMilitiaPanelIfPeopleOnTheCursor(sOldTown);
2978
2979 DeleteMilitiaPanelBottomButton();
2980 gfMilitiaPopupCreated = FALSE;
2981 }
2982 }
2983
2984
2985 static INT16 GetBaseSectorForCurrentTown(void);
2986
2987
RenderIconsPerSectorForSelectedTown(void)2988 static void RenderIconsPerSectorForSelectedTown(void)
2989 {
2990 // render icons for map
2991 INT16 const sBaseSectorValue = GetBaseSectorForCurrentTown();
2992 for (INT32 iCounter = 0; iCounter != 9; ++iCounter)
2993 {
2994 INT32 const dx = iCounter % MILITIA_BOX_ROWS;
2995 INT32 const dy = iCounter / MILITIA_BOX_ROWS;
2996 INT16 const sector = sBaseSectorValue + dx + dy * 16;
2997
2998 // skip sectors not in the selected town (nearby other towns or wilderness SAM Sites)
2999 if (GetTownIdForSector(sector) != sSelectedMilitiaTown) continue;
3000
3001 // get number of each
3002 SECTORINFO const& si = SectorInfo[sector];
3003 INT32 const n_greens = si.ubNumberOfCivsAtLevel[GREEN_MILITIA];
3004 INT32 const n_regulars = si.ubNumberOfCivsAtLevel[REGULAR_MILITIA];
3005 INT32 const n_elites = si.ubNumberOfCivsAtLevel[ELITE_MILITIA];
3006 INT32 const n_total = n_greens + n_regulars + n_elites;
3007
3008 StrategicMapElement const& e = StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(sector)];
3009 if (e.bNameId != BLANK_SECTOR && !e.fEnemyControlled)
3010 {
3011 // print number of troops
3012 SetFont(FONT10ARIAL);
3013 ST::string sString = ST::format("{}", n_total);
3014 INT16 sX;
3015 INT16 sY;
3016 INT16 const x = MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + dx * MILITIA_BOX_BOX_WIDTH;
3017 INT16 const y = MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y + dy * MILITIA_BOX_BOX_HEIGHT;
3018 FindFontRightCoordinates(x, y, MILITIA_BOX_BOX_WIDTH, 0, sString, FONT10ARIAL, &sX, &sY);
3019 MPrint(sSectorMilitiaMapSector != iCounter ? sX : sX - 15, sY + MILITIA_BOX_BOX_HEIGHT - 5, sString);
3020 }
3021
3022 // now display
3023 for (INT32 i = 0; i < n_total; ++i)
3024 {
3025 // get screen x and y coords
3026 INT16 x = MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + dx * MILITIA_BOX_BOX_WIDTH;
3027 INT16 y = MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y + dy * MILITIA_BOX_BOX_HEIGHT;
3028 UINT16 icon_base;
3029 if (sSectorMilitiaMapSector == iCounter)
3030 {
3031 x += i % POPUP_MILITIA_ICONS_PER_ROW * MEDIUM_MILITIA_ICON_SPACING + 2;
3032 y += i / POPUP_MILITIA_ICONS_PER_ROW * (MEDIUM_MILITIA_ICON_SPACING - 1) + 3;
3033 icon_base = 5;
3034 }
3035 else
3036 {
3037 x += i % POPUP_MILITIA_ICONS_PER_ROW * MEDIUM_MILITIA_ICON_SPACING + 3;
3038 y += i / POPUP_MILITIA_ICONS_PER_ROW * MEDIUM_MILITIA_ICON_SPACING + 3;
3039 icon_base = 8;
3040 }
3041
3042 UINT16 const icon =
3043 i < n_greens ? icon_base :
3044 i < n_greens + n_regulars ? icon_base + 1 :
3045 icon_base + 2;
3046 BltVideoObject(FRAME_BUFFER, guiMilitia, icon, x, y);
3047 }
3048 }
3049 }
3050
3051
3052 // Get the sector value for the upper left corner
GetBaseSectorForCurrentTown(void)3053 static INT16 GetBaseSectorForCurrentTown(void)
3054 {
3055 INT16 sBaseSector = 0;
3056
3057 // is the current town
3058 if( sSelectedMilitiaTown != 0 )
3059 {
3060 sBaseSector = sBaseSectorList[ ( INT16 )( sSelectedMilitiaTown - 1 ) ];
3061 }
3062
3063 // return the current sector value
3064 return( sBaseSector );
3065 }
3066
3067
ShowHighLightedSectorOnMilitiaMap(void)3068 static void ShowHighLightedSectorOnMilitiaMap(void)
3069 {
3070 // show the highlighted sector on the militia map
3071 INT16 sX = 0, sY = 0;
3072
3073 if( sSectorMilitiaMapSector != -1 )
3074 {
3075 sX = MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + ( ( sSectorMilitiaMapSector % MILITIA_BOX_ROWS ) * MILITIA_BOX_BOX_WIDTH );
3076 sY = MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y + ( ( sSectorMilitiaMapSector / MILITIA_BOX_ROWS ) * MILITIA_BOX_BOX_HEIGHT );
3077 BltVideoObject(FRAME_BUFFER, guiMilitiaSectorHighLight, 0, sX, sY);
3078 }
3079
3080 if( sSectorMilitiaMapSectorOutline != -1 )
3081 {
3082 sX = MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + ( ( sSectorMilitiaMapSectorOutline % MILITIA_BOX_ROWS ) * MILITIA_BOX_BOX_WIDTH );
3083 sY = MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y + ( ( sSectorMilitiaMapSectorOutline / MILITIA_BOX_ROWS ) * MILITIA_BOX_BOX_HEIGHT );
3084 BltVideoObject(FRAME_BUFFER, guiMilitiaSectorOutline, 0, sX, sY);
3085 }
3086 }
3087
3088
3089 static bool IsThisMilitiaTownSectorAllowable(INT16 sSectorIndexValue);
3090
3091
MilitiaRegionClickCallback(MOUSE_REGION * const r,INT32 const reason)3092 static void MilitiaRegionClickCallback(MOUSE_REGION* const r, INT32 const reason)
3093 {
3094 if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
3095 {
3096 INT16 const val = MSYS_GetRegionUserData(r, 0);
3097 sSectorMilitiaMapSector =
3098 !IsThisMilitiaTownSectorAllowable(val) ? -1 :
3099 sSectorMilitiaMapSector == val ? -1 :
3100 val;
3101 }
3102 else if (reason & MSYS_CALLBACK_REASON_RBUTTON_UP)
3103 {
3104 sSectorMilitiaMapSector = -1;
3105 }
3106 }
3107
3108
MilitiaRegionMoveCallback(MOUSE_REGION * const r,INT32 const reason)3109 static void MilitiaRegionMoveCallback(MOUSE_REGION* const r, INT32 const reason)
3110 {
3111 if (reason & MSYS_CALLBACK_REASON_GAIN_MOUSE)
3112 {
3113 INT16 const val = MSYS_GetRegionUserData(r, 0);
3114 sSectorMilitiaMapSectorOutline =
3115 !IsThisMilitiaTownSectorAllowable(val) ? -1 :
3116 val;
3117 }
3118 else if (reason & MSYS_CALLBACK_REASON_LOST_MOUSE)
3119 {
3120 sSectorMilitiaMapSectorOutline = -1;
3121 }
3122 }
3123
3124
3125 static void MilitiaButtonCallback(GUI_BUTTON* btn, INT32 reason);
3126
3127
CreateDestroyMilitiaSectorButtons()3128 void CreateDestroyMilitiaSectorButtons()
3129 {
3130 static INT16 sOldSectorValue = -1;
3131
3132 if (!fMilitiaMapButtonsCreated && sOldSectorValue == sSectorMilitiaMapSector && fShowMilitia && sSelectedMilitiaTown && sSectorMilitiaMapSector != -1)
3133 {
3134 // given sector..place down the 3 buttons
3135 INT16 const x = MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + sSectorMilitiaMapSector % MILITIA_BOX_ROWS * MILITIA_BOX_BOX_WIDTH + MILITIA_BTN_OFFSET_X;
3136 INT16 y = MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y + sSectorMilitiaMapSector / MILITIA_BOX_ROWS * MILITIA_BOX_BOX_HEIGHT + 2;
3137 for (INT32 i = 0; i != 3; y += MILITIA_BTN_HEIGHT, ++i)
3138 {
3139 GUIButtonRef b = QuickCreateButtonImg(INTERFACEDIR "/militia.sti", 3, 4, x, y, MSYS_PRIORITY_HIGHEST - 1, MilitiaButtonCallback);
3140 giMapMilitiaButton[i] = b;
3141 b->SetUserData(i);
3142 b->SpecifyGeneralTextAttributes(ST::null, FONT10ARIAL, gsMilitiaSectorButtonColors[i], FONT_BLACK);
3143 b->SpecifyTextSubOffsets(0, 0, TRUE);
3144 b->fShiftText = FALSE;
3145 b->SetFastHelpText(pMilitiaButtonsHelpText[i]);
3146 }
3147
3148 CreateScreenMaskForMoveBox();
3149
3150 // Set the fact that the buttons are created
3151 fMilitiaMapButtonsCreated = true;
3152 }
3153 else if (fMilitiaMapButtonsCreated && (sOldSectorValue != sSectorMilitiaMapSector || !fShowMilitia || !sSelectedMilitiaTown || sSectorMilitiaMapSector == -1))
3154 {
3155 sOldSectorValue = sSectorMilitiaMapSector;
3156
3157 // get rid of the buttons
3158 for (INT32 i = 0; i != 3; ++i)
3159 {
3160 RemoveButton(giMapMilitiaButton[i]);
3161 }
3162
3163 if (!fShowMilitia || !sSelectedMilitiaTown)
3164 {
3165 sSectorMilitiaMapSector = -1;
3166 sSelectedMilitiaTown = 0;
3167 }
3168
3169 RemoveScreenMaskForMoveBox();
3170
3171 // Set the fact that the buttons are destroyed
3172 fMilitiaMapButtonsCreated = false;
3173 }
3174
3175 sOldSectorValue = sSectorMilitiaMapSector;
3176 }
3177
3178
SetMilitiaMapButtonsText()3179 static void SetMilitiaMapButtonsText()
3180 {
3181 if (!fMilitiaMapButtonsCreated) return;
3182
3183 // grab the appropriate global sector value in the world
3184 INT16 const base_sector = GetBaseSectorForCurrentTown();
3185 INT16 const sector = base_sector + (sSectorMilitiaMapSector % MILITIA_BOX_ROWS + sSectorMilitiaMapSector / MILITIA_BOX_ROWS * 16);
3186 SECTORINFO const& si = SectorInfo[sector];
3187 ST::string buf;
3188
3189 // the greens in this sector
3190 buf = ST::format("{}", si.ubNumberOfCivsAtLevel[GREEN_MILITIA]);
3191 giMapMilitiaButton[0]->SpecifyText(buf);
3192
3193 // the regulars in this sector
3194 buf = ST::format("{}", si.ubNumberOfCivsAtLevel[REGULAR_MILITIA]);
3195 giMapMilitiaButton[1]->SpecifyText(buf);
3196
3197 // the number of elites in this sector
3198 buf = ST::format("{}", si.ubNumberOfCivsAtLevel[ELITE_MILITIA]);
3199 giMapMilitiaButton[2]->SpecifyText(buf);
3200 }
3201
3202
MilitiaButtonCallback(GUI_BUTTON * btn,INT32 reason)3203 static void MilitiaButtonCallback(GUI_BUTTON *btn, INT32 reason)
3204 {
3205 INT32 const iValue = btn->GetUserData();
3206
3207 // get the sector value for the upper left corner
3208 INT16 sBaseSectorValue = GetBaseSectorForCurrentTown();
3209 INT16 sGlobalMapSector = sBaseSectorValue + sSectorMilitiaMapSector % MILITIA_BOX_ROWS + sSectorMilitiaMapSector / MILITIA_BOX_ROWS * 16;
3210
3211 if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
3212 {
3213 DropAPersonInASector(iValue, sGlobalMapSector);
3214 }
3215 else if (reason & MSYS_CALLBACK_REASON_RBUTTON_UP)
3216 {
3217 PickUpATownPersonFromSector(iValue, sGlobalMapSector);
3218 }
3219 }
3220
3221
DisplayUnallocatedMilitia(void)3222 static void DisplayUnallocatedMilitia(void)
3223 {
3224 // show the nunber on the cursor
3225 INT32 iTotalNumberOfTroops =0, iNumberOfGreens = 0, iNumberOfRegulars =0, iNumberOfElites = 0, iCurrentTroopIcon = 0;
3226 INT32 iCurrentIcon = 0;
3227 INT16 sX = 0, sY = 0;
3228
3229 // get number of each
3230 iNumberOfGreens = sGreensOnCursor;
3231 iNumberOfRegulars = sRegularsOnCursor;
3232 iNumberOfElites = sElitesOnCursor;
3233
3234 // get total
3235 iTotalNumberOfTroops = iNumberOfGreens + iNumberOfRegulars + iNumberOfElites;
3236
3237 // now display
3238 for( iCurrentTroopIcon = 0; iCurrentTroopIcon < iTotalNumberOfTroops; iCurrentTroopIcon++ )
3239 {
3240 // get screen x and y coords
3241 sX = ( iCurrentTroopIcon % NUMBER_OF_MILITIA_ICONS_PER_LOWER_ROW ) * MEDIUM_MILITIA_ICON_SPACING + MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + 1;
3242 sY = ( iCurrentTroopIcon / NUMBER_OF_MILITIA_ICONS_PER_LOWER_ROW ) * MEDIUM_MILITIA_ICON_SPACING + MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_LOWER_ROW_Y;
3243
3244 if( iCurrentTroopIcon < iNumberOfGreens )
3245 {
3246 iCurrentIcon = 8;
3247 }
3248 else if( iCurrentTroopIcon < iNumberOfGreens + iNumberOfRegulars )
3249 {
3250 iCurrentIcon = 9;
3251 }
3252 else
3253 {
3254 iCurrentIcon = 10;
3255 }
3256
3257 BltVideoObject(FRAME_BUFFER, guiMilitia, iCurrentIcon, sX, sY);
3258 }
3259 }
3260
3261
3262 // Is this sector allowed to be clicked on?
IsThisMilitiaTownSectorAllowable(INT16 const sSectorIndexValue)3263 static bool IsThisMilitiaTownSectorAllowable(INT16 const sSectorIndexValue)
3264 {
3265 INT16 const base_sector = GetBaseSectorForCurrentTown();
3266 INT16 const sector = base_sector + sSectorIndexValue % MILITIA_BOX_ROWS + sSectorIndexValue / MILITIA_BOX_ROWS * 16;
3267 INT16 const x = SECTORX(sector);
3268 INT16 const y = SECTORY(sector);
3269 return
3270 StrategicMap[CALCULATE_STRATEGIC_INDEX(x, y)].bNameId != BLANK_SECTOR &&
3271 SectorOursAndPeaceful(x, y, 0);
3272 }
3273
3274
DrawTownMilitiaName()3275 static void DrawTownMilitiaName()
3276 {
3277 ST::string town = GCM->getTownName(sSelectedMilitiaTown);
3278 INT16 const x = MAP_MILITIA_BOX_POS_X;
3279 INT16 const y = MAP_MILITIA_BOX_POS_Y;
3280 INT16 const w = MILITIA_BOX_WIDTH;
3281 ST::string buf;
3282 INT16 sX;
3283 INT16 sY;
3284
3285 // get the name for the current militia town
3286 buf = ST::format("{} {}", town, pMilitiaString[0]);
3287 FindFontCenterCoordinates(x, y + MILITIA_BOX_TEXT_OFFSET_Y, w, MILITIA_BOX_TEXT_TITLE_HEIGHT, buf, FONT10ARIAL, &sX, &sY);
3288 MPrint(sX, sY, buf);
3289
3290 // might as well show the unassigned string
3291 buf = ST::format("{} {}", town, pMilitiaString[1]);
3292 FindFontCenterCoordinates(x, y + MILITIA_BOX_UNASSIGNED_TEXT_OFFSET_Y, w, GetFontHeight(FONT10ARIAL), buf, FONT10ARIAL, &sX, &sY);
3293 MPrint(sX, sY, buf);
3294 }
3295
3296
HandleShutDownOfMilitiaPanelIfPeopleOnTheCursor(INT16 const town)3297 static void HandleShutDownOfMilitiaPanelIfPeopleOnTheCursor(INT16 const town)
3298 {
3299 // check if anyone still on the cursor
3300 if (sGreensOnCursor == 0 && sRegularsOnCursor == 0 && sElitesOnCursor == 0) return;
3301
3302 FOR_EACH_SECTOR_IN_TOWN(i, town)
3303 {
3304 UINT8 const sector = i->sector;
3305 if (!SectorOursAndPeaceful(SECTORX(sector), SECTORY(sector), 0)) continue;
3306 SECTORINFO& si = SectorInfo[sector];
3307 UINT8& n_green = si.ubNumberOfCivsAtLevel[GREEN_MILITIA];
3308 UINT8& n_regular = si.ubNumberOfCivsAtLevel[REGULAR_MILITIA];
3309 UINT8& n_elite = si.ubNumberOfCivsAtLevel[ELITE_MILITIA];
3310 INT32 n = MAX_ALLOWABLE_MILITIA_PER_SECTOR - n_green - n_regular - n_elite;
3311 if (n == 0) continue;
3312
3313 while (sGreensOnCursor != 0 || sRegularsOnCursor != 0 || sElitesOnCursor != 0)
3314 {
3315 if (sGreensOnCursor != 0)
3316 {
3317 ++n_green;
3318 --sGreensOnCursor;
3319 if (--n == 0) break;
3320 }
3321
3322 if (sRegularsOnCursor != 0)
3323 {
3324 ++n_regular;
3325 --sRegularsOnCursor;
3326 if (--n == 0) break;
3327 }
3328
3329 if (sElitesOnCursor != 0)
3330 {
3331 ++n_elite;
3332 --sElitesOnCursor;
3333 if (--n == 0) break;
3334 }
3335 }
3336
3337 if (sector == GetWorldSector()) gfStrategicMilitiaChangesMade = TRUE;
3338 }
3339
3340 Assert(sGreensOnCursor == 0);
3341 Assert(sRegularsOnCursor == 0);
3342 Assert(sElitesOnCursor == 0);
3343 }
3344
3345
3346 // Even out troops among the town
HandleEveningOutOfTroopsAmongstSectors()3347 static void HandleEveningOutOfTroopsAmongstSectors()
3348 {
3349 INT8 const town = sSelectedMilitiaTown;
3350
3351 // How many sectors in the selected town do we control?
3352 INT32 const n_under_control = GetTownSectorsUnderControl(town);
3353 if (n_under_control == 0) return; // If none, there's nothing to be done
3354
3355 INT32 n_green = 0;
3356 INT32 n_regular = 0;
3357 INT32 n_elite = 0;
3358 INT16 const base_sector = GetBaseSectorForCurrentTown();
3359 for (INT32 i = 0; i != 9; ++i)
3360 {
3361 INT16 const sector = base_sector + i % MILITIA_BOX_ROWS + i / MILITIA_BOX_ROWS * 16;
3362
3363 // Skip sectors not in the selected town (nearby other towns or wilderness SAM Sites)
3364 if (GetTownIdForSector(sector) != town) continue;
3365
3366 if (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(sector)].fEnemyControlled) continue;
3367
3368 SECTORINFO const& si = SectorInfo[sector];
3369 n_green += si.ubNumberOfCivsAtLevel[GREEN_MILITIA];
3370 n_regular += si.ubNumberOfCivsAtLevel[REGULAR_MILITIA];
3371 n_elite += si.ubNumberOfCivsAtLevel[ELITE_MILITIA];
3372 }
3373
3374 // Grab those on the cursor
3375 n_green += sGreensOnCursor;
3376 n_regular += sRegularsOnCursor;
3377 n_elite += sElitesOnCursor;
3378
3379 if (n_green + n_regular + n_elite == 0) return;
3380
3381 INT32 const n_green_per_sector = n_green / n_under_control;
3382 INT32 const n_regular_per_sector = n_regular / n_under_control;
3383 INT32 const n_elite_per_sector = n_elite / n_under_control;
3384
3385 // Get the left overs
3386 INT32 n_left_over_green = n_green % n_under_control;
3387 INT32 n_left_over_regular = n_regular % n_under_control;
3388 INT32 n_left_over_elite = n_elite % n_under_control;
3389
3390 FOR_EACH_SECTOR_IN_TOWN(i, town)
3391 {
3392 UINT8 const sector = i->sector;
3393 if (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(sector)].fEnemyControlled) continue;
3394 if (NumHostilesInSector(SECTORX(sector), SECTORY(sector), 0) != 0) continue;
3395
3396 SECTORINFO& si = SectorInfo[sector];
3397
3398 // Distribute here
3399 si.ubNumberOfCivsAtLevel[GREEN_MILITIA] = n_green_per_sector;
3400 si.ubNumberOfCivsAtLevel[REGULAR_MILITIA] = n_regular_per_sector;
3401 si.ubNumberOfCivsAtLevel[ELITE_MILITIA] = n_elite_per_sector;
3402
3403 // Add leftovers that weren't included in the div operation
3404 INT16 total_so_far = n_green_per_sector + n_regular_per_sector + n_elite_per_sector;
3405 if (n_left_over_green != 0 && total_so_far < MAX_ALLOWABLE_MILITIA_PER_SECTOR)
3406 {
3407 ++si.ubNumberOfCivsAtLevel[GREEN_MILITIA];
3408 ++total_so_far;
3409 --n_left_over_green;
3410 }
3411 if (n_left_over_regular != 0 && total_so_far < MAX_ALLOWABLE_MILITIA_PER_SECTOR)
3412 {
3413 ++si.ubNumberOfCivsAtLevel[REGULAR_MILITIA];
3414 ++total_so_far;
3415 --n_left_over_regular;
3416 }
3417 if (n_left_over_elite && total_so_far < MAX_ALLOWABLE_MILITIA_PER_SECTOR)
3418 {
3419 ++si.ubNumberOfCivsAtLevel[ELITE_MILITIA];
3420 ++total_so_far;
3421 --n_left_over_elite;
3422 }
3423
3424 if (sector == GetWorldSector()) gfStrategicMilitiaChangesMade = TRUE;
3425 }
3426
3427 // Zero out numbers on the cursor
3428 sGreensOnCursor = 0;
3429 sRegularsOnCursor = 0;
3430 sElitesOnCursor = 0;
3431 }
3432
3433
MakeButton(UINT idx,INT16 x,GUI_CALLBACK click,const ST::string & text)3434 static void MakeButton(UINT idx, INT16 x, GUI_CALLBACK click, const ST::string& text)
3435 {
3436 GUIButtonRef const btn = QuickCreateButtonImg(INTERFACEDIR "/militia.sti", 1, 2, x, MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_BOX_AUTO_BOX_Y, MSYS_PRIORITY_HIGHEST - 1, click);
3437 giMapMilitiaButton[idx] = btn;
3438 btn->SpecifyGeneralTextAttributes(text, FONT10ARIAL, FONT_BLACK, FONT_BLACK);
3439 }
3440
3441
3442 static bool CanMilitiaAutoDistribute();
3443 static void MilitiaAutoButtonCallback(GUI_BUTTON* btn, INT32 reason);
3444 static void MilitiaDoneButtonCallback(GUI_BUTTON* btn, INT32 reason);
3445
3446
CreateMilitiaPanelBottomButton(void)3447 static void CreateMilitiaPanelBottomButton(void)
3448 {
3449 MakeButton(3, MAP_MILITIA_BOX_POS_X + MAP_MILITIA_BOX_AUTO_BOX_X, MilitiaAutoButtonCallback, pMilitiaButtonString[0]);
3450 MakeButton(4, MAP_MILITIA_BOX_POS_X + MAP_MILITIA_BOX_DONE_BOX_X, MilitiaDoneButtonCallback, pMilitiaButtonString[1]);
3451
3452 // AUTO button help
3453 giMapMilitiaButton[3]->SetFastHelpText(pMilitiaButtonsHelpText[3]);
3454
3455
3456 // if auto-distribution is not possible
3457 if ( !CanMilitiaAutoDistribute( ) )
3458 {
3459 // disable the AUTO button
3460 DisableButton( giMapMilitiaButton[ 3 ] );
3461 }
3462 }
3463
3464
DeleteMilitiaPanelBottomButton(void)3465 static void DeleteMilitiaPanelBottomButton(void)
3466 {
3467
3468 // delete militia panel bottom
3469 RemoveButton( giMapMilitiaButton[ 3 ] );
3470 RemoveButton( giMapMilitiaButton[ 4 ] );
3471
3472 if( sSelectedMilitiaTown != 0 )
3473 {
3474 HandleShutDownOfMilitiaPanelIfPeopleOnTheCursor( sSelectedMilitiaTown );
3475 }
3476
3477 // redraw the map
3478 fMapPanelDirty = TRUE;
3479
3480 }
3481
3482
MilitiaAutoButtonCallback(GUI_BUTTON * btn,INT32 reason)3483 static void MilitiaAutoButtonCallback(GUI_BUTTON *btn, INT32 reason)
3484 {
3485 if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
3486 {
3487 // distribute troops over all the sectors under control
3488 HandleEveningOutOfTroopsAmongstSectors();
3489 fMapPanelDirty = TRUE;
3490 }
3491 }
3492
3493
MilitiaDoneButtonCallback(GUI_BUTTON * btn,INT32 reason)3494 static void MilitiaDoneButtonCallback(GUI_BUTTON *btn, INT32 reason)
3495 {
3496 if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
3497 {
3498 // reset fact we are in the box
3499 sSelectedMilitiaTown = 0;
3500 fMapPanelDirty = TRUE;
3501 }
3502 }
3503
3504
RenderShadingForUnControlledSectors(void)3505 static void RenderShadingForUnControlledSectors(void)
3506 {
3507 // now render shading over any uncontrolled sectors
3508 INT16 const sBaseSectorValue = GetBaseSectorForCurrentTown();
3509 for (INT32 dy = 0; dy != 3; ++dy)
3510 {
3511 for (INT32 dx = 0; dx != 3; ++dx)
3512 {
3513 INT32 const x = SECTORX(sBaseSectorValue) + dx;
3514 INT32 const y = SECTORY(sBaseSectorValue) + dy;
3515
3516 StrategicMapElement const& e = StrategicMap[CALCULATE_STRATEGIC_INDEX(x, y)];
3517 if (e.bNameId == BLANK_SECTOR) continue;
3518 if (!e.fEnemyControlled && NumHostilesInSector(x, y, 0) == 0) continue;
3519
3520 // shade this sector, not under our control
3521 INT16 const sX = MAP_MILITIA_BOX_POS_X + MAP_MILITIA_MAP_X + dx * MILITIA_BOX_BOX_WIDTH;
3522 INT16 const sY = MAP_MILITIA_BOX_POS_Y + MAP_MILITIA_MAP_Y + dy * MILITIA_BOX_BOX_HEIGHT;
3523 FRAME_BUFFER->ShadowRect(sX, sY, sX + MILITIA_BOX_BOX_WIDTH - 1, sY + MILITIA_BOX_BOX_HEIGHT - 1);
3524 }
3525 }
3526 }
3527
3528
DrawMilitiaForcesForSector(INT32 const sector)3529 static void DrawMilitiaForcesForSector(INT32 const sector)
3530 {
3531 INT16 const x = SECTORX(sector);
3532 INT16 const y = SECTORY(sector);
3533
3534 if (StrategicMap[CALCULATE_STRATEGIC_INDEX(x, y)].fEnemyControlled) return;
3535
3536 /* Large/small icon offset in the .sti */
3537 INT32 const icon = 5;
3538 INT32 pos = 0;
3539 SECTORINFO const& si = SectorInfo[sector];
3540 for (INT32 i = si.ubNumberOfCivsAtLevel[GREEN_MILITIA]; i != 0; --i)
3541 {
3542 DrawMapBoxIcon(guiMilitia, icon, x, y, pos++);
3543 }
3544 for (INT32 i = si.ubNumberOfCivsAtLevel[REGULAR_MILITIA]; i != 0; --i)
3545 {
3546 DrawMapBoxIcon(guiMilitia, icon + 1, x, y, pos++);
3547 }
3548 for (INT32 i = si.ubNumberOfCivsAtLevel[ELITE_MILITIA]; i != 0; --i)
3549 {
3550 DrawMapBoxIcon(guiMilitia, icon + 2, x, y, pos++);
3551 }
3552 }
3553
3554
DrawTownMilitiaForcesOnMap()3555 static void DrawTownMilitiaForcesOnMap()
3556 {
3557 ClipBlitsToMapViewRegion();
3558
3559 FOR_EACH_TOWN_SECTOR(i)
3560 {
3561 DrawMilitiaForcesForSector(i->sector);
3562 }
3563
3564 // now handle militia for sam sectors
3565 for(auto s : GCM->getSamSites())
3566 {
3567 DrawMilitiaForcesForSector(s->sectorId);
3568 }
3569
3570 RestoreClipRegionToFullScreen();
3571 }
3572
3573
CheckAndUpdateStatesOfSelectedMilitiaSectorButtons()3574 static void CheckAndUpdateStatesOfSelectedMilitiaSectorButtons()
3575 {
3576 if (!fMilitiaMapButtonsCreated)
3577 {
3578 EnableButton(giMapMilitiaButton[4]);
3579 return;
3580 }
3581
3582 INT16 const base_sector = GetBaseSectorForCurrentTown();
3583 INT16 const sector = base_sector + (sSectorMilitiaMapSector % MILITIA_BOX_ROWS + sSectorMilitiaMapSector / MILITIA_BOX_ROWS * 16);
3584 SECTORINFO const& si = SectorInfo[sector];
3585 INT32 const n_green = si.ubNumberOfCivsAtLevel[GREEN_MILITIA] + sGreensOnCursor;
3586 INT32 const n_regular = si.ubNumberOfCivsAtLevel[REGULAR_MILITIA] + sRegularsOnCursor;
3587 INT32 const n_elite = si.ubNumberOfCivsAtLevel[ELITE_MILITIA] + sElitesOnCursor;
3588 EnableButton(giMapMilitiaButton[4], sGreensOnCursor + sRegularsOnCursor + sElitesOnCursor == 0); // Done
3589 EnableButton(giMapMilitiaButton[0], n_green != 0); // greens button
3590 EnableButton(giMapMilitiaButton[1], n_regular != 0); // regulars button
3591 EnableButton(giMapMilitiaButton[2], n_elite != 0); // elites button
3592 }
3593
3594
3595 static void HideExistenceOfUndergroundMapSector(UINT8 ubSectorX, UINT8 ubSectorY);
3596
3597
ShadeSubLevelsNotVisited(void)3598 static void ShadeSubLevelsNotVisited(void)
3599 {
3600 // Obtain the 16-bit version of the same color used in the mine STIs
3601 gusUndergroundNearBlack = Get16BPPColor(FROMRGB(2, 2, 0));
3602
3603 // Run through all (real & possible) underground sectors
3604 for (UNDERGROUND_SECTORINFO const* i = gpUndergroundSectorInfoHead; i; i = i->next)
3605 {
3606 if (i->ubSectorZ != (UINT8)iCurrentMapSectorZ) continue;
3607 if (i->uiFlags & SF_ALREADY_VISITED) continue;
3608 /* The sector is on the currently displayed sublevel and has never been
3609 * visited. Remove that portion of the "mine" graphics from view. */
3610 HideExistenceOfUndergroundMapSector(i->ubSectorX, i->ubSectorY);
3611 }
3612 }
3613
3614
HandleLowerLevelMapBlit(void)3615 static void HandleLowerLevelMapBlit(void)
3616 {
3617 // blits the sub level maps
3618 const SGPVObject* vo; // XXX HACK000E
3619 switch( iCurrentMapSectorZ )
3620 {
3621 case 1: vo = guiSubLevel1; break;
3622 case 2: vo = guiSubLevel2; break;
3623 case 3: vo = guiSubLevel3; break;
3624
3625 default: abort(); // HACK000E
3626 }
3627
3628 // handle the blt of the sublevel
3629 BltVideoObject(guiSAVEBUFFER, vo, 0, MAP_VIEW_START_X + 21, MAP_VIEW_START_Y + 17);
3630
3631 // handle shading of sublevels
3632 ShadeSubLevelsNotVisited( );
3633 }
3634
3635
GetNumberOfMilitiaInSector(INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)3636 INT32 GetNumberOfMilitiaInSector( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ )
3637 {
3638 INT32 iNumberInSector = 0;
3639
3640 if( !bSectorZ )
3641 {
3642 iNumberInSector = SectorInfo[ SECTOR( sSectorX, sSectorY )].ubNumberOfCivsAtLevel[ GREEN_MILITIA ]
3643 + SectorInfo[ SECTOR( sSectorX, sSectorY )].ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]
3644 + SectorInfo[ SECTOR( sSectorX, sSectorY )].ubNumberOfCivsAtLevel[ ELITE_MILITIA ];
3645 }
3646
3647 return( iNumberInSector );
3648 }
3649
3650
3651 //There is a special case flag used when players encounter enemies in a sector, then retreat. The number of enemies
3652 //will display on mapscreen until time is compressed. When time is compressed, the flag is cleared, and
3653 //a question mark is displayed to reflect that the player no longer knows. This is the function that clears that
3654 //flag.
ClearAnySectorsFlashingNumberOfEnemies()3655 void ClearAnySectorsFlashingNumberOfEnemies()
3656 {
3657 INT32 i;
3658 for( i = 0; i < 256; i++ )
3659 {
3660 SectorInfo[ i ].uiFlags &= ~SF_PLAYER_KNOWS_ENEMIES_ARE_HERE;
3661 }
3662
3663 // redraw map
3664 fMapPanelDirty = TRUE;
3665 }
3666
3667
3668 static BOOLEAN CanMercsScoutThisSector(INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ);
3669
3670
WhatPlayerKnowsAboutEnemiesInSector(INT16 sSectorX,INT16 sSectorY)3671 UINT32 WhatPlayerKnowsAboutEnemiesInSector( INT16 sSectorX, INT16 sSectorY )
3672 {
3673 UINT32 uiSectorFlags = SectorInfo[ SECTOR( sSectorX, sSectorY ) ].uiFlags;
3674
3675
3676 // if player has militia close enough to scout this sector out, if there are mercs who can scout here, OR
3677 //Special case flag used when players encounter enemies in a sector, then retreat. The number of enemies
3678 //will display on mapscreen until time is compressed. When time is compressed, the flag is cleared, and
3679 //a question mark is displayed to reflect that the player no longer knows.
3680 if ( CanMercsScoutThisSector( sSectorX, sSectorY, 0 ) ||
3681 CanNearbyMilitiaScoutThisSector( sSectorX, sSectorY ) ||
3682 ( uiSectorFlags & SF_PLAYER_KNOWS_ENEMIES_ARE_HERE ) )
3683 {
3684 // if the enemies are stationary (i.e. mercs attacking a garrison)
3685 if ( NumStationaryEnemiesInSector( sSectorX, sSectorY ) > 0 )
3686 {
3687 // inside a garrison - hide their # (show question mark) to match what the PBI is showing
3688 return KNOWS_THEYRE_THERE;
3689 }
3690 else
3691 {
3692 // other situations - show exactly how many there are
3693 return KNOWS_HOW_MANY;
3694 }
3695 }
3696
3697 // if the player has visited the sector during this game
3698 if (GetSectorFlagStatus(sSectorX, sSectorY, 0, SF_ALREADY_VISITED))
3699 {
3700 // then he always knows about any enemy presence for the remainder of the game, but not exact numbers
3701 return KNOWS_THEYRE_THERE;
3702 }
3703
3704 // if Skyrider noticed the enemis in the sector recently
3705 if ( uiSectorFlags & SF_SKYRIDER_NOTICED_ENEMIES_HERE )
3706 {
3707 // and Skyrider is still in this sector, flying
3708 if( IsSkyriderFlyingInSector( sSectorX, sSectorY ) )
3709 {
3710 // player remains aware of them as long as Skyrider remains in the sector
3711 return KNOWS_THEYRE_THERE;
3712 }
3713 else
3714 {
3715 // Skyrider is gone, reset the flag that he noticed enemies here
3716 SectorInfo[ SECTOR( sSectorX, sSectorY ) ].uiFlags &= ~SF_SKYRIDER_NOTICED_ENEMIES_HERE;
3717 }
3718 }
3719
3720
3721 // no information available
3722 return KNOWS_NOTHING;
3723 }
3724
3725
CanMercsScoutThisSector(INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)3726 static BOOLEAN CanMercsScoutThisSector(INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ)
3727 {
3728 CFOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
3729 {
3730 // vehicles can't scout!
3731 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
3732 {
3733 continue;
3734 }
3735
3736 // POWs, dead guys, guys in transit, sleeping, and really hurt guys can't scout!
3737 if ( ( pSoldier->bAssignment == IN_TRANSIT ) ||
3738 ( pSoldier->bAssignment == ASSIGNMENT_POW ) ||
3739 ( pSoldier->bAssignment == ASSIGNMENT_DEAD ) ||
3740 pSoldier->fMercAsleep ||
3741 ( pSoldier->bLife < OKLIFE ) )
3742 {
3743 continue;
3744 }
3745
3746 // don't count mercs aboard Skyrider
3747 if (InHelicopter(*pSoldier)) continue;
3748
3749 // mercs on the move can't scout
3750 if ( pSoldier->fBetweenSectors )
3751 {
3752 continue;
3753 }
3754
3755 // is he here?
3756 if( ( pSoldier->sSectorX == sSectorX ) && ( pSoldier->sSectorY == sSectorY ) && ( pSoldier->bSectorZ == bSectorZ ) )
3757 {
3758 return( TRUE );
3759 }
3760 }
3761
3762 // none here who can scout
3763 return( FALSE );
3764 }
3765
3766
HandleShowingOfEnemyForcesInSector(INT16 const x,INT16 const y,INT8 const z,UINT8 const icon_pos)3767 static void HandleShowingOfEnemyForcesInSector(INT16 const x, INT16 const y, INT8 const z, UINT8 const icon_pos)
3768 {
3769 // ATE: If game has just started, don't do it
3770 if (DidGameJustStart()) return;
3771
3772 // Never display enemies underground - sector info doesn't have support for it
3773 if (z != 0) return;
3774
3775 INT16 const n_enemies = NumEnemiesInSector(x, y);
3776 if (n_enemies == 0) return; // No enemies here, display nothing
3777
3778 switch (WhatPlayerKnowsAboutEnemiesInSector(x, y))
3779 {
3780 case KNOWS_NOTHING: // Display nothing
3781 break;
3782
3783 case KNOWS_THEYRE_THERE: // Display a question mark
3784 ShowUncertainNumberEnemiesInSector(x, y);
3785 break;
3786
3787 case KNOWS_HOW_MANY:
3788 /* Display individual icons for each enemy, starting at the received icon
3789 * position index */
3790 ShowEnemiesInSector(x, y, n_enemies, icon_pos);
3791 break;
3792 }
3793 }
3794
3795
3796 static void BlitSAMGridMarkers();
3797
3798
ShowSAMSitesOnStrategicMap()3799 static void ShowSAMSitesOnStrategicMap()
3800 {
3801 if (fShowAircraftFlag) BlitSAMGridMarkers();
3802
3803 for (auto s : GCM->getSamSites())
3804 {
3805 INT16 const sector = s->sectorId;
3806 INT16 const sec_x = SECTORX(sector);
3807 INT16 const sec_y = SECTORY(sector);
3808
3809 // Has the sam site here been found?
3810 auto secret = GetMapSecretBySectorID(sector);
3811 if (secret && !IsSecretFoundAt(s->sectorId)) continue;
3812
3813 DrawSite(sec_x, sec_y, guiSAMICON);
3814
3815 if (fShowAircraftFlag)
3816 { // write "SAM Site" centered underneath
3817 INT16 x;
3818 INT16 y;
3819 GetScreenXYFromMapXY(sec_x, sec_y, &x, &y);
3820 x += 11;
3821 y += 19;
3822
3823 const ST::string& sam_site = GCM->getLandTypeString(SAM_SITE);
3824
3825 // Center the first string around x.
3826 x -= StringPixLength(sam_site, MAP_FONT) / 2;
3827
3828 if (x < MAP_VIEW_START_X || MAP_VIEW_START_X + MAP_VIEW_WIDTH < x) continue;
3829 if (y < MAP_VIEW_START_Y || MAP_VIEW_START_Y + MAP_VIEW_HEIGHT < y) continue;
3830 // Within view, render.
3831
3832 SetFontDestBuffer(guiSAVEBUFFER, MapScreenRect.iLeft + 2, MapScreenRect.iTop, MapScreenRect.iRight, MapScreenRect.iBottom);
3833
3834 ClipBlitsToMapViewRegion();
3835
3836 // Green on green does not contrast well, use yellow.
3837 SetFontAttributes(MAP_FONT, FONT_MCOLOR_LTYELLOW);
3838 GDirtyPrint(x, y, sam_site);
3839
3840 RestoreClipRegionToFullScreen();
3841 }
3842 }
3843 }
3844
3845
BlitSAMGridMarkers()3846 static void BlitSAMGridMarkers()
3847 {
3848 UINT16 const colour = Get16BPPColor(FROMRGB(100, 100, 100));
3849
3850 SGPVSurface::Lock l(guiSAVEBUFFER);
3851 UINT32 const uiDestPitchBYTES = l.Pitch();
3852
3853 ClipBlitsToMapViewRegionForRectangleAndABit(uiDestPitchBYTES);
3854
3855 for (auto s : GCM->getSamSites())
3856 {
3857 // Has the sam site here been found?
3858 if (!IsSecretFoundAt(s->sectorId)) continue;
3859
3860 INT16 x;
3861 INT16 y;
3862 INT16 w;
3863 INT16 h;
3864 INT16 const sector = s->sectorId;
3865
3866 GetScreenXYFromMapXY(SECTORX(sector), SECTORY(sector), &x, &y);
3867 w = MAP_GRID_X;
3868 h = MAP_GRID_Y;
3869
3870 RectangleDraw(TRUE, x, y - 1, x + w, y + h - 1, colour, l.Buffer<UINT16>());
3871 }
3872
3873 RestoreClipRegionToFullScreenForRectangle(uiDestPitchBYTES);
3874 }
3875
3876
CanMilitiaAutoDistribute()3877 static bool CanMilitiaAutoDistribute()
3878 {
3879 INT8 const town = sSelectedMilitiaTown;
3880
3881 // Cannot auto-distribute, if we don't have a town selected. (this excludes SAM sites)
3882 if (town == BLANK_SECTOR) return false;
3883
3884 FOR_EACH_SECTOR_IN_TOWN(i, town)
3885 {
3886 INT16 const sector = i->sector;
3887 if (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(sector)].fEnemyControlled) continue;
3888
3889 UINT8 const (&n_milita)[MAX_MILITIA_LEVELS] = SectorInfo[sector].ubNumberOfCivsAtLevel;
3890 if (n_milita[GREEN_MILITIA] + n_milita[REGULAR_MILITIA] + n_milita[ELITE_MILITIA] != 0) return true;
3891 }
3892
3893 // No militia in town, cannot auto-distribute.
3894 return false;
3895 }
3896
3897
ShowItemsOnMap(void)3898 static void ShowItemsOnMap(void)
3899 {
3900 ClipBlitsToMapViewRegion();
3901 SetFontDestBuffer(guiSAVEBUFFER, MapScreenRect.iLeft + 2, MapScreenRect.iTop, MapScreenRect.iRight, MapScreenRect.iBottom);
3902 SetFontAttributes(MAP_FONT, FONT_MCOLOR_LTGREEN);
3903
3904 // run through sectors
3905 for (INT16 x = 1; x < MAP_WORLD_X - 1; ++x)
3906 {
3907 for (INT16 y = 1; y < MAP_WORLD_Y - 1; ++y)
3908 {
3909 // to speed this up, only look at sector that player has visited
3910 if (!GetSectorFlagStatus(x, y, iCurrentMapSectorZ, SF_ALREADY_VISITED)) continue;
3911
3912 UINT32 const n_items = GetNumberOfVisibleWorldItemsFromSectorStructureForSector(x, y, iCurrentMapSectorZ);
3913 if (n_items == 0) continue;
3914
3915 INT16 usXPos;
3916 INT16 usYPos;
3917 INT16 const sXCorner = MAP_VIEW_START_X + x * MAP_GRID_X;
3918 INT16 const sYCorner = MAP_VIEW_START_Y + y * MAP_GRID_Y;
3919 ST::string sString = ST::format("{}", n_items);
3920 FindFontCenterCoordinates(sXCorner, sYCorner, MAP_GRID_X, MAP_GRID_Y, sString, MAP_FONT, &usXPos, &usYPos);
3921 GDirtyPrint(usXPos, usYPos, sString);
3922 }
3923 }
3924
3925 RestoreClipRegionToFullScreen();
3926 }
3927
3928
DrawMapBoxIcon(HVOBJECT const vo,UINT16 const icon,INT16 const sec_x,INT16 const sec_y,UINT8 const icon_pos)3929 static void DrawMapBoxIcon(HVOBJECT const vo, UINT16 const icon, INT16 const sec_x, INT16 const sec_y, UINT8 const icon_pos)
3930 {
3931 /* Don't show any more icons than will fit into one sector, to keep them from
3932 * spilling into sector(s) beneath */
3933 if (icon_pos >= MERC_ICONS_PER_LINE * ROWS_PER_SECTOR) return;
3934
3935 INT32 const col = icon_pos % MERC_ICONS_PER_LINE;
3936 INT32 const row = icon_pos / MERC_ICONS_PER_LINE;
3937
3938 INT32 const x = MAP_VIEW_START_X + sec_x * MAP_GRID_X + MAP_X_ICON_OFFSET + 3 * col;
3939 INT32 const y = MAP_VIEW_START_Y + sec_y * MAP_GRID_Y + MAP_Y_ICON_OFFSET + 3 * row;
3940 BltVideoObject(guiSAVEBUFFER, vo, icon, x, y);
3941 InvalidateRegion(x, y, x + DMAP_GRID_X, y + DMAP_GRID_Y);
3942 }
3943
DrawSecretSite(const StrategicMapSecretModel * secret)3944 void DrawSecretSite(const StrategicMapSecretModel* secret)
3945 {
3946 if (!secret->secretMapIcon.empty())
3947 {
3948 const SGPVObject *const icon = gSecretSiteIcons.at(secret->secretMapIcon);
3949 const INT16 x = SECTORX(secret->sectorID);
3950 const INT16 y = SECTORY(secret->sectorID);
3951 DrawSite(x, y, icon);
3952 }
3953 }
3954
3955
DrawBullseye(void)3956 static void DrawBullseye(void)
3957 {
3958 INT16 sX, sY;
3959
3960 GetScreenXYFromMapXY(SECTORX(g_merc_arrive_sector), SECTORY(g_merc_arrive_sector), &sX, &sY);
3961 sY -= 2;
3962
3963 BltVideoObject(guiSAVEBUFFER, guiBULLSEYE, 0, sX, sY);
3964 }
3965
3966
HideExistenceOfUndergroundMapSector(UINT8 ubSectorX,UINT8 ubSectorY)3967 static void HideExistenceOfUndergroundMapSector(UINT8 ubSectorX, UINT8 ubSectorY)
3968 {
3969 INT16 sScreenX;
3970 INT16 sScreenY;
3971
3972 GetScreenXYFromMapXY( ubSectorX, ubSectorY, &sScreenX, &sScreenY );
3973
3974 // fill it with near black
3975 ColorFillVideoSurfaceArea( guiSAVEBUFFER, sScreenX + 1, sScreenY, sScreenX + MAP_GRID_X, sScreenY + MAP_GRID_Y - 1, gusUndergroundNearBlack );
3976 }
3977
3978
CanRedistributeMilitiaInSector(INT16 sClickedSectorX,INT16 sClickedSectorY,INT8 bClickedTownId)3979 BOOLEAN CanRedistributeMilitiaInSector(INT16 sClickedSectorX, INT16 sClickedSectorY, INT8 bClickedTownId)
3980 {
3981 INT32 iCounter = 0;
3982 INT16 sBaseSectorValue = 0, sCurrentSectorValue = 0;
3983 INT16 sSectorX = 0, sSectorY = 0;
3984
3985 // if no world is loaded, we can't be in combat (PBI/Auto-resolve locks out normal mapscreen interface for this)
3986 if( !gfWorldLoaded )
3987 {
3988 // ok to redistribute
3989 return( TRUE );
3990 }
3991
3992 // if tactically not in combat, hostile sector, or air-raid
3993 if (!(gTacticalStatus.uiFlags & INCOMBAT) && !(gTacticalStatus.fEnemyInSector))
3994 {
3995 // ok to redistribute
3996 return( TRUE );
3997 }
3998
3999 // if the fight is underground
4000 if ( gbWorldSectorZ != 0 )
4001 {
4002 // ok to redistribute
4003 return( TRUE );
4004 }
4005
4006
4007 // currently loaded surface sector IS hostile - so we must check if it's also one of the sectors in this "militia map"
4008
4009 // get the sector value for the upper left corner
4010 sBaseSectorValue = sBaseSectorList[ bClickedTownId - 1 ];
4011
4012 // render icons for map
4013 for( iCounter = 0; iCounter < 9; iCounter++ )
4014 {
4015 // grab current sector value
4016 sCurrentSectorValue = sBaseSectorValue + ( ( iCounter % MILITIA_BOX_ROWS ) + ( iCounter / MILITIA_BOX_ROWS ) * ( 16 ) );
4017
4018 sSectorX = SECTORX( sCurrentSectorValue );
4019 sSectorY = SECTORY( sCurrentSectorValue );
4020
4021 // not in the same town?
4022 if( StrategicMap[ CALCULATE_STRATEGIC_INDEX( sSectorX, sSectorY ) ].bNameId != bClickedTownId )
4023 {
4024 continue;
4025 }
4026
4027 // if this is the loaded sector that is currently hostile
4028 if ( ( sSectorX == gWorldSectorX ) && ( sSectorY == gWorldSectorY ) )
4029 {
4030 // the fight is within this town! Can't redistribute.
4031 return( FALSE );
4032 }
4033 }
4034
4035
4036 // the fight is elsewhere - ok to redistribute
4037 return( TRUE );
4038 }
4039