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