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