1 #include "Map_Screen_Interface.h"
2 #include "ContentManager.h"
3 #include "Dialogue_Control.h"
4 #include "Directories.h"
5 #include "EMail.h"
6 #include "FileMan.h"
7 #include "Finances.h"
8 #include "Font.h"
9 #include "Font_Control.h"
10 #include "Game_Clock.h"
11 #include "Game_Event_Hook.h"
12 #include "Game_Init.h"
13 #include "GameInstance.h"
14 #include "GameSettings.h"
15 #include "HImage.h"
16 #include "Interface_Items.h"
17 #include "Isometric_Utils.h"
18 #include "Items.h"
19 #include "JAScreens.h"
20 #include "Keys.h"
21 #include "Line.h"
22 #include "LoadSaveObjectType.h"
23 #include "Map_Screen_Helicopter.h"
24 #include "Map_Screen_Interface_Border.h"
25 #include "Map_Screen_Interface_Bottom.h"
26 #include "Map_Screen_Interface_Map_Inventory.h"
27 #include "MapScreen.h"
28 #include "MercPortrait.h"
29 #include "Message.h"
30 #include "Overhead.h"
31 #include "PopUpBox.h"
32 #include "PreBattle_Interface.h"
33 #include "Queen_Command.h"
34 #include "Quests.h"
35 #include "Render_Dirty.h"
36 #include "Render_Fun.h"
37 #include "RenderWorld.h"
38 #include "SamSiteModel.h"
39 #include "ShippingDestinationModel.h"
40 #include "Soldier_Macros.h"
41 #include "SoundMan.h"
42 #include "Squads.h"
43 #include "Strategic.h"
44 #include "StrategicMap_Secrets.h"
45 #include "Strategic_Mines.h"
46 #include "Strategic_Pathing.h"
47 #include "SysUtil.h"
48 #include "Tactical_Placement_GUI.h"
49 #include "Tactical_Save.h"
50 #include "Text.h"
51 #include "Timer_Control.h"
52 #include "Video.h"
53 #include "VObject.h"
54 #include "VSurface.h"
55 #include "WordWrap.h"
56 #include <algorithm>
57 #include <iterator>
58 #include <string_theory/format>
59 #include <string_theory/string>
60 struct FACETYPE;
61 struct PopUpBox;
62 
63 // number of LINKED LISTS for sets of leave items (each slot holds an unlimited # of items)
64 #define NUM_LEAVE_LIST_SLOTS 20
65 
66 #define SELECTED_CHAR_ARROW_X (STD_SCREEN_X + 8)
67 
68 #define SIZE_OF_UPDATE_BOX 20
69 
70 // as deep as the map goes
71 #define MAX_DEPTH_OF_MAP 3
72 
73 
74 // number of merc columns for four wide mode
75 #define NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE 4
76 
77 // number of merc columns for 2 wide mode
78 #define NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE 2
79 
80 // number needed for 4 wide mode to activate
81 #define NUMBER_OF_MERCS_FOR_FOUR_WIDTH_UPDATE_PANEL 4
82 
83 #define DBL_CLICK_DELAY_FOR_MOVE_MENU 200
84 
85 
86 #define REASON_FOR_SOLDIER_UPDATE_OFFSET_Y				(14)
87 
88 #define MAX_MAPSCREEN_FAST_HELP			100
89 
90 #define VEHICLE_ONLY				FALSE
91 #define AND_ALL_ON_BOARD		TRUE
92 
93 
94 // the regions int he movemenu
95 enum{
96 	SQUAD_REGION = 0,
97 	VEHICLE_REGION,
98 	SOLDIER_REGION,
99 	DONE_REGION,
100 	CANCEL_REGION,
101 	OTHER_REGION,
102 };
103 
104 
105 // waiting list for update box
106 INT32 iUpdateBoxWaitingList[ MAX_CHARACTER_COUNT ];
107 
108 
109 struct FASTHELPREGION
110 {
111 	ST::string FastHelpText;
112 	INT32 iX;
113 	INT32 iY;
114 	INT32 iW;
115 };
116 
117 
118 static FASTHELPREGION pFastHelpMapScreenList[MAX_MAPSCREEN_FAST_HELP];
119 
120 // the move menu region
121 static MOUSE_REGION gMoveMenuRegion[MAX_POPUP_BOX_STRING_COUNT];
122 
123 static MOUSE_REGION gMapScreenHelpTextMask;
124 
125 static BOOLEAN fScreenMaskForMoveCreated = FALSE;
126 
127 static ST::string gsCustomErrorString;
128 
129 BOOLEAN fShowUpdateBox = FALSE;
130 static BOOLEAN fInterfaceFastHelpTextActive = FALSE;
131 BOOLEAN fReBuildCharacterList = FALSE;
132 static INT32 giSizeOfInterfaceFastHelpTextList = 0;
133 
134 //Animated sector locator icon variables.
135 INT16 gsSectorLocatorX;
136 INT16 gsSectorLocatorY;
137 UINT8 gubBlitSectorLocatorCode; //color
138 static SGPVObject* guiSectorLocatorGraphicID;
139 // the animate time per frame in milliseconds
140 #define ANIMATED_BATTLEICON_FRAME_TIME 80
141 #define MAX_FRAME_COUNT_FOR_ANIMATED_BATTLE_ICON 12
142 
143 
144 // number of mercs in sector capable of moving
145 static INT32 giNumberOfSoldiersInSectorMoving = 0;
146 
147 // number of squads capable of moving
148 static INT32 giNumberOfSquadsInSectorMoving = 0;
149 
150 // number of vehicles in sector moving
151 static INT32 giNumberOfVehiclesInSectorMoving = 0;
152 
153 // the list of soldiers that are moving
154 static SOLDIERTYPE* pSoldierMovingList[MAX_CHARACTER_COUNT];
155 static bool         fSoldierIsMoving[MAX_CHARACTER_COUNT];
156 
157 static SOLDIERTYPE* pUpdateSoldierBox[SIZE_OF_UPDATE_BOX];
158 
159 static SGPVObject* giUpdateSoldierFaces[SIZE_OF_UPDATE_BOX];
160 
161 // the squads thata re moving
162 static INT32 iSquadMovingList[NUMBER_OF_SQUADS];
163 static INT32 fSquadIsMoving[NUMBER_OF_SQUADS];
164 
165 // the vehicles thata re moving
166 static INT32 iVehicleMovingList[NUMBER_OF_SQUADS];
167 static INT32 fVehicleIsMoving[NUMBER_OF_SQUADS];
168 
169 static MOUSE_REGION gMoveBoxScreenMask;
170 
171 
172 BOOLEAN fShowMapScreenMovementList = FALSE;
173 
174 
175 MapScreenCharacterSt gCharactersList[ MAX_CHARACTER_COUNT+1];
176 
177 MOUSE_REGION	gMapStatusBarsRegion;
178 
179 static UpdateBoxReason iReasonForSoldierUpDate = NO_REASON_FOR_UPDATE;
180 
181 
182 // disable team info panels due to battle roster
183 BOOLEAN   fDisableDueToBattleRoster = FALSE;
184 
185 // track old contract times
186 static INT32 iOldContractTimes[MAX_CHARACTER_COUNT];
187 
188 // position of pop up box
189 INT32 giBoxY = 0;
190 
191 static MOUSE_REGION gContractIconRegion;
192 static MOUSE_REGION gInsuranceIconRegion;
193 static MOUSE_REGION gDepositIconRegion;
194 
195 // general line..current and old
196 INT32 giHighLine=-1;
197 
198 // assignment's line...glow box
199 INT32 giAssignHighLine=-1;
200 
201 // destination plot line....glow box
202 INT32 giDestHighLine=-1;
203 
204 // contract selection glow box
205 INT32 giContractHighLine = -1;
206 
207 // the sleep column glow box
208 INT32 giSleepHighLine = -1;
209 
210 // pop up box textures
211 SGPVSurface* guiPOPUPTEX;
212 SGPVObject* guiPOPUPBORDERS;
213 
214 // the currently selected character arrow
215 static SGPVObject* guiSelectedCharArrow;
216 
217 static GUIButtonRef guiUpdatePanelButtons[2];
218 
219 // the update panel
220 SGPVObject* guiUpdatePanelTactical;
221 
222 struct MERC_LEAVE_ITEM
223 {
224 	OBJECTTYPE o;
225 	MERC_LEAVE_ITEM* pNext;
226 };
227 
228 // the leave item list
229 static MERC_LEAVE_ITEM* gpLeaveListHead[NUM_LEAVE_LIST_SLOTS];
230 
231 // holds ids of mercs who left stuff behind
232 static UINT32 guiLeaveListOwnerProfileId[NUM_LEAVE_LIST_SLOTS];
233 
234 // timers for double click
235 static INT32 giDblClickTimersForMoveBoxMouseRegions[MAX_POPUP_BOX_STRING_COUNT];
236 
237 UINT32 guiSectorLocatorBaseTime = 0;
238 
239 
240 // which menus are we showing
241 BOOLEAN fShowAssignmentMenu = FALSE;
242 BOOLEAN fShowTrainingMenu = FALSE;
243 BOOLEAN fShowAttributeMenu = FALSE;
244 BOOLEAN fShowSquadMenu = FALSE;
245 BOOLEAN fShowContractMenu = FALSE;
246 
247 static BOOLEAN fRebuildMoveBox = FALSE;
248 
249 // at least one merc was hired at some time
250 BOOLEAN gfAtLeastOneMercWasHired = FALSE;
251 
252 
InitalizeVehicleAndCharacterList(void)253 void InitalizeVehicleAndCharacterList( void )
254 {
255 	// will init the vehicle and character lists to zero
256 	std::fill(std::begin(gCharactersList), std::end(gCharactersList), MapScreenCharacterSt{});
257 }
258 
259 
SetEntryInSelectedCharacterList(INT8 bEntry)260 void SetEntryInSelectedCharacterList( INT8 bEntry )
261 {
262 	Assert( ( bEntry >= 0 ) && ( bEntry < MAX_CHARACTER_COUNT ) );
263 	gCharactersList[bEntry].selected = TRUE;
264 }
265 
266 
ResetEntryForSelectedList(INT8 bEntry)267 void ResetEntryForSelectedList( INT8 bEntry )
268 {
269 	Assert( ( bEntry >= 0 ) && ( bEntry < MAX_CHARACTER_COUNT ) );
270 	gCharactersList[bEntry].selected = FALSE;
271 }
272 
273 
ResetSelectedListForMapScreen(void)274 void ResetSelectedListForMapScreen( void )
275 {
276 	// set all the entries int he selected list to false
277 	for (size_t i = 0; i != MAX_CHARACTER_COUNT; ++i)
278 	{
279 		gCharactersList[i].selected = FALSE;
280 	}
281 
282 	// if we still have a valid dude selected
283 	if (GetSelectedInfoChar() != NULL)
284 	{
285 		// then keep him selected
286 		SetEntryInSelectedCharacterList( bSelectedInfoChar );
287 	}
288 }
289 
290 
IsEntryInSelectedListSet(INT8 bEntry)291 BOOLEAN IsEntryInSelectedListSet( INT8 bEntry )
292 {
293 	Assert( ( bEntry >= 0 ) && ( bEntry < MAX_CHARACTER_COUNT ) );
294 	return gCharactersList[bEntry].selected;
295 }
296 
297 
ToggleEntryInSelectedList(INT8 bEntry)298 void ToggleEntryInSelectedList( INT8 bEntry )
299 {
300 	Assert( ( bEntry >= 0 ) && ( bEntry < MAX_CHARACTER_COUNT ) );
301 	MapScreenCharacterSt* const c = &gCharactersList[bEntry];
302 	c->selected = !c->selected;
303 }
304 
305 
BuildSelectedListFromAToB(INT8 bA,INT8 bB)306 void BuildSelectedListFromAToB( INT8 bA, INT8 bB )
307 {
308 	INT8 bStart =0, bEnd = 0;
309 
310 	// run from a to b..set slots as selected
311 
312 	if( bA > bB )
313 	{
314 		bStart = bB;
315 		bEnd = bA;
316 	}
317 	else
318 	{
319 		bStart = bA;
320 		bEnd = bB;
321 	}
322 
323 	// run through list and set all intermediaries to true
324 
325 	for( ; bStart <= bEnd; bStart++ )
326 	{
327 		SetEntryInSelectedCharacterList( bStart );
328 	}
329 }
330 
331 
MultipleCharacterListEntriesSelected(void)332 BOOLEAN MultipleCharacterListEntriesSelected( void )
333 {
334 	UINT8 ubSelectedCnt = 0;
335 
336 	// check if more than one person is selected in the selected list
337 	CFOR_EACH_SELECTED_IN_CHAR_LIST(c)
338 	{
339 		++ubSelectedCnt;
340 	}
341 
342 	if( ubSelectedCnt > 1 )
343 	{
344 		return( TRUE );
345 	}
346 	else
347 	{
348 		return( FALSE );
349 	}
350 }
351 
352 
ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList()353 void ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList()
354 {
355 	CFOR_EACH_IN_CHAR_LIST(c)
356 	{
357 		SOLDIERTYPE* const pSoldier = c->merc;
358 		if( pSoldier->bAssignment == TRAIN_TOWN )
359 		{
360 			if (!SectorInfo[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)].fMilitiaTrainingPaid)
361 			{
362 				ResumeOldAssignment( pSoldier );
363 			}
364 		}
365 	}
366 }
367 
368 
ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector(INT16 sSectorX,INT16 sSectorY)369 void ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( INT16 sSectorX, INT16 sSectorY )
370 {
371 	CFOR_EACH_IN_CHAR_LIST(c)
372 	{
373 		SOLDIERTYPE* const pSoldier = c->merc;
374 		if( pSoldier->bAssignment == TRAIN_TOWN )
375 		{
376 			if( ( pSoldier->sSectorX == sSectorX ) && ( pSoldier->sSectorY == sSectorY ) && ( pSoldier->bSectorZ == 0 ) )
377 			{
378 				ResumeOldAssignment( pSoldier );
379 			}
380 		}
381 	}
382 }
383 
384 
385 static BOOLEAN CanSoldierMoveWithVehicleId(const SOLDIERTYPE* s, INT32 iVehicle1Id);
386 
387 
388 // check if the members of the selected list move with this guy... are they in the same mvt group?
DeselectSelectedListMercsWhoCantMoveWithThisGuy(const SOLDIERTYPE * const pSoldier)389 void DeselectSelectedListMercsWhoCantMoveWithThisGuy(const SOLDIERTYPE* const pSoldier)
390 {
391 	INT32 iCounter = 0;
392 
393 	// deselect any other selected mercs that can't travel together with pSoldier
394 	for( iCounter = 0; iCounter < MAX_CHARACTER_COUNT; iCounter++ )
395 	{
396 		const MapScreenCharacterSt* const c = &gCharactersList[iCounter];
397 		if (!c->selected) continue;
398 
399 		const SOLDIERTYPE* const pSoldier2 = c->merc;
400 		if (pSoldier2 == NULL) continue;
401 
402 		// skip the guy we are
403 		if (pSoldier == pSoldier2) continue;
404 
405 		// NOTE ABOUT THE VEHICLE TESTS BELOW:
406 		// Vehicles and foot squads can't plot movement together!
407 		// The ETAs are different, and unlike squads, vehicles can't travel everywhere!
408 		// However, different vehicles CAN plot together, since they all travel at the same rates now
409 
410 		// if anchor guy is IN a vehicle
411 		if( pSoldier->bAssignment == VEHICLE )
412 		{
413 			if ( !CanSoldierMoveWithVehicleId( pSoldier2, pSoldier->iVehicleId ) )
414 			{
415 				// reset entry for selected list
416 				ResetEntryForSelectedList( ( INT8 )iCounter );
417 			}
418 		}
419 		// if anchor guy IS a vehicle
420 		else if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
421 		{
422 			if ( !CanSoldierMoveWithVehicleId( pSoldier2, pSoldier->bVehicleID ) )
423 			{
424 				// reset entry for selected list
425 				ResetEntryForSelectedList( ( INT8 )iCounter );
426 			}
427 		}
428 		// if this guy is IN a vehicle
429 		else if( pSoldier2->bAssignment == VEHICLE )
430 		{
431 			if ( !CanSoldierMoveWithVehicleId( pSoldier, pSoldier2->iVehicleId ) )
432 			{
433 				// reset entry for selected list
434 				ResetEntryForSelectedList( ( INT8 )iCounter );
435 			}
436 		}
437 		// if this guy IS a vehicle
438 		else if ( pSoldier2->uiStatusFlags & SOLDIER_VEHICLE )
439 		{
440 			if ( !CanSoldierMoveWithVehicleId( pSoldier, pSoldier2->bVehicleID ) )
441 			{
442 				// reset entry for selected list
443 				ResetEntryForSelectedList( ( INT8 )iCounter );
444 			}
445 		}
446 		// reject those not a squad (vehicle handled above)
447 		else if( pSoldier2->bAssignment >= ON_DUTY )
448 		{
449 			ResetEntryForSelectedList( ( INT8 )iCounter );
450 		}
451 		else
452 		{
453 			// reject those not in the same sector
454 			if( ( pSoldier->sSectorX != pSoldier2->sSectorX ) ||
455 					( pSoldier->sSectorY != pSoldier2->sSectorY ) ||
456 					( pSoldier->bSectorZ != pSoldier2->bSectorZ ) )
457 			{
458 				ResetEntryForSelectedList( ( INT8 )iCounter );
459 			}
460 
461 			// if either is between sectors, they must be in the same movement group
462 			if ( ( pSoldier->fBetweenSectors || pSoldier2->fBetweenSectors ) &&
463 				( pSoldier->ubGroupID != pSoldier2->ubGroupID ) )
464 			{
465 				ResetEntryForSelectedList( ( INT8 )iCounter );
466 			}
467 		}
468 
469 		// different movement groups in same sector is OK, even if they're not travelling together
470 	}
471 }
472 
473 
474 static BOOLEAN AnyMercInSameSquadOrVehicleIsSelected(const SOLDIERTYPE* s);
475 
476 
SelectUnselectedMercsWhoMustMoveWithThisGuy(void)477 void SelectUnselectedMercsWhoMustMoveWithThisGuy( void )
478 {
479 	INT32 iCounter = 0;
480 
481 	for( iCounter = 0; iCounter < MAX_CHARACTER_COUNT; iCounter++ )
482 	{
483 		const MapScreenCharacterSt* const c = &gCharactersList[iCounter];
484 		if (c->selected) continue;
485 
486 		const SOLDIERTYPE* const pSoldier = c->merc;
487 		if (pSoldier == NULL) continue;
488 
489 		// if on a squad or in a vehicle
490 		if ( ( pSoldier->bAssignment < ON_DUTY ) || ( pSoldier->bAssignment == VEHICLE ) )
491 		{
492 			// and a member of that squad or vehicle is selected
493 			if ( AnyMercInSameSquadOrVehicleIsSelected( pSoldier ) )
494 			{
495 				// then also select this guy
496 				SetEntryInSelectedCharacterList( ( INT8 ) iCounter );
497 			}
498 		}
499 	}
500 }
501 
502 
AnyMercInSameSquadOrVehicleIsSelected(const SOLDIERTYPE * const pSoldier)503 static BOOLEAN AnyMercInSameSquadOrVehicleIsSelected(const SOLDIERTYPE* const pSoldier)
504 {
505 	CFOR_EACH_SELECTED_IN_CHAR_LIST(c)
506 	{
507 		const SOLDIERTYPE* const pSoldier2 = c->merc;
508 
509 		// if they have the same assignment
510 		if( pSoldier->bAssignment == pSoldier2->bAssignment )
511 		{
512 			// same squad?
513 			if ( pSoldier->bAssignment < ON_DUTY )
514 			{
515 				return ( TRUE );
516 			}
517 
518 			// same vehicle?
519 			if ( ( pSoldier->bAssignment == VEHICLE ) && ( pSoldier->iVehicleId == pSoldier2->iVehicleId ) )
520 			{
521 				return ( TRUE );
522 			}
523 		}
524 
525 		// target guy is in a vehicle, and this guy IS that vehicle
526 		if( ( pSoldier->bAssignment == VEHICLE ) && ( pSoldier2->uiStatusFlags & SOLDIER_VEHICLE ) &&
527 				( pSoldier->iVehicleId == pSoldier2->bVehicleID ) )
528 		{
529 			return ( TRUE );
530 		}
531 
532 		// this guy is in a vehicle, and the target guy IS that vehicle
533 		if( ( pSoldier2->bAssignment == VEHICLE ) && ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) &&
534 				( pSoldier2->iVehicleId == pSoldier->bVehicleID ) )
535 		{
536 			return ( TRUE );
537 		}
538 	}
539 
540 	return ( FALSE );
541 }
542 
543 
544 
RestoreBackgroundForAssignmentGlowRegionList(void)545 void RestoreBackgroundForAssignmentGlowRegionList( void )
546 {
547 	static INT32 iOldAssignmentLine = -1;
548 
549 	// will restore the background region of the assignment list after a glow has ceased
550 	// ( a _LOST_MOUSE reason to the assignment region mvt callback handler )
551 
552 	if (fShowAssignmentMenu)
553 	{
554 		// force update
555 		ForceUpDateOfBox( ghAssignmentBox );
556 		ForceUpDateOfBox( ghEpcBox );
557 		ForceUpDateOfBox( ghRemoveMercAssignBox );
558 		if (fShowSquadMenu)
559 		{
560 			ForceUpDateOfBox( ghSquadBox );
561 		}
562 		else if (fShowTrainingMenu)
563 		{
564 			ForceUpDateOfBox( ghTrainingBox );
565 		}
566 
567 	}
568 
569 	if( fDisableDueToBattleRoster )
570 	{
571 		return;
572 	}
573 
574 	if( iOldAssignmentLine != giAssignHighLine )
575 	{
576 		// restore background
577 		RestoreExternBackgroundRect( ASSIGN_X, Y_START - 1, ASSIGN_WIDTH, ( INT16 )( ( ( MAX_CHARACTER_COUNT + 1 ) * ( Y_SIZE + 2 ) ) + 1 ) );
578 
579 		// ARM: not good enough! must reblit the whole panel to erase glow chunk restored by help text disappearing!!!
580 		fTeamPanelDirty = TRUE;
581 
582 		// set old to current
583 		iOldAssignmentLine = giAssignHighLine;
584 	}
585 }
586 
RestoreBackgroundForDestinationGlowRegionList(void)587 void RestoreBackgroundForDestinationGlowRegionList( void )
588 {
589 	static INT32 iOldDestinationLine = -1;
590 
591 	// will restore the background region of the destinationz list after a glow has ceased
592 	// ( a _LOST_MOUSE reason to the assignment region mvt callback handler )
593 
594 	if( fDisableDueToBattleRoster )
595 	{
596 		return;
597 	}
598 
599 	if( iOldDestinationLine != giDestHighLine )
600 	{
601 		// restore background
602 		RestoreExternBackgroundRect( DEST_ETA_X, Y_START - 1, DEST_ETA_WIDTH, ( INT16 )( ( ( MAX_CHARACTER_COUNT + 1 ) * ( Y_SIZE + 2 ) ) + 1 ) );
603 
604 		// ARM: not good enough! must reblit the whole panel to erase glow chunk restored by help text disappearing!!!
605 		fTeamPanelDirty = TRUE;
606 
607 		// set old to current
608 		iOldDestinationLine = giDestHighLine;
609 	}
610 }
611 
RestoreBackgroundForContractGlowRegionList(void)612 void RestoreBackgroundForContractGlowRegionList( void )
613 {
614 	static INT32 iOldContractLine = -1;
615 
616 	// will restore the background region of the destinationz list after a glow has ceased
617 	// ( a _LOST_MOUSE reason to the assignment region mvt callback handler )
618 
619 	if( fDisableDueToBattleRoster )
620 	{
621 		return;
622 	}
623 
624 	if( iOldContractLine != giContractHighLine )
625 	{
626 		// restore background
627 		RestoreExternBackgroundRect( TIME_REMAINING_X, Y_START - 1, TIME_REMAINING_WIDTH, ( INT16 )( ( ( MAX_CHARACTER_COUNT + 1 ) * ( Y_SIZE + 2 ) ) + 1 ) ) ;
628 
629 		// ARM: not good enough! must reblit the whole panel to erase glow chunk restored by help text disappearing!!!
630 		fTeamPanelDirty = TRUE;
631 
632 		// set old to current
633 		iOldContractLine = giContractHighLine;
634 	}
635 }
636 
637 
RestoreBackgroundForSleepGlowRegionList(void)638 void RestoreBackgroundForSleepGlowRegionList( void )
639 {
640 	static INT32 iOldSleepHighLine = -1;
641 
642 	// will restore the background region of the sleep list after a glow has ceased
643 	// ( a _LOST_MOUSE reason to the assignment region mvt callback handler )
644 
645 	if( fDisableDueToBattleRoster )
646 	{
647 		return;
648 	}
649 
650 	if( iOldSleepHighLine != giSleepHighLine )
651 	{
652 		// restore background
653 		RestoreExternBackgroundRect( SLEEP_X, Y_START - 1, SLEEP_WIDTH, ( INT16 )( ( ( MAX_CHARACTER_COUNT + 1 ) * ( Y_SIZE + 2 ) ) + 1 ) ) ;
654 
655 		// ARM: not good enough! must reblit the whole panel to erase glow chunk restored by help text disappearing!!!
656 		fTeamPanelDirty = TRUE;
657 
658 		// set old to current
659 		iOldSleepHighLine = giSleepHighLine;
660 	}
661 }
662 
PlayGlowRegionSound(void)663 void PlayGlowRegionSound( void )
664 {
665 	// play a new message sound, if there is one playing, do nothing
666 	static UINT32 uiSoundId = 0;
667 
668 	if( uiSoundId != 0 )
669 	{
670 		// is sound playing?..don't play new one
671 		if (SoundIsPlaying(uiSoundId))
672 		{
673 			return;
674 		}
675 	}
676 
677 	// otherwise no sound playing, play one
678 	uiSoundId = PlayJA2SampleFromFile(SOUNDSDIR "/glowclick.wav", MIDVOLUME, 1, MIDDLEPAN);
679 }
680 
681 
682 
CharacterIsGettingPathPlotted(INT16 const sCharNumber)683 BOOLEAN CharacterIsGettingPathPlotted(INT16 const sCharNumber)
684 {
685 	// valid character number?
686 	if( ( sCharNumber < 0 ) || ( sCharNumber >= MAX_CHARACTER_COUNT ) )
687 	{
688 		return( FALSE );
689 	}
690 
691 	// is the character a valid one?
692 	if (gCharactersList[sCharNumber].merc == NULL) return FALSE;
693 
694 	// if the highlighted line character is also selected
695 	if ( ( ( giDestHighLine != -1 ) && IsEntryInSelectedListSet ( ( INT8 ) giDestHighLine ) ) ||
696 		( ( bSelectedDestChar != -1 ) && IsEntryInSelectedListSet ( bSelectedDestChar ) ) )
697 	{
698 		// then ALL selected lines will be affected
699 		if( IsEntryInSelectedListSet( ( INT8 ) sCharNumber ) )
700 		{
701 			return( TRUE );
702 		}
703 	}
704 	else
705 	{
706 		// if he is *the* selected dude
707 		if( bSelectedDestChar == sCharNumber )
708 		{
709 			return ( TRUE );
710 		}
711 
712 		// ONLY the highlighted line will be affected
713 		if ( sCharNumber == giDestHighLine )
714 		{
715 			return( TRUE );
716 		}
717 	}
718 
719 	return ( FALSE );
720 }
721 
IsCharacterSelectedForAssignment(INT16 sCharNumber)722 BOOLEAN IsCharacterSelectedForAssignment( INT16 sCharNumber )
723 {
724 	// valid character number?
725 	if( ( sCharNumber < 0 ) || ( sCharNumber >= MAX_CHARACTER_COUNT ) )
726 	{
727 		return( FALSE );
728 	}
729 
730 	// is the character a valid one?
731 	if (gCharactersList[sCharNumber].merc == NULL) return FALSE;
732 
733 	// if the highlighted line character is also selected
734 	if ( ( giAssignHighLine != -1 ) && IsEntryInSelectedListSet ( ( INT8 ) giAssignHighLine ) )
735 	{
736 		// then ALL selected lines will be affected
737 		if( IsEntryInSelectedListSet( ( INT8 ) sCharNumber ) )
738 		{
739 			return( TRUE );
740 		}
741 	}
742 	else
743 	{
744 		// ONLY the highlighted line will be affected
745 		if ( sCharNumber == giAssignHighLine )
746 		{
747 			return( TRUE );
748 		}
749 	}
750 
751 	return( FALSE );
752 }
753 
754 
755 
IsCharacterSelectedForSleep(INT16 sCharNumber)756 BOOLEAN IsCharacterSelectedForSleep( INT16 sCharNumber )
757 {
758 	// valid character number?
759 	if( ( sCharNumber < 0 ) || ( sCharNumber >= MAX_CHARACTER_COUNT ) )
760 	{
761 		return( FALSE );
762 	}
763 
764 	// is the character a valid one?
765 	if (gCharactersList[sCharNumber].merc == NULL) return FALSE;
766 
767 	// if the highlighted line character is also selected
768 	if ( ( giSleepHighLine != -1 ) && IsEntryInSelectedListSet ( ( INT8 ) giSleepHighLine ) )
769 	{
770 		// then ALL selected lines will be affected
771 		if( IsEntryInSelectedListSet( ( INT8 ) sCharNumber ) )
772 		{
773 			return( TRUE );
774 		}
775 	}
776 	else
777 	{
778 		// ONLY the highlighted line will be affected
779 		if ( sCharNumber == giSleepHighLine )
780 		{
781 			return( TRUE );
782 		}
783 	}
784 
785 	return( FALSE );
786 }
787 
DisableTeamInfoPanels(void)788 void DisableTeamInfoPanels( void )
789 {
790 	// disable team info panel
791 	fDisableDueToBattleRoster = TRUE;
792 }
793 
794 
EnableTeamInfoPanels(void)795 void EnableTeamInfoPanels( void )
796 {
797 	// enable team info panel
798 	fDisableDueToBattleRoster = FALSE;
799 }
800 
801 
DoMapMessageBoxWithRect(MessageBoxStyleID ubStyle,const ST::string & str,ScreenID uiExitScreen,MessageBoxFlags usFlags,MSGBOX_CALLBACK ReturnCallback,const SGPBox * centering_rect)802 void DoMapMessageBoxWithRect(MessageBoxStyleID ubStyle, const ST::string& str, ScreenID uiExitScreen, MessageBoxFlags usFlags, MSGBOX_CALLBACK ReturnCallback, const SGPBox* centering_rect)
803 {	// reset the highlighted line
804 	giHighLine = -1;
805 	DoMessageBox(ubStyle, str, uiExitScreen, usFlags, ReturnCallback, centering_rect);
806 }
807 
808 
DoMapMessageBox(MessageBoxStyleID ubStyle,const ST::string & str,ScreenID uiExitScreen,MessageBoxFlags usFlags,MSGBOX_CALLBACK ReturnCallback)809 void DoMapMessageBox(MessageBoxStyleID ubStyle, const ST::string& str, ScreenID uiExitScreen, MessageBoxFlags usFlags, MSGBOX_CALLBACK ReturnCallback)
810 {
811 	// reset the highlighted line
812 	giHighLine = -1;
813 
814 	// do message box and return
815 	SGPBox const centering_rect = { 0, 0, SCREEN_WIDTH, INV_INTERFACE_START_Y };
816 	DoMessageBox(ubStyle, str, uiExitScreen, usFlags, ReturnCallback, &centering_rect);
817 }
818 
819 
820 
GoDownOneLevelInMap(void)821 void GoDownOneLevelInMap( void )
822 {
823 	JumpToLevel( iCurrentMapSectorZ + 1 );
824 }
825 
826 
GoUpOneLevelInMap(void)827 void GoUpOneLevelInMap( void )
828 {
829 	JumpToLevel( iCurrentMapSectorZ - 1 );
830 }
831 
832 
JumpToLevel(INT32 iLevel)833 void JumpToLevel( INT32 iLevel )
834 {
835 	if (gfPreBattleInterfaceActive) return;
836 
837 	// disable level-changes while in inventory pool (for keyboard equivalents!)
838 	if( fShowMapInventoryPool )
839 		return;
840 
841 	if (bSelectedDestChar != -1 || fPlotForHelicopter)
842 	{
843 		AbortMovementPlottingMode( );
844 	}
845 
846 	if( iLevel < 0 )
847 	{
848 		iLevel = 0;
849 	}
850 
851 	if( iLevel > MAX_DEPTH_OF_MAP )
852 	{
853 		iLevel = MAX_DEPTH_OF_MAP;
854 	}
855 
856 	// set current sector Z to level passed
857 	ChangeSelectedMapSector( sSelMapX, sSelMapY, ( INT8 )iLevel );
858 }
859 
860 
861 // check against old contract times, update as nessacary
CheckAndUpdateBasedOnContractTimes(void)862 void CheckAndUpdateBasedOnContractTimes( void )
863 {
864 	INT32 iCounter = 0;
865 	INT32 iTimeRemaining = 0;
866 
867 	for( iCounter = 0; iCounter < MAX_CHARACTER_COUNT; iCounter++ )
868 	{
869 		const SOLDIERTYPE* const s = gCharactersList[iCounter].merc;
870 		if (s == NULL) continue;
871 
872 		// what kind of merc
873 		if (s->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC)
874 		{
875 			// amount of time left on contract
876 			iTimeRemaining = s->iEndofContractTime-GetWorldTotalMin();
877 			if(iTimeRemaining >60*24)
878 			{
879 				// more than a day, display in green
880 				iTimeRemaining/=(60*24);
881 
882 				// check if real change in contract time
883 				if( iTimeRemaining != iOldContractTimes[ iCounter ])
884 				{
885 					iOldContractTimes[ iCounter ] = iTimeRemaining;
886 
887 					// dirty screen
888 					fTeamPanelDirty = TRUE;
889 					fCharacterInfoPanelDirty = TRUE;
890 				}
891 			}
892 			else
893 			{
894 				// less than a day, display hours left in red
895 				iTimeRemaining/=60;
896 
897 				// check if real change in contract time
898 				if( iTimeRemaining != iOldContractTimes[ iCounter ])
899 				{
900 					iOldContractTimes[ iCounter ] = iTimeRemaining;
901 					// dirty screen
902 					fTeamPanelDirty = TRUE;
903 					fCharacterInfoPanelDirty = TRUE;
904 				}
905 			}
906 		}
907 		else if (s->ubWhatKindOfMercAmI == MERC_TYPE__MERC)
908 		{
909 			iTimeRemaining = s->iTotalContractLength;
910 
911 			if( iTimeRemaining != iOldContractTimes[ iCounter ])
912 			{
913 				iOldContractTimes[ iCounter ] = iTimeRemaining;
914 
915 				// dirty screen
916 				fTeamPanelDirty = TRUE;
917 				fCharacterInfoPanelDirty = TRUE;
918 			}
919 		}
920 	}
921 }
922 
923 
HandleDisplayOfSelectedMercArrows()924 void HandleDisplayOfSelectedMercArrows()
925 {
926 	if (!GetSelectedInfoChar()) return;
927 	if (fShowInventoryFlag)     return;
928 
929 	{ // Blit one by the selected merc
930 		INT16 y = Y_START + bSelectedInfoChar * (Y_SIZE + 2) - 1;
931 		if (bSelectedInfoChar >= FIRST_VEHICLE) y += 6;
932 		BltVideoObject(guiSAVEBUFFER, guiSelectedCharArrow, 0,SELECTED_CHAR_ARROW_X, y);
933 	}
934 
935 	// now run through the selected list of guys, an arrow for each
936 	UINT8 const dest_group = bSelectedDestChar != -1 ? gCharactersList[bSelectedDestChar].merc->ubGroupID : 0;
937 	for (UINT8 i = 0; i != MAX_CHARACTER_COUNT; ++i)
938 	{
939 		SOLDIERTYPE const* const s = gCharactersList[i].merc;
940 		if (!s) continue;
941 
942 		// Is he in the selected list or in the same mvt group as this guy?
943 		if (!IsEntryInSelectedListSet(i) && (s->ubGroupID == 0 || s->ubGroupID != dest_group)) continue;
944 
945 		INT16 y = Y_START + i * (Y_SIZE + 2) - 1;
946 		if (i >= FIRST_VEHICLE) y += 6;
947 		BltVideoObject(guiSAVEBUFFER, guiSelectedCharArrow, 0, SELECTED_CHAR_ARROW_X, y);
948 	}
949 }
950 
951 
GetMoraleString(SOLDIERTYPE const & s)952 ST::string GetMoraleString(SOLDIERTYPE const& s)
953 {
954 	return
955 		s.uiStatusFlags & SOLDIER_DEAD ? pMoralStrings[5] :
956 		s.bMorale > 80                 ? pMoralStrings[0] :
957 		s.bMorale > 65                 ? pMoralStrings[1] :
958 		s.bMorale > 35                 ? pMoralStrings[2] :
959 		s.bMorale > 20                 ? pMoralStrings[3] :
960 		pMoralStrings[4];
961 }
962 
963 
964 // NOTE: This doesn't use the "LeaveList" system at all!
HandleLeavingOfEquipmentInCurrentSector(SOLDIERTYPE & s)965 void HandleLeavingOfEquipmentInCurrentSector(SOLDIERTYPE& s)
966 {
967 	// Just drop the stuff in the current sector
968 	GridNo gridno;
969 	bool const here = s.sSectorX == gWorldSectorX && s.sSectorY == gWorldSectorY && s.bSectorZ == gbWorldSectorZ;
970 	if (here)
971 	{
972 		// ATE: Mercs can have a gridno of NOWHERE
973 		gridno = s.sGridNo;
974 		if (gridno == NOWHERE)
975 		{
976 			gridno = RandomGridNo();
977 
978 			GridNo tmp_gridno = FindNearestAvailableGridNoForItem(gridno, 5);
979 			if (tmp_gridno == NOWHERE) tmp_gridno = FindNearestAvailableGridNoForItem(gridno, 15);
980 			if (tmp_gridno != NOWHERE) gridno = tmp_gridno;
981 		}
982 	}
983 	else
984 	{
985 		// ATE: Use insertion gridno if not nowhere and insertion is gridno
986 		if (s.ubStrategicInsertionCode == INSERTION_CODE_GRIDNO && s.usStrategicInsertionData != NOWHERE)
987 		{
988 			gridno = s.usStrategicInsertionData;
989 		}
990 		else
991 		{
992 			gridno = RandomGridNo();
993 		}
994 	}
995 
996 	FOR_EACH_SOLDIER_INV_SLOT(i, s)
997 	{
998 		if (i->ubNumberOfObjects == 0) continue;
999 
1000 		if (here)
1001 		{
1002 			AddItemToPool(gridno, i, VISIBLE, s.bLevel, WORLD_ITEM_REACHABLE, 0);
1003 		}
1004 		else
1005 		{
1006 			AddItemsToUnLoadedSector(s.sSectorX, s.sSectorY, s.bSectorZ, gridno, 1, i, s.bLevel, WOLRD_ITEM_FIND_SWEETSPOT_FROM_GRIDNO | WORLD_ITEM_REACHABLE, 0, VISIBLE);
1007 		}
1008 	}
1009 
1010 	DropKeysInKeyRing(s, gridno, s.bLevel, VISIBLE, false, 0, false);
1011 }
1012 
1013 
1014 static INT32 SetUpDropItemListForMerc(SOLDIERTYPE&);
1015 
1016 
HandleMercLeavingEquipment(SOLDIERTYPE & s,bool const in_drassen)1017 void HandleMercLeavingEquipment(SOLDIERTYPE& s, bool const in_drassen)
1018 {
1019 	// Stash the items into a linked list hanging of a free "leave item list" slot
1020 	INT32 const slot = SetUpDropItemListForMerc(s);
1021 	if (slot != -1)
1022 	{ // Post event to drop it there 6 hours later
1023 		StrategicEventKind const e = in_drassen ? EVENT_MERC_LEAVE_EQUIP_IN_DRASSEN : EVENT_MERC_LEAVE_EQUIP_IN_OMERTA;
1024 		AddStrategicEvent(e, GetWorldTotalMin() + 6 * 60, slot);
1025 	}
1026 	else
1027 	{ // Otherwise there's no free slots left (shouldn't ever happen)
1028 		AssertMsg(FALSE, "HandleMercLeavingEquipment: No more free slots, equipment lost");
1029 	}
1030 }
1031 
1032 
1033 static void FreeLeaveListSlot(UINT32 uiSlotIndex);
1034 
1035 
HandleEquipmentLeft(UINT32 const slot_idx,INT const sector,GridNo const grid)1036 static void HandleEquipmentLeft(UINT32 const slot_idx, INT const sector, GridNo const grid)
1037 {
1038 	Assert(slot_idx < NUM_LEAVE_LIST_SLOTS);
1039 
1040 	if (MERC_LEAVE_ITEM* i = gpLeaveListHead[slot_idx])
1041 	{
1042 		ST::string sString;
1043 		ST::string town = GCM->getTownLocative(GetTownIdForSector(sector));
1044 		int            const x    = SECTORX(sector);
1045 		char           const y    = SECTORY(sector) - 1 + 'A';
1046 		ProfileID      const id   = guiLeaveListOwnerProfileId[slot_idx];
1047 		if (id != NO_PROFILE)
1048 		{
1049 			sString = st_format_printf(str_left_equipment, GetProfile(id).zNickname, town, y, x);
1050 		}
1051 		else
1052 		{
1053 			sString = ST::format("A departing merc has left their equipment in {} ({c}{}).", town, y, x);
1054 		}
1055 		ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, sString);
1056 
1057 		bool const is_sector_loaded = gWorldSectorX == SECTORX(sector) && gWorldSectorY == SECTORY(sector) && gbWorldSectorZ == 0;
1058 		for (; i; i = i->pNext)
1059 		{
1060 			if (is_sector_loaded)
1061 			{
1062 				AddItemToPool(grid, &i->o, VISIBLE, 0, WORLD_ITEM_REACHABLE, 0);
1063 			}
1064 			else
1065 			{ // Given this slot value, add to sector item list
1066 				AddItemsToUnLoadedSector(SECTORX(sector), SECTORY(sector), 0, grid, 1, &i->o, 0, WORLD_ITEM_REACHABLE, 0, VISIBLE);
1067 			}
1068 		}
1069 	}
1070 
1071 	FreeLeaveListSlot(slot_idx);
1072 }
1073 
1074 
HandleEquipmentLeftInOmerta(const UINT32 uiSlotIndex)1075 void HandleEquipmentLeftInOmerta(const UINT32 uiSlotIndex)
1076 {
1077 	HandleEquipmentLeft(uiSlotIndex, START_SECTOR, START_SECTOR_LEAVE_EQUIP_GRIDNO);
1078 }
1079 
1080 
HandleEquipmentLeftInDrassen(const UINT32 uiSlotIndex)1081 void HandleEquipmentLeftInDrassen(const UINT32 uiSlotIndex)
1082 {
1083 	auto primaryAirport = GCM->getPrimaryShippingDestination();
1084 	HandleEquipmentLeft(uiSlotIndex, primaryAirport->getDeliverySector(), 10433);
1085 }
1086 
1087 
InitLeaveList(void)1088 void InitLeaveList( void )
1089 {
1090 	INT32 iCounter = 0;
1091 
1092 	// init leave list with NULLS/zeroes
1093 	for( iCounter = 0; iCounter < NUM_LEAVE_LIST_SLOTS; iCounter++ )
1094 	{
1095 		gpLeaveListHead[ iCounter ] = NULL;
1096 		guiLeaveListOwnerProfileId[ iCounter ] = NO_PROFILE;
1097 	}
1098 }
1099 
1100 
ShutDownLeaveList(void)1101 void ShutDownLeaveList( void )
1102 {
1103 	INT32 iCounter = 0;
1104 
1105 	for( iCounter = 0; iCounter < NUM_LEAVE_LIST_SLOTS; iCounter++ )
1106 	{
1107 		// go through nodes and free them
1108 		if ( gpLeaveListHead[ iCounter ] != NULL )
1109 		{
1110 			FreeLeaveListSlot( iCounter );
1111 		}
1112 	}
1113 }
1114 
1115 
AddItemToLeaveIndex(const OBJECTTYPE * const o,const UINT32 uiSlotIndex)1116 void AddItemToLeaveIndex(const OBJECTTYPE* const o, const UINT32 uiSlotIndex)
1117 {
1118 	Assert(uiSlotIndex < NUM_LEAVE_LIST_SLOTS);
1119 
1120 	if (o == NULL) return;
1121 
1122 	MERC_LEAVE_ITEM* const mli = new MERC_LEAVE_ITEM{};
1123 	mli->o     = *o;
1124 	mli->pNext = NULL;
1125 
1126 	// Append node to list
1127 	MERC_LEAVE_ITEM** anchor = &gpLeaveListHead[uiSlotIndex];
1128 	while (*anchor != NULL) anchor = &(*anchor)->pNext;
1129 	*anchor = mli;
1130 }
1131 
1132 
1133 // release memory for all items in this slot's leave item list
FreeLeaveListSlot(UINT32 uiSlotIndex)1134 static void FreeLeaveListSlot(UINT32 uiSlotIndex)
1135 {
1136 	MERC_LEAVE_ITEM *pCurrent = NULL, *pTemp = NULL;
1137 
1138 	Assert( uiSlotIndex < NUM_LEAVE_LIST_SLOTS );
1139 
1140 	pCurrent = gpLeaveListHead[ uiSlotIndex ];
1141 
1142 	// go through nodes and free them
1143 	while( pCurrent )
1144 	{
1145 		pTemp = pCurrent->pNext;
1146 		delete pCurrent;
1147 		pCurrent = pTemp;
1148 	}
1149 
1150 	gpLeaveListHead[ uiSlotIndex ] = NULL;
1151 }
1152 
1153 
1154 // first free slot in equip leave list
FindFreeSlotInLeaveList(void)1155 static INT32 FindFreeSlotInLeaveList(void)
1156 {
1157 	INT32 iCounter = 0;
1158 
1159 	for( iCounter = 0; iCounter < NUM_LEAVE_LIST_SLOTS; iCounter++ )
1160 	{
1161 		if( gpLeaveListHead[ iCounter ] == NULL )
1162 		{
1163 			return( iCounter );
1164 		}
1165 	}
1166 
1167 	return( -1 );
1168 }
1169 
1170 
1171 static void SetUpMercAboutToLeaveEquipment(UINT32 ubProfileId, UINT32 uiSlotIndex);
1172 
1173 
1174 // Set up a drop list for this grunt, remove items from inventory and profile
SetUpDropItemListForMerc(SOLDIERTYPE & s)1175 static INT32 SetUpDropItemListForMerc(SOLDIERTYPE& s)
1176 {
1177 	INT32 const slot = FindFreeSlotInLeaveList();
1178 	if (slot == -1) return -1;
1179 
1180 	CFOR_EACH_SOLDIER_INV_SLOT(i, s)
1181 	{
1182 		if (i->ubNumberOfObjects == 0) continue;
1183 
1184 		// Make a linked list of the items left behind, with the ptr to its head in this free slot
1185 		AddItemToLeaveIndex(i, slot);
1186 		// Store owner's profile id for the items added to this leave slot index
1187 		SetUpMercAboutToLeaveEquipment(s.ubProfile, slot);
1188 	}
1189 
1190 	/* ATE: Added this to drop keyring keys - the 2nd last paramter says to add it
1191 	 * to a leave list; the gridno, level and visiblity are ignored */
1192 	DropKeysInKeyRing(s, NOWHERE, 0, VISIBILITY_0, true, slot, false);
1193 
1194 	// Zero out profiles
1195 	MERCPROFILESTRUCT& p = GetProfile(s.ubProfile);
1196 	std::fill(std::begin(p.bInvStatus), std::end(p.bInvStatus), 0);
1197 	std::fill(std::begin(p.bInvNumber), std::end(p.bInvNumber), 0);
1198 	std::fill(std::begin(p.inv), std::end(p.inv), 0);
1199 
1200 	return slot;
1201 }
1202 
1203 
1204 // store owner's profile id for the items added to this leave slot index
SetUpMercAboutToLeaveEquipment(UINT32 ubProfileId,UINT32 uiSlotIndex)1205 static void SetUpMercAboutToLeaveEquipment(UINT32 ubProfileId, UINT32 uiSlotIndex)
1206 {
1207 	Assert( uiSlotIndex < NUM_LEAVE_LIST_SLOTS );
1208 
1209 	// store the profile ID of this merc in the same slot that the items are gonna be dropped in
1210 	guiLeaveListOwnerProfileId[ uiSlotIndex ] = ubProfileId;
1211 
1212 }
1213 
1214 
HandleGroupAboutToArrive(void)1215 void HandleGroupAboutToArrive( void )
1216 {
1217 	// reblit map to change the color of the "people in motion" marker
1218 	fMapPanelDirty = TRUE;
1219 
1220 	DoDeadIsDeadSaveIfNecessary();
1221 
1222 	// ARM - commented out - don't see why this is needed
1223 //	fTeamPanelDirty = TRUE;
1224 //	fCharacterInfoPanelDirty = TRUE;
1225 }
1226 
1227 
CreateMapStatusBarsRegion(void)1228 void CreateMapStatusBarsRegion( void )
1229 {
1230 
1231 	// create the status region over the bSelectedCharacter info region, to get quick rundown of merc's status
1232 	MSYS_DefineRegion( &gMapStatusBarsRegion, BAR_INFO_X - 3, BAR_INFO_Y - 42,(INT16)( BAR_INFO_X + 17), (INT16)(BAR_INFO_Y ), MSYS_PRIORITY_HIGH + 5,
1233 							MSYS_NO_CURSOR, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK );
1234 }
1235 
1236 
RemoveMapStatusBarsRegion(void)1237 void RemoveMapStatusBarsRegion( void )
1238 {
1239 
1240 	// remove the bSelectedInfoCharacter helath, breath and morale bars info region
1241 	MSYS_RemoveRegion( &gMapStatusBarsRegion );
1242 }
1243 
1244 
UpdateCharRegionHelpText(void)1245 void UpdateCharRegionHelpText(void)
1246 {
1247 	SOLDIERTYPE const* const s = GetSelectedInfoChar();
1248 
1249 	// health/energy/morale
1250 	ST::string status;
1251 	if (!s || s->bLife == 0)
1252 	{
1253 		status = ST::null;
1254 	}
1255 	else if (s->bAssignment == ASSIGNMENT_POW)
1256 	{
1257 		// POW - stats unknown
1258 		status = ST::format("{}: ??, {}: ??, {}: ??",
1259 			pMapScreenStatusStrings[0],
1260 			pMapScreenStatusStrings[1],
1261 			pMapScreenStatusStrings[2]
1262 		);
1263 	}
1264 	else if (AM_A_ROBOT(s))
1265 	{
1266 		// robot (condition only)
1267 		status = ST::format("{}: {}/{}",
1268 			pMapScreenStatusStrings[3], s->bLife, s->bLifeMax
1269 		);
1270 	}
1271 	else if (s->uiStatusFlags & SOLDIER_VEHICLE)
1272 	{
1273 		// vehicle (condition/fuel)
1274 		status = ST::format("{}: {}/{}, {}: {}/{}",
1275 			pMapScreenStatusStrings[3], s->bLife,   s->bLifeMax,
1276 			pMapScreenStatusStrings[4], s->bBreath, s->bBreathMax
1277 		);
1278 	}
1279 	else
1280 	{
1281 		// person (health/energy/morale)
1282 		ST::string morale = GetMoraleString(*s);
1283 		status = ST::format("{}: {}/{}, {}: {}/{}, {}: {}",
1284 			pMapScreenStatusStrings[0], s->bLife,   s->bLifeMax,
1285 			pMapScreenStatusStrings[1], s->bBreath, s->bBreathMax,
1286 			pMapScreenStatusStrings[2], morale
1287 		);
1288 	}
1289 	gMapStatusBarsRegion.SetFastHelpText(status);
1290 
1291 	// update contract button help text
1292 	EnableButton(giMapContractButton, s && CanExtendContractForSoldier(s));
1293 }
1294 
1295 
1296 // find this merc in the mapscreen list and set as selected
FindAndSetThisContractSoldier(SOLDIERTYPE * pSoldier)1297 void FindAndSetThisContractSoldier( SOLDIERTYPE *pSoldier )
1298 {
1299 	INT32 iCounter = 0;
1300 
1301 	fShowContractMenu = FALSE;
1302 
1303 	for( iCounter = 0; iCounter < MAX_CHARACTER_COUNT; iCounter++ )
1304 	{
1305 		const SOLDIERTYPE* const s = gCharactersList[iCounter].merc;
1306 		if (s == NULL) continue;
1307 
1308 		if (s == pSoldier)
1309 		{
1310 			ChangeSelectedInfoChar( ( INT8 )iCounter, TRUE );
1311 			bSelectedContractChar = ( INT8 )iCounter;
1312 			fShowContractMenu = TRUE;
1313 
1314 			// create
1315 			RebuildContractBoxForMerc( pSoldier );
1316 
1317 			fTeamPanelDirty = TRUE;
1318 			fCharacterInfoPanelDirty = TRUE;
1319 		}
1320 	}
1321 }
1322 
1323 
HandleMAPUILoseCursorFromOtherScreen(void)1324 void HandleMAPUILoseCursorFromOtherScreen( void )
1325 {
1326 	// rerender map without cursors
1327 	fMapPanelDirty = TRUE;
1328 
1329 	if ( fInMapMode )
1330 	{
1331 		RenderMapRegionBackground( );
1332 	}
1333 }
1334 
1335 
UpdateMapScreenAssignmentPositions(void)1336 void UpdateMapScreenAssignmentPositions( void )
1337 {
1338 	// set the position of the pop up boxes
1339 
1340 	if( guiCurrentScreen != MAP_SCREEN )
1341 	{
1342 		return;
1343 	}
1344 
1345 
1346 	if( bSelectedAssignChar == -1 )
1347 	{
1348 		if (!gfPreBattleInterfaceActive) giBoxY = 0;
1349 		return;
1350 	}
1351 
1352 	if (gCharactersList[bSelectedAssignChar].merc == NULL)
1353 	{
1354 		if (!gfPreBattleInterfaceActive) giBoxY = 0;
1355 		return;
1356 	}
1357 
1358 	if( gfPreBattleInterfaceActive )
1359 	{
1360 		// do nothing
1361 	}
1362 	else
1363 	{
1364 		giBoxY = ( Y_START + ( bSelectedAssignChar ) * ( Y_SIZE + 2 ) );
1365 
1366 /* ARM: Removed this - refreshes fine without it, apparently
1367 		// make sure the menus don't overlap the map screen bottom panel (but where did 102 come from?)
1368 		if( giBoxY >= ( MAP_BOTTOM_Y - 102 ) )
1369 			giBoxY = MAP_BOTTOM_Y - 102;
1370 */
1371 	}
1372 
1373 
1374 
1375 	AssignmentPosition.iY = giBoxY;
1376 
1377 	AttributePosition.iY = TrainPosition.iY = AssignmentPosition.iY + ( GetFontHeight( MAP_SCREEN_FONT ) + 2 )* ASSIGN_MENU_TRAIN;
1378 
1379 	VehiclePosition.iY = AssignmentPosition.iY + ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_VEHICLE;
1380 	SquadPosition.iY = AssignmentPosition.iY;
1381 
1382 	if( fShowAssignmentMenu )
1383 	{
1384 		SetBoxY(ghAssignmentBox, giBoxY);
1385 		SetBoxY(ghEpcBox,        giBoxY);
1386 	}
1387 
1388 	if( fShowAttributeMenu )
1389 	{
1390 		SetBoxY(ghAttributeBox, giBoxY + (GetFontHeight(MAP_SCREEN_FONT) + 2) * ASSIGN_MENU_TRAIN);
1391 	}
1392 
1393 	if( fShowRepairMenu )
1394 	{
1395 		SetBoxY(ghRepairBox, giBoxY + (GetFontHeight(MAP_SCREEN_FONT) + 2) * ASSIGN_MENU_REPAIR);
1396 	}
1397 }
1398 
1399 
RandomMercInGroupSaysQuote(GROUP const & g,UINT16 const quote_num)1400 void RandomMercInGroupSaysQuote(GROUP const& g, UINT16 const quote_num)
1401 {
1402 	/* If traversing tactically, don't do this, unless time compression was
1403 	 * required for some reason (don't go to sector) */
1404 	if ((gfTacticalTraversal || g.ubSectorZ > 0) && !IsTimeBeingCompressed())
1405 	{
1406 		return;
1407 	}
1408 
1409 	// Choose somebody in group
1410 	SOLDIERTYPE* mercs_in_group[20];
1411 	UINT8        n_mercs = 0;
1412 	CFOR_EACH_PLAYER_IN_GROUP(i, &g)
1413 	{
1414 		SOLDIERTYPE& s = *i->pSoldier;
1415 		if (s.bLife < OKLIFE) continue;
1416 		if (IsMechanical(s))  continue;
1417 		if (AM_AN_EPC(&s))    continue;
1418 		if (s.fMercAsleep)    continue;
1419 		mercs_in_group[n_mercs++] = &s;
1420 	}
1421 
1422 	if (n_mercs > 0)
1423 	{
1424 		SOLDIERTYPE& chosen = *mercs_in_group[Random(n_mercs)];
1425 		TacticalCharacterDialogue(&chosen, quote_num);
1426 	}
1427 }
1428 
1429 
GetNumberOfPeopleInCharacterList(void)1430 INT32 GetNumberOfPeopleInCharacterList( void )
1431 {
1432 	// get the number of valid mercs in the mapscreen character list
1433 	INT32 count = 0;
1434 	CFOR_EACH_IN_CHAR_LIST(c) ++count;
1435 	return count;
1436 }
1437 
1438 
ValidSelectableCharForNextOrPrev(SOLDIERTYPE const & s)1439 static bool ValidSelectableCharForNextOrPrev(SOLDIERTYPE const& s)
1440 {
1441 	bool const holding_item = gpItemPointer || fMapInventoryItem;
1442 	return
1443 		/* If showing merc inventory or holding an item, then the new guy must have
1444 			* accessible inventory */
1445 		((!holding_item && !fShowInventoryFlag) || MapCharacterHasAccessibleInventory(s)) &&
1446 		(!holding_item || MapscreenCanPassItemToChar(&s));
1447 }
1448 
1449 
MapscreenCanPassItemToChar(const SOLDIERTYPE * const pNewSoldier)1450 BOOLEAN MapscreenCanPassItemToChar(const SOLDIERTYPE* const pNewSoldier)
1451 {
1452 	SOLDIERTYPE *pOldSoldier;
1453 
1454 	// assumes we're holding an item
1455 	Assert(gpItemPointer || fMapInventoryItem);
1456 
1457 	// if in a hostile sector, disallow
1458 	if ( gTacticalStatus.fEnemyInSector )
1459 	{
1460 		return( FALSE );
1461 	}
1462 
1463 	// if showing sector inventory, and the item came from there
1464 	if ( fShowMapInventoryPool && !gpItemPointerSoldier && fMapInventoryItem )
1465 	{
1466 		// disallow passing items to anyone not in that sector
1467 		if ( pNewSoldier->sSectorX != sSelMapX ||
1468 			pNewSoldier->sSectorY != sSelMapY ||
1469 			pNewSoldier->bSectorZ != ( INT8 )( iCurrentMapSectorZ ) )
1470 		{
1471 			return( FALSE );
1472 		}
1473 
1474 		if ( pNewSoldier->fBetweenSectors )
1475 		{
1476 			return( FALSE );
1477 		}
1478 	}
1479 
1480 
1481 	// if we know who it came from
1482 	if ( gpItemPointerSoldier )
1483 	{
1484 		pOldSoldier = gpItemPointerSoldier;
1485 	}
1486 	else
1487 	{
1488 		// it came from either the currently selected merc, or the sector inventory
1489 		if (fMapInventoryItem)
1490 		{
1491 			pOldSoldier = NULL;
1492 		}
1493 		else
1494 		{
1495 			pOldSoldier = GetSelectedInfoChar();
1496 		}
1497 	}
1498 
1499 
1500 	// if another merc had it previously
1501 	if ( pOldSoldier != NULL )
1502 	{
1503 		// disallow passing items to a merc not in the same sector
1504 		if ( pNewSoldier->sSectorX != pOldSoldier->sSectorX ||
1505 			pNewSoldier->sSectorY != pOldSoldier->sSectorY ||
1506 			pNewSoldier->bSectorZ != pOldSoldier->bSectorZ )
1507 		{
1508 			return( FALSE );
1509 		}
1510 
1511 		// if on the road
1512 		if ( pNewSoldier->fBetweenSectors )
1513 		{
1514 			// other guy must also be on the road...
1515 			if ( !pOldSoldier->fBetweenSectors )
1516 			{
1517 				return( FALSE );
1518 			}
1519 
1520 			// only exchanges between those is the same squad or vehicle are permitted
1521 			if ( pNewSoldier->bAssignment != pOldSoldier->bAssignment )
1522 			{
1523 				return( FALSE );
1524 			}
1525 
1526 			// if in vehicles, make sure it's the same one
1527 			if ( ( pNewSoldier->bAssignment == VEHICLE ) && ( pNewSoldier->iVehicleId != pOldSoldier->iVehicleId ) )
1528 			{
1529 				return( FALSE );
1530 			}
1531 		}
1532 	}
1533 
1534 
1535 	// passed all tests
1536 	return( TRUE );
1537 }
1538 
1539 
1540 
GoToNextCharacterInList(void)1541 void GoToNextCharacterInList( void )
1542 {
1543 	INT32 iCounter = 0, iCount = 0;
1544 
1545 	if (fShowDescriptionFlag) return;
1546 
1547 	if (bSelectedDestChar != -1 || fPlotForHelicopter)
1548 	{
1549 		AbortMovementPlottingMode( );
1550 	}
1551 
1552 	// is the current guy invalid or the first one?
1553 	if( ( bSelectedInfoChar == -1 )|| ( bSelectedInfoChar == MAX_CHARACTER_COUNT ) )
1554 	{
1555 		iCount = 0;
1556 	}
1557 	else
1558 	{
1559 		iCount = bSelectedInfoChar + 1;
1560 	}
1561 
1562 	for( iCounter = 0; iCounter < MAX_CHARACTER_COUNT; iCounter++ )
1563 	{
1564 		const SOLDIERTYPE* const s = gCharactersList[iCount].merc;
1565 		if (s != NULL &&
1566 				iCount < MAX_CHARACTER_COUNT &&
1567 				ValidSelectableCharForNextOrPrev(*s))
1568 		{
1569 			ChangeSelectedInfoChar( ( INT8 )iCount, TRUE );
1570 			break;
1571 		}
1572 		else
1573 		{
1574 			iCount++;
1575 
1576 			if( iCount >= MAX_CHARACTER_COUNT )
1577 			{
1578 				iCount = 0;
1579 			}
1580 		}
1581 	}
1582 }
1583 
1584 
GoToPrevCharacterInList(void)1585 void GoToPrevCharacterInList( void )
1586 {
1587 	INT32 iCounter = 0, iCount = 0;
1588 
1589 	if (fShowDescriptionFlag) return;
1590 
1591 	if (bSelectedDestChar != -1 || fPlotForHelicopter)
1592 	{
1593 		AbortMovementPlottingMode( );
1594 	}
1595 
1596 	// is the current guy invalid or the first one?
1597 	if( ( bSelectedInfoChar == -1 ) || ( bSelectedInfoChar == 0 ) )
1598 	{
1599 		iCount = MAX_CHARACTER_COUNT;
1600 	}
1601 	else
1602 	{
1603 		iCount = bSelectedInfoChar - 1;
1604 	}
1605 
1606 	// now run through the list and find first prev guy
1607 	for( iCounter = 0; iCounter < MAX_CHARACTER_COUNT; iCounter++ )
1608 	{
1609 		const SOLDIERTYPE* const s = gCharactersList[iCount].merc;
1610 		if (s != NULL &&
1611 				iCount < MAX_CHARACTER_COUNT &&
1612 				ValidSelectableCharForNextOrPrev(*s))
1613 		{
1614 			ChangeSelectedInfoChar( ( INT8 )iCount, TRUE );
1615 			break;
1616 		}
1617 		else
1618 		{
1619 			iCount--;
1620 
1621 			if( iCount < 0 )
1622 			{
1623 				// was FIRST_VEHICLE
1624 				iCount = MAX_CHARACTER_COUNT;
1625 			}
1626 		}
1627 	}
1628 }
1629 
1630 
HandleMinerEvent(ProfileID const ubMinerProfileID,INT16 const sQuoteNumber,BOOLEAN const fForceMapscreen)1631 void HandleMinerEvent(ProfileID const ubMinerProfileID, INT16 const sQuoteNumber, BOOLEAN const fForceMapscreen)
1632 {
1633 	BOOLEAN fFromMapscreen = FALSE;
1634 
1635 
1636 	if ( guiCurrentScreen == MAP_SCREEN )
1637 	{
1638 		fFromMapscreen = TRUE;
1639 	}
1640 	else
1641 	{
1642 		// if transition to mapscreen is required
1643 		if ( fForceMapscreen )
1644 		{
1645 			// switch to mapscreen so we can flash the mine sector the guy is talking about
1646 			LeaveTacticalScreen(MAP_SCREEN);
1647 			fFromMapscreen = TRUE;
1648 		}
1649 	}
1650 
1651 	if ( fFromMapscreen )
1652 	{
1653 		// if not showing map surface level
1654 		if ( iCurrentMapSectorZ != 0 )
1655 		{
1656 			// switch to it, because the miner locators wouldn't show up if we're underground while they speak
1657 			ChangeSelectedMapSector( sSelMapX, sSelMapY, 0 );
1658 		}
1659 
1660 		fMapPanelDirty = TRUE;
1661 	}
1662 
1663 	CharacterDialogue(ubMinerProfileID, sQuoteNumber, GetExternalNPCFace(ubMinerProfileID), DIALOGUE_EXTERNAL_NPC_UI, FALSE);
1664 }
1665 
1666 
1667 static void StopMapScreenHelpText(void);
1668 static void StopShowingInterfaceFastHelpText(void);
1669 
1670 
ShutDownUserDefineHelpTextRegions(void)1671 void ShutDownUserDefineHelpTextRegions( void )
1672 {
1673 	// dirty the tactical panel
1674 	fInterfacePanelDirty = DIRTYLEVEL2;
1675 	SetRenderFlags( RENDER_FLAG_FULL );
1676 
1677 	//dirty the map panel
1678 	StopMapScreenHelpText( );
1679 
1680 	//r eset tactical flag too
1681 	StopShowingInterfaceFastHelpText( );
1682 }
1683 
1684 
SetUpFastHelpRegion(INT32 x,INT32 y,INT32 width,const ST::string & str)1685 void SetUpFastHelpRegion(INT32 x, INT32 y, INT32 width, const ST::string& str)
1686 {
1687 	FASTHELPREGION* fhr = &pFastHelpMapScreenList[0];
1688 	fhr->iX = x;
1689 	fhr->iY = y;
1690 	fhr->iW = width;
1691 	fhr->FastHelpText = str;
1692 	giSizeOfInterfaceFastHelpTextList = 1;
1693 }
1694 
1695 
1696 static void DisplayFastHelpRegions(FASTHELPREGION* pRegion, INT32 iSize);
1697 static void SetUpShutDownMapScreenHelpTextScreenMask(void);
1698 
1699 
1700 // handle the actual showing of the interface fast help text
HandleShowingOfTacticalInterfaceFastHelpText(void)1701 void HandleShowingOfTacticalInterfaceFastHelpText( void )
1702 {
1703 	static BOOLEAN fTextActive = FALSE;
1704 
1705 	if( fInterfaceFastHelpTextActive )
1706 	{
1707 		DisplayFastHelpRegions(pFastHelpMapScreenList, giSizeOfInterfaceFastHelpTextList);
1708 
1709 		PauseGame();
1710 
1711 		// lock out the screen
1712 		SetUpShutDownMapScreenHelpTextScreenMask( );
1713 
1714 		gfIgnoreScrolling = TRUE;
1715 
1716 		// the text is active
1717 		fTextActive = TRUE;
1718 
1719 	}
1720 	else if (!fInterfaceFastHelpTextActive && fTextActive)
1721 	{
1722 		fTextActive = FALSE;
1723 		UnPauseGame();
1724 		gfIgnoreScrolling = FALSE;
1725 
1726 		// shut down
1727 		ShutDownUserDefineHelpTextRegions( );
1728 	}
1729 }
1730 
1731 // start showing fast help text
StartShowingInterfaceFastHelpText(void)1732 void StartShowingInterfaceFastHelpText( void )
1733 {
1734 	fInterfaceFastHelpTextActive = TRUE;
1735 }
1736 
1737 
1738 // stop showing interface fast help text
StopShowingInterfaceFastHelpText(void)1739 static void StopShowingInterfaceFastHelpText(void)
1740 {
1741 	fInterfaceFastHelpTextActive = FALSE;
1742 }
1743 
1744 
1745 // is the interface text up?
IsTheInterfaceFastHelpTextActive(void)1746 BOOLEAN IsTheInterfaceFastHelpTextActive( void )
1747 {
1748 	return( fInterfaceFastHelpTextActive );
1749 }
1750 
1751 
1752 static void DisplayUserDefineHelpTextRegions(FASTHELPREGION* pRegion);
1753 
1754 
1755 // display all the regions in the list
DisplayFastHelpRegions(FASTHELPREGION * pRegion,INT32 iSize)1756 static void DisplayFastHelpRegions(FASTHELPREGION* pRegion, INT32 iSize)
1757 {
1758 	INT32 iCounter = 0;
1759 
1760 	// run through and show all the regions
1761 	for( iCounter = 0; iCounter < iSize; iCounter++ )
1762 	{
1763 		DisplayUserDefineHelpTextRegions( &( pRegion[ iCounter ] ) );
1764 	}
1765 }
1766 
1767 
1768 // show one region
DisplayUserDefineHelpTextRegions(FASTHELPREGION * pRegion)1769 static void DisplayUserDefineHelpTextRegions(FASTHELPREGION* pRegion)
1770 {
1771 	INT32 iX,iY,iW,iH;
1772 
1773 	iX = pRegion->iX;
1774 	iY = pRegion->iY;
1775 	// get the width and height of the string
1776 	iW = (INT32)( pRegion->iW ) + 14;
1777 	iH = IanWrappedStringHeight(pRegion->iW, 0, FONT10ARIAL, pRegion->FastHelpText);
1778 
1779 	// tack on the outer border
1780 	iH += 14;
1781 
1782 	// gone not far enough?
1783 	if ( iX < 0 )
1784 		iX = 0;
1785 
1786 	// gone too far
1787 	if ( ( pRegion->iX + iW ) >= SCREEN_WIDTH )
1788 		iX = (SCREEN_WIDTH - iW - 4);
1789 
1790 	// what about the y value?
1791 	iY = (INT32)pRegion->iY - (  iH * 3 / 4);
1792 
1793 	// not far enough
1794 	if (iY < 0)
1795 		iY = 0;
1796 
1797 	// too far
1798 	if ( (iY + iH) >= SCREEN_HEIGHT )
1799 		iY = (SCREEN_HEIGHT - iH - 15);
1800 
1801 	{ SGPVSurface::Lock l(FRAME_BUFFER);
1802 		SetClippingRegionAndImageWidth(l.Pitch(), 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
1803 		UINT16* const pDestBuf = l.Buffer<UINT16>();
1804 		RectangleDraw(TRUE, iX + 1, iY + 1, iX + iW - 1, iY + iH - 1, Get16BPPColor(FROMRGB( 65,  57, 15)), pDestBuf);
1805 		RectangleDraw(TRUE, iX,     iY,     iX + iW - 2, iY + iH - 2, Get16BPPColor(FROMRGB(227, 198, 88)), pDestBuf);
1806 	}
1807 	FRAME_BUFFER->ShadowRect(iX + 2, iY + 2, iX + iW - 3, iY + iH - 3);
1808 	FRAME_BUFFER->ShadowRect(iX + 2, iY + 2, iX + iW - 3, iY + iH - 3);
1809 
1810 	iH = DisplayWrappedString(iX + 10, iY + 6, pRegion->iW, 0, FONT10ARIAL, FONT_BEIGE, pRegion->FastHelpText, FONT_NEARBLACK, MARK_DIRTY);
1811 
1812 	InvalidateRegion(  iX, iY, (iX + iW) , (iY + iH + 20 ) );
1813 }
1814 
1815 
1816 // stop the help text in mapscreen
StopMapScreenHelpText(void)1817 static void StopMapScreenHelpText(void)
1818 {
1819 	fTeamPanelDirty = TRUE;
1820 	fMapPanelDirty = TRUE;
1821 	fCharacterInfoPanelDirty = TRUE;
1822 	fMapScreenBottomDirty = TRUE;
1823 
1824 	SetUpShutDownMapScreenHelpTextScreenMask( );
1825 }
1826 
1827 
1828 static void MapScreenHelpTextScreenMaskBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
1829 
1830 
SetUpShutDownMapScreenHelpTextScreenMask(void)1831 static void SetUpShutDownMapScreenHelpTextScreenMask(void)
1832 {
1833 	static BOOLEAN fCreated = FALSE;
1834 
1835 	// create or destroy the screen mask as needed
1836 	if (fInterfaceFastHelpTextActive && !fCreated)
1837 	{
1838 		MSYS_DefineRegion(&gMapScreenHelpTextMask, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MSYS_PRIORITY_HIGHEST, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, MapScreenHelpTextScreenMaskBtnCallback);
1839 		fCreated = TRUE;
1840 	}
1841 	else if (!fInterfaceFastHelpTextActive && fCreated)
1842 	{
1843 		MSYS_RemoveRegion( &gMapScreenHelpTextMask );
1844 
1845 		fCreated = FALSE;
1846 	}
1847 }
1848 
1849 
MapScreenHelpTextScreenMaskBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)1850 static void MapScreenHelpTextScreenMaskBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
1851 {
1852 	if( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP )
1853 	{
1854 		// stop showing
1855 		ShutDownUserDefineHelpTextRegions( );
1856 	}
1857 	else if( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1858 	{
1859 		// stop showing
1860 		ShutDownUserDefineHelpTextRegions( );
1861 	}
1862 }
1863 
1864 
IsSoldierSelectedForMovement(SOLDIERTYPE const & s)1865 static bool IsSoldierSelectedForMovement(SOLDIERTYPE const& s)
1866 {
1867 	// run through the list and turn this soldiers value on
1868 	for (INT32 i = 0; i != giNumberOfSoldiersInSectorMoving; ++i)
1869 	{
1870 		if (pSoldierMovingList[i] != &s) continue;
1871 		if (!fSoldierIsMoving[i])        continue; // XXX break?
1872 		return true;
1873 	}
1874 	return false;
1875 }
1876 
1877 
IsSquadSelectedForMovement(INT32 const squad_no)1878 static bool IsSquadSelectedForMovement(INT32 const squad_no)
1879 {
1880 	for (INT32 i = 0; i != giNumberOfSquadsInSectorMoving; ++i)
1881 	{
1882 		if (iSquadMovingList[i] != squad_no) continue;
1883 		if (!fSquadIsMoving[i])              continue; // XXX break?
1884 		return true;
1885 	}
1886 	return false;
1887 }
1888 
1889 
IsVehicleSelectedForMovement(INT32 const vehicle_id)1890 static bool IsVehicleSelectedForMovement(INT32 const vehicle_id)
1891 {
1892 	for (INT32 i = 0; i != giNumberOfVehiclesInSectorMoving; ++i)
1893 	{
1894 		if (iVehicleMovingList[i] != vehicle_id) continue;
1895 		if (!fVehicleIsMoving[i])                continue; // XXX break?
1896 		return true;
1897 	}
1898 	return false;
1899 }
1900 
1901 
SelectSoldierForMovement(SOLDIERTYPE const & s)1902 static void SelectSoldierForMovement(SOLDIERTYPE const& s)
1903 {
1904 	for (INT32 i = 0; i != giNumberOfSoldiersInSectorMoving; ++i)
1905 	{
1906 		if (pSoldierMovingList[i] != &s) continue;
1907 		// Turn the selected soldier on
1908 		fSoldierIsMoving[i] = true;
1909 		break;
1910 	}
1911 }
1912 
1913 
DeselectSoldierForMovement(SOLDIERTYPE const & s)1914 static void DeselectSoldierForMovement(SOLDIERTYPE const& s)
1915 {
1916 	for (INT32 i = 0; i != giNumberOfSoldiersInSectorMoving; ++i)
1917 	{
1918 		if (pSoldierMovingList[i] != &s) continue;
1919 		// Turn the selected soldier off
1920 		fSoldierIsMoving[i] = false;
1921 		break;
1922 	}
1923 }
1924 
1925 
1926 static BOOLEAN CanMoveBoxSoldierMoveStrategically(SOLDIERTYPE* pSoldier, BOOLEAN fShowErrorMessage);
1927 
1928 
SelectSquadForMovement(INT32 const squad_no)1929 static void SelectSquadForMovement(INT32 const squad_no)
1930 {
1931 	// Run through squad list and set them on
1932 	for (INT32 i = 0; i != giNumberOfSquadsInSectorMoving; ++i)
1933 	{
1934 		if (iSquadMovingList[i] != squad_no) continue;
1935 
1936 		// Try to select everyone in squad
1937 		bool some_cant_move = false;
1938 		bool first_failure  = true;
1939 		FOR_EACH_IN_SQUAD(k, squad_no)
1940 		{
1941 			SOLDIERTYPE& s = **k;
1942 			if (!s.bActive) continue;
1943 			/* Is he able and allowed to move? Report only the first reason for
1944 				* failure encountered */
1945 			if (CanMoveBoxSoldierMoveStrategically(&s, first_failure))
1946 			{
1947 				SelectSoldierForMovement(s);
1948 			}
1949 			else
1950 			{
1951 				some_cant_move = true;
1952 				first_failure  = false;
1953 			}
1954 		}
1955 
1956 		if (!some_cant_move) fSquadIsMoving[i] = TRUE;
1957 		break;
1958 	}
1959 }
1960 
1961 
DeselectSquadForMovement(INT32 const squad_no)1962 static void DeselectSquadForMovement(INT32 const squad_no)
1963 {
1964 	// Run through squad list and set them off
1965 	for (INT32 i = 0; i != giNumberOfSquadsInSectorMoving; ++i)
1966 	{
1967 		if (iSquadMovingList[i] != squad_no) continue;
1968 
1969 		fSquadIsMoving[i] = FALSE;
1970 
1971 		// Now deselect everyone in squad
1972 		FOR_EACH_IN_SQUAD(k, squad_no)
1973 		{
1974 			SOLDIERTYPE& s = **k;
1975 			if (!s.bActive) continue;
1976 			DeselectSoldierForMovement(s);
1977 		}
1978 		break;
1979 	}
1980 }
1981 
1982 
AllSoldiersInSquadSelected(INT32 const squad_no)1983 static bool AllSoldiersInSquadSelected(INT32 const squad_no)
1984 {
1985 	// Is everyone on this squad moving?
1986 	for (INT32 i = 0; i != giNumberOfSoldiersInSectorMoving; ++i)
1987 	{
1988 		if (pSoldierMovingList[i]->bAssignment != squad_no) continue;
1989 		if (fSoldierIsMoving[i]) continue;
1990 		return false;
1991 	}
1992 	return true;
1993 }
1994 
1995 
SelectVehicleForMovement(INT32 const vehicle_id,BOOLEAN const and_all_on_board)1996 static void SelectVehicleForMovement(INT32 const vehicle_id, BOOLEAN const and_all_on_board)
1997 {
1998 	// Run through vehicle list and set them on
1999 	for (INT32 i = 0; i != giNumberOfVehiclesInSectorMoving; ++i)
2000 	{
2001 		if (iVehicleMovingList[i] != vehicle_id) continue;
2002 
2003 		bool               first_failure = true;
2004 		bool               has_driver    = false;
2005 		VEHICLETYPE const& v             = pVehicleList[vehicle_id];
2006 		CFOR_EACH_PASSENGER(v, k)
2007 		{
2008 			SOLDIERTYPE& s = **k;
2009 
2010 			if (and_all_on_board)
2011 			{ // Try to select everyone in vehicle
2012 				if (s.bActive)
2013 				{
2014 					// Is he able and allowed to move?
2015 					if (CanMoveBoxSoldierMoveStrategically(&s, first_failure))
2016 					{
2017 						SelectSoldierForMovement(s);
2018 					}
2019 					else
2020 					{
2021 						first_failure = false;
2022 					}
2023 				}
2024 			}
2025 
2026 			if (IsSoldierSelectedForMovement(s)) has_driver = true;
2027 		}
2028 
2029 		/* Vehicle itself can only move if at least one passenger can move and is
2030 			* moving */
2031 		if (has_driver) fVehicleIsMoving[i] = TRUE;
2032 		break;
2033 	}
2034 }
2035 
2036 
DeselectVehicleForMovement(INT32 const vehicle_id)2037 static void DeselectVehicleForMovement(INT32 const vehicle_id)
2038 {
2039 	// run through vehicle list and set them off
2040 	for (INT32 i = 0; i != giNumberOfVehiclesInSectorMoving; ++i)
2041 	{
2042 		if (iVehicleMovingList[i] != vehicle_id) continue;
2043 
2044 		fVehicleIsMoving[i] = FALSE;
2045 
2046 		// Now deselect everyone in vehicle
2047 		VEHICLETYPE const& v = pVehicleList[vehicle_id];
2048 		CFOR_EACH_PASSENGER(v, i)
2049 		{
2050 			SOLDIERTYPE& s = **i;
2051 			if (s.bActive) DeselectSoldierForMovement(s);
2052 		}
2053 		break;
2054 	}
2055 }
2056 
2057 
HowManyMovingSoldiersInVehicle(INT32 const vehicle_id)2058 static INT32 HowManyMovingSoldiersInVehicle(INT32 const vehicle_id)
2059 {
2060 	INT32 n = 0;
2061 	for (INT32 i = 0; i != giNumberOfSoldiersInSectorMoving; ++i)
2062 	{
2063 		SOLDIERTYPE const& s = *pSoldierMovingList[i];
2064 		if (s.bAssignment != VEHICLE)    continue;
2065 		if (s.iVehicleId  != vehicle_id) continue;
2066 		if (!fSoldierIsMoving[i])        continue;
2067 		++n;
2068 	}
2069 	return n;
2070 }
2071 
2072 
HowManyMovingSoldiersInSquad(INT32 const squad_no)2073 static INT32 HowManyMovingSoldiersInSquad(INT32 const squad_no)
2074 {
2075 	INT32 n = 0;
2076 	for (INT32 i = 0; i != giNumberOfSoldiersInSectorMoving; ++i)
2077 	{
2078 		if (pSoldierMovingList[i]->bAssignment != squad_no) continue;
2079 		if (!fSoldierIsMoving[i])                           continue;
2080 		++n;
2081 	}
2082 	return n;
2083 }
2084 
2085 
AddSoldierToMovingLists(SOLDIERTYPE & s)2086 static void AddSoldierToMovingLists(SOLDIERTYPE& s)
2087 {
2088 	for (INT32 i = 0; i != MAX_CHARACTER_COUNT; ++i)
2089 	{
2090 		SOLDIERTYPE*& slot = pSoldierMovingList[i];
2091 		if (slot == &s) return;
2092 
2093 		if (slot) continue;
2094 		// Found a free slot
2095 		slot                = &s;
2096 		fSoldierIsMoving[i] = false;
2097 		++giNumberOfSoldiersInSectorMoving;
2098 		return;
2099 	}
2100 }
2101 
2102 
AddSquadToMovingLists(INT32 const squad_no)2103 static void AddSquadToMovingLists(INT32 const squad_no)
2104 {
2105 	if (squad_no == -1) return; // Invalid squad
2106 
2107 	for(INT32 i = 0; i < NUMBER_OF_SQUADS; ++i)
2108 	{
2109 		INT32& slot = iSquadMovingList[i];
2110 		if (slot == squad_no) return;
2111 
2112 		if (slot != -1) continue;
2113 		// Found a free slot
2114 		slot              = squad_no;
2115 		fSquadIsMoving[i] = FALSE;
2116 		++giNumberOfSquadsInSectorMoving;
2117 		return;
2118 	}
2119 }
2120 
2121 
AddVehicleToMovingLists(INT32 const vehicle_id)2122 static void AddVehicleToMovingLists(INT32 const vehicle_id)
2123 {
2124 	if (vehicle_id == -1) return;
2125 
2126 	for (INT32 i = 0; i != NUMBER_OF_SQUADS; ++i)
2127 	{
2128 		INT32& slot = iVehicleMovingList[i];
2129 		if (slot == vehicle_id) return;
2130 
2131 		if (slot != -1) continue;
2132 		// Found a free slot
2133 		slot                = vehicle_id;
2134 		fVehicleIsMoving[i] = FALSE;
2135 		++giNumberOfVehiclesInSectorMoving;
2136 		return;
2137 	}
2138 }
2139 
2140 
2141 // The alternate mapscreen movement system
InitializeMovingLists()2142 static void InitializeMovingLists()
2143 {
2144 	giNumberOfSoldiersInSectorMoving = 0;
2145 	giNumberOfSquadsInSectorMoving   = 0;
2146 	giNumberOfVehiclesInSectorMoving = 0;
2147 
2148 	// Init the soldiers
2149 	for (INT32 i = 0; i != MAX_CHARACTER_COUNT; ++i)
2150 	{
2151 		pSoldierMovingList[i] = 0;
2152 		fSoldierIsMoving[i]   = false;
2153 	}
2154 
2155 	// Init the squads
2156 	for (INT32 i = 0; i != NUMBER_OF_SQUADS; ++i)
2157 	{
2158 		iSquadMovingList[i] = -1;
2159 		fSquadIsMoving[i]   = FALSE;
2160 	}
2161 
2162 	// Init the vehicles
2163 	for (INT32 i = 0; i != NUMBER_OF_SQUADS; ++i)
2164 	{
2165 		iVehicleMovingList[i] = -1;
2166 		fVehicleIsMoving[i]   = FALSE;
2167 	}
2168 }
2169 
2170 
IsAnythingSelectedForMoving()2171 static bool IsAnythingSelectedForMoving()
2172 {
2173 	for (INT32 i = 0; i != MAX_CHARACTER_COUNT; ++i)
2174 	{
2175 		if (!pSoldierMovingList[i]) continue;
2176 		if (!fSoldierIsMoving[i])   continue;
2177 		return true;
2178 	}
2179 
2180 	for (INT32 i = 0; i != NUMBER_OF_SQUADS; ++i)
2181 	{
2182 		if (iSquadMovingList[i] == -1) continue;
2183 		if (!fSquadIsMoving[i])        continue;
2184 		return true;
2185 	}
2186 
2187 	for (INT32 i = 0; i != NUMBER_OF_SQUADS; ++i)
2188 	{
2189 		if (iVehicleMovingList[i] == -1) continue;
2190 		if (!fVehicleIsMoving[i])        continue;
2191 		return true;
2192 	}
2193 
2194 	return false;
2195 }
2196 
2197 
2198 static void BuildMouseRegionsForMoveBox(void);
2199 static void ClearMouseRegionsForMoveBox(void);
2200 static void CreatePopUpBoxForMovementBox(void);
2201 
2202 
CreateDestroyMovementBox(INT16 sSectorX,INT16 sSectorY,INT16 sSectorZ)2203 void CreateDestroyMovementBox( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ )
2204 {
2205 	static BOOLEAN fCreated = FALSE;
2206 
2207 
2208 	// not allowed for underground movement!
2209 	Assert( sSectorZ == 0 );
2210 
2211 	if (fShowMapScreenMovementList && !fCreated)
2212 	{
2213 		fCreated = TRUE;
2214 
2215 		// create the box and mouse regions
2216 		CreatePopUpBoxForMovementBox( );
2217 		BuildMouseRegionsForMoveBox( );
2218 		CreateScreenMaskForMoveBox( );
2219 		fMapPanelDirty = TRUE;
2220 	}
2221 	else if (!fShowMapScreenMovementList && fCreated)
2222 	{
2223 		fCreated = FALSE;
2224 
2225 		// destroy the box and mouse regions
2226 		ClearMouseRegionsForMoveBox( );
2227 		RemoveBox( ghMoveBox );
2228 		ghMoveBox = NO_POPUP_BOX;
2229 		RemoveScreenMaskForMoveBox( );
2230 		fMapPanelDirty = TRUE;
2231 		fMapScreenBottomDirty = TRUE;		// really long move boxes can overlap bottom panel
2232 	}
2233 
2234 }
2235 
2236 
SetUpMovingListsForSector(INT16 const x,INT16 const y,INT16 const z)2237 void SetUpMovingListsForSector(INT16 const x, INT16 const y, INT16 const z)
2238 {
2239 	// Not allowed for underground movement
2240 	Assert(z == 0);
2241 
2242 	InitializeMovingLists();
2243 
2244 	/* Note that Skyrider can't be moved using the move box, and won't appear
2245 	 * because the helicoprer is not in the char list */
2246 	CFOR_EACH_IN_CHAR_LIST(i)
2247 	{
2248 		SOLDIERTYPE& s = *i->merc;
2249 		if (s.bAssignment == IN_TRANSIT)     continue;
2250 		if (s.bAssignment == ASSIGNMENT_POW) continue;
2251 		if (s.sSectorX != x || s.sSectorY != y || s.bSectorZ != z) continue;
2252 
2253 		if (s.uiStatusFlags & SOLDIER_VEHICLE)
2254 		{
2255 			VEHICLETYPE const& v = GetVehicle(s.bVehicleID);
2256 			// If it can move (can't be empty)
2257 			if (GetNumberInVehicle(v) == 0) continue;
2258 			AddVehicleToMovingLists(s.bVehicleID);
2259 		}
2260 		else
2261 		{
2262 			// Alive, not aboard Skyrider (airborne or not!)
2263 			if (s.bLife < OKLIFE) continue;
2264 			if (InHelicopter(s))  continue;
2265 			AddSoldierToMovingLists(s);
2266 			if (s.bAssignment < ON_DUTY) // If on a squad
2267 			{ // Add squad (duplicates ok, they're ignored inside the function)
2268 				AddSquadToMovingLists(s.bAssignment);
2269 			}
2270 		}
2271 	}
2272 
2273 	fShowMapScreenMovementList = TRUE;
2274 	CreateDestroyMovementBox(x, y, z);
2275 }
2276 
2277 
2278 static void AddStringsToMoveBox(PopUpBox*);
2279 
2280 
CreatePopUpBoxForMovementBox(void)2281 static void CreatePopUpBoxForMovementBox(void)
2282 {
2283 	SGPPoint const MovePosition = { (UINT16)(STD_SCREEN_X + 450), (UINT16)(STD_SCREEN_Y + 100) };
2284 
2285 	// create the pop up box and mouse regions for movement list
2286 	PopUpBox* const box = CreatePopUpBox(MovePosition, POPUP_BOX_FLAG_RESIZE, FRAME_BUFFER, guiPOPUPBORDERS, guiPOPUPTEX, 6, 6, 4, 4, 2);
2287 	ghMoveBox = box;
2288 
2289 	AddStringsToMoveBox(box);
2290 
2291 	SetBoxFont(      box, MAP_SCREEN_FONT);
2292 	SetBoxHighLight( box, FONT_WHITE);
2293 	SetBoxForeground(box, FONT_LTGREEN);
2294 	SetBoxBackground(box, FONT_BLACK);
2295 	SetBoxShade(     box, FONT_BLACK);
2296 
2297 	// make the header line WHITE
2298 	SetBoxLineForeground(box, 0, FONT_WHITE);
2299 
2300 	// make the done and cancel lines YELLOW
2301 	const UINT32 line_count = GetNumberOfLinesOfTextInBox(box);
2302 	SetBoxLineForeground(box, line_count - 1, FONT_YELLOW);
2303 	if (IsAnythingSelectedForMoving()) SetBoxLineForeground(box, line_count - 2, FONT_YELLOW);
2304 
2305 	ResizeBoxToText(box);
2306 
2307 	// adjust position to try to keep it in the map area as good as possible
2308 	SGPBox const& area = GetBoxArea(box);
2309 	if (area.x + area.w >= MAP_VIEW_START_X + MAP_VIEW_WIDTH)
2310 	{
2311 		SetBoxX(box, MAX(MAP_VIEW_START_X, MAP_VIEW_START_X + MAP_VIEW_WIDTH - area.w));
2312 	}
2313 	if (area.y + area.h >= MAP_VIEW_START_Y + MAP_VIEW_HEIGHT)
2314 	{
2315 		SetBoxY(box, MAX(MAP_VIEW_START_Y, MAP_VIEW_START_Y + MAP_VIEW_HEIGHT - area.h));
2316 	}
2317 }
2318 
2319 
2320 static BOOLEAN AllOtherSoldiersInListAreSelected(void);
2321 
2322 
AddStringsToMoveBox(PopUpBox * const box)2323 static void AddStringsToMoveBox(PopUpBox* const box)
2324 {
2325 	INT32 iCount = 0, iCountB = 0;
2326 	BOOLEAN fFirstOne = TRUE;
2327 
2328 	// clear all the strings out of the box
2329 	RemoveAllBoxStrings(box);
2330 
2331 	// add title
2332 	ST::string sStringB = GetShortSectorString(sSelMapX, sSelMapY);
2333 	ST::string sString = st_format_printf(pMovementMenuStrings[0], sStringB);
2334 	AddMonoString(box, sString);
2335 
2336 
2337 	// blank line
2338 	AddMonoString(box, ST::null);
2339 
2340 
2341 	// add squads
2342 	for( iCount = 0; iCount < giNumberOfSquadsInSectorMoving; iCount++ )
2343 	{
2344 		// add this squad, now add all the grunts in it
2345 		sString = ST::format(fSquadIsMoving[iCount] ? "*{}*" : "{}", pSquadMenuStrings[iSquadMovingList[iCount]]);
2346 		AddMonoString(box, sString);
2347 
2348 		// now add all the grunts in it
2349 		for( iCountB = 0; iCountB < giNumberOfSoldiersInSectorMoving; iCountB++ )
2350 		{
2351 			if( pSoldierMovingList[ iCountB ] -> bAssignment == iSquadMovingList[ iCount ] )
2352 			{
2353 				// add mercs in squads
2354 				if (IsSoldierSelectedForMovement(*pSoldierMovingList[iCountB]))
2355 				{
2356 					sString = ST::format("   *{}*", pSoldierMovingList[ iCountB ]->name);
2357 				}
2358 				else
2359 				{
2360 					sString = ST::format("   {}", pSoldierMovingList[ iCountB ]->name);
2361 				}
2362 				AddMonoString(box, sString);
2363 			}
2364 		}
2365 	}
2366 
2367 
2368 	// add vehicles
2369 	for( iCount = 0; iCount < giNumberOfVehiclesInSectorMoving; iCount++ )
2370 	{
2371 		// add this vehicle
2372 		sString = ST::format(fVehicleIsMoving[iCount] ? "*{}*" : "{}", pVehicleStrings[pVehicleList[iVehicleMovingList[iCount]].ubVehicleType]);
2373 		AddMonoString(box, sString);
2374 
2375 		// now add all the grunts in it
2376 		for( iCountB = 0; iCountB < giNumberOfSoldiersInSectorMoving; iCountB++ )
2377 		{
2378 			if( ( pSoldierMovingList[ iCountB ] -> bAssignment == VEHICLE ) &&( pSoldierMovingList[ iCountB ] -> iVehicleId == iVehicleMovingList[ iCount ] ) )
2379 			{
2380 				// add mercs in vehicles
2381 				if (IsSoldierSelectedForMovement(*pSoldierMovingList[iCountB]))
2382 				{
2383 					sString = ST::format("   *{}*", pSoldierMovingList[ iCountB ]->name);
2384 				}
2385 				else
2386 				{
2387 					sString = ST::format("   {}", pSoldierMovingList[ iCountB ]->name);
2388 				}
2389 				AddMonoString(box, sString);
2390 			}
2391 		}
2392 	}
2393 
2394 
2395 	fFirstOne = TRUE;
2396 
2397 	// add "other" soldiers heading, once, if there are any
2398 	for( iCount = 0; iCount < giNumberOfSoldiersInSectorMoving; iCount++ )
2399 	{
2400 		// not on duty, not in a vehicle
2401 		if( ( pSoldierMovingList[ iCount ]->bAssignment >= ON_DUTY ) && ( pSoldierMovingList[ iCount ]->bAssignment != VEHICLE ) )
2402 		{
2403 			if ( fFirstOne )
2404 			{
2405 				// add OTHER header line
2406 				sString = ST::format(AllOtherSoldiersInListAreSelected() ? "*{}*" : "{}", pMovementMenuStrings[3]);
2407 				AddMonoString(box, sString);
2408 
2409 				fFirstOne = FALSE;
2410 			}
2411 
2412 			// add OTHER soldiers (not on duty nor in a vehicle)
2413 			sString = ST::format(IsSoldierSelectedForMovement(*pSoldierMovingList[iCount]) ? "  *{} ( {} )*" : "  {} ( {} )", pSoldierMovingList[iCount]->name, pAssignmentStrings[pSoldierMovingList[iCount]->bAssignment]);
2414 			AddMonoString(box, sString);
2415 		}
2416 	}
2417 
2418 
2419 	// blank line
2420 	AddMonoString(box, ST::null);
2421 
2422 
2423 	if ( IsAnythingSelectedForMoving() )
2424 	{
2425 		// add PLOT MOVE line
2426 		AddMonoString(box, pMovementMenuStrings[1]);
2427 	}
2428 	else
2429 	{
2430 		// blank line
2431 		AddMonoString(box, ST::null);
2432 	}
2433 
2434 	// add cancel line
2435 	AddMonoString(box, pMovementMenuStrings[2]);
2436 }
2437 
2438 
MakeRegionBlank(const INT32 i,const UINT16 x,const UINT16 y,const UINT16 w,const UINT16 h)2439 static void MakeRegionBlank(const INT32 i, const UINT16 x, const UINT16 y, const UINT16 w, const UINT16 h)
2440 {
2441 	MSYS_DefineRegion(&gMoveMenuRegion[i], x, y + h * i, x + w, y + h * (i + 1), MSYS_PRIORITY_HIGHEST, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK);
2442 }
2443 
2444 
2445 static void MoveMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
2446 static void MoveMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason);
2447 
2448 
MakeRegion(const INT32 i,const UINT16 x,const UINT16 y,const UINT16 w,const UINT16 h,const UINT32 val_a,const UINT32 val_b)2449 static void MakeRegion(const INT32 i, const UINT16 x, const UINT16 y, const UINT16 w, const UINT16 h, const UINT32 val_a, const UINT32 val_b)
2450 {
2451 	MOUSE_REGION* const r = &gMoveMenuRegion[i];
2452 	MSYS_DefineRegion(r, x, y + h * i, x + w, y + h * (i + 1), MSYS_PRIORITY_HIGHEST, MSYS_NO_CURSOR, MoveMenuMvtCallback, MoveMenuBtnCallback);
2453 	MSYS_SetRegionUserData(r, 0, i);
2454 	MSYS_SetRegionUserData(r, 1, val_a);
2455 	MSYS_SetRegionUserData(r, 2, val_b);
2456 }
2457 
2458 
BuildMouseRegionsForMoveBox(void)2459 static void BuildMouseRegionsForMoveBox(void)
2460 {
2461 	SGPBox const& area = GetBoxArea(ghMoveBox);
2462 	INT32  const  x    = area.x;
2463 	INT32  const  y    = area.y + GetTopMarginSize(ghMoveBox) - 2; // -2 to improve highlighting accuracy between lines
2464 	INT32  const  w    = area.w;
2465 	INT32  const  h    = GetLineSpace(ghMoveBox) + GetFontHeight(GetBoxFont(ghMoveBox));
2466 	INT32         i    = 0; // Region index
2467 
2468 	MakeRegionBlank(i++, x, y, w, h); // Box heading
2469 	MakeRegionBlank(i++, x, y, w, h); // Blank line
2470 
2471 	// Define regions for squad lines
2472 	for (INT32 iCount = 0; iCount < giNumberOfSquadsInSectorMoving; ++iCount)
2473 	{
2474 		MakeRegion(i++, x, y, w, h, SQUAD_REGION, iCount);
2475 
2476 		for (INT32 iCountB = 0; iCountB < giNumberOfSoldiersInSectorMoving; ++iCountB)
2477 		{
2478 			if (pSoldierMovingList[iCountB]->bAssignment == iSquadMovingList[iCount])
2479 			{
2480 				MakeRegion(i++, x, y, w, h, SOLDIER_REGION, iCountB);
2481 			}
2482 		}
2483 	}
2484 
2485 	// Define regions for vehicle lines
2486 	for (INT32 iCount = 0; iCount < giNumberOfVehiclesInSectorMoving; ++iCount)
2487 	{
2488 		MakeRegion(i++, x, y, w, h, VEHICLE_REGION, iCount);
2489 
2490 		for (INT32 iCountB = 0; iCountB < giNumberOfSoldiersInSectorMoving; ++iCountB)
2491 		{
2492 			if (pSoldierMovingList[iCountB]->bAssignment == VEHICLE &&
2493 					pSoldierMovingList[iCountB]->iVehicleId  == iVehicleMovingList[iCount])
2494 			{
2495 				MakeRegion(i++, x, y, w, h, SOLDIER_REGION, iCountB);
2496 			}
2497 		}
2498 	}
2499 
2500 	// Define regions for "other" soldiers
2501 	BOOLEAN fDefinedOtherRegion = FALSE;
2502 	for (INT32 iCount = 0; iCount < giNumberOfSoldiersInSectorMoving; ++iCount)
2503 	{
2504 		// this guy is not in a squad or vehicle
2505 		if (pSoldierMovingList[iCount]->bAssignment >= ON_DUTY &&
2506 				pSoldierMovingList[iCount]->bAssignment != VEHICLE)
2507 		{
2508 			// this line gets place only once...
2509 			if (!fDefinedOtherRegion)
2510 			{
2511 				MakeRegion(i++, x, y, w, h, OTHER_REGION, 0);
2512 				fDefinedOtherRegion = TRUE;
2513 			}
2514 			MakeRegion(i++, x, y, w, h, SOLDIER_REGION, iCount);
2515 		}
2516 	}
2517 
2518 	Assert(i == 2 /* heading + blank line */ + giNumberOfSquadsInSectorMoving + giNumberOfVehiclesInSectorMoving + giNumberOfSoldiersInSectorMoving + (fDefinedOtherRegion ? 1 : 0));
2519 
2520 	MakeRegionBlank(i++, x, y, w, h); // blank line
2521 
2522 	if (IsAnythingSelectedForMoving())
2523 	{
2524 		MakeRegion(i++, x, y, w, h, DONE_REGION, 0); // DONE line
2525 	}
2526 	else
2527 	{
2528 		MakeRegionBlank(i++, x, y, w, h); // blank line
2529 	}
2530 
2531 	MakeRegion(i++, x, y, w, h, CANCEL_REGION, 0); // CANCEL line
2532 }
2533 
2534 
ClearMouseRegionsForMoveBox(void)2535 static void ClearMouseRegionsForMoveBox(void)
2536 {
2537 	INT32 iCounter = 0;
2538 
2539 	// run through list of mouse regions
2540 	for( iCounter = 0; iCounter < ( INT32 )GetNumberOfLinesOfTextInBox( ghMoveBox ); iCounter++ )
2541 	{
2542 		// remove this region
2543 		MSYS_RemoveRegion( &gMoveMenuRegion[ iCounter ] );
2544 	}
2545 }
2546 
2547 
MoveMenuMvtCallback(MOUSE_REGION * pRegion,INT32 iReason)2548 static void MoveMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason)
2549 {
2550 	// mvt callback handler for move box line regions
2551 	INT32 iValue = -1;
2552 
2553 
2554 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
2555 
2556 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
2557 	{
2558 		// highlight string
2559 		HighLightBoxLine( ghMoveBox, iValue );
2560 	}
2561 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
2562 	{
2563 		// unhighlight all strings in box
2564 		UnHighLightBox( ghMoveBox );
2565 	}
2566 }
2567 
2568 
2569 static void DeselectAllOtherSoldiersInList(void);
2570 static void HandleMoveoutOfSectorMovementTroops(void);
2571 static void SelectAllOtherSoldiersInList(void);
2572 
2573 
MoveMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)2574 static void MoveMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
2575 {
2576 	// btn callback handler for move box line regions
2577 	INT32 iMoveBoxLine = -1, iRegionType = -1, iListIndex = -1, iClickTime = 0;
2578 	SOLDIERTYPE *pSoldier = NULL;
2579 
2580 
2581 	iMoveBoxLine = MSYS_GetRegionUserData( pRegion, 0 );
2582 	iRegionType  = MSYS_GetRegionUserData( pRegion, 1 );
2583 	iListIndex   = MSYS_GetRegionUserData( pRegion, 2 );
2584 	iClickTime   = GetJA2Clock();
2585 
2586 	if( ( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP )  )
2587 	{
2588 		if( iClickTime - giDblClickTimersForMoveBoxMouseRegions[ iMoveBoxLine ] < DBL_CLICK_DELAY_FOR_MOVE_MENU  )
2589 		{
2590 			// dbl click, and something is selected?
2591 			if ( IsAnythingSelectedForMoving() )
2592 			{
2593 				// treat like DONE
2594 				HandleMoveoutOfSectorMovementTroops( );
2595 				return;
2596 			}
2597 		}
2598 		else
2599 		{
2600 			giDblClickTimersForMoveBoxMouseRegions[ iMoveBoxLine ] = iClickTime;
2601 
2602 			if( iRegionType == SQUAD_REGION )
2603 			{
2604 				// is the squad moving
2605 				if (fSquadIsMoving[iListIndex])
2606 				{
2607 					// squad stays
2608 					DeselectSquadForMovement( iSquadMovingList[ iListIndex ] );
2609 				}
2610 				else
2611 				{
2612 					// squad goes
2613 					SelectSquadForMovement( iSquadMovingList[ iListIndex ] );
2614 				}
2615 			}
2616 			else if( iRegionType == VEHICLE_REGION )
2617 			{
2618 				// is the vehicle moving
2619 				if (fVehicleIsMoving[iListIndex])
2620 				{
2621 					// vehicle stays
2622 					DeselectVehicleForMovement( iVehicleMovingList[ iListIndex ] );
2623 				}
2624 				else
2625 				{
2626 					// vehicle goes
2627 					SelectVehicleForMovement( iVehicleMovingList[ iListIndex ], AND_ALL_ON_BOARD );
2628 				}
2629 			}
2630 			else if( iRegionType == OTHER_REGION )
2631 			{
2632 				if (AllOtherSoldiersInListAreSelected())
2633 				{
2634 					// deselect all others in the list
2635 					DeselectAllOtherSoldiersInList( );
2636 				}
2637 				else
2638 				{
2639 					// select all others in the list
2640 					SelectAllOtherSoldiersInList( );
2641 				}
2642 			}
2643 			else if( iRegionType == SOLDIER_REGION )
2644 			{
2645 				pSoldier = pSoldierMovingList[ iListIndex ];
2646 
2647 				if ( pSoldier->fBetweenSectors )
2648 				{
2649 					// we don't allow mercs to change squads or get out of vehicles between sectors, easiest way to handle this
2650 					// is to prevent any toggling of individual soldiers on the move at the outset.
2651 					DoScreenIndependantMessageBox( pMapErrorString[ 41 ], MSG_BOX_FLAG_OK, NULL );
2652 					return;
2653 				}
2654 
2655 				// if soldier is currently selected to move
2656 				if (IsSoldierSelectedForMovement(*pSoldier))
2657 				{
2658 					// change him to NOT move instead
2659 
2660 					if( pSoldier->bAssignment == VEHICLE )
2661 					{
2662 						// if he's the only one left moving in the vehicle, deselect whole vehicle
2663 						if( HowManyMovingSoldiersInVehicle( pSoldier->iVehicleId ) == 1 )
2664 						{
2665 							// whole vehicle stays
2666 							DeselectVehicleForMovement( pSoldier->iVehicleId );
2667 						}
2668 						else
2669 						{
2670 							// soldier is staying behind
2671 							DeselectSoldierForMovement(*pSoldier);
2672 						}
2673 					}
2674 					else if( pSoldier->bAssignment < ON_DUTY )
2675 					{
2676 						// if he's the only one left moving in the squad, deselect whole squad
2677 						if( HowManyMovingSoldiersInSquad( pSoldier->bAssignment ) == 1 )
2678 						{
2679 							// whole squad stays
2680 							DeselectSquadForMovement( pSoldier->bAssignment );
2681 						}
2682 						else
2683 						{
2684 							// soldier is staying behind
2685 							DeselectSoldierForMovement(*pSoldier);
2686 						}
2687 					}
2688 					else
2689 					{
2690 						// soldier is staying behind
2691 						DeselectSoldierForMovement(*pSoldier);
2692 					}
2693 				}
2694 				else	// currently NOT moving
2695 				{
2696 					// is he able & allowed to move?  (Errors with a reason are reported within)
2697 					if ( CanMoveBoxSoldierMoveStrategically( pSoldier, TRUE ) )
2698 					{
2699 						// change him to move instead
2700 						SelectSoldierForMovement(*pSoldier);
2701 
2702 						if( pSoldier->bAssignment < ON_DUTY )
2703 						{
2704 							// if everyone in the squad is now selected, select the squad itself
2705 							if( AllSoldiersInSquadSelected( pSoldier->bAssignment ) )
2706 							{
2707 								SelectSquadForMovement( pSoldier->bAssignment );
2708 							}
2709 						}
2710 /* ARM: it's more flexible without this - player can take the vehicle along or not without having to exit it.
2711 						else if( pSoldier->bAssignment == VEHICLE )
2712 						{
2713 							// his vehicle MUST also go while he's moving, but not necessarily others on board
2714 							SelectVehicleForMovement( pSoldier->iVehicleId, VEHICLE_ONLY );
2715 						}
2716 */
2717 					}
2718 				}
2719 			}
2720 			else if( iRegionType == DONE_REGION )
2721 			{
2722 				// is something selected?
2723 				if ( IsAnythingSelectedForMoving() )
2724 				{
2725 					HandleMoveoutOfSectorMovementTroops( );
2726 					return;
2727 				}
2728 			}
2729 			else if( iRegionType == CANCEL_REGION )
2730 			{
2731 				fShowMapScreenMovementList = FALSE;
2732 				return;
2733 			}
2734 			else
2735 			{
2736 				SLOGA("MoveMenuBtnCallback: Invalid regionType %d, moveBoxLine %d", iRegionType, iMoveBoxLine);
2737 				return;
2738 			}
2739 
2740 			fRebuildMoveBox = TRUE;
2741 			fTeamPanelDirty = TRUE;
2742 			fMapPanelDirty = TRUE;
2743 			fCharacterInfoPanelDirty = TRUE;
2744 			MarkAllBoxesAsAltered( );
2745 		}
2746 	}
2747 }
2748 
2749 
2750 static MoveError CanCharacterMoveInStrategic(SOLDIERTYPE&);
2751 
2752 
CanMoveBoxSoldierMoveStrategically(SOLDIERTYPE * pSoldier,BOOLEAN fShowErrorMessage)2753 static BOOLEAN CanMoveBoxSoldierMoveStrategically(SOLDIERTYPE* pSoldier, BOOLEAN fShowErrorMessage)
2754 {
2755 	// valid soldier?
2756 	Assert( pSoldier );
2757 	Assert( pSoldier->bActive );
2758 
2759 	MoveError const ret = CanCharacterMoveInStrategic(*pSoldier);
2760 	if (ret == ME_OK) return TRUE;
2761 
2762 	if (fShowErrorMessage) ReportMapScreenMovementError(ret);
2763 	return FALSE;
2764 }
2765 
2766 
SelectAllOtherSoldiersInList(void)2767 static void SelectAllOtherSoldiersInList(void)
2768 {
2769 	INT32 iCounter = 0;
2770 	BOOLEAN fSomeCantMove = FALSE;
2771 
2772 
2773 	for( iCounter = 0; iCounter < giNumberOfSoldiersInSectorMoving; iCounter++ )
2774 	{
2775 		if( ( pSoldierMovingList[ iCounter ] ->bAssignment >= ON_DUTY ) && ( pSoldierMovingList[ iCounter ] ->bAssignment != VEHICLE ) )
2776 		{
2777 			if ( CanMoveBoxSoldierMoveStrategically( pSoldierMovingList[ iCounter ], FALSE ) )
2778 			{
2779 				fSoldierIsMoving[ iCounter ] = true;
2780 			}
2781 			else
2782 			{
2783 				fSomeCantMove = TRUE;
2784 			}
2785 		}
2786 	}
2787 
2788 	if ( fSomeCantMove )
2789 	{
2790 		// can't - some of the OTHER soldiers can't move
2791 		ReportMapScreenMovementError( 46 );
2792 	}
2793 }
2794 
2795 
DeselectAllOtherSoldiersInList(void)2796 static void DeselectAllOtherSoldiersInList(void)
2797 {
2798 	INT32 iCounter = 0;
2799 
2800 	for( iCounter = 0; iCounter < giNumberOfSoldiersInSectorMoving; iCounter++ )
2801 	{
2802 		if( ( pSoldierMovingList[ iCounter ] ->bAssignment >= ON_DUTY ) && ( pSoldierMovingList[ iCounter ] ->bAssignment != VEHICLE ) )
2803 		{
2804 			fSoldierIsMoving[ iCounter ] = false;
2805 		}
2806 	}
2807 }
2808 
2809 
2810 static INT8 FindSquadThatSoldierCanJoin(SOLDIERTYPE* pSoldier);
2811 static void HandleSettingTheSelectedListOfMercs(void);
2812 
2813 
HandleMoveoutOfSectorMovementTroops(void)2814 static void HandleMoveoutOfSectorMovementTroops(void)
2815 {
2816 	INT32 iCounter = 0;
2817 	SOLDIERTYPE *pSoldier = 0;
2818 	INT32 iSquadNumber = -1;
2819 	BOOLEAN fCheckForCompatibleSquad = FALSE;
2820 
2821 
2822 	// cancel move box
2823 	fShowMapScreenMovementList = FALSE;
2824 
2825 
2826 	for( iCounter = 0; iCounter < giNumberOfSoldiersInSectorMoving; iCounter++ )
2827 	{
2828 		pSoldier = pSoldierMovingList[ iCounter ];
2829 
2830 		fCheckForCompatibleSquad = FALSE;
2831 
2832 		// if he is on a valid squad
2833 		if( pSoldier->bAssignment < ON_DUTY )
2834 		{
2835 			// if he and his squad are parting ways (soldier is staying behind, but squad is leaving, or vice versa)
2836 			if( fSoldierIsMoving[ iCounter ] != IsSquadSelectedForMovement( pSoldier->bAssignment ) )
2837 			{
2838 				// split the guy from his squad to any other compatible squad
2839 				fCheckForCompatibleSquad = TRUE;
2840 			}
2841 		}
2842 		// if in a vehicle
2843 		else if( pSoldier->bAssignment == VEHICLE )
2844 		{
2845 			// if he and his vehicle are parting ways (soldier is staying behind, but vehicle is leaving, or vice versa)
2846 			if( fSoldierIsMoving[ iCounter ] != IsVehicleSelectedForMovement( pSoldier->iVehicleId ) )
2847 			{
2848 				// split the guy from his vehicle to any other compatible squad
2849 				fCheckForCompatibleSquad = TRUE;
2850 			}
2851 		}
2852 		else	// on his own - not on a squad or in a vehicle
2853 		{
2854 			// if he's going anywhere
2855 			if( fSoldierIsMoving[ iCounter ] )
2856 			{
2857 				// find out if anyone is going with this guy...see if he can tag along
2858 				fCheckForCompatibleSquad = TRUE;
2859 			}
2860 		}
2861 
2862 
2863 		if ( fCheckForCompatibleSquad )
2864 		{
2865 			// look for a squad that's doing the same thing as this guy is and has room for him
2866 			iSquadNumber = FindSquadThatSoldierCanJoin( pSoldier );
2867 			if( iSquadNumber != -1 )
2868 			{
2869 				if ( !AddCharacterToSquad( pSoldier, ( INT8 )( iSquadNumber ) ) )
2870 				{
2871 					SLOGA("HandleMoveoutOfSectorMovementTroops: AddCharacterToSquad %d failed, iCounter %d", iSquadNumber, iCounter);
2872 					// toggle whether he's going or not to try and recover somewhat gracefully
2873 					fSoldierIsMoving[ iCounter ] = !fSoldierIsMoving[ iCounter ];
2874 				}
2875 			}
2876 			else
2877 			{
2878 				// no existing squad is compatible, will have to start his own new squad
2879 				iSquadNumber = AddCharacterToUniqueSquad( pSoldier );
2880 
2881 				// It worked.  Add his new squad to the "moving squads" list so others can join it, too!
2882 				AddSquadToMovingLists( iSquadNumber );
2883 
2884 				// If this guy is moving
2885 				if( fSoldierIsMoving[ iCounter ] )
2886 				{
2887 					// mark this new squad as moving too, so those moving can join it
2888 					SelectSquadForMovement( iSquadNumber );
2889 				}
2890 			}
2891 		}
2892 
2893 	}
2894 
2895 	// now actually set the list
2896 	HandleSettingTheSelectedListOfMercs( );
2897 }
2898 
2899 
HandleSettingTheSelectedListOfMercs(void)2900 static void HandleSettingTheSelectedListOfMercs(void)
2901 {
2902 	BOOLEAN fFirstOne = TRUE;
2903 	INT32 iCounter = 0;
2904 	BOOLEAN fSelected;
2905 
2906 	// reset the selected character
2907 	bSelectedDestChar = -1;
2908 
2909 	// run through the list of grunts
2910 	for( iCounter = 0; iCounter < MAX_CHARACTER_COUNT; iCounter++ )
2911 	{
2912 		const SOLDIERTYPE* const pSoldier = gCharactersList[iCounter].merc;
2913 		if (pSoldier == NULL) continue;
2914 
2915 		if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
2916 		{
2917 			fSelected = IsVehicleSelectedForMovement( pSoldier->bVehicleID );
2918 		}
2919 		else
2920 		{
2921 			fSelected = IsSoldierSelectedForMovement(*pSoldier);
2922 		}
2923 
2924 		// is he/she selected for movement?
2925 		if( fSelected )
2926 		{
2927 			// yes, are they the first one to be selected?
2928 			if (fFirstOne)
2929 			{
2930 				// yes, then set them as the destination plotting character for movement arrow purposes
2931 				fFirstOne = FALSE;
2932 
2933 				bSelectedDestChar = ( INT8 )iCounter;
2934 				// make DEST column glow
2935 				giDestHighLine = iCounter;
2936 
2937 				ChangeSelectedInfoChar( ( INT8 ) iCounter, TRUE );
2938 			}
2939 
2940 			// add this guy to the selected list of grunts
2941 			SetEntryInSelectedCharacterList( ( INT8 )iCounter );
2942 		}
2943 	}
2944 
2945 	if( bSelectedDestChar != -1 )
2946 	{
2947 		// set cursor
2948 		SetUpCursorForStrategicMap( );
2949 		fTeamPanelDirty = TRUE;
2950 		fMapPanelDirty = TRUE;
2951 		fCharacterInfoPanelDirty = TRUE;
2952 
2953 		DeselectSelectedListMercsWhoCantMoveWithThisGuy(gCharactersList[bSelectedDestChar].merc);
2954 
2955 		// remember the current paths for all selected characters so we can restore them if need be
2956 		RememberPreviousPathForAllSelectedChars();
2957 	}
2958 }
2959 
2960 
AllOtherSoldiersInListAreSelected(void)2961 static BOOLEAN AllOtherSoldiersInListAreSelected(void)
2962 {
2963 	INT32 iCounter = 0, iCount = 0;
2964 
2965 	for( iCounter = 0; iCounter < giNumberOfSoldiersInSectorMoving; iCounter++ )
2966 	{
2967 		if( ( pSoldierMovingList[ iCounter ]->bAssignment >= ON_DUTY ) && (  pSoldierMovingList[ iCounter ]->bAssignment >= VEHICLE ) )
2968 		{
2969 			if (!fSoldierIsMoving[iCounter])
2970 			{
2971 				return( FALSE );
2972 			}
2973 
2974 			iCount++;
2975 		}
2976 	}
2977 
2978 	// some merc on other assignments and no result?
2979 	if( iCount )
2980 	{
2981 		return( TRUE );
2982 	}
2983 
2984 	return( FALSE );
2985 }
2986 
2987 
IsThisSquadInThisSector(const INT16 sSectorX,const INT16 sSectorY,const INT8 bSectorZ,const INT8 bSquadValue)2988 static BOOLEAN IsThisSquadInThisSector(const INT16 sSectorX, const INT16 sSectorY, const INT8 bSectorZ, const INT8 bSquadValue)
2989 {
2990 	INT16 sX;
2991 	INT16 sY;
2992 	INT8  bZ;
2993 	return
2994 		SectorSquadIsIn(bSquadValue, &sX, &sY, &bZ) &&
2995 		sSectorX == sX                              &&
2996 		sSectorY == sY                              &&
2997 		bSectorZ == bZ                              &&
2998 		!IsThisSquadOnTheMove(bSquadValue);
2999 }
3000 
3001 
FindSquadThatSoldierCanJoin(SOLDIERTYPE * pSoldier)3002 static INT8 FindSquadThatSoldierCanJoin(SOLDIERTYPE* pSoldier)
3003 {
3004 	// look for a squad that isn't full that can take this character
3005 	INT8 bCounter = 0;
3006 
3007 	// run through the list of squads
3008 	for( bCounter = 0; bCounter < NUMBER_OF_SQUADS; bCounter++ )
3009 	{
3010 		// is this squad in this sector
3011 		if( IsThisSquadInThisSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ, bCounter ) )
3012 		{
3013 			// does it have room?
3014 			if (!IsThisSquadFull(bCounter))
3015 			{
3016 				// is it doing the same thing as the soldier is (staying or going) ?
3017 				if (IsSquadSelectedForMovement(bCounter) == IsSoldierSelectedForMovement(*pSoldier))
3018 				{
3019 					// go ourselves a match, then
3020 					return( bCounter );
3021 				}
3022 			}
3023 		}
3024 	}
3025 
3026 	return( -1 );
3027 }
3028 
ReBuildMoveBox(void)3029 void ReBuildMoveBox( void )
3030 {
3031 	// check to see if we need to rebuild the movement box and mouse regions
3032 	if (!fRebuildMoveBox) return;
3033 
3034 	// reset the fact
3035 	fRebuildMoveBox = FALSE;
3036 	fTeamPanelDirty = TRUE;
3037 	fMapPanelDirty = TRUE;
3038 	fCharacterInfoPanelDirty = TRUE;
3039 
3040 	// stop showing the box
3041 	fShowMapScreenMovementList = FALSE;
3042 	CreateDestroyMovementBox( sSelMapX, sSelMapY, ( INT16 )iCurrentMapSectorZ );
3043 
3044 	// show the box
3045 	fShowMapScreenMovementList = TRUE;
3046 	CreateDestroyMovementBox( sSelMapX, sSelMapY, ( INT16 )iCurrentMapSectorZ );
3047 	ShowBox( ghMoveBox );
3048 	MarkAllBoxesAsAltered( );
3049 }
3050 
3051 
3052 static void MoveScreenMaskBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3053 
3054 
CreateScreenMaskForMoveBox(void)3055 void CreateScreenMaskForMoveBox( void )
3056 {
3057 	if (!fScreenMaskForMoveCreated)
3058 	{
3059 		// set up the screen mask
3060 		MSYS_DefineRegion(&gMoveBoxScreenMask, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MSYS_PRIORITY_HIGHEST - 4, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, MoveScreenMaskBtnCallback);
3061 
3062 		fScreenMaskForMoveCreated = TRUE;
3063 	}
3064 }
3065 
RemoveScreenMaskForMoveBox(void)3066 void RemoveScreenMaskForMoveBox( void )
3067 {
3068 	if (fScreenMaskForMoveCreated)
3069 	{
3070 		// remove the screen mask
3071 		MSYS_RemoveRegion( &gMoveBoxScreenMask );
3072 		fScreenMaskForMoveCreated = FALSE;
3073 	}
3074 }
3075 
3076 
MoveScreenMaskBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)3077 static void MoveScreenMaskBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
3078 {
3079 	// btn callback handler for move box screen mask region
3080 	if( ( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP )  )
3081 	{
3082 		fShowMapScreenMovementList = FALSE;
3083 	}
3084 	else if( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP )
3085 	{
3086 		sSelectedMilitiaTown = 0;
3087 
3088 		// are we showing the update box
3089 		if( fShowUpdateBox )
3090 		{
3091 			fShowUpdateBox = FALSE;
3092 		}
3093 	}
3094 }
3095 
3096 
ResetSoldierUpdateBox(void)3097 static void ResetSoldierUpdateBox(void)
3098 {
3099 	INT32 iCounter = 0;
3100 
3101 	// delete any loaded faces
3102 	for( iCounter = 0; iCounter < SIZE_OF_UPDATE_BOX; iCounter++ )
3103 	{
3104 		if( pUpdateSoldierBox[ iCounter ] != NULL )
3105 		{
3106 			DeleteVideoObject(giUpdateSoldierFaces[iCounter]);
3107 		}
3108 	}
3109 
3110 	if( giMercPanelImage != 0 )
3111 	{
3112 		DeleteVideoObject(giMercPanelImage);
3113 	}
3114 
3115 	// reset the soldier ptrs in the update box
3116 	for( iCounter = 0; iCounter < SIZE_OF_UPDATE_BOX; iCounter++ )
3117 	{
3118 		pUpdateSoldierBox[ iCounter ] = NULL;
3119 	}
3120 }
3121 
3122 
GetNumberOfMercsInUpdateList()3123 INT32 GetNumberOfMercsInUpdateList()
3124 {
3125 	INT32 n = 0;
3126 	FOR_EACH(SOLDIERTYPE* const, i, pUpdateSoldierBox)
3127 	{
3128 		if (*i) ++n;
3129 	}
3130 	return n;
3131 }
3132 
3133 
3134 static void AddSoldierToUpdateBox(SOLDIERTYPE*);
3135 
3136 
AddSoldierToWaitingListQueue(SOLDIERTYPE & s)3137 void AddSoldierToWaitingListQueue(SOLDIERTYPE& s)
3138 {
3139 	class DialogueEventUpdateBoxAddSoldier : public CharacterDialogueEvent
3140 	{
3141 		public:
3142 			DialogueEventUpdateBoxAddSoldier(SOLDIERTYPE& s) : CharacterDialogueEvent(s) {}
3143 
3144 			bool Execute()
3145 			{
3146 				AddSoldierToUpdateBox(&soldier_);
3147 				return false;
3148 			}
3149 	};
3150 
3151 	DialogueEvent::Add(new DialogueEventUpdateBoxAddSoldier(s));
3152 }
3153 
3154 
AddReasonToWaitingListQueue(UpdateBoxReason const reason)3155 void AddReasonToWaitingListQueue(UpdateBoxReason const reason)
3156 {
3157 	class DialogueEventUpdateBoxSetReason : public DialogueEvent
3158 	{
3159 		public:
3160 			DialogueEventUpdateBoxSetReason(UpdateBoxReason const reason) : reason_(reason) {}
3161 
3162 			bool Execute()
3163 			{
3164 				iReasonForSoldierUpDate = reason_;
3165 				return false;
3166 			}
3167 
3168 		private:
3169 			UpdateBoxReason const reason_;
3170 	};
3171 
3172 	DialogueEvent::Add(new DialogueEventUpdateBoxSetReason(reason));
3173 }
3174 
3175 
AddDisplayBoxToWaitingQueue(void)3176 void AddDisplayBoxToWaitingQueue(void)
3177 {
3178 	class DialogueEventUpdateBoxShow : public DialogueEvent
3179 	{
3180 		public:
3181 			bool Execute()
3182 			{
3183 				fShowUpdateBox = TRUE;
3184 				return false;
3185 			}
3186 	};
3187 
3188 	DialogueEvent::Add(new DialogueEventUpdateBoxShow());
3189 }
3190 
3191 
AddSoldierToUpdateBox(SOLDIERTYPE * const pSoldier)3192 static void AddSoldierToUpdateBox(SOLDIERTYPE* const pSoldier)
3193 {
3194 	INT32 iCounter = 0;
3195 
3196 	// going to load face
3197 
3198 	if( pSoldier->bLife == 0 )
3199 	{
3200 		return;
3201 	}
3202 
3203 	if (!pSoldier->bActive) return;
3204 
3205 	// if update
3206 	if( pUpdateSoldierBox[ iCounter ] == NULL )
3207 	{
3208 		giMercPanelImage = AddVideoObjectFromFile(INTERFACEDIR "/panels.sti");
3209 	}
3210 
3211 	// run thought list of update soldiers
3212 	for( iCounter = 0; iCounter < SIZE_OF_UPDATE_BOX; iCounter++ )
3213 	{
3214 		// find a free slot
3215 		if( pUpdateSoldierBox[ iCounter ] == NULL )
3216 		{
3217 			// add to box
3218 			pUpdateSoldierBox[ iCounter ] = pSoldier;
3219 			giUpdateSoldierFaces[iCounter] = Load65Portrait(GetProfile(pSoldier->ubProfile));
3220 			return;
3221 		}
3222 	}
3223 }
3224 
3225 
3226 static void CreateDestroyUpdatePanelButtons(INT32 iX, INT32 iY, BOOLEAN fFourWideMode);
3227 static void RenderSoldierSmallFaceForUpdatePanel(INT32 iIndex, INT32 iX, INT32 iY);
3228 
3229 
DisplaySoldierUpdateBox()3230 void DisplaySoldierUpdateBox( )
3231 {
3232 	INT32 iNumberOfMercsOnUpdatePanel = 0;
3233 	INT32 iNumberHigh = 0, iNumberWide = 0;
3234 	INT32 iUpdatePanelWidth = 0, iUpdatePanelHeight = 0;
3235 	INT32 iX = 0, iY = 0;
3236 	INT32 iFaceX = 0, iFaceY = 0;
3237 	BOOLEAN fFourWideMode = FALSE;
3238 	INT32 iCounter = 0;
3239 	INT32 iUpperLimit = 0;
3240 
3241 	if (!fShowUpdateBox) return;
3242 
3243 	// get the number of mercs
3244 	iNumberOfMercsOnUpdatePanel = GetNumberOfMercsInUpdateList( );
3245 
3246 	if( iNumberOfMercsOnUpdatePanel == 0 )
3247 	{
3248 		// nobody home
3249 		fShowUpdateBox = FALSE;
3250 		// unpause
3251 		UnPauseDialogueQueue( );
3252 		return;
3253 	}
3254 
3255 	giSleepHighLine = -1;
3256 	giDestHighLine = -1;
3257 	giContractHighLine = -1;
3258 	giAssignHighLine = -1;
3259 
3260 
3261 	//InterruptTime();
3262 	PauseGame( );
3263 	LockPauseState(LOCK_PAUSE_DISPLAY_SOLDIER_UPDATE);
3264 
3265 	PauseDialogueQueue( );
3266 
3267 	// do we have enough for 4 wide, or just 2 wide?
3268 	if( iNumberOfMercsOnUpdatePanel > NUMBER_OF_MERCS_FOR_FOUR_WIDTH_UPDATE_PANEL )
3269 	{
3270 		fFourWideMode = TRUE;
3271 	}
3272 
3273 	// get number of rows
3274 	iNumberHigh = ( fFourWideMode ?  iNumberOfMercsOnUpdatePanel / NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE
3275 		:  iNumberOfMercsOnUpdatePanel / NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE);
3276 
3277 	//number of columns
3278 	iNumberWide = ( fFourWideMode ? NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE
3279 		: NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE );
3280 
3281 
3282 
3283 	// get the height and width of the box .. will need to add in stuff for borders, lower panel...etc
3284 	if( fFourWideMode )
3285 	{
3286 		// do we need an extra row for left overs
3287 		if( iNumberOfMercsOnUpdatePanel % NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE )
3288 		{
3289 			iNumberHigh++;
3290 		}
3291 	}
3292 	else
3293 	{
3294 		// do we need an extra row for left overs
3295 		if( iNumberOfMercsOnUpdatePanel % NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE )
3296 		{
3297 			iNumberHigh++;
3298 		}
3299 	}
3300 
3301 		// round off
3302 	if( fFourWideMode )
3303 	{
3304 		if(iNumberOfMercsOnUpdatePanel % NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE )
3305 		{
3306 			iNumberOfMercsOnUpdatePanel += NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE - ( iNumberOfMercsOnUpdatePanel % NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE );
3307 		}
3308 	}
3309 	else
3310 	{
3311 		if(iNumberOfMercsOnUpdatePanel % NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE )
3312 		{
3313 			iNumberOfMercsOnUpdatePanel += NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE - ( iNumberOfMercsOnUpdatePanel % NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE );
3314 		}
3315 	}
3316 
3317 	iUpdatePanelWidth = iNumberWide * TACT_WIDTH_OF_UPDATE_PANEL_BLOCKS;
3318 
3319 	iUpdatePanelHeight = ( iNumberHigh + 1 ) * TACT_HEIGHT_OF_UPDATE_PANEL_BLOCKS;
3320 
3321 	// get the x,y offsets on the screen of the panel
3322 	iX = STD_SCREEN_X + 290 + ( 336 - iUpdatePanelWidth ) / 2;
3323 
3324 //	iY = 28 + ( 288 - iUpdatePanelHeight ) / 2;
3325 
3326 	// Have the bottom of the box ALWAYS a set distance from the bottom of the map ( so user doesnt have to move mouse far )
3327 	iY = STD_SCREEN_Y + 280 - iUpdatePanelHeight;
3328 
3329 	const SGPVObject* const hBackGroundHandle = guiUpdatePanelTactical;
3330 
3331 	//Display the 2 TOP corner pieces
3332 	BltVideoObject( guiSAVEBUFFER, hBackGroundHandle, 0, iX-4, iY - 4);
3333 	BltVideoObject( guiSAVEBUFFER, hBackGroundHandle, 2, iX+iUpdatePanelWidth, iY - 4);
3334 
3335 	if( fFourWideMode )
3336 	{
3337 		//Display 2 vertical lines starting at the bottom
3338 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 3, iX - 4, iY + iUpdatePanelHeight - 3 - 70);
3339 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 5, iX + iUpdatePanelWidth, iY + iUpdatePanelHeight - 3 - 70);
3340 
3341 		//Display the 2 bottom corner pieces
3342 		BltVideoObject( guiSAVEBUFFER, hBackGroundHandle, 0, iX-4, iY + iUpdatePanelHeight - 3);
3343 		BltVideoObject( guiSAVEBUFFER, hBackGroundHandle, 2, iX+iUpdatePanelWidth, iY + iUpdatePanelHeight - 3);
3344 	}
3345 
3346 
3347 	SetFontDestBuffer(guiSAVEBUFFER);
3348 
3349 	iUpperLimit = fFourWideMode ? ( iNumberOfMercsOnUpdatePanel + NUMBER_OF_MERC_COLUMNS_FOR_FOUR_WIDE_MODE ) : ( iNumberOfMercsOnUpdatePanel + NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE );
3350 
3351 	//need to put the background down first
3352 	for( iCounter = 0; iCounter < iUpperLimit; iCounter++ )
3353 	{
3354 		// blt the face and name
3355 
3356 		// get the face x and y
3357 		iFaceX = iX + ( iCounter % iNumberWide ) * TACT_UPDATE_MERC_FACE_X_WIDTH;
3358 		iFaceY = iY + ( iCounter / iNumberWide ) * TACT_UPDATE_MERC_FACE_X_HEIGHT;
3359 
3360 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 20, iFaceX, iFaceY);
3361 	}
3362 
3363 	//loop through the mercs to be displayed
3364 	for( iCounter = 0; iCounter < ( iNumberOfMercsOnUpdatePanel <= NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE ? NUMBER_OF_MERC_COLUMNS_FOR_TWO_WIDE_MODE : iNumberOfMercsOnUpdatePanel ); iCounter++ )
3365 	{
3366 		//
3367 		// blt the face and name
3368 		//
3369 
3370 		// get the face x and y
3371 		iFaceX = iX + ( iCounter % iNumberWide ) * TACT_UPDATE_MERC_FACE_X_WIDTH;
3372 		iFaceY = iY + ( iCounter / iNumberWide ) * TACT_UPDATE_MERC_FACE_X_HEIGHT +  REASON_FOR_SOLDIER_UPDATE_OFFSET_Y;
3373 
3374 		// now get the face
3375 		if( pUpdateSoldierBox[ iCounter ] )
3376 		{
3377 			iFaceX += TACT_UPDATE_MERC_FACE_X_OFFSET;
3378 			iFaceY += TACT_UPDATE_MERC_FACE_Y_OFFSET;
3379 
3380 			// there is a face
3381 			RenderSoldierSmallFaceForUpdatePanel( iCounter, iFaceX, iFaceY );
3382 
3383 			// display the mercs name
3384 			DrawTextToScreen(pUpdateSoldierBox[iCounter]->name, iFaceX - 5, iFaceY + 31, 57, TINYFONT1, FONT_LTRED, FONT_BLACK, CENTER_JUSTIFIED);
3385 		}
3386 	}
3387 
3388 	// the button container box
3389 	if( fFourWideMode )
3390 	{
3391 		//def: 3/1/99 WAS SUBINDEX 6,
3392 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 19, iX - 4 + TACT_UPDATE_MERC_FACE_X_WIDTH,  iY + iNumberHigh * TACT_UPDATE_MERC_FACE_X_HEIGHT + REASON_FOR_SOLDIER_UPDATE_OFFSET_Y+3);
3393 
3394 		// ATE: Display string for time compression
3395 		DisplayWrappedString(iX, iY + iNumberHigh * TACT_UPDATE_MERC_FACE_X_HEIGHT + 5 + REASON_FOR_SOLDIER_UPDATE_OFFSET_Y + 3, iUpdatePanelWidth, 0, MAP_SCREEN_FONT, FONT_WHITE, gzLateLocalizedString[STR_LATE_49], FONT_BLACK, CENTER_JUSTIFIED);
3396 	}
3397 	else
3398 	{
3399 		//def: 3/1/99 WAS SUBINDEX 6,
3400 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 19, iX - 4 , iY + iNumberHigh * TACT_UPDATE_MERC_FACE_X_HEIGHT + REASON_FOR_SOLDIER_UPDATE_OFFSET_Y+3);
3401 
3402 		// ATE: Display string for time compression
3403 		DisplayWrappedString(iX, iY + iNumberHigh * TACT_UPDATE_MERC_FACE_X_HEIGHT + 5 + REASON_FOR_SOLDIER_UPDATE_OFFSET_Y + 3, iUpdatePanelWidth, 0, MAP_SCREEN_FONT, FONT_WHITE, gzLateLocalizedString[STR_LATE_49], FONT_BLACK, CENTER_JUSTIFIED);
3404 	}
3405 
3406 	// now wrap the border
3407 	for( iCounter = 0; iCounter < iNumberHigh ; iCounter++ )
3408 	{
3409 		// the sides
3410 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 3, iX - 4, iY + ( iCounter ) * TACT_UPDATE_MERC_FACE_X_HEIGHT);
3411 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 5, iX + iUpdatePanelWidth, iY + ( iCounter ) * TACT_UPDATE_MERC_FACE_X_HEIGHT);
3412 	}
3413 
3414 	//big horizontal line
3415 	for( iCounter = 0; iCounter < iNumberWide; iCounter++ )
3416 	{
3417 		// the top bottom
3418 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 1, iX + TACT_UPDATE_MERC_FACE_X_WIDTH * (iCounter ) , iY - 4);
3419 		BltVideoObject( guiSAVEBUFFER , hBackGroundHandle, 1, iX + TACT_UPDATE_MERC_FACE_X_WIDTH * (iCounter ) , iY + iUpdatePanelHeight - 3);
3420 	}
3421 
3422 	//Display the reason for the update box
3423 	DisplayWrappedString(iX, iY + (fFourWideMode ? 6 : 3), iUpdatePanelWidth, 0, MAP_SCREEN_FONT, FONT_WHITE, pUpdateMercStrings[iReasonForSoldierUpDate], FONT_BLACK, CENTER_JUSTIFIED);
3424 
3425 	SetFontDestBuffer(FRAME_BUFFER);
3426 
3427 	// restore extern background rect
3428 	RestoreExternBackgroundRect( ( INT16 )( iX - 5 ), ( INT16 )( iY - 5 ), ( INT16 )(  iUpdatePanelWidth + 10 ), ( INT16 )(iUpdatePanelHeight + 6 ) );
3429 
3430 	CreateDestroyUpdatePanelButtons( iX, ( iY + iUpdatePanelHeight - 18 ), fFourWideMode );
3431 	MarkAButtonDirty( guiUpdatePanelButtons[ 0 ] );
3432 	MarkAButtonDirty( guiUpdatePanelButtons[ 1 ] );
3433 }
3434 
3435 
MakeButton(UINT idx,INT16 x,INT16 y,GUI_CALLBACK click,const ST::string & text,const ST::string & help_text)3436 static void MakeButton(UINT idx, INT16 x, INT16 y, GUI_CALLBACK click, const ST::string& text, const ST::string& help_text)
3437 {
3438 	GUIButtonRef const btn = QuickCreateButtonImg(INTERFACEDIR "/group_confirm_tactical.sti", 7, 8, x, y, MSYS_PRIORITY_HIGHEST - 1, click);
3439 	guiUpdatePanelButtons[idx] = btn;
3440 	btn->SpecifyGeneralTextAttributes(text, MAP_SCREEN_FONT, FONT_MCOLOR_BLACK, FONT_BLACK);
3441 	btn->SetFastHelpText(help_text);
3442 }
3443 
3444 
3445 static void ContinueUpdateButtonCallback(GUI_BUTTON* btn, INT32 reason);
3446 static void StopUpdateButtonCallback(GUI_BUTTON* btn, INT32 reason);
3447 
3448 
CreateDestroyUpdatePanelButtons(INT32 iX,INT32 iY,BOOLEAN fFourWideMode)3449 static void CreateDestroyUpdatePanelButtons(INT32 iX, INT32 iY, BOOLEAN fFourWideMode)
3450 {
3451 	static BOOLEAN fCreated = FALSE;
3452 
3453 	if (fShowUpdateBox && !fCreated)
3454 	{
3455 		// set to created
3456 		fCreated = TRUE;
3457 
3458 		fShowAssignmentMenu = FALSE;
3459 		fShowContractMenu = FALSE;
3460 
3461 		INT16 x = iX;
3462 		if (fFourWideMode) x += TACT_UPDATE_MERC_FACE_X_WIDTH;
3463 		MakeButton(0, x,                                 iY, ContinueUpdateButtonCallback, pUpdatePanelButtons[0], gzLateLocalizedString[STR_LATE_51]);
3464 		MakeButton(1, x + TACT_UPDATE_MERC_FACE_X_WIDTH, iY, StopUpdateButtonCallback,     pUpdatePanelButtons[1], gzLateLocalizedString[STR_LATE_52]);
3465 	}
3466 	else if (!fShowUpdateBox && fCreated)
3467 	{
3468 		// set to uncreated
3469 		fCreated = FALSE;
3470 
3471 		// get rid of the buttons and images
3472 		RemoveButton( guiUpdatePanelButtons[ 0 ] );
3473 		RemoveButton( guiUpdatePanelButtons[ 1 ] );
3474 
3475 		// unpause
3476 		UnPauseDialogueQueue( );
3477 	}
3478 }
3479 
3480 
CreateDestroyTheUpdateBox(void)3481 void CreateDestroyTheUpdateBox( void )
3482 {
3483 	static BOOLEAN fCreated = FALSE;
3484 
3485 	if (!fCreated && fShowUpdateBox)
3486 	{
3487 		if( GetNumberOfMercsInUpdateList( ) == 0 )
3488 		{
3489 			fShowUpdateBox = FALSE;
3490 			return;
3491 		}
3492 
3493 		fCreated = TRUE;
3494 
3495 		//InterruptTime();
3496 		// create screen mask
3497 		CreateScreenMaskForMoveBox( );
3498 
3499 		// lock it paused
3500 		PauseGame();
3501 		LockPauseState(LOCK_PAUSE_CREATE_SOLDIER_UPDATE);
3502 
3503 		// display the box
3504 		DisplaySoldierUpdateBox( );
3505 
3506 		// Do beep
3507 		PlayJA2SampleFromFile(SOUNDSDIR "/newbeep.wav", MIDVOLUME, 1, MIDDLEPAN);
3508 	}
3509 	else if (fCreated && !fShowUpdateBox)
3510 	{
3511 		fCreated = FALSE;
3512 
3513 		UnLockPauseState( );
3514 		UnPauseGame();
3515 
3516 		// dirty screen
3517 		fMapPanelDirty = TRUE;
3518 		fTeamPanelDirty = TRUE;
3519 		fCharacterInfoPanelDirty = TRUE;
3520 
3521 		// remove screen mask
3522 		RemoveScreenMaskForMoveBox( );
3523 
3524 		ResetSoldierUpdateBox( );
3525 
3526 		CreateDestroyUpdatePanelButtons( 0, 0, FALSE );
3527 	}
3528 }
3529 
3530 
RenderSoldierSmallFaceForUpdatePanel(INT32 iIndex,INT32 iX,INT32 iY)3531 static void RenderSoldierSmallFaceForUpdatePanel(INT32 iIndex, INT32 iX, INT32 iY)
3532 {
3533 	INT32 iStartY = 0;
3534 	SOLDIERTYPE *pSoldier = NULL;
3535 
3536 	// fill the background for the info bars black
3537 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iY+2, iX+44,	iY+30, 0 );
3538 
3539 	// put down the background
3540 	BltVideoObject(guiSAVEBUFFER, giMercPanelImage, 0, iX, iY);
3541 
3542 	// grab the face
3543 	BltVideoObject(guiSAVEBUFFER, giUpdateSoldierFaces[iIndex], 0, iX + 2, iY + 2);
3544 
3545 	//HEALTH BAR
3546 	pSoldier = pUpdateSoldierBox[ iIndex ];
3547 
3548 	// is the merc alive?
3549 	if( !pSoldier->bLife )
3550 		return;
3551 
3552 
3553 	//yellow one for bleeding
3554 	iStartY = iY + 29 - 27*pSoldier->bLifeMax/100;
3555 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 107, 107, 57 ) ) );
3556 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 222, 181, 115 ) ) );
3557 
3558 	//pink one for bandaged.
3559 	iStartY += 27*pSoldier->bBleeding/100;
3560 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 156, 57, 57 ) ) );
3561 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 222, 132, 132 ) ) );
3562 
3563 	//red one for actual health
3564 	iStartY = iY + 29 - 27*pSoldier->bLife/100;
3565 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 107, 8, 8 ) ) );
3566 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 206, 0, 0 ) ) );
3567 
3568 	//BREATH BAR
3569 	iStartY = iY + 29 - 27*pSoldier->bBreathMax/100;
3570 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+39, iStartY, iX+40, iY+29, Get16BPPColor( FROMRGB( 8, 8, 132 ) ) );
3571 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+40, iStartY, iX+41, iY+29, Get16BPPColor( FROMRGB( 8, 8, 107 ) ) );
3572 
3573 	//MORALE BAR
3574 	iStartY = iY + 29 - 27*pSoldier->bMorale/100;
3575 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+42, iStartY, iX+43, iY+29, Get16BPPColor( FROMRGB( 8, 156, 8 ) ) );
3576 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+43, iStartY, iX+44, iY+29, Get16BPPColor( FROMRGB( 8, 107, 8 ) ) );
3577 }
3578 
3579 
ContinueUpdateButtonCallback(GUI_BUTTON * btn,INT32 reason)3580 static void ContinueUpdateButtonCallback(GUI_BUTTON *btn, INT32 reason)
3581 {
3582 	if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
3583 	{
3584 		EndUpdateBox(TRUE); // restart time compression
3585 	}
3586 }
3587 
3588 
StopUpdateButtonCallback(GUI_BUTTON * btn,INT32 reason)3589 static void StopUpdateButtonCallback(GUI_BUTTON *btn, INT32 reason)
3590 {
3591 	if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
3592 	{
3593 		EndUpdateBox(FALSE); // stop time compression
3594 	}
3595 }
3596 
3597 
EndUpdateBox(BOOLEAN fContinueTimeCompression)3598 void EndUpdateBox( BOOLEAN fContinueTimeCompression )
3599 {
3600 	fShowUpdateBox = FALSE;
3601 
3602 	CreateDestroyTheUpdateBox();
3603 
3604 	if ( fContinueTimeCompression )
3605 	{
3606 		StartTimeCompression();
3607 	}
3608 	else
3609 	{
3610 		StopTimeCompression();
3611 	}
3612 }
3613 
InitTimersForMoveMenuMouseRegions()3614 void InitTimersForMoveMenuMouseRegions()
3615 {
3616 	FOR_EACH(INT32, i, giDblClickTimersForMoveBoxMouseRegions) *i = 0;
3617 }
3618 
3619 
UpdateHelpTextForMapScreenMercIcons()3620 void UpdateHelpTextForMapScreenMercIcons()
3621 {
3622 	const SOLDIERTYPE* const s = GetSelectedInfoChar();
3623 
3624 	// if merc is an AIM merc
3625 	ST::string contract = s && s->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC ?
3626 		zMarksMapScreenText[21] : ST::null;
3627 	gContractIconRegion.SetFastHelpText(contract);
3628 
3629 	// if merc has life insurance
3630 	ST::string insurance = s && s->usLifeInsurance > 0 ?
3631 		zMarksMapScreenText[3] : ST::null;
3632 	gInsuranceIconRegion.SetFastHelpText(insurance);
3633 
3634 	// if merc has a medical deposit
3635 	ST::string medical = s && s->usMedicalDeposit > 0 ?
3636 		zMarksMapScreenText[12] : ST::null;
3637 	gDepositIconRegion.SetFastHelpText(medical);
3638 }
3639 
3640 
CreateDestroyInsuranceMouseRegionForMercs(BOOLEAN fCreate)3641 void CreateDestroyInsuranceMouseRegionForMercs( BOOLEAN fCreate )
3642 {
3643 	static BOOLEAN fCreated = FALSE;
3644 
3645 	if (!fCreated && fCreate)
3646 	{
3647 		MSYS_DefineRegion( &gContractIconRegion, CHAR_ICON_X, CHAR_ICON_CONTRACT_Y, CHAR_ICON_X + CHAR_ICON_WIDTH, CHAR_ICON_CONTRACT_Y + CHAR_ICON_HEIGHT,
3648 					MSYS_PRIORITY_HIGH - 1, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK );
3649 
3650 		MSYS_DefineRegion( &gInsuranceIconRegion, CHAR_ICON_X, CHAR_ICON_CONTRACT_Y + CHAR_ICON_SPACING, CHAR_ICON_X + CHAR_ICON_WIDTH, CHAR_ICON_CONTRACT_Y + CHAR_ICON_SPACING + CHAR_ICON_HEIGHT,
3651 					MSYS_PRIORITY_HIGH - 1, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK );
3652 
3653 		MSYS_DefineRegion( &gDepositIconRegion, CHAR_ICON_X, CHAR_ICON_CONTRACT_Y + ( 2 * CHAR_ICON_SPACING ), CHAR_ICON_X + CHAR_ICON_WIDTH, CHAR_ICON_CONTRACT_Y + ( 2 * CHAR_ICON_SPACING ) + CHAR_ICON_HEIGHT,
3654 					MSYS_PRIORITY_HIGH - 1, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK );
3655 
3656 		fCreated = TRUE;
3657 	}
3658 	else if (fCreated && !fCreate)
3659 	{
3660 		MSYS_RemoveRegion( &gContractIconRegion );
3661 		MSYS_RemoveRegion( &gInsuranceIconRegion );
3662 		MSYS_RemoveRegion( &gDepositIconRegion );
3663 		fCreated = FALSE;
3664 	}
3665 }
3666 
3667 
HandleTimeCompressWithTeamJackedInAndGearedToGo(void)3668 BOOLEAN HandleTimeCompressWithTeamJackedInAndGearedToGo( void )
3669 {
3670 	// check a team is ready to go
3671 	if( !( AnyMercsHired( ) ) )
3672 	{
3673 		// no mercs, leave
3674 		return( FALSE );
3675 	}
3676 
3677 	// make sure the game just started
3678 	if (!DidGameJustStart()) return FALSE;
3679 
3680 	// Select starting sector.
3681 	ChangeSelectedMapSector(SECTORX(START_SECTOR), SECTORY(START_SECTOR), 0);
3682 
3683 	// load starting sector
3684 	try
3685 	{
3686 		SetCurrentWorldSector(SECTORX(START_SECTOR), SECTORY(START_SECTOR), 0);
3687 	}
3688 	catch (...) /* XXX exception should probably propagate; caller ignores return value */
3689 	{
3690 		return FALSE;
3691 	}
3692 
3693 	//Setup variables in the PBI for this first battle.  We need to support the
3694 	//non-persistant PBI in case the user goes to mapscreen.
3695 	gfBlitBattleSectorLocator = TRUE;
3696 	gubPBSectorX = SECTORX(START_SECTOR);
3697 	gubPBSectorY = SECTORY(START_SECTOR);
3698 	gubPBSectorZ = 0;
3699 	gubEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE;
3700 
3701 	InitHelicopterEntranceByMercs( );
3702 
3703 	FadeInGameScreen( );
3704 
3705 	SetUpShutDownMapScreenHelpTextScreenMask( );
3706 
3707 	// Add e-mail message
3708 	AddEmail(ENRICO_CONGRATS,ENRICO_CONGRATS_LENGTH,MAIL_ENRICO, GetWorldTotalMin() );
3709 
3710 
3711 	return( TRUE );
3712 }
3713 
3714 
NotifyPlayerWhenEnemyTakesControlOfImportantSector(INT16 const x,INT16 const y,INT8 const z)3715 void NotifyPlayerWhenEnemyTakesControlOfImportantSector(INT16 const x, INT16 const y, INT8 const z)
3716 {
3717 	// There is nothing important to player below ground
3718 	if (z != 0) return;
3719 
3720 	ST::string sector_desc = GetSectorIDString(x, y, z, TRUE);
3721 
3722 	ST::string buf;
3723 	if (IsThisSectorASAMSector(x, y, z))
3724 	{
3725 		buf = st_format_printf(pMapErrorString[15], sector_desc);
3726 	}
3727 	else
3728 	{
3729 		UINT8 const sector  = SECTOR(x, y);
3730 		INT8  const mine_id = GetMineIndexForSector(sector);
3731 		if (mine_id != -1                           &&
3732 				GetMaxDailyRemovalFromMine(mine_id) > 0 &&
3733 				SpokenToHeadMiner(mine_id))
3734 		{ // There is a mine and it was producing for us
3735 			// Get how much we now will get from the mines
3736 			INT32 const income = GetProjectedTotalDailyIncome();
3737 			ST::string income_string = SPrintMoney(income);
3738 			buf = st_format_printf(pMapErrorString[16], sector_desc, income_string);
3739 		}
3740 		else
3741 		{
3742 			INT8 const town_id = GetTownIdForSector(sector);
3743 			if (town_id == BLANK_SECTOR) return;
3744 			// San Mona isn't important
3745 			if (town_id == SAN_MONA) return;
3746 
3747 			buf = st_format_printf(pMapErrorString[25], sector_desc);
3748 		}
3749 	}
3750 	DoScreenIndependantMessageBox(buf, MSG_BOX_FLAG_OK, MapScreenDefaultOkBoxCallback);
3751 }
3752 
3753 
NotifyPlayerOfInvasionByEnemyForces(INT16 const x,INT16 const y,INT8 const z,MSGBOX_CALLBACK const return_callback)3754 void NotifyPlayerOfInvasionByEnemyForces(INT16 const x, INT16 const y, INT8 const z, MSGBOX_CALLBACK const return_callback)
3755 {
3756 	if (z != 0) return;
3757 
3758 	// If enemy controlled anyways, leave
3759 	if (StrategicMap[CALCULATE_STRATEGIC_INDEX(x, y)].fEnemyControlled) return;
3760 
3761 	ST::string buf;
3762 	ST::string sector_desc;
3763 
3764 	// check if SAM site here
3765 	if (IsThisSectorASAMSector(x, y, z))
3766 	{
3767 		sector_desc = GetShortSectorString(x, y);
3768 		buf = st_format_printf(pMapErrorString[22], sector_desc);
3769 		DoScreenIndependantMessageBox(buf, MSG_BOX_FLAG_OK, return_callback);
3770 	}
3771 	else if (GetTownIdForSector(SECTOR(x, y)) != BLANK_SECTOR)
3772 	{
3773 		sector_desc = GetSectorIDString(x, y, z, TRUE);
3774 		buf = st_format_printf(pMapErrorString[23], sector_desc);
3775 		DoScreenIndependantMessageBox(buf, MSG_BOX_FLAG_OK, return_callback);
3776 	}
3777 	else
3778 	{
3779 		sector_desc = GetShortSectorString(x, y);
3780 		buf = st_format_printf(pMapErrorString[24], sector_desc);
3781 		ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, buf);
3782 	}
3783 }
3784 
3785 
CanCharacterMoveInStrategic(SOLDIERTYPE & s)3786 static MoveError CanCharacterMoveInStrategic(SOLDIERTYPE& s)
3787 {
3788 	Assert(s.bActive);
3789 
3790 	/* NOTE: Check for the most permanent conditions first, and the most easily
3791 	 * remedied ones last. In case several cases apply, only the reason found
3792 	 * first will be given, so make it a good one. */
3793 
3794 	// Still in transit?
3795 	if (IsCharacterInTransit(s)) return ME_TRANSIT;
3796 	// A POW?
3797 	if (s.bAssignment == ASSIGNMENT_POW) return ME_POW;
3798 	// Underground? (can't move strategically, must use tactical traversal)
3799 	if (s.bSectorZ != 0) return ME_UNDERGROUND;
3800 
3801 	// Vehicle checks
3802 	if (s.uiStatusFlags & SOLDIER_VEHICLE)
3803 	{
3804 		VEHICLETYPE const& v = GetVehicle(s.bVehicleID);
3805 		// Empty (needs a driver!)?
3806 		if (GetNumberInVehicle(v) == 0) return ME_VEHICLE_EMPTY;
3807 		// Too damaged?
3808 		if (s.bLife < OKLIFE) return ME_VEHICLE_DAMAGED;
3809 		// Out of fuel?
3810 		if (s.sBreathRed == 0) return ME_VEHICLE_NO_GAS;
3811 	}
3812 	else // Non-vehicle
3813 	{
3814 		// Dead?
3815 		if (s.bLife <= 0)
3816 		{
3817 			gsCustomErrorString = st_format_printf(pMapErrorString[35], s.name);
3818 			return ME_CUSTOM;
3819 		}
3820 
3821 		// Too injured?
3822 		if (s.bLife < OKLIFE)
3823 		{
3824 			gsCustomErrorString = st_format_printf(pMapErrorString[33], s.name);
3825 			return ME_CUSTOM;
3826 		}
3827 	}
3828 
3829 	/* If merc is in a particular sector, not somewhere in between, and he's NOT
3830 	 * flying above it all in a working helicopter */
3831 	if (!s.fBetweenSectors && !SoldierAboardAirborneHeli(s))
3832 	{
3833 		// And that sector is loaded
3834 		if (s.sSectorX == gWorldSectorX &&
3835 				s.sSectorY == gWorldSectorY &&
3836 				s.bSectorZ == gbWorldSectorZ)
3837 		{
3838 			// In combat?
3839 			if (gTacticalStatus.uiFlags & INCOMBAT) return ME_COMBAT;
3840 			// Hostile sector?
3841 			if (gTacticalStatus.fEnemyInSector) return ME_ENEMY;
3842 			// Bloodcat ambush?
3843 			if (gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE && HostileBloodcatsPresent()) return ME_COMBAT;
3844 		}
3845 
3846 		// Not necessarily loaded - if there are any hostiles there
3847 		if (NumHostilesInSector(s.sSectorX, s.sSectorY, s.bSectorZ) > 0) return ME_ENEMY;
3848 	}
3849 
3850 	// If in L12 museum, and the museum alarm went off, and Eldin still around
3851 	if (s.sSectorX == 12        &&
3852 			s.sSectorY == MAP_ROW_L &&
3853 			s.bSectorZ == 0         &&
3854 			!s.fBetweenSectors && GetProfile(ELDIN).bMercStatus != MERC_IS_DEAD)
3855 	{
3856 		UINT8	const room = GetRoom(s.sGridNo);
3857 		if (22 <= room && room <= 41)
3858 		{
3859 			CFOR_EACH_IN_TEAM(s, OUR_TEAM)
3860 			{
3861 				if (FindObj(s, CHALICE) != ITEM_NOT_FOUND) return ME_MUSEUM;
3862 			}
3863 		}
3864 	}
3865 
3866 	// On assignment, other than just in a VEHICLE?
3867 	if (s.bAssignment >= ON_DUTY && s.bAssignment != VEHICLE) return ME_BUSY;
3868 
3869 	/* If he's walking/driving, and so tired that he would just stop the group
3870 	 * anyway in the next sector, or already asleep and can't be awakened */
3871 	if (PlayerSoldierTooTiredToTravel(s))
3872 	{
3873 		gsCustomErrorString = st_format_printf(pMapErrorString[43], s.name);
3874 		return ME_CUSTOM;
3875 	}
3876 
3877 	if (AM_A_ROBOT(&s))
3878 	{ // Going alone?
3879 		if ((s.bAssignment == VEHICLE && !IsRobotControllerInVehicle(s.iVehicleId)) ||
3880 				(s.bAssignment  < ON_DUTY && !IsRobotControllerInSquad(s.bAssignment)))
3881 		{
3882 			return ME_ROBOT_ALONE;
3883 		}
3884 	}
3885 	else if (s.ubWhatKindOfMercAmI == MERC_TYPE__EPC) // an Escorted NPC?
3886 	{
3887 		// Going alone?
3888 		if ((s.bAssignment == VEHICLE && GetNumberOfNonEPCsInVehicle(s.iVehicleId) == 0) ||
3889 				(s.bAssignment  < ON_DUTY && NumberOfNonEPCsInSquad(s.bAssignment) == 0))
3890 		{
3891 			MERCPROFILESTRUCT const&       p    = GetProfile(s.ubProfile);
3892 			ST::string text = p.bSex == MALE ? pMapErrorString[6] : pMapErrorString[7];
3893 			gsCustomErrorString = st_format_printf(text, s.name);
3894 			return ME_CUSTOM;
3895 		}
3896 	}
3897 
3898 	// Find out if this particular character can't move for some reason
3899 	bool problem_exists = false;
3900 	switch (s.ubProfile)
3901 	{
3902 		case MARIA:
3903 			// Maria can't move if she's in sector C5
3904 			if (SECTOR(s.sSectorX, s.sSectorY) == SEC_C5) problem_exists = true;
3905 			break;
3906 	}
3907 
3908 	if (problem_exists)
3909 	{ // Inform user this specific merc cannot be moved out of the sector
3910 		gsCustomErrorString = st_format_printf(pMapErrorString[29], s.name);
3911 		return ME_CUSTOM;
3912 	}
3913 
3914 	// Passed all checks - this character may move strategically!
3915 	return ME_OK;
3916 }
3917 
3918 
CanEntireMovementGroupMercIsInMove(SOLDIERTYPE & s)3919 MoveError CanEntireMovementGroupMercIsInMove(SOLDIERTYPE& s)
3920 {
3921 	// First check the requested character himself
3922 	MoveError const ret = CanCharacterMoveInStrategic(s);
3923 	if (ret != ME_OK) return ret; // Failed no point checking anyone else
3924 
3925 	// Now check anybody who would be traveling with him
3926 
3927 	/* Even if group is 0 (not that that should happen, should it?) still loop
3928 	 * through for other mercs selected to move */
3929 	GROUP const* const g = GetSoldierGroup(s);
3930 
3931 	/* If anyone in the merc's group or also selected cannot move for whatever
3932 	 * reason return an error */
3933 	CFOR_EACH_IN_CHAR_LIST(c)
3934 	{
3935 		SOLDIERTYPE& other = *c->merc;
3936 		if (&other == &s) continue; // Skip the same guy we did already
3937 
3938 		/* Is he in the same movement group (i.e. squad), or is he still selected to
3939 			* go with us (legal?) */
3940 		if (GetSoldierGroup(other) != g && !c->selected) continue;
3941 
3942 		MoveError const ret = CanCharacterMoveInStrategic(other);
3943 		// Cannot move, fail, and don't bother checking anyone else, either
3944 		if (ret != ME_OK) return ret;
3945 	}
3946 
3947 	// Everybody can move
3948 	return ME_OK;
3949 }
3950 
3951 
ReportMapScreenMovementError(const INT8 bErrorNumber)3952 void ReportMapScreenMovementError(const INT8 bErrorNumber)
3953 {
3954 	// a customized message?
3955 	ST::string text = (bErrorNumber != ME_CUSTOM ? pMapErrorString[bErrorNumber] : gsCustomErrorString);
3956 	DoMapMessageBox(MSG_BOX_BASIC_STYLE, text, MAP_SCREEN, MSG_BOX_FLAG_OK, MapScreenDefaultOkBoxCallback);
3957 }
3958 
3959 
3960 // we are checking to see if we need to in fact rebuild the characterlist for mapscreen
HandleRebuildingOfMapScreenCharacterList(void)3961 void HandleRebuildingOfMapScreenCharacterList( void )
3962 {
3963 	// check if we need to rebuild the list?
3964 	if( fReBuildCharacterList )
3965 	{
3966 		// do the actual rebuilding
3967 		ReBuildCharactersList( );
3968 
3969 		// reset the flag
3970 		fReBuildCharacterList = FALSE;
3971 	}
3972 }
3973 
3974 
RequestToggleTimeCompression(void)3975 void RequestToggleTimeCompression( void )
3976 {
3977 	if( !IsTimeBeingCompressed( ) )
3978 	{
3979 		StartTimeCompression();
3980 	}
3981 	else	// currently compressing
3982 	{
3983 		StopTimeCompression();
3984 	}
3985 }
3986 
3987 
RequestIncreaseInTimeCompression(void)3988 void RequestIncreaseInTimeCompression( void )
3989 {
3990 	if ( IsTimeBeingCompressed( ) )
3991 	{
3992 		IncreaseGameTimeCompressionRate();
3993 	}
3994 	else
3995 	{
3996 /*
3997 		// start compressing
3998 		StartTimeCompression();
3999 */
4000 		// ARM Change: start over at 5x compression
4001 		SetGameTimeCompressionLevel( TIME_COMPRESS_5MINS );
4002 	}
4003 }
4004 
4005 
4006 
RequestDecreaseInTimeCompression(void)4007 void RequestDecreaseInTimeCompression( void )
4008 {
4009 	if ( IsTimeBeingCompressed( ) )
4010 	{
4011 		DecreaseGameTimeCompressionRate();
4012 	}
4013 	else
4014 	{
4015 		// check that we can
4016 		if ( !AllowedToTimeCompress( ) )
4017 		{
4018 			// not allowed to compress time
4019 			TellPlayerWhyHeCantCompressTime();
4020 			return;
4021 		}
4022 
4023 		// ARM Change: do nothing
4024 /*
4025 		// if compression mode is set, just restart time so player can see it
4026 		if ( giTimeCompressMode > TIME_COMPRESS_X1 )
4027 		{
4028 			StartTimeCompression();
4029 		}
4030 */
4031 	}
4032 }
4033 
4034 
CanSoldierMoveWithVehicleId(const SOLDIERTYPE * const pSoldier,const INT32 iVehicle1Id)4035 static BOOLEAN CanSoldierMoveWithVehicleId(const SOLDIERTYPE* const pSoldier, const INT32 iVehicle1Id)
4036 {
4037 	INT32 iVehicle2Id = -1;
4038 	VEHICLETYPE *pVehicle1, *pVehicle2;
4039 
4040 
4041 	Assert( iVehicle1Id != -1 );
4042 
4043 	// if soldier is IN a vehicle
4044 	if( pSoldier->bAssignment == VEHICLE )
4045 	{
4046 		iVehicle2Id = pSoldier->iVehicleId;
4047 	}
4048 	else
4049 	// if soldier IS a vehicle
4050 	if( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
4051 	{
4052 		iVehicle2Id = pSoldier->bVehicleID;
4053 	}
4054 
4055 
4056 	// if also (in) a vehicle
4057 	if ( iVehicle2Id != -1 )
4058 	{
4059 		// if it's the same vehicle
4060 		if ( iVehicle1Id == iVehicle2Id )
4061 		{
4062 			return( TRUE );
4063 		}
4064 
4065 		// helicopter can't move together with ground vehicles!
4066 		if ( ( iVehicle1Id == iHelicopterVehicleId ) || ( iVehicle2Id == iHelicopterVehicleId ) )
4067 		{
4068 			return( FALSE );
4069 		}
4070 
4071 		pVehicle1 = &( pVehicleList[ iVehicle1Id ] );
4072 		pVehicle2 = &( pVehicleList[ iVehicle2Id ] );
4073 
4074 		// as long as they're in the same location, amd neither is between sectors, different vehicles is also ok
4075 		if( ( pVehicle1->sSectorX == pVehicle2->sSectorX ) &&
4076 			( pVehicle1->sSectorY == pVehicle2->sSectorY ) &&
4077 			( pVehicle1->sSectorZ == pVehicle2->sSectorZ ) &&
4078 			!pVehicle1->fBetweenSectors &&
4079 			!pVehicle2->fBetweenSectors )
4080 		{
4081 			return( TRUE );
4082 		}
4083 	}
4084 
4085 
4086 	// not in/is a vehicle, or in a different vehicle that isn't in the same location
4087 	return ( FALSE );
4088 }
4089 
4090 
SaveLeaveItemList(HWFILE const f)4091 void SaveLeaveItemList(HWFILE const f)
4092 {
4093 	for (INT32 i = 0; i < NUM_LEAVE_LIST_SLOTS; ++i)
4094 	{
4095 		MERC_LEAVE_ITEM const* const head = gpLeaveListHead[i];
4096 		if (head)
4097 		{
4098 			// Save to specify that a node DOES exist
4099 			BOOLEAN const node_exists = TRUE;
4100 			FileWrite(f, &node_exists, sizeof(BOOLEAN));
4101 
4102 			// Save number of items
4103 			UINT32 n_items = 0;
4104 			for (MERC_LEAVE_ITEM const* i = head; i; i = i->pNext)
4105 			{
4106 				++n_items;
4107 			}
4108 			FileWrite(f, &n_items, sizeof(UINT32));
4109 
4110 			for (MERC_LEAVE_ITEM const* i = head; i; i = i->pNext)
4111 			{
4112 				BYTE  data[40];
4113 				DataWriter d{data};
4114 				InjectObject(d, &i->o);
4115 				INJ_SKIP(d, 4)
4116 				Assert(d.getConsumed() == lengthof(data));
4117 
4118 				FileWrite(f, data, sizeof(data));
4119 			}
4120 		}
4121 		else
4122 		{
4123 			// Save to specify that a node DOENST exist
4124 			BOOLEAN const node_exists = FALSE;
4125 			FileWrite(f, &node_exists, sizeof(BOOLEAN));
4126 		}
4127 	}
4128 
4129 	// Save the leave list profile IDs
4130 	for (INT32 i = 0; i < NUM_LEAVE_LIST_SLOTS; ++i)
4131 	{
4132 		FileWrite(f, &guiLeaveListOwnerProfileId[i], sizeof(UINT32));
4133 	}
4134 }
4135 
4136 
LoadLeaveItemList(HWFILE const f)4137 void LoadLeaveItemList(HWFILE const f)
4138 {
4139 	ShutDownLeaveList();
4140 	InitLeaveList();
4141 
4142 	for (INT32 i = 0; i < NUM_LEAVE_LIST_SLOTS; ++i)
4143 	{
4144 		// Load flag which specifies whether a node exists
4145 		BOOLEAN	node_exists;
4146 		FileRead(f, &node_exists, sizeof(BOOLEAN));
4147 		if (!node_exists) continue;
4148 
4149 		// Load the number specifing how many items there are in the list
4150 		UINT32 n_items;
4151 		FileRead(f, &n_items, sizeof(UINT32));
4152 
4153 		MERC_LEAVE_ITEM** anchor = &gpLeaveListHead[i];
4154 		for (UINT32 n = n_items; n != 0; --n)
4155 		{
4156 			MERC_LEAVE_ITEM* const li = new MERC_LEAVE_ITEM{};
4157 
4158 			BYTE  data[40];
4159 			FileRead(f, data, sizeof(data));
4160 
4161 			DataReader d{data};
4162 			ExtractObject(d, &li->o);
4163 			EXTR_SKIP(d, 4)
4164 			Assert(d.getConsumed() == lengthof(data));
4165 
4166 			// Append node to list
4167 			*anchor = li;
4168 			anchor  = &li->pNext;
4169 		}
4170 	}
4171 
4172 	// Load the leave list profile IDs
4173 	for (INT32 i = 0; i < NUM_LEAVE_LIST_SLOTS; ++i)
4174 	{
4175 		FileRead(f, &guiLeaveListOwnerProfileId[i], sizeof(UINT32));
4176 	}
4177 }
4178 
4179 
TurnOnSectorLocator(UINT8 ubProfileID)4180 void TurnOnSectorLocator( UINT8 ubProfileID )
4181 {
4182 	Assert( ubProfileID != NO_PROFILE );
4183 
4184 	const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubProfileID);
4185 	if( pSoldier )
4186 	{
4187 		gsSectorLocatorX = pSoldier->sSectorX;
4188 		gsSectorLocatorY = pSoldier->sSectorY;
4189 	}
4190 	else
4191 	{
4192 		// if it's Skyrider (when he's not on our team), and his chopper has been setup
4193 		if ( ( ubProfileID == SKYRIDER ) && fSkyRiderSetUp )
4194 		{
4195 			// if helicopter position is being shown, don't do this, too, cause the helicopter icon is on top and it looks
4196 			// like crap.  I tried moving the heli icon blit to before, but that screws up it's blitting.
4197 			if ( !fShowAircraftFlag )
4198 			{
4199 				// can't use his profile, he's where his chopper is
4200 				VEHICLETYPE const& v = GetHelicopter();
4201 				gsSectorLocatorX = v.sSectorX;
4202 				gsSectorLocatorY = v.sSectorY;
4203 			}
4204 			else
4205 			{
4206 				return;
4207 			}
4208 		}
4209 		else
4210 		{
4211 			gsSectorLocatorX = gMercProfiles[ ubProfileID ].sSectorX;
4212 			gsSectorLocatorY = gMercProfiles[ ubProfileID ].sSectorY;
4213 		}
4214 	}
4215 	gubBlitSectorLocatorCode = LOCATOR_COLOR_YELLOW;
4216 }
4217 
4218 
TurnOffSectorLocator()4219 void TurnOffSectorLocator()
4220 {
4221 	gubBlitSectorLocatorCode = LOCATOR_COLOR_NONE;
4222 	fMapPanelDirty = TRUE;
4223 }
4224 
4225 
4226 
HandleBlitOfSectorLocatorIcon(INT16 sSectorX,INT16 sSectorY,INT16 sSectorZ,UINT8 ubLocatorID)4227 void HandleBlitOfSectorLocatorIcon( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, UINT8 ubLocatorID )
4228 {
4229 	static UINT8  ubFrame = 0;
4230 	UINT8 ubBaseFrame = 0;
4231 	UINT32 uiTimer = 0;
4232 	INT16 sScreenX, sScreenY;
4233 
4234 
4235 	// blits at 0,0 had been observerd...
4236 	Assert( ( sSectorX >= 1 ) && ( sSectorX <= 16 ) );
4237 	Assert( ( sSectorY >= 1 ) && ( sSectorY <= 16 ) );
4238 	Assert( ( sSectorZ >= 0 ) && ( sSectorZ <=  3 ) );
4239 
4240 	if( sSectorZ != iCurrentMapSectorZ )
4241 	{ //if the z level of the map screen renderer is different than the
4242 		//sector z that we wish to locate, then don't render it
4243 		return;
4244 	}
4245 
4246 	// if showing sector inventory, don't do this
4247 	if( fShowMapInventoryPool )
4248 	{
4249 		return;
4250 	}
4251 
4252 	switch( ubLocatorID )
4253 	{
4254 		// grab zoomed out icon
4255 		case LOCATOR_COLOR_RED:
4256 			ubBaseFrame = 0;
4257 			ubFrame = (UINT8)(ubFrame % 13);
4258 			break;
4259 		case LOCATOR_COLOR_YELLOW:
4260 			ubBaseFrame = 13;
4261 			ubFrame = (UINT8)(13 + (ubFrame % 13) );
4262 			break;
4263 		default:
4264 			//not supported
4265 			return;
4266 	}
4267 
4268 	//Convert the sector value into screen values.
4269 	GetScreenXYFromMapXY( sSectorX, sSectorY, &sScreenX, &sScreenY );
4270 	// make sure we are on the border
4271 	if( sScreenX < MAP_GRID_X )
4272 	{
4273 		sScreenX = MAP_GRID_X;
4274 	}
4275 	sScreenY--; //Carterism ritual
4276 	if( sScreenY < MAP_GRID_Y )
4277 	{
4278 		sScreenY = MAP_GRID_Y;
4279 	}
4280 
4281 	uiTimer = GetJA2Clock();
4282 
4283 	// if first time in, reset value
4284 	if( guiSectorLocatorBaseTime == 0 )
4285 	{
4286 		guiSectorLocatorBaseTime = GetJA2Clock( );
4287 	}
4288 
4289 	// check if enough time has passed to update the frame counter
4290 	if( ANIMATED_BATTLEICON_FRAME_TIME < ( uiTimer - guiSectorLocatorBaseTime ) )
4291 	{
4292 		guiSectorLocatorBaseTime = uiTimer;
4293 		ubFrame++;
4294 
4295 		if( ubFrame > ubBaseFrame + MAX_FRAME_COUNT_FOR_ANIMATED_BATTLE_ICON )
4296 		{
4297 			ubFrame = ubBaseFrame;
4298 		}
4299 	}
4300 
4301 	RestoreExternBackgroundRect(  (INT16)(sScreenX + 1), (INT16)(sScreenY - 1),  MAP_GRID_X , MAP_GRID_Y );
4302 
4303 	// blit object to frame buffer
4304 	BltVideoObject(FRAME_BUFFER, guiSectorLocatorGraphicID, ubFrame, sScreenX, sScreenY);
4305 
4306 	// invalidate region on frame buffer
4307 	InvalidateRegion(sScreenX, sScreenY, sScreenX + MAP_GRID_X + 1, sScreenY + MAP_GRID_Y + 1);
4308 }
4309 
4310 
MakeDialogueEventShowContractMenu(SOLDIERTYPE & s)4311 void MakeDialogueEventShowContractMenu(SOLDIERTYPE& s)
4312 {
4313 	class CharacterDialogueEventShowContractMenu : public CharacterDialogueEvent
4314 	{
4315 		public:
4316 			CharacterDialogueEventShowContractMenu(SOLDIERTYPE& s) : CharacterDialogueEvent(s) {}
4317 
4318 			bool Execute()
4319 			{
4320 				if (!MayExecute()) return true;
4321 
4322 				// ATE: THis is working with MARK'S STUFF :(
4323 				// Need this stuff so that bSelectedInfoChar is set...
4324 				SOLDIERTYPE const& s = soldier_;
4325 				SetInfoChar(&s);
4326 				fShowContractMenu = TRUE;
4327 				RebuildContractBoxForMerc(&s);
4328 				bSelectedContractChar = bSelectedInfoChar;
4329 
4330 				return false;
4331 			}
4332 	};
4333 
4334 	DialogueEvent::Add(new CharacterDialogueEventShowContractMenu(s));
4335 }
4336 
4337 
CheckIfSalaryIncreasedAndSayQuote(SOLDIERTYPE * pSoldier,BOOLEAN fTriggerContractMenu)4338 BOOLEAN CheckIfSalaryIncreasedAndSayQuote( SOLDIERTYPE *pSoldier, BOOLEAN fTriggerContractMenu )
4339 {
4340 	Assert( pSoldier );
4341 
4342 	// OK, check if their price has gone up
4343 	if( pSoldier->fContractPriceHasIncreased )
4344 	{
4345 		if ( fTriggerContractMenu )
4346 		{
4347 			// have him say so first - post the dialogue event with the contract menu event
4348 			MakeDialogueEventEnterMapScreen();
4349 			HandleImportantMercQuote( pSoldier, QUOTE_MERC_GONE_UP_IN_PRICE );
4350 			MakeDialogueEventShowContractMenu(*pSoldier);
4351 		}
4352 		else
4353 		{
4354 			// now post the dialogue event and the contratc menu event
4355 			HandleImportantMercQuote( pSoldier, QUOTE_MERC_GONE_UP_IN_PRICE );
4356 		}
4357 
4358 		pSoldier->fContractPriceHasIncreased = FALSE;
4359 
4360 		// said quote / triggered contract menu
4361 		return( TRUE );
4362 	}
4363 	else
4364 	{
4365 		// nope, nothing to do
4366 		return( FALSE );
4367 	}
4368 }
4369 
4370 
LoadMapScreenInterfaceGraphics()4371 void LoadMapScreenInterfaceGraphics()
4372 {
4373 	guiSectorLocatorGraphicID = AddVideoObjectFromFile(INTERFACEDIR "/hilite.sti");
4374 	guiSelectedCharArrow      = AddVideoObjectFromFile(INTERFACEDIR "/selectedchararrow.sti");
4375 }
4376 
4377 
DeleteMapScreenInterfaceGraphics()4378 void DeleteMapScreenInterfaceGraphics()
4379 {
4380 	DeleteVideoObject(guiSectorLocatorGraphicID);
4381 	DeleteVideoObject(guiSelectedCharArrow);
4382 }
4383