1 #include "Directories.h"
2 #include "Interface_Control.h"
3 #include "Interface_Panels.h"
4 #include "LoadSaveMercProfile.h"
5 #include "MapScreen.h"
6 #include "Merc_Hiring.h"
7 #include "Debug.h"
8 #include "math.h"
9 #include "WorldDef.h"
10 #include "Soldier_Control.h"
11 #include "Animation_Data.h"
12 #include "Render_Fun.h"
13 #include "Render_Dirty.h"
14 #include "MouseSystem.h"
15 #include "Interface.h"
16 #include "SysUtil.h"
17 #include "FileMan.h"
18 #include "Points.h"
19 #include "Random.h"
20 #include "AI.h"
21 #include "Soldier_Ani.h"
22 #include "Overhead.h"
23 #include "Soldier_Profile.h"
24 #include "Game_Clock.h"
25 #include "Assignments.h"
26 #include "Dialogue_Control.h"
27 #include "Soldier_Create.h"
28 #include "Soldier_Add.h"
29 #include "OppList.h"
30 #include "Weapons.h"
31 #include "Strategic_Town_Loyalty.h"
32 #include "Squads.h"
33 #include "Tactical_Save.h"
34 #include "Quests.h"
35 #include "AIM.h"
36 #include "Interface_Dialogue.h"
37 #include "GameSettings.h"
38 #include "Interface_Utils.h"
39 #include "StrategicMap.h"
40 #include "Game_Event_Hook.h"
41 #include "Map_Information.h"
42 #include "History.h"
43 #include "Personnel.h"
44 #include "Environment.h"
45 #include "Items.h"
46 #include "GameRes.h"
47 #include "Faces.h"
48
49 #include "ContentManager.h"
50 #include "GameInstance.h"
51 #include "content/ContentMercs.h"
52 #include "WeaponModels.h"
53 #include "MercProfile.h"
54
55 extern BOOLEAN gfProfileDataLoaded;
56
57
58 BOOLEAN gfPotentialTeamChangeDuringDeath = FALSE;
59
60
61 MERCPROFILESTRUCT gMercProfiles[ NUM_PROFILES ];
62
63 INT8 gbSkillTraitBonus[NUM_SKILLTRAITS] =
64 {
65 0, //NO_SKILLTRAIT
66 25, //LOCKPICKING
67 15, //HANDTOHAND
68 15, //ELECTRONICS
69 15, //NIGHTOPS
70 12, //THROWING
71 15, //TEACHING
72 15, //HEAVY_WEAPS
73 0, //AUTO_WEAPS
74 15, //STEALTHY
75 0, //AMBIDEXT
76 0, //THIEF // UNUSED!
77 30, //MARTIALARTS
78 30, //KNIFING
79 15, //ONROOF
80 0, //CAMOUFLAGED
81 };
82
83
84 UINT8 gubNumTerrorists = 0;
85
86 struct TerroristInfo
87 {
88 ProfileID profile;
89 UINT8 sectors[5];
90 };
91
92 static TerroristInfo const g_terrorist_infos[] =
93 {
94 {DRUGGIST, { 0, 0, 0, 0, 0 }}, // Elgin, preplaced
95 {SLAY, { SEC_F9, SEC_I14, SEC_G1, SEC_G2, SEC_G8 }}, // Slay
96 {ANNIE, { SEC_I14, SEC_C6, SEC_B2, SEC_L11, SEC_G8 }}, // Matron
97 {CHRIS, { SEC_G1, SEC_F9, SEC_L11, SEC_G8, SEC_G2 }}, // Imposter
98 {TIFFANY, { SEC_I14, SEC_G2, SEC_H14, SEC_C6, SEC_B2 }}, // Tiffany
99 {T_REX, { SEC_F9, SEC_H14, SEC_H2, SEC_G1, SEC_B2 }} // Rexall
100 };
101
102 INT16 gsRobotGridNo;
103
104
105 struct AssassinInfo
106 {
107 ProfileID profile;
108 UINT8 towns[5];
109 };
110
111 static AssassinInfo const g_assassin_info[] =
112 {
113 { JIM, { CAMBRIA, DRASSEN, ALMA, BALIME, GRUMM } },
114 { JACK, { CHITZENA, ESTONI, ALMA, BALIME, GRUMM } },
115 { OLAF, { DRASSEN, ESTONI, ALMA, CAMBRIA, BALIME } },
116 { RAY, { CAMBRIA, OMERTA, BALIME, GRUMM, DRASSEN } },
117 { OLGA, { CHITZENA, OMERTA, CAMBRIA, ALMA, GRUMM } },
118 { TYRONE, { CAMBRIA, BALIME, ALMA, GRUMM, DRASSEN } }
119 };
120
121
122 static INT16 CalcMedicalDeposit(MERCPROFILESTRUCT const&);
123 static void DecideActiveTerrorists();
124 static void StartSomeMercsOnAssignment(void);
125
126
LoadMercProfiles()127 void LoadMercProfiles()
128 {
129 { AutoSGPFile f(GCM->openGameResForReading(BINARYDATADIR "/prof.dat"));
130 LoadRawMercProfiles(f, NUM_PROFILES, gMercProfiles, getDataFilesEncodingCorrector());
131 for (UINT32 i = 0; i != NUM_PROFILES; ++i)
132 {
133 MERCPROFILESTRUCT& p = gMercProfiles[i];
134
135 // // dumping std inventory
136 // printf("%03d/%s\n", i, p.zNickname.c_str());
137 // FOR_EACH(UINT16, k, p.inv)
138 // {
139 // const ItemModel *item = GCM->getItem(*k);
140 // printf(" %s\n", item->getInternalName().c_str());
141 // }
142
143 // If the dialogue exists for the merc, allow the merc to be hired
144 p.bMercStatus = Content::canMercBeHired(GCM, i) ? 0 : MERC_HAS_NO_TEXT_FILE;
145
146 p.sMedicalDepositAmount = p.bMedicalDeposit ? CalcMedicalDeposit(p) : 0;
147
148 // ATE: New, face display independent of ID num now, default is the
149 // profile ID
150 p.ubFaceIndex = i;
151
152 if (!gGameOptions.fGunNut)
153 {
154 // CJC: replace guns in profile if they aren't available
155 FOR_EACH(UINT16, k, p.inv)
156 {
157 const ItemModel *item = GCM->getItem(*k);
158 if (!item->isGun() || !item->isInBigGunList()) continue;
159
160 const WeaponModel *oldWeapon = item->asWeapon();
161 const WeaponModel *newWeapon = GCM->getWeaponByName(oldWeapon->getStandardReplacement());
162
163 *k = newWeapon->getItemIndex();
164
165 // Search through inventory and replace ammo accordingly
166 FOR_EACH(UINT16, l, p.inv)
167 {
168 UINT16 const ammo = *l;
169 if (!(GCM->getItem(ammo)->isAmmo())) continue;
170 UINT16 const new_ammo = FindReplacementMagazineIfNecessary(oldWeapon, ammo, newWeapon);
171 if (new_ammo == NOTHING) continue;
172 // Found a new magazine, replace
173 *l = new_ammo;
174 }
175 }
176 }
177
178 // Calculate inital attractiveness for the merc's initial gun and armour.
179 // Calculate the optional gear cost.
180 p.bMainGunAttractiveness = -1;
181 p.bArmourAttractiveness = -1;
182 p.usOptionalGearCost = 0;
183 FOR_EACH(UINT16 const, k, p.inv)
184 {
185 UINT16 const item_id = *k;
186 if (item_id == NOTHING) continue;
187 const ItemModel * item = GCM->getItem(item_id);
188
189 if (item->isGun()) p.bMainGunAttractiveness = GCM->getWeapon(item_id)->ubDeadliness;
190 if (item->isArmour()) p.bArmourAttractiveness = Armour[item->getClassIndex()].ubProtection;
191
192 p.usOptionalGearCost += item->getPrice();
193 }
194
195 // These variables to get loaded in
196 p.fUseProfileInsertionInfo = FALSE;
197 p.sGridNo = 0;
198
199 // ARM: this is also being done inside the profile editor, but put it here
200 // too, so this project's code makes sense
201 p.bHatedCount[0] = p.bHatedTime[0];
202 p.bHatedCount[1] = p.bHatedTime[1];
203 p.bLearnToHateCount = p.bLearnToHateTime;
204 p.bLearnToLikeCount = p.bLearnToLikeTime;
205 }
206 }
207
208 DecideActiveTerrorists();
209
210 // Initialize mercs' status
211 StartSomeMercsOnAssignment();
212
213 gfProfileDataLoaded = TRUE;
214
215 // no better place..heh?.. will load faces for profiles that are 'extern'.....won't have soldiertype instances
216 PreloadExternalNPCFaces();
217
218 LoadCarPortraitValues();
219 }
220
221
222 #define MAX_ADDITIONAL_TERRORISTS 4
223
224
225 // One terrorist is always Elgin. Determine how many more terrorists - 2 to 4
226 // more.
DecideActiveTerrorists()227 static void DecideActiveTerrorists()
228 {
229 // Using this stochastic process(!), the chances for terrorists are:
230 // EASY: 3, 9% 4, 42% 5, 49%
231 // MEDIUM: 3, 25% 4, 50% 5, 25%
232 // HARD: 3, 49% 4, 42% 5, 9%
233 UINT32 chance;
234 switch (gGameOptions.ubDifficultyLevel)
235 {
236 case DIF_LEVEL_EASY: chance = 70; break;
237 default: chance = 50; break;
238 case DIF_LEVEL_HARD: chance = 30; break;
239 }
240 UINT8 n_additional_terrorists = 2; // Add at least 2 more.
241 for (UINT8 n = MAX_ADDITIONAL_TERRORISTS - n_additional_terrorists; n != 0; --n)
242 {
243 if (Chance(chance)) ++n_additional_terrorists;
244 }
245
246 UINT8 terrorist_placement[MAX_ADDITIONAL_TERRORISTS];
247 for (UINT8 n_terrorists_added = 0; n_terrorists_added != n_additional_terrorists;)
248 {
249 FOR_EACH(TerroristInfo const, i, g_terrorist_infos)
250 {
251 if (n_terrorists_added == n_additional_terrorists) break;
252
253 TerroristInfo const& t = *i;
254 MERCPROFILESTRUCT& p = GetProfile(t.profile);
255 // Random 40% chance of adding this terrorist if not yet placed.
256 if (p.sSectorX != 0) continue;
257 if (Random(100) >= 40) continue;
258
259 // Since there are 5 spots per terrorist and a maximum of 5 terrorists, we
260 // are guaranteed to be able to find a spot for each terrorist since there
261 // aren't enough other terrorists to use up all the spots for any one
262 // terrorist
263 pick_sector:
264 // Pick a random spot, see if it's already been used by another terrorist.
265 UINT8 const sector = t.sectors[Random(lengthof(t.sectors))];
266 for (UINT8 k = 0; k != n_terrorists_added; ++k)
267 {
268 if (terrorist_placement[k] == sector) goto pick_sector;
269 }
270
271 // Place terrorist.
272 p.sSectorX = SECTORX(sector);
273 p.sSectorY = SECTORY(sector);
274 p.bSectorZ = 0;
275 terrorist_placement[n_terrorists_added++] = sector;
276 }
277 }
278
279 // Set total terrorists outstanding in Carmen's info byte.
280 GetProfile(CARMEN).bNPCData = 1 + n_additional_terrorists;
281 // Store total terrorists.
282 gubNumTerrorists = 1 + n_additional_terrorists;
283 }
284
285
MakeRemainingTerroristsTougher()286 void MakeRemainingTerroristsTougher()
287 {
288 UINT8 n_remaining_terrorists = 0;
289 FOR_EACH(TerroristInfo const, i, g_terrorist_infos)
290 {
291 ProfileID const pid = i->profile;
292 MERCPROFILESTRUCT const& p = GetProfile(pid);
293 if (p.bMercStatus == MERC_IS_DEAD || p.sSectorX == 0 || p.sSectorY == 0) continue;
294 // Slay on player's team, doesn't count towards remaining terrorists
295 if (pid == SLAY && FindSoldierByProfileIDOnPlayerTeam(SLAY)) continue;
296 ++n_remaining_terrorists;
297 }
298
299 UINT8 remaining_difficulty = 60 / gubNumTerrorists * (gubNumTerrorists - n_remaining_terrorists);
300
301 switch (gGameOptions.ubDifficultyLevel)
302 {
303 case DIF_LEVEL_MEDIUM: remaining_difficulty = remaining_difficulty * 13 / 10; break;
304 case DIF_LEVEL_HARD: remaining_difficulty = remaining_difficulty * 16 / 10; break;
305 default: break;
306 }
307
308 UINT16 old_item;
309 UINT16 new_item;
310 if (remaining_difficulty < 14)
311 {
312 // nothing
313 return;
314 }
315 else if (remaining_difficulty < 28)
316 {
317 // mini grenade
318 old_item = NOTHING;
319 new_item = MINI_GRENADE;
320 }
321 else if (remaining_difficulty < 42)
322 {
323 // hand grenade
324 old_item = MINI_GRENADE;
325 new_item = HAND_GRENADE;
326 }
327 else if (remaining_difficulty < 56)
328 {
329 // mustard
330 old_item = HAND_GRENADE;
331 new_item = MUSTARD_GRENADE;
332 }
333 else if (remaining_difficulty < 70)
334 {
335 // LAW
336 old_item = MUSTARD_GRENADE;
337 new_item = ROCKET_LAUNCHER;
338 }
339 else
340 {
341 // LAW and hand grenade
342 old_item = NOTHING;
343 new_item = HAND_GRENADE;
344 }
345
346 OBJECTTYPE Object;
347 DeleteObj(&Object);
348 Object.usItem = new_item;
349 Object.bStatus[0] = 100;
350
351 FOR_EACH(TerroristInfo const, i, g_terrorist_infos)
352 {
353 ProfileID const pid = i->profile;
354 MERCPROFILESTRUCT const& p = GetProfile(pid);
355 if (p.bMercStatus == MERC_IS_DEAD || p.sSectorX == 0 || p.sSectorY == 0) continue;
356 // Slay on player's team, doesn't count towards remaining terrorists
357 if (pid == SLAY && FindSoldierByProfileIDOnPlayerTeam(SLAY)) continue;
358
359 if (old_item != NOTHING)
360 {
361 RemoveObjectFromSoldierProfile(pid, old_item);
362 }
363 PlaceObjectInSoldierProfile(pid, &Object);
364 }
365 }
366
367
DecideOnAssassin()368 void DecideOnAssassin()
369 {
370 ProfileID assassins[lengthof(g_assassin_info)];
371 UINT8 n = 0;
372 UINT8 const town = GetTownIdForSector(SECTOR(gWorldSectorX, gWorldSectorY));
373 FOR_EACH(AssassinInfo const, i, g_assassin_info)
374 {
375 AssassinInfo const a = *i;
376 MERCPROFILESTRUCT const& p = GetProfile(a.profile);
377 // Make sure alive and not placed already.
378 if (p.bMercStatus == MERC_IS_DEAD) continue;
379 if (p.sSectorX != 0 || p.sSectorY != 0) continue;
380 // Check this merc to see if the town is a possibility.
381 FOR_EACH(UINT8 const, k, a.towns)
382 {
383 if (*k != town) continue;
384 assassins[n++] = a.profile;
385 break;
386 }
387 }
388
389 if (n == 0) return;
390 ProfileID const pid = assassins[Random(n)];
391 MERCPROFILESTRUCT& p = GetProfile(pid);
392 p.sSectorX = gWorldSectorX;
393 p.sSectorY = gWorldSectorY;
394 AddStrategicEvent(EVENT_REMOVE_ASSASSIN, GetWorldTotalMin() + 60 * (3 + Random(3)), pid);
395 }
396
397
MakeRemainingAssassinsTougher()398 void MakeRemainingAssassinsTougher()
399 {
400 UINT8 n_remaining_assassins = 0;
401 FOR_EACH(AssassinInfo const, i, g_assassin_info)
402 {
403 if (GetProfile(i->profile).bMercStatus == MERC_IS_DEAD) continue;
404 ++n_remaining_assassins;
405 }
406
407 size_t const n_assassins = lengthof(g_assassin_info);
408 UINT8 difficulty = 60 / n_assassins * (n_assassins - n_remaining_assassins);
409 switch (gGameOptions.ubDifficultyLevel)
410 {
411 case DIF_LEVEL_MEDIUM: difficulty = difficulty * 13 / 10; break;
412 case DIF_LEVEL_HARD: difficulty = difficulty * 16 / 10; break;
413 default: break;
414 }
415
416 UINT16 new_item;
417 UINT16 old_item;
418 if (difficulty < 14)
419 {
420 // Nothing
421 return;
422 }
423 else if (difficulty < 28)
424 {
425 // Mini grenade
426 old_item = NOTHING;
427 new_item = MINI_GRENADE;
428 }
429 else if (difficulty < 42)
430 {
431 // Hand grenade
432 old_item = MINI_GRENADE;
433 new_item = HAND_GRENADE;
434 }
435 else if (difficulty < 56)
436 {
437 // Mustard grenade
438 old_item = HAND_GRENADE;
439 new_item = MUSTARD_GRENADE;
440 }
441 else if (difficulty < 70)
442 {
443 // LAW
444 old_item = MUSTARD_GRENADE;
445 new_item = ROCKET_LAUNCHER;
446 }
447 else
448 {
449 // LAW and hand grenade
450 old_item = NOTHING;
451 new_item = HAND_GRENADE;
452 }
453
454 OBJECTTYPE o;
455 CreateItem(new_item, 100, &o);
456 FOR_EACH(AssassinInfo const, i, g_assassin_info)
457 {
458 ProfileID const pid = i->profile;
459 if (GetProfile(pid).bMercStatus == MERC_IS_DEAD) continue;
460 if (old_item != NOTHING)
461 {
462 RemoveObjectFromSoldierProfile(pid, old_item);
463 }
464 PlaceObjectInSoldierProfile(pid, &o);
465 }
466 }
467
468
StartSomeMercsOnAssignment(void)469 static void StartSomeMercsOnAssignment(void)
470 {
471 UINT32 uiChance;
472
473 // some randomly picked A.I.M. mercs will start off "on assignment" at the beginning of each new game
474 for (auto profile : GCM->listMercProfiles())
475 {
476 if (!profile->isAIMMerc() && !profile->isMERCMerc()) continue;
477
478 MERCPROFILESTRUCT& p = profile->getStruct();
479
480 // calc chance to start on assignment
481 uiChance = 5 * p.bExpLevel;
482
483 if (Random(100) < uiChance)
484 {
485 p.bMercStatus = MERC_WORKING_ELSEWHERE;
486 p.uiDayBecomesAvailable = 1 + Random(6 + p.bExpLevel / 2); // 1-(6 to 11) days
487 }
488 else
489 {
490 p.bMercStatus = MERC_OK;
491 p.uiDayBecomesAvailable = 0;
492 }
493
494 p.uiPrecedentQuoteSaid = 0;
495 p.ubDaysOfMoraleHangover = 0;
496 }
497 }
498
499
SetProfileFaceData(ProfileID const pid,UINT8 const face_idx,UINT16 const eyes_x,UINT16 const eyes_y,UINT16 const mouth_x,UINT16 const mouth_y)500 void SetProfileFaceData(ProfileID const pid, UINT8 const face_idx, UINT16 const eyes_x, UINT16 const eyes_y, UINT16 const mouth_x, UINT16 const mouth_y)
501 {
502 MERCPROFILESTRUCT& p = GetProfile(pid);
503 p.ubFaceIndex = face_idx;
504 p.usEyesX = eyes_x;
505 p.usEyesY = eyes_y;
506 p.usMouthX = mouth_x;
507 p.usMouthY = mouth_y;
508 }
509
510
CalcCompetence(MERCPROFILESTRUCT const & p)511 static UINT16 CalcCompetence(MERCPROFILESTRUCT const& p)
512 {
513 UINT32 uiStats, uiSkills, uiActionPoints, uiSpecialSkills;
514 UINT16 usCompetence;
515
516
517 // count life twice 'cause it's also hit points
518 // mental skills are halved 'cause they're actually not that important within the game
519 uiStats = ((2 * p.bLifeMax) + p.bStrength + p.bAgility + p.bDexterity + ((p.bLeadership + p.bWisdom) / 2)) / 3;
520
521 // marksmanship is very important, count it double
522 uiSkills = (UINT32) ((2 * (pow((double)p.bMarksmanship, 3) / 10000)) +
523 1.5 * (pow((double)p.bMedical, 3) / 10000) +
524 (pow((double)p.bMechanical, 3) / 10000) +
525 (pow((double)p.bExplosive, 3) / 10000));
526
527 // action points
528 uiActionPoints = 5 + (((10 * p.bExpLevel +
529 3 * p.bAgility +
530 2 * p.bLifeMax +
531 2 * p.bDexterity) + 20) / 40);
532
533
534 // count how many he has, don't care what they are
535 uiSpecialSkills = ((p.bSkillTrait != 0) ? 1 : 0) + ((p.bSkillTrait2 != 0) ? 1 : 0);
536
537 usCompetence = (UINT16) ((pow(p.bExpLevel, 0.2) * uiStats * uiSkills * (uiActionPoints - 6) * (1 + (0.05 * (FLOAT)uiSpecialSkills))) / 1000);
538
539 // this currently varies from about 10 (Flo) to 1200 (Gus)
540 return(usCompetence);
541 }
542
543
CalcMedicalDeposit(MERCPROFILESTRUCT const & p)544 static INT16 CalcMedicalDeposit(MERCPROFILESTRUCT const& p)
545 {
546 UINT16 usDeposit;
547
548 // this rounds off to the nearest hundred
549 usDeposit = (5 * CalcCompetence(p) + 50) / 100 * 100;
550
551 return(usDeposit);
552 }
553
554
FindSoldierByProfileID(const ProfileID pid)555 SOLDIERTYPE* FindSoldierByProfileID(const ProfileID pid)
556 {
557 FOR_EACH_SOLDIER(s)
558 {
559 if (s->ubProfile == pid) return s;
560 }
561 return NULL;
562 }
563
564
FindSoldierByProfileIDOnPlayerTeam(const ProfileID pid)565 SOLDIERTYPE* FindSoldierByProfileIDOnPlayerTeam(const ProfileID pid)
566 {
567 FOR_EACH_IN_TEAM(s, OUR_TEAM)
568 {
569 if (s->ubProfile == pid) return s;
570 }
571 return NULL;
572 }
573
574
ChangeSoldierTeam(SOLDIERTYPE * const old_s,UINT8 const team)575 SOLDIERTYPE* ChangeSoldierTeam(SOLDIERTYPE* const old_s, UINT8 const team)
576 {
577 if (gfInTalkPanel) DeleteTalkingMenu();
578
579 GridNo const old_gridno = old_s->sGridNo;
580
581 // At the low level check if this guy is in inv panel, else remove.
582 if (gsCurInterfacePanel == SM_PANEL && gpSMCurrentMerc == old_s)
583 {
584 SetCurrentInterfacePanel(TEAM_PANEL);
585 }
586
587 // Remove him from the game.
588 InternalTacticalRemoveSoldier(*old_s, FALSE);
589
590 // Create a new one.
591 SOLDIERCREATE_STRUCT c;
592 c = SOLDIERCREATE_STRUCT{};
593 c.bTeam = team;
594 c.ubProfile = old_s->ubProfile;
595 c.bBodyType = old_s->ubBodyType;
596 c.sSectorX = old_s->sSectorX;
597 c.sSectorY = old_s->sSectorY;
598 c.bSectorZ = old_s->bSectorZ;
599 c.sInsertionGridNo = old_s->sGridNo; // XXX always NOWHERE due to InternalTacticalRemoveSoldier() above
600 c.bDirection = old_s->bDirection;
601 if (old_s->uiStatusFlags & SOLDIER_VEHICLE)
602 {
603 c.ubProfile = NO_PROFILE;
604 c.fUseGivenVehicle = TRUE;
605 c.bUseGivenVehicleID = old_s->bVehicleID;
606 }
607 SOLDIERTYPE* const new_s = TacticalCreateSoldier(c);
608 if (!new_s) return 0;
609
610 // Copy vital stats back.
611 new_s->bLife = old_s->bLife;
612 new_s->bLifeMax = old_s->bLifeMax;
613 new_s->bAgility = old_s->bAgility;
614 new_s->bLeadership = old_s->bLeadership;
615 new_s->bDexterity = old_s->bDexterity;
616 new_s->bStrength = old_s->bStrength;
617 new_s->bWisdom = old_s->bWisdom;
618 new_s->bExpLevel = old_s->bExpLevel;
619 new_s->bMarksmanship = old_s->bMarksmanship;
620 new_s->bMedical = old_s->bMedical;
621 new_s->bMechanical = old_s->bMechanical;
622 new_s->bExplosive = old_s->bExplosive;
623 new_s->bLastRenderVisibleValue = old_s->bLastRenderVisibleValue;
624 new_s->bVisible = old_s->bVisible;
625 new_s->bCamo = old_s->bCamo;
626 new_s->bLevel = old_s->bLevel;
627 if(old_s->bLevel == SECOND_LEVEL) SetSoldierHeight(new_s, SECOND_LEVEL_Z_OFFSET);
628 if (new_s->bCamo != 0) CreateSoldierPalettes(*new_s);
629
630 if (team == OUR_TEAM) new_s->bVisible = 1;
631
632 // Copy over any items.
633 for (UINT32 i = 0; i != NUM_INV_SLOTS; ++i)
634 {
635 new_s->inv[i] = old_s->inv[i];
636 }
637
638 // Loop through all active merc slots, change any attacker's target if they
639 // were once on this guy.
640 FOR_EACH_MERC(i)
641 {
642 SOLDIERTYPE& s = **i;
643 if (s.target == old_s) s.target = new_s;
644 }
645
646 new_s->sInsertionGridNo = old_gridno;
647
648 if (gfPotentialTeamChangeDuringDeath)
649 {
650 HandleCheckForDeathCommonCode(old_s);
651 }
652
653 if (gfWorldLoaded && old_s->bInSector)
654 {
655 AddSoldierToSectorNoCalculateDirectionUseAnimation(new_s, old_s->usAnimState, old_s->usAniCode);
656 HandleSight(*new_s, SIGHT_LOOK | SIGHT_RADIO);
657 }
658
659 if (new_s->ubProfile != NO_PROFILE)
660 {
661 UINT8& misc_flags = GetProfile(new_s->ubProfile).ubMiscFlags;
662 misc_flags = team == OUR_TEAM ?
663 misc_flags | PROFILE_MISC_FLAG_RECRUITED :
664 misc_flags & ~PROFILE_MISC_FLAG_RECRUITED;
665 }
666
667 return new_s;
668 }
669
670
RecruitRPC(UINT8 ubCharNum)671 BOOLEAN RecruitRPC( UINT8 ubCharNum )
672 {
673 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubCharNum);
674 if (!pSoldier)
675 {
676 return( FALSE );
677 }
678
679 // OK, set recruit flag..
680 gMercProfiles[ ubCharNum ].ubMiscFlags |= PROFILE_MISC_FLAG_RECRUITED;
681
682 // Add this guy to our team!
683 SOLDIERTYPE* const pNewSoldier = ChangeSoldierTeam(pSoldier, OUR_TEAM);
684
685 // handle set up any RPC's that will leave us in time
686 if ( ubCharNum == SLAY )
687 {
688 // slay will leave in a week
689 pNewSoldier->iEndofContractTime = GetWorldTotalMin() + ( 7 * 24 * 60 );
690
691 KickOutWheelchair( pNewSoldier );
692 }
693 else if ( ubCharNum == DYNAMO && gubQuest[ QUEST_FREE_DYNAMO ] == QUESTINPROGRESS )
694 {
695 EndQuest( QUEST_FREE_DYNAMO, pSoldier->sSectorX, pSoldier->sSectorY );
696 }
697 // handle town loyalty adjustment
698 HandleTownLoyaltyForNPCRecruitment( pNewSoldier );
699
700 // Try putting them into the current squad
701 if (!AddCharacterToSquad(pNewSoldier, CurrentSquad()))
702 {
703 AddCharacterToAnySquad( pNewSoldier );
704 }
705
706 ResetDeadSquadMemberList( pNewSoldier->bAssignment );
707
708 DirtyMercPanelInterface( pNewSoldier, DIRTYLEVEL2 );
709
710 if ( pNewSoldier->inv[ HANDPOS ].usItem == NOTHING )
711 {
712 // empty handed - swap in first available weapon
713 INT8 bSlot;
714
715 bSlot = FindObjClass( pNewSoldier, IC_WEAPON );
716 if ( bSlot != NO_SLOT )
717 {
718 if ( GCM->getItem(pNewSoldier->inv[ bSlot ].usItem)->isTwoHanded() )
719 {
720 if ( bSlot != SECONDHANDPOS && pNewSoldier->inv[ SECONDHANDPOS ].usItem != NOTHING )
721 {
722 // need to move second hand item out first
723 AutoPlaceObject( pNewSoldier, &(pNewSoldier->inv[ SECONDHANDPOS ]), FALSE );
724 }
725 }
726 // swap item to hand
727 SwapObjs( &(pNewSoldier->inv[ bSlot ]), &(pNewSoldier->inv[ HANDPOS ]) );
728 }
729 }
730
731 if ( ubCharNum == IRA )
732 {
733 // trigger 0th PCscript line
734 TriggerNPCRecord( IRA, 0 );
735 }
736
737 // Set whatkind of merc am i
738 pNewSoldier->ubWhatKindOfMercAmI = MERC_TYPE__NPC;
739
740
741 //
742 //add a history log that tells the user that a npc has joined the team
743 //
744 // ( pass in pNewSoldier->sSectorX cause if its invalid, -1, n/a will appear as the sector in the history log )
745 AddHistoryToPlayersLog( HISTORY_RPC_JOINED_TEAM, pNewSoldier->ubProfile, GetWorldTotalMin(), pNewSoldier->sSectorX, pNewSoldier->sSectorY );
746
747
748 //remove the merc from the Personnel screens departed list ( if they have never been hired before, its ok to call it )
749 RemoveNewlyHiredMercFromPersonnelDepartedList( pSoldier->ubProfile );
750
751 return( TRUE );
752 }
753
RecruitEPC(UINT8 ubCharNum)754 BOOLEAN RecruitEPC( UINT8 ubCharNum )
755 {
756 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubCharNum);
757 if (!pSoldier)
758 {
759 return( FALSE );
760 }
761
762 // OK, set recruit flag..
763 gMercProfiles[ ubCharNum ].ubMiscFlags |= PROFILE_MISC_FLAG_EPCACTIVE;
764
765 gMercProfiles[ ubCharNum ].ubMiscFlags3 &= ~PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE;
766
767 // Add this guy to our team!
768 SOLDIERTYPE* const pNewSoldier = ChangeSoldierTeam( pSoldier, OUR_TEAM );
769 pNewSoldier->ubWhatKindOfMercAmI = MERC_TYPE__EPC;
770
771 // Try putting them into the current squad
772 if (!AddCharacterToSquad(pNewSoldier, CurrentSquad()))
773 {
774 AddCharacterToAnySquad( pNewSoldier );
775 }
776
777 ResetDeadSquadMemberList( pNewSoldier->bAssignment );
778
779 DirtyMercPanelInterface( pNewSoldier, DIRTYLEVEL2 );
780 // Make the interface panel dirty..
781 // This will dirty the panel next frame...
782 gfRerenderInterfaceFromHelpText = TRUE;
783
784
785 // If we are a robot, look to update controller....
786 if ( pNewSoldier->uiStatusFlags & SOLDIER_ROBOT )
787 {
788 UpdateRobotControllerGivenRobot( pNewSoldier );
789 }
790
791 // Set whatkind of merc am i
792 pNewSoldier->ubWhatKindOfMercAmI = MERC_TYPE__EPC;
793
794 UpdateTeamPanelAssignments( );
795
796 return( TRUE );
797 }
798
799
UnRecruitEPC(ProfileID const pid)800 BOOLEAN UnRecruitEPC(ProfileID const pid)
801 {
802 SOLDIERTYPE* const s = FindSoldierByProfileID(pid);
803 if (!s) return FALSE;
804 if (s->ubWhatKindOfMercAmI != MERC_TYPE__EPC) return FALSE;
805
806 if (s->bAssignment < ON_DUTY) ResetDeadSquadMemberList(s->bAssignment);
807
808 MERCPROFILESTRUCT& p = GetProfile(pid);
809
810 // OK, UN set recruit flag..
811 p.ubMiscFlags &= ~PROFILE_MISC_FLAG_EPCACTIVE;
812
813 // update sector values to current
814
815 // check to see if this person should disappear from the map after this
816 if ((pid == JOHN || pid == MARY) &&
817 s->sSectorX == 13 &&
818 s->sSectorY == MAP_ROW_B &&
819 s->bSectorZ == 0)
820 {
821 p.sSectorX = 0;
822 p.sSectorY = 0;
823 p.bSectorZ = 0;
824 }
825 else
826 {
827 p.sSectorX = s->sSectorX;
828 p.sSectorY = s->sSectorY;
829 p.bSectorZ = s->bSectorZ;
830 }
831
832 // how do we decide whether or not to set this?
833 p.fUseProfileInsertionInfo = TRUE;
834 p.ubMiscFlags3 |= PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE;
835
836 ChangeSoldierTeam(s, CIV_TEAM);
837 UpdateTeamPanelAssignments();
838 return TRUE;
839 }
840
841
WhichBuddy(UINT8 ubCharNum,UINT8 ubBuddy)842 INT8 WhichBuddy( UINT8 ubCharNum, UINT8 ubBuddy )
843 {
844 if (ubCharNum == NO_PROFILE)
845 {
846 return -1;
847 }
848
849 MERCPROFILESTRUCT const& p = GetProfile(ubCharNum);
850 for (INT8 bLoop = 0; bLoop < 3; bLoop++)
851 {
852 if (p.bBuddy[bLoop] == ubBuddy)
853 {
854 return( bLoop );
855 }
856 }
857 return( -1 );
858 }
859
WhichHated(UINT8 ubCharNum,UINT8 ubHated)860 INT8 WhichHated( UINT8 ubCharNum, UINT8 ubHated )
861 {
862 INT8 bLoop;
863
864 MERCPROFILESTRUCT const& p = GetProfile(ubCharNum);
865
866 for (bLoop = 0; bLoop < 3; bLoop++)
867 {
868 if (p.bHated[bLoop] == ubHated)
869 {
870 return( bLoop );
871 }
872 }
873 return( -1 );
874 }
875
876
GetFirstBuddyOnTeam(MERCPROFILESTRUCT const & p)877 INT8 GetFirstBuddyOnTeam(MERCPROFILESTRUCT const& p)
878 {
879 for (INT i = 0; i != 3; ++i)
880 {
881 INT8 const buddy = p.bBuddy[i];
882 if (buddy < 0) continue;
883 if (!IsMercOnTeam(buddy)) continue;
884 if (IsMercDead(GetProfile(buddy))) continue;
885 return buddy;
886 }
887 return -1;
888 }
889
890
IsProfileATerrorist(ProfileID const pid)891 bool IsProfileATerrorist(ProfileID const pid)
892 {
893 FOR_EACH(TerroristInfo const, i, g_terrorist_infos)
894 {
895 if (i->profile == pid) return true;
896 }
897 return false;
898 }
899
900
IsProfileAHeadMiner(UINT8 ubProfile)901 BOOLEAN IsProfileAHeadMiner(UINT8 ubProfile)
902 {
903 switch (ubProfile)
904 {
905 case FRED:
906 case MATT:
907 case OSWALD:
908 case CALVIN:
909 case CARL: return TRUE;
910 default: return FALSE;
911 }
912 }
913
914
UpdateSoldierPointerDataIntoProfile()915 void UpdateSoldierPointerDataIntoProfile()
916 {
917 FOR_EACH_MERC(i)
918 {
919 SOLDIERTYPE const& s = **i;
920 if (s.ubProfile == NO_PROFILE) continue;
921 // If we are above player mercs
922 if (MercProfile(s.ubProfile).isPlayerMerc()) continue;
923
924 MERCPROFILESTRUCT& p = GetProfile(s.ubProfile);
925 p.bLife = s.bLife;
926 p.bLifeMax = s.bLifeMax;
927 p.bAgility = s.bAgility;
928 p.bLeadership = s.bLeadership;
929 p.bDexterity = s.bDexterity;
930 p.bStrength = s.bStrength;
931 p.bWisdom = s.bWisdom;
932 p.bExpLevel = s.bExpLevel;
933 p.bMarksmanship = s.bMarksmanship;
934 p.bMedical = s.bMedical;
935 p.bMechanical = s.bMechanical;
936 p.bExplosive = s.bExplosive;
937 }
938 }
939
SwapLarrysProfiles(SOLDIERTYPE * const s)940 SOLDIERTYPE* SwapLarrysProfiles(SOLDIERTYPE* const s)
941 {
942 const ProfileID src_id = s->ubProfile;
943 ProfileID dst_id;
944 switch (src_id)
945 {
946 case LARRY_NORMAL: dst_id = LARRY_DRUNK; break;
947 case LARRY_DRUNK: dst_id = LARRY_NORMAL; break;
948 default: return s; // I don't think so!
949 }
950
951 MERCPROFILESTRUCT const& src = GetProfile(src_id);
952 MERCPROFILESTRUCT& dst = GetProfile(dst_id);
953
954 dst.ubMiscFlags2 = src.ubMiscFlags2;
955 dst.ubMiscFlags = src.ubMiscFlags;
956 dst.sSectorX = src.sSectorX;
957 dst.sSectorY = src.sSectorY;
958 dst.uiDayBecomesAvailable = src.uiDayBecomesAvailable;
959 dst.usKills = src.usKills;
960 dst.usAssists = src.usAssists;
961 dst.usShotsFired = src.usShotsFired;
962 dst.usShotsHit = src.usShotsHit;
963 dst.usBattlesFought = src.usBattlesFought;
964 dst.usTimesWounded = src.usTimesWounded;
965 dst.usTotalDaysServed = src.usTotalDaysServed;
966 dst.fUseProfileInsertionInfo = src.fUseProfileInsertionInfo;
967 dst.sGridNo = src.sGridNo;
968 dst.ubQuoteActionID = src.ubQuoteActionID;
969 dst.ubLastQuoteSaid = src.ubLastQuoteSaid;
970 dst.ubStrategicInsertionCode = src.ubStrategicInsertionCode;
971 dst.bMercStatus = src.bMercStatus;
972 dst.bSectorZ = src.bSectorZ;
973 dst.usStrategicInsertionData = src.usStrategicInsertionData;
974 dst.ubMiscFlags3 = src.ubMiscFlags3;
975 dst.ubDaysOfMoraleHangover = src.ubDaysOfMoraleHangover;
976 dst.ubNumTimesDrugUseInLifetime = src.ubNumTimesDrugUseInLifetime;
977 dst.uiPrecedentQuoteSaid = src.uiPrecedentQuoteSaid;
978 dst.sPreCombatGridNo = src.sPreCombatGridNo;
979
980 // CJC: this is causing problems so just skip the transfer of exp...
981 /*
982 dst.sLifeGain = src.sLifeGain;
983 dst.sAgilityGain = src.sAgilityGain;
984 dst.sDexterityGain = src.sDexterityGain;
985 dst.sStrengthGain = src.sStrengthGain;
986 dst.sLeadershipGain = src.sLeadershipGain;
987 dst.sWisdomGain = src.sWisdomGain;
988 dst.sExpLevelGain = src.sExpLevelGain;
989 dst.sMarksmanshipGain = src.sMarksmanshipGain;
990 dst.sMedicalGain = src.sMedicalGain;
991 dst.sMechanicGain = src.sMechanicGain;
992 dst.sExplosivesGain = src.sExplosivesGain;
993
994 dst.bLifeDelta = src.bLifeDelta;
995 dst.bAgilityDelta = src.bAgilityDelta;
996 dst.bDexterityDelta = src.bDexterityDelta;
997 dst.bStrengthDelta = src.bStrengthDelta;
998 dst.bLeadershipDelta = src.bLeadershipDelta;
999 dst.bWisdomDelta = src.bWisdomDelta;
1000 dst.bExpLevelDelta = src.bExpLevelDelta;
1001 dst.bMarksmanshipDelta = src.bMarksmanshipDelta;
1002 dst.bMedicalDelta = src.bMedicalDelta;
1003 dst.bMechanicDelta = src.bMechanicDelta;
1004 dst.bExplosivesDelta = src.bExplosivesDelta;
1005 */
1006
1007 memcpy(dst.bInvStatus, src.bInvStatus, sizeof(dst.bInvStatus));
1008 memcpy(dst.bInvNumber, src.bInvStatus, sizeof(dst.bInvNumber));
1009 memcpy(dst.inv, src.inv, sizeof(dst.inv));
1010
1011 DeleteSoldierFace(s);
1012 s->ubProfile = dst_id;
1013 InitSoldierFace(*s);
1014
1015 s->bStrength = dst.bStrength + dst.bStrengthDelta;
1016 s->bDexterity = dst.bDexterity + dst.bDexterityDelta;
1017 s->bAgility = dst.bAgility + dst.bAgilityDelta;
1018 s->bWisdom = dst.bWisdom + dst.bWisdomDelta;
1019 s->bExpLevel = dst.bExpLevel + dst.bExpLevelDelta;
1020 s->bLeadership = dst.bLeadership + dst.bLeadershipDelta;
1021 s->bMarksmanship = dst.bMarksmanship + dst.bMarksmanshipDelta;
1022 s->bMechanical = dst.bMechanical + dst.bMechanicDelta;
1023 s->bMedical = dst.bMedical + dst.bMedicalDelta;
1024 s->bExplosive = dst.bExplosive + dst.bExplosivesDelta;
1025
1026 if (s->ubProfile == LARRY_DRUNK)
1027 {
1028 SetFactTrue(FACT_LARRY_CHANGED);
1029 }
1030 else
1031 {
1032 SetFactFalse(FACT_LARRY_CHANGED);
1033 }
1034
1035 DirtyMercPanelInterface(s, DIRTYLEVEL2);
1036
1037 return s;
1038 }
1039
1040
DoesNPCOwnBuilding(SOLDIERTYPE * pSoldier,INT16 sGridNo)1041 BOOLEAN DoesNPCOwnBuilding( SOLDIERTYPE *pSoldier, INT16 sGridNo )
1042 {
1043 UINT8 ubRoomInfo;
1044
1045 // Get room info
1046 ubRoomInfo = gubWorldRoomInfo[ sGridNo ];
1047
1048 if ( ubRoomInfo == NO_ROOM )
1049 {
1050 return( FALSE );
1051 }
1052
1053 // Are we an NPC?
1054 if ( pSoldier->bTeam != CIV_TEAM )
1055 {
1056 return( FALSE );
1057 }
1058
1059 // OK, check both ranges
1060 if ( ubRoomInfo >= gMercProfiles[ pSoldier->ubProfile ].ubRoomRangeStart[ 0 ] &&
1061 ubRoomInfo <= gMercProfiles[ pSoldier->ubProfile ].ubRoomRangeEnd[ 0 ] )
1062 {
1063 return( TRUE );
1064 }
1065
1066 if ( ubRoomInfo >= gMercProfiles[ pSoldier->ubProfile ].ubRoomRangeStart[ 1 ] &&
1067 ubRoomInfo <= gMercProfiles[ pSoldier->ubProfile ].ubRoomRangeEnd[ 1 ] )
1068 {
1069 return( TRUE );
1070 }
1071
1072 return( FALSE );
1073 }
1074
IsProfileIdAnAimOrMERCMerc(UINT8 ubProfileID)1075 BOOLEAN IsProfileIdAnAimOrMERCMerc(UINT8 ubProfileID)
1076 {
1077 // AIM: ubProfileID < BIFF
1078 // MERC: ubProfileID >= BIFF && ubProfileID <= BUBBA
1079 MercProfile p(ubProfileID);
1080 return p.isAIMMerc() || p.isMERCMerc();
1081 }
1082