1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2013 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 "cl_inventory_callbacks.h"
26 #include "cl_shared.h"
27 #include "cl_inventory.h"
28 #include "ui/ui_main.h"
29 #include "ui/ui_nodes.h"
30 #include "cgame/cl_game.h"
31 #include "ui/ui_popup.h"
32 
33 static const objDef_t* currentDisplayedObject;
34 static int itemIndex;
35 static int fireModeIndex;
36 
37 /**
38  * @brief Translate a weaponSkill integer to a translated string
39  */
CL_WeaponSkillToName(int weaponSkill)40 static const char* CL_WeaponSkillToName (int weaponSkill)
41 {
42 	switch (weaponSkill) {
43 	case SKILL_CLOSE:
44 		return _("Close quarters");
45 	case SKILL_HEAVY:
46 		return _("Heavy");
47 	case SKILL_ASSAULT:
48 		return _("Assault");
49 	case SKILL_SNIPER:
50 		return _("Sniper");
51 	case SKILL_EXPLOSIVE:
52 		return _("Explosives");
53 	default:
54 		return _("Unknown weapon skill");
55 	}
56 }
57 
58 /**
59  * @brief Prints the description for items (weapons, armour, ...)
60  * @param[in] od The object definition of the item
61  * @note Not only called from UFOpaedia but also from other places to display
62  * weapon and ammo statistics
63  * @todo Do we need to add checks for @c od->isDummy here somewhere?
64  */
INV_ItemDescription(const objDef_t * od)65 void INV_ItemDescription (const objDef_t* od)
66 {
67 	static char itemText[UI_MAX_SMALLTEXTLEN];
68 	int i;
69 	int count;
70 
71 	currentDisplayedObject = od;
72 
73 	if (!od) {	/* If nothing selected return */
74 		Cvar_Set("mn_itemname", "");
75 		Cvar_Set("mn_item", "");
76 		UI_ResetData(TEXT_ITEMDESCRIPTION);
77 		itemIndex = fireModeIndex = 0;
78 		UI_ExecuteConfunc("itemdesc_view 0 0;");
79 		return;
80 	}
81 
82 	/* select item */
83 	Cvar_Set("mn_itemname", "%s", _(od->name));
84 	Cvar_Set("mn_item", "%s", od->id);
85 
86 	count = 0;
87 	if (GAME_ItemIsUseable(od)) {
88 		if (od->isAmmo()) {
89 			/* We display the pre/next buttons for changing weapon only if there are at least 2 researched weapons
90 			 * we are counting the number of weapons that are usable with this ammo */
91 			for (i = 0; i < od->numWeapons; i++)
92 				if (GAME_ItemIsUseable(od->weapons[i]))
93 					count++;
94 			if (itemIndex >= od->numWeapons || itemIndex < 0)
95 				itemIndex = 0;
96 			if (count > 0) {
97 				while (!GAME_ItemIsUseable(od->weapons[itemIndex])) {
98 					itemIndex++;
99 					if (itemIndex >= od->numWeapons)
100 						itemIndex = 0;
101 				}
102 				Cvar_ForceSet("mn_linkname", _(od->weapons[itemIndex]->name));
103 			}
104 		} else if (od->weapon) {
105 			/* We display the pre/next buttons for changing ammo only if there are at least 2 researched ammo
106 			 * we are counting the number of ammo that is usable with this weapon */
107 			for (i = 0; i < od->numAmmos; i++)
108 				if (GAME_ItemIsUseable(od->ammos[i]))
109 					count++;
110 
111 			if (itemIndex >= od->numAmmos || itemIndex < 0)
112 				itemIndex = 0;
113 
114 			/* Only display ammos if at least one has been researched */
115 			if (count > 0) {
116 				/* We have a weapon that uses ammos */
117 				while (!GAME_ItemIsUseable(od->ammos[itemIndex])) {
118 					itemIndex++;
119 					if (itemIndex >= od->numAmmos)
120 						itemIndex = 0;
121 				}
122 				Cvar_ForceSet("mn_linkname", _(od->ammos[itemIndex]->name));
123 			}
124 		}
125 	}
126 
127 	/* set description text if item has been researched or one of its ammo/weapon has been researched */
128 	if (count > 0 || GAME_ItemIsUseable(od)) {
129 		int numFiredefs = 0;
130 
131 		*itemText = '\0';
132 		if (od->isArmour()) {
133 			Com_sprintf(itemText, sizeof(itemText), _("Size:\t%i\n"), od->size);
134 			Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight);
135 			Q_strcat(itemText, sizeof(itemText), "\n");
136 			Q_strcat(itemText, sizeof(itemText), _("^BDamage type:\tProtection:\n"));
137 			for (i = 0; i < csi.numDTs; i++) {
138 				const damageType_t* dt = &csi.dts[i];
139 				if (!dt->showInMenu)
140 					continue;
141 				Q_strcat(itemText, sizeof(itemText), _("%s\t%i\n"), _(dt->id), od->ratings[i]);
142 			}
143 		} else if ((od->weapon && od->numAmmos) || od->isAmmo()) {
144 			const objDef_t* odAmmo;
145 
146 			if (count > 0) {
147 				int weaponIndex;
148 				if (od->weapon) {
149 					Com_sprintf(itemText, sizeof(itemText), _("%s weapon\n"), (od->fireTwoHanded ? _("Two-handed") : _("One-handed")));
150 					if (od->ammo > 0)
151 						Q_strcat(itemText, sizeof(itemText), _("Max ammo:\t%i\n"), od->ammo);
152 					odAmmo = (od->numAmmos) ? od->ammos[itemIndex] : od;
153 					assert(odAmmo);
154 					for (weaponIndex = 0; (weaponIndex < odAmmo->numWeapons) && (odAmmo->weapons[weaponIndex] != od); weaponIndex++) {}
155 				} else {
156 					odAmmo = od;
157 					weaponIndex = itemIndex;
158 				}
159 
160 				Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight);
161 				/** @todo is there ammo with no firedefs? */
162 				if (GAME_ItemIsUseable(odAmmo) && odAmmo->numFiredefs[weaponIndex] > 0) {
163 					const fireDef_t* fd;
164 					numFiredefs = odAmmo->numFiredefs[weaponIndex];
165 
166 					/* This contains everything common for weapons and ammos */
167 					/* We check if the wanted firemode to display exists. */
168 					if (fireModeIndex > numFiredefs - 1)
169 						fireModeIndex = 0;
170 					if (fireModeIndex < 0)
171 						fireModeIndex = numFiredefs - 1;
172 
173 					fd = &odAmmo->fd[weaponIndex][fireModeIndex];
174 
175 					/* We always display the name of the firemode for an ammo */
176 					Cvar_Set("mn_firemodename", "%s", _(fd->name));
177 
178 					/* We display the characteristics of this firemode */
179 					Q_strcat(itemText, sizeof(itemText), _("Skill:\t%s\n"), CL_WeaponSkillToName(fd->weaponSkill));
180 					Q_strcat(itemText, sizeof(itemText), _("Damage:\t%i\n"), (int) (fd->damage[0] + fd->spldmg[0]) * fd->shots);
181 					Q_strcat(itemText, sizeof(itemText), _("Time units:\t%i\n"), fd->time);
182 					Q_strcat(itemText, sizeof(itemText), _("Range:\t%g\n"), fd->range / UNIT_SIZE);
183 					Q_strcat(itemText, sizeof(itemText), _("Spreads:\t%g\n"), (fd->spread[0] + fd->spread[1]) / 2);
184 				}
185 			} else {
186 				Com_sprintf(itemText, sizeof(itemText), _("%s. No detailed info available.\n"), od->isAmmo() ? _("Ammunition") : _("Weapon"));
187 				Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight);
188 			}
189 		} else if (od->weapon) {
190 			Com_sprintf(itemText, sizeof(itemText), _("%s ammo-less weapon\n"), (od->fireTwoHanded ? _("Two-handed") : _("One-handed")));
191 			Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight);
192 		} else {
193 			/* just an item - only primary definition */
194 			Com_sprintf(itemText, sizeof(itemText), _("%s auxiliary equipment\n"), (od->fireTwoHanded ? _("Two-handed") : _("One-handed")));
195 			Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight);
196 			if (od->numWeapons > 0 && od->numFiredefs[0] > 0) {
197 				const fireDef_t* fd = &od->fd[0][0];
198 				Q_strcat(itemText, sizeof(itemText), _("Action:\t%s\n"), _(fd->name));
199 				Q_strcat(itemText, sizeof(itemText), _("Time units:\t%i\n"), fd->time);
200 				Q_strcat(itemText, sizeof(itemText), _("Range:\t%g\n"), fd->range / UNIT_SIZE);
201 			}
202 		}
203 
204 		UI_RegisterText(TEXT_ITEMDESCRIPTION, itemText);
205 		UI_ExecuteConfunc("itemdesc_view %i %i;", count, numFiredefs);
206 	} else {
207 		Com_sprintf(itemText, sizeof(itemText), _("Unknown - not useable"));
208 		UI_RegisterText(TEXT_ITEMDESCRIPTION, itemText);
209 		UI_ExecuteConfunc("itemdesc_view 0 0;");
210 	}
211 }
212 
213 /**
214  * @brief Increases the number of the firemode to display
215  * @sa UP_ItemDescription
216  */
INV_IncreaseFiremode_f(void)217 static void INV_IncreaseFiremode_f (void)
218 {
219 	if (!currentDisplayedObject)
220 		return;
221 
222 	fireModeIndex++;
223 
224 	INV_ItemDescription(currentDisplayedObject);
225 }
226 
227 /**
228  * @brief Decreases the number of the firemode to display
229  * @sa UP_ItemDescription
230  */
INV_DecreaseFiremode_f(void)231 static void INV_DecreaseFiremode_f (void)
232 {
233 	if (!currentDisplayedObject)
234 		return;
235 
236 	fireModeIndex--;
237 
238 	INV_ItemDescription(currentDisplayedObject);
239 }
240 
241 /**
242  * @brief Increases the number of the weapon to display (for ammo) or the ammo to display (for weapon)
243  * @sa UP_ItemDescription
244  */
INV_IncreaseItem_f(void)245 static void INV_IncreaseItem_f (void)
246 {
247 	const objDef_t* od = currentDisplayedObject;
248 
249 	if (!od)
250 		return;
251 
252 	if (od->numWeapons) {
253 		const int current = itemIndex;
254 		do {
255 			itemIndex++;
256 			if (itemIndex > od->numWeapons - 1) {
257 				itemIndex = 0;
258 			}
259 		} while (itemIndex != current && !GAME_ItemIsUseable(od->weapons[itemIndex]));
260 	} else if (od->numAmmos) {
261 		const int current = itemIndex;
262 		do {
263 			itemIndex++;
264 			if (itemIndex > od->numAmmos - 1) {
265 				itemIndex = 0;
266 			}
267 		} while (itemIndex != current && !GAME_ItemIsUseable(od->ammos[itemIndex]));
268 	}
269 	INV_ItemDescription(od);
270 }
271 
272 /**
273  * @brief Decreases the number of the firemode to display (for ammo) or the ammo to display (for weapon)
274  * @sa UP_ItemDescription
275  */
INV_DecreaseItem_f(void)276 static void INV_DecreaseItem_f (void)
277 {
278 	const objDef_t* od = currentDisplayedObject;
279 
280 	if (!od)
281 		return;
282 
283 	if (od->numWeapons) {
284 		const int current = itemIndex;
285 		do {
286 			itemIndex--;
287 			if (itemIndex < 0) {
288 				itemIndex = od->numWeapons - 1;
289 			}
290 		} while (itemIndex != current && !GAME_ItemIsUseable(od->weapons[itemIndex]));
291 	} else if (od->numAmmos) {
292 		const int current = itemIndex;
293 		do {
294 			itemIndex--;
295 			if (itemIndex < 0) {
296 				itemIndex = od->numAmmos - 1;
297 			}
298 		} while (itemIndex != current && !GAME_ItemIsUseable(od->ammos[itemIndex]));
299 	}
300 	INV_ItemDescription(od);
301 }
302 
303 /**
304  * @brief Update the GUI with the selected item
305  */
INV_UpdateObject_f(void)306 static void INV_UpdateObject_f (void)
307 {
308 	/* check syntax */
309 	if (Cmd_Argc() < 3) {
310 		Com_Printf("Usage: %s <objectid> <confunc> [mustwechangetab]\n", Cmd_Argv(0));
311 		return;
312 	}
313 
314 	bool changeTab = true;
315 	if (Cmd_Argc() == 4)
316 		changeTab = atoi(Cmd_Argv(3)) >= 1;
317 
318 	const int num = atoi(Cmd_Argv(1));
319 	if (num < 0 || num >= csi.numODs) {
320 		Com_Printf("Id %i out of range 0..%i\n", num, csi.numODs);
321 		return;
322 	}
323 	const objDef_t* obj = INVSH_GetItemByIDX(num);
324 
325 	/* update tab */
326 	if (changeTab) {
327 		const cvar_t* var = Cvar_FindVar("mn_equiptype");
328 		const int filter = INV_GetFilterFromItem(obj);
329 		if (var && var->integer != filter) {
330 			Cvar_SetValue("mn_equiptype", filter);
331 			UI_ExecuteConfunc("%s", Cmd_Argv(2));
332 		}
333 	}
334 
335 	/* update item description */
336 	INV_ItemDescription(obj);
337 }
338 
339 /**
340  * @brief Update the equipment weight for the selected actor.
341  */
INV_UpdateActorLoad_f(void)342 static void INV_UpdateActorLoad_f (void)
343 {
344 	if (Cmd_Argc() < 2) {
345 		Com_Printf("Usage: %s <callback>\n", Cmd_Argv(0));
346 		return;
347 	}
348 
349 	const character_t* chr = GAME_GetSelectedChr();
350 	if (chr == nullptr)
351 		return;
352 
353 	const float invWeight = chr->inv.getWeight();
354 	const int maxWeight = GAME_GetChrMaxLoad(chr);
355 	const float penalty = GET_ENCUMBRANCE_PENALTY(invWeight, chr->score.skills[ABILITY_POWER]);
356 	const int normalTU = GET_TU(chr->score.skills[ABILITY_SPEED], 1.0f - WEIGHT_NORMAL_PENALTY);
357 	const int tus = GET_TU(chr->score.skills[ABILITY_SPEED], penalty);
358 	const int tuPenalty = tus - normalTU;
359 	int count = 0;
360 
361 	const Container* cont = nullptr;
362 	while ((cont = chr->inv.getNextCont(cont))) {
363 		for (Item* invList = cont->_invList, *next; invList; invList = next) {
364 			next = invList->getNext();
365 			const fireDef_t* fireDef = invList->getFiredefs();
366 			if (fireDef == nullptr)
367 				continue;
368 			for (int i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) {
369 				if (fireDef[i].time <= 0)
370 					continue;
371 				if (fireDef[i].time <= tus)
372 					continue;
373 				if (count <= 0)
374 					Com_sprintf(popupText, sizeof(popupText), _("This soldier no longer has enough TUs to use the following items:\n\n"));
375 				Q_strcat(popupText, sizeof(popupText), "%s: %s (%i)\n", _(invList->def()->name), _(fireDef[i].name), fireDef[i].time);
376 				++count;
377 			}
378 		}
379 	}
380 
381 	if ((Cmd_Argc() == 2 || atoi(Cmd_Argv(2)) == 0) && count > 0)
382 		UI_Popup(_("Warning"), popupText);
383 
384 	char label[MAX_VAR];
385 	char tooltip[MAX_VAR];
386 	Com_sprintf(label, sizeof(label), "%g/%i %s %s", invWeight, maxWeight, _("Kg"),
387 			(count > 0 ? _("Warning: Not enough TUs...") : ""));
388 	Com_sprintf(tooltip, sizeof(tooltip), "%s %i (%+i)", _("TU:"), tus, tuPenalty);
389 	UI_ExecuteConfunc("%s \"%s\" \"%s\" %f %i", Cmd_Argv(1), label, tooltip, WEIGHT_NORMAL_PENALTY - (1.0f - penalty), count);
390 }
391 
INV_InitCallbacks(void)392 void INV_InitCallbacks (void)
393 {
394 	Cmd_AddCommand("mn_increasefiremode", INV_IncreaseFiremode_f, "Increases the number of the firemode to display");
395 	Cmd_AddCommand("mn_decreasefiremode", INV_DecreaseFiremode_f, "Decreases the number of the firemode to display");
396 	Cmd_AddCommand("mn_increaseitem", INV_IncreaseItem_f, "Increases the number of the weapon or the ammo to display");
397 	Cmd_AddCommand("mn_decreaseitem", INV_DecreaseItem_f, "Decreases the number of the weapon or the ammo to display");
398 	Cmd_AddCommand("object_update", INV_UpdateObject_f, "Update the GUI with the selected item");
399 	Cmd_AddCommand("mn_updateactorload", INV_UpdateActorLoad_f, "Update the GUI with the selected actor inventory load");
400 }
401