1 /**
2 * @file
3 * @brief Common object-, inventory-, container- and firemode-related functions.
4 * @note Shared inventory management functions prefix: INVSH_
5 * @note Shared firemode management functions prefix: FIRESH_
6 */
7
8 /*
9 Copyright (C) 2002-2013 UFO: Alien Invasion.
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19
20 See the GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 */
27
28 #include "q_shared.h"
29
30 static const csi_t* CSI;
31
32 /**
33 * @brief Initializes client server shared data pointer. This works because the client and the server are both
34 * using exactly the same pointer.
35 * @param[in] import The client server interface pointer
36 * @sa G_Init
37 * @sa Com_ParseScripts
38 */
INVSH_InitCSI(const csi_t * import)39 void INVSH_InitCSI (const csi_t* import)
40 {
41 CSI = import;
42 }
43
44 /**
45 * @brief Checks whether the inventory definition is the floor
46 * @return @c true if the given inventory definition is of type floor
47 */
isFloorDef() const48 bool invDef_t::isFloorDef () const
49 {
50 return id == CID_FLOOR;
51 }
52
53 /**
54 * @brief Checks whether the inventory definition is the right Hand
55 * @return @c true if the given inventory definition is of type right
56 */
isRightDef() const57 bool invDef_t::isRightDef () const
58 {
59 return id == CID_RIGHT;
60 }
61
62 /**
63 * @brief Checks whether a given inventory definition is of special type
64 * @return @c true if the given inventory definition is of type left
65 */
isLeftDef() const66 bool invDef_t::isLeftDef () const
67 {
68 return id == CID_LEFT;
69 }
70
71 /**
72 * @brief Checks whether a given inventory definition is of special type
73 * @return @c true if the given inventory definition is of type equip
74 */
isEquipDef() const75 bool invDef_t::isEquipDef () const
76 {
77 return id == CID_EQUIP;
78 }
79
80 /**
81 * @brief Checks whether a given inventory definition is of special type
82 * @return @c true if the given inventory definition is of type armour
83 */
isArmourDef() const84 bool invDef_t::isArmourDef () const
85 {
86 return id == CID_ARMOUR;
87 }
88
89 static int cacheCheckToInventory = INV_DOES_NOT_FIT;
90
91 /**
92 * @brief Will check if the item-shape is colliding with something else in the container-shape at position x/y.
93 * @note The function expects an already rotated shape for itemShape. Use getShapeRotated() if needed.
94 * @param[in] shape The shape of the container [SHAPE_BIG_MAX_HEIGHT]
95 * @param[in] itemShape The shape of the item [SHAPE_SMALL_MAX_HEIGHT]
96 * @param[in] x The x value in the container (1 << x in the shape bitmask)
97 * @param[in] y The y value in the container (SHAPE_BIG_MAX_HEIGHT is the max)
98 */
INVSH_CheckShapeCollision(const uint32_t * shape,const uint32_t itemShape,const int x,const int y)99 static bool INVSH_CheckShapeCollision (const uint32_t* shape, const uint32_t itemShape, const int x, const int y)
100 {
101 int i;
102
103 /* Negative positions not allowed (all items are supposed to have at least one bit set in the first row and column) */
104 if (x < 0 || y < 0) {
105 Com_DPrintf(DEBUG_SHARED, "INVSH_CheckShapeCollision: x or y value negative: x=%i y=%i!\n", x, y);
106 return true;
107 }
108
109 for (i = 0; i < SHAPE_SMALL_MAX_HEIGHT; i++) {
110 /* 0xFF is the length of one row in a "small shape" i.e. SHAPE_SMALL_MAX_WIDTH */
111 const int itemRow = (itemShape >> (i * SHAPE_SMALL_MAX_WIDTH)) & 0xFF;
112 /* Result has to be limited to 32bit (SHAPE_BIG_MAX_WIDTH) */
113 const uint32_t itemRowShifted = itemRow << x;
114
115 /* Check x maximum. */
116 if (itemRowShifted >> x != itemRow)
117 /* Out of bounds (32bit; a few bits of this row in itemShape were truncated) */
118 return true;
119
120 /* Check y maximum. */
121 if (y + i >= SHAPE_BIG_MAX_HEIGHT && itemRow)
122 /* This row (row "i" in itemShape) is outside of the max. bound and has bits in it. */
123 return true;
124
125 /* Check for collisions of the item with the big mask. */
126 if (itemRowShifted & shape[y + i])
127 return true;
128 }
129
130 return false;
131 }
132
133 /**
134 * @brief Checks if an item-shape can be put into a container at a certain position... ignores any 'special' types of containers.
135 * @param[in] inv The inventory
136 * @param[in] container The container (index) to look into.
137 * @param[in] itemShape The shape info of an item to fit into the container.
138 * @param[in] x The x value in the container (1 << x in the shape bitmask)
139 * @param[in] y The y value in the container (SHAPE_BIG_MAX_HEIGHT is the max)
140 * @param[in] ignoredItem You can ignore one item in the container (most often the currently dragged one). Use nullptr if you want to check against all items in the container.
141 * @sa canHoldItem
142 * @return false if the item does not fit, true if it fits.
143 */
INVSH_CheckToInventory_shape(const Inventory * const inv,const invDef_t * container,const uint32_t itemShape,const int x,const int y,const Item * ignoredItem)144 static bool INVSH_CheckToInventory_shape (const Inventory* const inv, const invDef_t* container, const uint32_t itemShape, const int x, const int y, const Item* ignoredItem)
145 {
146 static uint32_t mask[SHAPE_BIG_MAX_HEIGHT];
147
148 assert(container);
149
150 if (container->scroll)
151 Sys_Error("INVSH_CheckToInventory_shape: No scrollable container will ever use this. This type does not support grid-packing!");
152
153 /* check bounds */
154 if (x < 0 || y < 0 || x >= SHAPE_BIG_MAX_WIDTH || y >= SHAPE_BIG_MAX_HEIGHT)
155 return false;
156
157 if (!cacheCheckToInventory) {
158 int j;
159 /* extract shape info */
160 for (j = 0; j < SHAPE_BIG_MAX_HEIGHT; j++)
161 mask[j] = ~container->shape[j];
162
163 /* Add other items to mask. (i.e. merge their shapes at their location into the generated mask) */
164 const Container &cont = inv->getContainer(container->id);
165 Item* item = nullptr;
166 while ((item = cont.getNextItem(item))) {
167 if (ignoredItem == item)
168 continue;
169
170 if (item->rotated)
171 INVSH_MergeShapes(mask, item->def()->getShapeRotated(), item->getX(), item->getY());
172 else
173 INVSH_MergeShapes(mask, item->def()->shape, item->getX(), item->getY());
174 }
175 }
176
177 /* Test for collisions with newly generated mask. */
178 if (INVSH_CheckShapeCollision(mask, itemShape, x, y))
179 return false;
180
181 /* Everything ok. */
182 return true;
183 }
184
185 /**
186 * @brief Checks the shape if there is a 1-bit on the position x/y.
187 * @param[in] shape The shape to check in. (8x4)
188 * @param[in] x The x value in the shape (1 << x in the shape bitmask)
189 * @param[in] y The y value in the shape (SHAPE_SMALL_MAX_HEIGHT is the max)
190 */
INVSH_CheckShapeSmall(const uint32_t shape,const int x,const int y)191 static bool INVSH_CheckShapeSmall (const uint32_t shape, const int x, const int y)
192 {
193 if (y >= SHAPE_SMALL_MAX_HEIGHT || x >= SHAPE_SMALL_MAX_WIDTH|| x < 0 || y < 0) {
194 return false;
195 }
196
197 return shape & (0x01 << (y * SHAPE_SMALL_MAX_WIDTH + x));
198 }
199
200 /**
201
202 * @brief Check if a position in a container is used by an item (i.e. collides with the shape).
203 * @param[in] item A pointer to the Item in question.
204 * @param[in] x The x location in the container.
205 * @param[in] y The y location in the container.
206 */
INVSH_ShapeCheckPosition(const Item * item,const int x,const int y)207 static bool INVSH_ShapeCheckPosition (const Item* item, const int x, const int y)
208 {
209 uint32_t shape;
210
211 assert(item);
212
213 /* Check if the position is inside the shape (depending on rotation value) of the item. */
214 if (item->rotated) {
215 shape = item->def()->getShapeRotated();
216 } else {
217 shape = item->def()->shape;
218 }
219
220 return INVSH_CheckShapeSmall(shape, x - item->getX(), y - item->getY());
221 }
222
223 /**
224 * @brief Checks whether a given item is an aircraftitem item
225 * @note This is done by checking whether it's a craftitem and not
226 * marked as a dummy item - the combination of both means, that it's a
227 * basedefence item.
228 * @return true if the given item is an aircraftitem item
229 * @sa isBaseDefenceItem
230 */
isCraftItem() const231 bool objDef_t::isCraftItem () const
232 {
233 return craftitem.type != MAX_ACITEMS && !isDummy;
234 }
235
236 /**
237 * @brief Checks whether the item is a basedefence item
238 * @note This is done by checking whether it's a craftitem and whether it's
239 * marked as a dummy item - the combination of both means, that it's a
240 * basedefence item.
241 * @return true if the given item is a basedefence item
242 * @sa isCraftItem
243 */
isBaseDefenceItem() const244 bool objDef_t::isBaseDefenceItem () const
245 {
246 return craftitem.type != MAX_ACITEMS && isDummy;
247 }
248
249 /**
250 * @brief Returns the item that belongs to the given id or @c nullptr if it wasn't found.
251 * @param[in] id the item id in our object definition array (csi.ods)
252 * @sa INVSH_GetItemByID
253 */
INVSH_GetItemByIDSilent(const char * id)254 const objDef_t* INVSH_GetItemByIDSilent (const char* id)
255 {
256 if (!id)
257 return nullptr;
258
259 for (int i = 0; i < CSI->numODs; ++i) { /* i = item index */
260 const objDef_t* item = &CSI->ods[i];
261 if (Q_streq(id, item->id)) {
262 return item;
263 }
264 }
265 return nullptr;
266 }
267
268 /**
269 * @brief Returns the item that belongs to the given index or @c nullptr if the index is invalid.
270 */
INVSH_GetItemByIDX(int index)271 const objDef_t* INVSH_GetItemByIDX (int index)
272 {
273 if (index == NONE)
274 return nullptr;
275
276 if (index < 0 || index >= CSI->numODs)
277 Sys_Error("Invalid object index given: %i", index);
278
279 return &CSI->ods[index];
280 }
281
282 /**
283 * @brief Returns the item that belongs to the given id or @c nullptr if it wasn't found.
284 * @param[in] id the item id in our object definition array (csi.ods)
285 * @sa INVSH_GetItemByIDSilent
286 */
INVSH_GetItemByID(const char * id)287 const objDef_t* INVSH_GetItemByID (const char* id)
288 {
289 const objDef_t* od = INVSH_GetItemByIDSilent(id);
290 if (!od)
291 Com_Printf("INVSH_GetItemByID: Item \"%s\" not found.\n", id);
292
293 return od;
294 }
295
INVSH_GetImplantForObjDef(const objDef_t * od)296 const implantDef_t* INVSH_GetImplantForObjDef (const objDef_t* od)
297 {
298 for (int i = 0; i < CSI->numImplants; i++) {
299 const implantDef_t* id = &CSI->implants[i];
300 if (id->item == od)
301 return id;
302 }
303 Com_Printf("INVSH_GetImplantForObjDef: could not get implant for %s\n", od->id);
304 return nullptr;
305 }
306
307 /**
308 * @brief Returns the implant that belongs to the given id or @c nullptr if it wasn't found.
309 * @param[in] id the implant id in our implant definition array (csi.implants)
310 * @sa INVSH_GetImplantByID
311 */
INVSH_GetImplantByIDSilent(const char * id)312 const implantDef_t* INVSH_GetImplantByIDSilent (const char* id)
313 {
314 if (!id)
315 return nullptr;
316
317 for (int i = 0; i < CSI->numImplants; ++i) {
318 const implantDef_t* item = &CSI->implants[i];
319 if (Q_streq(id, item->id)) {
320 return item;
321 }
322 }
323 return nullptr;
324 }
325
326 /**
327 * @brief Returns the implant that belongs to the given id or @c nullptr if it wasn't found.
328 * @param[in] id the implant id in our implant definition array (csi.implants)
329 * @sa INVSH_GetImplantByIDSilent
330 */
INVSH_GetImplantByID(const char * id)331 const implantDef_t* INVSH_GetImplantByID (const char* id)
332 {
333 const implantDef_t* implantDef = INVSH_GetImplantByIDSilent(id);
334 if (!implantDef)
335 Com_Printf("INVSH_GetImplantByID: Implant \"%s\" not found.\n", id);
336
337 return implantDef;
338 }
339
340 /**
341 * Searched an inventory container by a given container id
342 * @param[in] id ID or name of the inventory container to search for
343 * @return @c nullptr if not found
344 */
INVSH_GetInventoryDefinitionByID(const char * id)345 const invDef_t* INVSH_GetInventoryDefinitionByID (const char* id)
346 {
347 containerIndex_t i;
348 const invDef_t* container;
349
350 for (i = 0, container = CSI->ids; i < CID_MAX; ++container, ++i)
351 if (Q_streq(id, container->name))
352 return container;
353
354 return nullptr;
355 }
356
357 /**
358 * @brief Checks if an item can be used to reload a weapon.
359 * @param[in] weapon The weapon (in the inventory) to check the item with. Might not be @c nullptr.
360 * @return @c true if the item can be used in the given weapon, otherwise @c false.
361 */
isLoadableInWeapon(const objDef_t * weapon) const362 bool objDef_t::isLoadableInWeapon (const objDef_t* weapon) const
363 {
364 int i;
365
366 assert(weapon);
367
368 /* check whether the weapon is only linked to itself. */
369 if (this->numWeapons == 1 && this->weapons[0] == this)
370 return false;
371
372 for (i = 0; i < this->numWeapons; i++)
373 if (weapon == this->weapons[i])
374 return true;
375
376 return false;
377 }
378
379 /*
380 ===============================
381 FIREMODE MANAGEMENT FUNCTIONS
382 ===============================
383 */
384
385 /**
386 * @brief Get the fire definitions for a given object
387 * @param[in] obj The object to get the firedef for
388 * @param[in] weapFdsIdx the weapon index in the fire definition array
389 * @param[in] fdIdx the fire definition index for the weapon (given by @c weapFdsIdx)
390 * @return Will never return nullptr
391 * @sa getFiredefs
392 */
FIRESH_GetFiredef(const objDef_t * obj,const weaponFireDefIndex_t weapFdsIdx,const fireDefIndex_t fdIdx)393 const fireDef_t* FIRESH_GetFiredef (const objDef_t* obj, const weaponFireDefIndex_t weapFdsIdx, const fireDefIndex_t fdIdx)
394 {
395 if (weapFdsIdx < 0 || weapFdsIdx >= MAX_WEAPONS_PER_OBJDEF)
396 Sys_Error("FIRESH_GetFiredef: weapFdsIdx out of bounds [%i] for item '%s'", weapFdsIdx, obj->id);
397 if (fdIdx < 0 || fdIdx >= MAX_FIREDEFS_PER_WEAPON)
398 Sys_Error("FIRESH_GetFiredef: fdIdx out of bounds [%i] for item '%s'", fdIdx, obj->id);
399 return &obj->fd[weapFdsIdx & (MAX_WEAPONS_PER_OBJDEF - 1)][fdIdx & (MAX_FIREDEFS_PER_WEAPON - 1)];
400 }
401
402 /**
403 * @brief Will merge the second shape (=itemShape) into the first one (=big container shape) on the position x/y.
404 * @note The function expects an already rotated shape for itemShape. Use getShapeRotated() if needed.
405 * @param[in] shape The shape of the container [SHAPE_BIG_MAX_HEIGHT]'
406 * @param[in] itemShape The shape of the item [SHAPE_SMALL_MAX_HEIGHT]
407 * @param[in] x The x value in the container (1 << x in the shape bitmask)
408 * @param[in] y The y value in the container (SHAPE_BIG_MAX_HEIGHT is the max)
409 */
INVSH_MergeShapes(uint32_t * shape,const uint32_t itemShape,const int x,const int y)410 void INVSH_MergeShapes (uint32_t* shape, const uint32_t itemShape, const int x, const int y)
411 {
412 for (int i = 0; (i < SHAPE_SMALL_MAX_HEIGHT) && (y + i < SHAPE_BIG_MAX_HEIGHT); ++i)
413 shape[y + i] |= ((itemShape >> i * SHAPE_SMALL_MAX_WIDTH) & 0xFF) << x;
414 }
415
416 /**
417 * @brief Checks the shape if there is a 1-bit on the position x/y.
418 * @param[in] shape Pointer to 'uint32_t shape[SHAPE_BIG_MAX_HEIGHT]'
419 * @param[in] x The x value in the container (1 << x in the shape bitmask)
420 * @param[in] y The y value in the container (SHAPE_BIG_MAX_HEIGHT is the max)
421 */
INVSH_CheckShape(const uint32_t * shape,const int x,const int y)422 bool INVSH_CheckShape (const uint32_t* shape, const int x, const int y)
423 {
424 const uint32_t row = shape[y];
425 const int position = powf(2.0f, (float)x);
426
427 if (y >= SHAPE_BIG_MAX_HEIGHT || x >= SHAPE_BIG_MAX_WIDTH || x < 0 || y < 0) {
428 Com_Printf("INVSH_CheckShape: Bad x or y value: (x=%i, y=%i)\n", x, y);
429 return false;
430 }
431
432 if ((row & position) == 0)
433 return false;
434
435 return true;
436 }
437
438 /**
439 * @brief Counts the used bits in a shape (item shape).
440 * @param[in] shape The shape to count the bits in.
441 * @return Number of bits.
442 * @note Used to calculate the real field usage in the inventory
443 */
INVSH_ShapeSize(const uint32_t shape)444 int INVSH_ShapeSize (const uint32_t shape)
445 {
446 int bitCounter = 0;
447
448 for (int i = 0; i < SHAPE_SMALL_MAX_HEIGHT * SHAPE_SMALL_MAX_WIDTH; ++i)
449 if (shape & (1 << i))
450 ++bitCounter;
451
452 return bitCounter;
453 }
454
455 /**
456 * @brief Sets one bit in a shape to true/1
457 * @note Only works for V_SHAPE_SMALL!
458 * If the bit is already set the shape is not changed.
459 * @param[in] shape The shape to modify. (8x4)
460 * @param[in] x The x (width) position of the bit to set.
461 * @param[in] y The y (height) position of the bit to set.
462 * @return The new shape.
463 */
INVSH_ShapeSetBit(uint32_t shape,const int x,const int y)464 static uint32_t INVSH_ShapeSetBit (uint32_t shape, const int x, const int y)
465 {
466 if (x >= SHAPE_SMALL_MAX_WIDTH || y >= SHAPE_SMALL_MAX_HEIGHT || x < 0 || y < 0) {
467 Com_Printf("INVSH_ShapeSetBit: Bad x or y value: (x=%i, y=%i)\n", x,y);
468 return shape;
469 }
470
471 shape |= 0x01 << (y * SHAPE_SMALL_MAX_WIDTH + x);
472 return shape;
473 }
474
475
476 /**
477 * @brief Rotates a shape definition 90 degree to the left (CCW)
478 * @note Only works for V_SHAPE_SMALL!
479 * @return The new shape.
480 */
getShapeRotated() const481 uint32_t objDef_t::getShapeRotated () const
482 {
483 int h, w;
484 uint32_t shapeNew = 0;
485 int maxWidth = -1;
486
487 for (w = SHAPE_SMALL_MAX_WIDTH - 1; w >= 0; w--) {
488 for (h = 0; h < SHAPE_SMALL_MAX_HEIGHT; h++) {
489 if (!INVSH_CheckShapeSmall(shape, w, h))
490 continue;
491 if (w >= SHAPE_SMALL_MAX_HEIGHT) {
492 /* Object can't be rotated (code-wise), it is longer than SHAPE_SMALL_MAX_HEIGHT allows. */
493 return shape;
494 }
495
496 if (maxWidth < 0)
497 maxWidth = w;
498
499 shapeNew = INVSH_ShapeSetBit(shapeNew, h, maxWidth - w);
500 }
501 }
502
503 return shapeNew;
504 }
505
506 /** @brief Item constructor with all default values */
Item()507 Item::Item ()
508 {
509 setAmmoLeft(NONE_AMMO);
510 setAmount(0);
511 setAmmoDef(nullptr);
512 _itemDef = nullptr;
513 _x = _y = rotated = 0;
514 _next = nullptr;
515 }
516
517 /** @brief Item constructor with the 3 most often changed attributes */
Item(const objDef_t * itemDef,const objDef_t * ammo,int ammoLeft)518 Item::Item (const objDef_t* itemDef, const objDef_t* ammo, int ammoLeft)
519 {
520 setAmmoLeft(ammoLeft);
521 setAmount(0);
522 setAmmoDef(ammo);
523 _itemDef = itemDef;
524 _x = _y = rotated = 0;
525 _next = nullptr;
526 }
527
528 /**
529 * @brief Return the weight of an item.
530 * @return The weight of the given item including any ammo loaded.
531 */
getWeight() const532 float Item::getWeight () const
533 {
534 float weight = def()->weight;
535 if (ammoDef() && ammoDef() != def() && getAmmoLeft() > 0) {
536 weight += ammoDef()->weight;
537 }
538 return weight;
539 }
540
541 /**
542 * @brief Check if the (physical) information of 2 items is exactly the same.
543 * @param[in] other Second item to compare.
544 * @return true if they are identical or false otherwise.
545 */
isSameAs(const Item * const other) const546 bool Item::isSameAs (const Item* const other) const
547 {
548 if (this == other)
549 return true;
550
551 if (this == nullptr || other == nullptr)
552 return false;
553
554 if (this->def() == other->def() && this->ammoDef() == other->ammoDef() && this->getAmmoLeft() == other->getAmmoLeft())
555 return true;
556
557 return false;
558 }
559
560 /**
561 * @brief Calculates the first "true" bit in the shape and returns its position in the item.
562 * @note Use this to get the first "grab-able" grid-location of an item (not in the container !).
563 * @param[out] x The x location inside the item.
564 * @param[out] y The x location inside the item.
565 * @sa canHoldItem
566 */
getFirstShapePosition(int * const x,int * const y) const567 void Item::getFirstShapePosition (int* const x, int* const y) const
568 {
569 int tempX, tempY;
570
571 for (tempX = 0; tempX < SHAPE_SMALL_MAX_HEIGHT; tempX++)
572 for (tempY = 0; tempY < SHAPE_SMALL_MAX_HEIGHT; tempY++)
573 if (INVSH_ShapeCheckPosition(this, this->getX() + tempX, this->getY() + tempY)) {
574 *x = tempX;
575 *y = tempY;
576 return;
577 }
578
579 *x = *y = NONE;
580 }
581
582 /**
583 * @brief Returns the firedefinitions for a given weapon/ammo
584 * @return The array (one-dimensional) of the firedefs of the ammo for a given weapon, or @c nullptr if the ammo
585 * doesn't support the given weapon
586 * @sa FIRESH_GetFiredef
587 */
getFiredefs() const588 const fireDef_t* Item::getFiredefs () const
589 {
590 const objDef_t* ammodef = ammoDef();
591 const objDef_t* weapon = def();
592
593 /* this weapon does not use ammo, check for
594 * existing firedefs in the weapon. */
595 if (weapon->numWeapons > 0)
596 ammodef = def();
597
598 if (!ammodef)
599 return nullptr;
600
601 for (int i = 0; i < ammodef->numWeapons; ++i) {
602 if (weapon == ammodef->weapons[i])
603 return &ammodef->fd[i][0];
604 }
605
606 return nullptr;
607 }
608
609 /**
610 * @brief Get the firedef that uses the most TU for this item.
611 * @return The firedef that uses the most TU for this item or @c nullptr.
612 */
getSlowestFireDef() const613 const fireDef_t* Item::getSlowestFireDef () const
614 {
615 const fireDef_t* fdArray = getFiredefs();
616 int slowest = 0;
617
618 if (fdArray == nullptr)
619 return nullptr;
620
621 for (int i = 0; i < MAX_FIREDEFS_PER_WEAPON; ++i)
622 if (fdArray[i].time > fdArray[slowest].time)
623 slowest = i;
624
625 return &fdArray[slowest];
626 }
627
628 /**
629 * @brief Checks whether this item is a reaction fire enabled weapon.
630 * @note The item is supposed to be in the right or left hand
631 * @return @c nullptr if no reaction fire enabled weapon, the
632 * reaction fire enabled object otherwise.
633 */
getReactionFireWeaponType() const634 const objDef_t* Item::getReactionFireWeaponType () const
635 {
636 if (!this)
637 return nullptr;
638
639 if (def()) {
640 const fireDef_t* fd = getFiredefs();
641 if (fd && fd->reaction)
642 return def();
643 }
644
645 return nullptr;
646 }
647
Container()648 Container::Container ()
649 {
650 _def = nullptr;
651 _invList = nullptr;
652 id = 0;
653 }
654
def() const655 const invDef_t* Container::def () const
656 {
657 return &CSI->ids[id];
658 }
getNextItem(const Item * prev) const659 Item* Container::getNextItem (const Item* prev) const
660 {
661 if (!prev)
662 return _invList; /* the first one */
663
664 return prev->getNext();
665 }
666
667 /** @brief Count the number of items in the Container
668 * @note For temp containers we count the number of different items */
countItems() const669 int Container::countItems () const
670 {
671 int nr = 0;
672 Item* item = nullptr;
673 while ((item = getNextItem(item))) {
674 /** For temp containers, we neglect Item::amount. */
675 ++nr;
676 }
677 return nr;
678 }
679
Inventory()680 Inventory::Inventory ()
681 {
682 /* This (prototype-)constructor does not work as intended. Seems like the first inventory is created before CSI is set.
683 * There is the static game_inventory, static character_t, static le_t ...
684 * containers[CID_LEFT]._invList = nullptr;
685 * containers[CID_LEFT]._def = &CSI->ids[CID_LEFT]; */
686
687 /* Plan B: add an 'id' member to class Container and init it here */
688 init();
689 }
690
init()691 void Inventory::init ()
692 {
693 OBJZERO(_containers);
694 for (int i = 0; i < CID_MAX; ++i)
695 _containers[i].id = i;
696 }
697
_getNextCont(const Container * prev) const698 const Container* Inventory::_getNextCont (const Container* prev) const
699 {
700 if (!prev)
701 /* the first one */
702 return &_containers[0];
703 if (prev >= &_containers[CID_MAX - 1])
704 /* prev was the last one */
705 return nullptr;
706
707 return ++prev;
708 }
709
getNextCont(const Container * prev,bool inclTemp) const710 const Container* Inventory::getNextCont (const Container* prev, bool inclTemp) const
711 {
712 const Container* cont = prev;
713
714 while ((cont = _getNextCont(cont))) {
715 /* if we don't want to include the temp containers ... */
716 if (!inclTemp) {
717 /* ...skip them ! */
718 if (cont == &_containers[CID_FLOOR])
719 continue;
720 if (cont == &_containers[CID_EQUIP])
721 continue;
722 }
723 break;
724 }
725 return cont;
726 }
727
728 /** @brief Count the number of items in the inventory (without temp containers) */
countItems() const729 int Inventory::countItems () const
730 {
731 int nr = 0;
732 const Container* cont = nullptr;
733 while ((cont = getNextCont(cont))) { /* skips the temp containers */
734 nr += cont->countItems();
735 }
736 return nr;
737 }
738 /**
739 * @param[in] container The index of the container in the inventory to check the item in.
740 * @param[in] od The type of item to check in the inventory.
741 * @param[in] x The x value in the container (1 << x in the shape bitmask)
742 * @param[in] y The y value in the container (SHAPE_BIG_MAX_HEIGHT is the max)
743 * @param[in] ignoredItem You can ignore one item in the container (most often the currently dragged one). Use nullptr if you want to check against all items in the container.
744 * @return INV_DOES_NOT_FIT if the item does not fit
745 * @return INV_FITS if it fits and
746 * @return INV_FITS_ONLY_ROTATED if it fits only when rotated 90 degree (to the left).
747 * @return INV_FITS_BOTH if it fits either normally or when rotated 90 degree (to the left).
748 */
canHoldItem(const invDef_t * container,const objDef_t * od,const int x,const int y,const Item * ignoredItem) const749 int Inventory::canHoldItem (const invDef_t* container, const objDef_t* od, const int x, const int y, const Item* ignoredItem) const
750 {
751 int fits;
752 assert(container);
753 assert(od);
754
755 /* armour vs item */
756 if (od->isArmour()) {
757 if (!container->armour && !container->all) {
758 return INV_DOES_NOT_FIT;
759 }
760 } else if (!od->implant && container->implant) {
761 return INV_DOES_NOT_FIT;
762 } else if (!od->headgear && container->headgear) {
763 return INV_DOES_NOT_FIT;
764 } else if (container->armour) {
765 return INV_DOES_NOT_FIT;
766 }
767
768 /* twohanded item */
769 if (od->holdTwoHanded) {
770 if ((container->isRightDef() && getContainer2(CID_LEFT)) || container->isLeftDef())
771 return INV_DOES_NOT_FIT;
772 }
773
774 /* left hand is busy if right wields twohanded */
775 if (container->isLeftDef()) {
776 if (getContainer2(CID_RIGHT) && getContainer2(CID_RIGHT)->isHeldTwoHanded())
777 return INV_DOES_NOT_FIT;
778
779 /* can't put an item that is 'fireTwoHanded' into the left hand */
780 if (od->fireTwoHanded)
781 return INV_DOES_NOT_FIT;
782 }
783
784 if (container->unique) {
785 const Item item(od);
786 if (containsItem(container->id, &item))
787 return INV_DOES_NOT_FIT;
788 }
789
790 /* Single item containers, e.g. hands or headgear. */
791 if (container->single) {
792 if (getContainer2(container->id)) {
793 /* There is already an item. */
794 return INV_DOES_NOT_FIT;
795 } else {
796 fits = INV_DOES_NOT_FIT; /* equals 0 */
797
798 if (INVSH_CheckToInventory_shape(this, container, od->shape, x, y, ignoredItem))
799 fits |= INV_FITS;
800 if (INVSH_CheckToInventory_shape(this, container, od->getShapeRotated(), x, y, ignoredItem))
801 fits |= INV_FITS_ONLY_ROTATED;
802
803 if (fits != INV_DOES_NOT_FIT)
804 return fits; /**< Return INV_FITS_BOTH if both if statements where true above. */
805
806 Com_DPrintf(DEBUG_SHARED, "canHoldItem: INFO: Moving to 'single' container but item would not fit normally.\n");
807 return INV_FITS; /**< We are returning with status true (1) if the item does not fit at all - unlikely but not impossible. */
808 }
809 }
810
811 /* Scrolling container have endless room, the item always fits. */
812 if (container->scroll)
813 return INV_FITS;
814
815 /* Check 'grid' containers. */
816 fits = INV_DOES_NOT_FIT; /* equals 0 */
817 if (INVSH_CheckToInventory_shape(this, container, od->shape, x, y, ignoredItem))
818 fits |= INV_FITS;
819 /** @todo aren't both (equip and floor) temp container? */
820 if (!container->isEquipDef() && !container->isFloorDef()
821 && INVSH_CheckToInventory_shape(this, container, od->getShapeRotated(), x, y, ignoredItem))
822 fits |= INV_FITS_ONLY_ROTATED;
823
824 return fits; /**< Return INV_FITS_BOTH if both if statements where true above. */
825 }
826
827 /**
828 * @brief Searches if there is an item at location (x,y) in a container.
829 * @param[in] container Container in the inventory.
830 * @param[in] x/y Position in the container that you want to check.
831 * @return Item Pointer to the Item/item that is located at x/y.
832 */
getItemAtPos(const invDef_t * container,const int x,const int y) const833 Item* Inventory::getItemAtPos (const invDef_t* container, const int x, const int y) const
834 {
835 assert(container);
836
837 /* Only a single item. */
838 if (container->single)
839 return getContainer2(container->id);
840
841 if (container->scroll)
842 Sys_Error("getItemAtPos: Scrollable containers (%i:%s) are not supported by this function.", container->id, container->name);
843
844 /* More than one item - search for the item that is located at location x/y in this container. */
845 const Container &cont = getContainer(container->id);
846 Item* item = nullptr;
847 while ((item = cont.getNextItem(item)))
848 if (INVSH_ShapeCheckPosition(item, x, y))
849 return item;
850
851 /* Found nothing. */
852 return nullptr;
853 }
854
855 /**
856 * @brief Finds space for item in inv at container
857 * @param[in] item The item to check the space for
858 * @param[in] container The container to search in
859 * @param[out] px The x position in the container
860 * @param[out] py The y position in the container
861 * @param[in] ignoredItem You can ignore one item in the container (most often the currently dragged one). Use nullptr if you want to check against all items in the container.
862 * @sa canHoldItem
863 * @note x and y are NONE if no free space is available
864 */
findSpace(const invDef_t * container,const Item * item,int * const px,int * const py,const Item * ignoredItem) const865 void Inventory::findSpace (const invDef_t* container, const Item* item, int* const px, int* const py, const Item* ignoredItem) const
866 {
867 assert(container);
868 assert(!cacheCheckToInventory);
869
870 /* Scrollable container always have room. We return a dummy location. */
871 if (container->scroll) {
872 *px = *py = 0;
873 return;
874 }
875
876 /** @todo optimize for single containers */
877
878 for (int y = 0; y < SHAPE_BIG_MAX_HEIGHT; ++y) {
879 for (int x = 0; x < SHAPE_BIG_MAX_WIDTH; ++x) {
880 const int checkedTo = canHoldItem(container, item->def(), x, y, ignoredItem);
881 if (checkedTo) {
882 cacheCheckToInventory = INV_DOES_NOT_FIT;
883 *px = x;
884 *py = y;
885 return;
886 } else {
887 cacheCheckToInventory = INV_FITS;
888 }
889 }
890 }
891 cacheCheckToInventory = INV_DOES_NOT_FIT;
892
893 #ifdef PARANOID
894 Com_DPrintf(DEBUG_SHARED, "findSpace: no space for %s: %s in %s\n",
895 item->def()->type, item->def()->id, container->name);
896 #endif
897 *px = *py = NONE;
898 }
899
900 /**
901 * @brief Check that adding an item to the inventory won't exceed the max permitted weight.
902 * @param[in] from Index of the container the item comes from.
903 * @param[in] to Index of the container the item is being placed.
904 * @param[in] item The item that is being added.
905 * @param[in] maxWeight The max permitted weight.
906 * @return @c true if it is Ok to add the item @c false otherwise.
907 */
canHoldItemWeight(containerIndex_t from,containerIndex_t to,const Item & item,int maxWeight) const908 bool Inventory::canHoldItemWeight (containerIndex_t from, containerIndex_t to, const Item &item, int maxWeight) const
909 {
910 if (CSI->ids[to].temp || !CSI->ids[from].temp)
911 return true;
912
913 const float itemWeight = item.getWeight();
914 if (itemWeight <= 0.00001f)
915 return true;
916 const bool swapArmour = item.isArmour() && getArmour();
917 const float invWeight = getWeight() - (swapArmour ? getArmour()->getWeight() : 0);
918
919 return (maxWeight < 0 || maxWeight >= invWeight + itemWeight);
920 }
921
922 /**
923 * @brief Get the weight of the items in the given inventory (excluding those in temp containers).
924 * @return The total weight of the inventory items (excluding those in temp containers)
925 */
getWeight() const926 float Inventory::getWeight () const
927 {
928 float weight = 0;
929 const Container* cont = nullptr;
930 while ((cont = getNextCont(cont))) {
931 Item* item = nullptr;
932 while ((item = cont->getNextItem(item))) {
933 weight += item->getWeight();
934 }
935 }
936 return weight;
937 }
938
setFloorContainer(Item * cont)939 void Inventory::setFloorContainer(Item* cont)
940 {
941 setContainer(CID_FLOOR, cont);
942 }
943
getRightHandContainer() const944 Item* Inventory::getRightHandContainer () const
945 {
946 return getContainer2(CID_RIGHT);
947 }
948
getLeftHandContainer() const949 Item* Inventory::getLeftHandContainer () const
950 {
951 return getContainer2(CID_LEFT);
952 }
953
getHeadgear() const954 Item* Inventory::getHeadgear () const
955 {
956 return getContainer2(CID_HEADGEAR);
957 }
958
getHolsterContainer() const959 Item* Inventory::getHolsterContainer () const
960 {
961 return getContainer2(CID_HOLSTER);
962 }
963
getFloorContainer() const964 Item* Inventory::getFloorContainer () const
965 {
966 return getContainer2(CID_FLOOR);
967 }
968
getEquipContainer() const969 Item* Inventory::getEquipContainer () const
970 {
971 return getContainer2(CID_EQUIP);
972 }
973
getArmour() const974 Item* Inventory::getArmour () const
975 {
976 return getContainer2(CID_ARMOUR);
977 }
978
979 /**
980 * @brief Searches a specific item in the inventory&container.
981 * @param[in] contId Container in the inventory.
982 * @param[in] searchItem The item to search for.
983 * @return Pointer to the first item of this type found, otherwise @c nullptr.
984 */
findInContainer(const containerIndex_t contId,const Item * const searchItem) const985 Item* Inventory::findInContainer (const containerIndex_t contId, const Item* const searchItem) const
986 {
987 const Container &cont = getContainer(contId);
988 Item* item = nullptr;
989 while ((item = cont.getNextItem(item)))
990 if (item->isSameAs(searchItem)) {
991 return item;
992 }
993
994 return nullptr;
995 }
996
997 /**
998 * @brief Checks if there is a weapon in the hands that can be used for reaction fire.
999 */
holdsReactionFireWeapon() const1000 bool Inventory::holdsReactionFireWeapon () const
1001 {
1002 if (getRightHandContainer()->getReactionFireWeaponType())
1003 return true;
1004 if (getLeftHandContainer()->getReactionFireWeaponType())
1005 return true;
1006 return false;
1007 }
1008
1009 /**
1010 * @brief Combine the rounds of partially used clips.
1011 */
addClip(const Item * item)1012 void equipDef_t::addClip (const Item* item)
1013 {
1014 const int ammoIdx = item->ammoDef()->idx;
1015 numItemsLoose[ammoIdx] += item->getAmmoLeft();
1016 /* Accumulate loose ammo into clips */
1017 if (numItemsLoose[ammoIdx] >= item->def()->ammo) {
1018 numItemsLoose[ammoIdx] -= item->def()->ammo;
1019 numItems[ammoIdx]++;
1020 }
1021 }
1022