File: | client/cgame/campaign/cp_fightequip_callbacks.cpp |
Location: | line 766, column 8 |
Description: | Access to field 'nextItem' results in a dereference of a null pointer (loaded from variable 'slot') |
1 | /** | ||
2 | * @file | ||
3 | * @brief Header file for menu callback functions used for base and aircraft equip menu | ||
4 | */ | ||
5 | |||
6 | /* | ||
7 | All original material Copyright (C) 2002-2011 UFO: Alien Invasion. | ||
8 | |||
9 | This program is free software; you can redistribute it and/or | ||
10 | modify it under the terms of the GNU General Public License | ||
11 | as published by the Free Software Foundation; either version 2 | ||
12 | of the License, or (at your option) any later version. | ||
13 | |||
14 | This program is distributed in the hope that it will be useful, | ||
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
17 | |||
18 | See the GNU General Public License for more details. | ||
19 | |||
20 | You should have received a copy of the GNU General Public License | ||
21 | along with this program; if not, write to the Free Software | ||
22 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | ||
23 | |||
24 | */ | ||
25 | #include "../../cl_shared.h" | ||
26 | #include "../../ui/ui_dataids.h" | ||
27 | #include "cp_campaign.h" | ||
28 | #include "cp_fightequip_callbacks.h" | ||
29 | #include "cp_mapfightequip.h" | ||
30 | #include "cp_ufo.h" | ||
31 | |||
32 | static aircraftItemType_t airequipID = MAX_ACITEMS; /**< value of aircraftItemType_t that defines what item we are installing. */ | ||
33 | |||
34 | static int airequipSelectedZone = ZONE_NONE; /**< Selected zone in equip menu */ | ||
35 | static int airequipSelectedSlot = ZONE_NONE; /**< Selected slot in equip menu */ | ||
36 | static technology_t *aimSelectedTechnology = NULL__null; /**< Selected technology in equip menu */ | ||
37 | |||
38 | /** | ||
39 | * @brief Check airequipID value and set the correct values for aircraft items | ||
40 | */ | ||
41 | static void AIM_CheckAirequipID (void) | ||
42 | { | ||
43 | switch (airequipID) { | ||
44 | case AC_ITEM_AMMO: | ||
45 | case AC_ITEM_WEAPON: | ||
46 | case AC_ITEM_SHIELD: | ||
47 | case AC_ITEM_ELECTRONICS: | ||
48 | return; | ||
49 | default: | ||
50 | airequipID = AC_ITEM_WEAPON; | ||
51 | break; | ||
52 | } | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * @brief Check that airequipSelectedSlot is the indice of an existing slot in the aircraft | ||
57 | * @note airequipSelectedSlot concerns only weapons and electronics | ||
58 | * @sa aircraft Pointer to the aircraft | ||
59 | */ | ||
60 | static void AIM_CheckAirequipSelectedSlot (const aircraft_t *aircraft) | ||
61 | { | ||
62 | switch (airequipID) { | ||
63 | case AC_ITEM_AMMO: | ||
64 | case AC_ITEM_WEAPON: | ||
65 | if (airequipSelectedSlot >= aircraft->maxWeapons) { | ||
66 | airequipSelectedSlot = 0; | ||
67 | return; | ||
68 | } | ||
69 | break; | ||
70 | case AC_ITEM_ELECTRONICS: | ||
71 | if (airequipSelectedSlot >= aircraft->maxElectronics) { | ||
72 | airequipSelectedSlot = 0; | ||
73 | return; | ||
74 | } | ||
75 | break; | ||
76 | default: | ||
77 | break; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * @brief Returns a pointer to the selected slot. | ||
83 | * @param[in] aircraft Pointer to the aircraft | ||
84 | * @param[in] type Type of slot we want to select | ||
85 | * @note also used by BDEF_ functions | ||
86 | * @return Pointer to the slot corresponding to airequipID | ||
87 | */ | ||
88 | aircraftSlot_t *AII_SelectAircraftSlot (aircraft_t *aircraft, aircraftItemType_t type) | ||
89 | { | ||
90 | aircraftSlot_t *slot; | ||
91 | |||
92 | AIM_CheckAirequipSelectedSlot(aircraft); | ||
93 | switch (type) { | ||
94 | case AC_ITEM_SHIELD: | ||
95 | slot = &aircraft->shield; | ||
96 | break; | ||
97 | case AC_ITEM_ELECTRONICS: | ||
98 | slot = aircraft->electronics + airequipSelectedSlot; | ||
99 | break; | ||
100 | case AC_ITEM_AMMO: | ||
101 | case AC_ITEM_WEAPON: | ||
102 | slot = aircraft->weapons + airequipSelectedSlot; | ||
103 | break; | ||
104 | default: | ||
105 | Com_Printf("AII_SelectAircraftSlot: Unknown airequipID: %i\n", type); | ||
106 | return NULL__null; | ||
107 | } | ||
108 | |||
109 | return slot; | ||
110 | } | ||
111 | |||
112 | /** | ||
113 | * @brief Check that airequipSelectedZone is available | ||
114 | * @sa slot Pointer to the slot | ||
115 | */ | ||
116 | static void AIM_CheckAirequipSelectedZone (aircraftSlot_t *slot) | ||
117 | { | ||
118 | assert(slot)(__builtin_expect(!(slot), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 118, "slot") : (void)0); | ||
119 | |||
120 | if (airequipSelectedZone == ZONE_AMMO && airequipID < AC_ITEM_AMMO && airequipID > AC_ITEM_WEAPON) { | ||
121 | /* you can select ammo only for weapons and ammo */ | ||
122 | airequipSelectedZone = ZONE_MAIN; | ||
123 | } | ||
124 | |||
125 | /* You can choose an ammo only if a weapon has already been selected */ | ||
126 | if (airequipID >= AC_ITEM_AMMO && !slot->item) { | ||
127 | airequipSelectedZone = ZONE_MAIN; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * @brief Returns the userfriendly name for craftitem types (shown in aircraft equip menu) | ||
133 | */ | ||
134 | static inline const char *AIM_AircraftItemtypeName (const int equiptype) | ||
135 | { | ||
136 | switch (equiptype) { | ||
137 | case AC_ITEM_WEAPON: | ||
138 | return _("Weapons")gettext("Weapons"); | ||
139 | case AC_ITEM_SHIELD: | ||
140 | return _("Armour")gettext("Armour"); | ||
141 | case AC_ITEM_ELECTRONICS: | ||
142 | return _("Items")gettext("Items"); | ||
143 | case AC_ITEM_AMMO: | ||
144 | /* ammo - only available from weapons */ | ||
145 | return _("Ammo")gettext("Ammo"); | ||
146 | default: | ||
147 | return _("Unknown")gettext("Unknown"); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * @return @c true if the technology is available and matches the filter | ||
153 | */ | ||
154 | static bool AIM_CrafttypeFilter (const base_t *base, aircraftItemType_t filterType, const technology_t *tech) | ||
155 | { | ||
156 | const objDef_t *item; | ||
157 | if (!base) | ||
158 | return false; | ||
159 | |||
160 | if (!RS_IsResearched_ptr(tech)) | ||
161 | return false; | ||
162 | |||
163 | item = INVSH_GetItemByID(tech->provides); | ||
164 | if (!item) | ||
165 | return false; | ||
166 | if (item->isVirtual) | ||
167 | return false; | ||
168 | if (!B_BaseHasItem(base, item)) | ||
169 | return false; | ||
170 | |||
171 | /* filter by type: special case for ammo because more than 1 type is an ammo type */ | ||
172 | if (filterType != AC_ITEM_AMMO) { | ||
173 | if (item->craftitem.type != filterType) | ||
174 | return false; | ||
175 | } else { | ||
176 | if (item->craftitem.type < AC_ITEM_AMMO) | ||
177 | return false; | ||
178 | } | ||
179 | |||
180 | /* you can't install an item that does not have an installation time (alien item) | ||
181 | * except for ammo which does not have installation time */ | ||
182 | if (item->craftitem.installationTime == -1 && filterType >= AC_ITEM_AMMO) | ||
183 | return false; | ||
184 | |||
185 | return true; | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * @brief Update the list of item you can choose | ||
190 | * @param[in] slot Pointer to aircraftSlot where items can be equiped | ||
191 | */ | ||
192 | static void AIM_UpdateAircraftItemList (const aircraftSlot_t *slot) | ||
193 | { | ||
194 | linkedList_t *amountList = NULL__null; | ||
195 | technology_t **techList; | ||
196 | technology_t **currentTech; | ||
197 | const base_t *base = slot->aircraft->homebase; | ||
198 | int count = 0; | ||
199 | uiNode_t *AIM_items = NULL__null; | ||
200 | |||
201 | /* Add all items corresponding to airequipID to list */ | ||
202 | techList = AII_GetCraftitemTechsByType(airequipID); | ||
203 | |||
204 | /* Count only those which are researched to buffer */ | ||
205 | currentTech = techList; | ||
206 | while (*currentTech) { | ||
207 | if (AIM_CrafttypeFilter(base, airequipID, *currentTech)) | ||
208 | count++; | ||
209 | currentTech++; | ||
210 | } | ||
211 | |||
212 | /* List only those which are researched to buffer */ | ||
213 | currentTech = techList; | ||
214 | while (*currentTech) { | ||
215 | if (AIM_CrafttypeFilter(base, airequipID, *currentTech)) { | ||
216 | uiNode_t *option; | ||
217 | const objDef_t *item = INVSH_GetItemByID((*currentTech)->provides); | ||
218 | const int amount = B_ItemInBase(item, base); | ||
219 | |||
220 | LIST_AddString(&amountList, va("%d", amount)); | ||
221 | option = cgi->UI_AddOption(&AIM_items, (*currentTech)->name, _((*currentTech)->name)gettext((*currentTech)->name), va("%d", (*currentTech)->idx)); | ||
222 | if (!AIM_SelectableCraftItem(slot, *currentTech)) | ||
223 | option->disabled = true; | ||
224 | } | ||
225 | currentTech++; | ||
226 | } | ||
227 | |||
228 | cgi->UI_RegisterOption(TEXT_LIST, AIM_items); | ||
229 | cgi->UI_RegisterLinkedListText(TEXT_LIST2, amountList); | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * @brief Draw only slots existing for this aircraft, and emphases selected one | ||
234 | * @return[out] aircraft Pointer to the aircraft | ||
235 | */ | ||
236 | static void AIM_DrawAircraftSlots (const aircraft_t *aircraft) | ||
237 | { | ||
238 | int i; | ||
239 | |||
240 | /* initialise model cvars */ | ||
241 | for (i = 0; i < AIR_POSITIONS_MAX; i++) | ||
242 | Cvar_Set(va("mn_aircraft_item_model_slot%i", i), ""); | ||
243 | |||
244 | for (i = 0; i < AIR_POSITIONS_MAX; i++) { | ||
245 | const aircraftSlot_t *slot; | ||
246 | int max, j; | ||
247 | |||
248 | /* Default value */ | ||
249 | cgi->UI_ExecuteConfunc("airequip_display_slot %i 0", i); | ||
250 | |||
251 | /* Draw available slots */ | ||
252 | switch (airequipID) { | ||
253 | case AC_ITEM_AMMO: | ||
254 | case AC_ITEM_WEAPON: | ||
255 | max = aircraft->maxWeapons; | ||
256 | slot = aircraft->weapons; | ||
257 | break; | ||
258 | case AC_ITEM_ELECTRONICS: | ||
259 | max = aircraft->maxElectronics; | ||
260 | slot = aircraft->electronics; | ||
261 | break; | ||
262 | /* do nothing for shield: there is only one slot */ | ||
263 | default: | ||
264 | continue; | ||
265 | } | ||
266 | for (j = 0; j < max; j++, slot++) { | ||
267 | /* check if one of the aircraft slots is at this position */ | ||
268 | if (slot->pos == i) { | ||
269 | /* draw in white if this is the selected slot */ | ||
270 | if (j == airequipSelectedSlot) { | ||
271 | cgi->UI_ExecuteConfunc("airequip_display_slot %i 2", i); | ||
272 | } else { | ||
273 | cgi->UI_ExecuteConfunc("airequip_display_slot %i 1", i); | ||
274 | } | ||
275 | if (slot->item) { | ||
276 | Cvar_Set(va("mn_aircraft_item_model_slot%i", i), RS_GetTechForItem(slot->item)->mdl); | ||
277 | } else | ||
278 | Cvar_Set(va("mn_aircraft_item_model_slot%i", i), ""); | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | |||
284 | /** | ||
285 | * @brief Write in red the text in zone ammo (zone 2) | ||
286 | * @sa AIM_NoEmphazeAmmoSlotText | ||
287 | * @note This is intended to show the player that there is no ammo in his aircraft | ||
288 | */ | ||
289 | static inline void AIM_EmphazeAmmoSlotText (void) | ||
290 | { | ||
291 | cgi->UI_ExecuteConfunc("airequip_zone2_color \"1 0 0 1\""); | ||
292 | } | ||
293 | |||
294 | /** | ||
295 | * @brief Write in white the text in zone ammo (zone 2) | ||
296 | * @sa AIM_EmphazeAmmoSlotText | ||
297 | * @note This is intended to revert effects of AIM_EmphazeAmmoSlotText | ||
298 | */ | ||
299 | static inline void AIM_NoEmphazeAmmoSlotText (void) | ||
300 | { | ||
301 | cgi->UI_ExecuteConfunc("airequip_zone2_color \"1 1 1 1\""); | ||
302 | } | ||
303 | |||
304 | static void AIM_AircraftEquipMenuUpdate (void) | ||
305 | { | ||
306 | static char smallbuffer1[256]; | ||
307 | static char smallbuffer2[128]; | ||
308 | const char *typeName; | ||
309 | aircraft_t *aircraft; | ||
310 | aircraftSlot_t *slot; | ||
311 | base_t *base = B_GetCurrentSelectedBase(); | ||
312 | |||
313 | if (!base) | ||
314 | return; | ||
315 | |||
316 | /* don't let old links appear on this menu */ | ||
317 | cgi->UI_ResetData(TEXT_AIREQUIP_1); | ||
318 | cgi->UI_ResetData(TEXT_AIREQUIP_2); | ||
319 | cgi->UI_ResetData(TEXT_ITEMDESCRIPTION); | ||
320 | cgi->UI_ResetData(TEXT_LIST); | ||
321 | |||
322 | aircraft = base->aircraftCurrent; | ||
323 | |||
324 | assert(aircraft)(__builtin_expect(!(aircraft), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 324, "aircraft") : (void)0); | ||
325 | |||
326 | /* Check that airequipSelectedSlot corresponds to an existing slot for this aircraft */ | ||
327 | AIM_CheckAirequipSelectedSlot(aircraft); | ||
328 | |||
329 | /* Select slot */ | ||
330 | slot = AII_SelectAircraftSlot(aircraft, airequipID); | ||
331 | |||
332 | /* Check that the selected zone is OK */ | ||
333 | AIM_CheckAirequipSelectedZone(slot); | ||
334 | |||
335 | /* Fill the list of item you can equip your aircraft with */ | ||
336 | AIM_UpdateAircraftItemList(slot); | ||
337 | |||
338 | Cvar_Set("mn_equip_itemtype_name", AIM_AircraftItemtypeName(airequipID)); | ||
339 | switch (airequipID) { | ||
340 | case AC_ITEM_ELECTRONICS: | ||
341 | typeName = "item"; | ||
342 | break; | ||
343 | case AC_ITEM_SHIELD: | ||
344 | typeName = "armour"; | ||
345 | break; | ||
346 | case AC_ITEM_AMMO: | ||
347 | typeName = "ammo"; | ||
348 | break; | ||
349 | case AC_ITEM_WEAPON: | ||
350 | typeName = "weapon"; | ||
351 | break; | ||
352 | default: | ||
353 | typeName = "unknown"; | ||
354 | break; | ||
355 | } | ||
356 | Cvar_Set("mn_equip_itemtype", typeName); | ||
357 | |||
358 | /* First slot: item currently assigned */ | ||
359 | if (!slot->item) { | ||
360 | Com_sprintf(smallbuffer1, sizeof(smallbuffer1), "%s", _("No item assigned.\n")gettext("No item assigned.\n")); | ||
361 | Q_strcat(smallbuffer1, va(_("This slot is for %s or smaller items.")gettext("This slot is for %s or smaller items."), | ||
362 | AII_WeightToName(slot->size)), sizeof(smallbuffer1)); | ||
363 | } else { | ||
364 | technology_t *itemTech = RS_GetTechForItem(slot->item); | ||
365 | technology_t *nextItemTech = slot->nextItem ? RS_GetTechForItem(slot->nextItem) : NULL__null; | ||
366 | /* Print next item if we are removing item currently installed and a new item has been added. */ | ||
367 | Com_sprintf(smallbuffer1, sizeof(smallbuffer1), "%s\n", slot->nextItem ? _(nextItemTech->name)gettext(nextItemTech->name) : _(itemTech->name)gettext(itemTech->name)); | ||
368 | if (!slot->installationTime) { | ||
369 | Q_strcat(smallbuffer1, _("This item is functional.\n")gettext("This item is functional.\n"), sizeof(smallbuffer1)); | ||
370 | } else if (slot->installationTime > 0) { | ||
371 | Q_strcat(smallbuffer1, va(_("This item will be installed in %i hours.\n")gettext("This item will be installed in %i hours.\n"), | ||
372 | slot->installationTime), sizeof(smallbuffer1)); | ||
373 | } else if (slot->nextItem) { | ||
374 | Q_strcat(smallbuffer1, va(_("%s will be removed in %i hours.\n")gettext("%s will be removed in %i hours.\n"), _(itemTech->name)gettext(itemTech->name), | ||
375 | - slot->installationTime), sizeof(smallbuffer1)); | ||
376 | Q_strcat(smallbuffer1, va(_("%s will be installed in %i hours.\n")gettext("%s will be installed in %i hours.\n"), _(nextItemTech->name)gettext(nextItemTech->name), | ||
377 | slot->nextItem->craftitem.installationTime - slot->installationTime), sizeof(smallbuffer1)); | ||
378 | } else { | ||
379 | Q_strcat(smallbuffer1, va(_("This item will be removed in %i hours.\n")gettext("This item will be removed in %i hours.\n"), | ||
380 | -slot->installationTime), sizeof(smallbuffer1)); | ||
381 | } | ||
382 | } | ||
383 | cgi->UI_RegisterText(TEXT_AIREQUIP_1, smallbuffer1); | ||
384 | |||
385 | /* Second slot: ammo slot (only used for weapons) */ | ||
386 | if ((airequipID == AC_ITEM_WEAPON || airequipID == AC_ITEM_AMMO) && slot->item) { | ||
387 | if (!slot->ammo) { | ||
388 | AIM_EmphazeAmmoSlotText(); | ||
389 | Com_sprintf(smallbuffer2, sizeof(smallbuffer2), "%s", _("No ammo assigned to this weapon.")gettext("No ammo assigned to this weapon.")); | ||
390 | } else { | ||
391 | const objDef_t *ammo = slot->nextAmmo ? slot->nextAmmo : slot->ammo; | ||
392 | const technology_t *tech = RS_GetTechForItem(ammo); | ||
393 | AIM_NoEmphazeAmmoSlotText(); | ||
394 | if (!ammo->isVirtual) | ||
395 | Q_strncpyz(smallbuffer2, _(tech->name), sizeof(smallbuffer2))Q_strncpyzDebug( smallbuffer2, gettext(tech->name), sizeof (smallbuffer2), "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 395 ); | ||
396 | else | ||
397 | Q_strncpyz(smallbuffer2, _("No ammo needed"), sizeof(smallbuffer2))Q_strncpyzDebug( smallbuffer2, gettext("No ammo needed"), sizeof (smallbuffer2), "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 397 ); | ||
398 | } | ||
399 | } else | ||
400 | *smallbuffer2 = '\0'; | ||
401 | |||
402 | cgi->UI_RegisterText(TEXT_AIREQUIP_2, smallbuffer2); | ||
403 | |||
404 | /* Draw existing slots for this aircraft */ | ||
405 | AIM_DrawAircraftSlots(aircraft); | ||
406 | } | ||
407 | |||
408 | #define AIM_LOADING_OK0 0 | ||
409 | #define AIM_LOADING_NOSLOTSELECTED1 1 | ||
410 | #define AIM_LOADING_NOTECHNOLOGYSELECTED2 2 | ||
411 | #define AIM_LOADING_ALIENTECH3 3 | ||
412 | #define AIM_LOADING_TECHNOLOGYNOTRESEARCHED4 4 | ||
413 | #define AIM_LOADING_TOOHEAVY5 5 | ||
414 | #define AIM_LOADING_UNKNOWNPROBLEM6 6 | ||
415 | #define AIM_LOADING_NOWEAPON7 7 | ||
416 | #define AIM_LOADING_NOTUSABLEWITHWEAPON8 8 | ||
417 | |||
418 | /** | ||
419 | * @todo It is a generic function, we can move it into cp_mapfightequip.c | ||
420 | * @param[in] slot Pointer to an aircraft slot (can be base/installation too) | ||
421 | * @param[in] tech Pointer to the technology to test | ||
422 | * @return The status of the technology versus the slot | ||
423 | */ | ||
424 | static int AIM_CheckTechnologyIntoSlot (const aircraftSlot_t *slot, const technology_t *tech) | ||
425 | { | ||
426 | const objDef_t *item; | ||
427 | |||
428 | if (!tech) | ||
429 | return AIM_LOADING_NOTECHNOLOGYSELECTED2; | ||
430 | |||
431 | if (!slot) | ||
432 | return AIM_LOADING_NOSLOTSELECTED1; | ||
433 | |||
434 | if (!RS_IsResearched_ptr(tech)) | ||
435 | return AIM_LOADING_TECHNOLOGYNOTRESEARCHED4; | ||
436 | |||
437 | item = INVSH_GetItemByID(tech->provides); | ||
438 | if (!item) | ||
439 | return AIM_LOADING_NOTECHNOLOGYSELECTED2; | ||
440 | |||
441 | if (item->craftitem.type >= AC_ITEM_AMMO) { | ||
442 | const objDef_t *weapon = slot->item; | ||
443 | int k; | ||
444 | if (slot->nextItem != NULL__null) | ||
445 | weapon = slot->nextItem; | ||
446 | |||
447 | if (weapon == NULL__null) | ||
448 | return AIM_LOADING_NOWEAPON7; | ||
449 | |||
450 | /* Is the ammo is usable with the slot */ | ||
451 | for (k = 0; k < weapon->numAmmos; k++) { | ||
452 | const objDef_t *usable = weapon->ammos[k]; | ||
453 | if (usable && item->idx == usable->idx) | ||
454 | break; | ||
455 | } | ||
456 | if (k >= weapon->numAmmos) | ||
457 | return AIM_LOADING_NOTUSABLEWITHWEAPON8; | ||
458 | |||
459 | #if 0 | ||
460 | /** @todo This only works for ammo that is useable in exactly one weapon | ||
461 | * check the weap_idx array and not only the first value */ | ||
462 | if (!slot->nextItem && item->weapons[0] != slot->item) | ||
463 | return AIM_LOADING_UNKNOWNPROBLEM6; | ||
464 | |||
465 | /* are we trying to change ammos for nextItem? */ | ||
466 | if (slot->nextItem && item->weapons[0] != slot->nextItem) | ||
467 | return AIM_LOADING_UNKNOWNPROBLEM6; | ||
468 | #endif | ||
469 | } | ||
470 | |||
471 | /* you can install an item only if its weight is small enough for the slot */ | ||
472 | if (AII_GetItemWeightBySize(item) > slot->size) | ||
473 | return AIM_LOADING_TOOHEAVY5; | ||
474 | |||
475 | /* you can't install an item that you don't possess | ||
476 | * virtual ammo don't need to be possessed | ||
477 | * installations always have weapon and ammo */ | ||
478 | if (slot->aircraft) { | ||
479 | if (!B_BaseHasItem(slot->aircraft->homebase, item)) | ||
480 | return AIM_LOADING_UNKNOWNPROBLEM6; | ||
481 | } else if (slot->base) { | ||
482 | if (!B_BaseHasItem(slot->base, item)) | ||
483 | return AIM_LOADING_UNKNOWNPROBLEM6; | ||
484 | } | ||
485 | |||
486 | /* you can't install an item that does not have an installation time (alien item) | ||
487 | * except for ammo which does not have installation time */ | ||
488 | if (item->craftitem.installationTime == -1 && slot->type < AC_ITEM_AMMO) | ||
489 | return AIM_LOADING_ALIENTECH3; | ||
490 | |||
491 | return AIM_LOADING_OK0; | ||
492 | } | ||
493 | |||
494 | /** | ||
495 | * @brief Update the item description according to the tech and the slot selected | ||
496 | */ | ||
497 | static void AIM_UpdateItemDescription (bool fromList, bool fromSlot) | ||
498 | { | ||
499 | int status; | ||
500 | aircraft_t *aircraft; | ||
501 | aircraftSlot_t *slot; | ||
502 | base_t *base = B_GetCurrentSelectedBase(); | ||
503 | assert(base)(__builtin_expect(!(base), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 503, "base") : (void)0); | ||
504 | |||
505 | aircraft = base->aircraftCurrent; | ||
506 | assert(aircraft)(__builtin_expect(!(aircraft), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 506, "aircraft") : (void)0); | ||
507 | slot = AII_SelectAircraftSlot(aircraft, airequipID); | ||
508 | |||
509 | /* update mini ufopedia */ | ||
510 | /** @todo we should clone the text, and not using the ufopedia text */ | ||
511 | if (fromList) | ||
512 | UP_AircraftItemDescription(INVSH_GetItemByIDSilent(aimSelectedTechnology ? aimSelectedTechnology->provides : NULL__null)); | ||
513 | else if (fromSlot) { | ||
514 | if (airequipID == AC_ITEM_AMMO) | ||
515 | UP_AircraftItemDescription(slot->ammo); | ||
516 | else | ||
517 | UP_AircraftItemDescription(slot->item); | ||
518 | } | ||
519 | |||
520 | /* update status */ | ||
521 | status = AIM_CheckTechnologyIntoSlot(slot, aimSelectedTechnology); | ||
522 | switch (status) { | ||
523 | case AIM_LOADING_NOSLOTSELECTED1: | ||
524 | Cvar_Set("mn_aircraft_item_warning", _("No slot selected.")gettext("No slot selected.")); | ||
525 | break; | ||
526 | case AIM_LOADING_NOTECHNOLOGYSELECTED2: | ||
527 | Cvar_Set("mn_aircraft_item_warning", _("No item selected.")gettext("No item selected.")); | ||
528 | break; | ||
529 | case AIM_LOADING_ALIENTECH3: | ||
530 | Cvar_Set("mn_aircraft_item_warning", _("You can't equip an alien technology.")gettext("You can't equip an alien technology.")); | ||
531 | break; | ||
532 | case AIM_LOADING_TECHNOLOGYNOTRESEARCHED4: | ||
533 | Cvar_Set("mn_aircraft_item_warning", _("Technology requested is not yet completed.")gettext("Technology requested is not yet completed.")); | ||
534 | break; | ||
535 | case AIM_LOADING_TOOHEAVY5: | ||
536 | Cvar_Set("mn_aircraft_item_warning", _("This item is too heavy for the selected slot.")gettext("This item is too heavy for the selected slot.")); | ||
537 | break; | ||
538 | case AIM_LOADING_NOWEAPON7: | ||
539 | Cvar_Set("mn_aircraft_item_warning", _("Equip a weapon first.")gettext("Equip a weapon first.")); | ||
540 | break; | ||
541 | case AIM_LOADING_NOTUSABLEWITHWEAPON8: | ||
542 | Cvar_Set("mn_aircraft_item_warning", _("Ammo not usable with current weapon.")gettext("Ammo not usable with current weapon.")); | ||
543 | break; | ||
544 | case AIM_LOADING_UNKNOWNPROBLEM6: | ||
545 | Cvar_Set("mn_aircraft_item_warning", _("Unknown problem.")gettext("Unknown problem.")); | ||
546 | break; | ||
547 | case AIM_LOADING_OK0: | ||
548 | Cvar_Set("mn_aircraft_item_warning", _("Ok")gettext("Ok")); | ||
549 | break; | ||
550 | } | ||
551 | |||
552 | if (*Cvar_GetString("mn_item") == '\0') { | ||
553 | cgi->UI_ExecuteConfunc("airequip_no_item"); | ||
554 | } else { | ||
555 | if (fromSlot) { | ||
556 | cgi->UI_ExecuteConfunc("airequip_installed_item"); | ||
557 | } else { | ||
558 | if (status == AIM_LOADING_OK0) | ||
559 | cgi->UI_ExecuteConfunc("airequip_installable_item"); | ||
560 | else | ||
561 | cgi->UI_ExecuteConfunc("airequip_noinstallable_item"); | ||
562 | } | ||
563 | } | ||
564 | } | ||
565 | |||
566 | /** | ||
567 | * @brief Fills the weapon and shield list of the aircraft equip menu | ||
568 | * @sa AIM_AircraftEquipMenuClick_f | ||
569 | */ | ||
570 | static void AIM_AircraftEquipMenuUpdate_f (void) | ||
571 | { | ||
572 | if (Cmd_Argc() != 2) { | ||
573 | if (airequipID == MAX_ACITEMS) { | ||
574 | Com_Printf("Usage: %s <num>\n", Cmd_Argv(0)); | ||
575 | return; | ||
576 | } | ||
577 | AIM_CheckAirequipID(); | ||
578 | } else { | ||
579 | const aircraftItemType_t type = (aircraftItemType_t)atoi(Cmd_Argv(1)); | ||
580 | switch (type) { | ||
581 | case AC_ITEM_ELECTRONICS: | ||
582 | case AC_ITEM_SHIELD: | ||
583 | airequipID = type; | ||
584 | cgi->UI_ExecuteConfunc("airequip_zone2_off"); | ||
585 | break; | ||
586 | case AC_ITEM_AMMO: | ||
587 | case AC_ITEM_WEAPON: | ||
588 | airequipID = type; | ||
589 | cgi->UI_ExecuteConfunc("airequip_zone2_on"); | ||
590 | break; | ||
591 | default: | ||
592 | airequipID = AC_ITEM_WEAPON; | ||
593 | break; | ||
594 | } | ||
595 | } | ||
596 | |||
597 | AIM_AircraftEquipMenuUpdate(); | ||
598 | } | ||
599 | |||
600 | /** | ||
601 | * @brief Select the current slot you want to assign the item to. | ||
602 | * @note This function is only for aircraft and not far bases. | ||
603 | */ | ||
604 | static void AIM_AircraftEquipSlotSelect_f (void) | ||
605 | { | ||
606 | int i; | ||
607 | itemPos_t pos; | ||
608 | aircraft_t *aircraft; | ||
609 | base_t *base = B_GetCurrentSelectedBase(); | ||
610 | int updateZone = 0; | ||
611 | |||
612 | if (!base) | ||
613 | return; | ||
614 | |||
615 | if (Cmd_Argc() < 2) { | ||
616 | Com_Printf("Usage: %s <arg> <zone1|zone2|item>\n", Cmd_Argv(0)); | ||
617 | return; | ||
618 | } | ||
619 | |||
620 | aircraft = base->aircraftCurrent; | ||
621 | assert(aircraft)(__builtin_expect(!(aircraft), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 621, "aircraft") : (void)0); | ||
622 | |||
623 | i = atoi(Cmd_Argv(1)); | ||
624 | pos = (itemPos_t)i; | ||
625 | |||
626 | if (Cmd_Argc() == 3) { | ||
627 | if (Q_streq(Cmd_Argv(2), "zone1")(strcmp(Cmd_Argv(2), "zone1") == 0)) { | ||
628 | updateZone = 1; | ||
629 | } else if (Q_streq(Cmd_Argv(2), "zone2")(strcmp(Cmd_Argv(2), "zone2") == 0)) { | ||
630 | updateZone = 2; | ||
631 | } | ||
632 | } | ||
633 | |||
634 | airequipSelectedSlot = ZONE_NONE; | ||
635 | |||
636 | /* select the slot corresponding to pos, and set airequipSelectedSlot to this slot */ | ||
637 | switch (airequipID) { | ||
638 | case AC_ITEM_ELECTRONICS: | ||
639 | /* electronics selected */ | ||
640 | for (i = 0; i < aircraft->maxElectronics; i++) { | ||
641 | if (aircraft->electronics[i].pos == pos) { | ||
642 | airequipSelectedSlot = i; | ||
643 | break; | ||
644 | } | ||
645 | } | ||
646 | if (i == aircraft->maxElectronics) | ||
647 | Com_Printf("this slot hasn't been found in aircraft electronics slots\n"); | ||
648 | break; | ||
649 | case AC_ITEM_AMMO: | ||
650 | case AC_ITEM_WEAPON: | ||
651 | /* weapon selected */ | ||
652 | for (i = 0; i < aircraft->maxWeapons; i++) { | ||
653 | if (aircraft->weapons[i].pos == pos) { | ||
654 | airequipSelectedSlot = i; | ||
655 | break; | ||
656 | } | ||
657 | } | ||
658 | if (i == aircraft->maxWeapons) | ||
659 | Com_Printf("this slot hasn't been found in aircraft weapon slots\n"); | ||
660 | break; | ||
661 | default: | ||
662 | Com_Printf("AIM_AircraftEquipSlotSelect_f : only weapons and electronics have several slots\n"); | ||
663 | break; | ||
664 | } | ||
665 | |||
666 | /* Update menu after changing slot */ | ||
667 | AIM_AircraftEquipMenuUpdate(); | ||
668 | |||
669 | /* update description with the selected slot */ | ||
670 | if (updateZone > 0) | ||
671 | AIM_UpdateItemDescription(false, true); | ||
672 | else | ||
673 | AIM_UpdateItemDescription(true, false); | ||
674 | } | ||
675 | |||
676 | /** | ||
677 | * @brief Select the current zone you want to assign the item to. | ||
678 | */ | ||
679 | static void AIM_AircraftEquipZoneSelect_f (void) | ||
680 | { | ||
681 | int zone; | ||
682 | aircraft_t *aircraft; | ||
683 | aircraftSlot_t *slot; | ||
684 | base_t *base = B_GetCurrentSelectedBase(); | ||
685 | |||
686 | if (!base) | ||
687 | return; | ||
688 | |||
689 | if (Cmd_Argc() < 2) { | ||
690 | Com_Printf("Usage: %s <arg>\n", Cmd_Argv(0)); | ||
691 | return; | ||
692 | } | ||
693 | |||
694 | zone = atoi(Cmd_Argv(1)); | ||
695 | |||
696 | aircraft = base->aircraftCurrent; | ||
697 | assert(aircraft)(__builtin_expect(!(aircraft), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 697, "aircraft") : (void)0); | ||
698 | /* Select slot */ | ||
699 | slot = AII_SelectAircraftSlot(aircraft, airequipID); | ||
700 | |||
701 | /* ammos are only available for weapons */ | ||
702 | switch (airequipID) { | ||
703 | /* a weapon was selected - select ammo type corresponding to this weapon */ | ||
704 | case AC_ITEM_WEAPON: | ||
705 | if (zone == ZONE_AMMO) { | ||
706 | if (slot->item) | ||
707 | airequipID = AC_ITEM_AMMO; | ||
708 | } | ||
709 | break; | ||
710 | /* an ammo was selected - select weapon type corresponding to this ammo */ | ||
711 | case AC_ITEM_AMMO: | ||
712 | if (zone != ZONE_AMMO) | ||
713 | airequipID = AC_ITEM_WEAPON; | ||
714 | break; | ||
715 | default : | ||
716 | /* ZONE_AMMO is not available for electronics and shields */ | ||
717 | if (zone == ZONE_AMMO) | ||
718 | return; | ||
719 | } | ||
720 | airequipSelectedZone = zone; | ||
721 | |||
722 | /* update menu */ | ||
723 | AIM_AircraftEquipMenuUpdate(); | ||
724 | /* Check that the selected zone is OK */ | ||
725 | AIM_CheckAirequipSelectedZone(slot); | ||
726 | |||
727 | AIM_UpdateItemDescription(false, true); | ||
728 | } | ||
729 | |||
730 | /** | ||
731 | * @brief Add selected item to current zone. | ||
732 | * @note Called from airequip menu | ||
733 | * @sa aircraftItemType_t | ||
734 | */ | ||
735 | static void AIM_AircraftEquipAddItem_f (void) | ||
736 | { | ||
737 | int zone; | ||
738 | aircraftSlot_t *slot; | ||
739 | aircraft_t *aircraft = NULL__null; | ||
740 | base_t *base = B_GetCurrentSelectedBase(); | ||
741 | |||
742 | zone = (airequipID == AC_ITEM_AMMO) ? 2 : 1; | ||
| |||
743 | |||
744 | /* proceed only if an item has been selected */ | ||
745 | if (!aimSelectedTechnology) | ||
| |||
746 | return; | ||
747 | |||
748 | assert(base)(__builtin_expect(!(base), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 748, "base") : (void)0); | ||
749 | aircraft = base->aircraftCurrent; | ||
750 | assert(aircraft)(__builtin_expect(!(aircraft), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 750, "aircraft") : (void)0); | ||
751 | base = aircraft->homebase; /* we need to know where items will be removed */ | ||
752 | slot = AII_SelectAircraftSlot(aircraft, airequipID); | ||
753 | |||
754 | /* the clicked button doesn't correspond to the selected zone */ | ||
755 | if (zone != airequipSelectedZone) | ||
| |||
756 | return; | ||
757 | |||
758 | /* check if the zone exists */ | ||
759 | if (zone >= ZONE_MAX) | ||
| |||
760 | return; | ||
761 | |||
762 | /* update the new item to slot */ | ||
763 | |||
764 | switch (zone) { | ||
| |||
765 | case ZONE_MAIN: | ||
766 | if (!slot->nextItem) { | ||
| |||
767 | /* we add the weapon, shield, item if slot is free or the installation of current item just began */ | ||
768 | if (!slot->item || (slot->item && slot->installationTime == slot->item->craftitem.installationTime)) { | ||
769 | AII_RemoveItemFromSlot(base, slot, false); | ||
770 | AII_AddItemToSlot(base, aimSelectedTechnology, slot, false); /* Aircraft stats are updated below */ | ||
771 | AII_AutoAddAmmo(slot); | ||
772 | break; | ||
773 | } else if (slot->item == INVSH_GetItemByID(aimSelectedTechnology->provides)) { | ||
774 | /* the added item is the same than the one in current slot */ | ||
775 | if (slot->installationTime == -slot->item->craftitem.installationTime) { | ||
776 | /* player changed his mind: he just want to re-add the item he just removed */ | ||
777 | slot->installationTime = 0; | ||
778 | break; | ||
779 | } else if (!slot->installationTime) { | ||
780 | /* player try to add a weapon he already have: just skip */ | ||
781 | return; | ||
782 | } | ||
783 | } else { | ||
784 | /* We start removing current item in slot, and the selected item will be installed afterwards */ | ||
785 | slot->installationTime = -slot->item->craftitem.installationTime; | ||
786 | /* more below */ | ||
787 | } | ||
788 | } else { | ||
789 | /* remove weapon and ammo of next item */ | ||
790 | AII_RemoveNextItemFromSlot(base, slot, false); | ||
791 | /* more below */ | ||
792 | } | ||
793 | |||
794 | /* we change the weapon, shield, item, or base defence that will be installed AFTER the removal | ||
795 | * of the one in the slot atm */ | ||
796 | AII_AddItemToSlot(base, aimSelectedTechnology, slot, true); | ||
797 | AII_AutoAddAmmo(slot); | ||
798 | break; | ||
799 | case ZONE_AMMO: | ||
800 | /* we can change ammo only if the selected item is an ammo (for weapon or base defence system) */ | ||
801 | if (airequipID >= AC_ITEM_AMMO) { | ||
802 | AII_AddAmmoToSlot(base, aimSelectedTechnology, slot); | ||
803 | } | ||
804 | break; | ||
805 | default: | ||
806 | /* Zone higher than ZONE_AMMO shouldn't exist */ | ||
807 | return; | ||
808 | } | ||
809 | |||
810 | /* Update the values of aircraft stats (just in case an item has an installationTime of 0) */ | ||
811 | AII_UpdateAircraftStats(aircraft); | ||
812 | |||
813 | AIM_AircraftEquipMenuUpdate(); | ||
814 | } | ||
815 | |||
816 | /** | ||
817 | * @brief Delete an object from a zone. | ||
818 | */ | ||
819 | static void AIM_AircraftEquipRemoveItem_f (void) | ||
820 | { | ||
821 | int zone; | ||
822 | aircraftSlot_t *slot; | ||
823 | aircraft_t *aircraft = NULL__null; | ||
824 | base_t *base = B_GetCurrentSelectedBase(); | ||
825 | |||
826 | zone = (airequipID == AC_ITEM_AMMO) ? 2 : 1; | ||
827 | |||
828 | assert(base)(__builtin_expect(!(base), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 828, "base") : (void)0); | ||
829 | aircraft = base->aircraftCurrent; | ||
830 | assert(aircraft)(__builtin_expect(!(aircraft), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_fightequip_callbacks.cpp" , 830, "aircraft") : (void)0); | ||
831 | slot = AII_SelectAircraftSlot(aircraft, airequipID); | ||
832 | |||
833 | /* no item in slot: nothing to remove */ | ||
834 | if (!slot->item) | ||
835 | return; | ||
836 | |||
837 | /* update the new item to slot */ | ||
838 | |||
839 | switch (zone) { | ||
840 | case ZONE_MAIN: | ||
841 | if (!slot->nextItem) { | ||
842 | /* we change the weapon, shield, item, or base defence that is already in the slot */ | ||
843 | /* if the item has been installed since less than 1 hour, you don't need time to remove it */ | ||
844 | if (slot->installationTime < slot->item->craftitem.installationTime) { | ||
845 | slot->installationTime = -slot->item->craftitem.installationTime; | ||
846 | AII_RemoveItemFromSlot(base, slot, true); /* we remove only ammo, not item */ | ||
847 | } else { | ||
848 | AII_RemoveItemFromSlot(base, slot, false); /* we remove weapon and ammo */ | ||
849 | } | ||
850 | /* aircraft stats are updated below */ | ||
851 | } else { | ||
852 | /* we change the weapon, shield, item, or base defence that will be installed AFTER the removal | ||
853 | * of the one in the slot atm */ | ||
854 | AII_RemoveNextItemFromSlot(base, slot, false); /* we remove weapon and ammo */ | ||
855 | /* if you canceled next item for less than 1 hour, previous item is still functional */ | ||
856 | if (slot->installationTime == -slot->item->craftitem.installationTime) { | ||
857 | slot->installationTime = 0; | ||
858 | } | ||
859 | } | ||
860 | break; | ||
861 | case ZONE_AMMO: | ||
862 | /* we can change ammo only if the selected item is an ammo (for weapon or base defence system) */ | ||
863 | if (airequipID >= AC_ITEM_AMMO) { | ||
864 | if (slot->nextAmmo) | ||
865 | AII_RemoveNextItemFromSlot(base, slot, true); | ||
866 | else | ||
867 | AII_RemoveItemFromSlot(base, slot, true); | ||
868 | } | ||
869 | break; | ||
870 | default: | ||
871 | /* Zone higher than ZONE_AMMO shouldn't exist */ | ||
872 | return; | ||
873 | } | ||
874 | |||
875 | /* Update the values of aircraft stats */ | ||
876 | AII_UpdateAircraftStats(aircraft); | ||
877 | |||
878 | AIM_AircraftEquipMenuUpdate(); | ||
879 | } | ||
880 | |||
881 | /** | ||
882 | * @brief Set AIM_selectedTechnology to the technology of current selected aircraft item. | ||
883 | * @sa AIM_AircraftEquipMenuUpdate_f | ||
884 | */ | ||
885 | static void AIM_AircraftEquipMenuClick_f (void) | ||
886 | { | ||
887 | int techIdx; | ||
888 | |||
889 | if (Cmd_Argc() < 2) { | ||
890 | Com_Printf("Usage: %s <num>\n", Cmd_Argv(0)); | ||
891 | return; | ||
892 | } | ||
893 | |||
894 | /* Which tech? */ | ||
895 | techIdx = atoi(Cmd_Argv(1)); | ||
896 | aimSelectedTechnology = RS_GetTechByIDX(techIdx); | ||
897 | AIM_UpdateItemDescription(true, false); | ||
898 | } | ||
899 | |||
900 | /** | ||
901 | * @brief Update the GUI with a named itemtype | ||
902 | */ | ||
903 | static void AIM_AircraftItemtypeByName_f (void) | ||
904 | { | ||
905 | aircraftItemType_t i; | ||
906 | const char *name; | ||
907 | |||
908 | if (Cmd_Argc() != 2) { | ||
909 | Com_Printf("Usage: %s <weapon|ammo|armour|item>\n", Cmd_Argv(0)); | ||
910 | return; | ||
911 | } | ||
912 | |||
913 | name = Cmd_Argv(1); | ||
914 | |||
915 | if (Q_streq(name, "weapon")(strcmp(name, "weapon") == 0)) | ||
916 | i = AC_ITEM_WEAPON; | ||
917 | else if (Q_streq(name, "ammo")(strcmp(name, "ammo") == 0)) | ||
918 | i = AC_ITEM_AMMO; | ||
919 | else if (Q_streq(name, "armour")(strcmp(name, "armour") == 0)) | ||
920 | i = AC_ITEM_SHIELD; | ||
921 | else if (Q_streq(name, "item")(strcmp(name, "item") == 0)) | ||
922 | i = AC_ITEM_ELECTRONICS; | ||
923 | else { | ||
924 | Com_Printf("AIM_AircraftItemtypeByName_f: Invalid itemtype!\n"); | ||
925 | return; | ||
926 | } | ||
927 | |||
928 | airequipID = i; | ||
929 | Cmd_ExecuteString(va("airequip_updatemenu %d", airequipID)); | ||
930 | } | ||
931 | |||
932 | void AIM_InitCallbacks (void) | ||
933 | { | ||
934 | Cmd_AddCommand("airequip_updatemenu", AIM_AircraftEquipMenuUpdate_f, "Init function for the aircraft equip menu"); | ||
935 | Cmd_AddCommand("airequip_selectcategory", AIM_AircraftItemtypeByName_f, "Select an item category and update the GUI"); | ||
936 | Cmd_AddCommand("airequip_list_click", AIM_AircraftEquipMenuClick_f); | ||
937 | Cmd_AddCommand("airequip_slot_select", AIM_AircraftEquipSlotSelect_f); | ||
938 | Cmd_AddCommand("airequip_add_item", AIM_AircraftEquipAddItem_f, "Add item to slot"); | ||
939 | Cmd_AddCommand("airequip_remove_item", AIM_AircraftEquipRemoveItem_f, "Remove item from slot"); | ||
940 | Cmd_AddCommand("airequip_zone_select", AIM_AircraftEquipZoneSelect_f); | ||
941 | } | ||
942 | |||
943 | void AIM_ShutdownCallbacks (void) | ||
944 | { | ||
945 | Cmd_RemoveCommand("airequip_updatemenu"); | ||
946 | Cmd_RemoveCommand("airequip_selectcategory"); | ||
947 | Cmd_RemoveCommand("airequip_list_click"); | ||
948 | Cmd_RemoveCommand("airequip_slot_select"); | ||
949 | Cmd_RemoveCommand("airequip_add_item"); | ||
950 | Cmd_RemoveCommand("airequip_remove_item"); | ||
951 | Cmd_RemoveCommand("airequip_zone_select"); | ||
952 | } |