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