Bug Summary

File:game/inventory.cpp
Location:line 27, column 5
Description:Access to field 'next' results in a dereference of a null pointer (loaded from variable 'prev')

Annotated Source Code

1#include "inventory.h"
2
3static inline void I_Free (inventoryInterface_t* self, void *data)
4{
5 self->import->Free(data);
6}
7
8static inline void *I_Alloc (inventoryInterface_t* self, size_t size)
9{
10 return self->import->Alloc(size);
11}
12
13static void I_RemoveInvList (inventoryInterface_t* self, invList_t *invList)
14{
15 Com_DPrintf(DEBUG_SHARED0x02, "I_RemoveInvList: remove one slot (%s)\n", self->name);
16
17 /* first entry */
18 if (self->invList == invList) {
14
Taking false branch
19 invList_t *ic = self->invList;
20 self->invList = ic->next;
21 I_Free(self, ic);
22 } else {
23 invList_t *ic = self->invList;
24 invList_t* prev = NULL__null;
25 while (ic) {
15
Loop condition is true. Entering loop body
26 if (ic == invList) {
16
Taking true branch
27 prev->next = ic->next;
17
Access to field 'next' results in a dereference of a null pointer (loaded from variable 'prev')
28 I_Free(self, ic);
29 break;
30 }
31 prev = ic;
32 ic = ic->next;
33 }
34 }
35}
36
37static invList_t* I_AddInvList (inventoryInterface_t* self, invList_t **invList)
38{
39 invList_t *newEntry;
40 invList_t *list;
41
42 Com_DPrintf(DEBUG_SHARED0x02, "I_AddInvList: add one slot (%s)\n", self->name);
43
44 /* create the list */
45 if (!*invList) {
46 *invList = (invList_t*)I_Alloc(self, sizeof(**invList));
47 (*invList)->next = NULL__null; /* not really needed - but for better readability */
48 return *invList;
49 } else
50 list = *invList;
51
52 while (list->next)
53 list = list->next;
54
55 newEntry = (invList_t*)I_Alloc(self, sizeof(*newEntry));
56 list->next = newEntry;
57 newEntry->next = NULL__null; /* not really needed - but for better readability */
58
59 return newEntry;
60}
61
62/**
63 * @brief Add an item to a specified container in a given inventory.
64 * @note Set x and y to NONE if the item should get added to an automatically chosen free spot in the container.
65 * @param[in] self The inventory interface pointer
66 * @param[in,out] inv Pointer to inventory definition, to which we will add item.
67 * @param[in] item Item to add to given container (needs to have "rotated" tag already set/checked, this is NOT checked here!)
68 * @param[in] container Container in given inventory definition, where the new item will be stored.
69 * @param[in] x The x location in the container.
70 * @param[in] y The x location in the container.
71 * @param[in] amount How many items of this type should be added. (this will overwrite the amount as defined in "item.amount")
72 * @sa I_RemoveFromInventory
73 * @return the @c invList_t pointer the item was added to, or @c NULL in case of an error (item wasn't added)
74 */
75static invList_t *I_AddToInventory (inventoryInterface_t* self, inventory_t * const inv, const item_t* const item, const invDef_t * container, int x, int y, int amount)
76{
77 invList_t *ic;
78 int checkedTo;
79
80 if (!item->t)
81 return NULL__null;
82
83 if (amount <= 0)
84 return NULL__null;
85
86 assert(inv)(__builtin_expect(!(inv), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 86, "inv") : (void)0)
;
87 assert(container)(__builtin_expect(!(container), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 87, "container") : (void)0)
;
88
89 if (container->single && inv->c[container->id] && inv->c[container->id]->next)
90 return NULL__null;
91
92 /* idEquip and idFloor */
93 if (container->temp) {
94 for (ic = inv->c[container->id]; ic; ic = ic->next)
95 if (INVSH_CompareItem(&ic->item, item)) {
96 ic->item.amount += amount;
97 Com_DPrintf(DEBUG_SHARED0x02, "I_AddToInventory: Amount of '%s': %i (%s)\n",
98 ic->item.t->name, ic->item.amount, self->name);
99 return ic;
100 }
101 }
102
103 if (x < 0 || y < 0 || x >= SHAPE_BIG_MAX_WIDTH32 || y >= SHAPE_BIG_MAX_HEIGHT16) {
104 /* No (sane) position in container given as parameter - find free space on our own. */
105 INVSH_FindSpace(inv, item, container, &x, &y, NULL__null);
106 if (x == NONE-1)
107 return NULL__null;
108 }
109
110 checkedTo = INVSH_CheckToInventory(inv, item->t, container, x, y, NULL__null);
111 assert(checkedTo)(__builtin_expect(!(checkedTo), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 111, "checkedTo") : (void)0)
;
112
113 /* not found - add a new one */
114 ic = I_AddInvList(self, &inv->c[container->id]);
115
116 /* Set the data in the new entry to the data we got via function-parameters.*/
117 ic->item = *item;
118 ic->item.amount = amount;
119
120 /* don't reset an already applied rotation */
121 if (checkedTo == INV_FITS_ONLY_ROTATED)
122 ic->item.rotated = true;
123 ic->x = x;
124 ic->y = y;
125
126 return ic;
127}
128
129/**
130 * @param[in] self The inventory interface pointer
131 * @param[in] i The inventory the container is in.
132 * @param[in] container The container where the item should be removed.
133 * @param[in] fItem The item to be removed.
134 * @return true If removal was successful.
135 * @return false If nothing was removed or an error occurred.
136 * @sa I_AddToInventory
137 */
138static bool I_RemoveFromInventory (inventoryInterface_t* self, inventory_t* const i, const invDef_t * container, invList_t *fItem)
139{
140 invList_t *ic, *previous;
141
142 assert(i)(__builtin_expect(!(i), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 142, "i") : (void)0)
;
143 assert(container)(__builtin_expect(!(container), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 143, "container") : (void)0)
;
144 assert(fItem)(__builtin_expect(!(fItem), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 144, "fItem") : (void)0)
;
145
146 ic = i->c[container->id];
147 if (!ic)
1
Taking false branch
148 return false;
149
150 /** @todo the problem here is, that in case of a move inside the same container
151 * the item don't just get updated x and y values but it is tried to remove
152 * one of the items => crap - maybe we have to change the inventory move function
153 * to check for this case of move and only update the x and y coordinates instead
154 * of calling the add and remove functions */
155 if (container->single || ic == fItem) {
2
Taking false branch
156 self->cacheItem = ic->item;
157 /* temp container like idEquip and idFloor */
158 if (container->temp && ic->item.amount > 1) {
159 ic->item.amount--;
160 Com_DPrintf(DEBUG_SHARED0x02, "I_RemoveFromInventory: Amount of '%s': %i (%s)\n",
161 ic->item.t->name, ic->item.amount, self->name);
162 return true;
163 }
164
165 if (container->single && ic->next)
166 Com_Printf("I_RemoveFromInventory: Error: single container %s has many items. (%s)\n", container->name, self->name);
167
168 /* An item in other containers than idFloor or idEquip should
169 * always have an amount value of 1.
170 * The other container types do not support stacking.*/
171 assert(ic->item.amount == 1)(__builtin_expect(!(ic->item.amount == 1), 0) ? __assert_rtn
(__func__, "src/game/inventory.cpp", 171, "ic->item.amount == 1"
) : (void)0)
;
172
173 i->c[container->id] = ic->next;
174
175 /* updated invUnused to be able to reuse this space later again */
176 I_RemoveInvList(self, ic);
177
178 return true;
179 }
180
181 for (previous = i->c[container->id]; ic; ic = ic->next) {
3
Loop condition is true. Entering loop body
5
Loop condition is true. Entering loop body
7
Loop condition is true. Entering loop body
9
Loop condition is true. Entering loop body
182 if (ic == fItem) {
4
Taking false branch
6
Taking false branch
8
Taking false branch
10
Taking true branch
183 self->cacheItem = ic->item;
184 /* temp container like idEquip and idFloor */
185 if (ic->item.amount > 1 && container->temp) {
11
Taking false branch
186 ic->item.amount--;
187 Com_DPrintf(DEBUG_SHARED0x02, "I_RemoveFromInventory: Amount of '%s': %i (%s)\n",
188 ic->item.t->name, ic->item.amount, self->name);
189 return true;
190 }
191
192 if (ic == i->c[container->id])
12
Taking false branch
193 i->c[container->id] = i->c[container->id]->next;
194 else
195 previous->next = ic->next;
196
197 I_RemoveInvList(self, ic);
13
Calling 'I_RemoveInvList'
198
199 return true;
200 }
201 previous = ic;
202 }
203 return false;
204}
205
206/**
207 * @brief Conditions for moving items between containers.
208 * @param[in] self The inventory interface pointer
209 * @param[in] inv Inventory to move in.
210 * @param[in] from Source container.
211 * @param[in] fItem The item to be moved.
212 * @param[in] to Destination container.
213 * @param[in] tx X coordinate in destination container.
214 * @param[in] ty Y coordinate in destination container.
215 * @param[in,out] TU pointer to entity available TU at this moment
216 * or @c NULL if TU doesn't matter (outside battlescape)
217 * @param[out] icp
218 * @return IA_NOTIME when not enough TU.
219 * @return IA_NONE if no action possible.
220 * @return IA_NORELOAD if you cannot reload a weapon.
221 * @return IA_RELOAD_SWAP in case of exchange of ammo in a weapon.
222 * @return IA_RELOAD when reloading.
223 * @return IA_ARMOUR when placing an armour on the actor.
224 * @return IA_MOVE when just moving an item.
225 */
226static inventory_action_t I_MoveInInventory (inventoryInterface_t* self, inventory_t* const inv, const invDef_t * from, invList_t *fItem, const invDef_t * to, int tx, int ty, int *TU, invList_t ** icp)
227{
228 invList_t *ic;
229
230 int time;
231 int checkedTo = INV_DOES_NOT_FIT;
232 bool alreadyRemovedSource = false;
233
234 assert(to)(__builtin_expect(!(to), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 234, "to") : (void)0)
;
235 assert(from)(__builtin_expect(!(from), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 235, "from") : (void)0)
;
236
237 if (icp)
238 *icp = NULL__null;
239
240 if (from == to && fItem->x == tx && fItem->y == ty)
241 return IA_NONE;
242
243 time = from->out + to->in;
244 if (from == to) {
245 if (INV_IsFloorDef(from))
246 time = 0;
247 else
248 time /= 2;
249 }
250
251 if (TU && *TU < time)
252 return IA_NOTIME;
253
254 assert(inv)(__builtin_expect(!(inv), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 254, "inv") : (void)0)
;
255
256 /* Special case for moving an item within the same container. */
257 if (from == to) {
258 /* Do nothing if we move inside a scroll container. */
259 if (from->scroll)
260 return IA_NONE;
261
262 ic = inv->c[from->id];
263 for (; ic; ic = ic->next) {
264 if (ic == fItem) {
265 if (ic->item.amount > 1) {
266 checkedTo = INVSH_CheckToInventory(inv, ic->item.t, to, tx, ty, fItem);
267 if (checkedTo & INV_FITS) {
268 ic->x = tx;
269 ic->y = ty;
270 if (icp)
271 *icp = ic;
272 return IA_MOVE;
273 }
274 return IA_NONE;
275 }
276 }
277 }
278 }
279
280 /* If weapon is twohanded and is moved from hand to hand do nothing. */
281 /* Twohanded weapon are only in CSI->idRight. */
282 if (fItem->item.t->fireTwoHanded && INV_IsLeftDef(to) && INV_IsRightDef(from)) {
283 return IA_NONE;
284 }
285
286 /* If non-armour moved to an armour slot then abort.
287 * Same for non extension items when moved to an extension slot. */
288 if ((to->armour && !INV_IsArmour(fItem->item.t)((strcmp((fItem->item.t)->type, "armour") == 0)))
289 || (to->extension && !fItem->item.t->extension)
290 || (to->headgear && !fItem->item.t->headgear)) {
291 return IA_NONE;
292 }
293
294 /* Check if the target is a blocked inv-armour and source!=dest. */
295 if (to->single)
296 checkedTo = INVSH_CheckToInventory(inv, fItem->item.t, to, 0, 0, fItem);
297 else {
298 if (tx == NONE-1 || ty == NONE-1)
299 INVSH_FindSpace(inv, &fItem->item, to, &tx, &ty, fItem);
300 /* still no valid location found */
301 if (tx == NONE-1 || ty == NONE-1)
302 return IA_NONE;
303
304 checkedTo = INVSH_CheckToInventory(inv, fItem->item.t, to, tx, ty, fItem);
305 }
306
307 if (to->armour && from != to && !checkedTo) {
308 item_t cacheItem2;
309 invList_t *icTo;
310 /* Store x/y origin coordinates of removed (source) item.
311 * When we re-add it we can use this. */
312 const int cacheFromX = fItem->x;
313 const int cacheFromY = fItem->y;
314
315 /* Check if destination/blocking item is the same as source/from item.
316 * In that case the move is not needed -> abort. */
317 icTo = INVSH_SearchInInventory(inv, to, tx, ty);
318 if (fItem->item.t == icTo->item.t)
319 return IA_NONE;
320
321 /* Actually remove the ammo from the 'from' container. */
322 if (!self->RemoveFromInventory(self, inv, from, fItem))
323 return IA_NONE;
324 else
325 /* Removal successful - store this info. */
326 alreadyRemovedSource = true;
327
328 cacheItem2 = self->cacheItem; /* Save/cache (source) item. The cacheItem is modified in I_MoveInInventory. */
329
330 /* Move the destination item to the source. */
331 self->MoveInInventory(self, inv, to, icTo, from, cacheFromX, cacheFromY, TU, icp);
332
333 /* Reset the cached item (source) (It'll be move to container emptied by destination item later.) */
334 self->cacheItem = cacheItem2;
335 } else if (!checkedTo) {
336 /* Get the target-invlist (e.g. a weapon). We don't need to check for
337 * scroll because checkedTo is always true here. */
338 ic = INVSH_SearchInInventory(inv, to, tx, ty);
339
340 if (ic && !INV_IsEquipDef(to) && INVSH_LoadableInWeapon(fItem->item.t, ic->item.t)) {
341 /* A target-item was found and the dragged item (implicitly ammo)
342 * can be loaded in it (implicitly weapon). */
343 if (ic->item.a >= ic->item.t->ammo && ic->item.m == fItem->item.t) {
344 /* Weapon already fully loaded with the same ammunition -> abort */
345 return IA_NORELOAD;
346 }
347 time += ic->item.t->reload;
348 if (!TU || *TU >= time) {
349 if (TU)
350 *TU -= time;
351 if (ic->item.a >= ic->item.t->ammo) {
352 /* exchange ammo */
353 const item_t item = {NONE_AMMO0, NULL__null, ic->item.m, 0, 0};
354 /* Put current ammo in place of the new ammo unless floor - there can be more than 1 item */
355 const int cacheFromX = INV_IsFloorDef(from) ? NONE-1 : fItem->x;
356 const int cacheFromY = INV_IsFloorDef(from) ? NONE-1 : fItem->y;
357
358 /* Actually remove the ammo from the 'from' container. */
359 if (!self->RemoveFromInventory(self, inv, from, fItem))
360 return IA_NONE;
361
362 /* Add the currently used ammo in place of the new ammo in the "from" container. */
363 if (self->AddToInventory(self, inv, &item, from, cacheFromX, cacheFromY, 1) == NULL__null)
364 Sys_Error("Could not reload the weapon - add to inventory failed (%s)", self->name);
365
366 ic->item.m = self->cacheItem.t;
367 if (icp)
368 *icp = ic;
369 return IA_RELOAD_SWAP;
370 } else {
371 /* Actually remove the ammo from the 'from' container. */
372 if (!self->RemoveFromInventory(self, inv, from, fItem))
373 return IA_NONE;
374
375 ic->item.m = self->cacheItem.t;
376 /* loose ammo of type ic->item.m saved on server side */
377 ic->item.a = ic->item.t->ammo;
378 if (icp)
379 *icp = ic;
380 return IA_RELOAD;
381 }
382 }
383 /* Not enough time -> abort. */
384 return IA_NOTIME;
385 }
386
387 /* temp container like idEquip and idFloor */
388 if (ic && to->temp) {
389 /* We are moving to a blocked location container but it's the base-equipment floor or a battlescape floor.
390 * We add the item anyway but it'll not be displayed (yet)
391 * This is then used in I_AddToInventory below.*/
392 /** @todo change the other code to browse trough these things. */
393 INVSH_FindSpace(inv, &fItem->item, to, &tx, &ty, fItem);
394 if (tx == NONE-1 || ty == NONE-1) {
395 Com_DPrintf(DEBUG_SHARED0x02, "I_MoveInInventory - item will be added non-visible (%s)\n", self->name);
396 }
397 } else {
398 /* Impossible move -> abort. */
399 return IA_NONE;
400 }
401 }
402
403 /* twohanded exception - only CSI->idRight is allowed for fireTwoHanded weapons */
404 if (fItem->item.t->fireTwoHanded && INV_IsLeftDef(to))
405 to = &self->csi->ids[self->csi->idRight];
406
407 if (checkedTo == INV_FITS_ONLY_ROTATED) {
408 /* Set rotated tag */
409 fItem->item.rotated = true;
410 } else if (fItem->item.rotated) {
411 /* Remove rotated tag */
412 fItem->item.rotated = false;
413 }
414
415 /* Actually remove the item from the 'from' container (if it wasn't already removed). */
416 if (!alreadyRemovedSource)
417 if (!self->RemoveFromInventory(self, inv, from, fItem))
418 return IA_NONE;
419
420 /* successful */
421 if (TU)
422 *TU -= time;
423
424 assert(self->cacheItem.t)(__builtin_expect(!(self->cacheItem.t), 0) ? __assert_rtn(
__func__, "src/game/inventory.cpp", 424, "self->cacheItem.t"
) : (void)0)
;
425 ic = self->AddToInventory(self, inv, &self->cacheItem, to, tx, ty, 1);
426
427 /* return data */
428 if (icp) {
429 assert(ic)(__builtin_expect(!(ic), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 429, "ic") : (void)0)
;
430 *icp = ic;
431 }
432
433 if (INV_IsArmourDef(to)) {
434 assert(INV_IsArmour(self->cacheItem.t))(__builtin_expect(!(((strcmp((self->cacheItem.t)->type,
"armour") == 0))), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 434, "INV_IsArmour(self->cacheItem.t)") : (void)0)
;
435 return IA_ARMOUR;
436 } else
437 return IA_MOVE;
438}
439
440/**
441 * @brief Tries to add an item to a container (in the inventory inv).
442 * @param[in] self The inventory interface pointer
443 * @param[in] inv Inventory pointer to add the item.
444 * @param[in] item Item to add to inventory.
445 * @param[in] container Container id.
446 * @sa INVSH_FindSpace
447 * @sa I_AddToInventory
448 */
449static bool I_TryAddToInventory (inventoryInterface_t* self, inventory_t* const inv, const item_t * const item, const invDef_t * container)
450{
451 int x, y;
452
453 INVSH_FindSpace(inv, item, container, &x, &y, NULL__null);
454
455 if (x == NONE-1) {
456 assert(y == NONE)(__builtin_expect(!(y == -1), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 456, "y == NONE") : (void)0)
;
457 return false;
458 } else {
459 const int checkedTo = INVSH_CheckToInventory(inv, item->t, container, x, y, NULL__null);
460 if (!checkedTo)
461 return false;
462 else {
463 item_t itemRotation = *item;
464 if (checkedTo == INV_FITS_ONLY_ROTATED)
465 itemRotation.rotated = true;
466 else
467 itemRotation.rotated = false;
468
469 return self->AddToInventory(self, inv, &itemRotation, container, x, y, 1) != NULL__null;
470 }
471 }
472}
473
474/**
475 * @brief Clears the linked list of a container - removes all items from this container.
476 * @param[in] self The inventory interface pointer
477 * @param[in] i The inventory where the container is located.
478 * @param[in] container Index of the container which will be cleared.
479 * @sa I_DestroyInventory
480 * @note This should only be called for temp containers if the container is really a temp container
481 * e.g. the container of a dropped weapon in tactical mission (ET_ITEM)
482 * in every other case just set the pointer to NULL for a temp container like idEquip or idFloor
483 */
484static void I_EmptyContainer (inventoryInterface_t* self, inventory_t* const i, const invDef_t * container)
485{
486 invList_t *ic;
487
488 ic = i->c[container->id];
489
490 while (ic) {
491 invList_t *old = ic;
492 ic = ic->next;
493 I_RemoveInvList(self, old);
494 }
495
496 i->c[container->id] = NULL__null;
497}
498
499/**
500 * @brief Destroys inventory.
501 * @param[in] self The inventory interface pointer
502 * @param[in] inv Pointer to the inventory which should be erased.
503 * @note Loops through all containers in inventory. @c NULL for temp containers are skipped,
504 * for real containers @c I_EmptyContainer is called.
505 * @sa I_EmptyContainer
506 */
507static void I_DestroyInventory (inventoryInterface_t* self, inventory_t* const inv)
508{
509 containerIndex_t container;
510
511 if (!inv)
512 return;
513
514 for (container = 0; container < self->csi->numIDs; container++) {
515 const invDef_t *invDef = &self->csi->ids[container];
516 if (!invDef->temp)
517 self->EmptyContainer(self, inv, invDef);
518 }
519
520 OBJZERO(*inv)(memset(&((*inv)), (0), sizeof((*inv))));
521}
522
523
524#define WEAPONLESS_BONUS0.4 0.4 /* if you got neither primary nor secondary weapon, this is the chance to retry to get one (before trying to get grenades or blades) */
525
526/**
527 * @brief Pack a weapon, possibly with some ammo
528 * @param[in] self The inventory interface pointer
529 * @param[in] inv The inventory that will get the weapon
530 * @param[in] weapon The weapon type index in gi.csi->ods
531 * @param[in] ed The equipment for debug messages
532 * @param[in] missedPrimary if actor didn't get primary weapon, this is 0-100 number to increase ammo number.
533 * @sa INVSH_LoadableInWeapon
534 */
535static int I_PackAmmoAndWeapon (inventoryInterface_t *self, inventory_t* const inv, const objDef_t* weapon, int missedPrimary, const equipDef_t *ed)
536{
537 const objDef_t *ammo = NULL__null;
538 item_t item = {NONE_AMMO0, NULL__null, NULL__null, 0, 0};
539 bool allowLeft;
540 bool packed;
541 int ammoMult = 1;
542
543 assert(!INV_IsArmour(weapon))(__builtin_expect(!(!((strcmp((weapon)->type, "armour") ==
0))), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp", 543
, "!INV_IsArmour(weapon)") : (void)0)
;
544 item.t = weapon;
545
546 /* are we going to allow trying the left hand */
547 allowLeft = !(inv->c[self->csi->idRight] && inv->c[self->csi->idRight]->item.t->fireTwoHanded);
548
549 if (weapon->oneshot) {
550 /* The weapon provides its own ammo (i.e. it is charged or loaded in the base.) */
551 item.a = weapon->ammo;
552 item.m = weapon;
553 Com_DPrintf(DEBUG_SHARED0x02, "I_PackAmmoAndWeapon: oneshot weapon '%s' in equipment '%s' (%s).\n",
554 weapon->id, ed->id, self->name);
555 } else if (!weapon->reload) {
556 item.m = item.t; /* no ammo needed, so fire definitions are in t */
557 } else {
558 /* find some suitable ammo for the weapon (we will have at least one if there are ammos for this
559 * weapon in equipment definition) */
560 int totalAvailableAmmo = 0;
561 int i;
562 for (i = 0; i < self->csi->numODs; i++) {
563 const objDef_t *obj = INVSH_GetItemByIDX(i);
564 if (ed->numItems[i] && INVSH_LoadableInWeapon(obj, weapon)) {
565 totalAvailableAmmo++;
566 }
567 }
568 if (totalAvailableAmmo) {
569 int randNumber = rand() % totalAvailableAmmo;
570 for (i = 0; i < self->csi->numODs; i++) {
571 const objDef_t *obj = INVSH_GetItemByIDX(i);
572 if (ed->numItems[i] && INVSH_LoadableInWeapon(obj, weapon)) {
573 randNumber--;
574 if (randNumber < 0) {
575 ammo = obj;
576 break;
577 }
578 }
579 }
580 }
581
582 if (!ammo) {
583 Com_DPrintf(DEBUG_SHARED0x02, "I_PackAmmoAndWeapon: no ammo for sidearm or primary weapon '%s' in equipment '%s' (%s).\n",
584 weapon->id, ed->id, self->name);
585 return 0;
586 }
587 /* load ammo */
588 item.a = weapon->ammo;
589 item.m = ammo;
590 }
591
592 if (!item.m) {
593 Com_Printf("I_PackAmmoAndWeapon: no ammo for sidearm or primary weapon '%s' in equipment '%s' (%s).\n",
594 weapon->id, ed->id, self->name);
595 return 0;
596 }
597
598 /* now try to pack the weapon */
599 packed = self->TryAddToInventory(self, inv, &item, &self->csi->ids[self->csi->idRight]);
600 if (packed)
601 ammoMult = 3;
602 if (!packed && allowLeft)
603 packed = self->TryAddToInventory(self, inv, &item, &self->csi->ids[self->csi->idLeft]);
604 if (!packed)
605 packed = self->TryAddToInventory(self, inv, &item, &self->csi->ids[self->csi->idBelt]);
606 if (!packed)
607 packed = self->TryAddToInventory(self, inv, &item, &self->csi->ids[self->csi->idHolster]);
608 if (!packed)
609 return 0;
610
611
612 /* pack some more ammo in the backpack */
613 if (ammo) {
614 int num;
615 int numpacked = 0;
616
617 /* how many clips? */
618 num = (1 + ed->numItems[ammo->idx])
619 * (float) (1.0f + missedPrimary / 100.0);
620
621 /* pack some ammo */
622 while (num--) {
623 item_t mun = {NONE_AMMO0, NULL__null, NULL__null, 0, 0};
624
625 mun.t = ammo;
626 /* ammo to backpack; belt is for knives and grenades */
627 numpacked += self->TryAddToInventory(self, inv, &mun, &self->csi->ids[self->csi->idBackpack]);
628 /* no problem if no space left; one ammo already loaded */
629 if (numpacked > ammoMult || numpacked * weapon->ammo > 11)
630 break;
631 }
632 }
633
634 return true;
635}
636
637
638/**
639 * @brief Equip melee actor with item defined per teamDefs.
640 * @param[in] self The inventory interface pointer
641 * @param[in] inv The inventory that will get the weapon.
642 * @param[in] td Pointer to a team definition.
643 * @note Weapons assigned here cannot be collected in any case. These are dummy "actor weapons".
644 */
645static void I_EquipActorMelee (inventoryInterface_t *self, inventory_t* const inv, const teamDef_t* td)
646{
647 const objDef_t *obj;
648 item_t item;
649
650 assert(td->onlyWeapon)(__builtin_expect(!(td->onlyWeapon), 0) ? __assert_rtn(__func__
, "src/game/inventory.cpp", 650, "td->onlyWeapon") : (void
)0)
;
651
652 /* Get weapon */
653 obj = td->onlyWeapon;
654
655 /* Prepare item. This kind of item has no ammo, fire definitions are in item.t. */
656 item.t = obj;
657 item.m = item.t;
658 item.a = NONE_AMMO0;
659 /* Every melee actor weapon definition is firetwohanded, add to right hand. */
660 if (!obj->fireTwoHanded)
661 Sys_Error("INVSH_EquipActorMelee: melee weapon %s for team %s is not firetwohanded! (%s)\n",
662 obj->id, td->id, self->name);
663 self->TryAddToInventory(self, inv, &item, &self->csi->ids[self->csi->idRight]);
664}
665
666/**
667 * @brief Equip robot actor with default weapon. (defined in ugv_t->weapon)
668 * @param[in] self The inventory interface pointer
669 * @param[in] inv The inventory that will get the weapon.
670 * @param[in] weapon Pointer to the item which being added to robot's inventory.
671 */
672static void I_EquipActorRobot (inventoryInterface_t *self, inventory_t* const inv, const objDef_t* weapon)
673{
674 item_t item;
675
676 assert(weapon)(__builtin_expect(!(weapon), 0) ? __assert_rtn(__func__, "src/game/inventory.cpp"
, 676, "weapon") : (void)0)
;
677
678 /* Prepare weapon in item. */
679 item.t = weapon;
680 item.a = NONE_AMMO0;
681
682 /* Get ammo for item/weapon. */
683 assert(weapon->numAmmos > 0)(__builtin_expect(!(weapon->numAmmos > 0), 0) ? __assert_rtn
(__func__, "src/game/inventory.cpp", 683, "weapon->numAmmos > 0"
) : (void)0)
; /* There _has_ to be at least one ammo-type. */
684 assert(weapon->ammos[0])(__builtin_expect(!(weapon->ammos[0]), 0) ? __assert_rtn(__func__
, "src/game/inventory.cpp", 684, "weapon->ammos[0]") : (void
)0)
;
685 item.m = weapon->ammos[0];
686
687 self->TryAddToInventory(self, inv, &item, &self->csi->ids[self->csi->idRight]);
688}
689
690/**
691 * @brief Types of weapon that can be selected
692 */
693typedef enum {
694 WEAPON_PARTICLE_OR_NORMAL = 0, /**< primary weapon is a particle or normal weapon */
695 WEAPON_OTHER = 1, /**< primary weapon is not a particle or normal weapon */
696 WEAPON_NO_PRIMARY = 2 /**< no primary weapon */
697} equipPrimaryWeaponType_t;
698
699/**
700 * @brief Fully equip one actor. The equipment that is added to the inventory of the given actor
701 * is taken from the equipment script definition.
702 * @param[in] self The inventory interface pointer
703 * @param[in] inv The inventory that will get the weapon.
704 * @param[in] ed The equipment that is added from to the actors inventory
705 * @param[in] td Pointer to teamdef data - to get the weapon and armour bools.
706 * @note The code below is a complete implementation
707 * of the scheme sketched at the beginning of equipment_missions.ufo.
708 * Beware: If two weapons in the same category have the same price,
709 * only one will be considered for inventory.
710 */
711static void I_EquipActor (inventoryInterface_t* self, inventory_t* const inv, const equipDef_t *ed, const teamDef_t* td)
712{
713 int i;
714 const int numEquip = lengthof(ed->numItems)(sizeof(ed->numItems) / sizeof(*(ed->numItems)));
715 int repeat = 0;
716 const float AKIMBO_CHANCE = 0.3; /**< if you got a one-handed secondary weapon (and no primary weapon),
717 this is the chance to get another one (between 0 and 1) */
718
719 if (td->weapons) {
720 equipPrimaryWeaponType_t primary = WEAPON_NO_PRIMARY;
721 int sum;
722 int missedPrimary = 0; /**< If actor has a primary weapon, this is zero. Otherwise, this is the probability * 100
723 * that the actor had to get a primary weapon (used to compensate the lack of primary weapon) */
724 const objDef_t *primaryWeapon = NULL__null;
725 int hasWeapon = 0;
726 /* Primary weapons */
727 const int maxWeaponIdx = std::min(self->csi->numODs - 1, numEquip - 1);
728 int randNumber = rand() % 100;
729 for (i = 0; i < maxWeaponIdx; i++) {
730 const objDef_t *obj = INVSH_GetItemByIDX(i);
731 if (ed->numItems[i] && obj->weapon && obj->fireTwoHanded && obj->isPrimary) {
732 randNumber -= ed->numItems[i];
733 missedPrimary += ed->numItems[i];
734 if (!primaryWeapon && randNumber < 0)
735 primaryWeapon = obj;
736 }
737 }
738 /* See if a weapon has been selected. */
739 if (primaryWeapon) {
740 hasWeapon += I_PackAmmoAndWeapon(self, inv, primaryWeapon, 0, ed);
741 if (hasWeapon) {
742 int ammo;
743
744 /* Find the first possible ammo to check damage type. */
745 for (ammo = 0; ammo < self->csi->numODs; ammo++)
746 if (ed->numItems[ammo] && INVSH_LoadableInWeapon(&self->csi->ods[ammo], primaryWeapon))
747 break;
748 if (ammo < self->csi->numODs) {
749 if (/* To avoid two particle weapons. */
750 !(self->csi->ods[ammo].dmgtype == self->csi->damParticle)
751 /* To avoid SMG + Assault Rifle */
752 && !(self->csi->ods[ammo].dmgtype == self->csi->damNormal)) {
753 primary = WEAPON_OTHER;
754 } else {
755 primary = WEAPON_PARTICLE_OR_NORMAL;
756 }
757 }
758 /* reset missedPrimary: we got a primary weapon */
759 missedPrimary = 0;
760 } else {
761 Com_DPrintf(DEBUG_SHARED0x02, "INVSH_EquipActor: primary weapon '%s' couldn't be equipped in equipment '%s' (%s).\n",
762 primaryWeapon->id, ed->id, self->name);
763 repeat = WEAPONLESS_BONUS0.4 > frand();
764 }
765 }
766
767 /* Sidearms (secondary weapons with reload). */
768 do {
769 int randNumber = rand() % 100;
770 const objDef_t *secondaryWeapon = NULL__null;
771 for (i = 0; i < self->csi->numODs; i++) {
772 const objDef_t *obj = INVSH_GetItemByIDX(i);
773 if (ed->numItems[i] && obj->weapon && obj->reload && !obj->deplete && obj->isSecondary) {
774 randNumber -= ed->numItems[i] / (primary == WEAPON_PARTICLE_OR_NORMAL ? 2 : 1);
775 if (randNumber < 0) {
776 secondaryWeapon = obj;
777 break;
778 }
779 }
780 }
781
782 if (secondaryWeapon) {
783 hasWeapon += I_PackAmmoAndWeapon(self, inv, secondaryWeapon, missedPrimary, ed);
784 if (hasWeapon) {
785 /* Try to get the second akimbo pistol if no primary weapon. */
786 if (primary == WEAPON_NO_PRIMARY && !secondaryWeapon->fireTwoHanded && frand() < AKIMBO_CHANCE) {
787 I_PackAmmoAndWeapon(self, inv, secondaryWeapon, 0, ed);
788 }
789 }
790 }
791 } while (!hasWeapon && repeat--);
792
793 /* Misc items and secondary weapons without reload. */
794 if (!hasWeapon)
795 repeat = WEAPONLESS_BONUS0.4 > frand();
796 else
797 repeat = 0;
798 /* Misc object probability can be bigger than 100 -- you're sure to
799 * have one misc if it fits your backpack */
800 sum = 0;
801 for (i = 0; i < self->csi->numODs; i++) {
802 const objDef_t *obj = INVSH_GetItemByIDX(i);
803 if (ed->numItems[i] && ((obj->weapon && obj->isSecondary
804 && (!obj->reload || obj->deplete)) || obj->isMisc)) {
805 /* if ed->num[i] is greater than 100, the first number is the number of items you'll get:
806 * don't take it into account for probability
807 * Make sure that the probability is at least one if an item can be selected */
808 sum += ed->numItems[i] ? std::max(ed->numItems[i] % 100, 1) : 0;
809 }
810 }
811 if (sum) {
812 do {
813 int randNumber = rand() % sum;
814 const objDef_t *secondaryWeapon = NULL__null;
815 for (i = 0; i < self->csi->numODs; i++) {
816 const objDef_t *obj = INVSH_GetItemByIDX(i);
817 if (ed->numItems[i] && ((obj->weapon && obj->isSecondary
818 && (!obj->reload || obj->deplete)) || obj->isMisc)) {
819 randNumber -= ed->numItems[i] ? std::max(ed->numItems[i] % 100, 1) : 0;
820 if (randNumber < 0) {
821 secondaryWeapon = obj;
822 break;
823 }
824 }
825 }
826
827 if (secondaryWeapon) {
828 int num = ed->numItems[secondaryWeapon->idx] / 100 + (ed->numItems[secondaryWeapon->idx] % 100 >= 100 * frand());
829 while (num--) {
830 hasWeapon += I_PackAmmoAndWeapon(self, inv, secondaryWeapon, 0, ed);
831 }
832 }
833 } while (repeat--); /* Gives more if no serious weapons. */
834 }
835
836 /* If no weapon at all, bad guys will always find a blade to wield. */
837 if (!hasWeapon) {
838 int maxPrice = 0;
839 const objDef_t *blade = NULL__null;
840 Com_DPrintf(DEBUG_SHARED0x02, "INVSH_EquipActor: no weapon picked in equipment '%s', defaulting to the most expensive secondary weapon without reload. (%s)\n",
841 ed->id, self->name);
842 for (i = 0; i < self->csi->numODs; i++) {
843 const objDef_t *obj = INVSH_GetItemByIDX(i);
844 if (ed->numItems[i] && obj->weapon && obj->isSecondary && !obj->reload) {
845 if (obj->price > maxPrice) {
846 maxPrice = obj->price;
847 blade = obj;
848 }
849 }
850 }
851 if (maxPrice)
852 hasWeapon += I_PackAmmoAndWeapon(self, inv, blade, 0, ed);
853 }
854 /* If still no weapon, something is broken, or no blades in equipment. */
855 if (!hasWeapon)
856 Com_DPrintf(DEBUG_SHARED0x02, "INVSH_EquipActor: cannot add any weapon; no secondary weapon without reload detected for equipment '%s' (%s).\n",
857 ed->id, self->name);
858
859 /* Armour; especially for those without primary weapons. */
860 repeat = (float) missedPrimary > frand() * 100.0;
861 } else {
862 return;
863 }
864
865 if (td->armour) {
866 do {
867 int randNumber = rand() % 100;
868 for (i = 0; i < self->csi->numODs; i++) {
869 const objDef_t *armour = INVSH_GetItemByIDX(i);
870 if (ed->numItems[i] && INV_IsArmour(armour)((strcmp((armour)->type, "armour") == 0))) {
871 randNumber -= ed->numItems[i];
872 if (randNumber < 0) {
873 const item_t item = {NONE_AMMO0, NULL__null, armour, 0, 0};
874 if (self->TryAddToInventory(self, inv, &item, &self->csi->ids[self->csi->idArmour])) {
875 repeat = 0;
876 break;
877 }
878 }
879 }
880 }
881 } while (repeat-- > 0);
882 } else {
883 Com_DPrintf(DEBUG_SHARED0x02, "INVSH_EquipActor: teamdef '%s' may not carry armour (%s)\n",
884 td->name, self->name);
885 }
886
887 {
888 int randNumber = rand() % 10;
889 for (i = 0; i < self->csi->numODs; i++) {
890 if (ed->numItems[i]) {
891 const objDef_t *miscItem = INVSH_GetItemByIDX(i);
892 if (miscItem->isMisc && !miscItem->weapon) {
893 randNumber -= ed->numItems[i];
894 if (randNumber < 0) {
895 const bool oneShot = miscItem->oneshot;
896 const item_t item = {oneShot ? miscItem->ammo : NONE_AMMO0, oneShot ? miscItem : NULL__null, miscItem, 0, 0};
897 containerIndex_t container;
898 if (miscItem->headgear)
899 container = self->csi->idHeadgear;
900 else if (miscItem->extension)
901 container = self->csi->idExtension;
902 else
903 container = self->csi->idBackpack;
904 self->TryAddToInventory(self, inv, &item, &self->csi->ids[container]);
905 }
906 }
907 }
908 }
909 }
910}
911
912/**
913 * @brief Calculate the number of used inventory slots
914 * @param[in] self The inventory interface pointer
915 * @return The number of free inventory slots
916 */
917static int I_GetUsedSlots (inventoryInterface_t* self)
918{
919 int i = 0;
920 const invList_t* slot = self->invList;
921 while (slot) {
922 slot = slot->next;
923 i++;
924 }
925 Com_DPrintf(DEBUG_SHARED0x02, "Used inventory slots %i (%s)\n", i, self->name);
926 return i;
927}
928
929/**
930 * @brief Initializes the inventory definition by linking the ->next pointers properly.
931 * @param[in] name The name that is shown in the output
932 * @param[out] interface The inventory interface pointer which should be initialized in this function.
933 * @param[in] csi The client-server-information structure
934 * @param[in] import Pointers to the lifecycle functions
935 * @sa G_Init
936 * @sa CL_InitLocal
937 */
938void INV_InitInventory (const char *name, inventoryInterface_t *interface, const csi_t* csi, const inventoryImport_t *import)
939{
940 const item_t item = {NONE_AMMO0, NULL__null, NULL__null, 0, 0};
941
942 OBJZERO(*interface)(memset(&((*interface)), (0), sizeof((*interface))));
943
944 interface->import = import;
945 interface->name = name;
946 interface->cacheItem = item;
947 interface->csi = csi;
948 interface->invList = NULL__null;
949
950 interface->TryAddToInventory = I_TryAddToInventory;
951 interface->AddToInventory = I_AddToInventory;
952 interface->RemoveFromInventory = I_RemoveFromInventory;
953 interface->MoveInInventory = I_MoveInInventory;
954 interface->DestroyInventory = I_DestroyInventory;
955 interface->EmptyContainer = I_EmptyContainer;
956 interface->EquipActor = I_EquipActor;
957 interface->EquipActorMelee = I_EquipActorMelee;
958 interface->EquipActorRobot = I_EquipActorRobot;
959 interface->GetUsedSlots = I_GetUsedSlots;
960}
961
962void INV_DestroyInventory (inventoryInterface_t *interface)
963{
964 if (interface->import == NULL__null)
965 return;
966 interface->import->FreeAll();
967 OBJZERO(*interface)(memset(&((*interface)), (0), sizeof((*interface))));
968}