1 #include "Font_Control.h"
2 #include "Handle_Items.h"
3 #include "Items.h"
4 #include "Action_Items.h"
5 #include "JAScreens.h"
6 #include "TileDef.h"
7 #include "Weapons.h"
8 #include "Interface_Cursors.h"
9 #include "Soldier_Control.h"
10 #include "Overhead.h"
11 #include "Handle_UI.h"
12 #include "Points.h"
13 #include "Sound_Control.h"
14 #include "Isometric_Utils.h"
15 #include "Animation_Data.h"
16 #include "Random.h"
17 #include "Campaign.h"
18 #include "Interface.h"
19 #include "Interface_Panels.h"
20 #include "Explosion_Control.h"
21 #include "Keys.h"
22 #include "WCheck.h"
23 #include "Soldier_Profile.h"
24 #include "SkillCheck.h"
25 #include "LOS.h"
26 #include "Message.h"
27 #include "Text.h"
28 #include "FOV.h"
29 #include "ShopKeeper_Interface.h"
30 #include "GamePolicy.h"
31 #include "GameSettings.h"
32 #include "Environment.h"
33 #include "Auto_Resolve.h"
34 #include "Interface_Items.h"
35 #include "Game_Clock.h"
36 #include "Smell.h"
37 #include "StrategicMap.h"
38 #include "Campaign_Types.h"
39 #include "Soldier_Macros.h"
40 #include "Debug.h"
41
42 #include "AmmoTypeModel.h"
43 #include "CalibreModel.h"
44 #include "ContentManager.h"
45 #include "GameInstance.h"
46 #include "ItemModel.h"
47 #include "MagazineModel.h"
48 #include "WeaponModels.h"
49 #include <array>
50 #include <initializer_list>
51 #include <map>
52 #include <set>
53 #include <stdexcept>
54
55 constexpr UINT8 ANY_MAGSIZE = 255; // magic number for FindAmmo's mag_size parameter
56
57 struct AttachmentInfoStruct
58 {
59 UINT16 usItem;
60 UINT32 uiItemClass;
61 INT8 bAttachmentSkillCheck;
62 INT8 bAttachmentSkillCheckMod;
63 };
64
65
66 // NB hack: if an item appears in this array with an item class of IC_MISC,
67 // it is a slot used for noting the skill check required for a merge or multi-item attachment
68
69 static const AttachmentInfoStruct AttachmentInfo[] =
70 {
71 {SILENCER, IC_GUN, NO_CHECK, 0},
72 {SNIPERSCOPE, IC_GUN, NO_CHECK, 0},
73 {LASERSCOPE, IC_GUN, NO_CHECK, 0},
74 {BIPOD, IC_GUN, NO_CHECK, 0},
75 {UNDER_GLAUNCHER, IC_GUN, NO_CHECK, 0},
76 {DUCKBILL, IC_GUN, NO_CHECK, 0},
77 {SPRING_AND_BOLT_UPGRADE, IC_GUN, ATTACHING_SPECIAL_ITEM_CHECK, 0},
78 {GUN_BARREL_EXTENDER, IC_GUN, ATTACHING_SPECIAL_ITEM_CHECK, 0},
79 {DETONATOR, IC_BOMB, ATTACHING_DETONATOR_CHECK, 0},
80 {REMDETONATOR, IC_BOMB, ATTACHING_REMOTE_DETONATOR_CHECK, -10},
81 {XRAY_BULB, IC_NONE, ATTACHING_SPECIAL_ELECTRONIC_ITEM_CHECK, -15},
82 {COPPER_WIRE, IC_NONE, ATTACHING_SPECIAL_ELECTRONIC_ITEM_CHECK, +20},
83 {CERAMIC_PLATES, IC_ARMOUR, NO_CHECK, 0},
84
85 // extras
86 {NIGHTGOGGLES, IC_ARMOUR, NO_CHECK, 0},
87 {UVGOGGLES, IC_ARMOUR, NO_CHECK, 0},
88 {SUNGOGGLES, IC_ARMOUR, NO_CHECK, 0},
89 {ROBOT_REMOTE_CONTROL, IC_ARMOUR, NO_CHECK, 0},
90
91 {ADRENALINE_BOOSTER, IC_ARMOUR, NO_CHECK, 0},
92 {REGEN_BOOSTER, IC_ARMOUR, NO_CHECK, 0},
93 {BREAK_LIGHT, IC_ARMOUR, NO_CHECK, 0},
94 };
95
96 static std::map<UINT16, std::set<UINT16> const> const g_attachments
97 {
98 {DETONATOR, {TNT, HMX, C1, C4}},
99 {REMDETONATOR, {TNT, HMX, C1, C4}},
100 {CERAMIC_PLATES, {FLAK_JACKET, FLAK_JACKET_18, FLAK_JACKET_Y, KEVLAR_VEST, KEVLAR_VEST_18, KEVLAR_VEST_Y,
101 KEVLAR2_VEST, KEVLAR2_VEST_18, KEVLAR2_VEST_Y, SPECTRA_VEST, SPECTRA_VEST_18, SPECTRA_VEST_Y}},
102 {SPRING, {ALUMINUM_ROD}},
103 {QUICK_GLUE, {STEEL_ROD}},
104 {DUCT_TAPE, {STEEL_ROD}},
105 {XRAY_BULB, {FUMBLE_PAK}},
106 {CHEWING_GUM, {FUMBLE_PAK}},
107 {BATTERIES, {XRAY_DEVICE}},
108 {COPPER_WIRE, {LAME_BOY}}
109 };
110
111 // additional possible attachments if the extra_attachments game policy is set
112 static std::set<UINT16> const g_helmets {STEEL_HELMET, KEVLAR_HELMET, KEVLAR_HELMET_18, KEVLAR_HELMET_Y, SPECTRA_HELMET, SPECTRA_HELMET_18, SPECTRA_HELMET_Y};
113 static std::set<UINT16> const g_leggings {KEVLAR_LEGGINGS, KEVLAR_LEGGINGS_18, KEVLAR_LEGGINGS_Y, SPECTRA_LEGGINGS, SPECTRA_LEGGINGS_18, SPECTRA_LEGGINGS_Y};
114 static std::map<UINT16, decltype(g_helmets) *> const g_attachments_mod
115 {
116 {NIGHTGOGGLES, &g_helmets},
117 {UVGOGGLES, &g_helmets},
118 {SUNGOGGLES, &g_helmets},
119 {ROBOT_REMOTE_CONTROL, &g_helmets},
120
121 {BREAK_LIGHT, &g_leggings},
122 {REGEN_BOOSTER, &g_leggings},
123 {ADRENALINE_BOOSTER, &g_leggings}
124 };
125
126 static std::map<UINT16, std::set<UINT16> const> const Launchable
127 {
128 {GL_HE_GRENADE, {GLAUNCHER, UNDER_GLAUNCHER}},
129 {GL_TEARGAS_GRENADE, {GLAUNCHER, UNDER_GLAUNCHER}},
130 {GL_STUN_GRENADE, {GLAUNCHER, UNDER_GLAUNCHER}},
131 {GL_SMOKE_GRENADE, {GLAUNCHER, UNDER_GLAUNCHER}},
132 {MORTAR_SHELL, {MORTAR}},
133 {TANK_SHELL, {TANK_CANNON}}
134 };
135
136 static std::initializer_list<std::array<UINT16, 2> const> const CompatibleFaceItems
137 {
138 { NIGHTGOGGLES, EXTENDEDEAR },
139 { NIGHTGOGGLES, WALKMAN },
140 { SUNGOGGLES, EXTENDEDEAR },
141 { SUNGOGGLES, WALKMAN },
142 { UVGOGGLES, EXTENDEDEAR },
143 { UVGOGGLES, WALKMAN },
144 { GASMASK, EXTENDEDEAR },
145 { GASMASK, WALKMAN }
146 };
147
148
149 enum MergeType
150 {
151 DESTRUCTION,
152 COMBINE_POINTS,
153 TREAT_ARMOUR,
154 EXPLOSIVE,
155 EASY_MERGE,
156 ELECTRONIC_MERGE
157 };
158
159
160 struct MergeInfo
161 {
162 UINT16 item1;
163 UINT16 item2;
164 UINT16 result;
165 MergeType action;
166 };
167
168
169 static MergeInfo const Merge[] =
170 {
171 // first item second item resulting item, merge type
172 {FIRSTAIDKIT, FIRSTAIDKIT, FIRSTAIDKIT, COMBINE_POINTS},
173 {MEDICKIT, MEDICKIT, MEDICKIT, COMBINE_POINTS},
174 {LOCKSMITHKIT, LOCKSMITHKIT, LOCKSMITHKIT, COMBINE_POINTS},
175 {TOOLKIT, TOOLKIT, TOOLKIT, COMBINE_POINTS},
176 {GAS_CAN, GAS_CAN, GAS_CAN, COMBINE_POINTS},
177 {CAMOUFLAGEKIT, CAMOUFLAGEKIT, CAMOUFLAGEKIT, COMBINE_POINTS},
178 {BEER, BEER, BEER, COMBINE_POINTS},
179 {WINE, WINE, WINE, COMBINE_POINTS},
180 {ALCOHOL, ALCOHOL, ALCOHOL, COMBINE_POINTS},
181 {CANTEEN, CANTEEN, CANTEEN, COMBINE_POINTS},
182
183 {COMPOUND18, FLAK_JACKET, FLAK_JACKET_18, TREAT_ARMOUR},
184 {COMPOUND18, KEVLAR_VEST, KEVLAR_VEST_18, TREAT_ARMOUR},
185 {COMPOUND18, KEVLAR2_VEST, KEVLAR2_VEST_18, TREAT_ARMOUR},
186 {COMPOUND18, SPECTRA_VEST, SPECTRA_VEST_18, TREAT_ARMOUR},
187 {COMPOUND18, LEATHER_JACKET_W_KEVLAR, LEATHER_JACKET_W_KEVLAR_18, TREAT_ARMOUR},
188 {COMPOUND18, KEVLAR_LEGGINGS, KEVLAR_LEGGINGS_18, TREAT_ARMOUR},
189 {COMPOUND18, SPECTRA_LEGGINGS, SPECTRA_LEGGINGS_18, TREAT_ARMOUR},
190 {COMPOUND18, KEVLAR_HELMET, KEVLAR_HELMET_18, TREAT_ARMOUR},
191 {COMPOUND18, SPECTRA_HELMET, SPECTRA_HELMET_18, TREAT_ARMOUR},
192 {COMPOUND18, FLAK_JACKET_Y, NOTHING, DESTRUCTION},
193 {COMPOUND18, KEVLAR_VEST_Y, NOTHING, DESTRUCTION},
194 {COMPOUND18, KEVLAR2_VEST_Y, NOTHING, DESTRUCTION},
195 {COMPOUND18, SPECTRA_VEST_Y, NOTHING, DESTRUCTION},
196 {COMPOUND18, LEATHER_JACKET_W_KEVLAR_Y, NOTHING, DESTRUCTION},
197 {COMPOUND18, KEVLAR_LEGGINGS_Y, NOTHING, DESTRUCTION},
198 {COMPOUND18, SPECTRA_LEGGINGS_Y, NOTHING, DESTRUCTION},
199 {COMPOUND18, KEVLAR_HELMET_Y, NOTHING, DESTRUCTION},
200 {COMPOUND18, SPECTRA_HELMET_Y, NOTHING, DESTRUCTION},
201
202 {JAR_QUEEN_CREATURE_BLOOD, FLAK_JACKET, FLAK_JACKET_Y, TREAT_ARMOUR},
203 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR_VEST, KEVLAR_VEST_Y, TREAT_ARMOUR},
204 {JAR_QUEEN_CREATURE_BLOOD, SPECTRA_VEST, SPECTRA_VEST_Y, TREAT_ARMOUR},
205 {JAR_QUEEN_CREATURE_BLOOD, LEATHER_JACKET_W_KEVLAR, LEATHER_JACKET_W_KEVLAR_Y, TREAT_ARMOUR},
206 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR2_VEST, KEVLAR2_VEST_Y, TREAT_ARMOUR},
207 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR_LEGGINGS, KEVLAR_LEGGINGS_Y, TREAT_ARMOUR},
208 {JAR_QUEEN_CREATURE_BLOOD, SPECTRA_LEGGINGS, SPECTRA_LEGGINGS_Y, TREAT_ARMOUR},
209 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR_HELMET, KEVLAR_HELMET_Y, TREAT_ARMOUR},
210 {JAR_QUEEN_CREATURE_BLOOD, SPECTRA_HELMET, SPECTRA_HELMET_Y, TREAT_ARMOUR},
211 {JAR_QUEEN_CREATURE_BLOOD, FLAK_JACKET_18, NOTHING, DESTRUCTION},
212 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR_VEST_18, NOTHING, DESTRUCTION},
213 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR2_VEST_18, NOTHING, DESTRUCTION},
214 {JAR_QUEEN_CREATURE_BLOOD, SPECTRA_VEST_18, NOTHING, DESTRUCTION},
215 {JAR_QUEEN_CREATURE_BLOOD, LEATHER_JACKET_W_KEVLAR_18, NOTHING, DESTRUCTION},
216 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR_LEGGINGS_18, NOTHING, DESTRUCTION},
217 {JAR_QUEEN_CREATURE_BLOOD, SPECTRA_LEGGINGS_18, NOTHING, DESTRUCTION},
218 {JAR_QUEEN_CREATURE_BLOOD, KEVLAR_HELMET_18, NOTHING, DESTRUCTION},
219 {JAR_QUEEN_CREATURE_BLOOD, SPECTRA_HELMET_18, NOTHING, DESTRUCTION},
220
221 {RDX, TNT, HMX, EXPLOSIVE},
222 {RDX, C1, C4, EXPLOSIVE},
223 {TNT, RDX, HMX, EXPLOSIVE},
224 {C1, RDX, C4, EXPLOSIVE},
225
226 {STRING, TIN_CAN, STRING_TIED_TO_TIN_CAN, EASY_MERGE},
227 {TIN_CAN, STRING, STRING_TIED_TO_TIN_CAN, EASY_MERGE},
228
229 {FLASH_DEVICE, DISPLAY_UNIT, XRAY_DEVICE, ELECTRONIC_MERGE},
230 {DISPLAY_UNIT, FLASH_DEVICE, XRAY_DEVICE, ELECTRONIC_MERGE},
231 };
232
233 struct ComboMergeInfoStruct
234 {
235 UINT16 usItem;
236 UINT16 usAttachment[2];
237 UINT16 usResult;
238 };
239
240
241 static ComboMergeInfoStruct const AttachmentComboMerge[] =
242 {
243 // base item attach 1 attach 2 result
244 {ALUMINUM_ROD, {SPRING, NOTHING}, SPRING_AND_BOLT_UPGRADE},
245 {STEEL_ROD, {QUICK_GLUE, DUCT_TAPE}, GUN_BARREL_EXTENDER},
246 {FUMBLE_PAK, {XRAY_BULB, CHEWING_GUM}, FLASH_DEVICE},
247 {LAME_BOY, {COPPER_WIRE, NOTHING}, DISPLAY_UNIT},
248 };
249
250
ItemIsLegal(UINT16 usItemIndex)251 BOOLEAN ItemIsLegal( UINT16 usItemIndex )
252 {
253 //if the user has selected the reduced gun list
254 if( !gGameOptions.fGunNut )
255 {
256 const ItemModel *item = GCM->getItem(usItemIndex);
257
258 if(item->isGun() && item->asWeapon()->isInBigGunList())
259 {
260 return false;
261 }
262
263 if(item->isAmmo() && item->asAmmo()->isInBigGunList())
264 {
265 return false;
266 }
267 }
268
269 return(TRUE);
270 }
271
WeaponInHand(const SOLDIERTYPE * const pSoldier)272 BOOLEAN WeaponInHand(const SOLDIERTYPE* const pSoldier)
273 {
274 if ( GCM->getItem(pSoldier->inv[HANDPOS].usItem)->getItemClass() & (IC_WEAPON | IC_THROWN) )
275 {
276 OBJECTTYPE const& o = pSoldier->inv[HANDPOS];
277 if (HasObjectImprint(o))
278 {
279 if (pSoldier->ubProfile != NO_PROFILE)
280 {
281 if (pSoldier->inv[HANDPOS].ubImprintID != pSoldier->ubProfile)
282 {
283 return( FALSE );
284 }
285 }
286 else
287 {
288 if (pSoldier->inv[HANDPOS].ubImprintID != (NO_PROFILE + 1) )
289 {
290 return( FALSE );
291 }
292 }
293 }
294 if (pSoldier->inv[HANDPOS].bGunStatus >= USABLE)
295 {
296 return( TRUE );
297 }
298 }
299 // return -1 or some "broken" value if weapon is broken?
300 return( FALSE );
301 }
302
ItemSlotLimit(UINT16 usItem,INT8 bSlot)303 UINT8 ItemSlotLimit( UINT16 usItem, INT8 bSlot )
304 {
305 UINT8 ubSlotLimit;
306
307 if ( bSlot < BIGPOCK1POS )
308 {
309 return( 1 );
310 }
311 else
312 {
313 ubSlotLimit = GCM->getItem(usItem)->getPerPocket();
314 if (bSlot >= SMALLPOCK1POS && ubSlotLimit > 1)
315 {
316 ubSlotLimit /= 2;
317 }
318 return( ubSlotLimit );
319 }
320 }
321
MoneySlotLimit(INT8 bSlot)322 UINT32 MoneySlotLimit( INT8 bSlot )
323 {
324 if ( bSlot >= SMALLPOCK1POS )
325 {
326 return( MAX_MONEY_PER_SLOT / 2 );
327 }
328 else
329 {
330 return( MAX_MONEY_PER_SLOT );
331 }
332 }
333
334
FindObj(const SOLDIERTYPE * pSoldier,UINT16 usItem)335 INT8 FindObj(const SOLDIERTYPE* pSoldier, UINT16 usItem)
336 {
337 INT8 bLoop;
338
339 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
340 {
341 if (pSoldier->inv[bLoop].usItem == usItem)
342 {
343 return( bLoop );
344 }
345 }
346 return( NO_SLOT );
347 }
348
FindUsableObj(const SOLDIERTYPE * pSoldier,UINT16 usItem)349 INT8 FindUsableObj( const SOLDIERTYPE * pSoldier, UINT16 usItem )
350 {
351 INT8 bLoop;
352
353 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
354 {
355 if ( pSoldier->inv[bLoop].usItem == usItem && pSoldier->inv[bLoop].bStatus[0] >= USABLE )
356 {
357 return( bLoop );
358 }
359 }
360 return( NO_SLOT );
361 }
362
363
FindObjExcludingSlot(const SOLDIERTYPE * pSoldier,UINT16 usItem,INT8 bExcludeSlot)364 static INT8 FindObjExcludingSlot(const SOLDIERTYPE* pSoldier, UINT16 usItem, INT8 bExcludeSlot)
365 {
366 INT8 bLoop;
367
368 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
369 {
370 if (bLoop == bExcludeSlot)
371 {
372 continue;
373 }
374 if (pSoldier->inv[bLoop].usItem == usItem)
375 {
376 return( bLoop );
377 }
378 }
379 return( NO_SLOT );
380 }
381
FindExactObj(const SOLDIERTYPE * pSoldier,OBJECTTYPE * pObj)382 INT8 FindExactObj( const SOLDIERTYPE * pSoldier, OBJECTTYPE * pObj )
383 {
384 INT8 bLoop;
385
386 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
387 {
388 if (pObj == &pSoldier->inv[bLoop]) return bLoop;
389 }
390 return( NO_SLOT );
391 }
392
393
FindObjWithin(SOLDIERTYPE * pSoldier,UINT16 usItem,INT8 bLower,INT8 bUpper)394 INT8 FindObjWithin( SOLDIERTYPE * pSoldier, UINT16 usItem, INT8 bLower, INT8 bUpper )
395 {
396 INT8 bLoop;
397
398 for (bLoop = bLower; bLoop <= bUpper; bLoop++)
399 {
400 if (pSoldier->inv[bLoop].usItem == usItem)
401 {
402 return( bLoop );
403 }
404 }
405 return( ITEM_NOT_FOUND );
406 }
407
408
FindObjInObjRange(const SOLDIERTYPE * const pSoldier,UINT16 usItem1,UINT16 usItem2)409 INT8 FindObjInObjRange(const SOLDIERTYPE* const pSoldier, UINT16 usItem1, UINT16 usItem2)
410 {
411 INT8 bLoop;
412 UINT16 usTemp;
413
414 if (usItem1 > usItem2 )
415 {
416 // swap the two...
417 usTemp = usItem2;
418 usItem2 = usItem1;
419 usItem1 = usTemp;
420 }
421
422 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
423 {
424 usTemp = pSoldier->inv[bLoop].usItem;
425 if ( usTemp >= usItem1 && usTemp <= usItem2 )
426 {
427 return( bLoop );
428 }
429 }
430
431 return( ITEM_NOT_FOUND );
432 }
433
434
FindObjClass(const SOLDIERTYPE * const pSoldier,const UINT32 usItemClass)435 INT8 FindObjClass(const SOLDIERTYPE* const pSoldier, const UINT32 usItemClass)
436 {
437 INT8 bLoop;
438
439 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
440 {
441 if (GCM->getItem(pSoldier->inv[bLoop].usItem)->getItemClass() & usItemClass)
442 {
443 return( bLoop );
444 }
445 }
446 return( NO_SLOT );
447 }
448
449
FindAIUsableObjClass(const SOLDIERTYPE * pSoldier,UINT32 usItemClass)450 INT8 FindAIUsableObjClass( const SOLDIERTYPE * pSoldier, UINT32 usItemClass )
451 {
452 // finds the first object of the specified class which does NOT have
453 // the "unusable by AI" flag set.
454
455 // uses & rather than == so that this function can search for any weapon
456 INT8 bLoop;
457
458 // This is for the AI only so:
459
460 // Do not consider tank cannons or rocket launchers to be "guns"
461
462 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
463 {
464 if ( (GCM->getItem(pSoldier->inv[bLoop].usItem)->getItemClass() & usItemClass) && !(pSoldier->inv[bLoop].fFlags & OBJECT_AI_UNUSABLE) && (pSoldier->inv[bLoop].bStatus[0] >= USABLE ) )
465 {
466 if ( usItemClass == IC_GUN && EXPLOSIVE_GUN( pSoldier->inv[bLoop].usItem ) )
467 {
468 continue;
469 }
470 return( bLoop );
471 }
472 }
473 return( NO_SLOT );
474 }
475
FindAIUsableObjClassWithin(const SOLDIERTYPE * pSoldier,UINT32 usItemClass,INT8 bLower,INT8 bUpper)476 INT8 FindAIUsableObjClassWithin( const SOLDIERTYPE * pSoldier, UINT32 usItemClass, INT8 bLower, INT8 bUpper )
477 {
478 INT8 bLoop;
479
480 // This is for the AI only so:
481 // Do not consider tank cannons or rocket launchers to be "guns"
482
483 for (bLoop = bLower; bLoop <= bUpper; bLoop++)
484 {
485 if ( (GCM->getItem(pSoldier->inv[bLoop].usItem)->getItemClass() & usItemClass) && !(pSoldier->inv[bLoop].fFlags & OBJECT_AI_UNUSABLE) && (pSoldier->inv[bLoop].bStatus[0] >= USABLE ) )
486 {
487 if ( usItemClass == IC_GUN && EXPLOSIVE_GUN( pSoldier->inv[bLoop].usItem ) )
488 {
489 continue;
490 }
491 return( bLoop );
492 }
493 }
494 return( NO_SLOT );
495 }
496
FindEmptySlotWithin(const SOLDIERTYPE * pSoldier,INT8 bLower,INT8 bUpper)497 INT8 FindEmptySlotWithin( const SOLDIERTYPE * pSoldier, INT8 bLower, INT8 bUpper )
498 {
499 INT8 bLoop;
500
501 for (bLoop = bLower; bLoop <= bUpper; bLoop++)
502 {
503 if (pSoldier->inv[bLoop].usItem == 0)
504 {
505 if (bLoop == SECONDHANDPOS && GCM->getItem(pSoldier->inv[HANDPOS].usItem)->isTwoHanded())
506 {
507 continue;
508 }
509 else
510 {
511 return( bLoop );
512 }
513 }
514 }
515 return( ITEM_NOT_FOUND );
516 }
517
518
GLGrenadeInSlot(const SOLDIERTYPE * pSoldier,INT8 bSlot)519 static BOOLEAN GLGrenadeInSlot(const SOLDIERTYPE* pSoldier, INT8 bSlot)
520 {
521 switch (pSoldier->inv[bSlot].usItem)
522 {
523 case GL_HE_GRENADE:
524 case GL_TEARGAS_GRENADE:
525 case GL_STUN_GRENADE:
526 case GL_SMOKE_GRENADE:
527 return(TRUE);
528 default:
529 return(FALSE);
530 }
531 }
532
533 // for grenade launchers
FindGLGrenade(const SOLDIERTYPE * pSoldier)534 INT8 FindGLGrenade( const SOLDIERTYPE * pSoldier )
535 {
536 INT8 bLoop;
537
538 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
539 {
540 if (GLGrenadeInSlot( pSoldier, bLoop ))
541 {
542 return( bLoop );
543 }
544 }
545 return( NO_SLOT );
546 }
547
FindThrowableGrenade(const SOLDIERTYPE * pSoldier)548 INT8 FindThrowableGrenade( const SOLDIERTYPE * pSoldier )
549 {
550 INT8 bLoop;
551 BOOLEAN fCheckForFlares = FALSE;
552
553 // JA2Gold: give some priority to looking for flares when at night
554 // this is AI only so we can put in some customization for night
555 if (GetTimeOfDayAmbientLightLevel() == NORMAL_LIGHTLEVEL_NIGHT)
556 {
557 if (pSoldier->bLife > (pSoldier->bLifeMax / 2))
558 {
559 fCheckForFlares = TRUE;
560 }
561 }
562 if (fCheckForFlares)
563 {
564 // Do a priority check for flares first
565 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
566 {
567 if (pSoldier->inv[ bLoop ].usItem == BREAK_LIGHT)
568 {
569 return( bLoop );
570 }
571 }
572 }
573
574 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
575 {
576 if ( (GCM->getItem(pSoldier->inv[ bLoop ].usItem)->isGrenade()) && !GLGrenadeInSlot( pSoldier, bLoop ) )
577 {
578 return( bLoop );
579 }
580 }
581 return( NO_SLOT );
582 }
583
584
FindAttachment(const OBJECTTYPE * pObj,UINT16 usItem)585 INT8 FindAttachment(const OBJECTTYPE* pObj, UINT16 usItem)
586 {
587 INT8 bLoop;
588
589 for (bLoop = 0; bLoop < MAX_ATTACHMENTS; bLoop++)
590 {
591 if (pObj->usAttachItem[bLoop] == usItem)
592 {
593 return( bLoop );
594 }
595 }
596 return( ITEM_NOT_FOUND );
597 }
598
599
FindAttachmentByClass(OBJECTTYPE const * const pObj,UINT32 const uiItemClass)600 INT8 FindAttachmentByClass(OBJECTTYPE const* const pObj, UINT32 const uiItemClass)
601 {
602 INT8 bLoop;
603
604 for (bLoop = 0; bLoop < MAX_ATTACHMENTS; bLoop++)
605 {
606 if (GCM->getItem(pObj->usAttachItem[bLoop])->getItemClass() == uiItemClass)
607 {
608 return( bLoop );
609 }
610 }
611 return( ITEM_NOT_FOUND );
612 }
613
FindLaunchable(const SOLDIERTYPE * pSoldier,UINT16 usWeapon)614 INT8 FindLaunchable( const SOLDIERTYPE * pSoldier, UINT16 usWeapon )
615 {
616 INT8 bLoop;
617
618 for (bLoop = 0; bLoop < NUM_INV_SLOTS; bLoop++)
619 {
620 if ( ValidLaunchable( pSoldier->inv[ bLoop ].usItem , usWeapon ) )
621 {
622 return( bLoop );
623 }
624 }
625 return( ITEM_NOT_FOUND );
626 }
627
628
FindLaunchableAttachment(const OBJECTTYPE * const pObj,const UINT16 usWeapon)629 INT8 FindLaunchableAttachment(const OBJECTTYPE* const pObj, const UINT16 usWeapon)
630 {
631 INT8 bLoop;
632
633 for ( bLoop = 0; bLoop < MAX_ATTACHMENTS; bLoop++ )
634 {
635 if ( pObj->usAttachItem[ bLoop ] != NOTHING && ValidLaunchable( pObj->usAttachItem[ bLoop ], usWeapon ) )
636 {
637 return( bLoop );
638 }
639 }
640
641 return( ITEM_NOT_FOUND );
642 }
643
644
ItemHasAttachments(OBJECTTYPE const & o)645 bool ItemHasAttachments(OBJECTTYPE const& o)
646 {
647 return o.usAttachItem[0] != NOTHING ||
648 o.usAttachItem[1] != NOTHING ||
649 o.usAttachItem[2] != NOTHING ||
650 o.usAttachItem[3] != NOTHING;
651 }
652
653
654 // Determine if it is possible to add this attachment to the CLASS of this item
655 // (i.e. to any item in the class)
ValidAttachmentClass(UINT16 usAttachment,UINT16 usItem)656 static BOOLEAN ValidAttachmentClass(UINT16 usAttachment, UINT16 usItem)
657 {
658 for (auto const& ai : AttachmentInfo)
659 {
660 // see comment for AttachmentInfo array for why we skip IC_NONE
661 if (ai.uiItemClass == IC_NONE) continue;
662
663 if (ai.usItem == usAttachment && ai.uiItemClass == GCM->getItem(usItem)->getItemClass())
664 {
665 return TRUE;
666 }
667 }
668 return FALSE;
669 }
670
671
GetAttachmentInfo(const UINT16 usItem)672 static const AttachmentInfoStruct* GetAttachmentInfo(const UINT16 usItem)
673 {
674 for (auto const& ai : AttachmentInfo)
675 {
676 if (ai.usItem == usItem) return &ai;
677 }
678 return NULL;
679 }
680
681
ValidAttachment(UINT16 const attachment,UINT16 const item)682 bool ValidAttachment(UINT16 const attachment, UINT16 const item)
683 {
684 const ItemModel *itemModel = GCM->getItem(item);
685 if (itemModel && itemModel->canBeAttached(attachment))
686 {
687 return true;
688 }
689
690 {
691 auto const it = g_attachments.find(attachment);
692 if (it != g_attachments.end() && it->second.count(item) == 1) return true;
693 }
694
695 if (gamepolicy(extra_attachments))
696 {
697 auto const it = g_attachments_mod.find(attachment);
698 if (it != g_attachments_mod.end() && (*it->second).count(item) == 1) return true;
699 }
700
701 return false;
702 }
703
704
ValidItemAttachment(const OBJECTTYPE * const pObj,const UINT16 usAttachment,const BOOLEAN fAttemptingAttachment)705 BOOLEAN ValidItemAttachment(const OBJECTTYPE* const pObj, const UINT16 usAttachment, const BOOLEAN fAttemptingAttachment)
706 {
707 BOOLEAN fSameItem = FALSE, fSimilarItems = FALSE;
708 UINT16 usSimilarItem = NOTHING;
709
710 if ( !ValidAttachment( usAttachment, pObj->usItem ) )
711 {
712 // check for an underslung grenade launcher attached to the gun
713 if ( (FindAttachment( pObj, UNDER_GLAUNCHER ) != ITEM_NOT_FOUND) && ValidLaunchable( usAttachment, UNDER_GLAUNCHER ) )
714 {
715 return ( TRUE );
716 }
717 else
718 {
719 if ( fAttemptingAttachment && ValidAttachmentClass( usAttachment, pObj->usItem ) )
720 {
721 // well, maybe the player thought he could
722 ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, st_format_printf(g_langRes->Message[STR_CANT_ATTACH], ItemNames[usAttachment], ItemNames[pObj->usItem]));
723 }
724
725 return( FALSE );
726 }
727 }
728 // special conditions go here
729 // can't have two of the same attachment on an item
730 if (FindAttachment( pObj, usAttachment ) != ITEM_NOT_FOUND)
731 {
732 fSameItem = TRUE;
733 }
734
735 // special code for items which won't attach if X is present
736 switch( usAttachment )
737 {
738 case BIPOD:
739 if ( FindAttachment( pObj, UNDER_GLAUNCHER) != ITEM_NOT_FOUND )
740 {
741 fSimilarItems = TRUE;
742 usSimilarItem = UNDER_GLAUNCHER;
743 }
744 break;
745 case UNDER_GLAUNCHER:
746 if ( FindAttachment( pObj, BIPOD ) != ITEM_NOT_FOUND )
747 {
748 fSimilarItems = TRUE;
749 usSimilarItem = BIPOD;
750 }
751 break;
752 case DETONATOR:
753 if( FindAttachment( pObj, REMDETONATOR ) != ITEM_NOT_FOUND )
754 {
755 fSameItem = TRUE;
756 }
757 break;
758 case REMDETONATOR:
759 if( FindAttachment( pObj, DETONATOR ) != ITEM_NOT_FOUND )
760 {
761 fSameItem = TRUE;
762 }
763 break;
764 }
765
766 if (fAttemptingAttachment)
767 {
768 if (fSameItem)
769 {
770 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, g_langRes->Message[ STR_ATTACHMENT_ALREADY ] );
771 return( FALSE );
772 }
773 else if (fSimilarItems)
774 {
775 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, st_format_printf(g_langRes->Message[ STR_CANT_USE_TWO_ITEMS ], ItemNames[ usSimilarItem ], ItemNames[ usAttachment ]) );
776 return( FALSE );
777 }
778 }
779
780 return( TRUE );
781 }
782
783 //Determines if it is possible to equip this weapon with this ammo.
ValidAmmoType(UINT16 usItem,UINT16 usAmmoType)784 bool ValidAmmoType( UINT16 usItem, UINT16 usAmmoType )
785 {
786 if (GCM->getItem(usItem)->getItemClass() == IC_GUN && GCM->getItem(usAmmoType)->getItemClass() == IC_AMMO)
787 {
788 return GCM->getWeapon(usItem)->matches(GCM->getItem(usAmmoType)->asAmmo()->calibre);
789 }
790 return false;
791 }
792
793
CompatibleFaceItem(UINT16 const item1,UINT16 const item2)794 BOOLEAN CompatibleFaceItem(UINT16 const item1, UINT16 const item2)
795 {
796 if (item2 == NOTHING) return TRUE;
797 for (auto const& i : CompatibleFaceItems)
798 {
799 if (i[0] == item1 && i[1] == item2) return TRUE;
800 if (i[0] == item2 && i[1] == item1) return TRUE;
801 }
802 return FALSE;
803 }
804
805
ValidLaunchable(UINT16 usLaunchable,UINT16 usItem)806 BOOLEAN ValidLaunchable( UINT16 usLaunchable, UINT16 usItem )
807 {
808 auto const it = Launchable.find(usLaunchable);
809 if (it != Launchable.end())
810 {
811 return it->second.count(usItem) == 1;
812 }
813
814 return FALSE;
815 }
816
817
GetLauncherFromLaunchable(UINT16 usLaunchable)818 UINT16 GetLauncherFromLaunchable( UINT16 usLaunchable )
819 {
820 auto const it = Launchable.find(usLaunchable);
821 return it != Launchable.end() ? *it->second.begin() : NOTHING;
822 }
823
824
EvaluateValidMerge(UINT16 const usMerge,UINT16 const usItem,UINT16 * const pusResult,UINT8 * const pubType)825 static BOOLEAN EvaluateValidMerge(UINT16 const usMerge, UINT16 const usItem, UINT16* const pusResult, UINT8* const pubType)
826 {
827 // NB "usMerge" is the object being merged with (e.g. compound 18)
828 // "usItem" is the item being merged "onto" (e.g. kevlar vest)
829
830 if (usMerge == usItem && GCM->getItem(usItem)->getItemClass() == IC_AMMO)
831 {
832 *pusResult = usItem;
833 *pubType = COMBINE_POINTS;
834 return TRUE;
835 }
836
837 for (auto const& m : Merge)
838 {
839 if (m.item1 != usMerge) continue;
840 if (m.item2 != usItem) continue;
841
842 *pusResult = m.result;
843 *pubType = m.action;
844 return TRUE;
845 }
846
847 return FALSE;
848 }
849
ValidMerge(UINT16 usMerge,UINT16 usItem)850 BOOLEAN ValidMerge( UINT16 usMerge, UINT16 usItem )
851 {
852 UINT16 usIgnoreResult;
853 UINT8 ubIgnoreType;
854 return( EvaluateValidMerge( usMerge, usItem, &usIgnoreResult, &ubIgnoreType ) );
855 }
856
857
CalculateObjectWeight(OBJECTTYPE const * const o)858 UINT8 CalculateObjectWeight(OBJECTTYPE const* const o)
859 {
860 const ItemModel *item = GCM->getItem(o->usItem);
861 UINT16 weight = item->getWeight(); // Start with base weight
862
863 if (item->getPerPocket() <= 1)
864 {
865 // Account for any attachments
866 FOR_EACH(UINT16 const, i, o->usAttachItem)
867 {
868 if (*i == NOTHING) continue;
869 weight += GCM->getItem(*i)->getWeight();
870 }
871
872 if (GCM->getItem(o->usItem)->getItemClass() == IC_GUN && o->ubGunShotsLeft > 0)
873 { // Add in weight of ammo
874 weight += GCM->getItem(o->usGunAmmoItem)->getWeight();
875 }
876 }
877
878 // Make sure it really fits into that UINT8, in case we ever add anything real
879 // heavy with attachments/ammo
880 Assert(weight <= 255);
881 return weight;
882 }
883
884
CalculateCarriedWeight(SOLDIERTYPE const * const s)885 UINT32 CalculateCarriedWeight(SOLDIERTYPE const* const s)
886 {
887 UINT32 total_weight = 0;
888 CFOR_EACH_SOLDIER_INV_SLOT(i, *s)
889 {
890 UINT16 weight = i->ubWeight;
891 if (GCM->getItem(i->usItem)->getPerPocket() > 1)
892 {
893 // Account for # of items
894 weight *= i->ubNumberOfObjects;
895 }
896 total_weight += weight;
897 }
898
899 UINT8 strength_for_carrying = EffectiveStrength(s);
900 if (strength_for_carrying > 80)
901 {
902 strength_for_carrying += strength_for_carrying - 80;
903 }
904
905 // For now, assume soldiers can carry 1/2 their strength in kg without
906 // penalty. Instead of multiplying by 100 for percent, and then dividing by 10
907 // to account for weight units being in 10ths of kilos, not kilos... we just
908 // start with 10 instead of 100!
909 UINT32 const percent = 10 * total_weight / (strength_for_carrying / 2);
910 return percent;
911 }
912
913
DeleteObj(OBJECTTYPE * pObj)914 void DeleteObj(OBJECTTYPE * pObj )
915 {
916 *pObj = OBJECTTYPE{};
917 }
918
919
SwapObjs(OBJECTTYPE * pObj1,OBJECTTYPE * pObj2)920 void SwapObjs( OBJECTTYPE * pObj1, OBJECTTYPE * pObj2 )
921 {
922 OBJECTTYPE Temp = *pObj1;
923 *pObj1 = *pObj2;
924 *pObj2 = Temp;
925 }
926
RemoveObjFrom(OBJECTTYPE * pObj,UINT8 ubRemoveIndex)927 void RemoveObjFrom( OBJECTTYPE * pObj, UINT8 ubRemoveIndex )
928 {
929 // remove 1 object from an OBJECTTYPE, starting at index bRemoveIndex
930 UINT8 ubLoop;
931
932 if (pObj->ubNumberOfObjects < ubRemoveIndex)
933 {
934 // invalid index!
935 return;
936 }
937 else if (pObj->ubNumberOfObjects == 1)
938 {
939 // delete!
940 DeleteObj( pObj );
941 }
942 else
943 {
944 // shift down all the values that should be down
945 for (ubLoop = ubRemoveIndex + 1; ubLoop < pObj->ubNumberOfObjects; ubLoop++)
946 {
947 pObj->bStatus[ubLoop - 1] = pObj->bStatus[ubLoop];
948 }
949 // and set the upper value to 0
950 pObj->bStatus[pObj->ubNumberOfObjects - 1] = 0;
951 // make the number of objects recorded match the array
952 pObj->ubNumberOfObjects--;
953 }
954 }
955
RemoveObjs(OBJECTTYPE * pObj,UINT8 ubNumberToRemove)956 void RemoveObjs( OBJECTTYPE * pObj, UINT8 ubNumberToRemove )
957 {
958 // remove a certain number of objects from an OBJECTTYPE, starting at index 0
959 UINT8 ubLoop;
960
961 if (ubNumberToRemove == 0)
962 {
963 return;
964 }
965 if (ubNumberToRemove >= pObj->ubNumberOfObjects)
966 {
967 // delete!
968 DeleteObj( pObj );
969 }
970 else
971 {
972 for (ubLoop = 0; ubLoop < ubNumberToRemove; ubLoop++)
973 {
974 RemoveObjFrom( pObj, 0 );
975 }
976 pObj->ubWeight = CalculateObjectWeight( pObj );
977 }
978 }
979
GetObjFrom(OBJECTTYPE * pObj,UINT8 ubGetIndex,OBJECTTYPE * pDest)980 void GetObjFrom( OBJECTTYPE * pObj, UINT8 ubGetIndex, OBJECTTYPE * pDest )
981 {
982 if (!pDest || ubGetIndex >= pObj->ubNumberOfObjects)
983 {
984 return;
985 }
986 if (pObj->ubNumberOfObjects == 1)
987 {
988 *pDest = *pObj;
989 DeleteObj( pObj );
990 }
991 else
992 {
993 pDest->usItem = pObj->usItem;
994 pDest->bStatus[0] = pObj->bStatus[ubGetIndex];
995 pDest->ubNumberOfObjects = 1;
996 pDest->ubWeight = CalculateObjectWeight( pDest );
997 RemoveObjFrom( pObj, ubGetIndex );
998 pObj->ubWeight = CalculateObjectWeight( pObj );
999 }
1000 }
1001
1002
DamageObj(OBJECTTYPE * pObj,INT8 bAmount)1003 void DamageObj( OBJECTTYPE * pObj, INT8 bAmount )
1004 {
1005 if (bAmount >= pObj->bStatus[0])
1006 {
1007 pObj->bStatus[0] = 1;
1008 }
1009 else
1010 {
1011 pObj->bStatus[0] -= bAmount;
1012 }
1013 }
1014
1015
StackObjs(OBJECTTYPE * pSourceObj,OBJECTTYPE * pTargetObj,UINT8 ubNumberToCopy)1016 void StackObjs(OBJECTTYPE* pSourceObj, OBJECTTYPE* pTargetObj, UINT8 ubNumberToCopy)
1017 {
1018 UINT8 ubLoop;
1019
1020 // copy over N status values
1021 for (ubLoop = 0; ubLoop < ubNumberToCopy; ubLoop++)
1022 {
1023 pTargetObj->bStatus[ubLoop + pTargetObj->ubNumberOfObjects] = pSourceObj->bStatus[ubLoop ];
1024 }
1025
1026 // now in the source object, move the rest down N places
1027 for (ubLoop = ubNumberToCopy; ubLoop < pSourceObj->ubNumberOfObjects; ubLoop++)
1028 {
1029 pSourceObj->bStatus[ubLoop - ubNumberToCopy] = pSourceObj->bStatus[ubLoop];
1030 }
1031
1032 pTargetObj->ubNumberOfObjects += ubNumberToCopy;
1033 RemoveObjs( pSourceObj, ubNumberToCopy );
1034 pSourceObj->ubWeight = CalculateObjectWeight( pSourceObj );
1035 pTargetObj->ubWeight = CalculateObjectWeight( pTargetObj );
1036 }
1037
1038
CleanUpStack(OBJECTTYPE * const o,OBJECTTYPE * const cursor_o)1039 void CleanUpStack(OBJECTTYPE* const o, OBJECTTYPE* const cursor_o)
1040 {
1041 const ItemModel * item = GCM->getItem(o->usItem);
1042 if (!(item->isAmmo()) &&
1043 !(item->isKit()) &&
1044 !(item->isMedkit()))
1045 {
1046 return;
1047 }
1048
1049 INT8 const max_points = item->isAmmo() ?
1050 item->asAmmo()->capacity : 100;
1051
1052 if (cursor_o && cursor_o->usItem == o->usItem)
1053 {
1054 for (INT8 i = (INT8)cursor_o->ubNumberOfObjects - 1; i >= 0; --i)
1055 {
1056 INT8& src_status = cursor_o->bStatus[i];
1057 if (src_status <= 0) continue;
1058
1059 // take the points here and distribute over the lower #d items
1060 for (INT8 k = o->ubNumberOfObjects - 1; k >= 0; --k)
1061 {
1062 INT8& dst_status = o->bStatus[k];
1063 if (dst_status >= max_points) continue;
1064
1065 INT8 const points_to_move = MIN(max_points - dst_status, src_status);
1066 dst_status += points_to_move;
1067 src_status -= points_to_move;
1068 if (src_status != 0) continue;
1069
1070 // done!
1071 --cursor_o->ubNumberOfObjects;
1072 break;
1073 }
1074 }
1075 }
1076
1077 for (INT8 i = (INT8)o->ubNumberOfObjects - 1; i >= 0; --i)
1078 {
1079 INT8& src_status = o->bStatus[i];
1080 if (src_status <= 0) continue;
1081
1082 // take the points here and distribute over the lower #d items
1083 for (INT8 k = i - 1; k >= 0; --k)
1084 {
1085 INT8& dst_status = o->bStatus[k];
1086 if (dst_status >= max_points) continue;
1087
1088 INT8 const points_to_move = MIN(max_points - dst_status, src_status);
1089 dst_status += points_to_move;
1090 src_status -= points_to_move;
1091 if (src_status != 0) continue;
1092
1093 // done!
1094 --o->ubNumberOfObjects;
1095 break;
1096 }
1097 }
1098 }
1099
1100
PlaceObjectAtObjectIndex(OBJECTTYPE * pSourceObj,OBJECTTYPE * pTargetObj,UINT8 ubIndex)1101 BOOLEAN PlaceObjectAtObjectIndex( OBJECTTYPE * pSourceObj, OBJECTTYPE * pTargetObj, UINT8 ubIndex )
1102 {
1103 INT8 bTemp;
1104
1105 if (pSourceObj->usItem != pTargetObj->usItem)
1106 {
1107 return( TRUE );
1108 }
1109 if (ubIndex < pTargetObj->ubNumberOfObjects)
1110 {
1111 // swap
1112 bTemp = pSourceObj->bStatus[0];
1113 pSourceObj->bStatus[0] = pTargetObj->bStatus[ubIndex];
1114 pTargetObj->bStatus[ubIndex] = bTemp;
1115 return( TRUE );
1116 }
1117 else
1118 {
1119 // add to end
1120 StackObjs( pSourceObj, pTargetObj, 1 );
1121 return( FALSE );
1122 }
1123 }
1124
1125 #define RELOAD_NONE 0
1126 #define RELOAD_PLACE 1
1127 #define RELOAD_SWAP 2
1128 #define RELOAD_TOPOFF 3
1129 #define RELOAD_AUTOPLACE_OLD 4
1130
ReloadGun(SOLDIERTYPE * pSoldier,OBJECTTYPE * pGun,OBJECTTYPE * pAmmo)1131 BOOLEAN ReloadGun( SOLDIERTYPE * pSoldier, OBJECTTYPE * pGun, OBJECTTYPE * pAmmo )
1132 {
1133 OBJECTTYPE OldAmmo;
1134 UINT8 ubBulletsToMove;
1135 BOOLEAN fSameAmmoType;
1136 BOOLEAN fSameMagazineSize;
1137 BOOLEAN fReloadingWithStack;
1138 BOOLEAN fEmptyGun;
1139 INT8 bReloadType;
1140 UINT16 usNewAmmoItem;
1141
1142 if (pGun->usItem == ROCKET_LAUNCHER) return( FALSE ); // IC_GUN but uses no ammo (LAW)
1143
1144 INT8 bAPs = 0; // XXX HACK000E
1145 if (gTacticalStatus.uiFlags & INCOMBAT)
1146 {
1147 bAPs = GetAPsToReloadGunWithAmmo( pGun, pAmmo );
1148 if ( !EnoughPoints( pSoldier, bAPs, 0,TRUE ) )
1149 {
1150 return( FALSE );
1151 }
1152
1153 }
1154
1155 if ( GCM->getItem(pGun->usItem)->getItemClass() == IC_LAUNCHER || pGun->usItem == TANK_CANNON )
1156 {
1157 if (!AttachObject(pSoldier, pGun, pAmmo))
1158 {
1159 // abort
1160 return( FALSE );
1161 }
1162 }
1163 else
1164 {
1165 fEmptyGun = (pGun->ubGunShotsLeft == 0);
1166 fReloadingWithStack = (pAmmo->ubNumberOfObjects > 1);
1167 fSameAmmoType = ( pGun->ubGunAmmoType == GCM->getItem(pAmmo->usItem)->asAmmo()->ammoType->index );
1168 fSameMagazineSize = ( GCM->getItem(pAmmo->usItem)->asAmmo()->capacity == GCM->getWeapon(pGun->usItem)->ubMagSize );
1169
1170 if (fEmptyGun)
1171 {
1172 bReloadType = RELOAD_PLACE;
1173 }
1174 else
1175 {
1176 // record old ammo
1177 OldAmmo = OBJECTTYPE{};
1178 OldAmmo.usItem = pGun->usGunAmmoItem;
1179 OldAmmo.ubNumberOfObjects = 1;
1180 OldAmmo.ubShotsLeft[0] = pGun->ubGunShotsLeft;
1181
1182 if (fSameMagazineSize)
1183 {
1184 if (fSameAmmoType)
1185 {
1186 if (gTacticalStatus.uiFlags & INCOMBAT)
1187 {
1188 bReloadType = RELOAD_SWAP;
1189 }
1190 else
1191 {
1192 bReloadType = RELOAD_TOPOFF;
1193 }
1194 }
1195 else
1196 {
1197 if (!fReloadingWithStack)
1198 {
1199 bReloadType = RELOAD_SWAP;
1200 }
1201 else
1202 {
1203 bReloadType = RELOAD_AUTOPLACE_OLD;
1204 }
1205 }
1206 }
1207 else // diff sized magazines
1208 {
1209 if (fSameAmmoType)
1210 {
1211 bReloadType = RELOAD_TOPOFF;
1212 }
1213 else
1214 {
1215 bReloadType = RELOAD_AUTOPLACE_OLD;
1216 }
1217 }
1218 }
1219
1220 if (fSameMagazineSize)
1221 {
1222 // record new ammo item for gun
1223 usNewAmmoItem = pAmmo->usItem;
1224
1225 if (bReloadType == RELOAD_TOPOFF)
1226 {
1227 ubBulletsToMove = __min( pAmmo->ubShotsLeft[0], GCM->getWeapon(pGun->usItem)->ubMagSize - pGun->ubGunShotsLeft );
1228 }
1229 else
1230 {
1231 ubBulletsToMove = pAmmo->ubShotsLeft[0];
1232 }
1233
1234 }
1235 else if (GCM->getItem(pAmmo->usItem)->asAmmo()->capacity > GCM->getWeapon(pGun->usItem)->ubMagSize)
1236 {
1237 usNewAmmoItem = pAmmo->usItem - 1;
1238 if (bReloadType == RELOAD_TOPOFF)
1239 {
1240 ubBulletsToMove = __min( pAmmo->ubShotsLeft[0], GCM->getWeapon(pGun->usItem)->ubMagSize - pGun->ubGunShotsLeft );
1241 }
1242 else
1243 {
1244 ubBulletsToMove = __min( pAmmo->ubShotsLeft[0], GCM->getWeapon(pGun->usItem)->ubMagSize );
1245 }
1246 }
1247 else // mag is smaller than weapon mag
1248 {
1249 usNewAmmoItem = pAmmo->usItem + 1;
1250 if (bReloadType == RELOAD_TOPOFF)
1251 {
1252 ubBulletsToMove = __min( pAmmo->ubShotsLeft[0], GCM->getWeapon(pGun->usItem)->ubMagSize - pGun->ubGunShotsLeft );
1253 }
1254 else
1255 {
1256 ubBulletsToMove = __min( pAmmo->ubShotsLeft[0], GCM->getWeapon(pGun->usItem)->ubMagSize );
1257 }
1258 }
1259
1260
1261 switch( bReloadType )
1262 {
1263
1264 case RELOAD_PLACE:
1265 pGun->ubGunShotsLeft = ubBulletsToMove;
1266 pGun->ubGunAmmoType = GCM->getItem(pAmmo->usItem)->asAmmo()->ammoType->index;
1267 pGun->usGunAmmoItem = usNewAmmoItem;
1268 break;
1269
1270 case RELOAD_SWAP:
1271 pGun->ubGunShotsLeft = ubBulletsToMove;
1272 pGun->ubGunAmmoType = GCM->getItem(pAmmo->usItem)->asAmmo()->ammoType->index;
1273 pGun->usGunAmmoItem = usNewAmmoItem;
1274 if (fReloadingWithStack)
1275 {
1276 // add to end of stack
1277 StackObjs( &OldAmmo, pAmmo, 1 );
1278 }
1279 else
1280 {
1281 // Copying the old ammo to the cursor in turnbased could screw up for the player
1282 // (suppose his inventory is full!)
1283
1284 if ((gTacticalStatus.uiFlags & INCOMBAT) && !EnoughPoints( pSoldier, (INT8) (bAPs + AP_PICKUP_ITEM), 0, FALSE))
1285 {
1286 // try autoplace
1287 if ( !AutoPlaceObject( pSoldier, &OldAmmo, FALSE ) )
1288 {
1289 // put it on the ground
1290 AddItemToPool(pSoldier->sGridNo, &OldAmmo, VISIBLE, pSoldier->bLevel, 0 , -1);
1291 }
1292 // delete the object now in the cursor
1293 DeleteObj( pAmmo );
1294 }
1295 else
1296 {
1297 // copy the old ammo to the cursor
1298 *pAmmo = OldAmmo;
1299 }
1300 }
1301 break;
1302 case RELOAD_AUTOPLACE_OLD:
1303 if ( !AutoPlaceObject( pSoldier, &OldAmmo, TRUE ) )
1304 {
1305 // error msg!
1306 return( FALSE );
1307 }
1308 // place first ammo in gun
1309 pGun->ubGunShotsLeft = ubBulletsToMove;
1310 pGun->ubGunAmmoType = GCM->getItem(pAmmo->usItem)->asAmmo()->ammoType->index;
1311 pGun->usGunAmmoItem = usNewAmmoItem;
1312
1313 break;
1314
1315 case RELOAD_TOPOFF:
1316 // ADD that many bullets to gun
1317 pGun->ubGunShotsLeft += ubBulletsToMove;
1318 break;
1319
1320 }
1321
1322 if ( ! ( bReloadType == RELOAD_SWAP && !fReloadingWithStack ) )
1323 {
1324 // remove # of bullets, delete 1 object if necessary
1325
1326 pAmmo->ubShotsLeft[0] -= ubBulletsToMove;
1327 if (pAmmo->ubShotsLeft[0] == 0)
1328 {
1329 RemoveObjs( pAmmo, 1 );
1330 }
1331
1332 }
1333 }
1334
1335 // OK, let's play a sound of reloading too...
1336 // If this guy is visible...
1337 if ( pSoldier->bVisible != -1 )
1338 {
1339 // Play some effects!
1340 SoundID const usReloadSound = GCM->getWeapon(pGun->usItem)->sReloadSound;
1341 if (usReloadSound != NO_SOUND && !IsAutoResolveActive())
1342 {
1343 PlayJA2Sample(usReloadSound, HIGHVOLUME, 1, MIDDLEPAN);
1344 }
1345 }
1346
1347 if (pSoldier->bTeam == OUR_TEAM)
1348 {
1349 // spit out a message if this is one of our folks reloading
1350 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(g_langRes->Message[STR_PLAYER_RELOADS], pSoldier->name) );
1351 }
1352
1353 DeductPoints( pSoldier, bAPs, 0 );
1354 pGun->ubWeight = CalculateObjectWeight( pGun );
1355
1356 if ( pGun->bGunAmmoStatus >= 0 )
1357 {
1358 // make sure gun ammo status is 100, if gun isn't jammed
1359 pGun->bGunAmmoStatus = 100;
1360 }
1361
1362 return( TRUE );
1363 }
1364
EmptyWeaponMagazine(OBJECTTYPE * pWeapon,OBJECTTYPE * pAmmo)1365 BOOLEAN EmptyWeaponMagazine( OBJECTTYPE * pWeapon, OBJECTTYPE *pAmmo )
1366 {
1367 CHECKF( pAmmo != NULL );
1368
1369 if ( pWeapon->ubGunShotsLeft > 0 )
1370 {
1371 // start by erasing ammo item, just in case...
1372 DeleteObj( pAmmo );
1373
1374 pAmmo->ubShotsLeft[0] = pWeapon->ubGunShotsLeft;
1375 pAmmo->usItem = pWeapon->usGunAmmoItem;
1376 pAmmo->ubNumberOfObjects = 1;
1377
1378 pWeapon->ubGunShotsLeft = 0;
1379 pWeapon->ubGunAmmoType = 0;
1380 //pWeapon->usGunAmmoItem = 0; // leaving the ammo item the same for auto-reloading purposes
1381
1382 // Play some effects!
1383 SoundID const usReloadSound = GCM->getWeapon(pWeapon->usItem)->sReloadSound;
1384 if (usReloadSound != NO_SOUND)
1385 {
1386 PlayJA2Sample(usReloadSound, HIGHVOLUME, 1, MIDDLEPAN);
1387 }
1388
1389 pWeapon->ubWeight = CalculateObjectWeight( pWeapon );
1390
1391 return( TRUE );
1392 }
1393 else
1394 {
1395 return( FALSE );
1396 }
1397 }
1398
1399
FindAmmo(const SOLDIERTYPE * s,const CalibreModel * calibre,UINT8 const mag_size,INT8 const exclude_slot)1400 INT8 FindAmmo(const SOLDIERTYPE* s, const CalibreModel * calibre, UINT8 const mag_size, INT8 const exclude_slot)
1401 {
1402 for (INT8 slot = HANDPOS; slot != NUM_INV_SLOTS; ++slot)
1403 {
1404 if (slot == exclude_slot) continue;
1405 const ItemModel * item = GCM->getItem(s->inv[slot].usItem);
1406 if (!item->isAmmo()) continue;
1407 const MagazineModel * m = item->asAmmo();
1408 if (m->calibre->index != calibre->index) continue;
1409 if (m->capacity != mag_size && mag_size != ANY_MAGSIZE) continue;
1410 return slot;
1411 }
1412 return NO_SLOT;
1413 }
1414
1415
FindAmmoToReload(const SOLDIERTYPE * pSoldier,INT8 bWeaponIn,INT8 bExcludeSlot)1416 INT8 FindAmmoToReload( const SOLDIERTYPE * pSoldier, INT8 bWeaponIn, INT8 bExcludeSlot )
1417 {
1418 const OBJECTTYPE *pObj;
1419 INT8 bSlot;
1420
1421 if (pSoldier == NULL)
1422 {
1423 return( NO_SLOT );
1424 }
1425 pObj = &(pSoldier->inv[bWeaponIn]);
1426 if ( GCM->getItem(pObj->usItem)->getItemClass() == IC_GUN && pObj->usItem != TANK_CANNON )
1427 {
1428 // look for same ammo as before
1429 bSlot = FindObjExcludingSlot( pSoldier, pObj->usGunAmmoItem, bExcludeSlot );
1430 if (bSlot != NO_SLOT)
1431 {
1432 // reload using this ammo!
1433 return( bSlot );
1434 }
1435 // look for any ammo that matches which is of the same calibre and magazine size
1436 bSlot = FindAmmo( pSoldier, GCM->getWeapon(pObj->usItem)->calibre, GCM->getWeapon(pObj->usItem)->ubMagSize, bExcludeSlot );
1437 if (bSlot != NO_SLOT)
1438 {
1439 return( bSlot );
1440 }
1441 else
1442 {
1443 // look for any ammo that matches which is of the same calibre (different size okay)
1444 return( FindAmmo( pSoldier, GCM->getWeapon(pObj->usItem)->calibre, ANY_MAGSIZE, bExcludeSlot ) );
1445 }
1446 }
1447 else
1448 {
1449 switch( pObj->usItem )
1450 {
1451 case MORTAR:
1452 return( FindObj( pSoldier, MORTAR_SHELL ) );
1453 case TANK_CANNON:
1454 return( FindObj( pSoldier, TANK_SHELL ) );
1455 case GLAUNCHER:
1456 case UNDER_GLAUNCHER:
1457 return( FindObjInObjRange( pSoldier, GL_HE_GRENADE, GL_SMOKE_GRENADE ) );
1458 default:
1459 return( NO_SLOT );
1460 }
1461 }
1462 }
1463
AutoReload(SOLDIERTYPE * pSoldier)1464 BOOLEAN AutoReload( SOLDIERTYPE * pSoldier )
1465 {
1466 OBJECTTYPE *pObj;
1467 INT8 bSlot, bAPCost;
1468 BOOLEAN fRet;
1469
1470 CHECKF( pSoldier );
1471 pObj = &(pSoldier->inv[HANDPOS]);
1472
1473 if (GCM->getItem(pObj->usItem)->getItemClass() == IC_GUN || GCM->getItem(pObj->usItem)->getItemClass() == IC_LAUNCHER)
1474 {
1475 bSlot = FindAmmoToReload( pSoldier, HANDPOS, NO_SLOT );
1476 if (bSlot != NO_SLOT)
1477 {
1478 // reload using this ammo!
1479 fRet = ReloadGun( pSoldier, pObj, &(pSoldier->inv[bSlot]) );
1480 // if we are valid for two-pistol shooting (reloading) and we have enough APs still
1481 // then do a reload of both guns!
1482 if (fRet && IsValidSecondHandShotForReloadingPurposes(pSoldier))
1483 {
1484 pObj = &(pSoldier->inv[SECONDHANDPOS]);
1485 bSlot = FindAmmoToReload( pSoldier, SECONDHANDPOS, NO_SLOT );
1486 if (bSlot != NO_SLOT)
1487 {
1488 // ce would reload using this ammo!
1489 bAPCost = GetAPsToReloadGunWithAmmo( pObj, &(pSoldier->inv[bSlot] ) );
1490 if ( EnoughPoints( pSoldier, (INT16) bAPCost, 0, FALSE ) )
1491 {
1492 // reload the 2nd gun too
1493 fRet = ReloadGun( pSoldier, pObj, &(pSoldier->inv[bSlot]) );
1494 }
1495 else
1496 {
1497 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(g_langRes->Message[ STR_RELOAD_ONLY_ONE_GUN ], pSoldier->name) );
1498 }
1499 }
1500 }
1501
1502 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
1503 return( fRet );
1504 }
1505 }
1506
1507 // couldn't reload
1508 return( FALSE );
1509 }
1510
1511
GetAttachmentComboMerge(OBJECTTYPE const & o)1512 static ComboMergeInfoStruct const* GetAttachmentComboMerge(OBJECTTYPE const& o)
1513 {
1514 for (auto const& m : AttachmentComboMerge)
1515 {
1516 if (m.usItem == NOTHING) break;
1517 if (m.usItem != o.usItem) continue;
1518
1519 // Search for all the appropriate attachments
1520 FOR_EACH(UINT16 const, k, m.usAttachment)
1521 {
1522 UINT16 const attachment = *k;
1523 if (attachment == NOTHING) continue;
1524 if (FindAttachment(&o, attachment) == -1) return nullptr; // Didn't find something required
1525 }
1526
1527 return &m; // Found everything required
1528 }
1529
1530 return nullptr;
1531 }
1532
1533
PerformAttachmentComboMerge(OBJECTTYPE & o,ComboMergeInfoStruct const & m)1534 static void PerformAttachmentComboMerge(OBJECTTYPE& o, ComboMergeInfoStruct const& m)
1535 {
1536 // This object has been validated as available for an attachment combo merge.
1537 // - Find all attachments in list and destroy them
1538 // - Status of new object should be average of items including attachments
1539 // - Change object
1540 UINT32 total_status = o.bStatus[0];
1541 INT8 n_status_contributors = 1;
1542 FOR_EACH(UINT16 const, i, m.usAttachment)
1543 {
1544 if (*i == NOTHING) continue;
1545
1546 INT8 const attach_pos = FindAttachment(&o, *i);
1547 AssertMsg(attach_pos != -1, "Attachment combo merge couldn't find a necessary attachment");
1548
1549 total_status += o.bAttachStatus[attach_pos];
1550 ++n_status_contributors;
1551
1552 o.usAttachItem[attach_pos] = NOTHING;
1553 o.bAttachStatus[attach_pos] = 0;
1554 }
1555
1556 o.usItem = m.usResult;
1557 o.bStatus[0] = total_status / n_status_contributors;
1558 }
1559
1560
AttachObject(SOLDIERTYPE * const s,OBJECTTYPE * const pTargetObj,OBJECTTYPE * const pAttachment,UINT8 const ubIndexInStack)1561 bool AttachObject(SOLDIERTYPE* const s, OBJECTTYPE* const pTargetObj, OBJECTTYPE* const pAttachment, UINT8 const ubIndexInStack)
1562 {
1563 OBJECTTYPE& target = *pTargetObj;
1564 OBJECTTYPE& attachment = *pAttachment;
1565 bool const valid_launchable = ValidLaunchable(attachment.usItem, target.usItem);
1566 if (valid_launchable || ValidItemAttachment(&target, attachment.usItem, TRUE))
1567 {
1568 // find an attachment position...
1569 // second half of this 'if' is for attaching GL grenades to a gun
1570 INT8 attach_pos;
1571 if (valid_launchable || (GL_HE_GRENADE <= attachment.usItem && attachment.usItem <= GL_SMOKE_GRENADE))
1572 {
1573 // try replacing if possible
1574 attach_pos = FindAttachmentByClass(&target, GCM->getItem(attachment.usItem)->getItemClass());
1575 if (attach_pos != NO_SLOT && attachment.ubNumberOfObjects > 1)
1576 {
1577 // we can only do a swap if there is only 1 grenade being attached
1578 return false;
1579 }
1580 }
1581 else
1582 {
1583 // try replacing if possible
1584 attach_pos = FindAttachment(&target, attachment.usItem);
1585 }
1586
1587 if (attach_pos == NO_SLOT)
1588 {
1589 attach_pos = FindAttachment(&target, NOTHING);
1590 if (attach_pos == NO_SLOT) return false;
1591 }
1592
1593 AttachmentInfoStruct const* attach_info = 0;
1594 if (s)
1595 {
1596 attach_info = GetAttachmentInfo(attachment.usItem);
1597 // in-game (not behind the scenes) attachment
1598 if (attach_info && attach_info->bAttachmentSkillCheck != NO_CHECK)
1599 {
1600 INT32 const iCheckResult = SkillCheck(s, attach_info->bAttachmentSkillCheck, attach_info->bAttachmentSkillCheckMod);
1601 if (iCheckResult < 0)
1602 {
1603 // the attach failure damages both items
1604 DamageObj(&target, (INT8)-iCheckResult);
1605 DamageObj(&attachment, (INT8)-iCheckResult);
1606 // there should be a quote here!
1607 DoMercBattleSound(s, BATTLE_SOUND_CURSE1);
1608 if (gfInItemDescBox) DeleteItemDescriptionBox();
1609 return false;
1610 }
1611 }
1612
1613 if (ValidItemAttachment(&target, attachment.usItem, TRUE)) // not launchable
1614 {
1615 // Attachment sounds
1616 const ItemModel * tgt_item = GCM->getItem(target.usItem);
1617 SoundID const sound =
1618 tgt_item->isWeapon() ? ATTACH_TO_GUN :
1619 tgt_item->isArmour() ? ATTACH_CERAMIC_PLATES :
1620 tgt_item->isBomb() ? ATTACH_DETONATOR :
1621 NO_SOUND;
1622 if (sound != NO_SOUND) PlayLocationJA2Sample(s->sGridNo, sound, MIDVOLUME, 1);
1623 }
1624 }
1625
1626 UINT16 const temp_item = target.usAttachItem[attach_pos];
1627 UINT16 const temp_status = target.bAttachStatus[attach_pos];
1628
1629 target.usAttachItem[attach_pos] = attachment.usItem;
1630 target.bAttachStatus[attach_pos] = attachment.bStatus[0];
1631
1632 // Transfer any attachment (max 1) from the grenade launcher to the gun
1633 if (attachment.usItem == UNDER_GLAUNCHER &&
1634 attachment.usAttachItem[0] != NOTHING)
1635 {
1636 INT8 const second_attach_pos = FindAttachment(&target, NOTHING);
1637 if (second_attach_pos == ITEM_NOT_FOUND)
1638 { // Not enough room for all attachments - cancel!
1639 target.usAttachItem[attach_pos] = NOTHING;
1640 target.bAttachStatus[attach_pos] = 0;
1641 return false;
1642 }
1643 else
1644 {
1645 target.usAttachItem[second_attach_pos] = attachment.usAttachItem[0];
1646 target.bAttachStatus[second_attach_pos] = attachment.bAttachStatus[0];
1647 attachment.usAttachItem[0] = NOTHING;
1648 attachment.bAttachStatus[0] = 0;
1649 }
1650 }
1651
1652 if (temp_item != NOTHING)
1653 {
1654 // overwrite/swap!
1655 CreateItem(temp_item, temp_status, &attachment);
1656 }
1657 else
1658 {
1659 RemoveObjs(&attachment, 1);
1660 }
1661
1662 // Check for attachment merge combos here
1663 if (ComboMergeInfoStruct const* const m = GetAttachmentComboMerge(target))
1664 {
1665 PerformAttachmentComboMerge(target, *m);
1666 if (attach_info && attach_info->bAttachmentSkillCheckMod < 20)
1667 {
1668 StatChange(*s, MECHANAMT, 20 - attach_info->bAttachmentSkillCheckMod, FROM_SUCCESS);
1669 }
1670 }
1671
1672 target.ubWeight = CalculateObjectWeight(&target);
1673 return true;
1674 }
1675
1676 // check for merges
1677 UINT16 merge_result;
1678 UINT8 merge_kind;
1679 if (!EvaluateValidMerge(attachment.usItem, target.usItem, &merge_result, &merge_kind)) return false;
1680
1681 if (merge_kind != COMBINE_POINTS)
1682 {
1683 if (!EnoughPoints(s, AP_MERGE, 0, TRUE)) return false;
1684 DeductPoints(s, AP_MERGE, 0);
1685 }
1686
1687 switch (merge_kind)
1688 {
1689 case COMBINE_POINTS:
1690 {
1691 // transfer points...
1692 const ItemModel * tgt_item = GCM->getItem(target.usItem);
1693 UINT8 const limit = tgt_item->getItemClass() == IC_AMMO ?
1694 tgt_item->asAmmo()->capacity : 100;
1695
1696 // count down through # of attaching items and add to status of item in position 0
1697 for (INT8 bLoop = attachment.ubNumberOfObjects - 1; bLoop >= 0; --bLoop)
1698 {
1699 INT8& targetStatus = target.bStatus[ubIndexInStack];
1700 if (targetStatus + attachment.bStatus[bLoop] <= limit)
1701 { // Consume this one totally and continue
1702 targetStatus += attachment.bStatus[bLoop];
1703 RemoveObjFrom(&attachment, bLoop);
1704 // reset loop limit
1705 bLoop = attachment.ubNumberOfObjects; // add 1 to counteract the -1 from the loop
1706 }
1707 else
1708 { // Add part of this one and then we're done
1709 attachment.bStatus[bLoop] -= limit - targetStatus;
1710 targetStatus = limit;
1711 break;
1712 }
1713 }
1714 break;
1715 }
1716
1717 case DESTRUCTION:
1718 // The merge destroyed both items!
1719 DeleteObj(&target);
1720 DeleteObj(&attachment);
1721 DoMercBattleSound(s, BATTLE_SOUND_CURSE1);
1722 break;
1723
1724 case ELECTRONIC_MERGE:
1725 if (s)
1726 {
1727 INT32 const iCheckResult = SkillCheck(s, ATTACHING_SPECIAL_ELECTRONIC_ITEM_CHECK, -30);
1728 if (iCheckResult < 0)
1729 {
1730 DamageObj(&target, (INT8)-iCheckResult);
1731 DamageObj(&attachment, (INT8)-iCheckResult);
1732 DoMercBattleSound(s, BATTLE_SOUND_CURSE1);
1733 return false;
1734 }
1735 // grant experience!
1736 }
1737 goto default_merge;
1738
1739 case EXPLOSIVE:
1740 if (s)
1741 {
1742 // requires a skill check, and gives experience
1743 INT32 const iCheckResult = SkillCheck(s, ATTACHING_DETONATOR_CHECK, -30);
1744 if (iCheckResult < 0)
1745 {
1746 // could have a chance of detonation
1747 // for now, damage both objects
1748 DamageObj(&target, (INT8)-iCheckResult);
1749 DamageObj(&attachment, (INT8)-iCheckResult);
1750 DoMercBattleSound(s, BATTLE_SOUND_CURSE1);
1751 return false;
1752 }
1753 StatChange(*s, EXPLODEAMT, 25, FROM_SUCCESS);
1754 }
1755 goto default_merge;
1756
1757 default:
1758 default_merge:
1759 // the merge will combine the two items
1760 target.usItem = merge_result;
1761 if (merge_kind != TREAT_ARMOUR)
1762 {
1763 target.bStatus[0] = (target.bStatus[0] + attachment.bStatus[0]) / 2;
1764 }
1765 DeleteObj(&attachment);
1766 target.ubWeight = CalculateObjectWeight(&target);
1767 if (s && s->bTeam == OUR_TEAM)
1768 {
1769 DoMercBattleSound(s, BATTLE_SOUND_COOL1);
1770 }
1771 break;
1772 }
1773 return true;
1774 }
1775
1776
CanItemFitInPosition(SOLDIERTYPE * pSoldier,OBJECTTYPE * pObj,INT8 bPos,BOOLEAN fDoingPlacement)1777 BOOLEAN CanItemFitInPosition(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, INT8 bPos, BOOLEAN fDoingPlacement)
1778 {
1779 UINT8 ubSlotLimit;
1780 INT8 bNewPos;
1781
1782 switch( bPos )
1783 {
1784 case SECONDHANDPOS:
1785 if (GCM->getItem(pSoldier->inv[HANDPOS].usItem)->isTwoHanded())
1786 {
1787 return( FALSE );
1788 }
1789 break;
1790 case HANDPOS:
1791 if (GCM->getItem(pObj->usItem)->isTwoHanded())
1792 {
1793 if (pSoldier->inv[HANDPOS].usItem != NOTHING && pSoldier->inv[SECONDHANDPOS].usItem != NOTHING)
1794 {
1795 // two items in hands; try moving the second one so we can swap
1796 if (GCM->getItem(pSoldier->inv[SECONDHANDPOS].usItem)->getPerPocket() == 0)
1797 {
1798 bNewPos = FindEmptySlotWithin( pSoldier, BIGPOCK1POS, BIGPOCK4POS );
1799 }
1800 else
1801 {
1802 bNewPos = FindEmptySlotWithin( pSoldier, BIGPOCK1POS, SMALLPOCK8POS );
1803 }
1804 if (bNewPos == NO_SLOT)
1805 {
1806 // nowhere to put second item
1807 return( FALSE );
1808 }
1809
1810 if ( fDoingPlacement )
1811 {
1812 // otherwise move it.
1813 pSoldier->inv[bNewPos] = pSoldier->inv[SECONDHANDPOS];
1814 DeleteObj( &(pSoldier->inv[SECONDHANDPOS]) );
1815 }
1816 }
1817 }
1818 break;
1819 case VESTPOS:
1820 case HELMETPOS:
1821 case LEGPOS:
1822 if (GCM->getItem(pObj->usItem)->getItemClass() != IC_ARMOUR)
1823 {
1824 return( FALSE );
1825 }
1826 switch (bPos)
1827 {
1828 case VESTPOS:
1829 if (Armour[GCM->getItem(pObj->usItem)->getClassIndex()].ubArmourClass != ARMOURCLASS_VEST)
1830 {
1831 return( FALSE );
1832 }
1833 break;
1834 case HELMETPOS:
1835 if (Armour[GCM->getItem(pObj->usItem)->getClassIndex()].ubArmourClass != ARMOURCLASS_HELMET)
1836 {
1837 return( FALSE );
1838 }
1839 break;
1840 case LEGPOS:
1841 if (Armour[GCM->getItem(pObj->usItem)->getClassIndex()].ubArmourClass != ARMOURCLASS_LEGGINGS)
1842 {
1843 return( FALSE );
1844 }
1845 break;
1846 default:
1847 break;
1848 }
1849 break;
1850 case HEAD1POS:
1851 case HEAD2POS:
1852 if (GCM->getItem(pObj->usItem)->getItemClass() != IC_FACE)
1853 {
1854 return( FALSE );
1855 }
1856 default:
1857 break;
1858 }
1859
1860 ubSlotLimit = ItemSlotLimit( pObj->usItem, bPos );
1861 if (ubSlotLimit == 0 && bPos >= SMALLPOCK1POS )
1862 {
1863 // doesn't fit!
1864 return( FALSE );
1865 }
1866
1867 return( TRUE );
1868 }
1869
1870
DropObjIfThereIsRoom(SOLDIERTYPE * pSoldier,INT8 bPos,OBJECTTYPE * pObj)1871 static BOOLEAN DropObjIfThereIsRoom(SOLDIERTYPE* pSoldier, INT8 bPos, OBJECTTYPE* pObj)
1872 {
1873 // try autoplacing item in bSlot elsewhere, then do a placement
1874 BOOLEAN fAutoPlacedOld;
1875
1876 fAutoPlacedOld = AutoPlaceObject( pSoldier, &(pSoldier->inv[bPos]), FALSE );
1877 if ( fAutoPlacedOld )
1878 {
1879 return( PlaceObject( pSoldier, bPos, pObj ) );
1880 }
1881 else
1882 {
1883 return( FALSE );
1884 }
1885 }
1886
1887
CollectKey(SOLDIERTYPE const & s,OBJECTTYPE const & o)1888 static void CollectKey(SOLDIERTYPE const& s, OBJECTTYPE const& o)
1889 {
1890 if (!(s.uiStatusFlags & SOLDIER_PC)) return;
1891 KEY& k = KeyTable[o.ubKeyID];
1892 if (k.usDateFound != 0) return;
1893 k.usDateFound = GetWorldDay();
1894 k.usSectorFound = SECTOR(s.sSectorX, s.sSectorY);
1895 }
1896
1897
PlaceObject(SOLDIERTYPE * pSoldier,INT8 bPos,OBJECTTYPE * pObj)1898 BOOLEAN PlaceObject( SOLDIERTYPE * pSoldier, INT8 bPos, OBJECTTYPE * pObj )
1899 {
1900 // returns object to have in hand after placement... same as original in the
1901 // case of error
1902
1903 UINT8 ubSlotLimit, ubNumberToDrop, ubLoop;
1904 OBJECTTYPE *pInSlot;
1905 BOOLEAN fObjectWasRobotRemote = FALSE;
1906
1907 if ( pObj->usItem == ROBOT_REMOTE_CONTROL )
1908 {
1909 fObjectWasRobotRemote = TRUE;
1910 }
1911
1912 if ( !CanItemFitInPosition( pSoldier, pObj, bPos, TRUE ) )
1913 {
1914 return( FALSE );
1915 }
1916
1917 // If the position is either head slot, then the item must be IC_FACE (checked in
1918 // CanItemFitInPosition).
1919 if ( bPos == HEAD1POS )
1920 {
1921 if ( !CompatibleFaceItem( pObj->usItem, pSoldier->inv[ HEAD2POS ].usItem ) )
1922 {
1923 ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, st_format_printf(g_langRes->Message[STR_CANT_USE_TWO_ITEMS],
1924 ItemNames[pObj->usItem], ItemNames[pSoldier->inv[HEAD2POS].usItem]));
1925 return( FALSE );
1926 }
1927 }
1928 else if ( bPos == HEAD2POS )
1929 {
1930 if ( !CompatibleFaceItem( pObj->usItem, pSoldier->inv[ HEAD1POS ].usItem ) )
1931 {
1932 ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, st_format_printf(g_langRes->Message[STR_CANT_USE_TWO_ITEMS],
1933 ItemNames[pObj->usItem], ItemNames[pSoldier->inv[HEAD1POS].usItem]));
1934 return( FALSE );
1935 }
1936 }
1937
1938 if (GCM->getItem(pObj->usItem)->getItemClass() == IC_KEY) CollectKey(*pSoldier, *pObj);
1939
1940 ubSlotLimit = ItemSlotLimit( pObj->usItem, bPos );
1941
1942 pInSlot = &(pSoldier->inv[bPos]);
1943
1944 if (pInSlot->ubNumberOfObjects == 0)
1945 {
1946 // placement in an empty slot
1947 ubNumberToDrop = pObj->ubNumberOfObjects;
1948
1949 if (ubNumberToDrop > __max( ubSlotLimit, 1 ) )
1950 {
1951 // drop as many as possible into pocket
1952 ubNumberToDrop = __max( ubSlotLimit, 1 );
1953 }
1954
1955 // could be wrong type of object for slot... need to check...
1956 // but assuming it isn't
1957 *pInSlot = *pObj;
1958
1959 if (ubNumberToDrop != pObj->ubNumberOfObjects)
1960 {
1961 // in the InSlot copy, zero out all the objects we didn't drop
1962 for (ubLoop = ubNumberToDrop; ubLoop < pObj->ubNumberOfObjects; ubLoop++)
1963 {
1964 pInSlot->bStatus[ubLoop] = 0;
1965 }
1966 }
1967 pInSlot->ubNumberOfObjects = ubNumberToDrop;
1968
1969 // remove a like number of objects from pObj
1970 RemoveObjs( pObj, ubNumberToDrop );
1971 if (pObj->ubNumberOfObjects == 0)
1972 {
1973 // dropped everything
1974 if (bPos == HANDPOS && GCM->getItem(pInSlot->usItem)->isTwoHanded())
1975 {
1976 // We just performed a successful drop of a two-handed object into the
1977 // main hand
1978 if (pSoldier->inv[SECONDHANDPOS].usItem != 0)
1979 {
1980 // swap what WAS in the second hand into the cursor
1981 SwapObjs( pObj, &(pSoldier->inv[SECONDHANDPOS]));
1982 }
1983 }
1984 }
1985 }
1986 else
1987 {
1988 // replacement/reloading/merging/stacking
1989 // keys have an additional check for key ID being the same
1990 if ((pObj->usItem == pInSlot->usItem) && (GCM->getItem(pObj->usItem)->getItemClass() != IC_KEY ||
1991 pObj->ubKeyID == pInSlot->ubKeyID))
1992 {
1993 if (GCM->getItem(pObj->usItem)->getItemClass() == IC_MONEY)
1994 {
1995
1996 UINT32 uiMoneyMax = MoneySlotLimit( bPos );
1997
1998 // always allow money to be combined!
1999 // IGNORE STATUS!
2000
2001 if (pInSlot->uiMoneyAmount + pObj->uiMoneyAmount > uiMoneyMax)
2002 {
2003 // remove X dollars
2004 pObj->uiMoneyAmount -= (uiMoneyMax - pInSlot->uiMoneyAmount);
2005 // set in slot to maximum
2006 pInSlot->uiMoneyAmount = uiMoneyMax;
2007 }
2008 else
2009 {
2010 pInSlot->uiMoneyAmount += pObj->uiMoneyAmount;
2011 DeleteObj( pObj );
2012 }
2013 }
2014 else if ( ubSlotLimit == 1 || (ubSlotLimit == 0 && bPos >= HANDPOS && bPos <= BIGPOCK4POS ) )
2015 {
2016 if (pObj->ubNumberOfObjects <= 1)
2017 {
2018 // swapping
2019 SwapObjs( pObj, pInSlot );
2020 }
2021 else
2022 {
2023 return( DropObjIfThereIsRoom( pSoldier, bPos, pObj ) );
2024 }
2025 }
2026 else if (ubSlotLimit == 0) // trying to drop into a small pocket
2027 {
2028 return( DropObjIfThereIsRoom( pSoldier, bPos, pObj ) );
2029 }
2030 else
2031 {
2032 // stacking
2033 ubNumberToDrop = ubSlotLimit - pInSlot->ubNumberOfObjects;
2034 if (ubNumberToDrop > pObj->ubNumberOfObjects)
2035 {
2036 ubNumberToDrop = pObj->ubNumberOfObjects;
2037 }
2038 StackObjs( pObj, pInSlot, ubNumberToDrop );
2039 }
2040 }
2041 else
2042 {
2043 // replacement, unless reloading...
2044 switch (GCM->getItem(pInSlot->usItem)->getItemClass())
2045 {
2046 case IC_GUN:
2047 if (GCM->getItem(pObj->usItem)->getItemClass() == IC_AMMO)
2048 {
2049 if (GCM->getWeapon(pInSlot->usItem)->matches(GCM->getItem(pObj->usItem)->asAmmo()->calibre))
2050 {
2051 // reload...
2052 return( ReloadGun( pSoldier, pInSlot, pObj ) );
2053 }
2054 else
2055 {
2056 // invalid ammo
2057 break;
2058 }
2059 }
2060 break;
2061 case IC_LAUNCHER:
2062 {
2063 if ( ValidLaunchable( pObj->usItem, pInSlot->usItem ) )
2064 {
2065 // reload...
2066 return( ReloadGun( pSoldier, pInSlot, pObj ) );
2067 }
2068 }
2069 break;
2070 }
2071
2072 if ( (GCM->getItem(pObj->usItem)->isTwoHanded()) && (bPos == HANDPOS) )
2073 {
2074 if (pSoldier->inv[SECONDHANDPOS].usItem != 0)
2075 {
2076 // both pockets have something in them, so we can't swap
2077 return( FALSE );
2078 }
2079 else
2080 {
2081 SwapObjs( pObj, pInSlot );
2082 }
2083 }
2084 else if (pObj->ubNumberOfObjects <= __max( ubSlotLimit, 1 ) )
2085 {
2086 // swapping
2087 SwapObjs( pObj, pInSlot );
2088 }
2089 else
2090 {
2091 return( DropObjIfThereIsRoom( pSoldier, bPos, pObj ) );
2092 }
2093
2094 }
2095 }
2096
2097 // ATE: Put this in to see if we should update the robot, if we were given a controller...
2098 if ( pSoldier->bTeam == OUR_TEAM && fObjectWasRobotRemote )
2099 {
2100 UpdateRobotControllerGivenController( pSoldier );
2101 }
2102
2103 return( TRUE );
2104 }
2105
2106
InternalAutoPlaceObject(SOLDIERTYPE * pSoldier,OBJECTTYPE * pObj,BOOLEAN fNewItem,INT8 bExcludeSlot)2107 static BOOLEAN InternalAutoPlaceObject(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, BOOLEAN fNewItem, INT8 bExcludeSlot)
2108 {
2109 INT8 bSlot;
2110 UINT8 ubPerSlot;
2111
2112 // statuses of extra objects would be 0 if the # exceeds the maximum
2113 Assert( pObj->ubNumberOfObjects <= MAX_OBJECTS_PER_SLOT);
2114
2115 const ItemModel * pItem = GCM->getItem(pObj->usItem);
2116 ubPerSlot = pItem->getPerPocket();
2117
2118 // Overrides to the standard system: put guns in hand, armour on body (if slot empty)
2119 switch (pItem->getItemClass())
2120 {
2121 case IC_GUN:
2122 case IC_BLADE:
2123 case IC_LAUNCHER:
2124 case IC_BOMB:
2125 case IC_GRENADE:
2126 if (!(pItem->isTwoHanded()))
2127 {
2128 if (pSoldier->inv[HANDPOS].usItem == NONE)
2129 {
2130 // put the one-handed weapon in the guy's hand...
2131 PlaceObject( pSoldier, HANDPOS, pObj );
2132 SetNewItem( pSoldier, HANDPOS, fNewItem );
2133 if ( pObj->ubNumberOfObjects == 0 )
2134 {
2135 return( TRUE );
2136 }
2137 }
2138 else if ( !(GCM->getItem(pSoldier->inv[HANDPOS].usItem)->isTwoHanded()) && pSoldier->inv[SECONDHANDPOS].usItem == NONE)
2139 {
2140 // put the one-handed weapon in the guy's 2nd hand...
2141 PlaceObject( pSoldier, SECONDHANDPOS, pObj );
2142 SetNewItem( pSoldier, SECONDHANDPOS, fNewItem );
2143 if ( pObj->ubNumberOfObjects == 0 )
2144 {
2145 return( TRUE );
2146 }
2147 }
2148 }
2149 // two-handed objects are best handled in the main loop for large objects,
2150 // which checks the hands first anyhow
2151 break;
2152
2153 case IC_ARMOUR:
2154 switch (Armour[GCM->getItem(pObj->usItem)->getClassIndex()].ubArmourClass)
2155 {
2156 case ARMOURCLASS_VEST:
2157 if (pSoldier->inv[VESTPOS].usItem == NONE)
2158 {
2159 // put on the armour!
2160 PlaceObject( pSoldier, VESTPOS, pObj );
2161 SetNewItem( pSoldier, VESTPOS, fNewItem );
2162 if ( pObj->ubNumberOfObjects == 0 )
2163 {
2164 return( TRUE );
2165 }
2166 }
2167 break;
2168 case ARMOURCLASS_LEGGINGS:
2169 if (pSoldier->inv[LEGPOS].usItem == NONE)
2170 {
2171 // put on the armour!
2172 PlaceObject( pSoldier, LEGPOS, pObj );
2173 SetNewItem( pSoldier, LEGPOS, fNewItem );
2174 if ( pObj->ubNumberOfObjects == 0 )
2175 {
2176 return( TRUE );
2177 }
2178 }
2179 break;
2180 case ARMOURCLASS_HELMET:
2181 if (pSoldier->inv[HELMETPOS].usItem == NONE)
2182 {
2183 // put on the armour!
2184 PlaceObject( pSoldier, HELMETPOS, pObj );
2185 SetNewItem( pSoldier, HELMETPOS, fNewItem );
2186 if ( pObj->ubNumberOfObjects == 0 )
2187 {
2188 return( TRUE );
2189 }
2190 }
2191 break;
2192 default:
2193 break;
2194 }
2195 // otherwise stuff it in a slot somewhere
2196 break;
2197 case IC_FACE:
2198 if ( (pSoldier->inv[HEAD1POS].usItem == NOTHING) && CompatibleFaceItem( pObj->usItem, pSoldier->inv[HEAD2POS].usItem ) )
2199 {
2200 PlaceObject( pSoldier, HEAD1POS, pObj );
2201 SetNewItem( pSoldier, HEAD1POS, fNewItem );
2202 if ( pObj->ubNumberOfObjects == 0 )
2203 {
2204 return( TRUE );
2205 }
2206 }
2207 else if ( (pSoldier->inv[HEAD2POS].usItem == NOTHING) && CompatibleFaceItem( pObj->usItem, pSoldier->inv[HEAD1POS].usItem ) )
2208 {
2209 PlaceObject( pSoldier, HEAD2POS, pObj );
2210 SetNewItem( pSoldier, HEAD2POS, fNewItem );
2211 if ( pObj->ubNumberOfObjects == 0 )
2212 {
2213 return( TRUE );
2214 }
2215 }
2216 break;
2217 default:
2218 break;
2219 }
2220
2221 if (ubPerSlot == 0)
2222 {
2223 // Large object; look for an empty hand/large pocket and dump it in there
2224 // FindObjWithin with 0 will search for empty slots!
2225 bSlot = HANDPOS;
2226 while (1)
2227 {
2228 bSlot = FindEmptySlotWithin( pSoldier, bSlot, BIGPOCK4POS );
2229 if (bSlot == ITEM_NOT_FOUND)
2230 {
2231 return( FALSE );
2232 }
2233 if (bSlot == SECONDHANDPOS)
2234 {
2235 if (pSoldier->inv[HANDPOS].usItem != NONE)
2236 {
2237 bSlot++;
2238 continue;
2239 }
2240 }
2241 // this might fail if we're trying to place in HANDPOS,
2242 // and SECONDHANDPOS is full
2243 PlaceObject( pSoldier, bSlot, pObj );
2244 SetNewItem( pSoldier, bSlot, fNewItem );
2245 if (pObj->ubNumberOfObjects == 0)
2246 {
2247 return( TRUE );
2248 }
2249 bSlot++;
2250 }
2251 }
2252 else
2253 {
2254 // Small items; don't allow stack/dumping for keys right now as that
2255 // would require a bunch of functions for finding the same object by two values...
2256 if ( ubPerSlot > 1 || GCM->getItem(pObj->usItem)->getItemClass() == IC_KEY || GCM->getItem(pObj->usItem)->getItemClass() == IC_MONEY )
2257 {
2258 // First, look for slots with the same object, and dump into them.
2259 bSlot = HANDPOS;
2260 while( 1 )
2261 {
2262 bSlot = FindObjWithin( pSoldier, pObj->usItem, bSlot, SMALLPOCK8POS );
2263 if (bSlot == ITEM_NOT_FOUND)
2264 {
2265 break;
2266 }
2267 if ( bSlot != bExcludeSlot )
2268 {
2269 if ( ( (GCM->getItem(pObj->usItem)->getItemClass() == IC_MONEY) && pSoldier->inv[ bSlot ].uiMoneyAmount < MoneySlotLimit( bSlot ) ) || (GCM->getItem(pObj->usItem)->getItemClass() != IC_MONEY && pSoldier->inv[bSlot].ubNumberOfObjects < ItemSlotLimit( pObj->usItem, bSlot ) ) )
2270 {
2271 // NEW: If in SKI, don't auto-place anything into a stackable slot that's currently hatched out! Such slots
2272 // will disappear in their entirety if sold/moved, causing anything added through here to vanish also!
2273 if (guiCurrentScreen != SHOPKEEPER_SCREEN || !ShouldSoldierDisplayHatchOnItem(pSoldier->ubProfile, bSlot))
2274 {
2275 PlaceObject( pSoldier, bSlot, pObj );
2276 SetNewItem( pSoldier, bSlot, fNewItem );
2277 if (pObj->ubNumberOfObjects == 0)
2278 {
2279 return( TRUE );
2280 }
2281 }
2282 }
2283 }
2284 bSlot++;
2285 }
2286 }
2287 // Search for empty slots to dump into, starting with small pockets
2288 bSlot = SMALLPOCK1POS;
2289 while( 1 )
2290 {
2291 bSlot = FindEmptySlotWithin( pSoldier, bSlot, SMALLPOCK8POS );
2292 if (bSlot == ITEM_NOT_FOUND)
2293 {
2294 break;
2295 }
2296 PlaceObject( pSoldier, bSlot, pObj );
2297 SetNewItem( pSoldier, bSlot, fNewItem );
2298 if (pObj->ubNumberOfObjects == 0)
2299 {
2300 return( TRUE );
2301 }
2302 bSlot++;
2303 }
2304 // now check hands/large pockets
2305 bSlot = HANDPOS;
2306 while (1)
2307 {
2308 bSlot = FindEmptySlotWithin( pSoldier, bSlot, BIGPOCK4POS );
2309 if (bSlot == ITEM_NOT_FOUND)
2310 {
2311 break;
2312 }
2313 PlaceObject( pSoldier, bSlot, pObj );
2314 SetNewItem( pSoldier, bSlot, fNewItem );
2315 if (pObj->ubNumberOfObjects == 0)
2316 {
2317 return( TRUE );
2318 }
2319 bSlot++;
2320 }
2321 }
2322 return( FALSE );
2323 }
2324
AutoPlaceObject(SOLDIERTYPE * pSoldier,OBJECTTYPE * pObj,BOOLEAN fNewItem)2325 BOOLEAN AutoPlaceObject( SOLDIERTYPE * pSoldier, OBJECTTYPE * pObj, BOOLEAN fNewItem )
2326 {
2327 return( InternalAutoPlaceObject( pSoldier, pObj, fNewItem, NO_SLOT ) );
2328 }
2329
RemoveObjectFromSlot(SOLDIERTYPE * pSoldier,INT8 bPos,OBJECTTYPE * pObj)2330 BOOLEAN RemoveObjectFromSlot( SOLDIERTYPE * pSoldier, INT8 bPos, OBJECTTYPE * pObj )
2331 {
2332 CHECKF( pObj );
2333 if (pSoldier->inv[bPos].ubNumberOfObjects == 0)
2334 {
2335 return( FALSE );
2336 }
2337 else
2338 {
2339 *pObj = pSoldier->inv[bPos];
2340 DeleteObj( &(pSoldier->inv[bPos]) );
2341 return( TRUE );
2342 }
2343 }
2344
RemoveKeyFromSlot(SOLDIERTYPE * pSoldier,INT8 bKeyRingPosition,OBJECTTYPE * pObj)2345 BOOLEAN RemoveKeyFromSlot( SOLDIERTYPE * pSoldier, INT8 bKeyRingPosition, OBJECTTYPE * pObj )
2346 {
2347 UINT8 ubItem = 0;
2348
2349 CHECKF( pObj );
2350
2351 if( ( pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber == 0 ) || ( pSoldier->pKeyRing[ bKeyRingPosition ].ubKeyID == INVALID_KEY_NUMBER ) )
2352 {
2353 return( FALSE );
2354 }
2355 else
2356 {
2357 //*pObj = pSoldier->inv[bPos];
2358
2359 // create an object
2360 ubItem = pSoldier->pKeyRing[ bKeyRingPosition ].ubKeyID;
2361
2362 if( pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber > 1 )
2363 {
2364 pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber--;
2365 }
2366 else
2367 {
2368
2369 pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber = 0;
2370 pSoldier->pKeyRing[ bKeyRingPosition ].ubKeyID = INVALID_KEY_NUMBER;
2371 }
2372
2373 CreateKeyObject(pObj, 1, ubItem);
2374 return TRUE;
2375 }
2376 }
2377
2378
RemoveKeysFromSlot(SOLDIERTYPE * pSoldier,INT8 bKeyRingPosition,UINT8 ubNumberOfKeys,OBJECTTYPE * pObj)2379 BOOLEAN RemoveKeysFromSlot( SOLDIERTYPE * pSoldier, INT8 bKeyRingPosition, UINT8 ubNumberOfKeys ,OBJECTTYPE * pObj )
2380 {
2381 UINT8 ubItems = 0;
2382
2383 CHECKF( pObj );
2384
2385
2386 if( ( pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber == 0 ) || ( pSoldier->pKeyRing[ bKeyRingPosition ].ubKeyID == INVALID_KEY_NUMBER ) )
2387 {
2388 return( FALSE );
2389 }
2390 else
2391 {
2392 //*pObj = pSoldier->inv[bPos];
2393
2394 if( pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber < ubNumberOfKeys )
2395 {
2396 ubNumberOfKeys = pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber;
2397 }
2398
2399
2400 ubItems = pSoldier->pKeyRing[ bKeyRingPosition ].ubKeyID;
2401 if( pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber - ubNumberOfKeys > 0 )
2402 {
2403 pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber--;
2404 }
2405 else
2406 {
2407 pSoldier->pKeyRing[ bKeyRingPosition ].ubNumber = 0;
2408 pSoldier->pKeyRing[ bKeyRingPosition ].ubKeyID = INVALID_KEY_NUMBER;
2409 }
2410
2411 CreateKeyObject(pObj, ubNumberOfKeys, ubItems);
2412 return TRUE;
2413 }
2414 }
2415
2416
2417 // return number added
AddKeysToSlot(SOLDIERTYPE & s,INT8 const key_ring_pos,OBJECTTYPE const & key)2418 UINT8 AddKeysToSlot(SOLDIERTYPE& s, INT8 const key_ring_pos, OBJECTTYPE const& key)
2419 {
2420 // redundant but what the hey
2421 CollectKey(s, key);
2422
2423 KEY_ON_RING& keyring = s.pKeyRing[key_ring_pos];
2424 if (keyring.ubNumber == 0) keyring.ubKeyID = key.ubKeyID;
2425 // Only take what we can
2426 UINT8 const n_added = MIN(key.ubNumberOfObjects, GCM->getItem(key.usItem)->getPerPocket() - keyring.ubNumber);
2427 keyring.ubNumber += n_added;
2428 return n_added;
2429 }
2430
2431
SwapKeysToSlot(SOLDIERTYPE & s,INT8 const key_ring_pos,OBJECTTYPE & key)2432 void SwapKeysToSlot(SOLDIERTYPE& s, INT8 const key_ring_pos, OBJECTTYPE& key)
2433 {
2434 KEY_ON_RING& keyring = s.pKeyRing[key_ring_pos];
2435 UINT8 const n_keys = keyring.ubNumber;
2436 UINT8 const key_id = keyring.ubKeyID;
2437
2438 keyring.ubNumber = key.ubNumberOfObjects;
2439 keyring.ubKeyID = key.ubKeyID;
2440
2441 CreateKeyObject(&key, n_keys, key_id);
2442 }
2443
2444
CreateKeyObject(OBJECTTYPE * const pObj,UINT8 const ubNumberOfKeys,UINT8 const ubKeyID)2445 void CreateKeyObject(OBJECTTYPE* const pObj, UINT8 const ubNumberOfKeys, UINT8 const ubKeyID)
2446 {
2447 CreateItems(FIRST_KEY + LockTable[ubKeyID].usKeyItem, 100, ubNumberOfKeys, pObj);
2448 pObj->ubKeyID = ubKeyID;
2449 }
2450
2451
AllocateObject(OBJECTTYPE ** const pObj)2452 void AllocateObject(OBJECTTYPE** const pObj)
2453 {
2454 // create a key object
2455 *pObj = new OBJECTTYPE{};
2456 }
2457
2458
DeleteKeyObject(OBJECTTYPE * pObj)2459 BOOLEAN DeleteKeyObject( OBJECTTYPE * pObj )
2460 {
2461 if (pObj == NULL) return FALSE;
2462
2463 // free up space
2464 delete pObj;
2465
2466 return( TRUE );
2467 }
2468
2469
TotalPoints(const OBJECTTYPE * const pObj)2470 UINT16 TotalPoints(const OBJECTTYPE* const pObj)
2471 {
2472 UINT16 usPoints = 0;
2473 UINT8 ubLoop;
2474
2475 for (ubLoop = 0; ubLoop < pObj->ubNumberOfObjects; ubLoop++)
2476 {
2477 usPoints += pObj->bStatus[ubLoop];
2478 }
2479 return( usPoints );
2480 }
2481
2482
UseKitPoints(OBJECTTYPE & o,UINT16 const original_points,SOLDIERTYPE const & s)2483 UINT16 UseKitPoints(OBJECTTYPE& o, UINT16 const original_points, SOLDIERTYPE const& s)
2484 {
2485 // Start consuming from the last kit in, so we end up with fewer fuller kits
2486 // rather than lots of half-empty ones.
2487 UINT16 points = original_points;
2488 UINT8& n = o.ubNumberOfObjects;
2489 for (INT8 i = n - 1; i >= 0; --i)
2490 {
2491 INT8& status = o.bStatus[i];
2492 if (points < (UINT16)status)
2493 {
2494 status -= (INT8)points;
2495 return original_points;
2496 }
2497 else
2498 {
2499 // Consume this kit totally
2500 points -= status;
2501 status = 0;
2502 --n;
2503 }
2504 }
2505
2506 // Pocket/hand emptied, update inventory, then update panel
2507 Assert(n == 0);
2508 DeleteObj(&o);
2509 DirtyMercPanelInterface(&s, DIRTYLEVEL2);
2510 return original_points - points;
2511 }
2512
DefaultMagazine(UINT16 const gun)2513 UINT16 DefaultMagazine(UINT16 const gun)
2514 {
2515 if (!(GCM->getItem(gun)->isGun()))
2516 {
2517 throw std::logic_error("Tried to get default ammo for item which is not a gun");
2518 }
2519
2520 const WeaponModel * w = GCM->getWeapon(gun);
2521 const std::vector<const MagazineModel*>& magazines = GCM->getMagazines();
2522 for (const MagazineModel* mag : magazines)
2523 {
2524 if (mag->calibre->index == NOAMMO) break;
2525 if (mag->dontUseAsDefaultMagazine) continue;
2526 if (mag->calibre->index != w->calibre->index) continue;
2527 if (mag->capacity != w->ubMagSize) continue;
2528 return mag->getItemIndex();
2529 }
2530
2531 throw std::logic_error("Found no default ammo for gun");
2532 }
2533
2534
FindReplacementMagazine(const CalibreModel * calibre,UINT8 const mag_size,UINT8 const ammo_type)2535 UINT16 FindReplacementMagazine(const CalibreModel * calibre, UINT8 const mag_size, UINT8 const ammo_type)
2536 {
2537 UINT16 default_mag = NOTHING;
2538 const std::vector<const MagazineModel*>& magazines = GCM->getMagazines();
2539 for (const MagazineModel* mag : magazines)
2540 {
2541 if (mag->calibre->index == NOAMMO) break;
2542 if (mag->calibre->index != calibre->index) continue;
2543 if (mag->capacity != mag_size) continue;
2544
2545 if (mag->ammoType->index == ammo_type) return mag->getItemIndex();
2546
2547 if (default_mag == NOTHING)
2548 { // Store this one to use if all else fails
2549 default_mag = mag->getItemIndex();
2550 }
2551 }
2552 return default_mag;
2553 }
2554
2555
FindReplacementMagazineIfNecessary(const WeaponModel * old_gun,UINT16 const old_ammo_id,const WeaponModel * new_gun)2556 UINT16 FindReplacementMagazineIfNecessary(const WeaponModel *old_gun, UINT16 const old_ammo_id, const WeaponModel *new_gun)
2557 {
2558 const MagazineModel * old_mag = GCM->getMagazineByItemIndex(old_ammo_id);
2559 if (old_mag->calibre->index != old_gun->calibre->index) return NOTHING;
2560 if (old_mag->capacity != old_gun->ubMagSize) return NOTHING;
2561 return FindReplacementMagazine(new_gun->calibre, new_gun->ubMagSize, old_mag->ammoType->index);
2562 }
2563
2564
RandomMagazine(UINT16 usItem,UINT8 ubPercentStandard)2565 UINT16 RandomMagazine( UINT16 usItem, UINT8 ubPercentStandard )
2566 {
2567 std::vector<const MagazineModel*> possibleMags;
2568 UINT8 ubMagChosen;
2569
2570 if (!(GCM->getItem(usItem)->isGun()))
2571 {
2572 return( 0 );
2573 }
2574
2575 const WeaponModel *pWeapon = GCM->getWeapon(usItem);
2576
2577 // find & store all possible mag types that fit this gun
2578 const std::vector<const MagazineModel*>& magazines = GCM->getMagazines();
2579 for (const MagazineModel* mag : magazines)
2580 {
2581 if (pWeapon->matches(mag))
2582 {
2583 possibleMags.push_back(mag);
2584 }
2585 }
2586
2587 // no matches?
2588 if (possibleMags.size() == 0)
2589 {
2590 return( 0 );
2591 }
2592 else if (possibleMags.size() == 1)
2593 {
2594 // only one match?
2595 // use that, no choice
2596 return possibleMags[0]->getItemIndex();
2597 }
2598 else // multiple choices
2599 {
2600 // Pick one at random, using supplied probability to pick the default
2601 if (Random(100) < ubPercentStandard)
2602 {
2603 ubMagChosen = 0;
2604 }
2605 else
2606 {
2607 // pick a non-standard type instead
2608 ubMagChosen = ( UINT8 ) (1 + Random(( UINT32 ) ( possibleMags.size() - 1 )));
2609 }
2610
2611 return possibleMags[ubMagChosen]->getItemIndex();
2612 }
2613 }
2614
2615
CreateGun(UINT16 usItem,INT8 bStatus,OBJECTTYPE * pObj)2616 static void CreateGun(UINT16 usItem, INT8 bStatus, OBJECTTYPE* pObj)
2617 {
2618 UINT16 usAmmo;
2619
2620 pObj->usItem = usItem;
2621 pObj->ubNumberOfObjects = 1;
2622 pObj->bGunStatus = bStatus;
2623 pObj->ubImprintID = NO_PROFILE;
2624 pObj->ubWeight = CalculateObjectWeight( pObj );
2625
2626 if (GCM->getWeapon( usItem )->ubWeaponClass == MONSTERCLASS)
2627 {
2628 pObj->ubGunShotsLeft = GCM->getWeapon( usItem )->ubMagSize;
2629 pObj->ubGunAmmoType = AMMO_MONSTER;
2630 }
2631 else if ( EXPLOSIVE_GUN( usItem ) )
2632 {
2633 if ( usItem == ROCKET_LAUNCHER )
2634 {
2635 pObj->ubGunShotsLeft = 1;
2636 }
2637 else
2638 {
2639 // cannon
2640 pObj->ubGunShotsLeft = 0;
2641 }
2642 pObj->bGunAmmoStatus = 100;
2643 pObj->ubGunAmmoType = 0;
2644 }
2645 else
2646 {
2647 usAmmo = DefaultMagazine( usItem );
2648 pObj->usGunAmmoItem = usAmmo;
2649 pObj->ubGunAmmoType = GCM->getItem(usAmmo)->asAmmo()->ammoType->index;
2650 pObj->bGunAmmoStatus = 100;
2651 pObj->ubGunShotsLeft = GCM->getItem(usAmmo)->asAmmo()->capacity;
2652 }
2653 }
2654
2655
CreateMagazine(UINT16 usItem,OBJECTTYPE * pObj)2656 static void CreateMagazine(UINT16 usItem, OBJECTTYPE* pObj)
2657 {
2658 pObj->usItem = usItem;
2659 pObj->ubNumberOfObjects = 1;
2660 pObj->ubShotsLeft[0] = GCM->getItem(usItem)->asAmmo()->capacity;
2661 pObj->ubWeight = CalculateObjectWeight( pObj );
2662 }
2663
2664
CreateItem(UINT16 const usItem,INT8 const bStatus,OBJECTTYPE * const pObj)2665 void CreateItem(UINT16 const usItem, INT8 const bStatus, OBJECTTYPE* const pObj)
2666 {
2667 *pObj = OBJECTTYPE{};
2668 if (usItem >= MAXITEMS)
2669 {
2670 throw std::logic_error("Tried to create item with invalid ID");
2671 }
2672
2673 if (GCM->getItem(usItem)->getItemClass() == IC_GUN)
2674 {
2675 CreateGun( usItem, bStatus, pObj );
2676 }
2677 else if (GCM->getItem(usItem)->getItemClass() == IC_AMMO)
2678 {
2679 CreateMagazine(usItem, pObj);
2680 }
2681 else
2682 {
2683 pObj->usItem = usItem;
2684 pObj->ubNumberOfObjects = 1;
2685 if (usItem == MONEY)
2686 {
2687 // special case... always set status to 100 when creating
2688 // and use status value to determine amount!
2689 pObj->bStatus[0] = 100;
2690 pObj->uiMoneyAmount = bStatus * 50;
2691 }
2692 else
2693 {
2694 pObj->bStatus[0] = bStatus;
2695 }
2696 pObj->ubWeight = CalculateObjectWeight( pObj );
2697 }
2698
2699 if (GCM->getItem(usItem)->getFlags() & ITEM_DEFAULT_UNDROPPABLE)
2700 {
2701 pObj->fFlags |= OBJECT_UNDROPPABLE;
2702 }
2703 }
2704
2705
CreateItems(UINT16 const usItem,INT8 const bStatus,UINT8 ubNumber,OBJECTTYPE * const pObj)2706 void CreateItems(UINT16 const usItem, INT8 const bStatus, UINT8 ubNumber, OBJECTTYPE* const pObj)
2707 {
2708 UINT8 ubLoop;
2709
2710 // can't create any more than this, the loop for setting the bStatus[] of others will overwrite memory!
2711 Assert( ubNumber <= MAX_OBJECTS_PER_SLOT );
2712
2713 // ARM: to avoid whacking memory once Assertions are removed... Items will be lost in this situation!
2714 if ( ubNumber > MAX_OBJECTS_PER_SLOT )
2715 {
2716 ubNumber = MAX_OBJECTS_PER_SLOT;
2717 }
2718
2719 CreateItem(usItem, bStatus, pObj);
2720 for (ubLoop = 1; ubLoop < ubNumber; ubLoop++)
2721 {
2722 // we reference status[0] here because the status value might actually be a
2723 // # of rounds of ammo, in which case the value won't be the bStatus value
2724 // passed in.
2725 pObj->bStatus[ubLoop] = pObj->bStatus[0];
2726 }
2727 pObj->ubNumberOfObjects = ubNumber;
2728 }
2729
2730
CreateMoney(UINT32 const uiMoney,OBJECTTYPE * const pObj)2731 void CreateMoney(UINT32 const uiMoney, OBJECTTYPE* const pObj)
2732 {
2733 CreateItem(MONEY, 100, pObj);
2734 pObj->uiMoneyAmount = uiMoney;
2735 }
2736
2737
ArmBomb(OBJECTTYPE * pObj,INT8 bSetting)2738 BOOLEAN ArmBomb( OBJECTTYPE * pObj, INT8 bSetting )
2739 {
2740 BOOLEAN fRemote = FALSE;
2741 BOOLEAN fPressure = FALSE;
2742 BOOLEAN fTimed = FALSE;
2743 BOOLEAN fSwitch = FALSE;
2744
2745 if (pObj->usItem == ACTION_ITEM)
2746 {
2747 switch( pObj->bActionValue )
2748 {
2749 case ACTION_ITEM_SMALL_PIT:
2750 case ACTION_ITEM_LARGE_PIT:
2751 fPressure = TRUE;
2752 break;
2753 default:
2754 fRemote = TRUE;
2755 break;
2756
2757 }
2758 }
2759 else if ( FindAttachment( pObj, DETONATOR ) != ITEM_NOT_FOUND )
2760 {
2761 fTimed = TRUE;
2762 }
2763 else if ( (FindAttachment( pObj, REMDETONATOR ) != ITEM_NOT_FOUND) || (pObj->usItem == ACTION_ITEM) )
2764 {
2765 fRemote = TRUE;
2766 }
2767 else if ( pObj->usItem == MINE || pObj->usItem == TRIP_FLARE || pObj->usItem == TRIP_KLAXON || pObj->usItem == ACTION_ITEM )
2768 {
2769 fPressure = TRUE;
2770 }
2771 else if ( pObj->usItem == SWITCH )
2772 {
2773 // this makes a remote detonator into a pressure-sensitive trigger
2774 if ( bSetting == PANIC_FREQUENCY )
2775 {
2776 // panic trigger is only activated by expending APs, not by
2777 // stepping on it... so don't define a detonator type
2778 fSwitch = TRUE;
2779 }
2780 else
2781 {
2782 fPressure = TRUE;
2783 }
2784 }
2785 else
2786 {
2787 // no sorta bomb at all!
2788 return( FALSE );
2789 }
2790
2791 if (fRemote)
2792 {
2793 pObj->bDetonatorType = BOMB_REMOTE;
2794 pObj->bFrequency = bSetting;
2795 }
2796 else if (fPressure)
2797 {
2798 pObj->bDetonatorType = BOMB_PRESSURE;
2799 pObj->bFrequency = 0;
2800 }
2801 else if (fTimed)
2802 {
2803 pObj->bDetonatorType = BOMB_TIMED;
2804 // In realtime the player could choose to put down a bomb right before a turn expires, SO
2805 // add 1 to the setting in RT
2806 pObj->bDelay = bSetting;
2807 if (!(gTacticalStatus.uiFlags & INCOMBAT))
2808 {
2809 pObj->bDelay++;
2810 }
2811
2812 }
2813 else if (fSwitch)
2814 {
2815 pObj->bDetonatorType = BOMB_SWITCH;
2816 pObj->bFrequency = bSetting;
2817 }
2818 else
2819 {
2820 return( FALSE );
2821 }
2822
2823 pObj->fFlags |= OBJECT_ARMED_BOMB;
2824 pObj->usBombItem = pObj->usItem;
2825 return( TRUE );
2826 }
2827
2828
RenumberAttachments(OBJECTTYPE * pObj)2829 static void RenumberAttachments(OBJECTTYPE* pObj)
2830 {
2831 // loop through attachment positions and make sure we don't have any empty
2832 // attachment slots before filled ones
2833 INT8 bAttachPos;
2834 INT8 bFirstSpace;
2835 BOOLEAN fDone = FALSE;
2836
2837 while (!fDone)
2838 {
2839 bFirstSpace = -1;
2840 for (bAttachPos = 0; bAttachPos < MAX_ATTACHMENTS; bAttachPos++)
2841 {
2842 if (pObj->usAttachItem[ bAttachPos ] == NOTHING)
2843 {
2844 if (bFirstSpace == -1)
2845 {
2846 bFirstSpace = bAttachPos;
2847 }
2848 }
2849 else
2850 {
2851 if (bFirstSpace != -1)
2852 {
2853 // move the attachment!
2854 pObj->usAttachItem[ bFirstSpace ] = pObj->usAttachItem[ bAttachPos ];
2855 pObj->bAttachStatus[ bFirstSpace ] = pObj->bAttachStatus[ bAttachPos ];
2856 pObj->usAttachItem[ bAttachPos ] = NOTHING;
2857 pObj->bAttachStatus[ bAttachPos ] = 0;
2858 // restart loop at beginning, or quit if we reached the end of the
2859 // attachments
2860 break;
2861 }
2862 }
2863 }
2864 if (bAttachPos == MAX_ATTACHMENTS)
2865 {
2866 // done!!
2867 fDone = TRUE;
2868 }
2869 }
2870
2871 }
2872
RemoveAttachment(OBJECTTYPE * pObj,INT8 bAttachPos,OBJECTTYPE * pNewObj)2873 BOOLEAN RemoveAttachment( OBJECTTYPE * pObj, INT8 bAttachPos, OBJECTTYPE * pNewObj )
2874 {
2875 INT8 bGrenade;
2876
2877 CHECKF( pObj );
2878
2879 if (bAttachPos < 0 || bAttachPos >= MAX_ATTACHMENTS)
2880 {
2881 return( FALSE );
2882 }
2883 if (pObj->usAttachItem[bAttachPos] == NOTHING )
2884 {
2885 return( FALSE );
2886 }
2887
2888 if ( GCM->getItem(pObj->usAttachItem[bAttachPos])->getFlags() & ITEM_INSEPARABLE )
2889 {
2890 return( FALSE );
2891 }
2892
2893 // if pNewObj is passed in NULL, then we just delete the attachment
2894 if (pNewObj != NULL)
2895 {
2896 CreateItem( pObj->usAttachItem[bAttachPos], pObj->bAttachStatus[bAttachPos], pNewObj );
2897 }
2898
2899 pObj->usAttachItem[bAttachPos] = NOTHING;
2900 pObj->bAttachStatus[bAttachPos] = 0;
2901
2902 if (pNewObj && pNewObj->usItem == UNDER_GLAUNCHER)
2903 {
2904 // look for any grenade; if it exists, we must make it an
2905 // attachment of the grenade launcher
2906 bGrenade = FindAttachmentByClass( pObj, IC_GRENADE );
2907 if (bGrenade != ITEM_NOT_FOUND)
2908 {
2909 pNewObj->usAttachItem[0] = pObj->usAttachItem[bGrenade];
2910 pNewObj->bAttachStatus[0] = pObj->bAttachStatus[bGrenade];
2911 pObj->usAttachItem[bGrenade] = NOTHING;
2912 pObj->bAttachStatus[bGrenade] = 0;
2913 pNewObj->ubWeight = CalculateObjectWeight( pNewObj );
2914 }
2915 }
2916
2917 RenumberAttachments( pObj );
2918
2919 pObj->ubWeight = CalculateObjectWeight( pObj );
2920 return( TRUE );
2921 }
2922
2923
SetNewItem(SOLDIERTYPE * pSoldier,UINT8 ubInvPos,BOOLEAN fNewItem)2924 void SetNewItem(SOLDIERTYPE* pSoldier, UINT8 ubInvPos, BOOLEAN fNewItem)
2925 {
2926 if( fNewItem )
2927 {
2928 pSoldier->bNewItemCount[ ubInvPos ] = -1;
2929 pSoldier->bNewItemCycleCount[ ubInvPos ] = NEW_ITEM_CYCLE_COUNT;
2930 pSoldier->fCheckForNewlyAddedItems = TRUE;
2931 }
2932 }
2933
2934
PlaceObjectInSoldierProfile(UINT8 ubProfile,OBJECTTYPE * pObject)2935 BOOLEAN PlaceObjectInSoldierProfile( UINT8 ubProfile, OBJECTTYPE *pObject )
2936 {
2937 INT8 bLoop, bLoop2;
2938 UINT16 usItem;
2939 INT8 bStatus;
2940 BOOLEAN fReturnVal = FALSE;
2941
2942 usItem = pObject->usItem;
2943 bStatus = pObject->bStatus[0];
2944 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubProfile);
2945
2946 if ( GCM->getItem(usItem)->getItemClass() == IC_MONEY && gMercProfiles[ ubProfile ].uiMoney > 0 )
2947 {
2948 gMercProfiles[ ubProfile ].uiMoney += pObject->uiMoneyAmount;
2949 SetMoneyInSoldierProfile( ubProfile, gMercProfiles[ ubProfile ].uiMoney );
2950 return( TRUE );
2951 }
2952
2953 for (bLoop = BIGPOCK1POS; bLoop < SMALLPOCK8POS; bLoop++)
2954 {
2955 if ( gMercProfiles[ ubProfile ].bInvNumber[ bLoop ] == 0 && (pSoldier == NULL || pSoldier->inv[ bLoop ].usItem == NOTHING ) )
2956 {
2957
2958 // CJC: Deal with money by putting money into # stored in profile
2959 if ( GCM->getItem(usItem)->getItemClass() == IC_MONEY )
2960 {
2961 gMercProfiles[ ubProfile ].uiMoney += pObject->uiMoneyAmount;
2962 // change any gold/silver to money
2963 usItem = MONEY;
2964 }
2965 else
2966 {
2967 gMercProfiles[ ubProfile ].inv[ bLoop ] = usItem;
2968 gMercProfiles[ ubProfile ].bInvStatus[ bLoop ] = bStatus;
2969 gMercProfiles[ ubProfile ].bInvNumber[ bLoop ] = pObject->ubNumberOfObjects;
2970 }
2971
2972 fReturnVal = TRUE;
2973 break;
2974 }
2975 }
2976
2977 if ( fReturnVal )
2978 {
2979 // ATE: Manage soldier pointer as well....
2980 // Do we have a valid profile?
2981 if ( pSoldier != NULL )
2982 {
2983 // OK, place in soldier...
2984 if ( usItem == MONEY )
2985 {
2986 CreateMoney( gMercProfiles[ ubProfile ].uiMoney, &(pSoldier->inv[ bLoop ] ) );
2987 }
2988 else
2989 {
2990 if ( pSoldier->ubProfile == MADLAB )
2991 {
2992 // remove attachments and drop them
2993 OBJECTTYPE Attachment;
2994
2995 for ( bLoop2 = MAX_ATTACHMENTS - 1; bLoop2 >= 0; bLoop2-- )
2996 {
2997 // remove also checks for existence attachment
2998 if (RemoveAttachment(pObject, bLoop2, &Attachment))
2999 {
3000 // drop it in Madlab's tile
3001 AddItemToPool(pSoldier->sGridNo, &Attachment, VISIBLE, 0, 0, 0);
3002 }
3003 }
3004 }
3005
3006 CreateItem( usItem, bStatus, &(pSoldier->inv[ bLoop ] ) );
3007 }
3008 }
3009 }
3010
3011 return( fReturnVal );
3012 }
3013
3014
3015 static void RemoveInvObject(SOLDIERTYPE* pSoldier, UINT16 usItem);
3016
3017
RemoveObjectFromSoldierProfile(UINT8 ubProfile,UINT16 usItem)3018 BOOLEAN RemoveObjectFromSoldierProfile( UINT8 ubProfile, UINT16 usItem )
3019 {
3020 INT8 bLoop;
3021 BOOLEAN fReturnVal = FALSE;
3022
3023 if ( usItem == NOTHING )
3024 {
3025 return( TRUE );
3026 }
3027
3028 for (bLoop = 0; bLoop < 19; bLoop++)
3029 {
3030 if ( gMercProfiles[ ubProfile ].inv[ bLoop ] == usItem )
3031 {
3032 gMercProfiles[ ubProfile ].inv[ bLoop ] = NOTHING;
3033 gMercProfiles[ ubProfile ].bInvStatus[ bLoop ] = 0;
3034 gMercProfiles[ ubProfile ].bInvNumber[ bLoop ] = 0;
3035
3036 fReturnVal = TRUE;
3037 break;
3038 }
3039 }
3040
3041 // ATE: Manage soldier pointer as well....
3042 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(ubProfile);
3043
3044 // Do we have a valid profile?
3045 if ( pSoldier != NULL )
3046 {
3047 // Remove item...
3048 RemoveInvObject( pSoldier, usItem );
3049 }
3050
3051 return( fReturnVal );
3052 }
3053
3054
SetMoneyInSoldierProfile(UINT8 ubProfile,UINT32 uiMoney)3055 void SetMoneyInSoldierProfile( UINT8 ubProfile, UINT32 uiMoney )
3056 {
3057 OBJECTTYPE Object;
3058 BOOLEAN fRet;
3059
3060 // remove all money from soldier
3061 do
3062 {
3063 fRet = RemoveObjectFromSoldierProfile( ubProfile, MONEY );
3064 }
3065 while (fRet);
3066
3067 gMercProfiles[ ubProfile ].uiMoney = 0;
3068
3069 if (uiMoney > 0)
3070 {
3071 // now add the amount specified
3072 CreateMoney( uiMoney, &Object );
3073 PlaceObjectInSoldierProfile( ubProfile, &Object );
3074 }
3075 }
3076
3077
FindObjectInSoldierProfile(MERCPROFILESTRUCT const & p,UINT16 const item_id)3078 INT8 FindObjectInSoldierProfile(MERCPROFILESTRUCT const& p, UINT16 const item_id)
3079 {
3080 for (INT8 i = 0; i != NUM_INV_SLOTS; ++i)
3081 {
3082 if (p.bInvNumber[i] == 0) continue;
3083 if (p.inv[i] != item_id) continue;
3084 return i;
3085 }
3086 return NO_SLOT;
3087 }
3088
3089
RemoveInvObject(SOLDIERTYPE * pSoldier,UINT16 usItem)3090 static void RemoveInvObject(SOLDIERTYPE* pSoldier, UINT16 usItem)
3091 {
3092 INT8 bInvPos;
3093
3094 // find object
3095 bInvPos = FindObj( pSoldier, usItem );
3096 if (bInvPos != NO_SLOT)
3097 {
3098
3099 // Erase!
3100 pSoldier->inv[ bInvPos ] = OBJECTTYPE{};
3101
3102 //Dirty!
3103 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
3104 }
3105
3106 }
3107
3108
CheckItemForDamage(UINT16 usItem,INT32 iMaxDamage)3109 static INT8 CheckItemForDamage(UINT16 usItem, INT32 iMaxDamage)
3110 {
3111 INT8 bDamage = 0;
3112
3113 // if the item is protective armour, reduce the amount of damage
3114 // by its armour value
3115 if (GCM->getItem(usItem)->getItemClass() == IC_ARMOUR)
3116 {
3117 iMaxDamage -= (iMaxDamage * Armour[GCM->getItem(usItem)->getClassIndex()].ubProtection) / 100;
3118 }
3119 // metal items are tough and will be damaged less
3120 if (GCM->getItem(usItem)->getFlags() & ITEM_METAL)
3121 {
3122 iMaxDamage /= 2;
3123 }
3124 else if ( usItem == BLOODCAT_PELT )
3125 {
3126 iMaxDamage *= 2;
3127 }
3128 if (iMaxDamage > 0)
3129 {
3130 bDamage = (INT8) PreRandom( iMaxDamage );
3131 }
3132 return( bDamage );
3133 }
3134
3135
CheckForChainReaction(UINT16 usItem,INT8 bStatus,INT8 bDamage,BOOLEAN fOnGround)3136 static BOOLEAN CheckForChainReaction(UINT16 usItem, INT8 bStatus, INT8 bDamage, BOOLEAN fOnGround)
3137 {
3138 INT32 iChance;
3139
3140 iChance = Explosive[GCM->getItem(usItem)->getClassIndex()].ubVolatility;
3141 if (iChance > 0)
3142 {
3143
3144 // Scale the base chance by the damage caused to the item
3145 // (bigger the shock, bigger chance) and the condition of
3146 // the item after being hit!
3147 if (fOnGround)
3148 {
3149 // improve chance to make it practical to blow up explosives on the ground
3150 iChance = 50 + (iChance - 1) * 10;
3151 }
3152
3153 iChance = iChance * ( 100 + ( (100 - bStatus) + bDamage ) / 2 ) / 100;
3154 if ((INT32) PreRandom( 100 ) < iChance)
3155 {
3156 return( TRUE );
3157 }
3158 }
3159 return( FALSE );
3160 }
3161
3162
DamageItem(OBJECTTYPE * pObject,INT32 iDamage,BOOLEAN fOnGround)3163 static BOOLEAN DamageItem(OBJECTTYPE* pObject, INT32 iDamage, BOOLEAN fOnGround)
3164 {
3165 INT8 bLoop;
3166 INT8 bDamage;
3167
3168 if ( (GCM->getItem(pObject->usItem)->getFlags() & ITEM_DAMAGEABLE || GCM->getItem(pObject->usItem)->getItemClass() == IC_AMMO) && pObject->ubNumberOfObjects > 0)
3169 {
3170
3171 for (bLoop = 0; bLoop < pObject->ubNumberOfObjects; bLoop++)
3172 {
3173 // if the status of the item is negative then it's trapped/jammed;
3174 // leave it alone
3175 if (pObject->usItem != NOTHING && pObject->bStatus[bLoop] > 0)
3176 {
3177 bDamage = CheckItemForDamage( pObject->usItem, iDamage );
3178 switch( pObject->usItem )
3179 {
3180 case JAR_CREATURE_BLOOD:
3181 case JAR:
3182 case JAR_HUMAN_BLOOD:
3183 case JAR_ELIXIR:
3184 if ( PreRandom( bDamage ) > 5 )
3185 {
3186 // smash!
3187 bDamage = pObject->bStatus[ bLoop ];
3188 }
3189 break;
3190 default:
3191 break;
3192 }
3193 if ( GCM->getItem(pObject->usItem)->getItemClass() == IC_AMMO )
3194 {
3195 if ( PreRandom( 100 ) < (UINT32) bDamage )
3196 {
3197 // destroy clip completely
3198 pObject->bStatus[ bLoop ] = 1;
3199 }
3200 }
3201 else
3202 {
3203 pObject->bStatus[bLoop] -= bDamage;
3204 if (pObject->bStatus[bLoop] < 1)
3205 {
3206 pObject->bStatus[bLoop] = 1;
3207 }
3208 }
3209 // I don't think we increase viewrange based on items any more
3210 // FUN STUFF! Check for explosives going off as a result!
3211 if (GCM->getItem(pObject->usItem)->isExplosive())
3212 {
3213 if (CheckForChainReaction( pObject->usItem, pObject->bStatus[bLoop], bDamage, fOnGround ))
3214 {
3215 return( TRUE );
3216 }
3217 }
3218
3219 // remove item from index AFTER checking explosions because need item data for explosion!
3220 if ( pObject->bStatus[bLoop] == 1 )
3221 {
3222 if ( pObject->ubNumberOfObjects > 1 )
3223 {
3224 RemoveObjFrom( pObject, bLoop );
3225 // since an item was just removed, the items above the current were all shifted down one;
3226 // to process them properly, we have to back up 1 in the counter
3227 bLoop = bLoop - 1;
3228 }
3229 }
3230 }
3231 }
3232
3233 for (bLoop = 0; bLoop < MAX_ATTACHMENTS; bLoop++)
3234 {
3235 if (pObject->usAttachItem[bLoop] != NOTHING && pObject->bAttachStatus[bLoop] > 0)
3236 {
3237 pObject->bAttachStatus[bLoop] -= CheckItemForDamage( pObject->usAttachItem[bLoop], iDamage );
3238 if (pObject->bAttachStatus[bLoop] < 1)
3239 {
3240 pObject->bAttachStatus[bLoop] = 1;
3241 }
3242 }
3243 }
3244 }
3245
3246 return( FALSE );
3247 }
3248
CheckEquipmentForDamage(SOLDIERTYPE * pSoldier,INT32 iDamage)3249 void CheckEquipmentForDamage( SOLDIERTYPE *pSoldier, INT32 iDamage )
3250 {
3251 BOOLEAN fBlowsUp;
3252 UINT8 ubNumberOfObjects;
3253
3254 if ( TANK( pSoldier ) )
3255 {
3256 return;
3257 }
3258
3259 FOR_EACH_SOLDIER_INV_SLOT(i, *pSoldier)
3260 {
3261 ubNumberOfObjects = i->ubNumberOfObjects;
3262 fBlowsUp = DamageItem(i, iDamage, FALSE);
3263 if (fBlowsUp)
3264 {
3265 // blow it up!
3266 SOLDIERTYPE* const owner = (gTacticalStatus.ubAttackBusyCount ? pSoldier->attacker : pSoldier);
3267 IgniteExplosion(owner, 0, pSoldier->sGridNo, i->usItem, pSoldier->bLevel);
3268
3269 // Remove item!
3270 DeleteObj(i);
3271
3272 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
3273 }
3274 else if (ubNumberOfObjects != i->ubNumberOfObjects)
3275 {
3276 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
3277 }
3278 }
3279 }
3280
CheckEquipmentForFragileItemDamage(SOLDIERTYPE * pSoldier,INT32 iDamage)3281 void CheckEquipmentForFragileItemDamage( SOLDIERTYPE *pSoldier, INT32 iDamage )
3282 {
3283 // glass jars etc can be damaged by falling over
3284 UINT8 ubNumberOfObjects;
3285 BOOLEAN fPlayedGlassBreak = FALSE;
3286
3287 FOR_EACH_SOLDIER_INV_SLOT(i, *pSoldier)
3288 {
3289 switch (i->usItem)
3290 {
3291 case JAR_CREATURE_BLOOD:
3292 case JAR:
3293 case JAR_HUMAN_BLOOD:
3294 case JAR_ELIXIR:
3295 ubNumberOfObjects = i->ubNumberOfObjects;
3296 DamageItem(i, iDamage, FALSE);
3297 if (!fPlayedGlassBreak && ubNumberOfObjects != i->ubNumberOfObjects)
3298 {
3299 PlayLocationJA2Sample(pSoldier->sGridNo, GLASS_CRACK, MIDVOLUME, 1);
3300 fPlayedGlassBreak = TRUE;
3301 // only dirty once
3302 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
3303 }
3304 break;
3305 default:
3306 break;
3307 }
3308 }
3309 }
3310
3311
DamageItemOnGround(OBJECTTYPE * const pObject,const INT16 sGridNo,const INT8 bLevel,const INT32 iDamage,SOLDIERTYPE * const owner)3312 BOOLEAN DamageItemOnGround(OBJECTTYPE* const pObject, const INT16 sGridNo, const INT8 bLevel, const INT32 iDamage, SOLDIERTYPE* const owner)
3313 {
3314 BOOLEAN fBlowsUp;
3315
3316 fBlowsUp = DamageItem( pObject, iDamage, TRUE );
3317 if ( fBlowsUp )
3318 {
3319 // OK, Ignite this explosion!
3320 IgniteExplosion(owner, 0, sGridNo, pObject->usItem, bLevel);
3321
3322 // Remove item!
3323 return( TRUE );
3324 }
3325 else if ( (pObject->ubNumberOfObjects < 2) && (pObject->bStatus[0] < USABLE) )
3326 {
3327 return( TRUE );
3328 }
3329 else
3330 {
3331 return( FALSE );
3332 }
3333 }
3334
3335
IsMedicalKitItem(const OBJECTTYPE * pObject)3336 BOOLEAN IsMedicalKitItem(const OBJECTTYPE* pObject)
3337 {
3338 return pObject->usItem == MEDICKIT;
3339 }
3340
3341
SwapHandItems(SOLDIERTYPE * pSoldier)3342 void SwapHandItems( SOLDIERTYPE * pSoldier )
3343 {
3344 BOOLEAN fOk;
3345
3346 CHECKV( pSoldier );
3347 if (pSoldier->inv[HANDPOS].usItem == NOTHING || pSoldier->inv[SECONDHANDPOS].usItem == NOTHING)
3348 {
3349 // whatever is in the second hand can be swapped to the main hand!
3350 SwapObjs( &(pSoldier->inv[HANDPOS]), &(pSoldier->inv[SECONDHANDPOS]) );
3351 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
3352 }
3353 else
3354 {
3355 if ( GCM->getItem( pSoldier->inv[SECONDHANDPOS].usItem )->isTwoHanded() )
3356 {
3357 // must move the item in the main hand elsewhere in the inventory
3358 fOk = InternalAutoPlaceObject( pSoldier, &(pSoldier->inv[HANDPOS]), FALSE, HANDPOS );
3359 if (!fOk)
3360 {
3361 return;
3362 }
3363 // the main hand is now empty so a swap is going to work...
3364 }
3365 SwapObjs( &(pSoldier->inv[HANDPOS]), &(pSoldier->inv[SECONDHANDPOS]) );
3366 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
3367 }
3368 }
3369
3370
WaterDamage(SOLDIERTYPE & s)3371 void WaterDamage(SOLDIERTYPE& s)
3372 {
3373 // damage guy's equipment and camouflage due to water
3374 INT8 bDieSize;
3375 UINT32 uiRoll;
3376
3377 if (s.bOverTerrainType == DEEP_WATER)
3378 {
3379 FOR_EACH_SOLDIER_INV_SLOT(i, s)
3380 {
3381 // if there's an item here that can get water damaged...
3382 if (i->usItem && GCM->getItem(i->usItem)->getFlags() & ITEM_WATER_DAMAGES)
3383 {
3384 // roll the 'ol 100-sided dice
3385 uiRoll = PreRandom(100);
3386
3387 // 10% chance of getting damage!
3388 if (uiRoll < 10)
3389 {
3390 // lose between 1 and 10 status points each time
3391 // but don't let anything drop lower than 1%
3392 i->bStatus[0] = std::max<INT8>(1, i->bStatus[0] - 10 + uiRoll);
3393 }
3394 }
3395 }
3396 }
3397 if (s.bCamo > 0 && !HAS_SKILL_TRAIT(&s, CAMOUFLAGED))
3398 {
3399 // reduce camouflage by 2% per tile of deep water
3400 // and 1% for medium water
3401 if (s.bOverTerrainType == DEEP_WATER )
3402 {
3403 s.bCamo = __max(0, s.bCamo - 2);
3404 }
3405 else
3406 {
3407 s.bCamo = __max(0, s.bCamo - 1);
3408 }
3409 if (s.bCamo == 0)
3410 {
3411 // Reload palettes....
3412 if (s.bInSector) CreateSoldierPalettes(s);
3413 ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(g_langRes->Message[STR_CAMO_WASHED_OFF], s.name));
3414 }
3415 }
3416 if (s.bTeam == OUR_TEAM && s.bMonsterSmell > 0)
3417 {
3418 if (s.bOverTerrainType == DEEP_WATER)
3419 {
3420 bDieSize = 10;
3421 }
3422 else
3423 {
3424 bDieSize = 20;
3425 }
3426 if ( Random( bDieSize ) == 0 )
3427 {
3428 --s.bMonsterSmell;
3429 }
3430 }
3431
3432 DirtyMercPanelInterface(&s, DIRTYLEVEL2);
3433 }
3434
3435
ApplyCamo(SOLDIERTYPE * const pSoldier,OBJECTTYPE * const pObj,BOOLEAN * const pfGoodAPs)3436 BOOLEAN ApplyCamo(SOLDIERTYPE* const pSoldier, OBJECTTYPE* const pObj, BOOLEAN* const pfGoodAPs)
3437 {
3438 INT8 bPointsToUse;
3439 UINT16 usTotalKitPoints;
3440
3441 (*pfGoodAPs) = TRUE;
3442
3443 if (pObj->usItem != CAMOUFLAGEKIT)
3444 {
3445 return( FALSE );
3446 }
3447
3448 if (!EnoughPoints( pSoldier, AP_CAMOFLAGE, 0, TRUE ) )
3449 {
3450 (*pfGoodAPs) = FALSE;
3451 return( TRUE );
3452 }
3453
3454 usTotalKitPoints = TotalPoints( pObj );
3455 if (usTotalKitPoints == 0)
3456 {
3457 // HUH???
3458 return( FALSE );
3459 }
3460
3461 if (pSoldier->bCamo == 100)
3462 {
3463 // nothing more to add
3464 return( FALSE );
3465 }
3466
3467 // points are used up at a rate of 50% kit = 100% camo on guy
3468 // add 1 to round off
3469 bPointsToUse = (100 - pSoldier->bCamo + 1 ) / 2;
3470 bPointsToUse = __min( bPointsToUse, usTotalKitPoints );
3471 pSoldier->bCamo = __min( 100, pSoldier->bCamo + bPointsToUse * 2);
3472
3473 UseKitPoints(*pObj, bPointsToUse, *pSoldier);
3474
3475 DeductPoints( pSoldier, AP_CAMOFLAGE, 0 );
3476
3477 // Reload palettes....
3478 if ( pSoldier->bInSector )
3479 {
3480 CreateSoldierPalettes(*pSoldier);
3481 }
3482
3483 return( TRUE );
3484 }
3485
ApplyCanteen(SOLDIERTYPE * pSoldier,OBJECTTYPE * pObj,BOOLEAN * pfGoodAPs)3486 BOOLEAN ApplyCanteen( SOLDIERTYPE * pSoldier, OBJECTTYPE * pObj, BOOLEAN *pfGoodAPs )
3487 {
3488 INT16 sPointsToUse;
3489 UINT16 usTotalKitPoints;
3490
3491 (*pfGoodAPs) = TRUE;
3492
3493 if (pObj->usItem != CANTEEN)
3494 {
3495 return( FALSE );
3496 }
3497
3498 usTotalKitPoints = TotalPoints( pObj );
3499 if (usTotalKitPoints == 0)
3500 {
3501 // HUH???
3502 return( FALSE );
3503 }
3504
3505 if (!EnoughPoints( pSoldier, AP_DRINK, 0, TRUE ) )
3506 {
3507 (*pfGoodAPs) = FALSE;
3508 return( TRUE );
3509 }
3510
3511 if ( pSoldier->bTeam == OUR_TEAM )
3512 {
3513 if ( gMercProfiles[ pSoldier->ubProfile ].bSex == MALE )
3514 {
3515 PlayJA2Sample(DRINK_CANTEEN_MALE, MIDVOLUME, 1, MIDDLEPAN);
3516 }
3517 else
3518 {
3519 PlayJA2Sample(DRINK_CANTEEN_FEMALE, MIDVOLUME, 1, MIDDLEPAN);
3520 }
3521 }
3522
3523 sPointsToUse = __min( 20, usTotalKitPoints );
3524
3525 // CJC Feb 9. Canteens don't seem effective enough, so doubled return from them
3526 DeductPoints( pSoldier, AP_DRINK, (INT16) (2 * sPointsToUse * -(100 - pSoldier->bBreath) ) );
3527
3528 UseKitPoints(*pObj, sPointsToUse, *pSoldier);
3529
3530 return( TRUE );
3531 }
3532
3533 #define MAX_HUMAN_CREATURE_SMELL (NORMAL_HUMAN_SMELL_STRENGTH - 1)
3534
ApplyElixir(SOLDIERTYPE * pSoldier,OBJECTTYPE * pObj,BOOLEAN * pfGoodAPs)3535 BOOLEAN ApplyElixir( SOLDIERTYPE * pSoldier, OBJECTTYPE * pObj, BOOLEAN *pfGoodAPs )
3536 {
3537 INT16 sPointsToUse;
3538 UINT16 usTotalKitPoints;
3539
3540 (*pfGoodAPs) = TRUE;
3541
3542 if (pObj->usItem != JAR_ELIXIR )
3543 {
3544 return( FALSE );
3545 }
3546
3547 usTotalKitPoints = TotalPoints( pObj );
3548 if (usTotalKitPoints == 0)
3549 {
3550 // HUH???
3551 return( FALSE );
3552 }
3553
3554 if (!EnoughPoints( pSoldier, AP_CAMOFLAGE, 0, TRUE ) )
3555 {
3556 (*pfGoodAPs) = FALSE;
3557 return( TRUE );
3558 }
3559
3560 DeductPoints( pSoldier, AP_CAMOFLAGE, 0 );
3561
3562 sPointsToUse = ( MAX_HUMAN_CREATURE_SMELL - pSoldier->bMonsterSmell ) * 2;
3563 sPointsToUse = __min( sPointsToUse, usTotalKitPoints );
3564
3565 UseKitPoints(*pObj, sPointsToUse, *pSoldier);
3566
3567 pSoldier->bMonsterSmell += sPointsToUse / 2;
3568
3569 return( TRUE );
3570 }
3571
3572
ItemIsCool(OBJECTTYPE const & o)3573 bool ItemIsCool(OBJECTTYPE const& o)
3574 {
3575 if (o.bStatus[0] < 60) return false;
3576 const ItemModel * item = GCM->getItem(o.usItem);
3577 if (item->isWeapon())
3578 {
3579 if (GCM->getWeapon(o.usItem)->ubDeadliness >= 30) return true;
3580 }
3581 else if (item->isArmour())
3582 {
3583 if (Armour[item->getClassIndex()].ubProtection >= 20) return true;
3584 }
3585
3586 return false;
3587 }
3588
ActivateXRayDevice(SOLDIERTYPE * pSoldier)3589 void ActivateXRayDevice( SOLDIERTYPE * pSoldier )
3590 {
3591 INT8 bBatteries;
3592
3593 // check for batteries
3594 bBatteries = FindAttachment( &(pSoldier->inv[HANDPOS]), BATTERIES );
3595 if ( bBatteries == NO_SLOT )
3596 {
3597 // doesn't work without batteries!
3598 return;
3599 }
3600
3601 // use up 8-12 percent of batteries
3602 pSoldier->inv[ HANDPOS ].bAttachStatus[ bBatteries ] -= (INT8) (8 + Random( 5 ));
3603 if ( pSoldier->inv[ HANDPOS ].bAttachStatus[ bBatteries ] <= 0 )
3604 {
3605 // destroy batteries
3606 pSoldier->inv[ HANDPOS ].usAttachItem[ bBatteries ] = NOTHING;
3607 pSoldier->inv[ HANDPOS ].bAttachStatus[ bBatteries ] = 0;
3608 }
3609
3610 // first, scan through all mercs and turn off xrayed flag for anyone
3611 // previously xrayed by this guy
3612 FOR_EACH_MERC(i)
3613 {
3614 SOLDIERTYPE* const tgt = *i;
3615 if (tgt->ubMiscSoldierFlags & SOLDIER_MISC_XRAYED &&
3616 tgt->xrayed_by == pSoldier)
3617 {
3618 tgt->ubMiscSoldierFlags &= ~SOLDIER_MISC_XRAYED;
3619 tgt->xrayed_by = NULL;
3620 }
3621 }
3622 // now turn on xray for anyone within range
3623 FOR_EACH_MERC(i)
3624 {
3625 SOLDIERTYPE* const tgt = *i;
3626 if (tgt->bTeam != pSoldier->bTeam &&
3627 PythSpacesAway(pSoldier->sGridNo, tgt->sGridNo) < XRAY_RANGE)
3628 {
3629 tgt->ubMiscSoldierFlags |= SOLDIER_MISC_XRAYED;
3630 tgt->xrayed_by = pSoldier;
3631 }
3632 }
3633 pSoldier->uiXRayActivatedTime = GetWorldTotalSeconds();
3634 }
3635
TurnOffXRayEffects(SOLDIERTYPE * pSoldier)3636 void TurnOffXRayEffects( SOLDIERTYPE * pSoldier )
3637 {
3638 if ( !pSoldier->uiXRayActivatedTime )
3639 {
3640 return;
3641 }
3642
3643 // scan through all mercs and turn off xrayed flag for anyone
3644 // xrayed by this guy
3645 FOR_EACH_MERC(i)
3646 {
3647 SOLDIERTYPE* const tgt = *i;
3648 if (tgt->ubMiscSoldierFlags & SOLDIER_MISC_XRAYED &&
3649 tgt->xrayed_by == pSoldier)
3650 {
3651 tgt->ubMiscSoldierFlags &= ~SOLDIER_MISC_XRAYED;
3652 tgt->xrayed_by = NULL;
3653 }
3654 }
3655 pSoldier->uiXRayActivatedTime = 0;
3656 }
3657
3658
HasObjectImprint(OBJECTTYPE const & o)3659 bool HasObjectImprint(OBJECTTYPE const& o)
3660 {
3661 return (o.usItem == ROCKET_RIFLE || o.usItem == AUTO_ROCKET_RIFLE) &&
3662 o.ubImprintID != NO_PROFILE;
3663 }
3664