UFO: Alien Invasion
Loading...
Searching...
No Matches
inventory.cpp
Go to the documentation of this file.
1
4
5/*
6Copyright (C) 2002-2025 UFO: Alien Invasion.
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17See the GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23*/
24
25#include "inventory.h"
26
31
33{
34 Com_DPrintf(DEBUG_SHARED, "removeInvList: remove one slot (%s)\n", invName);
35
36 /* first entry */
37 if (this->_invList == invList) {
38 Item* ic = this->_invList;
39 this->_invList = ic->getNext();
40 free(ic);
41 } else {
42 Item* ic = this->_invList;
43 Item* prev = nullptr;
44 while (ic) {
45 if (ic == invList) {
46 if (prev)
47 prev->setNext(ic->getNext());
48 free(ic);
49 break;
50 }
51 prev = ic;
52 ic = ic->getNext();
53 }
54 }
55}
56
58{
59 Item* newEntry = static_cast<Item*>(alloc(sizeof(Item)));
60 newEntry->setNext(nullptr); /* not really needed - but for better readability */
61
62 Com_DPrintf(DEBUG_SHARED, "AddInvList: add one slot (%s)\n", invName);
63
64 Item* firstEntry = inv->getContainer2(container->id);
65 if (!firstEntry) {
66 /* create the list */
67 inv->setContainer(container->id, newEntry);
68 } else {
69 /* read up to the end of the list */
70 Item* list = firstEntry;
71 while (list->getNext())
72 list = list->getNext();
73 /* append our new item as the last in the list */
74 list->setNext(newEntry);
75 }
76
77 return newEntry;
78}
79
91Item* InventoryInterface::addToInventory (Inventory* const inv, const Item* const item, const invDef_t* container, int x, int y, int amount)
92{
93 if (!item->def())
94 return nullptr;
95
96 if (amount <= 0)
97 return nullptr;
98
99 assert(inv);
100 assert(container);
101
102 if (container->single && inv->getContainer2(container->id))
103 return nullptr;
104
105 Item* ic;
106 /* CID_EQUIP and CID_FLOOR */
107 if (container->temp) {
108 for (ic = inv->getContainer2(container->id); ic; ic = ic->getNext())
109 if (ic->isSameAs(item)) {
110 ic->addAmount(amount);
111 Com_DPrintf(DEBUG_SHARED, "addToInventory: Amount of '%s': %i (%s)\n",
112 ic->def()->name, ic->getAmount(), invName);
113 return ic;
114 }
115 }
116
117 if (x < 0 || y < 0 || x >= SHAPE_BIG_MAX_WIDTH || y >= SHAPE_BIG_MAX_HEIGHT) {
118 /* No (sane) position in container given as parameter - find free space on our own. */
119 inv->findSpace(container, item, &x, &y, nullptr);
120 if (x == NONE)
121 return nullptr;
122 }
123
124 const int checkedTo = inv->canHoldItem(container, item->def(), x, y, nullptr);
125 assert(checkedTo);
126
127 /* not found - add a new one */
128 ic = addInvList(inv, container);
129
130 /* Set the data in the new entry to the data we got via function-parameters.*/
131 *ic = *item;
132 ic->setNext(nullptr);
133 ic->setAmount(amount);
134
135 /* don't reset an already applied rotation */
136 if (checkedTo == INV_FITS_ONLY_ROTATED)
137 ic->rotated = true;
138 ic->setX(x);
139 ic->setY(y);
140
141 return ic;
142}
143
152bool InventoryInterface::removeFromInventory (Inventory* const inv, const invDef_t* container, Item* fItem)
153{
154 assert(inv);
155 assert(container);
156 assert(fItem);
157
158 Item* ic = inv->getContainer2(container->id);
159 if (!ic)
160 return false;
161
167 if (container->single || ic == fItem) {
168 this->cacheItem = *ic;
169 /* temp container like CID_EQUIP and CID_FLOOR */
170 if (container->temp && ic->getAmount() > 1) {
171 ic->addAmount(-1);
172 Com_DPrintf(DEBUG_SHARED, "removeFromInventory: Amount of '%s': %i (%s)\n",
173 ic->def()->name, ic->getAmount(), invName);
174 return true;
175 }
176
177 if (container->single && ic->getNext())
178 Com_Printf("removeFromInventory: Error: single container %s has many items. (%s)\n", container->name, invName);
179
180 /* An item in other containers than CID_FLOOR or CID_EQUIP should
181 * always have an amount value of 1.
182 * The other container types do not support stacking.*/
183 assert(ic->getAmount() == 1);
184
185 inv->setContainer(container->id, ic->getNext());
186
187 /* updated invUnused to be able to reuse this space later again */
188 removeInvList(ic);
189
190 return true;
191 }
192
193 for (Item* previous = inv->getContainer2(container->id); ic; ic = ic->getNext()) {
194 if (ic != fItem) {
195 previous = ic;
196 continue;
197 }
198
199 this->cacheItem = *ic;
200 /* temp container like CID_EQUIP and CID_FLOOR */
201 if (ic->getAmount() > 1 && container->temp) {
202 ic->addAmount(-1);
203 Com_DPrintf(DEBUG_SHARED, "removeFromInventory: Amount of '%s': %i (%s)\n",
204 ic->def()->name, ic->getAmount(), invName);
205 return true;
206 }
207
208 if (ic == inv->getContainer2(container->id))
209 inv->setContainer(container->id, inv->getContainer2(container->id)->getNext());
210 else
211 previous->setNext(ic->getNext());
212
213 removeInvList(ic);
214
215 return true;
216 }
217 return false;
218}
219
239inventory_action_t InventoryInterface::moveInInventory (Inventory* const inv, const invDef_t* from, Item* fItem, const invDef_t* to, int tx, int ty, int* TU, Item** uponItem)
240{
241 assert(to);
242 assert(from);
243
244 if (uponItem)
245 *uponItem = nullptr;
246
247 if (from == to && fItem->getX() == tx && fItem->getY() == ty)
248 return IA_NONE;
249
250 int time = from->out + to->in;
251 if (from == to) {
252 if (from->isFloorDef())
253 time = 0;
254 else
255 time /= 2;
256 }
257
258 if (TU && *TU < time)
259 return IA_NOTIME;
260
261 assert(inv);
262
263 int checkedTo = INV_DOES_NOT_FIT;
264 /* Special case for moving an item within the same container. */
265 if (from == to) {
266 /* Do nothing if we move inside a scroll container. */
267 if (from->scroll)
268 return IA_NONE;
269
270 const Container& cont = inv->getContainer(from->id);
271 Item* item = nullptr;
272 while ((item = cont.getNextItem(item))) {
273 if (item != fItem)
274 continue;
275
276 if (item->getAmount() <= 1)
277 continue;
278 checkedTo = inv->canHoldItem(to, item->def(), tx, ty, fItem);
279 if (!(checkedTo & INV_FITS))
280 return IA_NONE;
281
282 item->setX(tx);
283 item->setY(ty);
284 if (uponItem)
285 *uponItem = item;
286 return IA_MOVE;
287 }
288 }
289
290 /* If weapon is twohanded and is moved from hand to hand do nothing. */
291 /* Twohanded weapon are only in CID_RIGHT. */
292 if (fItem->def()->fireTwoHanded && to->isLeftDef() && from->isRightDef()) {
293 return IA_NONE;
294 }
295
296 /* If non-armour moved to an armour slot then abort.
297 * Same for non extension items when moved to an extension slot. */
298 if ((to->armour && !fItem->isArmour())
299 || (to->implant && !fItem->def()->implant)
300 || (to->headgear && !fItem->def()->headgear)) {
301 return IA_NONE;
302 }
303
304 /* Check if the target is a blocked inv-armour and source!=dest. */
305 if (to->single)
306 checkedTo = inv->canHoldItem(to, fItem->def(), 0, 0, fItem);
307 else {
308 if (tx == NONE || ty == NONE)
309 inv->findSpace(to, fItem, &tx, &ty, fItem);
310 /* still no valid location found */
311 if (tx == NONE || ty == NONE)
312 return IA_NONE;
313
314 checkedTo = inv->canHoldItem(to, fItem->def(), tx, ty, fItem);
315 }
316
317 Item* ic;
318 bool alreadyRemovedSource = false;
319 if (to->armour && from != to && !checkedTo) {
320 /* Store x/y origin coordinates of removed (source) item.
321 * When we re-add it we can use this. */
322 const int cacheFromX = fItem->getX();
323 const int cacheFromY = fItem->getY();
324
325 /* Check if destination/blocking item is the same as source/from item.
326 * In that case the move is not needed -> abort. */
327 Item* icTo = inv->getItemAtPos(to, tx, ty);
328 if (fItem->def() == icTo->def())
329 return IA_NONE;
330
331 /* Actually remove the ammo from the 'from' container. */
332 if (!removeFromInventory(inv, from, fItem))
333 return IA_NONE;
334 else
335 /* Removal successful - store this info. */
336 alreadyRemovedSource = true;
337
338 Item cacheItem2 = this->cacheItem; /* Save/cache (source) item. The cacheItem is modified in MoveInInventory. */
339
340 /* Move the destination item to the source. */
341 moveInInventory(inv, to, icTo, from, cacheFromX, cacheFromY, TU, uponItem);
342
343 /* Reset the cached item (source) (It'll be move to container emptied by destination item later.) */
344 this->cacheItem = cacheItem2;
345 checkedTo = inv->canHoldItem(to, this->cacheItem.def(), 0, 0, fItem);
346 } else if (!checkedTo) {
347 /* Get the target-invlist (e.g. a weapon). We don't need to check for
348 * scroll because checkedTo is always true here. */
349 ic = inv->getItemAtPos(to, tx, ty);
350
351 if (ic && !to->isEquipDef() && fItem->def()->isLoadableInWeapon(ic->def())) {
352 /* A target-item was found and the dragged item (implicitly ammo)
353 * can be loaded in it (implicitly weapon). */
354 if (ic->getAmmoLeft() >= ic->def()->ammo && ic->ammoDef() == fItem->def()) {
355 /* Weapon already fully loaded with the same ammunition -> abort */
356 return IA_NORELOAD;
357 }
358 time += ic->def()->getReloadTime();
359 if (!TU || *TU >= time) {
360 if (TU)
361 *TU -= time;
362 if (ic->getAmmoLeft() >= ic->def()->ammo) {
363 /* exchange ammo */
364 const Item item(ic->ammoDef());
365 /* Put current ammo in place of the new ammo unless floor - there can be more than 1 item */
366 const int cacheFromX = from->isFloorDef() ? NONE : fItem->getX();
367 const int cacheFromY = from->isFloorDef() ? NONE : fItem->getY();
368
369 /* Actually remove the ammo from the 'from' container. */
370 if (!removeFromInventory(inv, from, fItem))
371 return IA_NONE;
372
373 /* Add the currently used ammo in place of the new ammo in the "from" container. */
374 if (addToInventory(inv, &item, from, cacheFromX, cacheFromY, 1) == nullptr)
375 Sys_Error("Could not reload the weapon - add to inventory failed (%s)", invName);
376
377 ic->setAmmoDef(this->cacheItem.def());
378 if (uponItem)
379 *uponItem = ic;
380 return IA_RELOAD_SWAP;
381 } else {
382 /* Actually remove the ammo from the 'from' container. */
383 if (!removeFromInventory(inv, from, fItem))
384 return IA_NONE;
385
386 ic->setAmmoDef(this->cacheItem.def());
387 /* loose ammo of type ic->m saved on server side */
388 ic->setAmmoLeft(ic->def()->ammo);
389 if (uponItem)
390 *uponItem = ic;
391 return IA_RELOAD;
392 }
393 }
394 /* Not enough time -> abort. */
395 return IA_NOTIME;
396 }
397
398 /* temp container like CID_EQUIP and CID_FLOOR */
399 if (ic && to->temp) {
400 /* We are moving to a blocked location container but it's the base-equipment floor or a battlescape floor.
401 * We add the item anyway but it'll not be displayed (yet)
402 * This is then used in addToInventory below.*/
404 inv->findSpace(to, fItem, &tx, &ty, fItem);
405 if (tx == NONE || ty == NONE) {
406 Com_DPrintf(DEBUG_SHARED, "MoveInInventory - item will be added non-visible (%s)\n", invName);
407 }
408 } else {
409 /* Impossible move -> abort. */
410 return IA_NONE;
411 }
412 }
413
414 /* twohanded exception - only CID_RIGHT is allowed for fireTwoHanded weapons */
415 if (fItem->def()->fireTwoHanded && to->isLeftDef())
416 to = &this->csi->ids[CID_RIGHT];
417
418 switch (checkedTo) {
419 case INV_DOES_NOT_FIT:
420 /* Impossible move - should be handled above, but add an abort just in case */
421 Com_Printf("MoveInInventory: Item doesn't fit into container.");
422 return IA_NONE;
423 case INV_FITS:
424 /* Remove rotated tag */
425 fItem->rotated = false;
426 break;
428 /* Set rotated tag */
429 fItem->rotated = true;
430 break;
431 case INV_FITS_BOTH:
432 /* Leave rotated tag as-is */
433 break;
434 }
435
436 /* Actually remove the item from the 'from' container (if it wasn't already removed). */
437 if (!alreadyRemovedSource)
438 if (!removeFromInventory(inv, from, fItem))
439 return IA_NONE;
440
441 /* successful */
442 if (TU)
443 *TU -= time;
444
445 assert(this->cacheItem.def());
446 ic = addToInventory(inv, &this->cacheItem, to, tx, ty, 1);
447
448 /* return data */
449 if (uponItem) {
450 assert(ic);
451 *uponItem = ic;
452 }
453
454 if (to->isArmourDef()) {
455 assert(this->cacheItem.isArmour());
456 return IA_ARMOUR;
457 }
458
459 return IA_MOVE;
460}
461
470bool InventoryInterface::tryAddToInventory (Inventory* const inv, const Item* const item, const invDef_t* container)
471{
472 int x, y;
473
474 inv->findSpace(container, item, &x, &y, nullptr);
475
476 if (x == NONE) {
477 assert(y == NONE);
478 return false;
479 } else {
480 const int checkedTo = inv->canHoldItem(container, item->def(), x, y, nullptr);
481 if (!checkedTo)
482 return false;
483
484 const bool rotated = checkedTo == INV_FITS_ONLY_ROTATED;
485 Item itemRotation = *item;
486 itemRotation.rotated = rotated;
487
488 return addToInventory(inv, &itemRotation, container, x, y, 1) != nullptr;
489 }
490}
491
502{
503 Item* ic = inv->getContainer2(containerId);
504
505 while (ic) {
506 Item* old = ic;
507 ic = ic->getNext();
508 removeInvList(old);
509 }
510
511 inv->resetContainer(containerId);
512}
513
522{
523 if (!inv)
524 return;
525
526 const Container* cont = nullptr;
527 while ((cont = inv->getNextCont(cont))) {
528 emptyContainer(inv, cont->id);
529 }
530
531 inv->init();
532}
533
540float InventoryInterface::GetInventoryState (const Inventory* inventory, int& slowestFd)
541{
542 slowestFd = 0;
543 const Container* cont = nullptr;
544 while ((cont = inventory->getNextCont(cont))) {
545 for (Item* ic = cont->_invList, *next; ic; ic = next) {
546 next = ic->getNext();
547 const fireDef_t* fireDef = ic->getSlowestFireDef();
548 if (slowestFd == 0 || (fireDef && fireDef->time > slowestFd))
549 slowestFd = fireDef->time;
550 }
551 }
552 return inventory->getWeight();
553}
554
555#define WEAPONLESS_BONUS 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) */
556
566int InventoryInterface::PackAmmoAndWeapon (character_t* const chr, const objDef_t* weapon, int missedPrimary, const equipDef_t* ed, int maxWeight)
567{
568 assert(!weapon->isArmour());
569 Item item(weapon);
570 const objDef_t* ammo = nullptr;
571
572 if (weapon->oneshot) {
573 /* The weapon provides its own ammo (i.e. it is charged or loaded in the base.) */
574 item.setAmmoLeft(weapon->ammo);
575 item.setAmmoDef(weapon);
576 Com_DPrintf(DEBUG_SHARED, "PackAmmoAndWeapon: oneshot weapon '%s' in equipment '%s' (%s).\n",
577 weapon->id, ed->id, invName);
578 } else if (!weapon->isReloadable()) {
579 item.setAmmoDef(weapon); /* no ammo needed, so fire definitions are in item */
580 } else {
581 /* find some suitable ammo for the weapon (we will have at least one if there are ammos for this
582 * weapon in equipment definition) */
583 int totalAvailableAmmo = 0;
584 for (int i = 0; i < csi->numODs; i++) {
585 const objDef_t* obj = INVSH_GetItemByIDX(i);
586 if (ed->numItems[i] && obj->isLoadableInWeapon(weapon)) {
587 totalAvailableAmmo++;
588 }
589 }
590 if (totalAvailableAmmo) {
591 int randNumber = rand() % totalAvailableAmmo;
592 for (int i = 0; i < csi->numODs; i++) {
593 const objDef_t* obj = INVSH_GetItemByIDX(i);
594 if (ed->numItems[i] && obj->isLoadableInWeapon(weapon)) {
595 randNumber--;
596 if (randNumber < 0) {
597 ammo = obj;
598 break;
599 }
600 }
601 }
602 }
603
604 if (!ammo) {
605 Com_DPrintf(DEBUG_SHARED, "PackAmmoAndWeapon: no ammo for sidearm or primary weapon '%s' in equipment '%s' (%s).\n",
606 weapon->id, ed->id, invName);
607 return 0;
608 }
609 /* load ammo */
610 item.setAmmoLeft(weapon->ammo);
611 item.setAmmoDef(ammo);
612 }
613
614 if (!item.ammoDef()) {
615 Com_Printf("PackAmmoAndWeapon: no ammo for sidearm or primary weapon '%s' in equipment '%s' (%s).\n",
616 weapon->id, ed->id, invName);
617 return 0;
618 }
619
620 Inventory* const inv = &chr->inv;
621 const int speed = chr->score.skills[ABILITY_SPEED];
622 int tuNeed = 0;
623 float weight = GetInventoryState(inv, tuNeed) + item.getWeight();
624 int maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER]));
625 if (weight > maxWeight * WEIGHT_FACTOR || tuNeed > maxTU) {
626 Com_DPrintf(DEBUG_SHARED, "PackAmmoAndWeapon: weapon too heavy: '%s' in equipment '%s' (%s).\n",
627 weapon->id, ed->id, invName);
628 return 0;
629 }
630
631 /* are we going to allow trying the left hand? */
632 const bool allowLeft = !(inv->getContainer2(CID_RIGHT) && inv->getContainer2(CID_RIGHT)->def()->fireTwoHanded);
633 int ammoMult = 1;
634 /* now try to pack the weapon */
635 bool packed = tryAddToInventory(inv, &item, &csi->ids[CID_RIGHT]);
636 if (packed)
637 ammoMult = 3;
638 if (!packed && allowLeft)
639 packed = tryAddToInventory(inv, &item, &csi->ids[CID_LEFT]);
640 if (!packed)
641 packed = tryAddToInventory(inv, &item, &csi->ids[CID_BELT]);
642 if (!packed)
643 packed = tryAddToInventory(inv, &item, &csi->ids[CID_HOLSTER]);
644 if (!packed)
645 packed = tryAddToInventory(inv, &item, &csi->ids[CID_BACKPACK]);
646 if (!packed)
647 return 0;
648
649 /* pack some more ammo in the backpack */
650 if (ammo) {
651 int numpacked = 0;
652
653 /* how many clips? */
654 int num = (1 + ed->numItems[ammo->idx])
655 * (float) (1.0f + missedPrimary / 100.0);
656
657 /* pack some ammo */
658 while (num--) {
659 weight = GetInventoryState(inv, tuNeed) + item.getWeight();
660 maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER]));
661
662 Item mun(ammo);
663 /* ammo to backpack; belt is for knives and grenades */
664 if (weight <= maxWeight * WEIGHT_FACTOR && tuNeed <= maxTU)
665 numpacked += tryAddToInventory(inv, &mun, &csi->ids[CID_BACKPACK]);
666 /* no problem if no space left; one ammo already loaded */
667 if (numpacked > ammoMult || numpacked * weapon->ammo > 11)
668 break;
669 }
670 }
671
672 return true;
673}
674
675
683{
684 assert(td->onlyWeapon);
685
686 /* Get weapon def */
687 const objDef_t* obj = td->onlyWeapon;
688
689 /* Prepare item. This kind of item has no ammo, fire definitions are in item.t. */
690 Item item(obj);
691 item.setAmmoDef(item.def());
693 /* Every melee actor weapon definition is firetwohanded, add to right hand. */
694 if (!obj->fireTwoHanded)
695 Sys_Error("INVSH_EquipActorMelee: melee weapon %s for team %s is not firetwohanded! (%s)",
696 obj->id, td->id, invName);
697 tryAddToInventory(inv, &item, &this->csi->ids[CID_RIGHT]);
698}
699
706{
707 assert(weapon);
708
709 /* Prepare weapon in item. */
710 Item item(weapon);
711 item.setAmmoLeft(weapon->ammo);
712
713 /* Get ammo for item/weapon. */
714 assert(weapon->numAmmos > 0); /* There _has_ to be at least one ammo-type. */
715 assert(weapon->ammos[0]);
716 item.setAmmoDef(weapon->ammos[0]);
717
718 tryAddToInventory(inv, &item, &this->csi->ids[CID_RIGHT]);
719}
720
729
741void InventoryInterface::EquipActorNormal (character_t* const chr, const equipDef_t* ed, int maxWeight)
742{
743 const teamDef_t* td = chr->teamDef;
744 const int numEquip = lengthof(ed->numItems);
745 int repeat = 0;
746
747 if (td->weapons) {
748 int missedPrimary = 0;
750 const objDef_t* primaryWeapon = nullptr;
751 /* Primary weapons */
752 const int maxWeaponIdx = std::min(this->csi->numODs - 1, numEquip - 1);
753 int randNumber = rand() % 100;
754 for (int i = 0; i < maxWeaponIdx; i++) {
755 const objDef_t* obj = INVSH_GetItemByIDX(i);
756 if (ed->numItems[i] && obj->weapon && obj->fireTwoHanded && obj->isPrimary) {
757 randNumber -= ed->numItems[i];
758 missedPrimary += ed->numItems[i];
759 if (!primaryWeapon && randNumber < 0)
760 primaryWeapon = obj;
761 }
762 }
763 /* See if a weapon has been selected. */
765 int hasWeapon = 0;
766 if (primaryWeapon) {
767 hasWeapon += PackAmmoAndWeapon(chr, primaryWeapon, 0, ed, maxWeight);
768 if (hasWeapon) {
769 int ammo;
770
771 /* Find the first possible ammo to check damage type. */
772 for (ammo = 0; ammo < this->csi->numODs; ammo++)
773 if (ed->numItems[ammo] && this->csi->ods[ammo].isLoadableInWeapon(primaryWeapon))
774 break;
775 if (ammo < this->csi->numODs) {
776 if (/* To avoid two particle weapons. */
777 !(this->csi->ods[ammo].dmgtype == this->csi->damParticle)
778 /* To avoid SMG + Assault Rifle */
779 && !(this->csi->ods[ammo].dmgtype == this->csi->damNormal)) {
780 primary = WEAPON_OTHER;
781 } else {
783 }
784 }
785 /* reset missedPrimary: we got a primary weapon */
786 missedPrimary = 0;
787 } else {
788 Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: primary weapon '%s' couldn't be equipped in equipment '%s' (%s).\n",
789 primaryWeapon->id, ed->id, invName);
790 repeat = WEAPONLESS_BONUS > frand();
791 }
792 }
793
794 /* Sidearms (secondary weapons with reload). */
795 do {
796 int randNumber = rand() % 100;
797 const objDef_t* secondaryWeapon = nullptr;
798 for (int i = 0; i < this->csi->numODs; i++) {
799 const objDef_t* obj = INVSH_GetItemByIDX(i);
800 if (ed->numItems[i] && obj->weapon && obj->isReloadable() && !obj->deplete && obj->isSecondary) {
801 randNumber -= ed->numItems[i] / (primary == WEAPON_PARTICLE_OR_NORMAL ? 2 : 1);
802 if (randNumber < 0) {
803 secondaryWeapon = obj;
804 break;
805 }
806 }
807 }
808
809 if (secondaryWeapon) {
810 hasWeapon += PackAmmoAndWeapon(chr, secondaryWeapon, missedPrimary, ed, maxWeight);
811 }
812 } while (!hasWeapon && repeat--);
813
814 /* Misc items and secondary weapons without reload. */
815 if (!hasWeapon)
816 repeat = WEAPONLESS_BONUS > frand();
817 else
818 repeat = 0;
819 /* Misc object probability can be bigger than 100 -- you're sure to
820 * have one misc if it fits your backpack */
821 int sum = 0;
822 for (int i = 0; i < this->csi->numODs; i++) {
823 const objDef_t* obj = INVSH_GetItemByIDX(i);
824 if (ed->numItems[i] && ((obj->weapon && obj->isSecondary
825 && (!obj->isReloadable() || obj->deplete)) || obj->isMisc)) {
826 /* if ed->num[i] is greater than 100, the first number is the number of items you'll get:
827 * don't take it into account for probability
828 * Make sure that the probability is at least one if an item can be selected */
829 sum += ed->numItems[i] ? std::max(ed->numItems[i] % 100, 1) : 0;
830 }
831 }
832 if (sum) {
833 do {
834 int randNumber = rand() % sum;
835 const objDef_t* secondaryWeapon = nullptr;
836 for (int i = 0; i < this->csi->numODs; i++) {
837 const objDef_t* obj = INVSH_GetItemByIDX(i);
838 if (ed->numItems[i] && ((obj->weapon && obj->isSecondary
839 && (!obj->isReloadable() || obj->deplete)) || obj->isMisc)) {
840 randNumber -= ed->numItems[i] ? std::max(ed->numItems[i] % 100, 1) : 0;
841 if (randNumber < 0) {
842 secondaryWeapon = obj;
843 break;
844 }
845 }
846 }
847
848 if (secondaryWeapon) {
849 int num = ed->numItems[secondaryWeapon->idx] / 100 + (ed->numItems[secondaryWeapon->idx] % 100 >= 100 * frand());
850 while (num--) {
851 hasWeapon += PackAmmoAndWeapon(chr, secondaryWeapon, 0, ed, maxWeight);
852 }
853 }
854 } while (repeat--); /* Gives more if no serious weapons. */
855 }
856
857 /* If no weapon at all, bad guys will always find a blade to wield. */
858 if (!hasWeapon) {
859 int maxPrice = 0;
860 const objDef_t* blade = nullptr;
861 Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: no weapon picked in equipment '%s', defaulting to the most expensive secondary weapon without reload. (%s)\n",
862 ed->id, invName);
863 for (int i = 0; i < this->csi->numODs; i++) {
864 const objDef_t* obj = INVSH_GetItemByIDX(i);
865 if (ed->numItems[i] && obj->weapon && obj->isSecondary && !obj->isReloadable()) {
866 if (obj->price > maxPrice) {
867 maxPrice = obj->price;
868 blade = obj;
869 }
870 }
871 }
872 if (maxPrice)
873 hasWeapon += PackAmmoAndWeapon(chr, blade, 0, ed, maxWeight);
874 }
875 /* If still no weapon, something is broken, or no blades in equipment. */
876 if (!hasWeapon)
877 Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: cannot add any weapon; no secondary weapon without reload detected for equipment '%s' (%s).\n",
878 ed->id, invName);
879
880 /* Armour; especially for those without primary weapons. */
881 repeat = (float) missedPrimary > frand() * 100.0;
882 } else {
883 return;
884 }
885
886 Inventory* const inv = &chr->inv;
887 const int speed = chr->score.skills[ABILITY_SPEED];
888 if (td->armour) {
889 do {
890 int randNumber = rand() % 100;
891 for (int i = 0; i < this->csi->numODs; i++) {
892 const objDef_t* armour = INVSH_GetItemByIDX(i);
893 if (ed->numItems[i] && armour->isArmour()) {
894 randNumber -= ed->numItems[i];
895 if (randNumber < 0) {
896 const Item item(armour);
897 int tuNeed = 0;
898 const int weight = GetInventoryState(inv, tuNeed) + item.getWeight();
899 const int maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER]));
900 if (weight > maxWeight * WEIGHT_FACTOR || tuNeed > maxTU)
901 continue;
902 if (tryAddToInventory(inv, &item, &this->csi->ids[CID_ARMOUR])) {
903 repeat = 0;
904 break;
905 }
906 }
907 }
908 }
909 } while (repeat-- > 0);
910 } else {
911 Com_DPrintf(DEBUG_SHARED, "INVSH_EquipActor: teamdef '%s' may not carry armour (%s)\n",
912 td->name, invName);
913 }
914
915 {
916 int randNumber = rand() % 10;
917 for (int i = 0; i < this->csi->numODs; i++) {
918 if (ed->numItems[i]) {
919 const objDef_t* miscItem = INVSH_GetItemByIDX(i);
920 if (miscItem->isMisc && !miscItem->weapon) {
921 randNumber -= ed->numItems[i];
922 if (randNumber < 0) {
923 const bool oneShot = miscItem->oneshot;
924 const Item item(miscItem, oneShot ? miscItem : nullptr, oneShot ? miscItem->ammo : NONE_AMMO);
925 containerIndex_t container;
926 int tuNeed;
927 const fireDef_t* itemFd = item.getSlowestFireDef();
928 const float weight = GetInventoryState(inv, tuNeed) + item.getWeight();
929 const int maxTU = GET_TU(speed, GET_ENCUMBRANCE_PENALTY(weight, chr->score.skills[ABILITY_POWER]));
930
931 if (miscItem->headgear)
932 container = CID_HEADGEAR;
933 else if (miscItem->implant)
934 container = CID_IMPLANT;
935 else
936 container = CID_BACKPACK;
937 if (weight > maxWeight * WEIGHT_FACTOR || tuNeed > maxTU || (itemFd && itemFd->time > maxTU))
938 continue;
939 tryAddToInventory(inv, &item, &this->csi->ids[container]);
940 }
941 }
942 }
943 }
944 }
945}
946
947void InventoryInterface::EquipActor (character_t* const chr, const equipDef_t* ed, const objDef_t* weapon, int maxWeight)
948{
949 if (chr->teamDef->robot && weapon) {
950 if (weapon->numAmmos > 0)
951 EquipActorRobot(&chr->inv, weapon);
952 else if (weapon->fireTwoHanded)
953 EquipActorMelee(&chr->inv, chr->teamDef);
954 else
955 Com_Printf("EquipActor: weapon %s has no ammo assigned and must not be fired two handed\n", weapon->id);
956 } else if (chr->teamDef->weapons && ed) {
957 EquipActorNormal(chr, ed, maxWeight);
958 } else {
959 Com_Printf("EquipActor: actor with no equipment\n");
960 }
961}
962
967{
968 int i = 0;
969 const Item* slot = _invList;
970 while (slot) {
971 slot = slot->getNext();
972 i++;
973 }
974 Com_DPrintf(DEBUG_SHARED, "Used inventory slots %i (%s)\n", i, invName);
975 return i;
976}
977
986void InventoryInterface::initInventory (const char* name, const csi_t* csi, const inventoryImport_t* import)
987{
988 const Item item;
989
990 this->import = import;
991 this->invName = name;
992 this->cacheItem = item;
993 this->csi = csi;
994 this->_invList = nullptr;
995}
996
998{
999 if (this->import == nullptr)
1000 return;
1001 this->import->FreeAll();
1002 initInventory(nullptr, nullptr, nullptr);
1003}
@ ABILITY_POWER
Definition chr_shared.h:37
@ ABILITY_SPEED
Definition chr_shared.h:38
Item * _invList
Definition inv_shared.h:516
Item * getNextItem(const Item *prev) const
inventory definition with all its containers
Definition inv_shared.h:525
int canHoldItem(const invDef_t *container, const objDef_t *od, const int x, const int y, const Item *ignoredItem) const
void findSpace(const invDef_t *container, const Item *item, int *const px, int *const py, const Item *ignoredItem) const
Finds space for item in inv at container.
int getWeight() const
Get the weight of the items in the given inventory (excluding those in temp containers).
void setContainer(const containerIndex_t idx, Item *cont)
Definition inv_shared.h:550
void resetContainer(const containerIndex_t idx)
Definition inv_shared.h:554
Item * getItemAtPos(const invDef_t *container, const int x, const int y) const
Searches if there is an item at location (x,y) in a container.
const Container & getContainer(const containerIndex_t idx) const
Definition inv_shared.h:537
Item * getContainer2(const containerIndex_t idx) const
Definition inv_shared.h:546
const Container * getNextCont(const Container *prev, bool inclTemp=false) const
void init()
void destroyInventory(Inventory *const inv)
Destroys inventory.
void removeInvList(Item *invList)
Definition inventory.cpp:32
void free(void *data)
Definition inventory.h:80
bool tryAddToInventory(Inventory *const inv, const Item *const item, const invDef_t *container)
Tries to add an item to a container (in the inventory inv).
inventory_action_t moveInInventory(Inventory *const inv, const invDef_t *from, Item *item, const invDef_t *to, int tx, int ty, int *TU, Item **icp)
Conditions for moving items between containers.
int PackAmmoAndWeapon(character_t *const chr, const objDef_t *weapon, int missedPrimary, const equipDef_t *ed, int maxWeight)
Pack a weapon, possibly with some ammo.
int GetUsedSlots()
Calculate the number of used inventory slots.
void EquipActorMelee(Inventory *const inv, const teamDef_t *td)
Equip melee actor with item defined per teamDefs.
const char * invName
Definition inventory.h:46
const inventoryImport_t * import
Definition inventory.h:42
void initInventory(const char *name, const csi_t *csi, const inventoryImport_t *import)
Initializes the inventory definition by linking the ->next pointers properly.
void EquipActorNormal(character_t *const chr, const equipDef_t *ed, int maxWeight)
Fully equip one actor. The equipment that is added to the inventory of the given actor is taken from ...
bool removeFromInventory(Inventory *const inv, const invDef_t *container, Item *fItem) __attribute__((warn_unused_result))
void EquipActor(character_t *const chr, const equipDef_t *ed, const objDef_t *weapon, int maxWeight)
void emptyContainer(Inventory *const inv, const containerIndex_t container)
Clears the linked list of a container - removes all items from this container.
void EquipActorRobot(Inventory *const inv, const objDef_t *weapon)
Equip robot actor with default weapon. (defined in ugv_t->weapon).
void destroyInventoryInterface(void)
void * alloc(size_t size)
Definition inventory.h:75
const csi_t * csi
Definition inventory.h:45
Item * addToInventory(Inventory *const inv, const Item *const item, const invDef_t *container, int x, int y, int amount) __attribute__((warn_unused_result))
Add an item to a specified container in a given inventory.
Definition inventory.cpp:91
Item * addInvList(Inventory *const inv, const invDef_t *container)
Definition inventory.cpp:57
float GetInventoryState(const Inventory *inventory, int &slowestFd)
Get the state of the given inventory: the items weight and the min TU needed to make full use of all ...
item instance data, with linked list capability
Definition inv_shared.h:402
bool isArmour() const
Definition inv_shared.h:489
const objDef_t * ammoDef(void) const
Definition inv_shared.h:460
int getAmmoLeft() const
Definition inv_shared.h:466
int getX() const
Definition inv_shared.h:454
void setAmount(int value)
Definition inv_shared.h:438
void setAmmoDef(const objDef_t *od)
Definition inv_shared.h:435
void addAmount(int value)
Definition inv_shared.h:497
int rotated
Definition inv_shared.h:412
int getAmount() const
Definition inv_shared.h:463
const objDef_t * def(void) const
Definition inv_shared.h:469
void setY(const int val)
Definition inv_shared.h:432
const fireDef_t * getSlowestFireDef() const
Get the firedef that uses the most TU for this item.
int getY() const
Definition inv_shared.h:457
void setX(const int val)
Definition inv_shared.h:429
int getWeight() const
Return the weight of an item.
bool isSameAs(const Item *const other) const
Check if the (physical) information of 2 items is exactly the same.
void setNext(Item *nx)
Definition inv_shared.h:426
void setAmmoLeft(int value)
Definition inv_shared.h:441
Item * getNext() const
Definition inv_shared.h:451
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition common.cpp:440
void Com_Printf(const char *const fmt,...)
Definition common.cpp:428
#define nullptr
Definition cxx.h:53
#define NONE_AMMO
Definition defines.h:69
#define NONE
Definition defines.h:68
#define DEBUG_SHARED
Definition defines.h:55
void Sys_Error(const char *error,...)
Definition g_main.cpp:421
const objDef_t * INVSH_GetItemByIDX(int index)
Returns the item that belongs to the given index or nullptr if the index is invalid.
#define CID_BACKPACK
Definition inv_shared.h:51
#define CID_BELT
Definition inv_shared.h:52
#define CID_IMPLANT
Definition inv_shared.h:49
@ INV_FITS
Definition inv_shared.h:363
@ INV_DOES_NOT_FIT
Definition inv_shared.h:362
@ INV_FITS_ONLY_ROTATED
Definition inv_shared.h:364
@ INV_FITS_BOTH
Definition inv_shared.h:365
#define CID_ARMOUR
Definition inv_shared.h:54
#define CID_HEADGEAR
Definition inv_shared.h:50
int32_t containerIndex_t
Definition inv_shared.h:46
#define SHAPE_BIG_MAX_WIDTH
32 bit mask
Definition inv_shared.h:190
#define SHAPE_BIG_MAX_HEIGHT
defines the max height of an inventory container
Definition inv_shared.h:188
#define CID_LEFT
Definition inv_shared.h:48
inventory_action_t
Possible inventory actions for moving items between containers.
Definition inv_shared.h:65
@ IA_NONE
Definition inv_shared.h:66
@ IA_NORELOAD
Definition inv_shared.h:74
@ IA_MOVE
Definition inv_shared.h:68
@ IA_ARMOUR
Definition inv_shared.h:69
@ IA_NOTIME
Definition inv_shared.h:73
@ IA_RELOAD
Definition inv_shared.h:70
@ IA_RELOAD_SWAP
Definition inv_shared.h:71
#define CID_HOLSTER
Definition inv_shared.h:53
#define CID_RIGHT
Definition inv_shared.h:47
#define WEAPONLESS_BONUS
equipPrimaryWeaponType_t
Types of weapon that can be selected.
@ WEAPON_PARTICLE_OR_NORMAL
@ WEAPON_NO_PRIMARY
@ WEAPON_OTHER
float frand(void)
Return random values between 0 and 1.
Definition mathlib.cpp:506
#define GET_TU(ab, md)
Definition q_shared.h:291
#define WEIGHT_FACTOR
Definition q_shared.h:285
#define GET_ENCUMBRANCE_PENALTY(weight, max)
Definition q_shared.h:287
QGL_EXTERN GLint i
Definition r_gl.h:113
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition r_gl.h:110
#define lengthof(x)
Definition shared.h:105
Describes a character with all its attributes.
Definition chr_shared.h:388
const teamDef_t * teamDef
Definition chr_shared.h:413
chrScoreGlobal_t score
Definition chr_shared.h:406
Inventory inv
Definition chr_shared.h:411
int skills[SKILL_NUM_TYPES]
Definition chr_shared.h:122
The csi structure is the client-server-information structure which contains all the static data neede...
Definition q_shared.h:515
char id[MAX_VAR]
Definition inv_shared.h:606
int numItems[MAX_OBJDEFS]
Definition inv_shared.h:608
this is a fire definition for our weapons/ammo
Definition inv_shared.h:110
inventory definition for our menus
Definition inv_shared.h:371
bool isFloorDef() const
Checks whether the inventory definition is the floor.
bool scroll
Definition inv_shared.h:383
char name[MAX_VAR]
Definition inv_shared.h:372
bool isArmourDef() const
Checks whether a given inventory definition is of special type.
bool isLeftDef() const
Checks whether a given inventory definition is of special type.
bool headgear
Definition inv_shared.h:378
bool isEquipDef() const
Checks whether a given inventory definition is of special type.
containerIndex_t id
Definition inv_shared.h:373
bool single
Definition inv_shared.h:375
bool implant
Definition inv_shared.h:377
bool isRightDef() const
Checks whether the inventory definition is the right Hand.
bool temp
Definition inv_shared.h:381
bool armour
Definition inv_shared.h:376
Defines all attributes of objects used in the inventory.
Definition inv_shared.h:264
const struct objDef_s * ammos[MAX_AMMOS_PER_OBJDEF]
Definition inv_shared.h:307
int getReloadTime() const
Definition inv_shared.h:349
bool isPrimary
Definition inv_shared.h:285
bool deplete
Definition inv_shared.h:301
bool weapon
Definition inv_shared.h:277
bool headgear
Definition inv_shared.h:280
bool oneshot
Definition inv_shared.h:299
bool isSecondary
Definition inv_shared.h:286
bool isLoadableInWeapon(const objDef_s *weapon) const
Checks if an item can be used to reload a weapon.
int numAmmos
Definition inv_shared.h:308
bool fireTwoHanded
Definition inv_shared.h:279
bool isReloadable() const
Definition inv_shared.h:352
bool implant
Definition inv_shared.h:282
const char * id
Definition inv_shared.h:268
bool isMisc
Definition inv_shared.h:288
bool isArmour() const
Definition inv_shared.h:346
const char * name
Definition inv_shared.h:267
bool weapons
Definition chr_shared.h:335
const objDef_t * onlyWeapon
Definition chr_shared.h:336
char id[MAX_VAR]
Definition chr_shared.h:309
bool armour
Definition chr_shared.h:334
char name[MAX_VAR]
Definition chr_shared.h:310