1 /**
2 * @file
3 * @brief General actor related inventory function for are used in every game mode
4 * @note Inventory functions prefix: INV_
5 */
6
7 /*
8 Copyright (C) 2002-2013 UFO: Alien Invasion.
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18
19 See the GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25 */
26
27 #include "client.h"
28 #include "cl_inventory.h"
29 #include "cl_inventory_callbacks.h"
30 #include "../shared/parse.h"
31 #include "ui/ui_popup.h"
32 #include "cgame/cl_game.h"
33
34 /**
35 * @brief Gets equipment definition by id.
36 * @param[in] name An id taken from scripts.
37 * @return Found @c equipDef_t or @c nullptr if no equipment definition found.
38 */
INV_GetEquipmentDefinitionByID(const char * name)39 const equipDef_t* INV_GetEquipmentDefinitionByID (const char* name)
40 {
41 int i;
42
43 for (i = 0; i < csi.numEDs; i++) {
44 const equipDef_t* ed = &csi.eds[i];
45 if (Q_streq(name, ed->id))
46 return ed;
47 }
48
49 Com_Error(ERR_DROP, "Could not find equipment %s", name);
50 }
51
52 /**
53 * @brief Move item between containers.
54 * @param[in] inv Pointer to the inventory we are working on.
55 * @param[in] toContainer Pointer to target container, to place the item in.
56 * @param[in] toX target x position in the toContainer container.
57 * @param[in] toY target y position in the toContainer container.
58 * @param[in] fromContainer Pointer to source container, the item is in.
59 * @param[in] fItem Pointer to item being moved.
60 * @param[out] uponItem The item the moving item is eventually dropped upon.
61 * @note If you set toX or toY to -1/NONE the item is automatically placed on
62 * @note a free spot in the targetContainer
63 * @return true if the move was successful.
64 */
INV_MoveItem(Inventory * inv,const invDef_t * toContainer,int toX,int toY,const invDef_t * fromContainer,Item * fItem,Item ** uponItem)65 bool INV_MoveItem (Inventory* inv, const invDef_t* toContainer, int toX, int toY,
66 const invDef_t* fromContainer, Item* fItem, Item** uponItem)
67 {
68 if (toX >= SHAPE_BIG_MAX_WIDTH || toY >= SHAPE_BIG_MAX_HEIGHT)
69 return false;
70
71 if (!fItem)
72 return false;
73
74 const int maxWeight = GAME_GetChrMaxLoad(GAME_GetSelectedChr());
75 if (!inv->canHoldItemWeight(fromContainer->id, toContainer->id, *fItem, maxWeight)) {
76 UI_Popup(_("Warning"), _("This soldier can not carry anything else."));
77 return false;
78 }
79
80 /* move the item */
81 const int moved = cls.i.moveInInventory(inv, fromContainer, fItem, toContainer, toX, toY, nullptr, uponItem);
82
83 switch (moved) {
84 case IA_MOVE:
85 case IA_ARMOUR:
86 case IA_RELOAD:
87 case IA_RELOAD_SWAP:
88 return true;
89 default:
90 return false;
91 }
92 }
93
94 /**
95 * @brief Load a weapon with ammo.
96 * @param[in] weaponList Pointer to weapon to load.
97 * @param[in] inv Pointer to inventory where the change happen.
98 * @param[in] srcContainer Pointer to inventorydef where to search ammo.
99 * @param[in] destContainer Pointer to inventorydef where the weapon is.
100 */
INV_LoadWeapon(const Item * weaponList,Inventory * inv,const invDef_t * srcContainer,const invDef_t * destContainer)101 bool INV_LoadWeapon (const Item* weaponList, Inventory* inv, const invDef_t* srcContainer, const invDef_t* destContainer)
102 {
103 assert(weaponList);
104
105 int x, y;
106 weaponList->getFirstShapePosition(&x, &y);
107 x += weaponList->getX();
108 y += weaponList->getY();
109
110 const objDef_t* weapon = weaponList->def();
111 if (weapon->weapons[0]) {
112 Item* ic = inv->getItemAtPos(destContainer, x, y);
113 if (ic) {
114 ic->setAmmoLeft(weapon->ammo);
115 ic->setAmmoDef(weapon);
116 }
117 } else if (weapon->isReloadable()) {
118 const itemFilterTypes_t equipType = INV_GetFilterFromItem(weapon);
119 /* search an ammo */
120 for (int i = 0; i < weapon->numAmmos; i++) {
121 const objDef_t* ammo = weapon->ammos[i];
122 Item *ic = INV_SearchInInventoryWithFilter(inv, srcContainer, ammo, equipType);
123 if (ic)
124 return INV_MoveItem(inv, destContainer, x, y, srcContainer, ic, nullptr);
125 }
126 }
127
128 return false;
129 }
130
131 /**
132 * @brief Unload a weapon and put the ammo in a container.
133 * @param[in,out] weapon The weapon to unload ammo.
134 * @param[in,out] inv inventory where the change happen.
135 * @param[in] container Inventory definition where to put the removed ammo.
136 * @return @c true if the ammo was moved to the container, @c false otherwise
137 */
INV_UnloadWeapon(Item * weapon,Inventory * inv,const invDef_t * container)138 bool INV_UnloadWeapon (Item* weapon, Inventory* inv, const invDef_t* container)
139 {
140 assert(weapon);
141 if (container && inv) {
142 bool moved = false;
143 if (weapon->def() != weapon->ammoDef() && weapon->getAmmoLeft() > 0) {
144 const Item item(weapon->ammoDef());
145 moved = cls.i.addToInventory(inv, &item, container, NONE, NONE, 1) != nullptr;
146 }
147 if (moved || weapon->def() == weapon->ammoDef()) {
148 weapon->setAmmoDef(nullptr);
149 weapon->setAmmoLeft(0);
150 return moved;
151 }
152 }
153 return false;
154 }
155
156 #ifdef DEBUG
157 /**
158 * @brief Lists all object definitions.
159 * @note called with debug_listinventory
160 */
INV_InventoryList_f(void)161 static void INV_InventoryList_f (void)
162 {
163 int i;
164
165 for (i = 0; i < csi.numODs; i++) {
166 int j;
167 const objDef_t* od = INVSH_GetItemByIDX(i);
168
169 Com_Printf("Item: %s\n", od->id);
170 Com_Printf("... name -> %s\n", od->name);
171 Com_Printf("... type -> %s\n", od->type);
172 Com_Printf("... category -> %i\n", od->animationIndex);
173 Com_Printf("... weapon -> %i\n", od->weapon);
174 Com_Printf("... holdtwohanded -> %i\n", od->holdTwoHanded);
175 Com_Printf("... firetwohanded -> %i\n", od->fireTwoHanded);
176 Com_Printf("... thrown -> %i\n", od->thrown);
177 Com_Printf("... usable for weapon (if type is ammo):\n");
178 for (j = 0; j < od->numWeapons; j++) {
179 if (od->weapons[j])
180 Com_Printf(" ... %s\n", od->weapons[j]->name);
181 }
182 Com_Printf("\n");
183 }
184 }
185 #endif
186
187 /**
188 * @brief Make sure equipment definitions used to generate teams are proper.
189 * @note Check that the sum of all probabilities is smaller or equal to 100 for a weapon type.
190 * @sa INVSH_EquipActor
191 */
INV_EquipmentDefSanityCheck(void)192 static bool INV_EquipmentDefSanityCheck (void)
193 {
194 int i, j;
195 int sum;
196 bool result = true;
197
198 for (i = 0; i < csi.numEDs; i++) {
199 const equipDef_t* const ed = &csi.eds[i];
200 /* only check definitions used for generating teams */
201 if (!Q_strstart(ed->id, "alien") && !Q_strstart(ed->id, "phalanx"))
202 continue;
203
204 /* Check primary */
205 sum = 0;
206 for (j = 0; j < csi.numODs; j++) {
207 const objDef_t* const obj = INVSH_GetItemByIDX(j);
208 if (obj->weapon && obj->fireTwoHanded
209 && (INV_ItemMatchesFilter(obj, FILTER_S_PRIMARY) || INV_ItemMatchesFilter(obj, FILTER_S_HEAVY)))
210 sum += ed->numItems[j];
211 }
212 if (sum > 100) {
213 Com_Printf("INV_EquipmentDefSanityCheck: Equipment Def '%s' has a total probability for primary weapons greater than 100\n", ed->id);
214 result = false;
215 }
216
217 /* Check secondary */
218 sum = 0;
219 for (j = 0; j < csi.numODs; j++) {
220 const objDef_t* const obj = INVSH_GetItemByIDX(j);
221 if (obj->weapon && obj->isReloadable() && !obj->deplete && INV_ItemMatchesFilter(obj, FILTER_S_SECONDARY))
222 sum += ed->numItems[j];
223 }
224 if (sum > 100) {
225 Com_Printf("INV_EquipmentDefSanityCheck: Equipment Def '%s' has a total probability for secondary weapons greater than 100\n", ed->id);
226 result = false;
227 }
228
229 /* Check armour */
230 sum = 0;
231 for (j = 0; j < csi.numODs; j++) {
232 const objDef_t* const obj = INVSH_GetItemByIDX(j);
233 if (INV_ItemMatchesFilter(obj, FILTER_S_ARMOUR))
234 sum += ed->numItems[j];
235 }
236 if (sum > 100) {
237 Com_Printf("INV_EquipmentDefSanityCheck: Equipment Def '%s' has a total probability for armours greater than 100\n", ed->id);
238 result = false;
239 }
240
241 /* Don't check misc: the total probability can be greater than 100 */
242 }
243
244 return result;
245 }
246
INV_GetFilterFromItem(const objDef_t * obj)247 itemFilterTypes_t INV_GetFilterFromItem (const objDef_t* obj)
248 {
249 assert(obj);
250
251 /* heavy weapons may be primary too. check heavy first */
252 if (obj->isHeavy)
253 return FILTER_S_HEAVY;
254 if (obj->implant)
255 return FILTER_S_IMPLANT;
256 else if (obj->isPrimary)
257 return FILTER_S_PRIMARY;
258 else if (obj->isSecondary)
259 return FILTER_S_SECONDARY;
260 else if (obj->isMisc)
261 return FILTER_S_MISC;
262 else if (obj->isArmour())
263 return FILTER_S_ARMOUR;
264
265 /** @todo need to implement everything */
266 Sys_Error("INV_GetFilterFromItem: unknown filter category for item '%s'", obj->id);
267 }
268
269 /**
270 * @brief Checks if the given object/item matched the given filter type.
271 * @param[in] obj A pointer to an objDef_t item.
272 * @param[in] filterType Filter type to check against.
273 * @return @c true if obj is in filterType
274 */
INV_ItemMatchesFilter(const objDef_t * obj,const itemFilterTypes_t filterType)275 bool INV_ItemMatchesFilter (const objDef_t* obj, const itemFilterTypes_t filterType)
276 {
277 int i;
278
279 if (!obj)
280 return false;
281
282 switch (filterType) {
283 case FILTER_S_PRIMARY:
284 if (obj->isPrimary && !obj->isHeavy)
285 return true;
286
287 /* Check if one of the items that uses this ammo matches this filter type. */
288 for (i = 0; i < obj->numWeapons; i++) {
289 const objDef_t* weapon = obj->weapons[i];
290 if (weapon && weapon != obj && INV_ItemMatchesFilter(weapon, filterType))
291 return true;
292 }
293 break;
294
295 case FILTER_S_SECONDARY:
296 if (obj->isSecondary && !obj->isHeavy)
297 return true;
298
299 /* Check if one of the items that uses this ammo matches this filter type. */
300 for (i = 0; i < obj->numWeapons; i++) {
301 const objDef_t* weapon = obj->weapons[i];
302 if (weapon && weapon != obj && INV_ItemMatchesFilter(weapon, filterType))
303 return true;
304 }
305 break;
306
307 case FILTER_S_HEAVY:
308 if (obj->isHeavy)
309 return true;
310
311 /* Check if one of the items that uses this ammo matches this filter type. */
312 for (i = 0; i < obj->numWeapons; i++) {
313 const objDef_t* weapon = obj->weapons[i];
314 if (weapon && weapon != obj && INV_ItemMatchesFilter(weapon, filterType))
315 return true;
316 }
317 break;
318
319 case FILTER_S_IMPLANT:
320 return obj->implant;
321
322 case FILTER_S_ARMOUR:
323 return obj->isArmour();
324
325 case FILTER_S_MISC:
326 return obj->isMisc;
327
328 case FILTER_CRAFTITEM:
329 /** @todo Should we handle FILTER_AIRCRAFT here as well? */
330 return obj->isCraftItem();
331
332 case FILTER_UGVITEM:
333 return obj->isUGVitem;
334
335 case FILTER_DUMMY:
336 return obj->isDummy;
337
338 case FILTER_AIRCRAFT:
339 return Q_streq(obj->type, "aircraft");
340
341 case FILTER_DISASSEMBLY:
342 /** @todo I guess we should search for components matching this item here. */
343 break;
344
345 case MAX_SOLDIER_FILTERTYPES:
346 case MAX_FILTERTYPES:
347 case FILTER_ENSURE_32BIT:
348 Com_Printf("INV_ItemMatchesFilter: Unknown filter type for items: %i\n", filterType);
349 break;
350 }
351
352 /* The given filter type is unknown. */
353 return false;
354 }
355
356 /**
357 * @brief Searches if there is an item at location (x/y) in a scrollable container. You can also provide an item to search for directly (x/y is ignored in that case).
358 * @note x = x-th item in a row, y = row. i.e. x/y does not equal the "grid" coordinates as used in those containers.
359 * @param[in] inv Pointer to the inventory where we will search.
360 * @param[in] container Container in the inventory.
361 * @param[in] itemType The item to search. Will ignore "x" and "y" if set, it'll also search invisible items.
362 * @param[in] filterType Enum definition of type (types of items for filtering purposes).
363 * @return @c Item Pointer to the Item/item that is located at x/y or equals "item".
364 * @sa Inventory::getItemAtPos
365 */
INV_SearchInInventoryWithFilter(const Inventory * const inv,const invDef_t * container,const objDef_t * itemType,const itemFilterTypes_t filterType)366 Item* INV_SearchInInventoryWithFilter (const Inventory* const inv, const invDef_t* container, const objDef_t* itemType, const itemFilterTypes_t filterType)
367 {
368 Item* ic;
369
370 if (inv == nullptr)
371 return nullptr;
372
373 if (itemType == nullptr)
374 return nullptr;
375
376 for (ic = inv->getContainer2(container->id); ic; ic = ic->getNext()) {
377 /* Search only in the items that could get displayed. */
378 if (ic && ic->def() && (filterType == MAX_FILTERTYPES || INV_ItemMatchesFilter(ic->def(), filterType))) {
379 /* We search _everything_, no matter what location it is (i.e. x/y are ignored). */
380 if (itemType == ic->def())
381 return ic;
382 }
383 }
384
385 /* No item with these coordinates (or matching item) found. */
386 return nullptr;
387 }
388
389 /** Names of the filter types as used in console function. e.g. in .ufo files.
390 * @sa inv_shared.h:itemFilterTypes_t */
391 static char const* const filterTypeNames[] = {
392 "primary", /**< FILTER_S_PRIMARY */
393 "secondary", /**< FILTER_S_SECONDARY */
394 "heavy", /**< FILTER_S_HEAVY */
395 "misc", /**< FILTER_S_MISC */
396 "armour", /**< FILTER_S_ARMOUR */
397 "implants", /**< FILTER_S_IMPLANT */
398 "", /**< MAX_SOLDIER_FILTERTYPES */
399 "craftitem", /**< FILTER_CRAFTITEM */
400 "ugvitem", /**< FILTER_UGVITEM */
401 "aircraft", /**< FILTER_AIRCRAFT */
402 "dummy", /**< FILTER_DUMMY */
403 "disassembly" /**< FILTER_DISASSEMBLY */
404 };
405 CASSERT(lengthof(filterTypeNames) == MAX_FILTERTYPES);
406
407 /**
408 * @brief Searches for a filter type name (as used in console functions) and returns the matching itemFilterTypes_t enum.
409 * @param[in] filterTypeID Filter type name so search for. @sa filterTypeNames.
410 */
INV_GetFilterTypeID(const char * filterTypeID)411 itemFilterTypes_t INV_GetFilterTypeID (const char* filterTypeID)
412 {
413 int i;
414
415 if (!filterTypeID)
416 return MAX_FILTERTYPES;
417
418 /* default filter type is primary */
419 if (filterTypeID[0] == '\0')
420 return FILTER_S_PRIMARY;
421
422 for (i = 0; i < MAX_FILTERTYPES; i++) {
423 const char* fileTypeName = filterTypeNames[i];
424 if (fileTypeName && Q_streq(fileTypeName, filterTypeID))
425 return (itemFilterTypes_t)i;
426 }
427
428 /* No matching filter type found, returning max value. */
429 return MAX_FILTERTYPES;
430 }
431
432 /**
433 * @param[in] id The filter type index
434 */
INV_GetFilterType(itemFilterTypes_t id)435 const char* INV_GetFilterType (itemFilterTypes_t id)
436 {
437 assert(id < MAX_FILTERTYPES);
438 return filterTypeNames[id];
439 }
440
INV_InitStartup(void)441 void INV_InitStartup (void)
442 {
443 #ifdef DEBUG
444 Cmd_AddCommand("debug_listinventory", INV_InventoryList_f, "Print the current inventory to the game console");
445 #endif
446 INV_InitCallbacks();
447
448 INV_EquipmentDefSanityCheck();
449 }
450