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, ¬hing_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, ¬hing_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, "e_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