UFO: Alien Invasion
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
inventory.cpp
Go to the documentation of this file.
1 
5 /*
6 Copyright (C) 2002-2020 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 
23 */
24 
25 #include "inventory.h"
26 
28  import(nullptr), _invList(nullptr), csi(nullptr), invName(nullptr)
29 {
30 }
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 
57 Item* InventoryInterface::addInvList (Inventory* const inv, const invDef_t* container)
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 
91 Item* 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 
152 bool 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 
239 inventory_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 
470 bool 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 
540 float 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 
566 int 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());
692  item.setAmmoLeft(NONE_AMMO);
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 
724 typedef enum {
729 
741 void 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 {
782  primary = WEAPON_PARTICLE_OR_NORMAL;
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 
947 void 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 }
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 
986 void InventoryInterface::initInventory (const char* name, const csi_t* csi, const inventoryImport_t* import)
987 {
988  const Item item;
989 
990  OBJZERO(*this);
991 
992  this->import = import;
993  this->invName = name;
994  this->cacheItem = item;
995  this->csi = csi;
996  this->_invList = nullptr;
997 }
998 
1000 {
1001  if (this->import == nullptr)
1002  return;
1003  this->import->FreeAll();
1004  OBJZERO(*this);
1005 }
const objDef_t * onlyWeapon
Definition: chr_shared.h:325
#define SHAPE_BIG_MAX_HEIGHT
defines the max height of an inventory container
Definition: inv_shared.h:188
inventory_action_t
Possible inventory actions for moving items between containers.
Definition: inv_shared.h:65
int price
Definition: inv_shared.h:332
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).
Definition: inventory.cpp:470
void destroyInventory(Inventory *const inv)
Destroys inventory.
Definition: inventory.cpp:521
void Sys_Error(const char *error,...)
Definition: g_main.cpp:421
void setAmmoLeft(int value)
Definition: inv_shared.h:441
chrScoreGlobal_t score
Definition: chr_shared.h:387
bool implant
Definition: inv_shared.h:282
char id[MAX_VAR]
Definition: chr_shared.h:298
bool isArmour() const
Definition: inv_shared.h:489
this is a fire definition for our weapons/ammo
Definition: inv_shared.h:110
int getAmmoLeft() const
Definition: inv_shared.h:466
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.
Definition: inventory.cpp:566
const teamDef_t * teamDef
Definition: chr_shared.h:394
bool robot
Definition: chr_shared.h:322
void resetContainer(const containerIndex_t idx)
Definition: inv_shared.h:554
void * alloc(size_t size)
Definition: inventory.h:75
Item * getContainer2(const containerIndex_t idx) const
Definition: inv_shared.h:546
bool isSecondary
Definition: inv_shared.h:286
csi_t csi
Definition: common.cpp:39
int getReloadTime() const
Definition: inv_shared.h:349
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.
Definition: inventory.cpp:239
void setY(const int val)
Definition: inv_shared.h:432
bool isMisc
Definition: inv_shared.h:288
const csi_t * csi
Definition: inventory.h:45
bool isRightDef() const
Checks whether the inventory definition is the right Hand.
Definition: inv_shared.cpp:57
const objDef_t * def(void) const
Definition: inv_shared.h:469
#define CID_ARMOUR
Definition: inv_shared.h:54
invDef_t ids[MAX_INVDEFS]
Definition: q_shared.h:524
bool isLeftDef() const
Checks whether a given inventory definition is of special type.
Definition: inv_shared.cpp:66
#define nullptr
Definition: cxx.h:53
void Com_Printf(const char *const fmt,...)
Definition: common.cpp:386
int numODs
Definition: q_shared.h:518
bool isReloadable() const
Definition: inv_shared.h:352
bool isSameAs(const Item *const other) const
Check if the (physical) information of 2 items is exactly the same.
Definition: inv_shared.cpp:536
const struct objDef_s * ammos[MAX_AMMOS_PER_OBJDEF]
Definition: inv_shared.h:307
Item * getNext() const
Definition: inv_shared.h:451
Defines all attributes of objects used in the inventory.
Definition: inv_shared.h:264
char name[MAX_VAR]
Definition: inv_shared.h:372
byte dmgtype
Definition: inv_shared.h:325
int getWeight() const
Return the weight of an item.
Definition: inv_shared.cpp:522
#define CID_BELT
Definition: inv_shared.h:52
#define WEIGHT_FACTOR
Definition: q_shared.h:285
const char * invName
Definition: inventory.h:46
bool headgear
Definition: inv_shared.h:280
bool scroll
Definition: inv_shared.h:383
#define CID_LEFT
Definition: inv_shared.h:48
bool fireTwoHanded
Definition: inv_shared.h:279
item instance data, with linked list capability
Definition: inv_shared.h:402
inventory definition with all its containers
Definition: inv_shared.h:525
bool deplete
Definition: inv_shared.h:301
#define CID_HEADGEAR
Definition: inv_shared.h:50
#define CID_RIGHT
Definition: inv_shared.h:47
void setNext(Item *nx)
Definition: inv_shared.h:426
#define SHAPE_BIG_MAX_WIDTH
32 bit mask
Definition: inv_shared.h:190
#define OBJZERO(obj)
Definition: shared.h:178
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.
Definition: inv_shared.cpp:876
void EquipActor(character_t *const chr, const equipDef_t *ed, const objDef_t *weapon, int maxWeight)
Definition: inventory.cpp:947
bool single
Definition: inv_shared.h:375
The csi structure is the client-server-information structure which contains all the static data neede...
Definition: q_shared.h:515
#define GET_TU(ab, md)
Definition: q_shared.h:291
int getWeight() const
Get the weight of the items in the given inventory (excluding those in temp containers).
Definition: inv_shared.cpp:937
void addAmount(int value)
Definition: inv_shared.h:497
void initInventory(const char *name, const csi_t *csi, const inventoryImport_t *import)
Initializes the inventory definition by linking the ->next pointers properly.
Definition: inventory.cpp:986
bool armour
Definition: inv_shared.h:376
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 ...
Definition: inventory.cpp:741
#define CID_IMPLANT
Definition: inv_shared.h:49
Item * addInvList(Inventory *const inv, const invDef_t *container)
Definition: inventory.cpp:57
bool oneshot
Definition: inv_shared.h:299
int canHoldItem(const invDef_t *container, const objDef_t *od, const int x, const int y, const Item *ignoredItem) const
Definition: inv_shared.cpp:761
bool weapon
Definition: inv_shared.h:277
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition: common.cpp:398
int getAmount() const
Definition: inv_shared.h:463
Item * getNextItem(const Item *prev) const
Definition: inv_shared.cpp:671
Item * _invList
Definition: inv_shared.h:516
void EquipActorRobot(Inventory *const inv, const objDef_t *weapon)
Equip robot actor with default weapon. (defined in ugv_t->weapon)
Definition: inventory.cpp:705
char id[MAX_VAR]
Definition: inv_shared.h:606
const char * name
Definition: inv_shared.h:267
void init()
Definition: inv_shared.cpp:703
#define CID_HOLSTER
Definition: inv_shared.h:53
#define CID_BACKPACK
Definition: inv_shared.h:51
const Container & getContainer(const containerIndex_t idx) const
Definition: inv_shared.h:537
int32_t containerIndex_t
Definition: inv_shared.h:46
bool isLoadableInWeapon(const objDef_s *weapon) const
Checks if an item can be used to reload a weapon.
Definition: inv_shared.cpp:356
bool isFloorDef() const
Checks whether the inventory definition is the floor.
Definition: inv_shared.cpp:48
int damNormal
Definition: q_shared.h:528
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
#define GET_ENCUMBRANCE_PENALTY(weight, max)
Definition: q_shared.h:287
char name[MAX_VAR]
Definition: chr_shared.h:299
void EquipActorMelee(Inventory *const inv, const teamDef_t *td)
Equip melee actor with item defined per teamDefs.
Definition: inventory.cpp:682
float frand(void)
Return random values between 0 and 1.
Definition: mathlib.cpp:506
QGL_EXTERN GLint i
Definition: r_gl.h:113
equipPrimaryWeaponType_t
Types of weapon that can be selected.
Definition: inventory.cpp:724
const Container * getNextCont(const Container *prev, bool inclTemp=false) const
Definition: inv_shared.cpp:722
int numItems[MAX_OBJDEFS]
Definition: inv_shared.h:608
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
void setAmmoDef(const objDef_t *od)
Definition: inv_shared.h:435
bool temp
Definition: inv_shared.h:381
int ammo
Definition: inv_shared.h:293
void setX(const int val)
Definition: inv_shared.h:429
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.
Definition: inv_shared.cpp:844
void free(void *data)
Definition: inventory.h:80
bool isArmour() const
Definition: inv_shared.h:346
#define NONE_AMMO
Definition: defines.h:69
bool isArmourDef() const
Checks whether a given inventory definition is of special type.
Definition: inv_shared.cpp:84
const objDef_t * ammoDef(void) const
Definition: inv_shared.h:460
inventory definition for our menus
Definition: inv_shared.h:371
bool armour
Definition: chr_shared.h:323
void setAmount(int value)
Definition: inv_shared.h:438
const objDef_t * INVSH_GetItemByIDX(int index)
Returns the item that belongs to the given index or nullptr if the index is invalid.
Definition: inv_shared.cpp:266
Inventory inv
Definition: chr_shared.h:392
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 ...
Definition: inventory.cpp:540
int getX() const
Definition: inv_shared.h:454
#define WEAPONLESS_BONUS
Definition: inventory.cpp:555
containerIndex_t id
Definition: inv_shared.h:373
objDef_t ods[MAX_OBJDEFS]
Definition: q_shared.h:517
bool isEquipDef() const
Checks whether a given inventory definition is of special type.
Definition: inv_shared.cpp:75
const fireDef_t * getSlowestFireDef() const
Get the firedef that uses the most TU for this item.
Definition: inv_shared.cpp:610
#define lengthof(x)
Definition: shared.h:105
int rotated
Definition: inv_shared.h:412
#define NONE
Definition: defines.h:68
int numAmmos
Definition: inv_shared.h:308
bool isPrimary
Definition: inv_shared.h:285
void emptyContainer(Inventory *const inv, const containerIndex_t container)
Clears the linked list of a container - removes all items from this container.
Definition: inventory.cpp:501
#define DEBUG_SHARED
Definition: defines.h:55
void destroyInventoryInterface(void)
Definition: inventory.cpp:999
bool weapons
Definition: chr_shared.h:324
int skills[SKILL_NUM_TYPES]
Definition: chr_shared.h:122
void setContainer(const containerIndex_t idx, Item *cont)
Definition: inv_shared.h:550
const char * id
Definition: inv_shared.h:268
int getY() const
Definition: inv_shared.h:457
bool headgear
Definition: inv_shared.h:378
bool removeFromInventory(Inventory *const inv, const invDef_t *container, Item *fItem) __attribute__((warn_unused_result))
Definition: inventory.cpp:152
void removeInvList(Item *invList)
Definition: inventory.cpp:32
int GetUsedSlots()
Calculate the number of used inventory slots.
Definition: inventory.cpp:966
bool implant
Definition: inv_shared.h:377
Describes a character with all its attributes.
Definition: chr_shared.h:369