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