1 #include "Assignments.h"
2 #include "Animation_Control.h"
3 #include "Auto_Resolve.h"
4 #include "Button_System.h"
5 #include "Campaign.h"
6 #include "Campaign_Types.h"
7 #include "ContentManager.h"
8 #include "Debug.h"
9 #include "Dialogue_Control.h"
10 #include "Directories.h"
11 #include "Facts.h"
12 #include "Finances.h"
13 #include "Font.h"
14 #include "Font_Control.h"
15 #include "Game_Clock.h"
16 #include "Game_Event_Hook.h"
17 #include "GameInstance.h"
18 #include "GameSettings.h"
19 #include "Handle_UI.h"
20 #include "History.h"
21 #include "Input.h"
22 #include "Interface.h"
23 #include "Interface_Dialogue.h"
24 #include "Isometric_Utils.h"
25 #include "Item_Types.h"
26 #include "ItemModel.h"
27 #include "Items.h"
28 #include "JA2Types.h"
29 #include "JAScreens.h"
30 #include "Logger.h"
31 #include "Map_Information.h"
32 #include "Map_Screen_Helicopter.h"
33 #include "Map_Screen_Interface.h"
34 #include "Map_Screen_Interface_Border.h"
35 #include "Map_Screen_Interface_Bottom.h"
36 #include "Map_Screen_Interface_Map.h"
37 #include "Map_Screen_Interface_Map_Inventory.h"
38 #include "Map_Screen_Interface_TownMine_Info.h"
39 #include "MapScreen.h"
40 #include "Merc_Contract.h"
41 #include "MercProfile.h"
42 #include "Message.h"
43 #include "MessageBoxScreen.h"
44 #include "MouseSystem.h"
45 #include "NPC.h"
46 #include "Overhead.h"
47 #include "Overhead_Types.h"
48 #include "PopUpBox.h"
49 #include "PreBattle_Interface.h"
50 #include "Queen_Command.h"
51 #include "Quests.h"
52 #include "Random.h"
53 #include "RenderWorld.h"
54 #include "SAM_Sites.h"
55 #include "ScreenIDs.h"
56 #include "SGPStrings.h"
57 #include "SkillCheck.h"
58 #include "Soldier_Add.h"
59 #include "Soldier_Control.h"
60 #include "Soldier_Find.h"
61 #include "Soldier_Macros.h"
62 #include "Soldier_Profile.h"
63 #include "Soldier_Profile_Type.h"
64 #include "Squads.h"
65 #include "Strategic_Event_Handler.h"
66 #include "Strategic_Movement.h"
67 #include "Strategic_Movement_Costs.h"
68 #include "Strategic_Status.h"
69 #include "Strategic_Town_Loyalty.h"
70 #include "StrategicMap.h"
71 #include "Text.h"
72 #include "Town_Militia.h"
73 #include "UILayout.h"
74 #include "Vehicles.h"
75 #include "VObject.h"
76 #include "VSurface.h"
77 #include <algorithm>
78 #include <iterator>
79 #include <string_theory/format>
80 #include <string_theory/string>
81 struct PopUpBox;
82 
83 
84 // various reason an assignment can be aborted before completion
85 enum AssignmentAbortReason
86 {
87 	NO_MORE_MED_KITS   = STR_LATE_40,
88 	INSUF_DOCTOR_SKILL = STR_LATE_41,
89 	NO_MORE_TOOL_KITS  = STR_LATE_42,
90 	INSUF_REPAIR_SKILL = STR_LATE_43,
91 
92 	NUM_ASSIGN_ABORT_REASONS
93 };
94 
95 enum{
96 	REPAIR_MENU_VEHICLE1 = 0,
97 	REPAIR_MENU_VEHICLE2,
98 	REPAIR_MENU_VEHICLE3,
99 	REPAIR_MENU_ROBOT,
100 	REPAIR_MENU_ITEMS,
101 	REPAIR_MENU_CANCEL,
102 };
103 
104 
105 enum {
106 	REPAIR_HANDS_AND_ARMOR = 0,
107 	REPAIR_HEADGEAR,
108 	REPAIR_POCKETS,
109 	NUM_REPAIR_PASS_TYPES,
110 };
111 
112 #define FINAL_REPAIR_PASS			REPAIR_POCKETS
113 
114 
115 struct REPAIR_PASS_SLOTS_TYPE
116 {
117 	UINT8		ubChoices;						// how many valid choices there are in this pass
118 	INT8		bSlot[ 12 ];					// list of slots to be repaired in this pass
119 };
120 
121 
122 REPAIR_PASS_SLOTS_TYPE gRepairPassSlotList[ NUM_REPAIR_PASS_TYPES ] =
123 {					// pass					# choices												slots repaired in this pass
124 	{ /* hands and armor */  5, { HANDPOS, SECONDHANDPOS, VESTPOS, HELMETPOS, LEGPOS, -1, -1, -1, -1, -1, -1, -1 } },
125 	{ /* headgear */         2, { HEAD1POS, HEAD2POS, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } },
126 	{ /* pockets */         12, { BIGPOCK1POS, BIGPOCK2POS, BIGPOCK3POS, BIGPOCK4POS, SMALLPOCK1POS, SMALLPOCK2POS, SMALLPOCK3POS, SMALLPOCK4POS, SMALLPOCK5POS, SMALLPOCK6POS, SMALLPOCK7POS, SMALLPOCK8POS } }
127 };
128 
129 
130 // PopUp Box Handles
131 PopUpBox* ghAssignmentBox;
132 PopUpBox* ghEpcBox;
133 PopUpBox* ghSquadBox;
134 static PopUpBox* ghVehicleBox;
135 PopUpBox* ghRepairBox;
136 PopUpBox* ghTrainingBox;
137 PopUpBox* ghAttributeBox;
138 PopUpBox* ghRemoveMercAssignBox;
139 PopUpBox* ghContractBox;
140 PopUpBox* ghMoveBox;
141 
142 // the x,y position of assignment pop up in tactical
143 static INT16 gsAssignmentBoxesX;
144 static INT16 gsAssignmentBoxesY;
145 
146 
147 // assignment menu mouse regions
148 static MOUSE_REGION gAssignmentMenuRegion[MAX_ASSIGN_STRING_COUNT];
149 static MOUSE_REGION gTrainingMenuRegion[MAX_TRAIN_STRING_COUNT];
150 static MOUSE_REGION gAttributeMenuRegion[MAX_ATTRIBUTE_STRING_COUNT];
151 static MOUSE_REGION gSquadMenuRegion[MAX_SQUAD_MENU_STRING_COUNT];
152 static MOUSE_REGION gContractMenuRegion[MAX_CONTRACT_MENU_STRING_COUNT];
153 static MOUSE_REGION gRemoveMercAssignRegion[MAX_REMOVE_MERC_COUNT];
154 static MOUSE_REGION gRepairMenuRegion[20];
155 
156 // mouse region for vehicle menu
157 static MOUSE_REGION gVehicleMenuRegion[20];
158 
159 static MOUSE_REGION gAssignmentScreenMaskRegion;
160 
161 BOOLEAN fShownAssignmentMenu    = FALSE;
162 static BOOLEAN fShowVehicleMenu = FALSE;
163 BOOLEAN fShowRepairMenu         = FALSE;
164 BOOLEAN fShownContractMenu      = FALSE;
165 
166 BOOLEAN fFirstClickInAssignmentScreenMask = FALSE;
167 
168 // we are in fact training?..then who temmates, or self?
169 static INT8 gbTrainingMode = -1;
170 
171 static BOOLEAN gfAddDisplayBoxToWaitingQueue = FALSE;
172 
173 static SOLDIERTYPE* gpDismissSoldier = NULL;
174 
175 BOOLEAN gfReEvaluateEveryonesNothingToDo = FALSE;
176 
177 
178 // the amount time must be on assignment before it can have any effect
179 #define MINUTES_FOR_ASSIGNMENT_TO_COUNT	45
180 
181 // number we divide the total pts accumlated per day by for each assignment period
182 #define ASSIGNMENT_UNITS_PER_DAY 24
183 
184 // base skill to deal with an emergency
185 #define BASE_MEDICAL_SKILL_TO_DEAL_WITH_EMERGENCY 20
186 
187 // multiplier for skill needed for each point below OKLIFE
188 #define MULTIPLIER_FOR_DIFFERENCE_IN_LIFE_VALUE_FOR_EMERGENCY 4
189 
190 // number of pts needed for each point below OKLIFE
191 #define POINT_COST_PER_HEALTH_BELOW_OKLIFE 2
192 
193 // how many points of healing each hospital patients gains per hour in the hospital
194 #define HOSPITAL_HEALING_RATE		5				// a top merc doctor can heal about 4 pts/hour maximum, but that's spread among patients!
195 
196 // increase to reduce repair pts, or vice versa
197 #define REPAIR_RATE_DIVISOR 2500
198 // increase to reduce doctoring pts, or vice versa
199 #define DOCTORING_RATE_DIVISOR 2400				// at 2400, the theoretical maximum is 150 full healing pts/day
200 
201 // cost to unjam a weapon in repair pts
202 #define REPAIR_COST_PER_JAM 2
203 
204 // divisor for rate of self-training
205 #define SELF_TRAINING_DIVISOR				1000
206 // the divisor for rate of training bonus due to instructors influence
207 #define INSTRUCTED_TRAINING_DIVISOR 3000
208 
209 // this controls how fast town militia gets trained
210 #define TOWN_TRAINING_RATE	4
211 
212 #define MAX_MILITIA_TRAINERS_PER_SECTOR 2
213 
214 // militia training bonus for EACH level of teaching skill (percentage points)
215 #define TEACH_BONUS_TO_TRAIN 30
216 // militia training bonus for RPC (percentage points)
217 #define RPC_BONUS_TO_TRAIN   10
218 
219 // the bonus to training in marksmanship in the Alma gun range sector
220 #define GUN_RANGE_TRAINING_BONUS	25
221 
222 // breath bonus divider
223 #define BREATH_BONUS_DIVIDER 10
224 
225 // the min rating that is need to teach a fellow teammate
226 #define MIN_RATING_TO_TEACH 25
227 
228 // activity levels for natural healing ( the higher the number, the slower the natural recover rate
229 #define LOW_ACTIVITY_LEVEL      1
230 #define MEDIUM_ACTIVITY_LEVEL   4
231 #define HIGH_ACTIVITY_LEVEL			12
232 
233 /* Assignment distance limits removed.  Sep/11/98.  ARM
234 #define MAX_DISTANCE_FOR_DOCTORING	5
235 #define MAX_DISTANCE_FOR_REPAIR			5
236 #define MAX_DISTANCE_FOR_TRAINING		5
237 */
238 
239 // a list of which sectors have characters
240 static BOOLEAN fSectorsWithSoldiers[MAP_WORLD_X * MAP_WORLD_Y][4];
241 
242 
InitSectorsWithSoldiersList(void)243 void InitSectorsWithSoldiersList( void )
244 {
245 	// init list of sectors
246 	for (auto& i : fSectorsWithSoldiers)
247 	{
248 		std::fill(std::begin(i), std::end(i), 0);
249 	}
250 }
251 
252 
BuildSectorsWithSoldiersList(void)253 void BuildSectorsWithSoldiersList( void )
254 {
255 	// fills array with pressence of player controlled characters
256 	CFOR_EACH_IN_TEAM(s, OUR_TEAM)
257 	{
258 		fSectorsWithSoldiers[s->sSectorX + s->sSectorY * MAP_WORLD_X][s->bSectorZ] = TRUE;
259 	}
260 }
261 
262 
ChangeSoldiersAssignment(SOLDIERTYPE * pSoldier,INT8 bAssignment)263 void ChangeSoldiersAssignment( SOLDIERTYPE *pSoldier, INT8 bAssignment )
264 {
265 	// This is the most basic assignment-setting function.  It must be called before setting any subsidiary
266 	// values like fFixingRobot.  It will clear all subsidiary values so we don't leave the merc in a messed
267 	// up state!
268 
269 	pSoldier->bAssignment = bAssignment;
270 /// don't kill iVehicleId, though, 'cause militia training tries to put guys back in their vehicles when it's done(!)
271 
272 	if (bAssignment == ASSIGNMENT_DEAD)
273 	{
274 		// life checks should agree with the assignment
275 		pSoldier->bLife = 0;
276 	}
277 	pSoldier->fFixingRobot = FALSE;
278 	pSoldier->bVehicleUnderRepairID = -1;
279 
280 	if ( ( bAssignment == DOCTOR ) || ( bAssignment == PATIENT ) || ( bAssignment == ASSIGNMENT_HOSPITAL ) )
281 	{
282 		AddStrategicEvent( EVENT_BANDAGE_BLEEDING_MERCS, GetWorldTotalMin() + 1, 0 );
283 	}
284 
285 	// update character info, and the team panel
286 	fCharacterInfoPanelDirty = TRUE;
287 	fTeamPanelDirty = TRUE;
288 
289 	// merc may have come on/off duty, make sure map icons are updated
290 	fMapPanelDirty = TRUE;
291 }
292 
293 
IsSoldierInHelicopterInHostileSector(SOLDIERTYPE const & s)294 static BOOLEAN IsSoldierInHelicopterInHostileSector(SOLDIERTYPE const& s)
295 {
296 	return InHelicopter(s) && NumEnemiesInSector(s.sSectorX, s.sSectorY) > 0;
297 }
298 
299 
300 /* Which conditions are allowed to perform an assignment? */
301 enum AssignmentConditions
302 {
303 	AC_NONE                      = 0,
304 	AC_IMPASSABLE                = 1U << 0,
305 	AC_UNCONSCIOUS               = 1U << 1,
306 	AC_COMBAT                    = 1U << 2,
307 	AC_EPC                       = 1U << 3,
308 	AC_IN_HELI_IN_HOSTILE_SECTOR = 1U << 4,
309 	AC_MECHANICAL                = 1U << 5,
310 	AC_MOVING                    = 1U << 6,
311 	AC_UNDERGROUND               = 1U << 7
312 };
ENUM_BITSET(AssignmentConditions)313 ENUM_BITSET(AssignmentConditions)
314 
315 
316 static bool AreAssignmentConditionsMet(SOLDIERTYPE const& s, AssignmentConditions const c)
317 {
318 	return
319 		(c & AC_IMPASSABLE  || SectorIsPassable(SECTOR(s.sSectorX, s.sSectorY)))       &&
320 		(c & AC_UNCONSCIOUS || s.bLife >= OKLIFE)                                      &&
321 		(c & AC_COMBAT      || !s.bInSector || !gTacticalStatus.fEnemyInSector)        &&
322 		(c & AC_EPC         || s.ubWhatKindOfMercAmI != MERC_TYPE__EPC)                &&
323 		(c & AC_IN_HELI_IN_HOSTILE_SECTOR || !IsSoldierInHelicopterInHostileSector(s)) &&
324 		(c & AC_MECHANICAL  || !IsMechanical(s))                                       &&
325 		(c & AC_MOVING      || !s.fBetweenSectors)                                     &&
326 		(c & AC_UNDERGROUND || s.bSectorZ == 0)                                        &&
327 		!IsCharacterInTransit(s)                                                       &&
328 		s.bAssignment != ASSIGNMENT_POW;
329 }
330 
331 
BasicCanCharacterDoctor(const SOLDIERTYPE * const s)332 static BOOLEAN BasicCanCharacterDoctor(const SOLDIERTYPE* const s)
333 {
334 	return
335 		s->bMedical > 0 &&
336 		AreAssignmentConditionsMet(*s, AC_UNDERGROUND);
337 }
338 
339 
340 // is character capable of 'playing' doctor?
341 // check that character is alive, conscious, has medical skill and equipment
CanCharacterDoctor(SOLDIERTYPE const * const pSoldier)342 static BOOLEAN CanCharacterDoctor(SOLDIERTYPE const* const pSoldier)
343 {
344 	INT8 bPocket = 0;
345 
346 	if (!BasicCanCharacterDoctor(pSoldier)) return FALSE;
347 
348 	// find med kit
349 	for (bPocket = HANDPOS; bPocket <= SMALLPOCK8POS; bPocket++)
350 	{
351 		if (IsMedicalKitItem(&pSoldier->inv[bPocket]))
352 		{
353 			return TRUE;
354 		}
355 	}
356 
357 	return FALSE;
358 }
359 
360 
361 static BOOLEAN CanCharacterRepairRobot(SOLDIERTYPE const*);
362 static bool CanCharacterRepairVehicle(SOLDIERTYPE const&, VEHICLETYPE const&);
363 static BOOLEAN DoesCharacterHaveAnyItemsToRepair(SOLDIERTYPE const*, INT8 bHighestPass);
364 
365 
IsAnythingAroundForSoldierToRepair(SOLDIERTYPE const & s)366 static bool IsAnythingAroundForSoldierToRepair(SOLDIERTYPE const& s)
367 {
368 	// Items?
369 	if (DoesCharacterHaveAnyItemsToRepair(&s, FINAL_REPAIR_PASS)) return true;
370 
371 	// Robot?
372 	if (CanCharacterRepairRobot(&s)) return true;
373 
374 	// Vehicles?
375 	if (s.bSectorZ == 0)
376 	{
377 		CFOR_EACH_VEHICLE(v)
378 		{
379 			// The helicopter, is NEVER repairable
380 			if (IsHelicopter(v))                         continue;
381 			if (!IsThisVehicleAccessibleToSoldier(s, v)) continue;
382 			if (!CanCharacterRepairVehicle(s, v))        continue;
383 			// There is a repairable vehicle here
384 			return true;
385 		}
386 	}
387 
388 	return false;
389 }
390 
391 
HasCharacterFinishedRepairing(SOLDIERTYPE * pSoldier)392 static BOOLEAN HasCharacterFinishedRepairing(SOLDIERTYPE* pSoldier)
393 {
394 	BOOLEAN fCanStillRepair;
395 
396 	// NOTE: This must detect situations where the vehicle/robot has left the sector, in which case we want the
397 	// guy to say "assignment done", so we return that he can no longer repair
398 
399 	// check if we are repairing a vehicle
400 	if ( pSoldier->bVehicleUnderRepairID != -1 )
401 	{
402 		fCanStillRepair = CanCharacterRepairVehicle(*pSoldier, GetVehicle(pSoldier->bVehicleUnderRepairID));
403 	}
404 	// check if we are repairing a robot
405 	else if( pSoldier -> fFixingRobot )
406 	{
407 		fCanStillRepair = CanCharacterRepairRobot( pSoldier );
408 	}
409 	else	// repairing items
410 	{
411 		fCanStillRepair = DoesCharacterHaveAnyItemsToRepair( pSoldier, FINAL_REPAIR_PASS );
412 	}
413 
414 	// if it's no longer damaged, we're finished!
415 	return( !fCanStillRepair );
416 }
417 
418 
419 static BOOLEAN CanCharacterRepairAnotherSoldiersStuff(const SOLDIERTYPE* pSoldier, const SOLDIERTYPE* pOtherSoldier);
420 static INT8 FindRepairableItemOnOtherSoldier(const SOLDIERTYPE* pSoldier, UINT8 ubPassType);
421 static bool IsItemRepairable(UINT16 item_id, INT8 status);
422 
423 
DoesCharacterHaveAnyItemsToRepair(SOLDIERTYPE const * const pSoldier,INT8 const bHighestPass)424 static BOOLEAN DoesCharacterHaveAnyItemsToRepair(SOLDIERTYPE const* const pSoldier, INT8 const bHighestPass)
425 {
426 	UINT8	ubItemsInPocket, ubObjectInPocketCounter;
427 	UINT8 ubPassType;
428 
429 	// check for jams
430 	CFOR_EACH_SOLDIER_INV_SLOT(i, *pSoldier)
431 	{
432 		ubItemsInPocket = i->ubNumberOfObjects;
433 		// unjam any jammed weapons
434 		// run through pocket and repair
435 		for( ubObjectInPocketCounter = 0; ubObjectInPocketCounter < ubItemsInPocket; ubObjectInPocketCounter++ )
436 		{
437 			// jammed gun?
438 			if (GCM->getItem(i->usItem)->getItemClass() == IC_GUN && i->bGunAmmoStatus < 0)
439 			{
440 				return( TRUE );
441 			}
442 		}
443 	}
444 
445 	// now check for items to repair
446 	CFOR_EACH_SOLDIER_INV_SLOT(i, *pSoldier)
447 	{
448 		OBJECTTYPE const& o = *i;
449 
450 		ubItemsInPocket = o.ubNumberOfObjects;
451 
452 		// run through pocket
453 		for( ubObjectInPocketCounter = 0; ubObjectInPocketCounter < ubItemsInPocket; ubObjectInPocketCounter++ )
454 		{
455 			// if it's repairable and NEEDS repairing
456 			if (IsItemRepairable(o.usItem, o.bStatus[ubObjectInPocketCounter]))
457 			{
458 				return( TRUE );
459 			}
460 		}
461 
462 		// have to check for attachments...
463 		for (INT8 bLoop = 0; bLoop < MAX_ATTACHMENTS; ++bLoop)
464 		{
465 			if (o.usAttachItem[bLoop] != NOTHING)
466 			{
467 				// if it's repairable and NEEDS repairing
468 				if (IsItemRepairable(o.usAttachItem[bLoop], o.bAttachStatus[bLoop]))
469 				{
470 					return( TRUE );
471 				}
472 			}
473 		}
474 	}
475 
476 
477 	// if we wanna check for the items belonging to others in the sector
478 	if ( bHighestPass != - 1 )
479 	{
480 		// now look for items to repair on other mercs
481 		CFOR_EACH_IN_TEAM(pOtherSoldier, OUR_TEAM)
482 		{
483 			if ( CanCharacterRepairAnotherSoldiersStuff( pSoldier, pOtherSoldier ) )
484 			{
485 				// okay, seems like a candidate!  Check if he has anything that needs unjamming or repairs
486 
487 				for (INT8 bPocket = HANDPOS; bPocket <= SMALLPOCK8POS; ++bPocket)
488 				{
489 					// the object a weapon? and jammed?
490 					if ( ( GCM->getItem(pOtherSoldier->inv[ bPocket ].usItem)->getItemClass() == IC_GUN ) && ( pOtherSoldier->inv[ bPocket ].bGunAmmoStatus < 0 ) )
491 					{
492 						return( TRUE );
493 					}
494 				}
495 
496 				// repair everyone's hands and armor slots first, then headgear, and pockets last
497 				for ( ubPassType = REPAIR_HANDS_AND_ARMOR; ubPassType <= ( UINT8 ) bHighestPass; ubPassType++ )
498 				{
499 					INT8 const bPocket = FindRepairableItemOnOtherSoldier(pOtherSoldier, ubPassType);
500 					if ( bPocket != NO_SLOT )
501 					{
502 						return( TRUE );
503 					}
504 				}
505 			}
506 		}
507 	}
508 
509 	return( FALSE );
510 }
511 
512 
BasicCanCharacterRepair(const SOLDIERTYPE * const s)513 static BOOLEAN BasicCanCharacterRepair(const SOLDIERTYPE* const s)
514 {
515 	return
516 		s->bMechanical > 0 &&
517 		AreAssignmentConditionsMet(*s, AC_UNDERGROUND);
518 }
519 
520 
CanCharacterRepairButDoesntHaveARepairkit(const SOLDIERTYPE * const s)521 static BOOLEAN CanCharacterRepairButDoesntHaveARepairkit(const SOLDIERTYPE* const s)
522 {
523 	return
524 		BasicCanCharacterRepair(s) &&
525 		FindObj(s, TOOLKIT) == NO_SLOT; // make sure he actually doesn't have a toolkit
526 }
527 
528 
529 // can character be assigned as repairman?
530 // check that character is alive, oklife, has repair skill, and equipment, etc.
CanCharacterRepair(SOLDIERTYPE const * const pSoldier)531 static BOOLEAN CanCharacterRepair(SOLDIERTYPE const* const pSoldier)
532 {
533 	if (!BasicCanCharacterRepair(pSoldier)) return FALSE;
534 
535 	// make sure he has a toolkit
536 	if ( FindObj( pSoldier, TOOLKIT ) == NO_SLOT )
537 	{
538 		return( FALSE );
539 	}
540 
541 	// anything around to fix?
542 	if (!IsAnythingAroundForSoldierToRepair(*pSoldier))
543 	{
544 		return( FALSE );
545 	}
546 
547 	// NOTE: This will not detect situations where character lacks the SKILL to repair the stuff that needs repairing...
548 	// So, in that situation, his assignment will NOT flash, but a message to that effect will be reported every hour.
549 
550 	// all criteria fit, can repair
551 	return ( TRUE );
552 }
553 
554 
555 // can character be set to patient?
CanCharacterPatient(const SOLDIERTYPE * const s)556 static BOOLEAN CanCharacterPatient(const SOLDIERTYPE* const s)
557 {
558 	return
559 		s->bLife > 0 &&
560 		s->bLife != s->bLifeMax &&
561 		AreAssignmentConditionsMet(*s, AC_UNCONSCIOUS | AC_EPC | AC_UNDERGROUND);
562 }
563 
564 
CanSectorContainMilita(const INT16 x,const INT16 y,const INT16 z)565 static BOOLEAN CanSectorContainMilita(const INT16 x, const INT16 y, const INT16 z)
566 {
567 	return
568 		(z == 0 && StrategicMap[CALCULATE_STRATEGIC_INDEX(x, y)].bNameId != BLANK_SECTOR) || // is there a town?
569 		IsThisSectorASAMSector(x, y, z);
570 }
571 
572 
573 // can this character EVER train militia?
BasicCanCharacterTrainMilitia(const SOLDIERTYPE * const s)574 static BOOLEAN BasicCanCharacterTrainMilitia(const SOLDIERTYPE* const s)
575 {
576 	// is the character capable of training a town?
577 	// they must be alive/conscious and in the sector with the town
578 	return
579 		s->bLeadership > 0 &&
580 		CanSectorContainMilita(s->sSectorX, s->sSectorY, s->bSectorZ) &&
581 		NumEnemiesInAnySector(s->sSectorX, s->sSectorY, s->bSectorZ) == 0 &&
582 		AreAssignmentConditionsMet(*s, AC_NONE);
583 }
584 
585 
586 static INT8 CountMilitiaTrainersInSoldiersSector(const SOLDIERTYPE* s);
587 
588 
CanCharacterTrainMilitia(const SOLDIERTYPE * const s)589 BOOLEAN CanCharacterTrainMilitia(const SOLDIERTYPE* const s)
590 {
591 	return
592 		BasicCanCharacterTrainMilitia(s)                                      &&
593 		MilitiaTrainingAllowedInSector(s->sSectorX, s->sSectorY, s->bSectorZ) &&
594 		DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia(s)              &&
595 		!IsAreaFullOfMilitia(s->sSectorX, s->sSectorY, s->bSectorZ)           &&
596 		CountMilitiaTrainersInSoldiersSector(s) < MAX_MILITIA_TRAINERS_PER_SECTOR;
597 }
598 
599 
DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia(const SOLDIERTYPE * const s)600 BOOLEAN DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia(const SOLDIERTYPE* const s)
601 {
602 	// underground training is not allowed (code doesn't support and it's a reasonable enough limitation)
603 	if (s->bSectorZ != 0) return FALSE;
604 
605 	INT8 const bTownId = GetTownIdForSector(SECTOR(s->sSectorX, s->sSectorY));
606 	if (bTownId != BLANK_SECTOR)
607 	{
608 		// Does this town have sufficient loyalty to train militia?
609 		return gTownLoyalty[bTownId].ubRating >= MIN_RATING_TO_TRAIN_TOWN;
610 	}
611 	else
612 	{
613 		return IsThisSectorASAMSector(s->sSectorX, s->sSectorY, s->bSectorZ);
614 	}
615 }
616 
617 
618 // only 2 trainers are allowed per sector, so this function counts the # in a guy's sector
CountMilitiaTrainersInSoldiersSector(const SOLDIERTYPE * const pSoldier)619 static INT8 CountMilitiaTrainersInSoldiersSector(const SOLDIERTYPE* const pSoldier)
620 {
621 	INT8 bCount = 0;
622 	CFOR_EACH_IN_TEAM(s, OUR_TEAM)
623 	{
624 		if (s != pSoldier &&
625 				s->bLife >= OKLIFE &&
626 				s->sSectorX == pSoldier->sSectorX &&
627 				s->sSectorY == pSoldier->sSectorY &&
628 				s->bSectorZ == pSoldier->bSectorZ &&
629 				s->bAssignment == TRAIN_TOWN)
630 		{
631 			++bCount;
632 		}
633 	}
634 	return bCount;
635 }
636 
637 
GetTrainingStatValue(const SOLDIERTYPE * const s,const INT8 stat)638 static INT8 GetTrainingStatValue(const SOLDIERTYPE* const s, const INT8 stat)
639 {
640 	switch (stat)
641 	{
642 		case STRENGTH:         return s->bStrength;
643 		case DEXTERITY:        return s->bDexterity;
644 		case AGILITY:          return s->bAgility;
645 		case HEALTH:           return s->bLifeMax;
646 		case MARKSMANSHIP:     return s->bMarksmanship;
647 		case MEDICAL:          return s->bMedical;
648 		case MECHANICAL:       return s->bMechanical;
649 		case LEADERSHIP:       return s->bLeadership;
650 		case EXPLOSIVE_ASSIGN: return s->bExplosive;
651 		// NOTE: Wisdom can't be trained!
652 
653 		default:
654 			SLOGE("Unknown training stat %d", stat);
655 			return 0;
656 	}
657 }
658 
659 
660 // can character train stat?..as train self or as trainer?
CanCharacterTrainStat(const SOLDIERTYPE * const s,INT8 bStat,const BOOLEAN fTrainSelf,const BOOLEAN fTrainTeammate)661 static BOOLEAN CanCharacterTrainStat(const SOLDIERTYPE* const s, INT8 bStat, const BOOLEAN fTrainSelf, const BOOLEAN fTrainTeammate)
662 {
663 	// is the character capable of training this stat? either self or as trainer
664 	// underground training is not allowed (code doesn't support and it's a reasonable enough limitation)
665 	if (!AreAssignmentConditionsMet(*s, AC_NONE)) return FALSE;
666 
667 	const INT8 stat_val = GetTrainingStatValue(s, bStat);
668 	return
669 		stat_val != 0 &&
670 		(!fTrainTeammate || stat_val >= MIN_RATING_TO_TEACH) &&
671 		(!fTrainSelf     || stat_val <  TRAINING_RATING_CAP);
672 }
673 
674 
675 // put character on duty?
CanCharacterOnDuty(const SOLDIERTYPE * const s)676 static BOOLEAN CanCharacterOnDuty(const SOLDIERTYPE* const s)
677 {
678 	// can character commit themselves to on duty?
679 	return AreAssignmentConditionsMet(*s, AC_COMBAT | AC_EPC | AC_MECHANICAL | AC_UNDERGROUND);
680 }
681 
682 
683 // is character capable of practising at all?
CanCharacterPractise(const SOLDIERTYPE * const s)684 static BOOLEAN CanCharacterPractise(const SOLDIERTYPE* const s)
685 {
686 	// can character practise right now?
687 	return AreAssignmentConditionsMet(*s, AC_NONE);
688 }
689 
690 
691 // can this character train others?
CanCharacterTrainTeammates(SOLDIERTYPE const * const pSoldier)692 static BOOLEAN CanCharacterTrainTeammates(SOLDIERTYPE const* const pSoldier)
693 {
694 	// can character train at all
695 	if (!CanCharacterPractise(pSoldier)) return FALSE;
696 
697 	// if alone in sector, can't enter the attributes submenu at all
698 	if ( PlayerMercsInSector( ( UINT8 ) pSoldier->sSectorX, ( UINT8 ) pSoldier->sSectorY, pSoldier->bSectorZ ) == 0 )
699 	{
700 		return( FALSE );
701 	}
702 
703 	// ARM: we allow this even if there are no students assigned yet.  Flashing is warning enough.
704 	return( TRUE );
705 }
706 
707 
CanCharacterBeTrainedByOther(SOLDIERTYPE const * const pSoldier)708 static BOOLEAN CanCharacterBeTrainedByOther(SOLDIERTYPE const* const pSoldier)
709 {
710 	// can character train at all
711 	if (!CanCharacterPractise(pSoldier)) return FALSE;
712 
713 	// if alone in sector, can't enter the attributes submenu at all
714 	if ( PlayerMercsInSector( ( UINT8 ) pSoldier->sSectorX, ( UINT8 ) pSoldier->sSectorY, pSoldier->bSectorZ ) == 0 )
715 	{
716 		return( FALSE );
717 	}
718 
719 	// ARM: we now allow this even if there are no trainers assigned yet.  Flashing is warning enough.
720 	return( TRUE );
721 }
722 
723 
724 // Can character sleep right now?
CanCharacterSleep(SOLDIERTYPE const & s,bool const explain_why_not)725 static bool CanCharacterSleep(SOLDIERTYPE const& s, bool const explain_why_not)
726 {
727 	if (!AreAssignmentConditionsMet(s, AC_IMPASSABLE | AC_COMBAT | AC_EPC | AC_IN_HELI_IN_HOSTILE_SECTOR | AC_MOVING | AC_UNDERGROUND)) return false;
728 
729 	ST::string why;
730 	if (s.fBetweenSectors) // Traveling?
731 	{
732 		if (s.bAssignment != VEHICLE)
733 		{ // Can't sleep while walking
734 			why = zMarksMapScreenText[5];
735 			goto cannot_sleep;
736 		}
737 		else // in a vehicle
738 		{
739 			// If this guy has to drive (because nobody else can)
740 			if (SoldierMustDriveVehicle(s, false))
741 			{ // Can't sleep while driving a vehicle
742 				why = zMarksMapScreenText[7];
743 				goto cannot_sleep;
744 			}
745 		}
746 	}
747 	else // In a sector
748 	{
749 		// If not above it all
750 		if (!SoldierAboardAirborneHeli(s))
751 		{
752 			// If he's in the loaded sector, and it's hostile or in combat
753 			if (s.bInSector && (gTacticalStatus.uiFlags & INCOMBAT || gTacticalStatus.fEnemyInSector))
754 			{
755 				why = g_langRes->Message[STR_SECTOR_NOT_CLEARED];
756 				goto cannot_sleep;
757 			}
758 
759 			// on surface, and enemies are in the sector
760 			if (s.bSectorZ == 0 && NumEnemiesInAnySector(s.sSectorX, s.sSectorY, s.bSectorZ) > 0)
761 			{
762 				why = g_langRes->Message[STR_SECTOR_NOT_CLEARED];
763 				goto cannot_sleep;
764 			}
765 		}
766 	}
767 
768 	if (s.bBreathMax >= BREATHMAX_FULLY_RESTED) // Not tired?
769 	{
770 		why = zMarksMapScreenText[4];
771 		goto cannot_sleep;
772 	}
773 
774 	return true;
775 
776 cannot_sleep:
777 	if (explain_why_not)
778 	{
779 		ST::string buf = st_format_printf(why, s.name);
780 		DoScreenIndependantMessageBox(buf, MSG_BOX_FLAG_OK, 0);
781 	}
782 	return false;
783 }
784 
785 
CanCharacterBeAwakened(SOLDIERTYPE * pSoldier,BOOLEAN fExplainWhyNot)786 static BOOLEAN CanCharacterBeAwakened(SOLDIERTYPE* pSoldier, BOOLEAN fExplainWhyNot)
787 {
788 	ST::string sString;
789 
790 	// if dead tired
791 	if( ( pSoldier -> bBreathMax <= BREATHMAX_ABSOLUTE_MINIMUM ) && !pSoldier->fMercCollapsedFlag )
792 	{
793 		// should be collapsed, then!
794 		pSoldier->fMercCollapsedFlag = TRUE;
795 	}
796 
797 	// merc collapsed due to being dead tired, you can't wake him up until he recovers substantially
798 	if (pSoldier->fMercCollapsedFlag)
799 	{
800 		if ( fExplainWhyNot )
801 		{
802 			sString = st_format_printf(zMarksMapScreenText[ 6 ], pSoldier->name);
803 			DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL );
804 		}
805 
806 		return( FALSE );
807 	}
808 
809 	// can be awakened
810 	return( TRUE );
811 }
812 
813 
814 // put character in vehicle?
CanCharacterVehicle(SOLDIERTYPE const & s)815 static bool CanCharacterVehicle(SOLDIERTYPE const& s)
816 { // Can character enter/leave vehicle?
817 	return
818 		fInMapMode && // Strictly for visual reasons - we don't want them just vanishing if in tactical
819 		AnyAccessibleVehiclesInSoldiersSector(s) &&
820 		( // If we're in battle in the current sector, disallow
821 			!gTacticalStatus.fEnemyInSector ||
822 			s.sSectorX != gWorldSectorX     ||
823 			s.sSectorY != gWorldSectorY     ||
824 			s.bSectorZ != gbWorldSectorZ
825 		) &&
826 		AreAssignmentConditionsMet(s, AC_EPC | AC_MECHANICAL);
827 }
828 
829 
830 enum JoinSquadResult
831 {
832 	CHARACTER_CANT_JOIN_SQUAD_ALREADY_IN_IT = -6,
833 	CHARACTER_CANT_JOIN_SQUAD_SQUAD_MOVING  = -5,
834 	CHARACTER_CANT_JOIN_SQUAD_MOVING        = -4,
835 	CHARACTER_CANT_JOIN_SQUAD_VEHICLE       = -3,
836 	CHARACTER_CANT_JOIN_SQUAD_TOO_FAR       = -2,
837 	CHARACTER_CANT_JOIN_SQUAD_FULL          = -1,
838 	CHARACTER_CANT_JOIN_SQUAD               =  0,
839 	CHARACTER_CAN_JOIN_SQUAD                =  1
840 };
841 
842 
843 // can character be added to squad
CanCharacterSquad(SOLDIERTYPE const & s,INT8 const squad_no)844 static JoinSquadResult CanCharacterSquad(SOLDIERTYPE const& s, INT8 const squad_no)
845 {
846 	Assert(squad_no < ON_DUTY);
847 
848 	INT16 x;
849 	INT16 y;
850 	INT8  z;
851 	if (s.bAssignment == squad_no)
852 	{
853 		return CHARACTER_CANT_JOIN_SQUAD_ALREADY_IN_IT;
854 	}
855 	else if (s.bLife < OKLIFE)
856 	{
857 		return CHARACTER_CANT_JOIN_SQUAD;
858 	}
859 	else if (IsCharacterInTransit(s))
860 	{
861 		return CHARACTER_CANT_JOIN_SQUAD;
862 	}
863 	else if (s.bAssignment == ASSIGNMENT_POW)
864 	{
865 		return CHARACTER_CANT_JOIN_SQUAD;
866 	}
867 	else if (SectorSquadIsIn(squad_no, &x, &y, &z) &&
868 			(x != s.sSectorX || y != s.sSectorY || z != s.bSectorZ))
869 	{
870 		return CHARACTER_CANT_JOIN_SQUAD_TOO_FAR;
871 	}
872 	else if (IsThisSquadOnTheMove(squad_no))
873 	{
874 		return CHARACTER_CANT_JOIN_SQUAD_SQUAD_MOVING;
875 	}
876 	else if (DoesVehicleExistInSquad(squad_no))
877 	{
878 		return CHARACTER_CANT_JOIN_SQUAD_VEHICLE;
879 	}
880 	else if (NumberOfPeopleInSquad(squad_no) >= NUMBER_OF_SOLDIERS_PER_SQUAD)
881 	{
882 		return CHARACTER_CANT_JOIN_SQUAD_FULL;
883 	}
884 	else
885 	{
886 		return CHARACTER_CAN_JOIN_SQUAD;
887 	}
888 }
889 
890 
IsCharacterInTransit(SOLDIERTYPE const & s)891 bool IsCharacterInTransit(SOLDIERTYPE const& s)
892 {
893 	return s.bAssignment == IN_TRANSIT;
894 }
895 
896 
897 static void CheckForAndHandleHospitalPatients(void);
898 static void HandleDoctorsInSector(INT16 x, INT16 y, INT8 z);
899 static void HandleNaturalHealing(void);
900 static void HandleRepairmenInSector(INT16 sX, INT16 sY, INT8 bZ);
901 static void HandleRestFatigueAndSleepStatus();
902 static void HandleTrainingInSector(INT16 sMapX, INT16 sMapY, INT8 bZ);
903 static void ReportTrainersTraineesWithoutPartners(void);
904 static void UpdatePatientsWhoAreDoneHealing();
905 
906 
UpdateAssignments()907 void UpdateAssignments()
908 {
909 	INT8 sX,sY, bZ;
910 
911 	// init sectors with soldiers list
912 	InitSectorsWithSoldiersList( );
913 
914 	// build list
915 	BuildSectorsWithSoldiersList(  );
916 
917 	// handle natural healing
918 	HandleNaturalHealing( );
919 
920 	// handle any patients in the hospital
921 	CheckForAndHandleHospitalPatients( );
922 
923 	// see if any grunt or trainer just lost student/teacher
924 	ReportTrainersTraineesWithoutPartners( );
925 
926 	// clear out the update list
927 	ClearSectorListForCompletedTrainingOfMilitia( );
928 
929 
930 	// rest resting mercs, fatigue active mercs,
931 	// check for mercs tired enough go to sleep, and wake up well-rested mercs
932 	HandleRestFatigueAndSleepStatus( );
933 
934 	// run through sectors and handle each type in sector
935 	for(sX = 0 ; sX < MAP_WORLD_X; sX++ )
936 	{
937 		for( sY =0; sY < MAP_WORLD_X; sY++ )
938 		{
939 			for( bZ = 0; bZ < 4; bZ++)
940 			{
941 				// is there anyone in this sector?
942 				if (fSectorsWithSoldiers[sX + sY * MAP_WORLD_X][bZ])
943 				{
944 					// handle any doctors
945 					HandleDoctorsInSector( sX, sY, bZ );
946 
947 					// handle any repairmen
948 					HandleRepairmenInSector( sX, sY, bZ );
949 
950 					// handle any training
951 					HandleTrainingInSector( sX, sY, bZ );
952 				}
953 			}
954 		}
955 	}
956 
957 	// check to see if anyone is done healing?
958 	UpdatePatientsWhoAreDoneHealing( );
959 
960 
961 	// check if we have anyone who just finished their assignment
962 	if( gfAddDisplayBoxToWaitingQueue )
963 	{
964 		AddDisplayBoxToWaitingQueue( );
965 		gfAddDisplayBoxToWaitingQueue = FALSE;
966 	}
967 
968 
969 	HandleContinueOfTownTraining( );
970 
971 	// check if anyone is on an assignment where they have nothing to do
972 	ReEvaluateEveryonesNothingToDo();
973 
974 	// update mapscreen
975 	fCharacterInfoPanelDirty = TRUE;
976 	fTeamPanelDirty = TRUE;
977 	fMapScreenBottomDirty = TRUE;
978 }
979 
980 
981 static BOOLEAN CanSoldierBeHealedByDoctor(const SOLDIERTYPE* pSoldier, const SOLDIERTYPE* pDoctor, BOOLEAN fThisHour, BOOLEAN fSkipKitCheck, BOOLEAN fSkipSkillCheck);
982 
983 
GetNumberThatCanBeDoctored(SOLDIERTYPE * pDoctor,BOOLEAN fThisHour,BOOLEAN fSkipKitCheck,BOOLEAN fSkipSkillCheck)984 static UINT8 GetNumberThatCanBeDoctored(SOLDIERTYPE* pDoctor, BOOLEAN fThisHour, BOOLEAN fSkipKitCheck, BOOLEAN fSkipSkillCheck)
985 {
986 	// go through list of characters, find all who are patients/doctors healable by this doctor
987 	UINT8 ubNumberOfPeople = 0;
988 	CFOR_EACH_IN_TEAM(s, OUR_TEAM)
989 	{
990 		if (CanSoldierBeHealedByDoctor(s, pDoctor, fThisHour, fSkipKitCheck, fSkipSkillCheck))
991 		{
992 			// increment number of doctorable patients/doctors
993 			++ubNumberOfPeople;
994 		}
995 	}
996 	return ubNumberOfPeople;
997 }
998 
999 
AnyDoctorWhoCanHealThisPatient(SOLDIERTYPE * pPatient,BOOLEAN fThisHour)1000 SOLDIERTYPE *AnyDoctorWhoCanHealThisPatient( SOLDIERTYPE *pPatient, BOOLEAN fThisHour )
1001 {
1002 	// go through list of characters, find all who are patients/doctors healable by this doctor
1003 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
1004 	{
1005 		// doctor?
1006 		if (s->bAssignment == DOCTOR &&
1007 				CanSoldierBeHealedByDoctor(pPatient, s, fThisHour, FALSE, FALSE))
1008 		{
1009 			// found one
1010 			return s;
1011 		}
1012 	}
1013 
1014 	// there aren't any doctors, or the ones there can't do the job
1015 	return NULL;
1016 }
1017 
1018 
1019 static UINT16 TotalMedicalKitPoints(SOLDIERTYPE* pSoldier);
1020 static BOOLEAN MakeSureMedKitIsInHand(SOLDIERTYPE* pSoldier);
1021 
1022 
CalculateHealingPointsForDoctor(SOLDIERTYPE * pDoctor,UINT16 * pusMaxPts,BOOLEAN fMakeSureKitIsInHand)1023 UINT16 CalculateHealingPointsForDoctor(SOLDIERTYPE *pDoctor, UINT16 *pusMaxPts, BOOLEAN fMakeSureKitIsInHand )
1024 {
1025 	UINT16 usHealPts = 0;
1026 	UINT16 usKitPts = 0;
1027 
1028 	// make sure he has a medkit in his hand
1029 	if( fMakeSureKitIsInHand )
1030 	{
1031 		if ( !MakeSureMedKitIsInHand( pDoctor ) )
1032 		{
1033 			return(0);
1034 		}
1035 	}
1036 
1037 	// calculate effective doctoring rate (adjusted for drugs, alcohol, etc.)
1038 	usHealPts = ( EffectiveMedical( pDoctor ) * (( EffectiveDexterity( pDoctor ) + EffectiveWisdom( pDoctor ) ) / 2) * (100 + ( 5 * EffectiveExpLevel( pDoctor) ) ) ) / DOCTORING_RATE_DIVISOR;
1039 
1040 	// calculate normal doctoring rate - what it would be if his stats were "normal" (ignoring drugs, fatigue, equipment condition)
1041 	// and equipment was not a hindrance
1042 	*pusMaxPts = ( pDoctor -> bMedical * (( pDoctor -> bDexterity + pDoctor -> bWisdom ) / 2 ) * (100 + ( 5 * pDoctor->bExpLevel) ) ) / DOCTORING_RATE_DIVISOR;
1043 
1044 	// adjust for fatigue
1045 	ReducePointsForFatigue( pDoctor, &usHealPts );
1046 
1047 	// count how much medical supplies we have
1048 	usKitPts = 100 * TotalMedicalKitPoints( pDoctor );
1049 
1050 	// if we don't have enough medical KIT points, reduce what we can heal
1051 	if (usKitPts < usHealPts)
1052 	{
1053 		usHealPts = usKitPts;
1054 	}
1055 
1056 	// return healing pts value
1057 	return( usHealPts );
1058 }
1059 
1060 
1061 static UINT16 ToolKitPoints(SOLDIERTYPE* pSoldier);
1062 static void MakeSureToolKitIsInHand(SOLDIERTYPE* pSoldier);
1063 
1064 
CalculateRepairPointsForRepairman(SOLDIERTYPE * pSoldier,UINT16 * pusMaxPts,BOOLEAN fMakeSureKitIsInHand)1065 UINT8 CalculateRepairPointsForRepairman(SOLDIERTYPE *pSoldier, UINT16 *pusMaxPts, BOOLEAN fMakeSureKitIsInHand )
1066 {
1067 	UINT16 usRepairPts;
1068 	UINT16 usKitPts;
1069 	UINT8  ubKitEffectiveness;
1070 
1071 	// make sure toolkit in hand?
1072 	if( fMakeSureKitIsInHand )
1073 	{
1074 		MakeSureToolKitIsInHand( pSoldier );
1075 	}
1076 
1077 	// can't repair at all without a toolkit
1078 	if (pSoldier -> inv[HANDPOS].usItem != TOOLKIT)
1079 	{
1080 		*pusMaxPts = 0;
1081 		return(0);
1082 	}
1083 
1084 	// calculate effective repair rate (adjusted for drugs, alcohol, etc.)
1085 	usRepairPts = (EffectiveMechanical( pSoldier ) * EffectiveDexterity( pSoldier ) * (100 + ( 5 * EffectiveExpLevel( pSoldier) ) ) ) / ( REPAIR_RATE_DIVISOR * ASSIGNMENT_UNITS_PER_DAY );
1086 
1087 	// calculate normal repair rate - what it would be if his stats were "normal" (ignoring drugs, fatigue, equipment condition)
1088 	// and equipment was not a hindrance
1089 	*pusMaxPts = ( pSoldier -> bMechanical * pSoldier -> bDexterity * (100 + ( 5 * pSoldier->bExpLevel) ) ) / ( REPAIR_RATE_DIVISOR * ASSIGNMENT_UNITS_PER_DAY );
1090 
1091 
1092 	// adjust for fatigue
1093 	ReducePointsForFatigue( pSoldier, &usRepairPts );
1094 
1095 
1096 	// figure out what shape his "equipment" is in ("coming" in JA3: Viagra - improves the "shape" your "equipment" is in)
1097 	usKitPts = ToolKitPoints( pSoldier );
1098 
1099 	// if kit(s) in extremely bad shape
1100 	if ( usKitPts < 25 )
1101 	{
1102 		ubKitEffectiveness = 50;
1103 	}
1104 	// if kit(s) in pretty bad shape
1105 	else if ( usKitPts < 50 )
1106 	{
1107 		ubKitEffectiveness = 75;
1108 	}
1109 	else
1110 	{
1111 		ubKitEffectiveness = 100;
1112 	}
1113 
1114 	// adjust for equipment
1115 	usRepairPts = (usRepairPts * ubKitEffectiveness) / 100;
1116 
1117 
1118 	// return current repair pts
1119 	return(( UINT8 )usRepairPts);
1120 }
1121 
1122 
1123 // how many points worth of tool kits does the character have?
ToolKitPoints(SOLDIERTYPE * pSoldier)1124 static UINT16 ToolKitPoints(SOLDIERTYPE* pSoldier)
1125 {
1126 	UINT16 usKitpts=0;
1127 	UINT8 ubPocket;
1128 
1129 	// add up kit points
1130 	for (ubPocket=HANDPOS; ubPocket <= SMALLPOCK8POS; ubPocket++)
1131 	{
1132 		if( pSoldier -> inv[ ubPocket ].usItem == TOOLKIT )
1133 		{
1134 			usKitpts += TotalPoints( &( pSoldier -> inv[ ubPocket ] ) );
1135 		}
1136 	}
1137 
1138 	return( usKitpts );
1139 }
1140 
1141 
1142 // how many points worth of doctoring does the character have in his medical kits?
TotalMedicalKitPoints(SOLDIERTYPE * pSoldier)1143 static UINT16 TotalMedicalKitPoints(SOLDIERTYPE* pSoldier)
1144 {
1145 	UINT8 ubPocket;
1146 	UINT16 usKitpts=0;
1147 
1148 	// add up kit points of all medkits
1149 	for (ubPocket = HANDPOS; ubPocket <= SMALLPOCK8POS; ubPocket++)
1150 	{
1151 		if (IsMedicalKitItem(&pSoldier->inv[ubPocket]))
1152 		{
1153 			usKitpts += TotalPoints( &( pSoldier -> inv[ ubPocket ] ) );
1154 		}
1155 	}
1156 
1157 	return( usKitpts );
1158 }
1159 
1160 
1161 // Have we spent enough time on assignment for it to count?
EnoughTimeOnAssignment(SOLDIERTYPE const & s)1162 static bool EnoughTimeOnAssignment(SOLDIERTYPE const& s)
1163 {
1164 	return GetWorldTotalMin() - s.uiLastAssignmentChangeMin >= MINUTES_FOR_ASSIGNMENT_TO_COUNT;
1165 }
1166 
1167 
1168 static void HealCharacters(SOLDIERTYPE* pDoctor);
1169 
1170 
1171 // handle doctor in this sector
HandleDoctorsInSector(INT16 const x,INT16 const y,INT8 const z)1172 static void HandleDoctorsInSector(INT16 const x, INT16 const y, INT8 const z)
1173 {
1174 	// will handle doctor/patient relationship in sector
1175 
1176 	// go through list of characters, find all doctors in sector
1177 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1178 	{
1179 		SOLDIERTYPE& s = *i;
1180 		if (s.sSectorX != x)         continue;
1181 		if (s.sSectorY != y)         continue;
1182 		if (s.bSectorZ != z)         continue;
1183 		if (s.bAssignment != DOCTOR) continue;
1184 		if (s.fMercAsleep)           continue;
1185 		MakeSureMedKitIsInHand(&s);
1186 		// Character is in sector, check if can doctor, if so, heal people
1187 		if (!CanCharacterDoctor(&s))    continue;
1188 		if (!EnoughTimeOnAssignment(s)) continue;
1189 		HealCharacters(&s);
1190 	}
1191 }
1192 
1193 
1194 // Update characters who might done healing but are still patients
UpdatePatientsWhoAreDoneHealing()1195 static void UpdatePatientsWhoAreDoneHealing()
1196 {
1197 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1198 	{
1199 		SOLDIERTYPE& s = *i;
1200 		if (s.bAssignment != PATIENT) continue;
1201 		if (s.bLife != s.bLifeMax)    continue;
1202 		// Patient who doesn't need healing
1203 		AssignmentDone(&s, TRUE, TRUE);
1204 	}
1205 }
1206 
1207 
1208 static void AssignmentAborted(SOLDIERTYPE const&, AssignmentAbortReason);
1209 static UINT16 HealPatient(SOLDIERTYPE* pPatient, SOLDIERTYPE* pDoctor, UINT16 usHundredthsHealed);
1210 
1211 
1212 // heal characters in this sector with this doctor
HealCharacters(SOLDIERTYPE * const pDoctor)1213 static void HealCharacters(SOLDIERTYPE* const pDoctor)
1214 {
1215 	// heal all patients in this sector
1216 	UINT16 usAvailableHealingPts = 0;
1217 	UINT16 usRemainingHealingPts = 0;
1218 	UINT16 usUsedHealingPts = 0;
1219 	UINT16 usEvenHealingAmount = 0;
1220 	UINT16 usMax =0;
1221 	UINT8 ubTotalNumberOfPatients = 0;
1222 	UINT16 usOldLeftOvers = 0;
1223 
1224 	// now find number of healable mercs in sector that are wounded
1225 	ubTotalNumberOfPatients = GetNumberThatCanBeDoctored( pDoctor, HEALABLE_THIS_HOUR, FALSE, FALSE );
1226 
1227 	// if there is anybody who can be healed right now
1228 	if( ubTotalNumberOfPatients > 0 )
1229 	{
1230 		// get available healing pts
1231 		usAvailableHealingPts = CalculateHealingPointsForDoctor( pDoctor, &usMax, TRUE );
1232 		usRemainingHealingPts = usAvailableHealingPts;
1233 
1234 		// find how many healing points can be evenly distributed to each wounded, healable merc
1235 		usEvenHealingAmount = usRemainingHealingPts / ubTotalNumberOfPatients;
1236 
1237 
1238 		// heal each of the healable mercs by this equal amount
1239 		FOR_EACH_IN_TEAM(s, OUR_TEAM)
1240 		{
1241 			if (CanSoldierBeHealedByDoctor(s, pDoctor, HEALABLE_THIS_HOUR, FALSE, FALSE))
1242 			{
1243 				// can heal and is patient, heal them
1244 				usRemainingHealingPts -= HealPatient(s, pDoctor, usEvenHealingAmount);
1245 			}
1246 		}
1247 
1248 
1249 		// if we have any remaining pts
1250 		if ( usRemainingHealingPts > 0)
1251 		{
1252 			// split those up based on need - lowest life patients get them first
1253 			SOLDIERTYPE* pWorstHurtSoldier;
1254 			do
1255 			{
1256 				// find the worst hurt patient
1257 				pWorstHurtSoldier = NULL;
1258 				FOR_EACH_IN_TEAM(s, OUR_TEAM)
1259 				{
1260 					if (CanSoldierBeHealedByDoctor(s, pDoctor, HEALABLE_THIS_HOUR, FALSE, FALSE))
1261 					{
1262 						if (pWorstHurtSoldier == NULL)
1263 						{
1264 							pWorstHurtSoldier = s;
1265 						}
1266 						else
1267 						{
1268 							// check to see if this guy is hurt worse than anyone previous?
1269 							if (s->bLife < pWorstHurtSoldier->bLife)
1270 							{
1271 								// he is now the worse hurt guy
1272 								pWorstHurtSoldier = s;
1273 							}
1274 						}
1275 					}
1276 				}
1277 
1278 				if( pWorstHurtSoldier != NULL )
1279 				{
1280 					// heal the worst hurt guy
1281 					usOldLeftOvers = usRemainingHealingPts;
1282 					usRemainingHealingPts -= HealPatient( pWorstHurtSoldier, pDoctor, usRemainingHealingPts );
1283 
1284 					// couldn't expend any pts, leave
1285 					if( usRemainingHealingPts == usOldLeftOvers )
1286 					{
1287 						usRemainingHealingPts = 0;
1288 					}
1289 				}
1290 			} while( ( usRemainingHealingPts > 0 ) && ( pWorstHurtSoldier != NULL ) );
1291 		}
1292 
1293 
1294 		usUsedHealingPts = usAvailableHealingPts - usRemainingHealingPts;
1295 
1296 		// increment skills based on healing pts used
1297 		StatChange(*pDoctor, MEDICALAMT, usUsedHealingPts / 100, FROM_SUCCESS);
1298 		StatChange(*pDoctor, DEXTAMT,    usUsedHealingPts / 200, FROM_SUCCESS);
1299 		StatChange(*pDoctor, WISDOMAMT,  usUsedHealingPts / 200, FROM_SUCCESS);
1300 	}
1301 
1302 
1303 	// if there's nobody else here who can EVER be helped by this doctor (regardless of whether they got healing this hour)
1304 	if( GetNumberThatCanBeDoctored( pDoctor, HEALABLE_EVER, FALSE, FALSE ) == 0 )
1305 	{
1306 		// then this doctor has done all that he can do, but let's find out why and tell player the reason
1307 
1308 		// try again, but skip the med kit check!
1309 		if( GetNumberThatCanBeDoctored( pDoctor, HEALABLE_EVER, TRUE, FALSE ) > 0 )
1310 		{
1311 			// he could doctor somebody, but can't because he doesn't have a med kit!
1312 			AssignmentAborted(*pDoctor, NO_MORE_MED_KITS);
1313 		}
1314 		// try again, but skip the skill check!
1315 		else if( GetNumberThatCanBeDoctored( pDoctor, HEALABLE_EVER, FALSE, TRUE ) > 0 )
1316 		{
1317 			// he could doctor somebody, but can't because he doesn't have enough skill!
1318 			AssignmentAborted(*pDoctor, INSUF_DOCTOR_SKILL);
1319 		}
1320 		else
1321 		{
1322 			// all patients should now be healed - truly DONE!
1323 			AssignmentDone( pDoctor, TRUE, TRUE );
1324 		}
1325 	}
1326 }
1327 
1328 
1329 // returns minimum medical skill necessary to treat this patient
GetMinHealingSkillNeeded(SOLDIERTYPE const * const patient)1330 static UINT8 GetMinHealingSkillNeeded(SOLDIERTYPE const* const patient)
1331 {
1332 	if (patient->bLife < OKLIFE)
1333 	{
1334 		return
1335 			BASE_MEDICAL_SKILL_TO_DEAL_WITH_EMERGENCY +
1336 			MULTIPLIER_FOR_DIFFERENCE_IN_LIFE_VALUE_FOR_EMERGENCY * (OKLIFE - patient->bLife);
1337 	}
1338 	else
1339 	{
1340 		return 1;
1341 	}
1342 }
1343 
1344 
1345 // can this soldier be healed by this doctor?
CanSoldierBeHealedByDoctor(SOLDIERTYPE const * const patient,SOLDIERTYPE const * const doctor,BOOLEAN const fThisHour,BOOLEAN const fSkipKitCheck,BOOLEAN const fSkipSkillCheck)1346 static BOOLEAN CanSoldierBeHealedByDoctor(SOLDIERTYPE const* const patient, SOLDIERTYPE const* const doctor, BOOLEAN const fThisHour, BOOLEAN const fSkipKitCheck, BOOLEAN const fSkipSkillCheck)
1347 {
1348 	if (patient->bAssignment != PATIENT && patient->bAssignment != DOCTOR)        return FALSE;
1349 	if (patient->bLife == 0)                                                      return FALSE;
1350 	if (patient->bLife == patient->bLifeMax)                                      return FALSE;
1351 	if (fThisHour && !EnoughTimeOnAssignment(*patient))                           return FALSE;
1352 	if (patient->sSectorX != doctor->sSectorX)                                    return FALSE;
1353 	if (patient->sSectorY != doctor->sSectorY)                                    return FALSE;
1354 	if (patient->bSectorZ != doctor->bSectorZ)                                    return FALSE;
1355 	if (patient->fBetweenSectors)                                                 return FALSE;
1356 	if (!fSkipSkillCheck && doctor->bMedical < GetMinHealingSkillNeeded(patient)) return FALSE;
1357 	if (!fSkipKitCheck && FindObj(doctor, MEDICKIT) == NO_SLOT)                   return FALSE;
1358 	return TRUE;
1359 }
1360 
1361 
1362 // heal patient, given doctor and total healing pts available to doctor at this time
HealPatient(SOLDIERTYPE * pPatient,SOLDIERTYPE * pDoctor,UINT16 usHundredthsHealed)1363 static UINT16 HealPatient(SOLDIERTYPE* pPatient, SOLDIERTYPE* pDoctor, UINT16 usHundredthsHealed)
1364 {
1365 	// heal patient and return the number of healing pts used
1366 	UINT16 usHealingPtsLeft;
1367 	UINT16 usTotalFullPtsUsed = 0;
1368 	UINT16 usTotalHundredthsUsed = 0;
1369 	INT8 bPointsToUse = 0;
1370 	INT8 bPointsUsed = 0;
1371 	INT8 bPointsHealed = 0;
1372 	INT8 bPocket = 0;
1373 //	INT8 bOldPatientLife = pPatient -> bLife;
1374 
1375 
1376 	pPatient->sFractLife += usHundredthsHealed;
1377 	usTotalHundredthsUsed = usHundredthsHealed;		// we'll subtract any unused amount later if we become fully healed...
1378 
1379 	// convert fractions into full points
1380 	usHealingPtsLeft = pPatient->sFractLife / 100;
1381 	pPatient->sFractLife %= 100;
1382 
1383 	// if we haven't accumulated any full points yet
1384 	if (usHealingPtsLeft == 0)
1385 	{
1386 		return( usTotalHundredthsUsed );
1387 	}
1388 
1389 	// if below ok life, heal these first at double point cost
1390 	if( pPatient -> bLife < OKLIFE )
1391 	{
1392 		// get points needed to heal him to OKLIFE
1393 		bPointsToUse = POINT_COST_PER_HEALTH_BELOW_OKLIFE * ( OKLIFE - pPatient -> bLife );
1394 
1395 		// if he needs more than we have, reduce to that
1396 		if( bPointsToUse > usHealingPtsLeft )
1397 		{
1398 			bPointsToUse = usHealingPtsLeft;
1399 		}
1400 
1401 		// go through doctor's pockets and heal, starting at with his in-hand item
1402 		for (bPocket = HANDPOS; bPocket <= SMALLPOCK8POS; bPocket++)
1403 		{
1404 			OBJECTTYPE o = pDoctor->inv[bPocket];
1405 			if (IsMedicalKitItem(&o))
1406 			{
1407 				// ok, we have med kit in this pocket, use it
1408 				bPointsUsed   = UseKitPoints(o, bPointsToUse, *pDoctor);
1409 				bPointsHealed = bPointsUsed;
1410 
1411 				bPointsToUse -= bPointsHealed;
1412 				usHealingPtsLeft -= bPointsHealed;
1413 				usTotalFullPtsUsed += bPointsHealed;
1414 
1415 				// heal person the amount / POINT_COST_PER_HEALTH_BELOW_OKLIFE
1416 				pPatient -> bLife += (bPointsHealed / POINT_COST_PER_HEALTH_BELOW_OKLIFE);
1417 
1418 				// if we're done all we're supposed to, or the guy's at OKLIFE, bail
1419 				if ( ( bPointsToUse <= 0 ) || ( pPatient -> bLife >= OKLIFE ) )
1420 				{
1421 					break;
1422 				}
1423 			}
1424 		}
1425 	}
1426 
1427 	// critical conditions handled, now apply normal healing
1428 
1429 	if (pPatient -> bLife < pPatient -> bLifeMax)
1430 	{
1431 		bPointsToUse = ( pPatient -> bLifeMax - pPatient -> bLife );
1432 
1433 		// if guy is hurt more than points we have...heal only what we have
1434 		if( bPointsToUse > usHealingPtsLeft )
1435 		{
1436 			bPointsToUse = ( INT8 )usHealingPtsLeft;
1437 		}
1438 
1439 		// go through doctor's pockets and heal, starting at with his in-hand item
1440 		// the healing pts are based on what type of medkit is in his hand, so we HAVE to start there first!
1441 		for (bPocket = HANDPOS; bPocket <= SMALLPOCK8POS; bPocket++)
1442 		{
1443 			OBJECTTYPE& o = pDoctor->inv[bPocket];
1444 			if (IsMedicalKitItem(&o))
1445 			{
1446 				// ok, we have med kit in this pocket, use it  (use only half if it's worth double)
1447 				bPointsUsed   = UseKitPoints(o, bPointsToUse, *pDoctor);
1448 				bPointsHealed = bPointsUsed;
1449 
1450 				bPointsToUse -= bPointsHealed;
1451 				usHealingPtsLeft -= bPointsHealed;
1452 				usTotalFullPtsUsed += bPointsHealed;
1453 
1454 				pPatient -> bLife += bPointsHealed;
1455 
1456 				// if we're done all we're supposed to, or the guy's fully healed, bail
1457 				if ( ( bPointsToUse <= 0 ) || ( pPatient -> bLife == pPatient -> bLifeMax ) )
1458 				{
1459 					break;
1460 				}
1461 			}
1462 		}
1463 	}
1464 
1465 
1466 	// if this patient is fully healed
1467 	if( pPatient->bLife == pPatient->bLifeMax )
1468 	{
1469 		// don't count unused full healing points as being used
1470 		usTotalHundredthsUsed -= (100 * usHealingPtsLeft);
1471 
1472 		// wipe out fractions of extra life, and DON'T count them as used
1473 		usTotalHundredthsUsed -= pPatient->sFractLife;
1474 		pPatient->sFractLife = 0;
1475 
1476 /* ARM Removed.  This is duplicating the check in UpdatePatientsWhoAreDoneHealing(), guy would show up twice!
1477 		// if it isn't the doctor himself)
1478 		if( ( pPatient != pDoctor )
1479 		{
1480 			AssignmentDone( pPatient, TRUE, TRUE );
1481 		}
1482 */
1483 	}
1484 
1485 	return ( usTotalHundredthsUsed );
1486 }
1487 
1488 
1489 static void HealHospitalPatient(SOLDIERTYPE* pPatient, UINT16 usHealingPtsLeft);
1490 
1491 
CheckForAndHandleHospitalPatients(void)1492 static void CheckForAndHandleHospitalPatients(void)
1493 {
1494 	if (!fSectorsWithSoldiers[HOSPITAL_SECTOR_X + HOSPITAL_SECTOR_Y * MAP_WORLD_X][0])
1495 	{
1496 		// nobody in the hospital sector... leave
1497 		return;
1498 	}
1499 
1500 	// go through list of characters, find all who are on this assignment
1501 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
1502 	{
1503 		if (s->bAssignment == ASSIGNMENT_HOSPITAL &&
1504 				s->sSectorX == HOSPITAL_SECTOR_X &&
1505 				s->sSectorY == HOSPITAL_SECTOR_Y &&
1506 				s->bSectorZ == 0)
1507 		{
1508 			// heal this character
1509 			HealHospitalPatient(s, HOSPITAL_HEALING_RATE);
1510 		}
1511 	}
1512 }
1513 
1514 
HealHospitalPatient(SOLDIERTYPE * pPatient,UINT16 usHealingPtsLeft)1515 static void HealHospitalPatient(SOLDIERTYPE* pPatient, UINT16 usHealingPtsLeft)
1516 {
1517 	INT8 bPointsToUse;
1518 
1519 	if (usHealingPtsLeft <= 0)
1520 	{
1521 		return;
1522 	}
1523 
1524 /*  Stopping hospital patients' bleeding must be handled immediately, not during a regular hourly check
1525 	// stop all bleeding of patient..for 1 pt.
1526 	if (pPatient -> bBleeding > 0)
1527 	{
1528 		usHealingPtsLeft--;
1529 		pPatient -> bBleeding = 0;
1530 	}
1531 */
1532 
1533 	// if below ok life, heal these first at double cost
1534 	if( pPatient -> bLife < OKLIFE )
1535 	{
1536 		// get points needed to heal him to OKLIFE
1537 		bPointsToUse = POINT_COST_PER_HEALTH_BELOW_OKLIFE * ( OKLIFE - pPatient -> bLife );
1538 
1539 		// if he needs more than we have, reduce to that
1540 		if( bPointsToUse > usHealingPtsLeft )
1541 		{
1542 			bPointsToUse = usHealingPtsLeft;
1543 		}
1544 
1545 		usHealingPtsLeft -= bPointsToUse;
1546 
1547 		// heal person the amount / POINT_COST_PER_HEALTH_BELOW_OKLIFE
1548 		pPatient -> bLife += ( bPointsToUse / POINT_COST_PER_HEALTH_BELOW_OKLIFE );
1549 	}
1550 
1551 	// critical condition handled, now solve normal healing
1552 
1553 	if ( pPatient -> bLife < pPatient -> bLifeMax )
1554 	{
1555 		bPointsToUse = ( pPatient -> bLifeMax - pPatient -> bLife );
1556 
1557 		// if guy is hurt more than points we have...heal only what we have
1558 		if( bPointsToUse > usHealingPtsLeft )
1559 		{
1560 			bPointsToUse = ( INT8 )usHealingPtsLeft;
1561 		}
1562 
1563 		usHealingPtsLeft -= bPointsToUse;
1564 
1565 		// heal person the amount
1566 		pPatient -> bLife += bPointsToUse;
1567 	}
1568 
1569 	// if this patient is fully healed
1570 	if ( pPatient -> bLife == pPatient -> bLifeMax )
1571 	{
1572 		AssignmentDone( pPatient, TRUE, TRUE );
1573 	}
1574 }
1575 
1576 
1577 static void HandleRepairBySoldier(SOLDIERTYPE&);
1578 
1579 
1580 // handle any repair man in sector
HandleRepairmenInSector(INT16 const x,INT16 const y,INT8 const z)1581 static void HandleRepairmenInSector(INT16 const x, INT16 const y, INT8 const z)
1582 {
1583 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1584 	{
1585 		SOLDIERTYPE& s = *i;
1586 		if (s.sSectorX    != x)      continue;
1587 		if (s.sSectorY    != y)      continue;
1588 		if (s.bSectorZ    != z)      continue;
1589 		if (s.bAssignment != REPAIR) continue;
1590 		if (s.fMercAsleep)           continue;
1591 
1592 		MakeSureToolKitIsInHand(&s);
1593 
1594 		// character is in sector, check if can repair
1595 		if (!CanCharacterRepair(&s))    continue;
1596 		if (!EnoughTimeOnAssignment(s)) continue;
1597 
1598 		HandleRepairBySoldier(s);
1599 	}
1600 }
1601 
1602 // does another merc have a repairable item on them?
FindRepairableItemOnOtherSoldier(const SOLDIERTYPE * const pSoldier,const UINT8 ubPassType)1603 static INT8 FindRepairableItemOnOtherSoldier(const SOLDIERTYPE* const pSoldier, const UINT8 ubPassType)
1604 {
1605 	INT8 bLoop, bLoop2;
1606 	REPAIR_PASS_SLOTS_TYPE *pPassList;
1607 	INT8 bSlotToCheck;
1608 
1609 	Assert( ubPassType < NUM_REPAIR_PASS_TYPES );
1610 
1611 	pPassList = &( gRepairPassSlotList[ ubPassType ] );
1612 
1613 	for ( bLoop = 0; bLoop < pPassList->ubChoices; bLoop++ )
1614 	{
1615 		bSlotToCheck = pPassList->bSlot[ bLoop ];
1616 		Assert( bSlotToCheck != -1 );
1617 
1618 		OBJECTTYPE const& o = pSoldier->inv[bSlotToCheck];
1619 		for ( bLoop2 = 0; bLoop2 < pSoldier->inv[ bSlotToCheck ].ubNumberOfObjects; bLoop2++ )
1620 		{
1621 			if (IsItemRepairable(o.usItem, o.bStatus[bLoop2]))
1622 			{
1623 				return( bSlotToCheck );
1624 			}
1625 		}
1626 
1627 		// have to check for attachments...
1628 		for ( bLoop2 = 0; bLoop2 < MAX_ATTACHMENTS; bLoop2++ )
1629 		{
1630 			if (o.usAttachItem[bLoop2] != NOTHING)
1631 			{
1632 				if (IsItemRepairable(o.usAttachItem[bLoop2], o.bAttachStatus[bLoop2]))
1633 				{
1634 					return( bSlotToCheck );
1635 				}
1636 			}
1637 		}
1638 	}
1639 
1640 	return( NO_SLOT );
1641 }
1642 
1643 
DoActualRepair(SOLDIERTYPE * pSoldier,UINT16 usItem,INT8 * pbStatus,UINT8 * pubRepairPtsLeft)1644 static void DoActualRepair(SOLDIERTYPE* pSoldier, UINT16 usItem, INT8* pbStatus, UINT8* pubRepairPtsLeft)
1645 {
1646 	INT16		sRepairCostAdj;
1647 	UINT16	usDamagePts, usPtsFixed;
1648 
1649 	// get item's repair ease, for each + point is 10% easier, each - point is 10% harder to repair
1650 	sRepairCostAdj = 100 - ( 10 * GCM->getItem(usItem)->getRepairEase() );
1651 
1652 	// make sure it ain't somehow gone too low!
1653 	if (sRepairCostAdj < 10)
1654 	{
1655 		sRepairCostAdj = 10;
1656 	}
1657 
1658 	// repairs on electronic items take twice as long if the guy doesn't have the skill
1659 	if ( ( GCM->getItem(usItem)->getFlags() & ITEM_ELECTRONIC ) && ( !( HAS_SKILL_TRAIT( pSoldier, ELECTRONICS ) ) ) )
1660 	{
1661 		sRepairCostAdj *= 2;
1662 	}
1663 
1664 	// how many points of damage is the item down by?
1665 	usDamagePts = 100 - *pbStatus;
1666 
1667 	// adjust that by the repair cost adjustment percentage
1668 	usDamagePts = (usDamagePts * sRepairCostAdj) / 100;
1669 
1670 	// do we have enough pts to fully repair the item?
1671 	if ( *pubRepairPtsLeft >= usDamagePts )
1672 	{
1673 		// fix it to 100%
1674 		*pbStatus = 100;
1675 		*pubRepairPtsLeft -= usDamagePts;
1676 	}
1677 	else	// not enough, partial fix only, if any at all
1678 	{
1679 		// fix what we can - add pts left adjusted by the repair cost
1680 		usPtsFixed = ( *pubRepairPtsLeft * 100 ) / sRepairCostAdj;
1681 
1682 		// if we have enough to actually fix anything
1683 		// NOTE: a really crappy repairman with only 1 pt/hr CAN'T repair electronics or difficult items!
1684 		if (usPtsFixed > 0)
1685 		{
1686 			*pbStatus += usPtsFixed;
1687 
1688 			// make sure we don't somehow end up over 100
1689 			if ( *pbStatus > 100 )
1690 			{
1691 				*pbStatus = 100;
1692 			}
1693 		}
1694 
1695 		*pubRepairPtsLeft = 0;
1696 	}
1697 }
1698 
1699 
DoRepair(SOLDIERTYPE * const repairer,SOLDIERTYPE const * const owner,UINT16 const item,INT8 & status,UINT8 * const repair_pts_left)1700 static bool DoRepair(SOLDIERTYPE* const repairer, SOLDIERTYPE const* const owner, UINT16 const item, INT8& status, UINT8* const repair_pts_left)
1701 {
1702 	// if it's repairable and NEEDS repairing
1703 	if (!IsItemRepairable(item, status)) return false;
1704 	DoActualRepair(repairer, item, &status, repair_pts_left);
1705 	if (status == 100)
1706 	{ // report it as fixed
1707 		if (repairer == owner)
1708 		{
1709 			ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(g_langRes->Message[STR_REPAIRED], repairer->name, ItemNames[item]));
1710 		}
1711 		else
1712 		{ // NOTE: may need to be changed for localized versions
1713 			ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(gzLateLocalizedString[STR_LATE_35], repairer->name, owner->name, ItemNames[item]));
1714 		}
1715 	}
1716 	return true;
1717 }
1718 
1719 
RepairObject(SOLDIERTYPE * const repairer,SOLDIERTYPE const * const owner,OBJECTTYPE * const pObj,UINT8 * const repair_pts_left)1720 static bool RepairObject(SOLDIERTYPE* const repairer, SOLDIERTYPE const* const owner, OBJECTTYPE* const pObj, UINT8* const repair_pts_left)
1721 {
1722 	bool something_was_repaired = false;
1723 
1724 	UINT8 const n_items = pObj->ubNumberOfObjects;
1725 	for (UINT8 i = 0; i != n_items; ++i)
1726 	{
1727 		UINT16 const item   = pObj->usItem;
1728 		INT8&        status = pObj->bStatus[i];
1729 		if (!DoRepair(repairer, owner, item, status, repair_pts_left)) continue;
1730 		something_was_repaired = true;
1731 		if (*repair_pts_left == 0) break; // we're out of points!
1732 	}
1733 
1734 	// now check for attachments
1735 	for (UINT8 i = 0; i != MAX_ATTACHMENTS; ++i)
1736 	{
1737 		UINT16 const item = pObj->usAttachItem[i];
1738 		if (item == NOTHING) continue;
1739 		INT8& status = pObj->bAttachStatus[i];
1740 		if (!DoRepair(repairer, owner, item, status, repair_pts_left)) continue;
1741 		something_was_repaired = true;
1742 		if (*repair_pts_left == 0) break; // we're out of points!
1743 	}
1744 
1745 	return something_was_repaired;
1746 }
1747 
1748 
1749 static UINT8 HandleRepairOfRobotBySoldier(UINT8 ubRepairPts, BOOLEAN* pfNothingLeftToRepair);
1750 static void RepairItemsOnOthers(SOLDIERTYPE* pSoldier, UINT8* pubRepairPtsLeft);
1751 static BOOLEAN UnjamGunsOnSoldier(SOLDIERTYPE* pOwnerSoldier, SOLDIERTYPE* pRepairSoldier, UINT8* pubRepairPtsLeft);
1752 
1753 
1754 // Repair stuff
HandleRepairBySoldier(SOLDIERTYPE & s)1755 static void HandleRepairBySoldier(SOLDIERTYPE& s)
1756 {
1757 	BOOLEAN nothing_left_to_repair;
1758 
1759 	// grab max number of repair pts open to this soldier
1760 	UINT16 usMax;
1761 	UINT8  initial_repair_pts = CalculateRepairPointsForRepairman(&s, &usMax, TRUE);
1762 	if (initial_repair_pts == 0)
1763 	{ // No points
1764 		AssignmentDone(&s, TRUE, TRUE);
1765 		return;
1766 	}
1767 	UINT8 repair_pts_left = initial_repair_pts;
1768 
1769 	// check if we are repairing a vehicle
1770 	if (s.bVehicleUnderRepairID != -1)
1771 	{
1772 		VEHICLETYPE const& v = GetVehicle(s.bVehicleUnderRepairID);
1773 		if (CanCharacterRepairVehicle(s, v))
1774 		{ // Attempt to fix vehicle
1775 			repair_pts_left -= RepairVehicle(v, repair_pts_left, &nothing_left_to_repair);
1776 		}
1777 	}
1778 	// check if we are repairing a robot
1779 	else if (s.fFixingRobot)
1780 	{
1781 		if (CanCharacterRepairRobot(&s))
1782 		{
1783 			// repairing the robot is very slow & difficult
1784 			repair_pts_left    /= 2;
1785 			initial_repair_pts /= 2;
1786 
1787 			if (!(HAS_SKILL_TRAIT(&s, ELECTRONICS)))
1788 			{
1789 				repair_pts_left    /= 2;
1790 				initial_repair_pts /= 2;
1791 			}
1792 
1793 			// Robot
1794 			repair_pts_left -= HandleRepairOfRobotBySoldier(repair_pts_left, &nothing_left_to_repair);
1795 		}
1796 	}
1797 	else
1798 	{
1799 		BOOLEAN fAnyOfSoldiersOwnItemsWereFixed = UnjamGunsOnSoldier(&s, &s, &repair_pts_left);
1800 
1801 		// Repair items on self
1802 		for (INT8 i = 0; i != 2; ++i)
1803 		{
1804 			INT8 start;
1805 			INT8 end;
1806 			if (i == 0)
1807 			{
1808 				start = SECONDHANDPOS;
1809 				end   = SMALLPOCK8POS;
1810 			}
1811 			else
1812 			{
1813 				start = HELMETPOS;
1814 				end   = HEAD2POS;
1815 			}
1816 
1817 			// now repair objects running from left hand to small pocket
1818 			for (INT8 pocket = start; pocket <= end; ++pocket)
1819 			{
1820 				if (RepairObject(&s, &s, &s.inv[pocket], &repair_pts_left))
1821 				{
1822 					fAnyOfSoldiersOwnItemsWereFixed = TRUE;
1823 
1824 					// quit looking if we're already out
1825 					if (repair_pts_left == 0) break;
1826 				}
1827 			}
1828 		}
1829 
1830 		// if he fixed something of his, and now has no more of his own items to fix
1831 		if (fAnyOfSoldiersOwnItemsWereFixed && !DoesCharacterHaveAnyItemsToRepair(&s, -1))
1832 		{
1833 			ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(sRepairsDoneString[0], s.name));
1834 
1835 			// let player react
1836 			StopTimeCompression();
1837 		}
1838 
1839 
1840 		// repair items on others
1841 		RepairItemsOnOthers(&s, &repair_pts_left);
1842 	}
1843 
1844 	// what are the total amount of pts used by character?
1845 	UINT8 const repair_pts_used = initial_repair_pts - repair_pts_left;
1846 	if (repair_pts_used > 0)
1847 	{
1848 		// improve stats
1849 		StatChange(s, MECHANAMT, repair_pts_used / 2, FROM_SUCCESS);
1850 		StatChange(s, DEXTAMT,   repair_pts_used / 2, FROM_SUCCESS);
1851 
1852 		// check if kit damaged/depleted
1853 		if (Random(100) < repair_pts_used * 5) // CJC: added a x5 as this wasn't going down anywhere fast enough
1854 		{
1855 			// kit item damaged/depleted, burn up points of toolkit..which is in right hand
1856 			UseKitPoints(s.inv[HANDPOS], 1, s);
1857 		}
1858 	}
1859 
1860 
1861 	// if he really done
1862 	if (HasCharacterFinishedRepairing(&s))
1863 	{
1864 		// yup, that's all folks
1865 		AssignmentDone(&s, TRUE, TRUE);
1866 	}
1867 	else // still has stuff to repair
1868 	{
1869 		// if nothing got repaired, there's a problem
1870 		if (repair_pts_used == 0)
1871 		{
1872 			// see if not having a toolkit is the problem
1873 			if (FindObj(&s, TOOLKIT) == NO_SLOT)
1874 			{
1875 				// he could (maybe) repair something, but can't because he doesn't have a tool kit!
1876 				AssignmentAborted(s, NO_MORE_TOOL_KITS);
1877 			}
1878 			else
1879 			{
1880 				// he can't repair anything because he doesn't have enough skill!
1881 				AssignmentAborted(s, INSUF_REPAIR_SKILL);
1882 			}
1883 		}
1884 	}
1885 }
1886 
1887 
1888 // can item be repaired?
IsItemRepairable(UINT16 const item_id,INT8 const status)1889 static bool IsItemRepairable(UINT16 const item_id, INT8 const status)
1890 {
1891 	return status < 100 && GCM->getItem(item_id)->getFlags() & ITEM_REPAIRABLE;
1892 }
1893 
1894 
1895 static UINT8 CalcSoldierNeedForSleep(SOLDIERTYPE const&);
1896 
1897 
1898 // rest the character
RestCharacter(SOLDIERTYPE * pSoldier)1899 static void RestCharacter(SOLDIERTYPE* pSoldier)
1900 {
1901 	// handle the sleep of this character, update bBreathMax based on sleep they have
1902 	INT8 bMaxBreathRegain = 0;
1903 
1904 	bMaxBreathRegain = 50 / CalcSoldierNeedForSleep(*pSoldier);
1905 
1906 	// if breath max is below the "really tired" threshold
1907 	if( pSoldier -> bBreathMax < BREATHMAX_PRETTY_TIRED )
1908 	{
1909 		// real tired, rest rate is 50% higher (this is to prevent absurdly long sleep times for totally exhausted mercs)
1910 		bMaxBreathRegain = ( bMaxBreathRegain * 3 / 2 );
1911 	}
1912 
1913 	pSoldier -> bBreathMax += bMaxBreathRegain;
1914 
1915 
1916 	if( pSoldier -> bBreathMax > 100 )
1917 	{
1918 		pSoldier -> bBreathMax = 100;
1919 	}
1920 	else if( pSoldier -> bBreathMax < BREATHMAX_ABSOLUTE_MINIMUM )
1921 	{
1922 		pSoldier -> bBreathMax = BREATHMAX_ABSOLUTE_MINIMUM;
1923 	}
1924 
1925 	pSoldier -> bBreath = pSoldier -> bBreathMax;
1926 
1927 
1928 	if ( pSoldier-> bBreathMax >= BREATHMAX_CANCEL_TIRED )
1929 	{
1930 		pSoldier->fComplainedThatTired = FALSE;
1931 	}
1932 }
1933 
1934 
FatigueCharacter(SOLDIERTYPE & s)1935 void FatigueCharacter(SOLDIERTYPE& s)
1936 {
1937 	if (IsMechanical(s))                 return;
1938 	if (IsCharacterInTransit(s))         return;
1939 	if (s.bAssignment == ASSIGNMENT_POW) return;
1940 
1941 	INT8 const divisor         = 24 - CalcSoldierNeedForSleep(s);
1942 	INT8       max_breath_loss = 50 / divisor;
1943 	if (max_breath_loss < 2) max_breath_loss = 2;
1944 
1945 	/* KM: Added encumbrance calculation to soldiers moving on foot. Anything
1946 	 * above 100% will increase rate of fatigue. 200% encumbrance will cause
1947 	 * soldiers to tire twice as quickly. */
1948 	if (s.fBetweenSectors && s.bAssignment != VEHICLE)
1949 	{ // Soldier is on foot and traveling. Factor encumbrance into fatigue rate.
1950 		INT32 const percent_encumbrance = CalculateCarriedWeight(&s);
1951 		if (percent_encumbrance > 100)
1952 		{
1953 			INT32 const breath_loss = max_breath_loss * percent_encumbrance / 100;
1954 			max_breath_loss = MIN(breath_loss, 127);
1955 		}
1956 	}
1957 
1958 	INT8 breath_max = s.bBreathMax;
1959 
1960 	if (breath_max < BREATHMAX_PRETTY_TIRED)
1961 	{ // Real tired, fatigue rate is 50% higher
1962 		max_breath_loss = max_breath_loss * 3 / 2;
1963 	}
1964 
1965 	breath_max -= max_breath_loss;
1966 
1967 	if (breath_max > 100)
1968 	{
1969 		breath_max = 100;
1970 	}
1971 	else if (breath_max < BREATHMAX_ABSOLUTE_MINIMUM)
1972 	{
1973 		breath_max = BREATHMAX_ABSOLUTE_MINIMUM;
1974 	}
1975 
1976 	s.bBreathMax = breath_max;
1977 	// Current breath can't exceed maximum
1978 	if (s.bBreath > breath_max) s.bBreath = breath_max;
1979 }
1980 
1981 
1982 static int TownTrainerQsortCompare(const void* pArg1, const void* pArg2);
1983 static void TrainSoldierWithPts(SOLDIERTYPE* pSoldier, INT16 sTrainPts);
1984 static BOOLEAN TrainTownInSector(SOLDIERTYPE* pTrainer, INT16 sMapX, INT16 sMapY, INT16 sTrainingPts);
1985 
1986 
1987 // ONCE PER HOUR, will handle ALL kinds of training (self, teaching, and town) in this sector
HandleTrainingInSector(const INT16 sMapX,const INT16 sMapY,const INT8 bZ)1988 static void HandleTrainingInSector(const INT16 sMapX, const INT16 sMapY, const INT8 bZ)
1989 {
1990 	UINT8 ubStat;
1991 	BOOLEAN fAtGunRange = FALSE;
1992 	INT16 sTotalTrainingPts = 0;
1993 	INT16 sTrainingPtsDueToInstructor = 0;
1994 	INT16 sBestTrainingPts;
1995 	INT16 sTownTrainingPts;
1996 	TOWN_TRAINER_TYPE TownTrainer[ MAX_CHARACTER_COUNT ];
1997 	UINT8 ubTownTrainers;
1998 	UINT16 usMaxPts;
1999 	BOOLEAN fTrainingCompleted = FALSE;
2000 
2001 	// Training in underground sectors is disallowed by the interface code, so there should never be any
2002 	if (bZ != 0)
2003 	{
2004 		return;
2005 	}
2006 
2007 	// if sector not under our control, has enemies in it, or is currently in combat mode
2008 	if (!SectorOursAndPeaceful( sMapX, sMapY, bZ ))
2009 	{
2010 		// then training is canceled for this hour.
2011 		// This is partly logical, but largely to prevent newly trained militia from appearing in mid-battle
2012 		return;
2013 	}
2014 
2015 	// are we training in the sector with gun range in Alma?
2016 	if ( (sMapX == GUN_RANGE_X) && (sMapY == GUN_RANGE_Y) && (bZ == GUN_RANGE_Z) )
2017 	{
2018 		fAtGunRange = TRUE;
2019 	}
2020 
2021 	// init trainer list
2022 	const SOLDIERTYPE* pStatTrainerList[NUM_TRAINABLE_STATS]; // can't have more "best" trainers than trainable stats
2023 	std::fill(std::begin(pStatTrainerList), std::end(pStatTrainerList), nullptr);
2024 
2025 	// build list of teammate trainers in this sector.
2026 
2027 	// Only the trainer with the HIGHEST training ability in each stat is effective.  This is mainly to avoid having to
2028 	// sort them from highest to lowest if some form of trainer degradation formula was to be used for multiple trainers.
2029 
2030 	// for each trainable stat
2031 	for (ubStat = 0; ubStat < NUM_TRAINABLE_STATS; ubStat++)
2032 	{
2033 		sBestTrainingPts = -1;
2034 
2035 		// search team for active instructors in this sector
2036 		CFOR_EACH_IN_TEAM(pTrainer, OUR_TEAM)
2037 		{
2038 			if (pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->bSectorZ == bZ)
2039 			{
2040 				// if he's training teammates in this stat
2041 				if (pTrainer->bAssignment == TRAIN_TEAMMATE &&
2042 						pTrainer->bTrainStat  == ubStat         &&
2043 						EnoughTimeOnAssignment(*pTrainer)       &&
2044 						!pTrainer->fMercAsleep)
2045 				{
2046 					sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, NULL, ubStat, fAtGunRange, &usMaxPts );
2047 
2048 					// if he's the best trainer so far for this stat
2049 					if (sTrainingPtsDueToInstructor > sBestTrainingPts)
2050 					{
2051 						// then remember him as that, and the points he scored
2052 						pStatTrainerList[ ubStat ] = pTrainer;
2053 						sBestTrainingPts = sTrainingPtsDueToInstructor;
2054 					}
2055 				}
2056 			}
2057 		}
2058 	}
2059 
2060 
2061 	// now search team for active self-trainers in this sector
2062 	FOR_EACH_IN_TEAM(pStudent, OUR_TEAM)
2063 	{
2064 		// see if this merc is active and in the same sector
2065 		if (pStudent->sSectorX == sMapX && pStudent->sSectorY == sMapY && pStudent->bSectorZ == bZ)
2066 		{
2067 			// if he's training himself (alone, or by others), then he's a student
2068 			if ( ( pStudent -> bAssignment == TRAIN_SELF ) || ( pStudent -> bAssignment == TRAIN_BY_OTHER ) )
2069 			{
2070 				if (EnoughTimeOnAssignment(*pStudent) && !pStudent->fMercAsleep)
2071 				{
2072 					// figure out how much the grunt can learn in one training period
2073 					sTotalTrainingPts = GetSoldierTrainingPts( pStudent, pStudent -> bTrainStat, fAtGunRange, &usMaxPts );
2074 
2075 					// if he's getting help
2076 					if ( pStudent -> bAssignment == TRAIN_BY_OTHER )
2077 					{
2078 						// grab the pointer to the (potential) trainer for this stat
2079 						const SOLDIERTYPE* const pTrainer = pStatTrainerList[pStudent->bTrainStat];
2080 
2081 						// if this stat HAS a trainer in sector at all
2082 						if (pTrainer != NULL)
2083 						{
2084 /* Assignment distance limits removed.  Sep/11/98.  ARM
2085 							// if this sector either ISN'T currently loaded, or it is but the trainer is close enough to the student
2086 							if ( ( sMapX != gWorldSectorX ) || ( sMapY != gWorldSectorY ) || ( pStudent -> bSectorZ != gbWorldSectorZ ) ||
2087 									PythSpacesAway(pStudent->sGridNo, pTrainer->sGridNo) < MAX_DISTANCE_FOR_TRAINING &&
2088 									EnoughTimeOnAssignment(*pTrainer))
2089 */
2090 							// NB this EnoughTimeOnAssignment() call is redundent since it is called up above
2091 							//if (EnoughTimeOnAssignment(*pTrainer))
2092 							{
2093 								// valid trainer is available, this gives the student a large training bonus!
2094 								sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, pStudent, pStudent -> bTrainStat, fAtGunRange, &usMaxPts );
2095 
2096 								// add the bonus to what merc can learn on his own
2097 								sTotalTrainingPts += sTrainingPtsDueToInstructor;
2098 							}
2099 						}
2100 					}
2101 
2102 					// now finally train the grunt
2103 					TrainSoldierWithPts( pStudent, sTotalTrainingPts );
2104 				}
2105 			}
2106 		}
2107 	}
2108 
2109 	// check if we're doing a sector where militia can be trained
2110 	if (CanSectorContainMilita(sMapX, sMapY, bZ))
2111 	{
2112 		// init town trainer list
2113 		std::fill(std::begin(TownTrainer), std::end(TownTrainer), TOWN_TRAINER_TYPE{});
2114 		ubTownTrainers = 0;
2115 
2116 		// build list of all the town trainers in this sector and their training pts
2117 		FOR_EACH_IN_TEAM(pTrainer, OUR_TEAM)
2118 		{
2119 			if (pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->bSectorZ == bZ)
2120 			{
2121 				if (pTrainer->bAssignment == TRAIN_TOWN &&
2122 						EnoughTimeOnAssignment(*pTrainer)   &&
2123 						!pTrainer->fMercAsleep)
2124 				{
2125 					sTownTrainingPts = GetTownTrainPtsForCharacter( pTrainer, &usMaxPts );
2126 
2127 					// if he's actually worth anything
2128 					if( sTownTrainingPts > 0 )
2129 					{
2130 						// remember this guy as a town trainer
2131 						TownTrainer[ubTownTrainers].sTrainingPts = sTownTrainingPts;
2132 						TownTrainer[ubTownTrainers].pSoldier = pTrainer;
2133 						ubTownTrainers++;
2134 					}
2135 				}
2136 			}
2137 		}
2138 
2139 
2140 		// if we have more than one
2141 		if (ubTownTrainers > 1)
2142 		{
2143 			// sort the town trainer list from best trainer to worst
2144 			qsort( TownTrainer, ubTownTrainers, sizeof(TOWN_TRAINER_TYPE), TownTrainerQsortCompare);
2145 		}
2146 
2147 		// for each trainer, in sorted order from the best to the worst
2148 		for (UINT32 uiCnt = 0; uiCnt < ubTownTrainers; uiCnt++)
2149 		{
2150 			// top trainer has full effect (divide by 1), then divide by 2, 4, 8, etc.
2151 			//sTownTrainingPts = TownTrainer[ uiCnt ].sTrainingPts / (UINT16) pow(2, uiCnt);
2152 			// CJC: took this out and replaced with limit of 2 guys per sector
2153 			sTownTrainingPts = TownTrainer[ uiCnt ].sTrainingPts;
2154 
2155 			if (sTownTrainingPts > 0)
2156 			{
2157 				fTrainingCompleted = TrainTownInSector( TownTrainer[ uiCnt ].pSoldier, sMapX, sMapY, sTownTrainingPts );
2158 
2159 				if ( fTrainingCompleted )
2160 				{
2161 					// there's no carryover into next session for extra training (cause player might cancel), so break out of loop
2162 					break;
2163 				}
2164 			}
2165 		}
2166 	}
2167 }
2168 
2169 
TownTrainerQsortCompare(const void * pArg1,const void * pArg2)2170 static int TownTrainerQsortCompare(const void* pArg1, const void* pArg2)
2171 {
2172 	const TOWN_TRAINER_TYPE* const t1 = (const TOWN_TRAINER_TYPE*)pArg1;
2173 	const TOWN_TRAINER_TYPE* const t2 = (const TOWN_TRAINER_TYPE*)pArg2;
2174 	return (t1->sTrainingPts < t2->sTrainingPts) - (t1->sTrainingPts > t2->sTrainingPts);
2175 }
2176 
2177 
GetBonusTrainingPtsDueToInstructor(const SOLDIERTYPE * pInstructor,const SOLDIERTYPE * pStudent,INT8 bTrainStat,BOOLEAN fAtGunRange,UINT16 * pusMaxPts)2178 INT16 GetBonusTrainingPtsDueToInstructor(const SOLDIERTYPE* pInstructor, const SOLDIERTYPE* pStudent, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16* pusMaxPts)
2179 {
2180 	// return the bonus training pts of this instructor with this student,...if student null, simply assignment student skill of 0 and student wisdom of 100
2181 	INT8 bTraineeEffWisdom = 0;
2182 	INT8 bTraineeNatWisdom = 0;
2183 	INT8 bTraineeSkill = 0;
2184 	INT8 bTrainerEffSkill = 0;
2185 	INT8 bTrainerNatSkill = 0;
2186 	INT8 bTrainingBonus = 0;
2187 	INT8 bOpinionFactor;
2188 
2189 	// assume training impossible for max pts
2190 	*pusMaxPts = 0;
2191 
2192 	if( pInstructor == NULL )
2193 	{
2194 		// no instructor, leave
2195 		return ( 0 );
2196 	}
2197 
2198 
2199 	switch( bTrainStat )
2200 	{
2201 		case( STRENGTH ):
2202 			bTrainerEffSkill = EffectiveStrength ( pInstructor );
2203 			bTrainerNatSkill = pInstructor->bStrength;
2204 		break;
2205 		case( DEXTERITY ):
2206 			bTrainerEffSkill = EffectiveDexterity ( pInstructor );
2207 			bTrainerNatSkill = pInstructor->bDexterity;
2208 		break;
2209 		case( AGILITY ):
2210 			bTrainerEffSkill = EffectiveAgility( pInstructor );
2211 			bTrainerNatSkill = pInstructor->bAgility;
2212 		break;
2213 		case( HEALTH ):
2214 			bTrainerEffSkill = pInstructor -> bLifeMax;
2215 			bTrainerNatSkill = pInstructor->bLifeMax;
2216 		break;
2217 		case( LEADERSHIP ):
2218 			bTrainerEffSkill = EffectiveLeadership( pInstructor );
2219 			bTrainerNatSkill = pInstructor->bLeadership;
2220 		break;
2221 		case( MARKSMANSHIP ):
2222 			bTrainerEffSkill = EffectiveMarksmanship( pInstructor );
2223 			bTrainerNatSkill = pInstructor->bMarksmanship;
2224 		break;
2225 		case( EXPLOSIVE_ASSIGN ):
2226 			bTrainerEffSkill = EffectiveExplosive( pInstructor );
2227 			bTrainerNatSkill = pInstructor->bExplosive;
2228 		break;
2229 		case( MEDICAL ):
2230 			bTrainerEffSkill = EffectiveMedical( pInstructor );
2231 			bTrainerNatSkill = pInstructor->bMedical;
2232 		break;
2233 		case( MECHANICAL ):
2234 			bTrainerEffSkill = EffectiveMechanical( pInstructor );
2235 			bTrainerNatSkill = pInstructor->bMechanical;
2236 		break;
2237 		// NOTE: Wisdom can't be trained!
2238 		default:
2239 			// BETA message
2240 			SLOGE("GetBonusTrainingPtsDueToInstructor: Unknown bTrainStat %d", bTrainStat);
2241 			return(0);
2242 	}
2243 
2244 
2245 	// if there's no student
2246 	if( pStudent == NULL )
2247 	{
2248 		// assume these default values
2249 		bTraineeEffWisdom = 100;
2250 		bTraineeNatWisdom = 100;
2251 		bTraineeSkill     =   0;
2252 		bOpinionFactor    =   0;
2253 	}
2254 	else
2255 	{
2256 		// set student's variables
2257 		bTraineeEffWisdom = EffectiveWisdom ( pStudent );
2258 		bTraineeNatWisdom = pStudent->bWisdom;
2259 
2260 		// for trainee's stat skill, must use the natural value, not the effective one, to avoid drunks training beyond cap
2261 		bTraineeSkill = GetTrainingStatValue(pStudent, bTrainStat);
2262 
2263 		// if trainee skill 0 or at/beyond the training cap, can't train
2264 		if ( ( bTraineeSkill == 0 ) || ( bTraineeSkill >= TRAINING_RATING_CAP ) )
2265 		{
2266 			return 0;
2267 		}
2268 
2269 		// factor in their mutual relationship
2270 		bOpinionFactor  = gMercProfiles[    pStudent->ubProfile ].bMercOpinion[ pInstructor->ubProfile ];
2271 		bOpinionFactor += gMercProfiles[ pInstructor->ubProfile ].bMercOpinion[    pStudent->ubProfile ] / 2;
2272 	}
2273 
2274 
2275 	// check to see if student better than/equal to instructor's effective skill, if so, return 0
2276 	// don't use natural skill - if the guy's too doped up to tell what he know, student learns nothing until sobriety returns!
2277 	if( bTraineeSkill >= bTrainerEffSkill )
2278 	{
2279 		return ( 0 );
2280 	}
2281 
2282 	// calculate effective training pts
2283 	UINT16 sTrainingPts = (bTrainerEffSkill - bTraineeSkill) * (bTraineeEffWisdom + (EffectiveWisdom(pInstructor) + EffectiveLeadership(pInstructor)) / 2) / INSTRUCTED_TRAINING_DIVISOR;
2284 
2285 	// calculate normal training pts - what it would be if his stats were "normal" (ignoring drugs, fatigue)
2286 	*pusMaxPts   = ( bTrainerNatSkill - bTraineeSkill ) * ( bTraineeNatWisdom + ( pInstructor->bWisdom + pInstructor->bLeadership ) / 2 ) / INSTRUCTED_TRAINING_DIVISOR;
2287 
2288 	// put in a minimum (that can be reduced due to instructor being tired?)
2289 	if (*pusMaxPts == 0)
2290 	{
2291 		// we know trainer is better than trainee, make sure they are at least 10 pts better
2292 		if ( bTrainerEffSkill > bTraineeSkill + 10 )
2293 		{
2294 			sTrainingPts = 1;
2295 			*pusMaxPts = 1;
2296 		}
2297 	}
2298 
2299 	// check for teaching skill bonuses
2300 	if( gMercProfiles[ pInstructor -> ubProfile ].bSkillTrait == TEACHING )
2301 	{
2302 		bTrainingBonus += TEACH_BONUS_TO_TRAIN;
2303 	}
2304 	if( gMercProfiles[ pInstructor -> ubProfile ].bSkillTrait2 == TEACHING )
2305 	{
2306 		bTrainingBonus += TEACH_BONUS_TO_TRAIN;
2307 	}
2308 
2309 	// teaching bonus is counted as normal, but gun range bonus is not
2310 	*pusMaxPts += ( ( ( bTrainingBonus + bOpinionFactor ) * *pusMaxPts ) / 100 );
2311 
2312 	// get special bonus if we're training marksmanship and we're in the gun range sector in Alma
2313 	if ( ( bTrainStat == MARKSMANSHIP ) && fAtGunRange )
2314 	{
2315 		bTrainingBonus += GUN_RANGE_TRAINING_BONUS;
2316 	}
2317 
2318 	// adjust for any training bonuses and for the relationship
2319 	sTrainingPts += ( ( ( bTrainingBonus + bOpinionFactor ) * sTrainingPts ) / 100 );
2320 
2321 	// adjust for instructor fatigue
2322 	ReducePointsForFatigue( pInstructor, &sTrainingPts );
2323 
2324 	return( sTrainingPts );
2325 }
2326 
2327 
GetSoldierTrainingPts(const SOLDIERTYPE * s,INT8 bTrainStat,BOOLEAN fAtGunRange,UINT16 * pusMaxPts)2328 INT16 GetSoldierTrainingPts(const SOLDIERTYPE* s, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16* pusMaxPts)
2329 {
2330 	INT8	bTrainingBonus = 0;
2331 
2332 	// assume training impossible for max pts
2333 	*pusMaxPts = 0;
2334 
2335 	// use NATURAL not EFFECTIVE values here
2336 	const INT8 bSkill = GetTrainingStatValue(s, bTrainStat);
2337 
2338 	// if skill 0 or at/beyond the training cap, can't train
2339 	if ( ( bSkill == 0 ) || ( bSkill >= TRAINING_RATING_CAP ) )
2340 	{
2341 		return 0;
2342 	}
2343 
2344 
2345 	// calculate normal training pts - what it would be if his stats were "normal" (ignoring drugs, fatigue)
2346 	*pusMaxPts = __max(s->bWisdom * (TRAINING_RATING_CAP - bSkill) / SELF_TRAINING_DIVISOR, 1);
2347 
2348 	// calculate effective training pts
2349 	UINT16 sTrainingPts = __max(EffectiveWisdom(s) * (TRAINING_RATING_CAP - bSkill) / SELF_TRAINING_DIVISOR, 1);
2350 
2351 	// get special bonus if we're training marksmanship and we're in the gun range sector in Alma
2352 	if ( ( bTrainStat == MARKSMANSHIP ) && fAtGunRange )
2353 	{
2354 		bTrainingBonus += GUN_RANGE_TRAINING_BONUS;
2355 	}
2356 
2357 	// adjust for any training bonuses
2358 	sTrainingPts += ( ( bTrainingBonus * sTrainingPts ) / 100 );
2359 
2360 	// adjust for fatigue
2361 	ReducePointsForFatigue(s, &sTrainingPts);
2362 
2363 	return( sTrainingPts );
2364 }
2365 
2366 
GetSoldierStudentPts(const SOLDIERTYPE * s,INT8 bTrainStat,BOOLEAN fAtGunRange,UINT16 * pusMaxPts)2367 INT16 GetSoldierStudentPts(const SOLDIERTYPE* s, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16* pusMaxPts)
2368 {
2369 	INT8	bTrainingBonus = 0;
2370 
2371 	INT16 sBestTrainingPts, sTrainingPtsDueToInstructor;
2372 	UINT16	usMaxTrainerPts;
2373 
2374 	// assume training impossible for max pts
2375 	*pusMaxPts = 0;
2376 
2377 	// use NATURAL not EFFECTIVE values here
2378 	const INT8 bSkill = GetTrainingStatValue(s, bTrainStat);
2379 
2380 	// if skill 0 or at/beyond the training cap, can't train
2381 	if ( ( bSkill == 0 ) || ( bSkill >= TRAINING_RATING_CAP ) )
2382 	{
2383 		return 0;
2384 	}
2385 
2386 
2387 	// calculate normal training pts - what it would be if his stats were "normal" (ignoring drugs, fatigue)
2388 	*pusMaxPts = __max(s->bWisdom * (TRAINING_RATING_CAP - bSkill) / SELF_TRAINING_DIVISOR, 1);
2389 
2390 	// calculate effective training pts
2391 	UINT16 sTrainingPts = __max(EffectiveWisdom(s) * (TRAINING_RATING_CAP - bSkill) / SELF_TRAINING_DIVISOR, 1);
2392 
2393 	// get special bonus if we're training marksmanship and we're in the gun range sector in Alma
2394 	if ( ( bTrainStat == MARKSMANSHIP ) && fAtGunRange )
2395 	{
2396 		bTrainingBonus += GUN_RANGE_TRAINING_BONUS;
2397 	}
2398 
2399 	// adjust for any training bonuses
2400 	sTrainingPts += ( ( bTrainingBonus * sTrainingPts ) / 100 );
2401 
2402 	// adjust for fatigue
2403 	ReducePointsForFatigue(s, &sTrainingPts);
2404 
2405 
2406 	// now add in stuff for trainer
2407 
2408 	// for each trainable stat
2409 	sBestTrainingPts = -1;
2410 	UINT16 usBestMaxTrainerPts = 0; // XXX HACK000E
2411 
2412 	// search team for active instructors in this sector
2413 	CFOR_EACH_IN_TEAM(pTrainer, OUR_TEAM)
2414 	{
2415 		if (pTrainer->sSectorX == s->sSectorX && pTrainer->sSectorY == s->sSectorY && pTrainer->bSectorZ == s->bSectorZ)
2416 		{
2417 			// if he's training teammates in this stat
2418 			// NB skip the EnoughTime requirement to display what the value should be even if haven't been training long yet...
2419 			if (pTrainer->bAssignment == TRAIN_TEAMMATE &&
2420 					pTrainer->bTrainStat  == bTrainStat     &&
2421 					!pTrainer->fMercAsleep)
2422 			{
2423 				sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor(pTrainer, s, bTrainStat, fAtGunRange, &usMaxTrainerPts);
2424 
2425 				// if he's the best trainer so far for this stat
2426 				if (sTrainingPtsDueToInstructor > sBestTrainingPts)
2427 				{
2428 					// then remember him as that, and the points he scored
2429 					sBestTrainingPts = sTrainingPtsDueToInstructor;
2430 					usBestMaxTrainerPts = usMaxTrainerPts;
2431 				}
2432 			}
2433 		}
2434 	}
2435 
2436 	if ( sBestTrainingPts != -1 )
2437 	{
2438 		// add the bonus to what merc can learn on his own
2439 		sTrainingPts += sBestTrainingPts;
2440 		*pusMaxPts += usBestMaxTrainerPts;
2441 	}
2442 
2443 	return( sTrainingPts );
2444 }
2445 
2446 
2447 // this function will actually pass on the pts to the mercs stat
TrainSoldierWithPts(SOLDIERTYPE * const s,const INT16 train_pts)2448 static void TrainSoldierWithPts(SOLDIERTYPE* const s, const INT16 train_pts)
2449 {
2450 	if (train_pts <= 0) return;
2451 
2452 	// which stat to modify?
2453 	StatKind stat;
2454 	switch (s->bTrainStat)
2455 	{
2456 		case STRENGTH:         stat = STRAMT;     break;
2457 		case DEXTERITY:        stat = DEXTAMT;    break;
2458 		case AGILITY:          stat = AGILAMT;    break;
2459 		case HEALTH:           stat = HEALTHAMT;  break;
2460 		case LEADERSHIP:       stat = LDRAMT;     break;
2461 		case MARKSMANSHIP:     stat = MARKAMT;    break;
2462 		case EXPLOSIVE_ASSIGN: stat = EXPLODEAMT; break;
2463 		case MEDICAL:          stat = MEDICALAMT; break;
2464 		case MECHANICAL:       stat = MECHANAMT;  break;
2465 		// NOTE: Wisdom can't be trained!
2466 		default:
2467 			// BETA message
2468 			SLOGE("TrainSoldierWithPts: Unknown bTrainStat %d", s->bTrainStat);
2469 			return;
2470 	}
2471 
2472 	// give this merc a few chances to increase a stat (TRUE means it's training, reverse evolution doesn't apply)
2473 	StatChange(*s, stat, train_pts, FROM_TRAINING);
2474 }
2475 
2476 
2477 // train militia in this sector with this soldier
TrainTownInSector(SOLDIERTYPE * pTrainer,INT16 sMapX,INT16 sMapY,INT16 sTrainingPts)2478 static BOOLEAN TrainTownInSector(SOLDIERTYPE* pTrainer, INT16 sMapX, INT16 sMapY, INT16 sTrainingPts)
2479 {
2480 	Assert(CanSectorContainMilita(pTrainer->sSectorX, pTrainer->sSectorY, pTrainer->bSectorZ));
2481 
2482 	SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
2483 
2484 	// trainer gains leadership - training argument is FROM_SUCCESS, because the trainer is not the one training!
2485 	StatChange(*pTrainer, LDRAMT, 1 + sTrainingPts / 200, FROM_SUCCESS);
2486 
2487 	// increase town's training completed percentage
2488 	pSectorInfo -> ubMilitiaTrainingPercentDone += (sTrainingPts / 100);
2489 	pSectorInfo -> ubMilitiaTrainingHundredths  += (sTrainingPts % 100);
2490 
2491 	if (pSectorInfo -> ubMilitiaTrainingHundredths >= 100)
2492 	{
2493 		pSectorInfo -> ubMilitiaTrainingPercentDone++;
2494 		pSectorInfo -> ubMilitiaTrainingHundredths -= 100;
2495 	}
2496 
2497 	// NOTE: Leave this at 100, change TOWN_TRAINING_RATE if necessary.  This value gets reported to player as a %age!
2498 	if( pSectorInfo -> ubMilitiaTrainingPercentDone >= 100 )
2499 	{
2500 		// zero out training completion - there's no carryover to the next training session
2501 		pSectorInfo -> ubMilitiaTrainingPercentDone = 0;
2502 		pSectorInfo -> ubMilitiaTrainingHundredths  = 0;
2503 
2504 		// make the player pay again next time he wants to train here
2505 		pSectorInfo -> fMilitiaTrainingPaid = FALSE;
2506 
2507 		TownMilitiaTrainingCompleted( pTrainer, sMapX, sMapY );
2508 
2509 		// training done
2510 		return( TRUE );
2511 	}
2512 	else
2513 	{
2514 		// not done
2515 		return ( FALSE );
2516 	}
2517 }
2518 
2519 
GetTownTrainPtsForCharacter(const SOLDIERTYPE * pTrainer,UINT16 * pusMaxPts)2520 INT16 GetTownTrainPtsForCharacter(const SOLDIERTYPE* pTrainer, UINT16* pusMaxPts)
2521 {
2522 	INT8 bTrainingBonus = 0;
2523 //	UINT8 ubTownId = 0;
2524 
2525 	// calculate normal training pts - what it would be if his stats were "normal" (ignoring drugs, fatigue)
2526 	*pusMaxPts = ( pTrainer -> bWisdom + pTrainer -> bLeadership + ( 10 * pTrainer -> bExpLevel ) ) * TOWN_TRAINING_RATE;
2527 
2528 	// calculate effective training points (this is hundredths of pts / hour)
2529 	// typical: 300/hr, maximum: 600/hr
2530 	UINT16 sTotalTrainingPts = (EffectiveWisdom(pTrainer) + EffectiveLeadership(pTrainer) + 10 * EffectiveExpLevel(pTrainer)) * TOWN_TRAINING_RATE;
2531 
2532 	// check for teaching bonuses
2533 	if( gMercProfiles[ pTrainer -> ubProfile ].bSkillTrait == TEACHING )
2534 	{
2535 		bTrainingBonus += TEACH_BONUS_TO_TRAIN;
2536 	}
2537 	if( gMercProfiles[ pTrainer -> ubProfile ].bSkillTrait2 == TEACHING )
2538 	{
2539 		bTrainingBonus += TEACH_BONUS_TO_TRAIN;
2540 	}
2541 
2542 	// RPCs get a small training bonus for being more familiar with the locals and their customs/needs than outsiders
2543 	if (MercProfile(pTrainer->ubProfile).isNPCorRPC())
2544 	{
2545 		bTrainingBonus += RPC_BONUS_TO_TRAIN;
2546 	}
2547 
2548 	// adjust for teaching bonus (a percentage)
2549 	sTotalTrainingPts += ( ( bTrainingBonus * sTotalTrainingPts ) / 100 );
2550 	// teach bonus is considered "normal" - it's always there
2551 	*pusMaxPts				+= ( ( bTrainingBonus * *pusMaxPts        ) / 100 );
2552 
2553 
2554 	// adjust for fatigue of trainer
2555 	ReducePointsForFatigue( pTrainer, &sTotalTrainingPts );
2556 
2557 
2558 /* ARM: Decided this didn't make much sense - the guys I'm training damn well BETTER be loyal - and screw the rest!
2559 	// get town index
2560 	ubTownId = StrategicMap[ pTrainer -> sSectorX + pTrainer -> sSectorY * MAP_WORLD_X ].bNameId;
2561 	Assert(ubTownId != BLANK_SECTOR);
2562 
2563 	// adjust for town loyalty
2564 	sTotalTrainingPts = (sTotalTrainingPts * gTownLoyalty[ ubTownId ].ubRating) / 100;
2565 */
2566 
2567 	return( sTotalTrainingPts );
2568 }
2569 
2570 
MakeSoldiersTacticalAnimationReflectAssignment(SOLDIERTYPE * const s)2571 void MakeSoldiersTacticalAnimationReflectAssignment(SOLDIERTYPE* const s)
2572 {
2573 	if (!s->bInSector || !gfWorldLoaded || s->bLife < OKLIFE) return;
2574 	// soldier is in tactical, world loaded, he's OKLIFE
2575 
2576 	// Set animation based on his assignment
2577 	switch (s->bAssignment)
2578 	{
2579 		case DOCTOR:  SoldierInSectorDoctor( s, s->usStrategicInsertionData); break;
2580 		case PATIENT: SoldierInSectorPatient(s, s->usStrategicInsertionData); break;
2581 		case REPAIR:  SoldierInSectorRepair( s, s->usStrategicInsertionData); break;
2582 
2583 		default:
2584 			if (s->usAnimState != WKAEUP_FROM_SLEEP)
2585 			{
2586 				ChangeSoldierState(s, STANDING, 1, TRUE);
2587 			}
2588 			break;
2589 	}
2590 }
2591 
2592 
AssignmentAborted(SOLDIERTYPE const & s,AssignmentAbortReason const reason)2593 static void AssignmentAborted(SOLDIERTYPE const& s, AssignmentAbortReason const reason)
2594 {
2595 	Assert(reason < NUM_ASSIGN_ABORT_REASONS);
2596 	ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(gzLateLocalizedString[reason], s.name));
2597 	StopTimeCompression();
2598 	fCharacterInfoPanelDirty = TRUE;
2599 	fTeamPanelDirty          = TRUE;
2600 	fMapScreenBottomDirty    = TRUE;
2601 }
2602 
2603 
AssignmentDone(SOLDIERTYPE * pSoldier,BOOLEAN fSayQuote,BOOLEAN fMeToo)2604 void AssignmentDone( SOLDIERTYPE *pSoldier, BOOLEAN fSayQuote, BOOLEAN fMeToo )
2605 {
2606 	if ( ( pSoldier -> bInSector ) && ( gfWorldLoaded ) )
2607 	{
2608 		if ( pSoldier -> bAssignment == DOCTOR )
2609 		{
2610 			const UINT16 state = (guiCurrentScreen == GAME_SCREEN ? END_DOCTOR : STANDING);
2611 			ChangeSoldierState(pSoldier, state, 1, TRUE);
2612 		}
2613 		else if ( pSoldier -> bAssignment == REPAIR )
2614 		{
2615 			const UINT16 state = (guiCurrentScreen == GAME_SCREEN ? END_REPAIRMAN : STANDING);
2616 			ChangeSoldierState(pSoldier, state, 1, TRUE);
2617 		}
2618 		else if ( pSoldier -> bAssignment == PATIENT )
2619 		{
2620 			if ( guiCurrentScreen == GAME_SCREEN )
2621 			{
2622 				ChangeSoldierStance( pSoldier, ANIM_CROUCH );
2623 			}
2624 			else
2625 			{
2626 				ChangeSoldierState( pSoldier, STANDING, 1, TRUE );
2627 			}
2628 		}
2629 	}
2630 
2631 	if ( pSoldier->bAssignment == ASSIGNMENT_HOSPITAL )
2632 	{
2633 		// hack - reset AbsoluteFinalDestination in case it was left non-nowhere
2634 		pSoldier->sAbsoluteFinalDestination = NOWHERE;
2635 	}
2636 
2637 	if ( fSayQuote )
2638 	{
2639 		if (!fMeToo && pSoldier->bAssignment == TRAIN_TOWN)
2640 		{
2641 			TacticalCharacterDialogue( pSoldier, QUOTE_ASSIGNMENT_COMPLETE );
2642 
2643 			if( pSoldier -> bAssignment == TRAIN_TOWN )
2644 			{
2645 				AddSectorForSoldierToListOfSectorsThatCompletedMilitiaTraining( pSoldier );
2646 			}
2647 		}
2648 	}
2649 
2650 	// don't bother telling us again about guys we already know about
2651 	if ( !( pSoldier->usQuoteSaidExtFlags & SOLDIER_QUOTE_SAID_DONE_ASSIGNMENT ) )
2652 	{
2653 		pSoldier->usQuoteSaidExtFlags |= SOLDIER_QUOTE_SAID_DONE_ASSIGNMENT;
2654 
2655 		if ( fSayQuote )
2656 		{
2657 			if ( pSoldier->bAssignment == DOCTOR || pSoldier->bAssignment == REPAIR ||
2658 					pSoldier->bAssignment == PATIENT || pSoldier->bAssignment == ASSIGNMENT_HOSPITAL)
2659 			{
2660 				TacticalCharacterDialogue( pSoldier, QUOTE_ASSIGNMENT_COMPLETE );
2661 			}
2662 		}
2663 
2664 
2665 		AddReasonToWaitingListQueue( ASSIGNMENT_FINISHED_FOR_UPDATE );
2666 		AddSoldierToWaitingListQueue(*pSoldier);
2667 
2668 		// trigger a single call AddDisplayBoxToWaitingQueue for assignments done
2669 		gfAddDisplayBoxToWaitingQueue = TRUE;
2670 	}
2671 
2672 	// update mapscreen
2673 	fCharacterInfoPanelDirty = TRUE;
2674 	fTeamPanelDirty = TRUE;
2675 	fMapScreenBottomDirty = TRUE;
2676 }
2677 
2678 
2679 static void HandleHealingByNaturalCauses(SOLDIERTYPE* pSoldier);
2680 
2681 
2682 // handle natural healing for all mercs on players team
HandleNaturalHealing(void)2683 static void HandleNaturalHealing(void)
2684 {
2685 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
2686 	{
2687 		SOLDIERTYPE& s = *i;
2688 		// mechanical members don't regenerate!
2689 		if (IsMechanical(s)) continue;
2690 		HandleHealingByNaturalCauses(&s);
2691 	}
2692 }
2693 
2694 
2695 static void UpDateSoldierLife(SOLDIERTYPE* pSoldier);
2696 
2697 
2698 // handle healing of this soldier by natural causes.
HandleHealingByNaturalCauses(SOLDIERTYPE * pSoldier)2699 static void HandleHealingByNaturalCauses(SOLDIERTYPE* pSoldier)
2700 {
2701 	UINT32 uiPercentHealth = 0;
2702 	INT8 bActivityLevelDivisor = 0;
2703 
2704 
2705 	// check if soldier valid
2706 	if( pSoldier == NULL )
2707 	{
2708 		return;
2709 	}
2710 
2711 	// dead
2712 	if( pSoldier -> bLife == 0 )
2713 	{
2714 		return;
2715 	}
2716 
2717 	// lost any pts?
2718 	if( pSoldier -> bLife == pSoldier -> bLifeMax )
2719 	{
2720 		return;
2721 	}
2722 
2723 	// any bleeding pts - can' recover if still bleeding!
2724 	if( pSoldier -> bBleeding != 0 )
2725 	{
2726 		return;
2727 	}
2728 
2729 
2730 	// not bleeding and injured...
2731 
2732 	if( pSoldier -> bAssignment == ASSIGNMENT_POW )
2733 	{
2734 		// use high activity level to simulate stress, torture, poor conditions for healing
2735 		bActivityLevelDivisor = HIGH_ACTIVITY_LEVEL;
2736 	}
2737 	if (pSoldier->fMercAsleep            ||
2738 			pSoldier->bAssignment == PATIENT ||
2739 			pSoldier->bAssignment == ASSIGNMENT_HOSPITAL)
2740 	{
2741 		bActivityLevelDivisor = LOW_ACTIVITY_LEVEL;
2742 	}
2743 	else if ( pSoldier->bAssignment < ON_DUTY )
2744 	{
2745 		// if time is being compressed, and the soldier is not moving strategically
2746 		if ( IsTimeBeingCompressed() && !PlayerIDGroupInMotion( pSoldier->ubGroupID ) )
2747 		{
2748 			// basically resting
2749 			bActivityLevelDivisor = LOW_ACTIVITY_LEVEL;
2750 		}
2751 		else
2752 		{
2753 			// either they're on the move, or they're being tactically active
2754 			bActivityLevelDivisor = HIGH_ACTIVITY_LEVEL;
2755 		}
2756 	}
2757 	else	// this includes being in a vehicle - that's neither very strenous, nor very restful
2758 	{
2759 		bActivityLevelDivisor = MEDIUM_ACTIVITY_LEVEL;
2760 	}
2761 
2762 
2763 	// what percentage of health is he down to
2764 	uiPercentHealth = ( pSoldier->bLife * 100 ) / pSoldier->bLifeMax;
2765 
2766 	// gain that many hundredths of life points back, divided by the activity level modifier
2767 	pSoldier->sFractLife += ( INT16 ) ( uiPercentHealth / bActivityLevelDivisor );
2768 
2769 	// now update the real life values
2770 	UpDateSoldierLife( pSoldier );
2771 }
2772 
2773 
UpDateSoldierLife(SOLDIERTYPE * pSoldier)2774 static void UpDateSoldierLife(SOLDIERTYPE* pSoldier)
2775 {
2776 	// update soldier life, make sure we don't go out of bounds
2777 	pSoldier -> bLife += pSoldier -> sFractLife / 100;
2778 
2779 	// keep remaining fract of life
2780 	pSoldier -> sFractLife %= 100;
2781 
2782 	// check if we have gone too far
2783 	if( pSoldier -> bLife >= pSoldier -> bLifeMax )
2784 	{
2785 		// reduce
2786 		pSoldier -> bLife = pSoldier -> bLifeMax;
2787 		pSoldier -> sFractLife = 0;
2788 	}
2789 }
2790 
2791 
CheckIfSoldierUnassigned(SOLDIERTYPE * pSoldier)2792 void CheckIfSoldierUnassigned( SOLDIERTYPE *pSoldier )
2793 {
2794 	if( pSoldier -> bAssignment == NO_ASSIGNMENT )
2795 	{
2796 		// unassigned
2797 		AddCharacterToAnySquad( pSoldier );
2798 
2799 		if( ( gfWorldLoaded ) && ( pSoldier->bInSector ) )
2800 		{
2801 			ChangeSoldierState( pSoldier, STANDING, 1, TRUE );
2802 		}
2803 	}
2804 }
2805 
2806 
2807 static void CreateDestroyMouseRegionsForRemoveMenu(void);
2808 
2809 
2810 // Check if we can only remove character from team
HandleRemoveMenu(const INT8 remove_char)2811 static BOOLEAN HandleRemoveMenu(const INT8 remove_char)
2812 {
2813 	static BOOLEAN fShowRemoveMenu = FALSE;
2814 
2815 	if (!fShowRemoveMenu)
2816 	{
2817 		if (remove_char == -1) return FALSE;
2818 		const SOLDIERTYPE* const s = gCharactersList[remove_char].merc;
2819 		if (s->bLife != 0 && s->bAssignment != ASSIGNMENT_POW) return FALSE;
2820 
2821 		bSelectedAssignChar = remove_char;
2822 	}
2823 
2824 	// dead guy handle menu stuff
2825 	fShowRemoveMenu = fShowAssignmentMenu | fShowContractMenu;
2826 	CreateDestroyMouseRegionsForRemoveMenu();
2827 	return TRUE;
2828 }
2829 
2830 
2831 static void AssignmentMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason);
2832 static void AssignmentMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
2833 static void CheckAndUpdateTacticalAssignmentPopUpPositions(void);
2834 static SOLDIERTYPE* GetSelectedAssignSoldier(BOOLEAN fNullOK);
2835 static void PositionCursorForTacticalAssignmentBox(void);
2836 
2837 
2838 // Create/destroy mouse regions for the map screen assignment main menu
CreateDestroyMouseRegionsForAssignmentMenu(void)2839 static void CreateDestroyMouseRegionsForAssignmentMenu(void)
2840 {
2841 	static BOOLEAN fCreated = FALSE;
2842 
2843 	if (HandleRemoveMenu(bSelectedAssignChar)) return;
2844 
2845 	if (fShowAssignmentMenu && !fCreated)
2846 	{
2847 		gfIgnoreScrolling = FALSE;
2848 
2849 		if (guiCurrentScreen == MAP_SCREEN)
2850 		{
2851 			SetBoxXY(ghAssignmentBox, AssignmentPosition.iX, AssignmentPosition.iY);
2852 		}
2853 
2854 		SOLDIERTYPE     const& s    = *GetSelectedAssignSoldier(FALSE);
2855 		PopUpBox const* const  box  = s.ubWhatKindOfMercAmI == MERC_TYPE__EPC ? ghEpcBox : ghAssignmentBox;
2856 		SGPBox          const& area = GetBoxArea(box);
2857 		UINT16          const  x    = area.x;
2858 		UINT16                 y    = area.y + GetTopMarginSize(ghAssignmentBox);
2859 		UINT16          const  w    = area.w;
2860 		UINT16          const  dy   = GetLineSpace(box) + GetFontHeight(GetBoxFont(box));
2861 
2862 		// Add mouse region for each line of text and set user data
2863 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(ghAssignmentBox); ++i)
2864 		{
2865 			MOUSE_REGION* const r = &gAssignmentMenuRegion[i];
2866 			MSYS_DefineRegion(r, x, y, x + w, y + dy, MSYS_PRIORITY_HIGHEST - 4, MSYS_NO_CURSOR, AssignmentMenuMvtCallBack, AssignmentMenuBtnCallback);
2867 			MSYS_SetRegionUserData(r, 0, i);
2868 			y += dy;
2869 		}
2870 
2871 		UnHighLightBox(ghAssignmentBox); // unhighlight all strings in box
2872 		CheckAndUpdateTacticalAssignmentPopUpPositions();
2873 		PositionCursorForTacticalAssignmentBox();
2874 
2875 		fCreated = TRUE;
2876 	}
2877 	else if (!fShowAssignmentMenu && fCreated)
2878 	{
2879 		// destroy
2880 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(ghAssignmentBox); ++i)
2881 		{
2882 			MSYS_RemoveRegion(&gAssignmentMenuRegion[i]);
2883 		}
2884 
2885 		fShownAssignmentMenu = FALSE;
2886 
2887 		SetRenderFlags(RENDER_FLAG_FULL);
2888 
2889 		fCreated = FALSE;
2890 	}
2891 }
2892 
2893 
2894 static void HandleShadingOfLinesForVehicleMenu(void);
2895 static void VehicleMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason);
2896 static void VehicleMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
2897 static void VehicleMenuCancelBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
2898 
2899 
CreateDestroyMouseRegionForVehicleMenu(void)2900 static void CreateDestroyMouseRegionForVehicleMenu(void)
2901 {
2902 	static BOOLEAN fCreated = FALSE;
2903 
2904 	PopUpBox* const box = ghVehicleBox;
2905 
2906 	if (fShowVehicleMenu)
2907 	{
2908 		SGPBox const& area = GetBoxArea(ghAssignmentBox);
2909 		VehiclePosition.iX = area.x + area.w;
2910 		SetBoxXY(box, VehiclePosition.iX, VehiclePosition.iY);
2911 	}
2912 
2913 	if (!fCreated && fShowVehicleMenu)
2914 	{
2915 		SGPBox      const& area = GetBoxArea(box);
2916 		UINT16      const  x    = area.x;
2917 		UINT16                    y    = area.y + GetTopMarginSize(ghAssignmentBox); // XXX wrong box?
2918 		UINT16      const  w    = area.w;
2919 		UINT16      const  h    = GetLineSpace(box) + GetFontHeight(GetBoxFont(box));
2920 		MOUSE_REGION*      r    = gVehicleMenuRegion;
2921 		SOLDIERTYPE const& s    = *GetSelectedAssignSoldier(FALSE);
2922 		FOR_EACH_VEHICLE(v)
2923 		{
2924 			if (!IsThisVehicleAccessibleToSoldier(s, v)) continue;
2925 
2926 			// add mouse region for each accessible vehicle
2927 			MSYS_DefineRegion(r, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 4, MSYS_NO_CURSOR, VehicleMenuMvtCallback, VehicleMenuBtnCallback);
2928 			r->SetUserPtr(&v);
2929 			y += h;
2930 			++r;
2931 		}
2932 
2933 		// cancel line
2934 		MSYS_DefineRegion(r, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 4, MSYS_NO_CURSOR, VehicleMenuMvtCallback, VehicleMenuCancelBtnCallback);
2935 
2936 		PauseGame();
2937 		UnHighLightBox(box);
2938 		HandleShadingOfLinesForVehicleMenu();
2939 
2940 		fCreated = TRUE;
2941 	}
2942 	else if (fCreated && (!fShowVehicleMenu || !fShowAssignmentMenu))
2943 	{
2944 		// remove these regions
2945 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(box); ++i)
2946 		{
2947 			MSYS_RemoveRegion(&gVehicleMenuRegion[i]);
2948 		}
2949 
2950 		fShowVehicleMenu = FALSE;
2951 
2952 		SetRenderFlags(RENDER_FLAG_FULL);
2953 		HideBox(box);
2954 
2955 		if (fShowAssignmentMenu) UnHighLightBox(ghAssignmentBox);
2956 
2957 		fCreated = FALSE;
2958 	}
2959 }
2960 
2961 
HandleShadingOfLinesForVehicleMenu()2962 static void HandleShadingOfLinesForVehicleMenu()
2963 {
2964 	if (!fShowVehicleMenu) return;
2965 
2966 	PopUpBox* const box = ghVehicleBox;
2967 	if (box == NO_POPUP_BOX) return;
2968 
2969 	SOLDIERTYPE const& s    = *GetSelectedAssignSoldier(FALSE);
2970 	UINT32             line = 0;
2971 	CFOR_EACH_VEHICLE(v)
2972 	{
2973 		// inaccessible vehicles aren't listed at all!
2974 		if (!IsThisVehicleAccessibleToSoldier(s, v)) continue;
2975 
2976 		PopUpShade const shade = IsEnoughSpaceInVehicle(v) ?
2977 			POPUP_SHADE_NONE : POPUP_SHADE_SECONDARY;
2978 		ShadeStringInBox(box, line++, shade);
2979 	}
2980 }
2981 
2982 
VehicleMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)2983 static void VehicleMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
2984 {
2985 	// btn callback handler for assignment region
2986 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
2987 	{
2988 		SOLDIERTYPE& s = *GetSelectedAssignSoldier(FALSE);
2989 		VEHICLETYPE& v = *pRegion->GetUserPtr<VEHICLETYPE>();
2990 
2991 		// inaccessible vehicles shouldn't be listed in the menu!
2992 		Assert(IsThisVehicleAccessibleToSoldier(s, v));
2993 
2994 		if (IsEnoughSpaceInVehicle(v))
2995 		{
2996 			PutSoldierInVehicle(s, v);
2997 		}
2998 		else
2999 		{
3000 			ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, st_format_printf(gzLateLocalizedString[STR_LATE_18], zVehicleName[v.ubVehicleType]));
3001 		}
3002 
3003 		fShowAssignmentMenu = FALSE;
3004 
3005 		// update mapscreen
3006 		fTeamPanelDirty          = TRUE;
3007 		fCharacterInfoPanelDirty = TRUE;
3008 		fMapScreenBottomDirty    = TRUE;
3009 		giAssignHighLine         = -1;
3010 
3011 		SetAssignmentForList(VEHICLE, VEHICLE2ID(v));
3012 	}
3013 }
3014 
3015 
VehicleMenuCancelBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)3016 static void VehicleMenuCancelBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
3017 {
3018 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
3019 	{
3020 		UnHighLightBox(ghAssignmentBox);
3021 		fShowVehicleMenu         = FALSE;
3022 		fTeamPanelDirty          = TRUE;
3023 		fMapScreenBottomDirty    = TRUE;
3024 		fCharacterInfoPanelDirty = TRUE;
3025 	}
3026 }
3027 
3028 
VehicleMenuMvtCallback(MOUSE_REGION * pRegion,INT32 iReason)3029 static void VehicleMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason)
3030 {
3031 	// mvt callback handler for assignment region
3032 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
3033 	{
3034 		const INT32 line = (INT32)(pRegion - gVehicleMenuRegion);
3035 		HighLightBoxLine(ghVehicleBox, line);
3036 	}
3037 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
3038 	{
3039 		// unhighlight all strings in box
3040 		UnHighLightBox( ghVehicleBox );
3041 
3042 		HandleShadingOfLinesForVehicleMenu( );
3043 	}
3044 }
3045 
3046 
SetBoxTextAttrs(PopUpBox * const box)3047 static void SetBoxTextAttrs(PopUpBox* const box)
3048 {
3049 	SetBoxFont(box, MAP_SCREEN_FONT);
3050 	SetBoxHighLight(box, FONT_WHITE);
3051 	SetBoxForeground(box, FONT_LTGREEN);
3052 	SetBoxBackground(box, FONT_BLACK);
3053 	SetBoxShade(box, FONT_GRAY7);
3054 	SetBoxSecondaryShade(box, FONT_YELLOW);
3055 }
3056 
3057 
3058 static PopUpBox* CreateRepairBox(void);
3059 static BOOLEAN IsRobotInThisSector(INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ);
3060 
3061 
DisplayRepairMenu(SOLDIERTYPE const & s)3062 static void DisplayRepairMenu(SOLDIERTYPE const& s)
3063 {
3064 	RemoveBox(ghRepairBox);
3065 	ghRepairBox = NO_POPUP_BOX;
3066 
3067 	/* PLEASE NOTE: make sure any changes you do here are reflected in all 3
3068 	 * routines which must remain in synch:
3069 	 * CreateDestroyMouseRegionForRepairMenu(), DisplayRepairMenu(), and
3070 	 * HandleShadingOfLinesForRepairMenu(). */
3071 	PopUpBox* const box = CreateRepairBox();
3072 
3073 	if (s.bSectorZ == 0)
3074 	{ // Run through list of vehicles in sector and add them to pop up box
3075 		CFOR_EACH_VEHICLE(v)
3076 		{
3077 			// Don't even list the helicopter, because it's never repairable
3078 			if (IsHelicopter(v))                         continue;
3079 			if (!IsThisVehicleAccessibleToSoldier(s, v)) continue;
3080 			AddMonoString(box, pVehicleStrings[v.ubVehicleType]);
3081 		}
3082 	}
3083 
3084 	if (IsRobotInThisSector(s.sSectorX, s.sSectorY, s.bSectorZ))
3085 	{ // Robot
3086 		AddMonoString(box, pRepairStrings[3]);
3087 	}
3088 
3089 	AddMonoString(box, pRepairStrings[0]); // Items
3090 	AddMonoString(box, pRepairStrings[2]); // Cancel
3091 
3092 	SetBoxTextAttrs(box);
3093 	ResizeBoxToText(box);
3094 	CheckAndUpdateTacticalAssignmentPopUpPositions();
3095 }
3096 
3097 
HandleShadingOfLinesForRepairMenu()3098 static void HandleShadingOfLinesForRepairMenu()
3099 {
3100 	if (!fShowRepairMenu) return;
3101 
3102 	PopUpBox* const box = ghRepairBox;
3103 	if (box == NO_POPUP_BOX) return;
3104 
3105 	/* PLEASE NOTE: make sure any changes you do here are reflected in all 3
3106 	 * routines, which must remain in synch:
3107 	 * CreateDestroyMouseRegionForRepairMenu(), DisplayRepairMenu() and
3108 	 * HandleShadingOfLinesForRepairMenu(). */
3109 	SOLDIERTYPE const& s    = *GetSelectedAssignSoldier(FALSE);
3110 	INT32              line = 0;
3111 
3112 	if (s.bSectorZ == 0)
3113 	{
3114 		CFOR_EACH_VEHICLE(v)
3115 		{
3116 			// don't even list the helicopter, because it's NEVER repairable...
3117 			if (IsHelicopter(v))                         continue;
3118 			if (!IsThisVehicleAccessibleToSoldier(s, v)) continue;
3119 			ShadeStringInBox(box, line++, !CanCharacterRepairVehicle(s, v));
3120 		}
3121 	}
3122 
3123 	if (IsRobotInThisSector(s.sSectorX, s.sSectorY, s.bSectorZ))
3124 	{
3125 		// handle shading of repair robot option
3126 		ShadeStringInBox(box, line++, !CanCharacterRepairRobot(&s));
3127 	}
3128 
3129 	ShadeStringInBox(box, line++, !DoesCharacterHaveAnyItemsToRepair(&s, FINAL_REPAIR_PASS));
3130 }
3131 
3132 
3133 static void RepairMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3134 static void RepairMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason);
3135 
3136 
MakeRepairRegion(const INT32 idx,const UINT16 x,const UINT16 y,const UINT16 w,const UINT16 h,const UINT32 data)3137 static void MakeRepairRegion(const INT32 idx, const UINT16 x, const UINT16 y, const UINT16 w, const UINT16 h, const UINT32 data)
3138 {
3139 	MOUSE_REGION* const r = &gRepairMenuRegion[idx];
3140 	MSYS_DefineRegion(r, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 4, MSYS_NO_CURSOR, RepairMenuMvtCallback, RepairMenuBtnCallback);
3141 	MSYS_SetRegionUserData(r, 0, idx);
3142 	MSYS_SetRegionUserData(r, 1, data);
3143 }
3144 
3145 
CreateDestroyMouseRegionForRepairMenu(void)3146 static void CreateDestroyMouseRegionForRepairMenu(void)
3147 {
3148 	static BOOLEAN fCreated = FALSE;
3149 
3150 	if (fShowRepairMenu && !fCreated)
3151 	{
3152 		CheckAndUpdateTacticalAssignmentPopUpPositions();
3153 
3154 		SOLDIERTYPE const& s = *GetSelectedAssignSoldier(FALSE);
3155 
3156 		PopUpBox* const  box  = ghRepairBox;
3157 		SGPBox    const& area = GetBoxArea(box);
3158 		UINT16    const  x    = area.x;
3159 		UINT16           y    = area.y + GetTopMarginSize(ghAssignmentBox); // XXX wrong box?
3160 		UINT16    const  w    = area.w;
3161 		UINT16    const  h    = GetLineSpace(box) + GetFontHeight(GetBoxFont(box));
3162 		INT32            idx  = 0;
3163 
3164 		// PLEASE NOTE: make sure any changes you do here are reflected in all 3 routines which must remain in synch:
3165 		// CreateDestroyMouseRegionForRepairMenu(), DisplayRepairMenu(), and HandleShadingOfLinesForRepairMenu().
3166 
3167 		if (s.bSectorZ == 0)
3168 		{
3169 			// vehicles
3170 			CFOR_EACH_VEHICLE(v)
3171 			{
3172 				// don't even list the helicopter, because it's NEVER repairable...
3173 				if (IsHelicopter(v)) continue;
3174 
3175 				// other vehicles *in the sector* are listed, but later shaded dark if they're not repairable
3176 				if (!IsThisVehicleAccessibleToSoldier(s, v)) continue;
3177 
3178 				// add mouse region for each line of text..and set user data
3179 				MakeRepairRegion(idx++, x, y, w, h, VEHICLE2ID(v));
3180 				y += h;
3181 			}
3182 		}
3183 
3184 		// robot
3185 		if (IsRobotInThisSector(s.sSectorX, s.sSectorY, s.bSectorZ))
3186 		{
3187 			MakeRepairRegion(idx++, x, y, w, h, REPAIR_MENU_ROBOT);
3188 			y += h;
3189 		}
3190 
3191 		// items
3192 		MakeRepairRegion(idx++, x, y, w, h, REPAIR_MENU_ITEMS);
3193 		y += h;
3194 
3195 		// cancel
3196 		MakeRepairRegion(idx, x, y, w, h, REPAIR_MENU_CANCEL);
3197 
3198 		PauseGame();
3199 
3200 		// unhighlight all strings in box
3201 		UnHighLightBox(box);
3202 
3203 		fCreated = TRUE;
3204 	}
3205 	else if ((!fShowRepairMenu || !fShowAssignmentMenu) && fCreated)
3206 	{
3207 		fCreated = FALSE;
3208 
3209 		// remove these regions
3210 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(ghRepairBox); ++i)
3211 		{
3212 			MSYS_RemoveRegion(&gRepairMenuRegion[i]);
3213 		}
3214 
3215 		fShowRepairMenu = FALSE;
3216 
3217 		SetRenderFlags(RENDER_FLAG_FULL);
3218 
3219 		HideBox(ghRepairBox);
3220 
3221 		// Remove highlight on the parent menu
3222 		if (fShowAssignmentMenu) UnHighLightBox(ghAssignmentBox);
3223 	}
3224 }
3225 
3226 
PreChangeAssignment(SOLDIERTYPE & s)3227 static void PreChangeAssignment(SOLDIERTYPE& s)
3228 {
3229 	if (s.bAssignment == VEHICLE) TakeSoldierOutOfVehicle(&s);
3230 	RemoveCharacterFromSquads(&s);
3231 }
3232 
3233 
3234 static bool AssignMercToAMovementGroup(SOLDIERTYPE&);
3235 
3236 
RepairMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)3237 static void RepairMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
3238 {
3239 	// btn callback handler for assignment region
3240 	INT32 iValue = -1;
3241 	SOLDIERTYPE *pSoldier = NULL;
3242 	INT32 iRepairWhat;
3243 
3244 
3245 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
3246 
3247 	// ignore clicks on disabled lines
3248 	if (GetBoxShadeFlag(ghRepairBox, iValue)) return;
3249 
3250 	// WHAT is being repaired is stored in the second user data argument
3251 	iRepairWhat = MSYS_GetRegionUserData( pRegion, 1 );
3252 
3253 
3254 	pSoldier = GetSelectedAssignSoldier( FALSE );
3255 
3256 
3257 	if ( pSoldier && pSoldier->bActive && ( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP ) )
3258 	{
3259 		if( ( iRepairWhat >= REPAIR_MENU_VEHICLE1 ) && ( iRepairWhat <= REPAIR_MENU_VEHICLE3 ) )
3260 		{
3261 			// repair VEHICLE
3262 			PreChangeAssignment(*pSoldier);
3263 
3264 			if( ( pSoldier->bAssignment != REPAIR )|| ( pSoldier -> fFixingRobot ) )
3265 			{
3266 				SetTimeOfAssignmentChangeForMerc( pSoldier );
3267 			}
3268 
3269 			MakeSureToolKitIsInHand( pSoldier );
3270 
3271 			ChangeSoldiersAssignment( pSoldier, REPAIR );
3272 
3273 			pSoldier -> bVehicleUnderRepairID = ( INT8 ) iRepairWhat;
3274 
3275 			MakeSureToolKitIsInHand( pSoldier );
3276 
3277 			// assign to a movement group
3278 			AssignMercToAMovementGroup(*pSoldier);
3279 
3280 			// set assignment for group
3281 			SetAssignmentForList( ( INT8 ) REPAIR, 0 );
3282 			fShowAssignmentMenu = FALSE;
3283 
3284 		}
3285 		else if( iRepairWhat == REPAIR_MENU_ROBOT )
3286 		{
3287 			// repair ROBOT
3288 			PreChangeAssignment(*pSoldier);
3289 			MakeSureToolKitIsInHand( pSoldier );
3290 
3291 			if (pSoldier->bAssignment != REPAIR || !pSoldier->fFixingRobot)
3292 			{
3293 				SetTimeOfAssignmentChangeForMerc( pSoldier );
3294 			}
3295 
3296 			ChangeSoldiersAssignment( pSoldier, REPAIR );
3297 			pSoldier->fFixingRobot = TRUE;
3298 
3299 			// the second argument is irrelevant here, function looks at pSoldier itself to know what's being repaired
3300 			SetAssignmentForList( ( INT8 ) REPAIR, 0 );
3301 			fShowAssignmentMenu = FALSE;
3302 
3303 			MakeSureToolKitIsInHand( pSoldier );
3304 
3305 			// assign to a movement group
3306 			AssignMercToAMovementGroup(*pSoldier);
3307 		}
3308 		else if( iRepairWhat == REPAIR_MENU_ITEMS )
3309 		{
3310 			// items
3311 			SetSoldierAssignmentRepair(*pSoldier, FALSE, -1);
3312 
3313 			// the second argument is irrelevant here, function looks at pSoldier itself to know what's being repaired
3314 			SetAssignmentForList( ( INT8 ) REPAIR, 0 );
3315 			fShowAssignmentMenu = FALSE;
3316 		}
3317 		else
3318 		{
3319 			// CANCEL
3320 			fShowRepairMenu = FALSE;
3321 		}
3322 
3323 		// update mapscreen
3324 		fCharacterInfoPanelDirty = TRUE;
3325 		fTeamPanelDirty = TRUE;
3326 		fMapScreenBottomDirty = TRUE;
3327 
3328 		giAssignHighLine = -1;
3329 	}
3330 }
3331 
3332 
RepairMenuMvtCallback(MOUSE_REGION * pRegion,INT32 iReason)3333 static void RepairMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason)
3334 {
3335 	// mvt callback handler for assignment region
3336 	INT32 iValue = -1;
3337 
3338 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
3339 
3340 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
3341 	{
3342 		if( iValue < REPAIR_MENU_CANCEL )
3343 		{
3344 			if (!GetBoxShadeFlag(ghRepairBox, iValue))
3345 			{
3346 				// highlight choice
3347 				HighLightBoxLine( ghRepairBox, iValue );
3348 			}
3349 		}
3350 		else
3351 		{
3352 			// highlight cancel line
3353 			HighLightBoxLine( ghRepairBox, GetNumberOfLinesOfTextInBox( ghRepairBox ) - 1 );
3354 		}
3355 	}
3356 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
3357 	{
3358 		// unhighlight all strings in box
3359 		UnHighLightBox( ghRepairBox );
3360 	}
3361 }
3362 
3363 
MakeSureToolKitIsInHand(SOLDIERTYPE * pSoldier)3364 static void MakeSureToolKitIsInHand(SOLDIERTYPE* pSoldier)
3365 {
3366 	INT8 bPocket = 0;
3367 
3368 	// if there isn't a toolkit in his hand
3369 	if( pSoldier -> inv[ HANDPOS].usItem != TOOLKIT )
3370 	{
3371 		// run through rest of inventory looking for toolkits, swap the first one into hand if found
3372 		for (bPocket = SECONDHANDPOS; bPocket <= SMALLPOCK8POS; bPocket++)
3373 		{
3374 			if( pSoldier -> inv[ bPocket ].usItem == TOOLKIT )
3375 			{
3376 				SwapObjs( &pSoldier -> inv[ HANDPOS ], &pSoldier -> inv[ bPocket ] );
3377 				break;
3378 			}
3379 		}
3380 	}
3381 }
3382 
3383 
MakeSureMedKitIsInHand(SOLDIERTYPE * pSoldier)3384 static BOOLEAN MakeSureMedKitIsInHand(SOLDIERTYPE* pSoldier)
3385 {
3386 	INT8 bPocket = 0;
3387 
3388 	fTeamPanelDirty = TRUE;
3389 
3390 	// if there is a MEDICAL BAG in his hand, we're set
3391 	if ( pSoldier -> inv[ HANDPOS ].usItem == MEDICKIT )
3392 	{
3393 		return(TRUE);
3394 	}
3395 
3396 	// run through rest of inventory looking for MEDICAL BAGS, swap the first one into hand if found
3397 	for (bPocket = SECONDHANDPOS; bPocket <= SMALLPOCK8POS; bPocket++)
3398 	{
3399 		if ( pSoldier -> inv[ bPocket ].usItem == MEDICKIT )
3400 		{
3401 			fCharacterInfoPanelDirty = TRUE;
3402 			SwapObjs( &pSoldier -> inv[ HANDPOS ], &pSoldier -> inv[ bPocket ] );
3403 			return(TRUE);
3404 		}
3405 	}
3406 
3407 	// no medkit items in possession!
3408 	return(FALSE);
3409 }
3410 
3411 
3412 static void HandleShadingOfLinesForAttributeMenus(void);
3413 static void HandleShadingOfLinesForSquadMenu(void);
3414 static void HandleShadingOfLinesForTrainingMenu(void);
3415 
3416 
3417 // updates which menus are selectable based on character status
HandleShadingOfLinesForAssignmentMenus()3418 void HandleShadingOfLinesForAssignmentMenus()
3419 {
3420 	if (!fShowAssignmentMenu || ghAssignmentBox == NO_POPUP_BOX) return;
3421 
3422 	SOLDIERTYPE const* const s = GetSelectedAssignSoldier(FALSE);
3423 	if (s && s->bActive)
3424 	{
3425 		if (s->ubWhatKindOfMercAmI == MERC_TYPE__EPC)
3426 		{
3427 			PopUpBox* const box = ghEpcBox;
3428 			ShadeStringInBox(box, EPC_MENU_PATIENT, !CanCharacterPatient(s));
3429 			ShadeStringInBox(box, EPC_MENU_ON_DUTY, !CanCharacterOnDuty(s));
3430 			ShadeStringInBox(box, EPC_MENU_VEHICLE, !CanCharacterVehicle(*s));
3431 		}
3432 		else
3433 		{
3434 			PopUpBox* const box = ghAssignmentBox;
3435 
3436 			{ // doctor
3437 				PopUpShade const shade =
3438 					!BasicCanCharacterDoctor(s) ? POPUP_SHADE           :
3439 					!CanCharacterDoctor(s)      ? POPUP_SHADE_SECONDARY :
3440 									POPUP_SHADE_NONE;
3441 				ShadeStringInBox(box, ASSIGN_MENU_DOCTOR, shade);
3442 			}
3443 
3444 			{ // repair
3445 				PopUpShade const shade =
3446 					!BasicCanCharacterRepair(s) ? POPUP_SHADE           :
3447 					!CanCharacterRepair(s)      ? POPUP_SHADE_SECONDARY :
3448 									POPUP_SHADE_NONE;
3449 				ShadeStringInBox(box, ASSIGN_MENU_REPAIR, shade);
3450 			}
3451 
3452 			ShadeStringInBox(box, ASSIGN_MENU_PATIENT, !CanCharacterPatient(s));
3453 			ShadeStringInBox(box, ASSIGN_MENU_ON_DUTY, !CanCharacterOnDuty(s));
3454 			ShadeStringInBox(box, ASSIGN_MENU_TRAIN,   !CanCharacterPractise(s));
3455 			ShadeStringInBox(box, ASSIGN_MENU_VEHICLE, !CanCharacterVehicle(*s));
3456 		}
3457 	}
3458 
3459 	HandleShadingOfLinesForSquadMenu();
3460 	HandleShadingOfLinesForVehicleMenu();
3461 	HandleShadingOfLinesForRepairMenu();
3462 	HandleShadingOfLinesForTrainingMenu();
3463 	HandleShadingOfLinesForAttributeMenus();
3464 }
3465 
3466 
HideBoxIfShown(PopUpBox * const box)3467 static void HideBoxIfShown(PopUpBox* const box)
3468 {
3469 	if (!IsBoxShown(box)) return;
3470 
3471 	HideBox(box);
3472 	fTeamPanelDirty     = TRUE;
3473 	gfRenderPBInterface = TRUE;
3474 }
3475 
3476 
HideBoxIfShownMap(PopUpBox * const box)3477 static void HideBoxIfShownMap(PopUpBox* const box)
3478 {
3479 	if (!IsBoxShown(box)) return;
3480 
3481 	HideBox(box);
3482 	fTeamPanelDirty     = TRUE;
3483 	fMapPanelDirty      = TRUE;
3484 	gfRenderPBInterface = TRUE;
3485 }
3486 
3487 
ShowAssignmentBox(void)3488 static void ShowAssignmentBox(void)
3489 {
3490 	if (fInMapMode)
3491 	{
3492 		const SOLDIERTYPE* const s = GetSelectedInfoChar();
3493 		if (s->bLife == 0 || s->bAssignment == ASSIGNMENT_POW)
3494 		{
3495 			ShowBox(ghRemoveMercAssignBox);
3496 			return;
3497 		}
3498 	}
3499 
3500 	SOLDIERTYPE const& s = *GetSelectedAssignSoldier(FALSE);
3501 	if (s.ubWhatKindOfMercAmI == MERC_TYPE__EPC)
3502 	{
3503 		ShowBox(ghEpcBox);
3504 	}
3505 	else
3506 	{
3507 		ShowBox(ghAssignmentBox);
3508 	}
3509 }
3510 
3511 
3512 static void CreateDestroyMouseRegionsForTrainingMenu(void);
3513 static void CreateDestroyMouseRegionsForAttributeMenu(void);
3514 static void CreateDestroyMouseRegionsForSquadMenu();
3515 static BOOLEAN HandleShowingOfMovementBox(void);
3516 
3517 
DetermineWhichAssignmentMenusCanBeShown(void)3518 void DetermineWhichAssignmentMenusCanBeShown(void)
3519 {
3520 	BOOLEAN fCharacterNoLongerValid = FALSE;
3521 
3522 	if (fInMapMode)
3523 	{
3524 		if (fShowMapScreenMovementList)
3525 		{
3526 			if( bSelectedDestChar == -1 )
3527 			{
3528 				fCharacterNoLongerValid = TRUE;
3529 				HandleShowingOfMovementBox( );
3530 			}
3531 			else
3532 			{
3533 				fShowMapScreenMovementList = FALSE;
3534 				fCharacterNoLongerValid = TRUE;
3535 			}
3536 		}
3537 		else if( bSelectedAssignChar == -1 )
3538 		{
3539 			fCharacterNoLongerValid = TRUE;
3540 		}
3541 
3542 		// update the assignment positions
3543 		UpdateMapScreenAssignmentPositions( );
3544 	}
3545 
3546 	// determine which assign menu needs to be shown
3547 	if (!fShowAssignmentMenu || fCharacterNoLongerValid)
3548 	{
3549 		// reset show assignment menus
3550 		fShowAssignmentMenu = FALSE;
3551 		fShowVehicleMenu = FALSE;
3552 		fShowRepairMenu = FALSE;
3553 
3554 		// destroy mask, if needed
3555 		CreateDestroyScreenMaskForAssignmentAndContractMenus( );
3556 
3557 
3558 		// destroy menu if needed
3559 		CreateDestroyMouseRegionForVehicleMenu( );
3560 		CreateDestroyMouseRegionsForAssignmentMenu( );
3561 		CreateDestroyMouseRegionsForTrainingMenu( );
3562 		CreateDestroyMouseRegionsForAttributeMenu( );
3563 		CreateDestroyMouseRegionsForSquadMenu();
3564 		CreateDestroyMouseRegionForRepairMenu( );
3565 
3566 		// hide all boxes being shown
3567 		HideBoxIfShown(ghEpcBox);
3568 		HideBoxIfShown(ghAssignmentBox);
3569 		HideBoxIfShown(ghTrainingBox);
3570 		HideBoxIfShown(ghRepairBox);
3571 		HideBoxIfShown(ghAttributeBox);
3572 		HideBoxIfShown(ghVehicleBox);
3573 
3574 		// do we really want ot hide this box?
3575 		if (!fShowContractMenu) HideBoxIfShown(ghRemoveMercAssignBox);
3576 		//HideBox( ghSquadBox );
3577 
3578 
3579 		//SetRenderFlags(RENDER_FLAG_FULL);
3580 
3581 		// no menus, leave
3582 		return;
3583 	}
3584 
3585 	// update the assignment positions
3586 	UpdateMapScreenAssignmentPositions( );
3587 
3588 	// create mask, if needed
3589 	CreateDestroyScreenMaskForAssignmentAndContractMenus( );
3590 
3591 
3592 	// created assignment menu if needed
3593 	CreateDestroyMouseRegionsForAssignmentMenu( );
3594 	CreateDestroyMouseRegionsForTrainingMenu( );
3595 	CreateDestroyMouseRegionsForAttributeMenu( );
3596 	CreateDestroyMouseRegionsForSquadMenu();
3597 	CreateDestroyMouseRegionForRepairMenu(  );
3598 
3599 	ShowAssignmentBox();
3600 
3601 	// TRAINING menu
3602 	if (fShowTrainingMenu)
3603 	{
3604 		HandleShadingOfLinesForTrainingMenu( );
3605 		ShowBox( ghTrainingBox );
3606 	}
3607 	else
3608 	{
3609 		HideBoxIfShownMap(ghTrainingBox);
3610 	}
3611 
3612 	// REPAIR menu
3613 	if (fShowRepairMenu)
3614 	{
3615 		HandleShadingOfLinesForRepairMenu( );
3616 		ShowBox( ghRepairBox );
3617 	}
3618 	else
3619 	{
3620 		HideBoxIfShownMap(ghRepairBox);
3621 	}
3622 
3623 	// ATTRIBUTE menu
3624 	if (fShowAttributeMenu)
3625 	{
3626 		HandleShadingOfLinesForAttributeMenus( );
3627 		ShowBox( ghAttributeBox );
3628 	}
3629 	else
3630 	{
3631 		HideBoxIfShownMap(ghAttributeBox);
3632 	}
3633 
3634 	// VEHICLE menu
3635 	if (fShowVehicleMenu)
3636 	{
3637 		ShowBox( ghVehicleBox );
3638 	}
3639 	else
3640 	{
3641 		HideBoxIfShownMap(ghVehicleBox);
3642 	}
3643 
3644 	CreateDestroyMouseRegionForVehicleMenu( );
3645 }
3646 
3647 
3648 static void AssignmentScreenMaskBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3649 
3650 
CreateDestroyScreenMaskForAssignmentAndContractMenus(void)3651 void CreateDestroyScreenMaskForAssignmentAndContractMenus( void )
3652 {
3653 
3654 	static BOOLEAN fCreated = FALSE;
3655 	// will create a screen mask to catch mouse input to disable assignment menus
3656 
3657 	// not created, create
3658 	if (!fCreated && (fShowAssignmentMenu || fShowContractMenu || fShowTownInfo))
3659 	{
3660 		MSYS_DefineRegion(&gAssignmentScreenMaskRegion, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MSYS_PRIORITY_HIGHEST - 4, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, AssignmentScreenMaskBtnCallback);
3661 
3662 		// created
3663 		fCreated = TRUE;
3664 
3665 		if (!fInMapMode)
3666 		{
3667 			gAssignmentScreenMaskRegion.ChangeCursor(0);
3668 		}
3669 
3670 	}
3671 	else if (fCreated && !fShowAssignmentMenu && !fShowContractMenu && !fShowTownInfo)
3672 	{
3673 		// created, get rid of it
3674 		MSYS_RemoveRegion(  &gAssignmentScreenMaskRegion );
3675 
3676 		// not created
3677 		fCreated = FALSE;
3678 	}
3679 }
3680 
3681 
AssignmentScreenMaskBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)3682 static void AssignmentScreenMaskBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
3683 {
3684 	// btn callback handler for assignment screen mask region
3685 
3686 	if( ( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP ) || ( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP ) )
3687 	{
3688 		if (fFirstClickInAssignmentScreenMask)
3689 		{
3690 			fFirstClickInAssignmentScreenMask = FALSE;
3691 			return;
3692 		}
3693 
3694 		// button event, stop showing menus
3695 		fShowAssignmentMenu = FALSE;
3696 
3697 		fShowVehicleMenu = FALSE;
3698 
3699 		fShowContractMenu = FALSE;
3700 
3701 		// stop showing town mine info
3702 		fShowTownInfo = FALSE;
3703 
3704 		// reset contract character and contract highlight line
3705 		giContractHighLine =-1;
3706 		bSelectedContractChar = -1;
3707 
3708 		// update mapscreen
3709 		fTeamPanelDirty          = TRUE;
3710 		fCharacterInfoPanelDirty = TRUE;
3711 		fMapScreenBottomDirty    = TRUE;
3712 		gfRenderPBInterface      = TRUE;
3713 		SetRenderFlags(RENDER_FLAG_FULL);
3714 	}
3715 }
3716 
ClearScreenMaskForMapScreenExit(void)3717 void ClearScreenMaskForMapScreenExit( void )
3718 {
3719 
3720 	// reset show assignment menu
3721 	fShowAssignmentMenu = FALSE;
3722 
3723 	// update the assignment positions
3724 	UpdateMapScreenAssignmentPositions( );
3725 
3726 	// stop showing town mine info too
3727 	fShowTownInfo = FALSE;
3728 
3729 	// destroy mask, if needed
3730 	CreateDestroyScreenMaskForAssignmentAndContractMenus( );
3731 
3732 	// destroy assignment menu if needed
3733 	CreateDestroyMouseRegionsForAssignmentMenu( );
3734 	CreateDestroyMouseRegionsForTrainingMenu( );
3735 	CreateDestroyMouseRegionsForAttributeMenu( );
3736 	CreateDestroyMouseRegionsForSquadMenu();
3737 	CreateDestroyMouseRegionForRepairMenu(  );
3738 }
3739 
3740 
3741 static void ContractMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason);
3742 static void ContractMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3743 
3744 
3745 // Create/destroy mouse regions for the map screen Contract main menu
CreateDestroyMouseRegionsForContractMenu(void)3746 void CreateDestroyMouseRegionsForContractMenu(void)
3747 {
3748 	static BOOLEAN fCreated = FALSE;
3749 
3750 	if (HandleRemoveMenu(bSelectedContractChar)) return;
3751 
3752 	PopUpBox* const box = ghContractBox;
3753 	if (fShowContractMenu && !fCreated)
3754 	{
3755 		if (bSelectedContractChar == -1) return;
3756 
3757 		SetBoxXY(box, ContractPosition.iX, ContractPosition.iY);
3758 
3759 		SGPBox const& area = GetBoxArea(box);
3760 		UINT16 const  x    = area.x;
3761 		UINT16        y    = area.y + GetTopMarginSize(box);
3762 		UINT16 const  w    = area.w;
3763 		UINT16 const  dy   = GetLineSpace(box) + GetFontHeight(GetBoxFont(box));
3764 
3765 		// Add mouse region for each line of text and set user data
3766 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(box); ++i)
3767 		{
3768 			MOUSE_REGION* const r = &gContractMenuRegion[i];
3769 			MSYS_DefineRegion(r, x, y, x + w, y + dy, MSYS_PRIORITY_HIGHEST - 4, MSYS_NO_CURSOR, ContractMenuMvtCallback, ContractMenuBtnCallback);
3770 			MSYS_SetRegionUserData(r, 0, i);
3771 			y += dy;
3772 		}
3773 
3774 		UnHighLightBox(box); // unhighlight all strings in box
3775 		PauseGame();
3776 
3777 		fCreated = TRUE;
3778 	}
3779 	else if (!fShowContractMenu && fCreated)
3780 	{
3781 		// destroy
3782 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(box); ++i)
3783 		{
3784 			MSYS_RemoveRegion(&gContractMenuRegion[i]);
3785 		}
3786 
3787 		fShownContractMenu       = FALSE;
3788 		fMapPanelDirty           = TRUE;
3789 		fCharacterInfoPanelDirty = TRUE;
3790 		fTeamPanelDirty          = TRUE;
3791 		fMapScreenBottomDirty    = TRUE;
3792 
3793 		fCreated = FALSE;
3794 	}
3795 }
3796 
3797 
3798 static void TrainingMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason);
3799 static void TrainingMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3800 
3801 
CreateDestroyMouseRegionsForTrainingMenu(void)3802 static void CreateDestroyMouseRegionsForTrainingMenu(void)
3803 {
3804 	static BOOLEAN fCreated = FALSE;
3805 
3806 	PopUpBox* const box = ghTrainingBox;
3807 	if (!fCreated && fShowTrainingMenu)
3808 	{
3809 		if (guiCurrentScreen == MAP_SCREEN)
3810 		{
3811 			SetBoxXY(box, TrainPosition.iX, TrainPosition.iY);
3812 		}
3813 
3814 		HandleShadingOfLinesForTrainingMenu();
3815 		CheckAndUpdateTacticalAssignmentPopUpPositions();
3816 
3817 		SGPBox const& area = GetBoxArea(box);
3818 		UINT16 const  x    = area.x;
3819 		UINT16        y    = area.y + GetTopMarginSize(box);
3820 		UINT16 const  w    = area.w;
3821 		UINT16 const  h    = GetLineSpace(box) + GetFontHeight(GetBoxFont(box));
3822 
3823 		// define regions
3824 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(box); ++i)
3825 		{
3826 			// add mouse region for each line of text..and set user data
3827 			MOUSE_REGION* const r = &gTrainingMenuRegion[i];
3828 			MSYS_DefineRegion(r, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 3, MSYS_NO_CURSOR, TrainingMenuMvtCallBack, TrainingMenuBtnCallback);
3829 			MSYS_SetRegionUserData(r, 0, i);
3830 			y += h;
3831 		}
3832 
3833 		UnHighLightBox(box);
3834 
3835 		fCreated = TRUE;
3836 	}
3837 	else if (fCreated && (!fShowAssignmentMenu || !fShowTrainingMenu))
3838 	{
3839 		fShowTrainingMenu = FALSE;
3840 
3841 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(box); ++i)
3842 		{
3843 			MSYS_RemoveRegion(&gTrainingMenuRegion[i]);
3844 		}
3845 
3846 		fMapPanelDirty           = TRUE;
3847 		fCharacterInfoPanelDirty = TRUE;
3848 		fTeamPanelDirty          = TRUE;
3849 		fMapScreenBottomDirty    = TRUE;
3850 		HideBox(box);
3851 		SetRenderFlags(RENDER_FLAG_FULL);
3852 
3853 		if (fShowAssignmentMenu) UnHighLightBox(ghAssignmentBox);
3854 
3855 		fCreated = FALSE;
3856 	}
3857 }
3858 
3859 
3860 static void AttributeMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason);
3861 static void AttributesMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3862 
3863 
CreateDestroyMouseRegionsForAttributeMenu(void)3864 static void CreateDestroyMouseRegionsForAttributeMenu(void)
3865 {
3866 	static BOOLEAN fCreated = FALSE;
3867 
3868 	// will create/destroy mouse regions for the map screen attribute  menu
3869 
3870 	PopUpBox* const box = ghAttributeBox;
3871 	if (!fCreated && fShowAttributeMenu)
3872 	{
3873 		if (fShowAssignmentMenu && guiCurrentScreen == MAP_SCREEN)
3874 		{
3875 			SetBoxXY(ghAssignmentBox, AssignmentPosition.iX, AssignmentPosition.iY);
3876 		}
3877 
3878 		HandleShadingOfLinesForAttributeMenus();
3879 		CheckAndUpdateTacticalAssignmentPopUpPositions();
3880 
3881 		// grab height of font
3882 		SGPBox const& area = GetBoxArea(box);
3883 		UINT16 const  x    = area.x;
3884 		UINT16        y    = area.y + GetTopMarginSize(box);
3885 		UINT16 const  w    = area.w;
3886 		UINT16 const  h    = GetLineSpace(box) + GetFontHeight(GetBoxFont(box));
3887 
3888 		// define regions
3889 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(box); ++i)
3890 		{
3891 			// add mouse region for each line of text..and set user data
3892 			MOUSE_REGION* const r = &gAttributeMenuRegion[i];
3893 			MSYS_DefineRegion(r, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 2, MSYS_NO_CURSOR, AttributeMenuMvtCallBack, AttributesMenuBtnCallback);
3894 			MSYS_SetRegionUserData(r, 0, i);
3895 			y += h;
3896 		}
3897 
3898 		UnHighLightBox(box);
3899 
3900 		fCreated = TRUE;
3901 	}
3902 	else if (fCreated && (!fShowAssignmentMenu || !fShowTrainingMenu || !fShowAttributeMenu))
3903 	{
3904 		fShowAttributeMenu = FALSE;
3905 
3906 		// destroy
3907 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(box); ++i)
3908 		{
3909 			MSYS_RemoveRegion(&gAttributeMenuRegion[i]);
3910 		}
3911 
3912 		gfRenderPBInterface      = TRUE;
3913 		fMapPanelDirty           = TRUE;
3914 		fCharacterInfoPanelDirty = TRUE;
3915 		fTeamPanelDirty          = TRUE;
3916 		fMapScreenBottomDirty    = TRUE;
3917 		HideBox(box);
3918 		SetRenderFlags(RENDER_FLAG_FULL);
3919 
3920 		if (fShowTrainingMenu) UnHighLightBox(ghTrainingBox);
3921 
3922 		fCreated = FALSE;
3923 	}
3924 }
3925 
3926 
3927 static void RemoveMercMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason);
3928 static void RemoveMercMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3929 
3930 
CreateDestroyMouseRegionsForRemoveMenu(void)3931 static void CreateDestroyMouseRegionsForRemoveMenu(void)
3932 {
3933 	static BOOLEAN fCreated = FALSE;
3934 
3935 	// will create/destroy mouse regions for the map screen attribute  menu
3936 	if ((fShowAssignmentMenu || fShowContractMenu) && !fCreated)
3937 	{
3938 		const SGPPoint* pos;
3939 		if (fShowContractMenu)
3940 		{
3941 			pos = &ContractPosition;
3942 			SetBoxXY(ghContractBox, pos->iX, pos->iY);
3943 		}
3944 		else
3945 		{
3946 			pos = &AssignmentPosition,
3947 			SetBoxXY(ghAssignmentBox, pos->iX, pos->iY);
3948 		}
3949 		SetBoxXY(ghRemoveMercAssignBox, pos->iX, pos->iY);
3950 
3951 		CheckAndUpdateTacticalAssignmentPopUpPositions();
3952 
3953 		SGPBox const& area = GetBoxArea(ghRemoveMercAssignBox);
3954 		INT32  const  x    = area.x;
3955 		INT32         y    = area.y + GetTopMarginSize(ghAttributeBox);
3956 		INT32  const  w    = area.w;
3957 		INT32  const  h    = GetLineSpace(ghRemoveMercAssignBox) + GetFontHeight(GetBoxFont(ghRemoveMercAssignBox));
3958 
3959 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(ghRemoveMercAssignBox); ++i)
3960 		{
3961 			// add mouse region for each line of text..and set user data
3962 			MOUSE_REGION* const r = &gRemoveMercAssignRegion[i];
3963 			MSYS_DefineRegion(r, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 2, MSYS_NO_CURSOR, RemoveMercMenuMvtCallBack, RemoveMercMenuBtnCallback);
3964 			MSYS_SetRegionUserData(r, 0, i);
3965 			y += h;
3966 		}
3967 
3968 		UnHighLightBox(ghRemoveMercAssignBox);
3969 
3970 		fCreated = TRUE;
3971 	}
3972 	else if (fCreated && !fShowAssignmentMenu && !fShowContractMenu)
3973 	{
3974 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(ghRemoveMercAssignBox); ++i)
3975 		{
3976 			MSYS_RemoveRegion(&gRemoveMercAssignRegion[i]);
3977 		}
3978 
3979 		fShownContractMenu = FALSE;
3980 
3981 		gfRenderPBInterface      = TRUE;
3982 		fMapPanelDirty           = TRUE;
3983 		fCharacterInfoPanelDirty = TRUE;
3984 		fTeamPanelDirty          = TRUE;
3985 		fMapScreenBottomDirty    = TRUE;
3986 
3987 		fShowAssignmentMenu = FALSE;
3988 
3989 		fCreated = FALSE;
3990 	}
3991 }
3992 
3993 
3994 static void SquadMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason);
3995 static void SquadMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
3996 static void CreateSquadBox(void);
3997 
3998 
CreateDestroyMouseRegionsForSquadMenu()3999 static void CreateDestroyMouseRegionsForSquadMenu()
4000 {
4001 	static BOOLEAN fCreated = FALSE;
4002 
4003 	if (fShowSquadMenu && !fCreated)
4004 	{
4005 		CreateSquadBox();
4006 		CheckAndUpdateTacticalAssignmentPopUpPositions();
4007 
4008 		SGPBox const& area = GetBoxArea(ghSquadBox);
4009 		INT32  const  x    = area.x;
4010 		INT32         y    = area.y + GetTopMarginSize(ghSquadBox);
4011 		INT32  const  w    = area.w;
4012 		INT32  const  h    = GetLineSpace(ghSquadBox) + GetFontHeight(GetBoxFont(ghSquadBox));
4013 
4014 		// add mouse region for each line of text except cancel
4015 		UINT32 lines = GetNumberOfLinesOfTextInBox(ghSquadBox);
4016 		if (lines > 0) lines--;
4017 		UINT32 i;
4018 		for (i = 0; i < lines; ++i)
4019 		{
4020 			MOUSE_REGION* const r = &gSquadMenuRegion[i];
4021 			MSYS_DefineRegion(r, x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 2, MSYS_NO_CURSOR, SquadMenuMvtCallBack, SquadMenuBtnCallback);
4022 			MSYS_SetRegionUserData(r, 0, i);
4023 			y += h;
4024 		}
4025 
4026 		// now create cancel region
4027 		MSYS_DefineRegion(&gSquadMenuRegion[i], x, y, x + w, y + h, MSYS_PRIORITY_HIGHEST - 2, MSYS_NO_CURSOR, SquadMenuMvtCallBack, SquadMenuBtnCallback);
4028 		MSYS_SetRegionUserData(&gSquadMenuRegion[i], 0, SQUAD_MENU_CANCEL);
4029 
4030 		ShowBox(ghSquadBox);
4031 		UnHighLightBox(ghSquadBox);
4032 		HandleShadingOfLinesForSquadMenu();
4033 
4034 		fCreated = TRUE;
4035 	}
4036 	else if ((!fShowAssignmentMenu || !fShowSquadMenu) && fCreated)
4037 	{
4038 		for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(ghSquadBox); ++i)
4039 		{
4040 			MSYS_RemoveRegion(&gSquadMenuRegion[i]);
4041 		}
4042 
4043 		fShowSquadMenu = FALSE;
4044 
4045 		RemoveBox(ghSquadBox);
4046 		ghSquadBox = NO_POPUP_BOX;
4047 
4048 		fMapPanelDirty           = TRUE;
4049 		fCharacterInfoPanelDirty = TRUE;
4050 		fTeamPanelDirty          = TRUE;
4051 		fMapScreenBottomDirty    = TRUE;
4052 		SetRenderFlags(RENDER_FLAG_FULL);
4053 
4054 		// remove highlight on the parent menu
4055 		if (fShowAssignmentMenu) UnHighLightBox(ghAssignmentBox);
4056 
4057 		fCreated = FALSE;
4058 	}
4059 }
4060 
4061 
4062 static BOOLEAN HandleAssignmentExpansionAndHighLightForAssignMenu(SOLDIERTYPE* pSoldier);
4063 
4064 
AssignmentMenuMvtCallBack(MOUSE_REGION * pRegion,INT32 iReason)4065 static void AssignmentMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason)
4066 {
4067 	// mvt callback handler for assignment region
4068 	INT32 iValue = -1;
4069 	SOLDIERTYPE *pSoldier;
4070 
4071 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4072 
4073 
4074 	pSoldier = GetSelectedAssignSoldier( FALSE );
4075 
4076 	if (HandleAssignmentExpansionAndHighLightForAssignMenu(pSoldier))
4077 	{
4078 		return;
4079 	}
4080 
4081 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
4082 	{
4083 		// is the line shaded?..if so, don't highlight
4084 		if( pSoldier -> ubWhatKindOfMercAmI == MERC_TYPE__EPC )
4085 		{
4086 			if (!GetBoxShadeFlag(ghEpcBox, iValue))
4087 			{
4088 				HighLightBoxLine( ghEpcBox, iValue );
4089 			}
4090 		}
4091 		else
4092 		{
4093 			if (!GetBoxShadeFlag(ghAssignmentBox, iValue))
4094 			{
4095 				HighLightBoxLine( ghAssignmentBox, iValue );
4096 			}
4097 		}
4098 	}
4099 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
4100 	{
4101 		if( pSoldier -> ubWhatKindOfMercAmI == MERC_TYPE__EPC )
4102 		{
4103 			// unhighlight all strings in box
4104 			UnHighLightBox( ghEpcBox );
4105 		}
4106 		else
4107 		{
4108 			// unhighlight all strings in box
4109 			UnHighLightBox( ghAssignmentBox );
4110 		}
4111 	}
4112 }
4113 
4114 
RemoveMercMenuMvtCallBack(MOUSE_REGION * pRegion,INT32 iReason)4115 static void RemoveMercMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason)
4116 {
4117 	// mvt callback handler for assignment region
4118 	INT32 iValue = -1;
4119 
4120 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4121 
4122 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
4123 	{
4124 		// highlight string
4125 
4126 		// get the string line handle
4127 		// is the line shaded?..if so, don't highlight
4128 		if (!GetBoxShadeFlag(ghRemoveMercAssignBox, iValue))
4129 		{
4130 			HighLightBoxLine( ghRemoveMercAssignBox, iValue );
4131 		}
4132 	}
4133 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
4134 	{
4135 		// unhighlight all strings in box
4136 		UnHighLightBox( ghRemoveMercAssignBox );
4137 	}
4138 }
4139 
4140 
ContractMenuMvtCallback(MOUSE_REGION * pRegion,INT32 iReason)4141 static void ContractMenuMvtCallback(MOUSE_REGION* pRegion, INT32 iReason)
4142 {
4143 	// mvt callback handler for Contract region
4144 	INT32 iValue = -1;
4145 
4146 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4147 
4148 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
4149 	{
4150 		// highlight string
4151 
4152 		if( iValue != CONTRACT_MENU_CURRENT_FUNDS )
4153 		{
4154 			if (!GetBoxShadeFlag(ghContractBox, iValue))
4155 			{
4156 				// get the string line handle
4157 				HighLightBoxLine( ghContractBox, iValue );
4158 			}
4159 		}
4160 	}
4161 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
4162 	{
4163 		// unhighlight all strings in box
4164 		UnHighLightBox( ghContractBox );
4165 	}
4166 }
4167 
4168 
SquadMenuMvtCallBack(MOUSE_REGION * pRegion,INT32 iReason)4169 static void SquadMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason)
4170 {
4171 	// mvt callback handler for assignment region
4172 	INT32 iValue = -1;
4173 
4174 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4175 
4176 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
4177 	{
4178 		// highlight string
4179 
4180 		if( iValue != SQUAD_MENU_CANCEL )
4181 		{
4182 			if (!GetBoxShadeFlag(ghSquadBox, iValue))
4183 			{
4184 				// get the string line handle
4185 				HighLightBoxLine( ghSquadBox, iValue );
4186 			}
4187 		}
4188 		else
4189 		{
4190 			// highlight cancel line
4191 			HighLightBoxLine(ghSquadBox, GetNumberOfLinesOfTextInBox(ghSquadBox) - 1);
4192 		}
4193 	}
4194 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
4195 	{
4196 		// unhighlight all strings in box
4197 		UnHighLightBox( ghSquadBox );
4198 
4199 		// update based on current squad
4200 		HandleShadingOfLinesForSquadMenu( );
4201 	}
4202 }
4203 
4204 
RemoveMercMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)4205 static void RemoveMercMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
4206 {
4207 	// btn callback handler for contract region
4208 	INT32 iValue = -1;
4209 	SOLDIERTYPE * pSoldier = NULL;
4210 
4211 
4212 	pSoldier = GetSelectedAssignSoldier( FALSE );
4213 
4214 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4215 
4216 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
4217 	{
4218 		switch( iValue )
4219 		{
4220 			case REMOVE_MERC_CANCEL:
4221 				// stop showing menus
4222 				fShowAssignmentMenu = FALSE;
4223 				fShowContractMenu = FALSE;
4224 
4225 				// reset characters
4226 				bSelectedAssignChar = -1;
4227 				bSelectedContractChar = -1;
4228 				giAssignHighLine = -1;
4229 
4230 				// dirty regions
4231 				fCharacterInfoPanelDirty = TRUE;
4232 				fTeamPanelDirty = TRUE;
4233 				fMapScreenBottomDirty = TRUE;
4234 				gfRenderPBInterface = TRUE;
4235 
4236 
4237 				// stop contratc glow if we are
4238 				giContractHighLine = -1;
4239 
4240 				break;
4241 			case( REMOVE_MERC ):
4242 				bSelectedInfoChar = -1;
4243 				StrategicRemoveMerc(*pSoldier);
4244 
4245 				// dirty region
4246 				fCharacterInfoPanelDirty = TRUE;
4247 				fTeamPanelDirty = TRUE;
4248 				fMapScreenBottomDirty = TRUE;
4249 				gfRenderPBInterface = TRUE;
4250 
4251 				// stop contratc glow if we are
4252 				giContractHighLine = -1;
4253 
4254 				// reset selected characters
4255 				bSelectedAssignChar = -1;
4256 				bSelectedContractChar = -1;
4257 				giAssignHighLine = -1;
4258 
4259 				// stop showing menus
4260 				fShowAssignmentMenu = FALSE;
4261 				fShowContractMenu = FALSE;
4262 
4263 				//Def: 10/13/99:  When a merc is either dead or a POW, the Remove Merc popup comes up instead of the
4264 				// Assign menu popup.  When the the user removes the merc, we need to make sure the assignment menu
4265 				//doesnt come up ( because the both assign menu and remove menu flags are needed for the remove pop up to appear
4266 				//dont ask why?!! )
4267 				fShownContractMenu = FALSE;
4268 				fShownAssignmentMenu = FALSE;
4269 				break;
4270 		}
4271 	}
4272 }
4273 
4274 
4275 // Setup the quote, then start dialogue beginning the actual leave sequence
BeginRemoveMercFromContract(SOLDIERTYPE * const s)4276 static void BeginRemoveMercFromContract(SOLDIERTYPE* const s)
4277 {
4278 	if (s->bLife <= 0 || s->bAssignment == ASSIGNMENT_POW) return;
4279 
4280 	switch (s->ubWhatKindOfMercAmI)
4281 	{
4282 		case MERC_TYPE__MERC:
4283 		case MERC_TYPE__NPC:
4284 			HandleImportantMercQuoteLocked(s, QUOTE_RESPONSE_TO_MIGUEL_SLASH_QUOTE_MERC_OR_RPC_LETGO);
4285 			break;
4286 
4287 		case MERC_TYPE__AIM_MERC:
4288 			if (WillMercRenew(s, FALSE)) // Only do this if they want to renew
4289 			{
4290 				// Quote is different if he's fired in less than 48 hours
4291 				UINT16 const quote = GetWorldTotalMin() - s->uiTimeOfLastContractUpdate < 60 * 48 ?
4292 					QUOTE_DEPART_COMMET_CONTRACT_NOT_RENEWED_OR_TERMINATED_UNDER_48 :
4293 					QUOTE_DEPARTING_COMMENT_CONTRACT_NOT_RENEWED_OR_48_OR_MORE;
4294 				HandleImportantMercQuoteLocked(s, quote);
4295 			}
4296 			break;
4297 	}
4298 	MakeCharacterDialogueEventContractEnding(*s, true);
4299 
4300 	if (GetWorldTotalMin() - s->uiTimeOfLastContractUpdate < 60 * 3)
4301 	{
4302 		/* This will cause him give us lame excuses for a while until he gets over
4303 		 * it.  3-6 days (but the first 1-2 days of that are spent "returning" home)
4304 		 */
4305 		gMercProfiles[s->ubProfile].ubDaysOfMoraleHangover = 3 + Random(4);
4306 
4307 		// if it's an AIM merc, word of this gets back to AIM...  Bad rep.
4308 		if (s->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC)
4309 		{
4310 			ModifyPlayerReputation(REPUTATION_EARLY_FIRING);
4311 		}
4312 	}
4313 }
4314 
4315 
MercDismissConfirmCallBack(MessageBoxReturnValue const bExitValue)4316 static void MercDismissConfirmCallBack(MessageBoxReturnValue const bExitValue)
4317 {
4318 	if (bExitValue == MSG_BOX_RETURN_YES)
4319 	{
4320 		// Setup history code
4321 		gpDismissSoldier->ubLeaveHistoryCode = HISTORY_MERC_FIRED;
4322 		BeginRemoveMercFromContract(gpDismissSoldier);
4323 	}
4324 }
4325 
4326 
ContractMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)4327 static void ContractMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
4328 {
4329 	// btn callback handler for contract region
4330 	INT32 iValue = -1;
4331 	BOOLEAN fOkToClose = FALSE;
4332 
4333 	// can't renew contracts from tactical!
4334 	Assert(fInMapMode);
4335 
4336 	SOLDIERTYPE* const pSoldier = GetSelectedInfoChar();
4337 	Assert(pSoldier);
4338 
4339 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4340 
4341 	if (iReason & MSYS_CALLBACK_REASON_RBUTTON_UP)
4342 	{
4343 		fOkToClose = TRUE;
4344 	}
4345 
4346 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
4347 	{
4348 		// not valid?
4349 		if (GetBoxShadeFlag(ghContractBox, iValue)) return;
4350 
4351 		if( iValue == CONTRACT_MENU_CANCEL )
4352 		{
4353 			// reset contract character and contract highlight line
4354 			giContractHighLine =-1;
4355 			bSelectedContractChar = -1;
4356 
4357 			fShowContractMenu = FALSE;
4358 			// dirty region
4359 			fTeamPanelDirty = TRUE;
4360 			fMapScreenBottomDirty = TRUE;
4361 			fCharacterInfoPanelDirty = TRUE;
4362 			gfRenderPBInterface = TRUE;
4363 
4364 			if ( gfInContractMenuFromRenewSequence )
4365 			{
4366 				BeginRemoveMercFromContract( pSoldier );
4367 			}
4368 			return;
4369 		}
4370 
4371 		// else handle based on contract
4372 
4373 		switch( iValue )
4374 		{
4375 			case CONTRACT_MENU_DAY:
4376 				MercContractHandling( pSoldier, CONTRACT_EXTEND_1_DAY );
4377 				fOkToClose = TRUE;
4378 			break;
4379 			case( CONTRACT_MENU_WEEK ):
4380 				MercContractHandling( pSoldier, CONTRACT_EXTEND_1_WEEK );
4381 				fOkToClose = TRUE;
4382 			break;
4383 			case( CONTRACT_MENU_TWO_WEEKS ):
4384 				MercContractHandling( pSoldier, CONTRACT_EXTEND_2_WEEK );
4385 				fOkToClose = TRUE;
4386 			break;
4387 
4388 			case( CONTRACT_MENU_TERMINATE ):
4389 				gpDismissSoldier = pSoldier;
4390 
4391 				// If in the renewal sequence.. do right away...
4392 				// else put up requester.
4393 				if (gfInContractMenuFromRenewSequence)
4394 				{
4395 					MercDismissConfirmCallBack(MSG_BOX_RETURN_YES);
4396 				}
4397 				else
4398 				{
4399 					DoMapMessageBox(MSG_BOX_BASIC_STYLE, gzLateLocalizedString[STR_LATE_48], MAP_SCREEN, MSG_BOX_FLAG_YESNO, MercDismissConfirmCallBack);
4400 				}
4401 
4402 				fOkToClose = TRUE;
4403 				break;
4404 		}
4405 	}
4406 
4407 	if (fOkToClose)
4408 	{
4409 		// reset contract character and contract highlight line
4410 		giContractHighLine =-1;
4411 		bSelectedContractChar = -1;
4412 		fShowContractMenu = FALSE;
4413 
4414 		// dirty region
4415 		fTeamPanelDirty          = TRUE;
4416 		fMapScreenBottomDirty    = TRUE;
4417 		fCharacterInfoPanelDirty = TRUE;
4418 		gfRenderPBInterface      = TRUE;
4419 	}
4420 }
4421 
4422 
4423 static BOOLEAN HandleAssignmentExpansionAndHighLightForTrainingMenu(void);
4424 
4425 
TrainingMenuMvtCallBack(MOUSE_REGION * pRegion,INT32 iReason)4426 static void TrainingMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason)
4427 {
4428 	// mvt callback handler for assignment region
4429 	INT32 iValue = -1;
4430 
4431 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4432 
4433 	if (HandleAssignmentExpansionAndHighLightForTrainingMenu()) return;
4434 
4435 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
4436 	{
4437 		// highlight string
4438 
4439 		// do not highlight current balance
4440 		if (!GetBoxShadeFlag(ghTrainingBox, iValue))
4441 		{
4442 			// get the string line handle
4443 			HighLightBoxLine(ghTrainingBox, iValue);
4444 		}
4445 	}
4446 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
4447 	{
4448 		// unhighlight all strings in box
4449 		UnHighLightBox( ghTrainingBox );
4450 	}
4451 }
4452 
4453 
AttributeMenuMvtCallBack(MOUSE_REGION * pRegion,INT32 iReason)4454 static void AttributeMenuMvtCallBack(MOUSE_REGION* pRegion, INT32 iReason)
4455 {
4456 	// mvt callback handler for assignment region
4457 	INT32 iValue = -1;
4458 
4459 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4460 
4461 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
4462 	{
4463 		// highlight string
4464 		if (!GetBoxShadeFlag(ghAttributeBox, iValue))
4465 		{
4466 			// get the string line handle
4467 			HighLightBoxLine( ghAttributeBox, iValue );
4468 		}
4469 	}
4470 	else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
4471 	{
4472 		// unhighlight all strings in box
4473 		UnHighLightBox( ghAttributeBox );
4474 	}
4475 }
4476 
4477 
SquadMenuBtnCallback(MOUSE_REGION * const pRegion,INT32 const reason)4478 static void SquadMenuBtnCallback(MOUSE_REGION* const pRegion, INT32 const reason)
4479 {
4480 	// btn callback handler for assignment region
4481 	if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
4482 	{
4483 		INT32 const value = MSYS_GetRegionUserData(pRegion, 0);
4484 
4485 		if (value == SQUAD_MENU_CANCEL)
4486 		{ // Stop displaying, leave
4487 			UnHighLightBox(ghAssignmentBox);
4488 			fShowSquadMenu           = FALSE;
4489 			fTeamPanelDirty          = TRUE;
4490 			fMapScreenBottomDirty    = TRUE;
4491 			fCharacterInfoPanelDirty = TRUE;
4492 			gfRenderPBInterface      = TRUE;
4493 			return;
4494 		}
4495 
4496 		/* Can the character join this squad?  If already in it, accept that as a
4497 			* legal choice and exit menu */
4498 		SOLDIERTYPE& s = *GetSelectedAssignSoldier(FALSE);
4499 		ST::string buf;
4500 		switch (CanCharacterSquad(s, value))
4501 		{
4502 			case CHARACTER_CAN_JOIN_SQUAD: // able to add, do it
4503 			{
4504 				bool const exiting_helicopter = InHelicopter(s);
4505 				PreChangeAssignment(s);
4506 				AddCharacterToSquad(&s, value);
4507 				if (exiting_helicopter) SetSoldierExitHelicopterInsertionData(&s); // XXX TODO001D
4508 				MakeSoldiersTacticalAnimationReflectAssignment(&s);
4509 			}
4510 				// fallthrough
4511 			case CHARACTER_CANT_JOIN_SQUAD_ALREADY_IN_IT:
4512 				// Stop displaying, leave
4513 				fShowAssignmentMenu      = FALSE;
4514 				giAssignHighLine         = -1;
4515 				fTeamPanelDirty          = TRUE;
4516 				fMapScreenBottomDirty    = TRUE;
4517 				fCharacterInfoPanelDirty = TRUE;
4518 				gfRenderPBInterface      = TRUE;
4519 				break;
4520 
4521 			case CHARACTER_CANT_JOIN_SQUAD_SQUAD_MOVING:
4522 				buf = st_format_printf(pMapErrorString[36], s.name, pLongAssignmentStrings[value]);
4523 				break;
4524 			case CHARACTER_CANT_JOIN_SQUAD_VEHICLE:
4525 				buf = st_format_printf(pMapErrorString[37], s.name);
4526 				break;
4527 			case CHARACTER_CANT_JOIN_SQUAD_TOO_FAR:
4528 				buf = st_format_printf(pMapErrorString[20], s.name, pLongAssignmentStrings[value]);
4529 				break;
4530 			case CHARACTER_CANT_JOIN_SQUAD_FULL:
4531 				buf = st_format_printf(pMapErrorString[19], s.name, pLongAssignmentStrings[value]);
4532 				break;
4533 			default: // generic "you can't join this squad" msg
4534 				buf = st_format_printf(pMapErrorString[38], s.name, pLongAssignmentStrings[value]);
4535 				break;
4536 		}
4537 		if (!buf.empty())
4538 		{
4539 			DoScreenIndependantMessageBox(buf, MSG_BOX_FLAG_OK, NULL);
4540 		}
4541 
4542 		SetAssignmentForList(value, 0);
4543 	}
4544 }
4545 
4546 
TrainingMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)4547 static void TrainingMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
4548 {
4549 	// btn callback handler for assignment region
4550 	INT32 iValue = -1;
4551 	SOLDIERTYPE * pSoldier = NULL;
4552 	ST::string sString;
4553 	ST::string sStringA;
4554 
4555 
4556 	pSoldier = GetSelectedAssignSoldier( FALSE );
4557 
4558 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4559 
4560 	if( ( iReason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) || ( iReason & MSYS_CALLBACK_REASON_RBUTTON_DWN ) )
4561 	{
4562 		if (fInMapMode && !fShowMapInventoryPool)
4563 		{
4564 			UnMarkButtonDirty( giMapBorderButtons[ MAP_BORDER_TOWN_BTN ] );
4565 		}
4566 	}
4567 
4568 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
4569 	{
4570 		if( fShowAttributeMenu )
4571 		{
4572 			// cancel attribute submenu
4573 			fShowAttributeMenu = FALSE;
4574 			// rerender tactical stuff
4575 			gfRenderPBInterface = TRUE;
4576 
4577 			return;
4578 		}
4579 
4580 		switch( iValue )
4581 		{
4582 			case( TRAIN_MENU_SELF):
4583 
4584 				// practise in stat
4585 				gbTrainingMode = TRAIN_SELF;
4586 
4587 				// show menu
4588 				fShowAttributeMenu = TRUE;
4589 				DetermineBoxPositions( );
4590 
4591 			break;
4592 			case( TRAIN_MENU_TOWN):
4593 				if( BasicCanCharacterTrainMilitia(pSoldier) )
4594 				{
4595 					INT8 const bTownId = GetTownIdForSector(SECTOR(pSoldier->sSectorX, pSoldier->sSectorY));
4596 
4597 					// if it's a town sector (the following 2 errors can't happen at non-town SAM sites)
4598 					if( bTownId != BLANK_SECTOR )
4599 					{
4600 						// can we keep militia in this town?
4601 						if (!MilitiaTrainingAllowedInSector(pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ))
4602 						{
4603 							sString = st_format_printf(pMapErrorString[ 31 ], GCM->getTownName(bTownId));
4604 							DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL );
4605 							break;
4606 						}
4607 
4608 						// is the current loyalty high enough to train some?
4609 						if (!DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia(pSoldier))
4610 						{
4611 							DoScreenIndependantMessageBox(zMarksMapScreenText[19], MSG_BOX_FLAG_OK, NULL);
4612 							break;
4613 						}
4614 					}
4615 
4616 					if (IsAreaFullOfMilitia(pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ))
4617 					{
4618 						if( bTownId == BLANK_SECTOR )
4619 						{
4620 							// SAM site
4621 							sStringA = GetShortSectorString(pSoldier->sSectorX, pSoldier->sSectorY);
4622 							sString = st_format_printf(zMarksMapScreenText[20], sStringA);
4623 						}
4624 						else
4625 						{
4626 							// town
4627 							sString = st_format_printf(zMarksMapScreenText[20], GCM->getTownName(bTownId));
4628 						}
4629 
4630 						DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL );
4631 						break;
4632 					}
4633 
4634 					if ( CountMilitiaTrainersInSoldiersSector( pSoldier ) >= MAX_MILITIA_TRAINERS_PER_SECTOR )
4635 					{
4636 						sString = st_format_printf(gzLateLocalizedString[STR_LATE_47], MAX_MILITIA_TRAINERS_PER_SECTOR);
4637 						DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL );
4638 						break;
4639 					}
4640 
4641 
4642 					// PASSED ALL THE TESTS - ALLOW SOLDIER TO TRAIN MILITIA HERE
4643 					PreChangeAssignment(*pSoldier);
4644 
4645 					if( ( pSoldier->bAssignment != TRAIN_TOWN ) )
4646 					{
4647 						SetTimeOfAssignmentChangeForMerc( pSoldier );
4648 					}
4649 
4650 					MakeSoldiersTacticalAnimationReflectAssignment( pSoldier );
4651 
4652 					// stop showing menu
4653 					fShowAssignmentMenu = FALSE;
4654 					giAssignHighLine = -1;
4655 
4656 					ChangeSoldiersAssignment( pSoldier, TRAIN_TOWN );
4657 
4658 					// assign to a movement group
4659 					AssignMercToAMovementGroup(*pSoldier);
4660 					if (!SectorInfo[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)].fMilitiaTrainingPaid)
4661 					{
4662 						// show a message to confirm player wants to charge cost
4663 						HandleInterfaceMessageForCostOfTrainingMilitia( pSoldier );
4664 					}
4665 					else
4666 					{
4667 						SetAssignmentForList( TRAIN_TOWN, 0 );
4668 					}
4669 
4670 					gfRenderPBInterface = TRUE;
4671 
4672 				}
4673 			break;
4674 
4675 			case( TRAIN_MENU_TEAMMATES):
4676 				if (CanCharacterTrainTeammates(pSoldier))
4677 				{
4678 					// train teammates
4679 					gbTrainingMode = TRAIN_TEAMMATE;
4680 
4681 					// show menu
4682 					fShowAttributeMenu = TRUE;
4683 					DetermineBoxPositions( );
4684 				}
4685 			break;
4686 
4687 			case( TRAIN_MENU_TRAIN_BY_OTHER ):
4688 				if (CanCharacterBeTrainedByOther(pSoldier))
4689 				{
4690 					// train teammates
4691 					gbTrainingMode = TRAIN_BY_OTHER;
4692 
4693 					// show menu
4694 					fShowAttributeMenu = TRUE;
4695 					DetermineBoxPositions( );
4696 				}
4697 			break;
4698 			case( TRAIN_MENU_CANCEL ):
4699 				// stop showing menu
4700 				fShowTrainingMenu = FALSE;
4701 
4702 				// unhighlight the assignment box
4703 				UnHighLightBox(ghAssignmentBox);
4704 
4705 				// reset list
4706 				ResetSelectedListForMapScreen();
4707 				gfRenderPBInterface = TRUE;
4708 			break;
4709 		}
4710 
4711 		fTeamPanelDirty = TRUE;
4712 		fMapScreenBottomDirty = TRUE;
4713 	}
4714 	else if( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP )
4715 	{
4716 		if( fShowAttributeMenu )
4717 		{
4718 			// cancel attribute submenu
4719 			fShowAttributeMenu = FALSE;
4720 			// rerender tactical stuff
4721 			gfRenderPBInterface = TRUE;
4722 
4723 			return;
4724 		}
4725 	}
4726 }
4727 
4728 
AttributesMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)4729 static void AttributesMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
4730 {
4731 	// btn callback handler for assignment region
4732 	INT32 iValue = -1;
4733 	SOLDIERTYPE * pSoldier = NULL;
4734 
4735 
4736 	pSoldier = GetSelectedAssignSoldier( FALSE );
4737 
4738 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4739 
4740 
4741 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
4742 	{
4743 		if( iValue == ATTRIB_MENU_CANCEL )
4744 		{
4745 			// cancel, leave
4746 
4747 			// stop showing menu
4748 			fShowAttributeMenu = FALSE;
4749 
4750 			// unhighlight the training box
4751 			UnHighLightBox( ghTrainingBox );
4752 		}
4753 		else if( CanCharacterTrainStat( pSoldier, ( INT8 )( iValue ), ( BOOLEAN )( ( gbTrainingMode == TRAIN_SELF ) || ( gbTrainingMode == TRAIN_BY_OTHER ) ), ( BOOLEAN )( gbTrainingMode == TRAIN_TEAMMATE ) ) )
4754 		{
4755 			PreChangeAssignment(*pSoldier);
4756 
4757 			if( ( pSoldier->bAssignment != gbTrainingMode ) )
4758 			{
4759 				SetTimeOfAssignmentChangeForMerc( pSoldier );
4760 			}
4761 
4762 			// set stat to train
4763 			pSoldier -> bTrainStat = ( INT8 )iValue;
4764 
4765 			MakeSoldiersTacticalAnimationReflectAssignment( pSoldier );
4766 
4767 			// stop showing ALL menus
4768 			fShowAssignmentMenu = FALSE;
4769 			giAssignHighLine = -1;
4770 
4771 			// train stat
4772 			ChangeSoldiersAssignment( pSoldier, gbTrainingMode );
4773 
4774 			// assign to a movement group
4775 			AssignMercToAMovementGroup(*pSoldier);
4776 
4777 			// set assignment for group
4778 			SetAssignmentForList( gbTrainingMode, ( INT8 )iValue );
4779 		}
4780 
4781 		// rerender tactical stuff
4782 		gfRenderPBInterface = TRUE;
4783 
4784 		fTeamPanelDirty = TRUE;
4785 		fMapScreenBottomDirty = TRUE;
4786 	}
4787 }
4788 
4789 
4790 static bool DisplayVehicleMenu(SOLDIERTYPE const&);
4791 
4792 
AssignmentMenuBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)4793 static void AssignmentMenuBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
4794 {
4795 	// btn callback handler for assignment region
4796 	INT32 iValue = -1;
4797 	ST::string sString;
4798 
4799 	SOLDIERTYPE * pSoldier = NULL;
4800 
4801 
4802 	pSoldier = GetSelectedAssignSoldier( FALSE );
4803 
4804 	iValue = MSYS_GetRegionUserData( pRegion, 0 );
4805 
4806 
4807 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
4808 	{
4809 
4810 		if( ( fShowAttributeMenu )||( fShowTrainingMenu ) || ( fShowRepairMenu ) || ( fShowVehicleMenu ) ||( fShowSquadMenu ) )
4811 		{
4812 			return;
4813 		}
4814 
4815 		UnHighLightBox( ghAssignmentBox );
4816 
4817 		if( pSoldier -> ubWhatKindOfMercAmI == MERC_TYPE__EPC )
4818 		{
4819 			switch( iValue )
4820 			{
4821 				case( EPC_MENU_ON_DUTY ):
4822 					if( CanCharacterOnDuty( pSoldier ) )
4823 						{
4824 							// put character on a team
4825 							fShowSquadMenu = TRUE;
4826 							fShowTrainingMenu = FALSE;
4827 							fShowVehicleMenu = FALSE;
4828 							fTeamPanelDirty = TRUE;
4829 							fMapScreenBottomDirty = TRUE;
4830 
4831 						}
4832 				break;
4833 				case( EPC_MENU_PATIENT ):
4834 						// can character doctor?
4835 					if( CanCharacterPatient( pSoldier ) )
4836 					{
4837 						PreChangeAssignment(*pSoldier);
4838 
4839 						if( ( pSoldier->bAssignment != PATIENT ) )
4840 						{
4841 							SetTimeOfAssignmentChangeForMerc( pSoldier );
4842 						}
4843 
4844 						// stop showing menu
4845 						fShowAssignmentMenu = FALSE;
4846 						giAssignHighLine = -1;
4847 
4848 						MakeSoldiersTacticalAnimationReflectAssignment( pSoldier );
4849 
4850 						// set dirty flag
4851 						fTeamPanelDirty = TRUE;
4852 						fMapScreenBottomDirty = TRUE;
4853 
4854 						ChangeSoldiersAssignment( pSoldier, PATIENT );
4855 						AssignMercToAMovementGroup(*pSoldier);
4856 
4857 						// set assignment for group
4858 						SetAssignmentForList( ( INT8 ) PATIENT, 0 );
4859 					}
4860 				break;
4861 
4862 				case( EPC_MENU_VEHICLE ):
4863 					if (CanCharacterVehicle(*pSoldier))
4864 					{
4865 						if (DisplayVehicleMenu(*pSoldier))
4866 						{
4867 							fShowVehicleMenu = TRUE;
4868 							ShowBox( ghVehicleBox );
4869 						}
4870 						else
4871 						{
4872 							fShowVehicleMenu = FALSE;
4873 						}
4874 					}
4875 				break;
4876 
4877 				case( EPC_MENU_REMOVE ):
4878 					fShowAssignmentMenu = FALSE;
4879 					UnEscortEPC(pSoldier);
4880 				break;
4881 
4882 				case( EPC_MENU_CANCEL ):
4883 					fShowAssignmentMenu = FALSE;
4884 					giAssignHighLine = -1;
4885 
4886 					// set dirty flag
4887 					fTeamPanelDirty = TRUE;
4888 					fMapScreenBottomDirty = TRUE;
4889 
4890 					// reset list of characters
4891 					ResetSelectedListForMapScreen( );
4892 				break;
4893 			}
4894 		}
4895 		else
4896 		{
4897 			switch( iValue )
4898 			{
4899 				case( ASSIGN_MENU_ON_DUTY ):
4900 						if( CanCharacterOnDuty( pSoldier ) )
4901 						{
4902 							// put character on a team
4903 							fShowSquadMenu = TRUE;
4904 							fShowTrainingMenu = FALSE;
4905 							fShowVehicleMenu = FALSE;
4906 							fTeamPanelDirty = TRUE;
4907 							fMapScreenBottomDirty = TRUE;
4908 							fShowRepairMenu = FALSE;
4909 						}
4910 				break;
4911 				case( ASSIGN_MENU_DOCTOR ):
4912 
4913 					// can character doctor?
4914 					if( CanCharacterDoctor( pSoldier ) )
4915 					{
4916 						// stop showing menu
4917 						fShowAssignmentMenu = FALSE;
4918 						giAssignHighLine = -1;
4919 
4920 						PreChangeAssignment(*pSoldier);
4921 
4922 						if( ( pSoldier->bAssignment != DOCTOR ) )
4923 						{
4924 							SetTimeOfAssignmentChangeForMerc( pSoldier );
4925 						}
4926 
4927 						ChangeSoldiersAssignment( pSoldier, DOCTOR );
4928 
4929 						MakeSureMedKitIsInHand( pSoldier );
4930 						AssignMercToAMovementGroup(*pSoldier);
4931 
4932 						MakeSoldiersTacticalAnimationReflectAssignment( pSoldier );
4933 
4934 						// set dirty flag
4935 						fTeamPanelDirty = TRUE;
4936 						fMapScreenBottomDirty = TRUE;
4937 
4938 
4939 						// set assignment for group
4940 						SetAssignmentForList( ( INT8 ) DOCTOR, 0 );
4941 					}
4942 					else if (BasicCanCharacterDoctor(pSoldier))
4943 					{
4944 						fTeamPanelDirty = TRUE;
4945 						fMapScreenBottomDirty = TRUE;
4946 						sString = st_format_printf(zMarksMapScreenText[18], pSoldier->name);
4947 
4948 						DoScreenIndependantMessageBox( sString , MSG_BOX_FLAG_OK, NULL );
4949 					}
4950 
4951 				break;
4952 				case( ASSIGN_MENU_PATIENT ):
4953 
4954 					// can character patient?
4955 					if( CanCharacterPatient( pSoldier ) )
4956 					{
4957 						PreChangeAssignment(*pSoldier);
4958 
4959 						if( ( pSoldier->bAssignment != PATIENT ) )
4960 						{
4961 							SetTimeOfAssignmentChangeForMerc( pSoldier );
4962 						}
4963 
4964 						MakeSoldiersTacticalAnimationReflectAssignment( pSoldier );
4965 
4966 						// stop showing menu
4967 						fShowAssignmentMenu = FALSE;
4968 						giAssignHighLine = -1;
4969 
4970 						// set dirty flag
4971 						fTeamPanelDirty = TRUE;
4972 						fMapScreenBottomDirty = TRUE;
4973 
4974 						ChangeSoldiersAssignment( pSoldier, PATIENT );
4975 
4976 						AssignMercToAMovementGroup(*pSoldier);
4977 
4978 						// set assignment for group
4979 						SetAssignmentForList( ( INT8 ) PATIENT, 0 );
4980 
4981 					}
4982 				break;
4983 
4984 				case( ASSIGN_MENU_VEHICLE ):
4985 					if (CanCharacterVehicle(*pSoldier))
4986 					{
4987 						if (DisplayVehicleMenu(*pSoldier))
4988 						{
4989 							fShowVehicleMenu = TRUE;
4990 							ShowBox( ghVehicleBox );
4991 						}
4992 						else
4993 						{
4994 							fShowVehicleMenu = FALSE;
4995 						}
4996 					}
4997 				break;
4998 				case( ASSIGN_MENU_REPAIR ):
4999 					if( CanCharacterRepair( pSoldier ) )
5000 					{
5001 						fShowSquadMenu = FALSE;
5002 						fShowTrainingMenu = FALSE;
5003 						fShowVehicleMenu = FALSE;
5004 						fTeamPanelDirty = TRUE;
5005 						fMapScreenBottomDirty = TRUE;
5006 						fShowRepairMenu = TRUE;
5007 						DisplayRepairMenu(*pSoldier);
5008 					}
5009 					else if( CanCharacterRepairButDoesntHaveARepairkit( pSoldier ) )
5010 					{
5011 						fTeamPanelDirty = TRUE;
5012 						fMapScreenBottomDirty = TRUE;
5013 						sString = st_format_printf(zMarksMapScreenText[17], pSoldier->name);
5014 
5015 						DoScreenIndependantMessageBox( sString , MSG_BOX_FLAG_OK, NULL );
5016 					}
5017 				break;
5018 				case( ASSIGN_MENU_TRAIN ):
5019 					if( CanCharacterPractise( pSoldier ) )
5020 					{
5021 						fShowTrainingMenu = TRUE;
5022 						DetermineBoxPositions( );
5023 						fShowSquadMenu = FALSE;
5024 						fShowVehicleMenu = FALSE;
5025 						fShowRepairMenu = FALSE;
5026 
5027 						fTeamPanelDirty = TRUE;
5028 						fMapScreenBottomDirty = TRUE;
5029 					}
5030 				break;
5031 				case( ASSIGN_MENU_CANCEL ):
5032 					fShowAssignmentMenu = FALSE;
5033 					giAssignHighLine = -1;
5034 
5035 					// set dirty flag
5036 					fTeamPanelDirty = TRUE;
5037 					fMapScreenBottomDirty = TRUE;
5038 
5039 					// reset list of characters
5040 					ResetSelectedListForMapScreen( );
5041 				break;
5042 			}
5043 		}
5044 		gfRenderPBInterface = TRUE;
5045 
5046 	}
5047 	else if( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP )
5048 	{
5049 		if( ( fShowAttributeMenu )||( fShowTrainingMenu ) || ( fShowRepairMenu ) || ( fShowVehicleMenu ) ||( fShowSquadMenu ) )
5050 		{
5051 			fShowAttributeMenu = FALSE;
5052 			fShowTrainingMenu = FALSE;
5053 			fShowRepairMenu = FALSE;
5054 			fShowVehicleMenu = FALSE;
5055 			fShowSquadMenu = FALSE;
5056 
5057 			// rerender tactical stuff
5058 			gfRenderPBInterface = TRUE;
5059 
5060 			// set dirty flag
5061 			fTeamPanelDirty       = TRUE;
5062 			fMapScreenBottomDirty = TRUE;
5063 		}
5064 	}
5065 }
5066 
5067 
MakeBox(const SGPPoint pos,const UINT32 flags)5068 static PopUpBox* MakeBox(const SGPPoint pos, const UINT32 flags)
5069 {
5070 	return CreatePopUpBox(pos, flags | POPUP_BOX_FLAG_RESIZE, FRAME_BUFFER, guiPOPUPBORDERS, guiPOPUPTEX, 6, 6, 4, 4, 2);
5071 }
5072 
5073 
5074 static UINT32 GetLastSquadListedInSquadMenu(void);
5075 
5076 
5077 // Create a pop up box for squad selection
CreateSquadBox(void)5078 static void CreateSquadBox(void)
5079 {
5080 	PopUpBox* const box = MakeBox(SquadPosition, 0);
5081 	ghSquadBox = box;
5082 
5083 	// add strings for box
5084 	UINT32 const uiMaxSquad = GetLastSquadListedInSquadMenu();
5085 	for (UINT32 i = 0; i <= uiMaxSquad; ++i)
5086 	{
5087 		// get info about current squad and put in  string
5088 		ST::string buf = ST::format("{} ( {}/{} )", pSquadMenuStrings[i], NumberOfPeopleInSquad(i), NUMBER_OF_SOLDIERS_PER_SQUAD);
5089 		AddMonoString(box, buf);
5090 	}
5091 
5092 	// add cancel line
5093 	AddMonoString(box, pSquadMenuStrings[NUMBER_OF_SQUADS]);
5094 
5095 	SetBoxTextAttrs(box);
5096 	ResizeBoxToText(box);
5097 	DetermineBoxPositions();
5098 
5099 	SGPBox const& area  = GetBoxArea(box);
5100 	INT16  const  max_y = SCREEN_HEIGHT - area.h;
5101 	if (giBoxY > max_y)
5102 	{
5103 		SquadPosition.iY = max_y;
5104 		SetBoxY(box, SquadPosition.iY);
5105 	}
5106 }
5107 
5108 
CreateEPCBox(void)5109 static void CreateEPCBox(void)
5110 {
5111 	// will create a pop up box for squad selection
5112 	PopUpBox* const box = MakeBox(AssignmentPosition, POPUP_BOX_FLAG_CENTER_TEXT);
5113 	ghEpcBox = box;
5114 
5115 	for (INT32 i = 0; i < MAX_EPC_MENU_STRING_COUNT; ++i)
5116 	{
5117 		AddMonoString(box, pEpcMenuStrings[i]);
5118 	}
5119 
5120 	SetBoxTextAttrs(box);
5121 	ResizeBoxToText(box);
5122 
5123 	SGPBox const& area  = GetBoxArea(box);
5124 	INT16  const  max_y = SCREEN_HEIGHT - area.h;
5125 	if (giBoxY > max_y)
5126 	{
5127 		AssignmentPosition.iY = max_y;
5128 		SetBoxY(box, AssignmentPosition.iY);
5129 	}
5130 }
5131 
5132 
HandleShadingOfLinesForSquadMenu(void)5133 static void HandleShadingOfLinesForSquadMenu(void)
5134 {
5135 	if (!fShowSquadMenu) return;
5136 
5137 	PopUpBox* const box = ghSquadBox;
5138 	if (box == NO_POPUP_BOX) return;
5139 
5140 	SOLDIERTYPE const& s         = *GetSelectedAssignSoldier(FALSE);
5141 	UINT32      const  max_squad = GetLastSquadListedInSquadMenu();
5142 	for (UINT32 i = 0; i <= max_squad; ++i)
5143 	{
5144 		JoinSquadResult const bResult = CanCharacterSquad(s, (INT8)i);
5145 		PopUpShade const shade =
5146 			// Shade, if the reason doesn't have a good explanatory message
5147 			bResult == CHARACTER_CANT_JOIN_SQUAD ? POPUP_SHADE      :
5148 			bResult == CHARACTER_CAN_JOIN_SQUAD  ? POPUP_SHADE_NONE :
5149 					POPUP_SHADE_SECONDARY;
5150 		ShadeStringInBox(box, i, shade);
5151 	}
5152 }
5153 
5154 
5155 static PopUpBox* CreateVehicleBox(void);
5156 
5157 
DisplayVehicleMenu(SOLDIERTYPE const & s)5158 static bool DisplayVehicleMenu(SOLDIERTYPE const& s)
5159 {
5160 	// First, clear pop up box
5161 	RemoveBox(ghVehicleBox);
5162 	ghVehicleBox = NO_POPUP_BOX;
5163 
5164 	PopUpBox* const box = CreateVehicleBox();
5165 
5166 	// Run through list of vehicles in sector and add them to pop up box
5167 	bool vehicle_present = false;
5168 	CFOR_EACH_VEHICLE(v)
5169 	{
5170 		if (!IsThisVehicleAccessibleToSoldier(s, v)) continue;
5171 		AddMonoString(box, pVehicleStrings[v.ubVehicleType]);
5172 		vehicle_present = true;
5173 	}
5174 
5175 	// Cancel string (borrow the one in the squad menu)
5176 	AddMonoString(box, pSquadMenuStrings[SQUAD_MENU_CANCEL]);
5177 
5178 	SetBoxTextAttrs(box);
5179 	ResizeBoxToText(box);
5180 	return vehicle_present;
5181 }
5182 
5183 
CreateVehicleBox(void)5184 static PopUpBox* CreateVehicleBox(void)
5185 {
5186 	ghVehicleBox = MakeBox(VehiclePosition, POPUP_BOX_FLAG_CENTER_TEXT);
5187 	return ghVehicleBox;
5188 }
5189 
5190 
CreateRepairBox(void)5191 static PopUpBox* CreateRepairBox(void)
5192 {
5193 	ghRepairBox = MakeBox(RepairPosition, POPUP_BOX_FLAG_CENTER_TEXT);
5194 	return ghRepairBox;
5195 }
5196 
5197 
CreateContractBox(const SOLDIERTYPE * const s)5198 void CreateContractBox(const SOLDIERTYPE* const s)
5199 {
5200 	if (giBoxY != 0) ContractPosition.iY = giBoxY;
5201 
5202 	PopUpBox* const box = MakeBox(ContractPosition, 0);
5203 	ghContractBox = box;
5204 
5205 	if (s)
5206 	{
5207 		MERCPROFILESTRUCT const* const p =
5208 			s->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC ?
5209 				&GetProfile(s->ubProfile) : 0;
5210 		for (UINT32 i = 0; i < MAX_CONTRACT_MENU_STRING_COUNT; ++i)
5211 		{
5212 			INT32 salary;
5213 			switch (i)
5214 			{
5215 				case CONTRACT_MENU_DAY:       salary = p ? p->sSalary          : 0; break;
5216 				case CONTRACT_MENU_WEEK:      salary = p ? p->uiWeeklySalary   : 0; break;
5217 				case CONTRACT_MENU_TWO_WEEKS: salary = p ? p->uiBiWeeklySalary : 0; break;
5218 
5219 				default:
5220 					AddMonoString(box, pContractStrings[i]);
5221 					continue;
5222 			}
5223 
5224 			ST::string sDollarString = SPrintMoney(salary);
5225 			ST::string sString = ST::format("{} ( {} )", pContractStrings[i], sDollarString);
5226 			AddMonoString(box, sString);
5227 		}
5228 	}
5229 
5230 	SetBoxTextAttrs(box);
5231 	if (s) SetBoxLineForeground(box, 0, FONT_YELLOW);
5232 	ResizeBoxToText(box);
5233 }
5234 
5235 
5236 // create attribute pop up menu for mapscreen assignments
CreateAttributeBox()5237 static void CreateAttributeBox()
5238 {
5239 	if (giBoxY != 0) AttributePosition.iY = giBoxY;
5240 
5241 	UpdateMapScreenAssignmentPositions();
5242 
5243 	PopUpBox* const box = MakeBox(AttributePosition, POPUP_BOX_FLAG_CENTER_TEXT);
5244 	ghAttributeBox = box;
5245 
5246 	for (UINT32 i = 0; i < MAX_ATTRIBUTE_STRING_COUNT; ++i)
5247 	{
5248 		AddMonoString(box, pAttributeMenuStrings[i]);
5249 	}
5250 
5251 	SetBoxTextAttrs(box);
5252 	ResizeBoxToText(box);
5253 }
5254 
5255 
CreateTrainingBox()5256 static void CreateTrainingBox()
5257 {
5258 	if (giBoxY != 0)
5259 	{
5260 		TrainPosition.iY = giBoxY + ASSIGN_MENU_TRAIN * GetFontHeight(MAP_SCREEN_FONT);
5261 	}
5262 
5263 	PopUpBox* const box = MakeBox(TrainPosition, POPUP_BOX_FLAG_CENTER_TEXT);
5264 	ghTrainingBox = box;
5265 
5266 	for (UINT32 i = 0; i < MAX_TRAIN_STRING_COUNT; ++i)
5267 	{
5268 		AddMonoString(box, pTrainingMenuStrings[i]);
5269 	}
5270 
5271 	SetBoxTextAttrs(box);
5272 	ResizeBoxToText(box);
5273 	DetermineBoxPositions();
5274 }
5275 
5276 
CreateAssignmentsBox()5277 static void CreateAssignmentsBox()
5278 {
5279 	if (giBoxY != 0) AssignmentPosition.iY = giBoxY;
5280 
5281 	PopUpBox* const box = MakeBox(AssignmentPosition, POPUP_BOX_FLAG_CENTER_TEXT);
5282 	ghAssignmentBox = box;
5283 
5284 	// No soldier is legal here!  Gets called during every mapscreen initialization even when nobody is assign char
5285 	SOLDIERTYPE* const s = GetSelectedAssignSoldier(TRUE);
5286 
5287 	for (UINT32 i = 0; i < MAX_ASSIGN_STRING_COUNT; ++i)
5288 	{
5289 		ST::string str = pAssignMenuStrings[i];
5290 		// if we have a soldier, and this is the squad line
5291 		ST::string buf;
5292 		if (i == ASSIGN_MENU_ON_DUTY && s != NULL && s->bAssignment < ON_DUTY)
5293 		{
5294 			// show his squad # in brackets
5295 			buf = ST::format("{}({})", str, s->bAssignment + 1);
5296 			str = buf;
5297 		}
5298 		AddMonoString(box, str);
5299 	}
5300 
5301 	SetBoxTextAttrs(box);
5302 	ResizeBoxToText(box);
5303 	DetermineBoxPositions();
5304 }
5305 
5306 
5307 // create remove mercbox to be placed in assignment area
CreateMercRemoveAssignBox()5308 void CreateMercRemoveAssignBox()
5309 {
5310 	PopUpBox* const box = MakeBox(AssignmentPosition, POPUP_BOX_FLAG_CENTER_TEXT);
5311 	ghRemoveMercAssignBox = box;
5312 
5313 	for (UINT32 i = 0; i < MAX_REMOVE_MERC_COUNT; ++i)
5314 	{
5315 		AddMonoString(box, pRemoveMercStrings[i]);
5316 	}
5317 
5318 	SetBoxTextAttrs(box);
5319 	ResizeBoxToText(box);
5320 }
5321 
5322 
CreateDestroyAssignmentPopUpBoxes()5323 void CreateDestroyAssignmentPopUpBoxes()
5324 {
5325 	static BOOLEAN fCreated = FALSE;
5326 
5327 	if (!fCreated && fShowAssignmentMenu)
5328 	{
5329 		guiPOPUPBORDERS = AddVideoObjectFromFile(INTERFACEDIR "/popup.sti");
5330 		guiPOPUPTEX     = AddVideoSurfaceFromFile(INTERFACEDIR "/popupbackground.pcx");
5331 
5332 		// these boxes are always created while in mapscreen...
5333 		CreateEPCBox();
5334 		CreateAssignmentsBox();
5335 		CreateTrainingBox();
5336 		CreateAttributeBox();
5337 		CreateVehicleBox();
5338 		CreateRepairBox();
5339 
5340 		UpdateMapScreenAssignmentPositions();
5341 
5342 		fCreated = TRUE;
5343 	}
5344 	else if (fCreated && !fShowAssignmentMenu)
5345 	{
5346 		DeleteVideoObject(guiPOPUPBORDERS);
5347 		DeleteVideoSurface(guiPOPUPTEX);
5348 
5349 		RemoveBox(ghAttributeBox);
5350 		ghAttributeBox = NO_POPUP_BOX;
5351 
5352 		RemoveBox(ghVehicleBox);
5353 		ghVehicleBox = NO_POPUP_BOX;
5354 
5355 		RemoveBox(ghAssignmentBox);
5356 		ghAssignmentBox = NO_POPUP_BOX;
5357 
5358 		RemoveBox(ghEpcBox);
5359 		ghEpcBox = NO_POPUP_BOX;
5360 
5361 		RemoveBox(ghRepairBox);
5362 		ghRepairBox = NO_POPUP_BOX;
5363 
5364 		RemoveBox(ghTrainingBox);
5365 		ghTrainingBox = NO_POPUP_BOX;
5366 
5367 		RebuildCurrentSquad();
5368 
5369 		gfIgnoreScrolling = FALSE;
5370 		fCreated          = FALSE;
5371 	}
5372 }
5373 
5374 
DetermineBoxPositions()5375 void DetermineBoxPositions()
5376 {
5377 	// depending on how many boxes there are, reposition as needed
5378 	if (!fShowAssignmentMenu || ghAssignmentBox == NO_POPUP_BOX) return;
5379 
5380 	SOLDIERTYPE const* const s = GetSelectedAssignSoldier(TRUE);
5381 	// no soldier is legal here!  Gets called during every mapscreen initialization even when nobody is assign char
5382 	if (s == NULL) return;
5383 
5384 	if (fInMapMode)
5385 	{
5386 		SGPBox const& area = GetBoxArea(ghAssignmentBox);
5387 		gsAssignmentBoxesX = area.x;
5388 		gsAssignmentBoxesY = area.y;
5389 	}
5390 
5391 	INT16 x = gsAssignmentBoxesX;
5392 	INT16 y = gsAssignmentBoxesY;
5393 
5394 	PopUpBox* const box = (s->ubWhatKindOfMercAmI == MERC_TYPE__EPC ? ghEpcBox : ghAssignmentBox);
5395 	SetBoxXY(box, x, y);
5396 
5397 	// hang it right beside the assignment/EPC box menu
5398 	x += GetBoxArea(box).w;
5399 
5400 	if (fShowSquadMenu && ghSquadBox != NO_POPUP_BOX)
5401 	{
5402 		SetBoxXY(ghSquadBox, x, y);
5403 	}
5404 
5405 	if (fShowRepairMenu && ghRepairBox != NO_POPUP_BOX)
5406 	{
5407 		CreateDestroyMouseRegionForRepairMenu();
5408 		y += (GetFontHeight(MAP_SCREEN_FONT) + 2) * ASSIGN_MENU_REPAIR;
5409 		SetBoxXY(ghRepairBox, x, y);
5410 	}
5411 
5412 	if (fShowTrainingMenu && ghTrainingBox != NO_POPUP_BOX)
5413 	{
5414 		y += (GetFontHeight(MAP_SCREEN_FONT) + 2) * ASSIGN_MENU_TRAIN;
5415 		SetBoxXY(ghTrainingBox, x, y);
5416 		TrainPosition.iX = x;
5417 		TrainPosition.iY = y;
5418 
5419 		if (fShowAttributeMenu && ghAttributeBox != NO_POPUP_BOX)
5420 		{
5421 			// hang it right beside the training box menu
5422 			SGPBox const& area = GetBoxArea(ghTrainingBox);
5423 			SetBoxXY(ghAttributeBox, area.x + area.w, area.y);
5424 		}
5425 	}
5426 }
5427 
5428 
SetTacticalPopUpAssignmentBoxXY()5429 void SetTacticalPopUpAssignmentBoxXY()
5430 {
5431 	SOLDIERTYPE const& s = *GetSelectedAssignSoldier(FALSE);
5432 	INT16 sX;
5433 	INT16 sY;
5434 	GetSoldierScreenPos(&s, &sX, &sY);
5435 
5436 	if (sX < 0) sX = 0;
5437 	if (sY < 0) sY = 0;
5438 	sX += 30;
5439 
5440 	// ATE: Check if we are past tactical viewport....
5441 	// Use estimate widths/heights
5442 	if (sX > g_ui.m_screenWidth  - 50)	sX -= 90;
5443 	if (sY > g_ui.m_screenHeight)		sY = g_ui.m_screenHeight;
5444 
5445 	gsAssignmentBoxesX = sX;
5446 	gsAssignmentBoxesY = sY;
5447 }
5448 
5449 
RepositionMouseRegions(void)5450 static void RepositionMouseRegions(void)
5451 {
5452 	INT16 const sDeltaX = gsAssignmentBoxesX - gAssignmentMenuRegion[0].RegionTopLeftX;
5453 	INT16 const sDeltaY = gsAssignmentBoxesY - gAssignmentMenuRegion[0].RegionTopLeftY + GetTopMarginSize(ghAssignmentBox);
5454 
5455 	// find the delta from the old to the new, and alter values accordingly
5456 	for (UINT32 i = 0; i < GetNumberOfLinesOfTextInBox(ghAssignmentBox); ++i)
5457 	{
5458 		MOUSE_REGION* const r = &gAssignmentMenuRegion[i];
5459 		r->RegionTopLeftX     += sDeltaX;
5460 		r->RegionTopLeftY     += sDeltaY;
5461 		r->RegionBottomRightX += sDeltaX;
5462 		r->RegionBottomRightY += sDeltaY;
5463 	}
5464 
5465 	gfPausedTacticalRenderFlags = TRUE;
5466 }
5467 
5468 
AdjustBoxPos(SGPBox const & assignment_area,PopUpBox * const other_box,INT32 const offset_line)5469 static void AdjustBoxPos(SGPBox const& assignment_area, PopUpBox* const other_box, INT32 const offset_line)
5470 {
5471 	SGPBox const& other_area = GetBoxArea(other_box);
5472 
5473 	INT16 const max_x = SCREEN_WIDTH - assignment_area.w - other_area.w;
5474 	if (gsAssignmentBoxesX > max_x)
5475 	{
5476 		gsAssignmentBoxesX = max_x;
5477 		SetRenderFlags(RENDER_FLAG_FULL);
5478 	}
5479 
5480 	INT16 const dy    = (GetFontHeight(MAP_SCREEN_FONT) + 2) * offset_line;
5481 	INT16 const ah    = assignment_area.h;
5482 	INT16 const oh    = other_area.h + dy;
5483 	INT16 const max_y = gsVIEWPORT_WINDOW_END_Y - (ah > oh ? ah : oh);
5484 	if (gsAssignmentBoxesY > max_y)
5485 	{
5486 		gsAssignmentBoxesY = max_y;
5487 		SetRenderFlags(RENDER_FLAG_FULL);
5488 	}
5489 
5490 	INT16 const x = gsAssignmentBoxesX + assignment_area.w;
5491 	INT16 const y = gsAssignmentBoxesY + dy;
5492 	SetBoxXY(other_box, x, y);
5493 }
5494 
5495 
CheckAndUpdateTacticalAssignmentPopUpPositions(void)5496 static void CheckAndUpdateTacticalAssignmentPopUpPositions(void)
5497 {
5498 	if (!fShowAssignmentMenu) return;
5499 	if (fInMapMode)           return;
5500 
5501 	SOLDIERTYPE const& s               = *GetSelectedAssignSoldier(FALSE);
5502 	PopUpBox*   const  assignment_box  = s.ubWhatKindOfMercAmI == MERC_TYPE__EPC ? ghEpcBox : ghAssignmentBox;
5503 	SGPBox      const& assignment_area = GetBoxArea(assignment_box);
5504 
5505 	if (fShowRepairMenu)
5506 	{
5507 		AdjustBoxPos(assignment_area, ghRepairBox, ASSIGN_MENU_REPAIR);
5508 	}
5509 	else if (fShowSquadMenu)
5510 	{
5511 		AdjustBoxPos(assignment_area, ghSquadBox, ASSIGN_MENU_ON_DUTY);
5512 	}
5513 	else if (fShowAttributeMenu)
5514 	{
5515 		SGPBox const& train_area = GetBoxArea(ghTrainingBox);
5516 		SGPBox const& attr_area  = GetBoxArea(ghAttributeBox);
5517 
5518 		INT16 const max_x = SCREEN_WIDTH - assignment_area.w - train_area.w - attr_area.w;
5519 		if (gsAssignmentBoxesX > max_x)
5520 		{
5521 			gsAssignmentBoxesX = max_x;
5522 			SetRenderFlags(RENDER_FLAG_FULL);
5523 		}
5524 
5525 		INT16 const dy    = (GetFontHeight(MAP_SCREEN_FONT) + 2) * ASSIGN_MENU_TRAIN;
5526 		INT16 const max_y = gsVIEWPORT_WINDOW_END_Y - attr_area.h - dy;
5527 		if (gsAssignmentBoxesY > max_y)
5528 		{
5529 			gsAssignmentBoxesY = max_y;
5530 			SetRenderFlags(RENDER_FLAG_FULL);
5531 		}
5532 
5533 		INT16 const x = gsAssignmentBoxesX + assignment_area.w;
5534 		INT16 const y = gsAssignmentBoxesY + dy;
5535 		SetBoxXY(ghAttributeBox, x + train_area.w, y);
5536 		SetBoxXY(ghTrainingBox,  x,                 y);
5537 	}
5538 	else if (fShowTrainingMenu)
5539 	{
5540 		AdjustBoxPos(assignment_area, ghTrainingBox, ASSIGN_MENU_TRAIN);
5541 	}
5542 	else
5543 	{
5544 		// just the assignment box
5545 		INT16 const max_x = SCREEN_WIDTH - assignment_area.w;
5546 		if (gsAssignmentBoxesX > max_x)
5547 		{
5548 			gsAssignmentBoxesX = max_x;
5549 			SetRenderFlags(RENDER_FLAG_FULL);
5550 		}
5551 
5552 		INT16 const max_y = gsVIEWPORT_WINDOW_END_Y - assignment_area.h;
5553 		if (gsAssignmentBoxesY > max_y)
5554 		{
5555 			gsAssignmentBoxesY = max_y;
5556 			SetRenderFlags(RENDER_FLAG_FULL);
5557 		}
5558 
5559 		SetBoxXY(assignment_box, gsAssignmentBoxesX, gsAssignmentBoxesY);
5560 	}
5561 
5562 	RepositionMouseRegions();
5563 }
5564 
5565 
PositionCursorForTacticalAssignmentBox(void)5566 static void PositionCursorForTacticalAssignmentBox(void)
5567 {
5568 	// position cursor over y of on duty in tactical assignments
5569 	if (gGameSettings.fOptions[TOPTION_DONT_MOVE_MOUSE]) return;
5570 
5571 	PopUpBox const* const  box  = ghAssignmentBox;
5572 	INT32           const  h    = GetLineSpace(box) + GetFontHeight(GetBoxFont(box));
5573 	SGPBox          const& area = GetBoxArea(box);
5574 	SimulateMouseMovement(area.x + area.w - 6, area.y + h / 2 + 2);
5575 }
5576 
5577 
5578 static bool CharacterIsTakingItEasy(SOLDIERTYPE const&);
5579 
5580 
HandleRestFatigueAndSleepStatus()5581 static void HandleRestFatigueAndSleepStatus()
5582 {
5583 	{ /* Run through all player characters and handle their rest, fatigue, and
5584 		* going to sleep */
5585 		bool   reason_added = false;
5586 		bool   box_set_up   = false;
5587 		UINT16 sleep_quote  = QUOTE_NEED_SLEEP;
5588 		FOR_EACH_IN_TEAM(i, OUR_TEAM)
5589 		{
5590 			SOLDIERTYPE& s = *i;
5591 			if (IsMechanical(s))                 continue;
5592 			if (s.bAssignment == ASSIGNMENT_POW) continue;
5593 			if (s.bAssignment == IN_TRANSIT)     continue;
5594 
5595 			/* If character can sleep, he doesn't actually have to be put asleep to
5596 			 * get some rest, many other assignments and conditions allow for
5597 			 * automatic recovering from fatigue. */
5598 			if (CharacterIsTakingItEasy(s))
5599 			{ // Let them rest some
5600 				RestCharacter(&s);
5601 			}
5602 			else
5603 			{ // Wear them down
5604 				FatigueCharacter(s);
5605 			}
5606 
5607 			// Check for mercs going to sleep
5608 			if (s.fMercAsleep) continue;
5609 
5610 			// If dead tired
5611 			if (s.bBreathMax <= BREATHMAX_ABSOLUTE_MINIMUM)
5612 			{
5613 				/* If between sectors, don't put tired mercs to sleep, will be handled
5614 					* when they arrive at the next sector */
5615 				if (s.fBetweenSectors) continue;
5616 
5617 				/* He goes to sleep, provided it's at all possible (it still won't happen
5618 					* in a hostile sector, etc.) */
5619 				if (!SetMercAsleep(s, false)) continue;
5620 
5621 				if (s.bAssignment < ON_DUTY || s.bAssignment == VEHICLE)
5622 				{ // On a squad/vehicle, complain, then drop
5623 					TacticalCharacterDialogue(&s, QUOTE_NEED_SLEEP);
5624 					MakeCharacterDialogueEventSleep(s, true);
5625 					sleep_quote = QUOTE_ME_TOO;
5626 				}
5627 
5628 				// Guy collapses
5629 				s.fMercCollapsedFlag = TRUE;
5630 			}
5631 			else if (s.bBreathMax < BREATHMAX_PRETTY_TIRED && !s.fForcedToStayAwake)
5632 			{ // Pretty tired, and not forced to stay awake
5633 				if (s.bAssignment >= ON_DUTY && s.bAssignment != VEHICLE)
5634 				{ // Not on squad/in vehicle
5635 					// Try to go to sleep on your own
5636 					if (!SetMercAsleep(s, false)) continue;
5637 
5638 					if (!gGameSettings.fOptions[TOPTION_SLEEPWAKE_NOTIFICATION]) continue;
5639 
5640 					// If the first one
5641 					if (!reason_added)
5642 					{ // Tell player about it
5643 						AddReasonToWaitingListQueue(ASLEEP_GOING_AUTO_FOR_UPDATE);
5644 						reason_added = true;
5645 					}
5646 
5647 					AddSoldierToWaitingListQueue(s);
5648 					box_set_up = true;
5649 				}
5650 				else
5651 				{ // Tired, in a squad / vehicle
5652 					if (s.fComplainedThatTired) continue;
5653 					// He hasn't complained yet
5654 
5655 					TacticalCharacterDialogue(&s, sleep_quote);
5656 					sleep_quote            = QUOTE_ME_TOO;
5657 					s.fComplainedThatTired = TRUE;
5658 				}
5659 			}
5660 		}
5661 
5662 		if (box_set_up) AddDisplayBoxToWaitingQueue();
5663 	}
5664 
5665 	{ /* Now handle waking. Needs seperate list queue, that's why it has its own
5666 		* loop */
5667 		bool box_set_up   = false;
5668 		bool reason_added = false;
5669 		FOR_EACH_IN_TEAM(i, OUR_TEAM)
5670 		{
5671 			SOLDIERTYPE& s = *i;
5672 			if (IsMechanical(s))                 continue;
5673 			if (s.bAssignment == ASSIGNMENT_POW) continue;
5674 			if (s.bAssignment == IN_TRANSIT)     continue;
5675 			// Guys between sectors CAN wake up while between sectors (sleeping in vehicle)
5676 
5677 			if (s.bBreathMax >= BREATHMAX_CANCEL_COLLAPSE)
5678 			{ // Reset the collapsed flag well before reaching the wakeup state
5679 				s.fMercCollapsedFlag = FALSE;
5680 			}
5681 
5682 			if (!s.fMercAsleep) continue;
5683 
5684 			// Had enough rest?
5685 			if (s.bBreathMax < BREATHMAX_FULLY_RESTED) continue;
5686 
5687 			// Try to wake merc up
5688 			if (!SetMercAwake(&s, FALSE, FALSE)) continue;
5689 
5690 			// If not on squad/in vehicle, tell player about it
5691 			if (s.bAssignment < ON_DUTY || s.bAssignment == VEHICLE) continue;
5692 
5693 			if (!gGameSettings.fOptions[TOPTION_SLEEPWAKE_NOTIFICATION]) continue;
5694 
5695 			if (!reason_added)
5696 			{
5697 				AddReasonToWaitingListQueue(ASSIGNMENT_RETURNING_FOR_UPDATE);
5698 				reason_added = true;
5699 			}
5700 
5701 			AddSoldierToWaitingListQueue(s);
5702 			box_set_up = true;
5703 		}
5704 
5705 		if (box_set_up)
5706 		{
5707 			AddDisplayBoxToWaitingQueue();
5708 		}
5709 	}
5710 }
5711 
5712 
5713 // Can the character repair this vehicle?
CanCharacterRepairVehicle(SOLDIERTYPE const & s,VEHICLETYPE const & v)5714 static bool CanCharacterRepairVehicle(SOLDIERTYPE const& s, VEHICLETYPE const& v)
5715 {
5716 	// is vehicle destroyed?
5717 	if (v.fDestroyed) return false;
5718 
5719 	// is it damaged at all?
5720 	if (!DoesVehicleNeedAnyRepairs(v)) return false;
5721 
5722 	// same sector, neither is between sectors, and OK To Use (player owns it) ?
5723 	if (!IsThisVehicleAccessibleToSoldier(s, v)) return false;
5724 
5725 #if 0 // Assignment distance limits removed.  Sep/11/98.  ARM
5726 	// If currently loaded sector, are we close enough?
5727 	if (s.sSectorX == gWorldSectorX  &&
5728 			s.sSectorY == gWorldSectorY  &&
5729 			s.bSectorZ == gbWorldSectorZ &&
5730 			PythSpacesAway(s.sGridNo, v.sGridNo) > MAX_DISTANCE_FOR_REPAIR)
5731 	{
5732 		return false;
5733 	}
5734 #endif
5735 
5736 	return true;
5737 }
5738 
5739 
5740 static SOLDIERTYPE* GetRobotSoldier(void);
5741 
5742 
IsRobotInThisSector(INT16 const sSectorX,INT16 const sSectorY,INT8 const bSectorZ)5743 static BOOLEAN IsRobotInThisSector(INT16 const sSectorX, INT16 const sSectorY, INT8 const bSectorZ)
5744 {
5745 	SOLDIERTYPE const* const s = GetRobotSoldier();
5746 	return
5747 		s                       &&
5748 		s->sSectorX == sSectorX &&
5749 		s->sSectorY == sSectorY &&
5750 		s->bSectorZ == bSectorZ &&
5751 		!s->fBetweenSectors;
5752 }
5753 
5754 
GetRobotSoldier(void)5755 static SOLDIERTYPE* GetRobotSoldier(void)
5756 {
5757 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
5758 	{
5759 		if (AM_A_ROBOT(s)) return s;
5760 	}
5761 	return NULL;
5762 }
5763 
5764 
5765 // can soldier repair robot
CanCharacterRepairRobot(SOLDIERTYPE const * const pSoldier)5766 static BOOLEAN CanCharacterRepairRobot(SOLDIERTYPE const* const pSoldier)
5767 {
5768 	SOLDIERTYPE *pRobot = NULL;
5769 
5770 	// do we in fact have the robot on the team?
5771 	pRobot = GetRobotSoldier( );
5772 	if( pRobot == NULL )
5773 	{
5774 		return( FALSE );
5775 	}
5776 
5777 	// if robot isn't damaged at all
5778 	if( pRobot -> bLife == pRobot -> bLifeMax )
5779 	{
5780 		return( FALSE );
5781 	}
5782 
5783 	// is the robot in the same sector
5784 	if (!IsRobotInThisSector(pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ))
5785 	{
5786 		return( FALSE );
5787 	}
5788 
5789 /* Assignment distance limits removed.  Sep/11/98.  ARM
5790 	// if that sector is currently loaded, check distance to robot
5791 	if( ( pSoldier -> sSectorX == gWorldSectorX ) && ( pSoldier -> sSectorY == gWorldSectorY ) && ( pSoldier -> bSectorZ == gbWorldSectorZ ) )
5792 	{
5793 		if( PythSpacesAway( pSoldier -> sGridNo, pRobot -> sGridNo ) > MAX_DISTANCE_FOR_REPAIR )
5794 		{
5795 			return FALSE;
5796 		}
5797 	}
5798 */
5799 
5800 	return( TRUE );
5801 }
5802 
5803 
5804 static UINT8 RepairRobot(SOLDIERTYPE* pRobot, UINT8 ubRepairPts, BOOLEAN* pfNothingLeftToRepair);
5805 
5806 
HandleRepairOfRobotBySoldier(UINT8 const ubRepairPts,BOOLEAN * const pfNothingLeftToRepair)5807 static UINT8 HandleRepairOfRobotBySoldier(UINT8 const ubRepairPts, BOOLEAN* const pfNothingLeftToRepair)
5808 {
5809 	SOLDIERTYPE *pRobot = NULL;
5810 
5811 	pRobot = GetRobotSoldier( );
5812 
5813 	// do the actual repairs
5814 	return( RepairRobot( pRobot, ubRepairPts, pfNothingLeftToRepair ) );
5815 }
5816 
5817 
RepairRobot(SOLDIERTYPE * pRobot,UINT8 ubRepairPts,BOOLEAN * pfNothingLeftToRepair)5818 static UINT8 RepairRobot(SOLDIERTYPE* pRobot, UINT8 ubRepairPts, BOOLEAN* pfNothingLeftToRepair)
5819 {
5820 	UINT8 ubPointsUsed = 0;
5821 
5822 
5823 	// is it "dead" ?
5824 	if( pRobot -> bLife == 0 )
5825 	{
5826 		*pfNothingLeftToRepair = TRUE;
5827 		return( ubPointsUsed );
5828 	}
5829 
5830 	// is it "unhurt" ?
5831 	if( pRobot -> bLife == pRobot -> bLifeMax )
5832 	{
5833 		*pfNothingLeftToRepair = TRUE;
5834 		return( ubPointsUsed );
5835 	}
5836 
5837 	// if we have enough or more than we need
5838 	if( pRobot -> bLife + ubRepairPts >= pRobot -> bLifeMax )
5839 	{
5840 		ubPointsUsed = ( pRobot -> bLifeMax - pRobot -> bLife );
5841 		pRobot -> bLife = pRobot -> bLifeMax;
5842 	}
5843 	else
5844 	{
5845 		// not enough, do what we can
5846 		ubPointsUsed = ubRepairPts;
5847 		pRobot -> bLife += ubRepairPts;
5848 	}
5849 
5850 	if ( pRobot->bLife == pRobot->bLifeMax )
5851 	{
5852 		*pfNothingLeftToRepair = TRUE;
5853 	}
5854 	else
5855 	{
5856 		*pfNothingLeftToRepair = FALSE;
5857 	}
5858 
5859 	return( ubPointsUsed );
5860 }
5861 
5862 
PreSetAssignment(SOLDIERTYPE & s,INT8 const assignment)5863 static void PreSetAssignment(SOLDIERTYPE& s, INT8 const assignment)
5864 {
5865 	PreChangeAssignment(s);
5866 	fTeamPanelDirty       = TRUE;
5867 	fMapScreenBottomDirty = TRUE;
5868 	gfRenderPBInterface   = TRUE;
5869 }
5870 
5871 
PostSetAssignment(SOLDIERTYPE & s,INT8 const assignment)5872 static void PostSetAssignment(SOLDIERTYPE& s, INT8 const assignment)
5873 {
5874 	ChangeSoldiersAssignment(&s, assignment);
5875 	AssignMercToAMovementGroup(s);
5876 }
5877 
5878 
SetSoldierAssignmentHospital(SOLDIERTYPE & s)5879 void SetSoldierAssignmentHospital(SOLDIERTYPE& s)
5880 {
5881 	if (!CanCharacterPatient(&s)) return;
5882 	PreSetAssignment(s, ASSIGNMENT_HOSPITAL);
5883 	s.bBleeding = 0;
5884 	if (s.bAssignment != ASSIGNMENT_HOSPITAL) SetTimeOfAssignmentChangeForMerc(&s);
5885 	RebuildCurrentSquad();
5886 	PostSetAssignment(s, ASSIGNMENT_HOSPITAL);
5887 }
5888 
5889 
SetSoldierAssignmentPatient(SOLDIERTYPE & s)5890 static void SetSoldierAssignmentPatient(SOLDIERTYPE& s)
5891 {
5892 	if (!CanCharacterPatient(&s)) return;
5893 	PreSetAssignment(s, PATIENT);
5894 	if (s.bAssignment != PATIENT) SetTimeOfAssignmentChangeForMerc(&s);
5895 	PostSetAssignment(s, PATIENT);
5896 }
5897 
5898 
SetSoldierAssignmentDoctor(SOLDIERTYPE & s)5899 static void SetSoldierAssignmentDoctor(SOLDIERTYPE& s)
5900 {
5901 	if (!CanCharacterDoctor(&s)) return;
5902 	PreSetAssignment(s, DOCTOR);
5903 	if (s.bAssignment != DOCTOR) SetTimeOfAssignmentChangeForMerc(&s);
5904 	MakeSureMedKitIsInHand(&s);
5905 	PostSetAssignment(s, DOCTOR);
5906 }
5907 
5908 
SetSoldierAssignmentTrainTown(SOLDIERTYPE & s)5909 static void SetSoldierAssignmentTrainTown(SOLDIERTYPE& s)
5910 {
5911 	if (!CanCharacterTrainMilitia(&s)) return;
5912 	PreSetAssignment(s, TRAIN_TOWN);
5913 	if (s.bAssignment != TRAIN_TOWN) SetTimeOfAssignmentChangeForMerc(&s);
5914 	if (!pMilitiaTrainerSoldier &&
5915 			!SectorInfo[SECTOR(s.sSectorX, s.sSectorY)].fMilitiaTrainingPaid)
5916 	{
5917 		// show a message to confirm player wants to charge cost
5918 		HandleInterfaceMessageForCostOfTrainingMilitia(&s);
5919 	}
5920 	PostSetAssignment(s, TRAIN_TOWN);
5921 }
5922 
5923 
SetSoldierAssignmentTrainSelf(SOLDIERTYPE & s,INT8 const stat)5924 static void SetSoldierAssignmentTrainSelf(SOLDIERTYPE& s, INT8 const stat)
5925 {
5926 	if (!CanCharacterTrainStat(&s, stat, TRUE, FALSE)) return;
5927 	PreSetAssignment(s, TRAIN_SELF);
5928 	if (s.bAssignment != TRAIN_SELF) SetTimeOfAssignmentChangeForMerc(&s);
5929 	// set stat to train
5930 	s.bTrainStat = stat;
5931 	PostSetAssignment(s, TRAIN_SELF);
5932 }
5933 
5934 
SetSoldierAssignmentTrainTeammate(SOLDIERTYPE & s,INT8 const stat)5935 static void SetSoldierAssignmentTrainTeammate(SOLDIERTYPE& s, INT8 const stat)
5936 {
5937 	if (!CanCharacterTrainStat(&s, stat, FALSE, TRUE)) return;
5938 	PreSetAssignment(s, TRAIN_TEAMMATE);
5939 	if (s.bAssignment != TRAIN_TEAMMATE) SetTimeOfAssignmentChangeForMerc(&s);
5940 	// set stat to train
5941 	s.bTrainStat = stat;
5942 	PostSetAssignment(s, TRAIN_TEAMMATE);
5943 }
5944 
5945 
SetSoldierAssignmentTrainByOther(SOLDIERTYPE & s,INT8 const stat)5946 static void SetSoldierAssignmentTrainByOther(SOLDIERTYPE& s, INT8 const stat)
5947 {
5948 	if (!CanCharacterTrainStat(&s, stat, TRUE, FALSE)) return;
5949 	PreSetAssignment(s, TRAIN_BY_OTHER);
5950 	if (s.bAssignment != TRAIN_BY_OTHER) SetTimeOfAssignmentChangeForMerc(&s);
5951 	// set stat to train
5952 	s.bTrainStat = stat;
5953 	PostSetAssignment(s, TRAIN_BY_OTHER);
5954 }
5955 
5956 
SetSoldierAssignmentRepair(SOLDIERTYPE & s,BOOLEAN const robot,INT8 const vehicle_id)5957 void SetSoldierAssignmentRepair(SOLDIERTYPE& s, BOOLEAN const robot, INT8 const vehicle_id)
5958 {
5959 	if (!CanCharacterRepair(&s)) return;
5960 	PreSetAssignment(s, REPAIR);
5961 	if (s.bAssignment != REPAIR || s.fFixingRobot != robot || s.bVehicleUnderRepairID != vehicle_id)
5962 	{
5963 		SetTimeOfAssignmentChangeForMerc(&s);
5964 	}
5965 	MakeSureToolKitIsInHand(&s);
5966 	s.fFixingRobot          = robot;
5967 	s.bVehicleUnderRepairID = vehicle_id;
5968 	PostSetAssignment(s, REPAIR);
5969 }
5970 
HandleAssignmentExpansionAndHighLightForAssignMenu(SOLDIERTYPE * pSoldier)5971 static BOOLEAN HandleAssignmentExpansionAndHighLightForAssignMenu(SOLDIERTYPE* pSoldier)
5972 {
5973 	if( fShowSquadMenu )
5974 	{
5975 		// squad menu up?..if so, highlight squad line the previous menu
5976 		if( pSoldier -> ubWhatKindOfMercAmI == MERC_TYPE__EPC )
5977 		{
5978 			HighLightBoxLine(ghEpcBox, EPC_MENU_ON_DUTY);
5979 		}
5980 		else
5981 		{
5982 			HighLightBoxLine( ghAssignmentBox, ASSIGN_MENU_ON_DUTY );
5983 		}
5984 
5985 		return( TRUE );
5986 	}
5987 	else if( fShowTrainingMenu )
5988 	{
5989 		// highlight train line the previous menu
5990 		HighLightBoxLine( ghAssignmentBox, ASSIGN_MENU_TRAIN );
5991 		return( TRUE );
5992 	}
5993 	else if( fShowRepairMenu )
5994 	{
5995 		// highlight repair line the previous menu
5996 		HighLightBoxLine( ghAssignmentBox, ASSIGN_MENU_REPAIR );
5997 		return( TRUE );
5998 	}
5999 	else if( fShowVehicleMenu )
6000 	{
6001 		// highlight vehicle line the previous menu
6002 		HighLightBoxLine( ghAssignmentBox, ASSIGN_MENU_VEHICLE );
6003 		return( TRUE );
6004 	}
6005 
6006 	return( FALSE );
6007 }
6008 
6009 
HandleAssignmentExpansionAndHighLightForTrainingMenu(void)6010 static BOOLEAN HandleAssignmentExpansionAndHighLightForTrainingMenu(void)
6011 {
6012 	if( fShowAttributeMenu )
6013 	{
6014 		switch ( gbTrainingMode )
6015 		{
6016 			case TRAIN_SELF:
6017 				HighLightBoxLine( ghTrainingBox, TRAIN_MENU_SELF );
6018 				return( TRUE );
6019 			case TRAIN_TEAMMATE:
6020 				HighLightBoxLine( ghTrainingBox, TRAIN_MENU_TEAMMATES );
6021 				return( TRUE );
6022 			case TRAIN_BY_OTHER:
6023 				HighLightBoxLine( ghTrainingBox, TRAIN_MENU_TRAIN_BY_OTHER );
6024 				return( TRUE );
6025 
6026 			default:
6027 				Assert( FALSE );
6028 				break;
6029 		}
6030 	}
6031 
6032 	return( FALSE );
6033 }
6034 
6035 
HandleShowingOfMovementBox(void)6036 static BOOLEAN HandleShowingOfMovementBox(void)
6037 {
6038 	// if the list is being shown, then show it
6039 	if (fShowMapScreenMovementList)
6040 	{
6041 		MarkAllBoxesAsAltered( );
6042 		ShowBox( ghMoveBox );
6043 		return( TRUE );
6044 	}
6045 	else
6046 	{
6047 		if( IsBoxShown( ghMoveBox ) )
6048 		{
6049 			HideBox( ghMoveBox );
6050 			fMapPanelDirty = TRUE;
6051 			gfRenderPBInterface = TRUE;
6052 			fTeamPanelDirty = TRUE;
6053 			fMapScreenBottomDirty = TRUE;
6054 			fCharacterInfoPanelDirty = TRUE;
6055 		}
6056 	}
6057 
6058 	return( FALSE );
6059 }
6060 
6061 
HandleShadingOfLinesForTrainingMenu(void)6062 static void HandleShadingOfLinesForTrainingMenu(void)
6063 {
6064 	if (!fShowTrainingMenu) return;
6065 
6066 	PopUpBox* const box = ghTrainingBox;
6067 	if (box == NO_POPUP_BOX) return;
6068 
6069 	SOLDIERTYPE const& s = *GetSelectedAssignSoldier(FALSE);
6070 
6071 	ShadeStringInBox(box, TRAIN_MENU_SELF,           !CanCharacterPractise(&s));
6072 	PopUpShade const shade =
6073 		!BasicCanCharacterTrainMilitia(&s) ? POPUP_SHADE           :
6074 		!CanCharacterTrainMilitia(&s)      ? POPUP_SHADE_SECONDARY :
6075 		POPUP_SHADE_NONE;
6076 	ShadeStringInBox(box, TRAIN_MENU_TOWN,           shade);
6077 	ShadeStringInBox(box, TRAIN_MENU_TEAMMATES,      !CanCharacterTrainTeammates(&s));
6078 	ShadeStringInBox(box, TRAIN_MENU_TRAIN_BY_OTHER, !CanCharacterBeTrainedByOther(&s));
6079 }
6080 
6081 
HandleShadingOfLinesForAttributeMenus(void)6082 static void HandleShadingOfLinesForAttributeMenus(void)
6083 {
6084 	// will do the same as updateassignments...but with training pop up box strings
6085 	if (!fShowAttributeMenu) return;
6086 
6087 	PopUpBox* const box = ghAttributeBox;
6088 	if (box == NO_POPUP_BOX) return;
6089 
6090 	SOLDIERTYPE const& s = *GetSelectedAssignSoldier(FALSE);
6091 	for (INT8 stat = 0; stat < ATTRIB_MENU_CANCEL; ++stat)
6092 	{
6093 		BOOLEAN stat_trainable;
6094 		switch (gbTrainingMode)
6095 		{
6096 			case TRAIN_SELF:
6097 				stat_trainable = CanCharacterTrainStat(&s, stat, TRUE, FALSE);
6098 				break;
6099 
6100 			case TRAIN_TEAMMATE:
6101 				// DO allow trainers to be assigned without any partners (students)
6102 				stat_trainable = CanCharacterTrainStat(&s, stat, FALSE, TRUE);
6103 				break;
6104 
6105 			case TRAIN_BY_OTHER:
6106 				// DO allow students to be assigned without any partners (trainers)
6107 				stat_trainable = CanCharacterTrainStat(&s, stat, TRUE, FALSE);
6108 				break;
6109 
6110 			default:
6111 				Assert(FALSE);
6112 				stat_trainable = FALSE;
6113 				break;
6114 		}
6115 		ShadeStringInBox(box, stat, !stat_trainable);
6116 	}
6117 }
6118 
6119 
6120 static BOOLEAN ValidTrainingPartnerInSameSectorOnAssignmentFound(SOLDIERTYPE* pTargetSoldier, INT8 bTargetAssignment, INT8 bTargetStat);
6121 
6122 
ReportTrainersTraineesWithoutPartners(void)6123 static void ReportTrainersTraineesWithoutPartners(void)
6124 {
6125 	// check for each instructor
6126 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
6127 	{
6128 		if (s->bAssignment == TRAIN_TEAMMATE &&
6129 				s->bLife > 0 &&
6130 				!ValidTrainingPartnerInSameSectorOnAssignmentFound(s, TRAIN_BY_OTHER, s->bTrainStat))
6131 		{
6132 			AssignmentDone(s, TRUE, TRUE);
6133 		}
6134 	}
6135 
6136 	// check each trainee
6137 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
6138 	{
6139 		if (s->bAssignment == TRAIN_BY_OTHER &&
6140 				s->bLife > 0 &&
6141 				!ValidTrainingPartnerInSameSectorOnAssignmentFound(s, TRAIN_TEAMMATE, s->bTrainStat))
6142 		{
6143 			AssignmentDone(s, TRUE, TRUE);
6144 		}
6145 	}
6146 }
6147 
6148 
SetMercAsleep(SOLDIERTYPE & s,bool const give_warning)6149 bool SetMercAsleep(SOLDIERTYPE& s, bool const give_warning)
6150 {
6151 	if (!CanCharacterSleep(s, give_warning)) return false;
6152 	PutMercInAsleepState(s);
6153 	return true;
6154 }
6155 
6156 
PutMercInAsleepState(SOLDIERTYPE & s)6157 void PutMercInAsleepState(SOLDIERTYPE& s)
6158 {
6159 	if (s.fMercAsleep) return;
6160 
6161 	if (gfWorldLoaded && s.bInSector)
6162 	{
6163 		UINT16 const state = guiCurrentScreen == GAME_SCREEN ? GOTO_SLEEP : SLEEPING;
6164 		ChangeSoldierState(&s, state, 1, TRUE);
6165 	}
6166 
6167 	// Set merc asleep
6168 	s.fMercAsleep            = TRUE;
6169 	// Refresh panels
6170 	fCharacterInfoPanelDirty = TRUE;
6171 	fTeamPanelDirty          = TRUE;
6172 }
6173 
6174 
SetMercAwake(SOLDIERTYPE * pSoldier,BOOLEAN fGiveWarning,BOOLEAN fForceHim)6175 BOOLEAN SetMercAwake( SOLDIERTYPE *pSoldier, BOOLEAN fGiveWarning, BOOLEAN fForceHim )
6176 {
6177 	// forcing him skips all normal checks!
6178 	if ( !fForceHim )
6179 	{
6180 		if ( !CanCharacterBeAwakened( pSoldier, fGiveWarning ) )
6181 		{
6182 			return( FALSE );
6183 		}
6184 	}
6185 
6186 	PutMercInAwakeState( pSoldier );
6187 	return( TRUE );
6188 }
6189 
6190 
PutMercInAwakeState(SOLDIERTYPE * pSoldier)6191 BOOLEAN PutMercInAwakeState( SOLDIERTYPE *pSoldier )
6192 {
6193 	if ( pSoldier->fMercAsleep )
6194 	{
6195 		if ( ( gfWorldLoaded ) && ( pSoldier->bInSector ) )
6196 		{
6197 			const UINT16 state = (guiCurrentScreen == GAME_SCREEN ? WKAEUP_FROM_SLEEP : STANDING);
6198 			ChangeSoldierState(pSoldier, state, 1, TRUE);
6199 		}
6200 
6201 		// set merc awake
6202 		pSoldier->fMercAsleep = FALSE;
6203 
6204 		// refresh panels
6205 		fCharacterInfoPanelDirty = TRUE;
6206 		fTeamPanelDirty = TRUE;
6207 
6208 		// determine if merc is being forced to stay awake
6209 		if ( pSoldier->bBreathMax < BREATHMAX_PRETTY_TIRED )
6210 		{
6211 			pSoldier->fForcedToStayAwake = TRUE;
6212 		}
6213 		else
6214 		{
6215 			pSoldier->fForcedToStayAwake = FALSE;
6216 		}
6217 	}
6218 
6219 	return( TRUE );
6220 }
6221 
6222 
IsThereASoldierInThisSector(INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)6223 BOOLEAN IsThereASoldierInThisSector( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ )
6224 {
6225 	return fSectorsWithSoldiers[sSectorX + sSectorY * MAP_WORLD_X][bSectorZ];
6226 }
6227 
6228 
6229 // set the time this soldier's assignment changed
SetTimeOfAssignmentChangeForMerc(SOLDIERTYPE * pSoldier)6230 void SetTimeOfAssignmentChangeForMerc( SOLDIERTYPE *pSoldier )
6231 {
6232 	// if someone is being taken off of HOSPITAL then track how much
6233 	// of payment wasn't used up
6234 	if ( pSoldier->bAssignment == ASSIGNMENT_HOSPITAL )
6235 	{
6236 		giHospitalRefund += CalcPatientMedicalCost( pSoldier );
6237 		pSoldier->bHospitalPriceModifier = 0;
6238 	}
6239 
6240 	// set time of last assignment change
6241 	pSoldier->uiLastAssignmentChangeMin = GetWorldTotalMin( );
6242 
6243 	// assigning new PATIENTs gives a DOCTOR something to do, etc., so set flag to recheck them all.
6244 	// CAN'T DO IT RIGHT AWAY IN HERE 'CAUSE WE TYPICALLY GET CALLED *BEFORE* bAssignment GETS SET TO NEW VALUE!!
6245 	gfReEvaluateEveryonesNothingToDo = TRUE;
6246 }
6247 
6248 
AnyMercInGroupCantContinueMoving(GROUP const & g)6249 BOOLEAN AnyMercInGroupCantContinueMoving(GROUP const& g)
6250 {
6251 	BOOLEAN group_must_stop = FALSE;
6252 	UINT16  quote           = QUOTE_NEED_SLEEP;
6253 	CFOR_EACH_PLAYER_IN_GROUP(i, &g)
6254 	{
6255 		if (!i->pSoldier) continue;
6256 		SOLDIERTYPE& s = *i->pSoldier;
6257 
6258 		if (!PlayerSoldierTooTiredToTravel(s)) continue;
6259 
6260 		/* NOTE: we only complain about it if it's gonna force the group to stop
6261 			* moving! */
6262 		group_must_stop = TRUE;
6263 
6264 		HandleImportantMercQuote(&s, quote);
6265 		quote = QUOTE_ME_TOO;
6266 
6267 		PutMercInAsleepState(s);
6268 
6269 		// player can't wake him up right away
6270 		s.fMercCollapsedFlag = TRUE;
6271 	}
6272 
6273 	return group_must_stop;
6274 }
6275 
6276 
PlayerSoldierTooTiredToTravel(SOLDIERTYPE & s)6277 bool PlayerSoldierTooTiredToTravel(SOLDIERTYPE& s)
6278 {
6279 	// If this guy ever needs sleep at all
6280 	if (!CanChangeSleepStatusForSoldier(&s)) return false;
6281 
6282 	if (s.bAssignment == VEHICLE && !SoldierMustDriveVehicle(s, true)) return false;
6283 
6284 	if (s.fMercAsleep)
6285 	{ // Asleep, and can't be awakened?
6286 		if (!CanCharacterBeAwakened(&s, FALSE)) return true;
6287 	}
6288 	else
6289 	{ // If awake, but so tired they can't move/drive anymore
6290 		if (s.bBreathMax < BREATHMAX_GOTTA_STOP_MOVING) return true;
6291 	}
6292 
6293 	return false;
6294 }
6295 
6296 
AssignMercToAMovementGroup(SOLDIERTYPE & s)6297 static bool AssignMercToAMovementGroup(SOLDIERTYPE& s)
6298 {
6299 	// If merc doesn't have a group or is in a vehicle or on a squad assign to group
6300 	if (s.bAssignment < ON_DUTY)     return false; // On a squad
6301 	if (s.bAssignment == VEHICLE)    return false; // In a vehicle
6302 	if (s.bAssignment == IN_TRANSIT) return false; // In transit
6303 	if (s.ubGroupID != 0)            return false; // In a movement group
6304 
6305 	GROUP& g = *CreateNewPlayerGroupDepartingFromSector(s.sSectorX, s.sSectorY);
6306 	AddPlayerToGroup(g, s);
6307 	return true;
6308 }
6309 
6310 
6311 // notify player of assignment attempt failure
NotifyPlayerOfAssignmentAttemptFailure(INT8 bAssignment)6312 static void NotifyPlayerOfAssignmentAttemptFailure(INT8 bAssignment)
6313 {
6314 	// notify player
6315 	if ( guiCurrentScreen != MSG_BOX_SCREEN )
6316 	{
6317 		DoScreenIndependantMessageBox( pMapErrorString[ 18 ], MSG_BOX_FLAG_OK, NULL);
6318 	}
6319 	else
6320 	{
6321 		// use screen msg instead!
6322 		ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pMapErrorString[ 18 ] );
6323 	}
6324 
6325 	if ( bAssignment == TRAIN_TOWN )
6326 	{
6327 		ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pMapErrorString[ 48 ] );
6328 	}
6329 }
6330 
6331 
HandleSelectedMercsBeingPutAsleep(BOOLEAN const wake_up,BOOLEAN const display_warning)6332 BOOLEAN HandleSelectedMercsBeingPutAsleep(BOOLEAN const wake_up, BOOLEAN const display_warning)
6333 {
6334 	BOOLEAN                  success = TRUE;
6335 	const SOLDIERTYPE* const sel     = GetSelectedInfoChar();
6336 	CFOR_EACH_SELECTED_IN_CHAR_LIST(c)
6337 	{
6338 		// if the current character in the list is valid...then grab soldier pointer for the character
6339 		SOLDIERTYPE& s = *c->merc;
6340 		if (&s == sel) continue;
6341 
6342 		// don't try to put vehicles, robots, to sleep if they're also selected
6343 		if (!CanChangeSleepStatusForSoldier(&s)) continue;
6344 
6345 		if (wake_up)
6346 		{
6347 			if (!SetMercAwake(&s, FALSE, FALSE)) success = FALSE;
6348 		}
6349 		else
6350 		{
6351 			if (!SetMercAsleep(s, false)) success = FALSE;
6352 		}
6353 	}
6354 
6355 	// if there was anyone processed, check for success and inform player of failure
6356 	if (!success && display_warning)
6357 	{
6358 		// inform player not everyone could be woken up/put to sleep
6359 		ST::string warning = wake_up ?
6360 			pMapErrorString[27] : pMapErrorString[26];
6361 		DoScreenIndependantMessageBox(warning, MSG_BOX_FLAG_OK, NULL);
6362 	}
6363 
6364 	return success;
6365 }
6366 
6367 
IsAnyOneOnPlayersTeamOnThisAssignment(INT8 bAssignment)6368 BOOLEAN IsAnyOneOnPlayersTeamOnThisAssignment( INT8 bAssignment )
6369 {
6370 	CFOR_EACH_IN_TEAM(s, OUR_TEAM)
6371 	{
6372 		if (s->bAssignment == bAssignment) return TRUE;
6373 	}
6374 	return FALSE;
6375 }
6376 
6377 
RebuildAssignmentsBox(void)6378 void RebuildAssignmentsBox( void )
6379 {
6380 	// destroy and recreate assignments box
6381 	if (ghAssignmentBox != NO_POPUP_BOX)
6382 	{
6383 		RemoveBox( ghAssignmentBox );
6384 		ghAssignmentBox = NO_POPUP_BOX;
6385 	}
6386 
6387 	CreateAssignmentsBox( );
6388 }
6389 
6390 
6391 
BandageBleedingDyingPatientsBeingTreated()6392 void BandageBleedingDyingPatientsBeingTreated( )
6393 {
6394 	SOLDIERTYPE *pDoctor = NULL;
6395 	INT32 iKitSlot;
6396 	UINT16 usKitPts;
6397 	UINT32 uiKitPtsUsed;
6398 	BOOLEAN fSomeoneStillBleedingDying = FALSE;
6399 
6400 	FOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
6401 	{
6402 		// and he is bleeding or dying
6403 		if( ( pSoldier->bBleeding ) || ( pSoldier->bLife < OKLIFE ) )
6404 		{
6405 			// if soldier is receiving care
6406 			if( ( pSoldier->bAssignment == PATIENT ) || ( pSoldier->bAssignment == ASSIGNMENT_HOSPITAL ) || ( pSoldier->bAssignment == DOCTOR ) )
6407 			{
6408 				// if in the hospital
6409 				if ( pSoldier->bAssignment == ASSIGNMENT_HOSPITAL )
6410 				{
6411 					// this is instantaneous, and doesn't use up any bandages!
6412 
6413 					// stop bleeding automatically
6414 					pSoldier->bBleeding = 0;
6415 
6416 					if ( pSoldier->bLife < OKLIFE )
6417 					{
6418 						pSoldier->bLife = OKLIFE;
6419 					}
6420 				}
6421 				else	// assigned to DOCTOR/PATIENT
6422 				{
6423 					// see if there's a doctor around who can help him
6424 					pDoctor = AnyDoctorWhoCanHealThisPatient( pSoldier, HEALABLE_EVER );
6425 					if ( pDoctor != NULL )
6426 					{
6427 						iKitSlot = FindObjClass( pDoctor, IC_MEDKIT );
6428 						if( iKitSlot != NO_SLOT )
6429 						{
6430 							OBJECTTYPE& kit = pDoctor->inv[iKitSlot];
6431 
6432 							usKitPts = TotalPoints(&kit);
6433 							if( usKitPts )
6434 							{
6435 								uiKitPtsUsed = VirtualSoldierDressWound(pDoctor, pSoldier, &kit, usKitPts, usKitPts);
6436 								UseKitPoints(kit, (UINT16)uiKitPtsUsed, *pDoctor);
6437 
6438 								// if he is STILL bleeding or dying
6439 								if( ( pSoldier->bBleeding ) || ( pSoldier->bLife < OKLIFE ) )
6440 								{
6441 									fSomeoneStillBleedingDying = TRUE;
6442 								}
6443 							}
6444 						}
6445 					}
6446 				}
6447 			}
6448 		}
6449 	}
6450 
6451 
6452 	// this event may be posted many times because of multiple assignment changes.  Handle it only once per minute!
6453 	DeleteAllStrategicEventsOfType( EVENT_BANDAGE_BLEEDING_MERCS );
6454 
6455 	if ( fSomeoneStillBleedingDying )
6456 	{
6457 		AddStrategicEvent( EVENT_BANDAGE_BLEEDING_MERCS, GetWorldTotalMin() + 1, 0 );
6458 	}
6459 }
6460 
6461 
6462 
ReEvaluateEveryonesNothingToDo()6463 void ReEvaluateEveryonesNothingToDo()
6464 {
6465 	BOOLEAN fNothingToDo;
6466 
6467 	FOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
6468 	{
6469 		switch (pSoldier->bAssignment)
6470 		{
6471 			case DOCTOR:
6472 				fNothingToDo = !CanCharacterDoctor(pSoldier) || GetNumberThatCanBeDoctored(pSoldier, HEALABLE_EVER, FALSE, FALSE) == 0;
6473 				break;
6474 
6475 			case PATIENT:
6476 				fNothingToDo = !CanCharacterPatient(pSoldier) || AnyDoctorWhoCanHealThisPatient(pSoldier, HEALABLE_EVER) == NULL;
6477 				break;
6478 
6479 			case ASSIGNMENT_HOSPITAL:
6480 				fNothingToDo = !CanCharacterPatient(pSoldier);
6481 				break;
6482 
6483 			case REPAIR:
6484 				fNothingToDo = !CanCharacterRepair(pSoldier) || HasCharacterFinishedRepairing(pSoldier);
6485 				break;
6486 
6487 			case TRAIN_TOWN:
6488 				fNothingToDo = !CanCharacterTrainMilitia(pSoldier);
6489 				break;
6490 
6491 			case TRAIN_SELF:
6492 				fNothingToDo = !CanCharacterTrainStat(pSoldier, pSoldier->bTrainStat, TRUE, FALSE);
6493 				break;
6494 
6495 			case TRAIN_TEAMMATE:
6496 				fNothingToDo = !CanCharacterTrainStat(pSoldier, pSoldier->bTrainStat, FALSE, TRUE) ||
6497 					!ValidTrainingPartnerInSameSectorOnAssignmentFound(pSoldier, TRAIN_BY_OTHER, pSoldier->bTrainStat);
6498 				break;
6499 
6500 			case TRAIN_BY_OTHER:
6501 				fNothingToDo = !CanCharacterTrainStat(pSoldier, pSoldier->bTrainStat, TRUE, FALSE) ||
6502 					!ValidTrainingPartnerInSameSectorOnAssignmentFound(pSoldier, TRAIN_TEAMMATE, pSoldier->bTrainStat);
6503 				break;
6504 
6505 			case VEHICLE:
6506 			default:	// squads
6507 				fNothingToDo = FALSE;
6508 				break;
6509 		}
6510 
6511 		// if his flag is wrong
6512 		if (fNothingToDo != pSoldier->fDoneAssignmentAndNothingToDoFlag)
6513 		{
6514 			// update it!
6515 			pSoldier->fDoneAssignmentAndNothingToDoFlag = fNothingToDo;
6516 
6517 			// update mapscreen's character list display
6518 			fDrawCharacterList = TRUE;
6519 		}
6520 
6521 		// if he now has something to do, reset the quote flag
6522 		if (!fNothingToDo)
6523 		{
6524 			pSoldier->usQuoteSaidExtFlags &= ~SOLDIER_QUOTE_SAID_DONE_ASSIGNMENT;
6525 		}
6526 	}
6527 
6528 	// re-evaluation completed
6529 	gfReEvaluateEveryonesNothingToDo = FALSE;
6530 
6531 
6532 	// redraw the map, in case we're showing teams, and someone just came on duty or off duty, their icon needs updating
6533 	fMapPanelDirty = TRUE;
6534 }
6535 
6536 
SetAssignmentForList(INT8 const bAssignment,INT8 const bParam)6537 void SetAssignmentForList(INT8 const bAssignment, INT8 const bParam)
6538 {
6539 	// if not in mapscreen, there is no functionality available to change multiple assignments simultaneously!
6540 	if (!fInMapMode) return;
6541 
6542 	SOLDIERTYPE* const sel = bSelectedAssignChar != -1 ?
6543 		gCharactersList[bSelectedAssignChar].merc : NULL;
6544 	Assert(sel && sel->bActive);
6545 
6546 	// sets assignment for the list
6547 	BOOLEAN fNotifiedOfFailure = FALSE;
6548 	CFOR_EACH_SELECTED_IN_CHAR_LIST(c)
6549 	{
6550 		SOLDIERTYPE& s = *c->merc;
6551 		if (&s == sel || s.uiStatusFlags & SOLDIER_VEHICLE) continue;
6552 
6553 		BOOLEAN fItWorked = FALSE; // assume it's NOT gonna work
6554 		switch (bAssignment)
6555 		{
6556 			case DOCTOR:
6557 				if (CanCharacterDoctor(&s))
6558 				{
6559 					SetSoldierAssignmentDoctor(s);
6560 					fItWorked = TRUE;
6561 				}
6562 				break;
6563 
6564 			case PATIENT:
6565 				if (CanCharacterPatient(&s))
6566 				{
6567 					SetSoldierAssignmentPatient(s);
6568 					fItWorked = TRUE;
6569 				}
6570 				break;
6571 
6572 			case VEHICLE:
6573 				if (CanCharacterVehicle(s))
6574 				{
6575 					VEHICLETYPE& v = GetVehicle(bParam);
6576 					if (IsThisVehicleAccessibleToSoldier(s, v))
6577 					{
6578 						// if the vehicle is FULL, then this will return FALSE!
6579 						fItWorked = PutSoldierInVehicle(s, v);
6580 						// failure produces its own error popup
6581 						fNotifiedOfFailure = TRUE;
6582 					}
6583 				}
6584 				break;
6585 
6586 			case REPAIR:
6587 				if (CanCharacterRepair(&s))
6588 				{
6589 					// make sure he can repair the SPECIFIC thing being repaired too (must be in its sector, for example)
6590 					BOOLEAN const fCanFixSpecificTarget =
6591 						sel->bVehicleUnderRepairID != -1 ? CanCharacterRepairVehicle(s, GetVehicle(sel->bVehicleUnderRepairID)) :
6592 						s.fFixingRobot                   ? CanCharacterRepairRobot(&s)                                          : // XXX s in condition seems wrong, should probably be sel
6593 						TRUE;
6594 					if (fCanFixSpecificTarget)
6595 					{
6596 						SetSoldierAssignmentRepair(s, sel->fFixingRobot, sel->bVehicleUnderRepairID);
6597 						fItWorked = TRUE;
6598 					}
6599 				}
6600 				break;
6601 
6602 			case TRAIN_SELF:
6603 				if (CanCharacterTrainStat(&s, bParam, TRUE, FALSE))
6604 				{
6605 					SetSoldierAssignmentTrainSelf(s, bParam);
6606 					fItWorked = TRUE;
6607 				}
6608 				break;
6609 
6610 			case TRAIN_TOWN:
6611 				if (CanCharacterTrainMilitia(&s))
6612 				{
6613 					SetSoldierAssignmentTrainTown(s);
6614 					fItWorked = TRUE;
6615 				}
6616 				break;
6617 
6618 			case TRAIN_TEAMMATE:
6619 				if (CanCharacterTrainStat(&s, bParam, FALSE, TRUE))
6620 				{
6621 					SetSoldierAssignmentTrainTeammate(s, bParam);
6622 					fItWorked = TRUE;
6623 				}
6624 				break;
6625 
6626 			case TRAIN_BY_OTHER:
6627 				if (CanCharacterTrainStat(&s, bParam, TRUE, FALSE))
6628 				{
6629 					SetSoldierAssignmentTrainByOther(s, bParam);
6630 					fItWorked = TRUE;
6631 				}
6632 				break;
6633 
6634 			case SQUAD_1:
6635 			case SQUAD_2:
6636 			case SQUAD_3:
6637 			case SQUAD_4:
6638 			case SQUAD_5:
6639 			case SQUAD_6:
6640 			case SQUAD_7:
6641 			case SQUAD_8:
6642 			case SQUAD_9:
6643 			case SQUAD_10:
6644 			case SQUAD_11:
6645 			case SQUAD_12:
6646 			case SQUAD_13:
6647 			case SQUAD_14:
6648 			case SQUAD_15:
6649 			case SQUAD_16:
6650 			case SQUAD_17:
6651 			case SQUAD_18:
6652 			case SQUAD_19:
6653 			case SQUAD_20:
6654 				switch (CanCharacterSquad(s, (INT8)bAssignment))
6655 				{
6656 					case CHARACTER_CAN_JOIN_SQUAD:
6657 					{
6658 						PreChangeAssignment(s);
6659 
6660 						// if the squad is, between sectors, remove from old mvt group
6661 						const SOLDIERTYPE* const t = Squad[bAssignment][0];
6662 						if (t                        &&
6663 								t->fBetweenSectors       &&
6664 								s.bAssignment >= ON_DUTY &&
6665 								s.ubGroupID != 0)
6666 						{
6667 							RemovePlayerFromGroup(s);
6668 						}
6669 
6670 						// able to add, do it
6671 						AddCharacterToSquad(&s, bAssignment);
6672 						fItWorked = TRUE;
6673 						break;
6674 					}
6675 
6676 					// if already in it, don't report that as an error
6677 					case CHARACTER_CANT_JOIN_SQUAD_ALREADY_IN_IT:
6678 						fItWorked = TRUE;
6679 						break;
6680 
6681 					default: break;
6682 				}
6683 				break;
6684 
6685 			default:
6686 				// remove from current vehicle/squad, if any
6687 				if (s.bAssignment == VEHICLE) TakeSoldierOutOfVehicle(&s);
6688 				AddCharacterToAnySquad(&s);
6689 				fItWorked = TRUE;
6690 				break;
6691 		}
6692 
6693 		if (fItWorked)
6694 		{
6695 			// remove him from his old squad if he was on one
6696 			MakeSoldiersTacticalAnimationReflectAssignment(&s);
6697 		}
6698 		else
6699 		{
6700 			// didn't work - report it once
6701 			if (!fNotifiedOfFailure)
6702 			{
6703 				fNotifiedOfFailure = TRUE;
6704 				NotifyPlayerOfAssignmentAttemptFailure(bAssignment);
6705 			}
6706 		}
6707 	}
6708 
6709 	// check if we should start/stop flashing any mercs' assignment strings after these changes
6710 	gfReEvaluateEveryonesNothingToDo = TRUE;
6711 }
6712 
6713 
ValidTrainingPartnerInSameSectorOnAssignmentFound(SOLDIERTYPE * pTargetSoldier,INT8 bTargetAssignment,INT8 bTargetStat)6714 static BOOLEAN ValidTrainingPartnerInSameSectorOnAssignmentFound(SOLDIERTYPE* pTargetSoldier, INT8 bTargetAssignment, INT8 bTargetStat)
6715 {
6716 	INT16 sTrainingPts = 0;
6717 	BOOLEAN fAtGunRange = FALSE;
6718 	UINT16 usMaxPts;
6719 
6720 	// this function only makes sense for training teammates or by others, not for self training which doesn't require partners
6721 	Assert( ( bTargetAssignment == TRAIN_TEAMMATE ) || ( bTargetAssignment == TRAIN_BY_OTHER ) );
6722 
6723 	CFOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
6724 	{
6725 		// if the guy is not the target, has the assignment we want, is training the same stat, and is in our sector, alive
6726 		// and is training the stat we want
6727 		if (pSoldier != pTargetSoldier &&
6728 				pSoldier->bAssignment == bTargetAssignment &&
6729 				// CJC: this seems incorrect in light of the check for bTargetStat and in any case would
6730 				// cause a problem if the trainer was assigned and we weren't!
6731 				//pSoldier->bTrainStat == pTargetSoldier->bTrainStat &&
6732 				pSoldier->sSectorX == pTargetSoldier->sSectorX &&
6733 				pSoldier->sSectorY == pTargetSoldier->sSectorY &&
6734 				pSoldier->bSectorZ == pTargetSoldier->bSectorZ &&
6735 				pSoldier->bTrainStat == bTargetStat &&
6736 				pSoldier->bLife > 0)
6737 		{
6738 			// so far so good, now let's see if the trainer can really teach the student anything new
6739 
6740 			// are we training in the sector with gun range in Alma?
6741 			if (pSoldier->sSectorX == GUN_RANGE_X && pSoldier->sSectorY == GUN_RANGE_Y && pSoldier->bSectorZ == GUN_RANGE_Z)
6742 			{
6743 				fAtGunRange = TRUE;
6744 			}
6745 
6746 			if (pSoldier->bAssignment == TRAIN_TEAMMATE)
6747 			{
6748 				// pSoldier is the instructor, target is the student
6749 				sTrainingPts = GetBonusTrainingPtsDueToInstructor(pSoldier, pTargetSoldier, bTargetStat, fAtGunRange, &usMaxPts);
6750 			}
6751 			else
6752 			{
6753 				// target is the instructor, pSoldier is the student
6754 				sTrainingPts = GetBonusTrainingPtsDueToInstructor(pTargetSoldier, pSoldier, bTargetStat, fAtGunRange, &usMaxPts);
6755 			}
6756 
6757 			if (sTrainingPts > 0)
6758 			{
6759 				// yes, then he makes a valid training partner for us!
6760 				return TRUE;
6761 			}
6762 		}
6763 	}
6764 
6765 	// no one found
6766 	return( FALSE );
6767 }
6768 
6769 
InternalUnescortEPC(SOLDIERTYPE * const s)6770 static void InternalUnescortEPC(SOLDIERTYPE* const s)
6771 {
6772 	SetupProfileInsertionDataForSoldier(s);
6773 
6774 	const ProfileID profile = s->ubProfile;
6775 	UINT16 quote_num;
6776 	Fact fact_to_set_to_true;
6777 	if (GetInfoForAbandoningEPC(profile, &quote_num, &fact_to_set_to_true))
6778 	{
6779 		gMercProfiles[profile].ubMiscFlags |= PROFILE_MISC_FLAG_FORCENPCQUOTE;
6780 		TacticalCharacterDialogue(s, quote_num);
6781 		SetFactTrue(fact_to_set_to_true);
6782 	}
6783 
6784 	class DialogueEventRemoveEPC : public DialogueEvent
6785 	{
6786 		public:
6787 			DialogueEventRemoveEPC(ProfileID const epc) : epc_(epc) {}
6788 
6789 			bool Execute()
6790 			{
6791 				GetProfile(epc_).ubMiscFlags &= ~PROFILE_MISC_FLAG_FORCENPCQUOTE;
6792 				UnRecruitEPC(epc_);
6793 				ReBuildCharactersList();
6794 				return false;
6795 			}
6796 
6797 		private:
6798 			ProfileID const epc_;
6799 	};
6800 
6801 	DialogueEvent::Add(new DialogueEventRemoveEPC(profile));
6802 }
6803 
6804 
UnEscortEPC(SOLDIERTYPE * const s)6805 void UnEscortEPC(SOLDIERTYPE* const s)
6806 {
6807 	if (fInMapMode)
6808 	{
6809 		InternalUnescortEPC(s);
6810 
6811 		SOLDIERTYPE* other;
6812 		switch (s->ubProfile)
6813 		{
6814 			case JOHN: other = FindSoldierByProfileIDOnPlayerTeam(MARY); break;
6815 			case MARY: other = FindSoldierByProfileIDOnPlayerTeam(JOHN); break;
6816 			default:   other = NULL;                                     break;
6817 		}
6818 		if (other != NULL) InternalUnescortEPC(other);
6819 
6820 		// stop showing menu
6821 		giAssignHighLine = -1;
6822 
6823 		// set dirty flag
6824 		fTeamPanelDirty          = TRUE;
6825 		fMapScreenBottomDirty    = TRUE;
6826 		fCharacterInfoPanelDirty = TRUE;
6827 	}
6828 	else
6829 	{
6830 		// how do we handle this if it's the right sector?
6831 		TriggerNPCWithGivenApproach(s->ubProfile, APPROACH_EPC_IN_WRONG_SECTOR);
6832 	}
6833 }
6834 
6835 
CharacterIsTakingItEasy(SOLDIERTYPE const & s)6836 static bool CharacterIsTakingItEasy(SOLDIERTYPE const& s)
6837 {
6838 	if (s.fMercAsleep) return true;
6839 
6840 	if (!CanCharacterSleep(s, false)) return false;
6841 
6842 	/* On duty, but able to catch naps (either not traveling, or not the driver of
6843 	 * the vehicle). The actual checks for this are in the "can he sleep" check
6844 	 * above */
6845 	if (s.bAssignment < ON_DUTY || s.bAssignment == VEHICLE) return true;
6846 
6847 	// Healing up?
6848 	if (s.bAssignment == PATIENT)             return true;
6849 	if (s.bAssignment == ASSIGNMENT_HOSPITAL) return true;
6850 
6851 	// On a real assignment, but done with it?
6852 	if (s.fDoneAssignmentAndNothingToDoFlag) return true;
6853 
6854 	// On assignment
6855 	return false;
6856 }
6857 
6858 
CalcSoldierNeedForSleep(SOLDIERTYPE const & s)6859 static UINT8 CalcSoldierNeedForSleep(SOLDIERTYPE const& s)
6860 {
6861 	// Base comes from profile
6862 	UINT8 need_for_sleep = GetProfile(s.ubProfile).ubNeedForSleep;
6863 
6864 	/* < 1/4 health -> 4 more
6865 	 * < 2/4 health -> 2 more
6866 	 * < 3/4 health -> 1 more */
6867 	UINT8 const part_health = 4 * s.bLife / s.bLifeMax;
6868 	need_for_sleep += 4U >> part_health;
6869 
6870 	// Reduce for each Night Ops or Martial Arts trait
6871 	need_for_sleep -= NUM_SKILL_TRAITS(&s, NIGHTOPS);
6872 	need_for_sleep -= NUM_SKILL_TRAITS(&s, MARTIALARTS);
6873 
6874 	if (need_for_sleep <  4) return  4;
6875 	if (need_for_sleep > 12) return 12;
6876 	return need_for_sleep;
6877 }
6878 
6879 
GetLastSquadListedInSquadMenu(void)6880 static UINT32 GetLastSquadListedInSquadMenu(void)
6881 {
6882 	UINT32 uiMaxSquad;
6883 
6884 	uiMaxSquad = GetLastSquadActive( ) + 1;
6885 
6886 	if ( uiMaxSquad >= NUMBER_OF_SQUADS )
6887 	{
6888 		uiMaxSquad = NUMBER_OF_SQUADS - 1;
6889 	}
6890 
6891 	return( uiMaxSquad );
6892 }
6893 
6894 
CanCharacterRepairAnotherSoldiersStuff(const SOLDIERTYPE * const pSoldier,const SOLDIERTYPE * const pOtherSoldier)6895 static BOOLEAN CanCharacterRepairAnotherSoldiersStuff(const SOLDIERTYPE* const pSoldier, const SOLDIERTYPE* const pOtherSoldier)
6896 {
6897 	if ( pOtherSoldier == pSoldier )
6898 	{
6899 		return( FALSE );
6900 	}
6901 	if ( pOtherSoldier->bLife == 0 )
6902 	{
6903 		return( FALSE );
6904 	}
6905 	if (pOtherSoldier->sSectorX != pSoldier->sSectorX ||
6906 			pOtherSoldier->sSectorY != pSoldier->sSectorY ||
6907 			pOtherSoldier->bSectorZ != pSoldier->bSectorZ )
6908 	{
6909 		return( FALSE );
6910 	}
6911 
6912 	if ( pOtherSoldier->fBetweenSectors )
6913 	{
6914 		return( FALSE );
6915 	}
6916 
6917 	if (pOtherSoldier->bAssignment == IN_TRANSIT ||
6918 			pOtherSoldier->bAssignment == ASSIGNMENT_POW ||
6919 			pOtherSoldier->bAssignment == ASSIGNMENT_DEAD ||
6920 			IsMechanical(*pSoldier) ||
6921 			pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__EPC)
6922 	{
6923 		return( FALSE );
6924 	}
6925 
6926 	return( TRUE );
6927 }
6928 
6929 
GetSelectedAssignSoldier(BOOLEAN fNullOK)6930 static SOLDIERTYPE* GetSelectedAssignSoldier(BOOLEAN fNullOK)
6931 {
6932 	SOLDIERTYPE *pSoldier = NULL;
6933 
6934 	if (fInMapMode)
6935 	{
6936 		// mapscreen version
6937 		if (bSelectedAssignChar >= 0 && bSelectedAssignChar < MAX_CHARACTER_COUNT)
6938 		{
6939 			pSoldier = gCharactersList[bSelectedAssignChar].merc;
6940 		}
6941 	}
6942 	else
6943 	{
6944 		// tactical version
6945 		pSoldier = gUIFullTarget;
6946 	}
6947 
6948 	if ( !fNullOK )
6949 	{
6950 		Assert( pSoldier );
6951 	}
6952 
6953 	if ( pSoldier != NULL )
6954 	{
6955 		// better be an active person, not a vehicle
6956 		Assert( pSoldier->bActive );
6957 		Assert( !( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) );
6958 	}
6959 
6960 	return( pSoldier );
6961 }
6962 
6963 
ResumeOldAssignment(SOLDIERTYPE * const s)6964 void ResumeOldAssignment(SOLDIERTYPE* const s)
6965 {
6966 	/* ARM: I don't think the whole "old assignment" idea is a very good one, and
6967 	 * I doubt the code that maintains that variable is very foolproof, plus what
6968 	 * meaning does the old assignemnt have later, anyway?  So I'd rather just
6969 	 * settle for putting him into any squad: */
6970 	AddCharacterToAnySquad(s);
6971 
6972 	// make sure the player has time to OK this before proceeding
6973 	StopTimeCompression();
6974 
6975 	/* Assignment has changed, redraw left side as well as the map (to update
6976 		* on/off duty icons) */
6977 	fTeamPanelDirty          = TRUE;
6978 	fCharacterInfoPanelDirty = TRUE;
6979 	fMapPanelDirty           = TRUE;
6980 }
6981 
6982 
RepairItemsOnOthers(SOLDIERTYPE * pSoldier,UINT8 * pubRepairPtsLeft)6983 static void RepairItemsOnOthers(SOLDIERTYPE* pSoldier, UINT8* pubRepairPtsLeft)
6984 {
6985 	UINT8 ubPassType;
6986 	INT8 bPocket;
6987 	SOLDIERTYPE * pBestOtherSoldier;
6988 	INT8 bPriority, bBestPriority = -1;
6989 	BOOLEAN fSomethingWasRepairedThisPass;
6990 
6991 
6992 	// repair everyone's hands and armor slots first, then headgear, and pockets last
6993 	for ( ubPassType = REPAIR_HANDS_AND_ARMOR; ubPassType <= FINAL_REPAIR_PASS; ubPassType++ )
6994 	{
6995 		fSomethingWasRepairedThisPass = FALSE;
6996 
6997 
6998 		// look for jammed guns on other soldiers in sector and unjam them
6999 		FOR_EACH_IN_TEAM(pOtherSoldier, OUR_TEAM)
7000 		{
7001 			// check character is valid, alive, same sector, not between, has inventory, etc.
7002 			if ( CanCharacterRepairAnotherSoldiersStuff( pSoldier, pOtherSoldier ) )
7003 			{
7004 				if ( UnjamGunsOnSoldier( pOtherSoldier, pSoldier, pubRepairPtsLeft ) )
7005 				{
7006 					fSomethingWasRepairedThisPass = TRUE;
7007 				}
7008 			}
7009 		}
7010 
7011 
7012 		while ( *pubRepairPtsLeft > 0 )
7013 		{
7014 			bBestPriority = -1;
7015 			pBestOtherSoldier = NULL;
7016 
7017 			// now look for items to repair on other mercs
7018 			FOR_EACH_IN_TEAM(pOtherSoldier, OUR_TEAM)
7019 			{
7020 				// check character is valid, alive, same sector, not between, has inventory, etc.
7021 				if ( CanCharacterRepairAnotherSoldiersStuff( pSoldier, pOtherSoldier ) )
7022 				{
7023 					// okay, seems like a candidate!
7024 					if ( FindRepairableItemOnOtherSoldier( pOtherSoldier, ubPassType ) != NO_SLOT )
7025 					{
7026 						bPriority = pOtherSoldier->bExpLevel;
7027 						if ( bPriority > bBestPriority )
7028 						{
7029 							bBestPriority = bPriority;
7030 							pBestOtherSoldier = pOtherSoldier;
7031 						}
7032 					}
7033 				}
7034 			}
7035 
7036 			// did we find anyone to repair on this pass?
7037 			if ( pBestOtherSoldier != NULL )
7038 			{
7039 				// yes, repair all items (for this pass type!) on this soldier that need repair
7040 				do
7041 				{
7042 					bPocket = FindRepairableItemOnOtherSoldier( pBestOtherSoldier, ubPassType );
7043 					if ( bPocket != NO_SLOT )
7044 					{
7045 						if ( RepairObject( pSoldier, pBestOtherSoldier, &(pBestOtherSoldier->inv[ bPocket ]), pubRepairPtsLeft ) )
7046 						{
7047 							fSomethingWasRepairedThisPass = TRUE;
7048 						}
7049 					}
7050 				}
7051 				while ( bPocket != NO_SLOT && *pubRepairPtsLeft > 0 );
7052 			}
7053 			else
7054 			{
7055 				break;
7056 			}
7057 		}
7058 
7059 		if ( fSomethingWasRepairedThisPass && !DoesCharacterHaveAnyItemsToRepair( pSoldier, ubPassType ) )
7060 		{
7061 			ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(sRepairsDoneString[ 1 + ubPassType ], pSoldier->name) );
7062 
7063 			// let player react
7064 			StopTimeCompression();
7065 		}
7066 	}
7067 }
7068 
7069 
UnjamGunsOnSoldier(SOLDIERTYPE * pOwnerSoldier,SOLDIERTYPE * pRepairSoldier,UINT8 * pubRepairPtsLeft)7070 static BOOLEAN UnjamGunsOnSoldier(SOLDIERTYPE* pOwnerSoldier, SOLDIERTYPE* pRepairSoldier, UINT8* pubRepairPtsLeft)
7071 {
7072 	BOOLEAN fAnyGunsWereUnjammed = FALSE;
7073 	INT8	bPocket;
7074 
7075 
7076 	// try to unjam everything before beginning any actual repairs.. successful unjamming costs 2 points per weapon
7077 	for (bPocket = HANDPOS; bPocket <= SMALLPOCK8POS; bPocket++)
7078 	{
7079 		// the object a weapon? and jammed?
7080 		if ( ( GCM->getItem(pOwnerSoldier->inv[ bPocket ].usItem)->getItemClass() == IC_GUN ) && ( pOwnerSoldier->inv[ bPocket ].bGunAmmoStatus < 0 ) )
7081 		{
7082 			if ( *pubRepairPtsLeft >= REPAIR_COST_PER_JAM )
7083 			{
7084 				*pubRepairPtsLeft -= REPAIR_COST_PER_JAM;
7085 
7086 				pOwnerSoldier->inv [ bPocket ].bGunAmmoStatus *= -1;
7087 
7088 				// MECHANICAL/DEXTERITY GAIN: Unjammed a gun
7089 				StatChange(*pRepairSoldier, MECHANAMT, 5, FROM_SUCCESS);
7090 				StatChange(*pRepairSoldier, DEXTAMT,   5, FROM_SUCCESS);
7091 
7092 				// report it as unjammed
7093 				if ( pRepairSoldier == pOwnerSoldier )
7094 				{
7095 					ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(gzLateLocalizedString[STR_LATE_53], pRepairSoldier->name, ItemNames[pOwnerSoldier->inv[bPocket].usItem]));
7096 				}
7097 				else
7098 				{
7099 					// NOTE: may need to be changed for localized versions
7100 					ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(gzLateLocalizedString[STR_LATE_54], pRepairSoldier->name, pOwnerSoldier->name, ItemNames[pOwnerSoldier->inv[bPocket].usItem]));
7101 				}
7102 
7103 				fAnyGunsWereUnjammed = TRUE;
7104 			}
7105 			else
7106 			{
7107 				// out of points, we're done for now
7108 				break;
7109 			}
7110 		}
7111 	}
7112 
7113 	return ( fAnyGunsWereUnjammed );
7114 }
7115