1 /**
2 * @file
3 * @brief contains everything related to equiping slots of aircraft or base
4 * @note Base defence functions prefix: BDEF_
5 * @note Aircraft items slots functions prefix: AII_
6 */
7
8 /*
9 Copyright (C) 2002-2013 UFO: Alien Invasion.
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19
20 See the GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
27 #include "../../cl_shared.h"
28 #include "cp_campaign.h"
29 #include "cp_mapfightequip.h"
30 #include "cp_ufo.h"
31 #include "save/save_fightequip.h"
32
33 #define UFO_RELOAD_DELAY_MULTIPLIER 2
34 #define AIRCRAFT_RELOAD_DELAY_MULTIPLIER 2
35 #define BASE_RELOAD_DELAY_MULTIPLIER 2
36 #define INSTALLATION_RELOAD_DELAY_MULTIPLIER 2
37
38 /**
39 * @brief Returns a list of craftitem technologies for the given type.
40 * @note This list is terminated by a nullptr pointer.
41 * @param[in] type Type of the craft-items to return.
42 */
AII_GetCraftitemTechsByType(aircraftItemType_t type)43 technology_t** AII_GetCraftitemTechsByType (aircraftItemType_t type)
44 {
45 static technology_t* techList[MAX_TECHNOLOGIES];
46 int i, j = 0;
47
48 for (i = 0; i < cgi->csi->numODs; i++) {
49 const objDef_t* aircraftitem = INVSH_GetItemByIDX(i);
50 if (aircraftitem->craftitem.type == type) {
51 technology_t* tech = RS_GetTechForItem(aircraftitem);
52 assert(j < MAX_TECHNOLOGIES);
53 techList[j] = tech;
54 j++;
55 }
56 /* j+1 because last item has to be nullptr */
57 if (j + 1 >= MAX_TECHNOLOGIES) {
58 Com_Printf("AII_GetCraftitemTechsByType: MAX_TECHNOLOGIES limit hit.\n");
59 break;
60 }
61 }
62 /* terminate the list */
63 techList[j] = nullptr;
64 return techList;
65 }
66
67 /**
68 * @brief Returns craftitem weight based on size.
69 * @param[in] od Pointer to objDef_t object being craftitem.
70 * @return itemWeight_t
71 * @sa AII_WeightToName
72 */
AII_GetItemWeightBySize(const objDef_t * od)73 itemWeight_t AII_GetItemWeightBySize (const objDef_t* od)
74 {
75 assert(od);
76
77 if (od->size < 50)
78 return ITEM_LIGHT;
79 else if (od->size < 100)
80 return ITEM_MEDIUM;
81 else
82 return ITEM_HEAVY;
83 }
84
85 /**
86 * @brief Check if an aircraft item should or should not be displayed in airequip menu
87 * @param[in] slot Pointer to an aircraft slot (can be base/installation too)
88 * @param[in] tech Pointer to the technology to test
89 * @return true if the craft item should be displayed, false else
90 */
AIM_SelectableCraftItem(const aircraftSlot_t * slot,const technology_t * tech)91 bool AIM_SelectableCraftItem (const aircraftSlot_t* slot, const technology_t* tech)
92 {
93 const objDef_t* item;
94
95 if (!slot)
96 return false;
97
98 if (!RS_IsResearched_ptr(tech))
99 return false;
100
101 item = INVSH_GetItemByID(tech->provides);
102 if (!item)
103 return false;
104
105 if (item->craftitem.type >= AC_ITEM_AMMO) {
106 const objDef_t* weapon = slot->item;
107 int k;
108 if (slot->nextItem != nullptr)
109 weapon = slot->nextItem;
110
111 if (weapon == nullptr)
112 return false;
113
114 /* Is the ammo is usable with the slot */
115 for (k = 0; k < weapon->numAmmos; k++) {
116 const objDef_t* usable = weapon->ammos[k];
117 if (usable && item->idx == usable->idx)
118 break;
119 }
120 if (k >= weapon->numAmmos)
121 return false;
122 }
123
124 /** @todo maybe this isn't working, aircraft slot type can't be an AMMO */
125 if (slot->type >= AC_ITEM_AMMO) {
126 /** @todo This only works for ammo that is usable in exactly one weapon
127 * check the weap_idx array and not only the first value */
128 if (!slot->nextItem && item->weapons[0] != slot->item)
129 return false;
130
131 /* are we trying to change ammos for nextItem? */
132 if (slot->nextItem && item->weapons[0] != slot->nextItem)
133 return false;
134 }
135
136 /* you can install an item only if its weight is small enough for the slot */
137 if (AII_GetItemWeightBySize(item) > slot->size)
138 return false;
139
140 /* you can't install an item that you don't possess
141 * virtual items don't need to be possessed
142 * installations always have weapon and ammo */
143 if (slot->aircraft) {
144 if (!B_BaseHasItem(slot->aircraft->homebase, item))
145 return false;
146 } else if (slot->base) {
147 if (!B_BaseHasItem(slot->base, item))
148 return false;
149 }
150
151 /* you can't install an item that does not have an installation time (alien item)
152 * except for ammo which does not have installation time */
153 if (item->craftitem.installationTime == -1 && slot->type < AC_ITEM_AMMO)
154 return false;
155
156 return true;
157 }
158
159 /**
160 * @brief Checks to see if the pilot is in any aircraft at this base.
161 * @param[in] base Which base has the aircraft to search for the employee in.
162 * @param[in] pilot Which employee to search for.
163 * @return true or false depending on if the employee was found on the base aircraft.
164 */
AIM_PilotAssignedAircraft(const base_t * base,const Employee * pilot)165 bool AIM_PilotAssignedAircraft (const base_t* base, const Employee* pilot)
166 {
167 bool found = false;
168
169 AIR_ForeachFromBase(aircraft, base) {
170 if (AIR_GetPilot(aircraft) == pilot) {
171 found = true;
172 break;
173 }
174 }
175
176 return found;
177 }
178
179 /**
180 * @brief Adds a defence system to base.
181 * @param[in] basedefType Base defence type (see basedefenceType_t)
182 * @param[in] base Pointer to the base in which the battery will be added
183 * @sa BDEF_RemoveBattery
184 */
BDEF_AddBattery(basedefenceType_t basedefType,base_t * base)185 void BDEF_AddBattery (basedefenceType_t basedefType, base_t* base)
186 {
187 switch (basedefType) {
188 case BASEDEF_MISSILE:
189 if (base->numBatteries >= MAX_BASE_SLOT) {
190 Com_Printf("BDEF_AddBattery: too many missile batteries in base\n");
191 return;
192 }
193 if (base->numBatteries)
194 base->batteries[base->numBatteries].autofire = base->batteries[0].autofire;
195 else if (base->numLasers)
196 base->batteries[base->numBatteries].autofire = base->lasers[0].autofire;
197 else
198 base->batteries[base->numBatteries].autofire = true;
199 base->numBatteries++;
200 break;
201 case BASEDEF_LASER:
202 if (base->numLasers >= MAX_BASE_SLOT) {
203 Com_Printf("BDEF_AddBattery: too many laser batteries in base\n");
204 return;
205 }
206 base->lasers[base->numLasers].slot.ammoLeft = AMMO_STATUS_NOT_SET;
207 if (base->numBatteries)
208 base->lasers[base->numLasers].autofire = base->batteries[0].autofire;
209 else if (base->numLasers)
210 base->lasers[base->numLasers].autofire = base->lasers[0].autofire;
211 else
212 base->lasers[base->numLasers].autofire = true;
213 base->numLasers++;
214 break;
215 default:
216 Com_Printf("BDEF_AddBattery: unknown type of base defence system.\n");
217 }
218 }
219
220 /**
221 * @brief Remove a base defence sytem from base.
222 * @param[in] base The base that is affected
223 * @param[in] basedefType (see basedefenceType_t)
224 * @param[in] idx index of the battery to destroy
225 * @note if idx is negative the function looks for an empty battery to remove,
226 * it removes the last one if every one equipped
227 * @sa BDEF_AddBattery
228 */
BDEF_RemoveBattery(base_t * base,basedefenceType_t basedefType,int idx)229 void BDEF_RemoveBattery (base_t* base, basedefenceType_t basedefType, int idx)
230 {
231 int i;
232 assert(base);
233
234 /* Select the type of base defence system to destroy */
235 switch (basedefType) {
236 case BASEDEF_MISSILE: /* this is a missile battery */
237 /* we must have at least one missile battery to remove it */
238 assert(base->numBatteries > 0);
239 /* look for an unequipped battery */
240 if (idx < 0) {
241 for (i = 0; i < base->numBatteries; i++) {
242 if (!base->batteries[i].slot.item) {
243 idx = i;
244 break;
245 }
246 }
247 }
248 /* if none found remove the last one */
249 if (idx < 0)
250 idx = base->numBatteries - 1;
251 REMOVE_ELEM(base->batteries, idx, base->numBatteries);
252 /* just for security */
253 AII_InitialiseSlot(&base->batteries[base->numBatteries].slot, nullptr, base, nullptr, AC_ITEM_BASE_MISSILE);
254 break;
255 case BASEDEF_LASER: /* this is a laser battery */
256 /* we must have at least one laser battery to remove it */
257 assert(base->numLasers > 0);
258 /* look for an unequipped battery */
259 if (idx < 0) {
260 for (i = 0; i < base->numLasers; i++) {
261 if (!base->lasers[i].slot.item) {
262 idx = i;
263 break;
264 }
265 }
266 }
267 /* if none found remove the last one */
268 if (idx < 0)
269 idx = base->numLasers - 1;
270 REMOVE_ELEM(base->lasers, idx, base->numLasers);
271 /* just for security */
272 AII_InitialiseSlot(&base->lasers[base->numLasers].slot, nullptr, base, nullptr, AC_ITEM_BASE_LASER);
273 break;
274 default:
275 Com_Printf("BDEF_RemoveBattery_f: unknown type of base defence system.\n");
276 }
277 }
278
279 /**
280 * @brief Initialise all values of base slot defence.
281 * @param[in] base Pointer to the base which needs initalisation of its slots.
282 */
BDEF_InitialiseBaseSlots(base_t * base)283 void BDEF_InitialiseBaseSlots (base_t* base)
284 {
285 int i;
286
287 for (i = 0; i < MAX_BASE_SLOT; i++) {
288 baseWeapon_t* battery = &base->batteries[i];
289 baseWeapon_t* laser = &base->lasers[i];
290 AII_InitialiseSlot(&battery->slot, nullptr, base, nullptr, AC_ITEM_BASE_MISSILE);
291 AII_InitialiseSlot(&laser->slot, nullptr, base, nullptr, AC_ITEM_BASE_LASER);
292 battery->autofire = true;
293 battery->target = nullptr;
294 laser->autofire = true;
295 laser->target = nullptr;
296 }
297 }
298
299 /**
300 * @brief Initialise all values of installation slot defence.
301 * @param[in] installation Pointer to the installation which needs initialisation of its slots.
302 */
BDEF_InitialiseInstallationSlots(installation_t * installation)303 void BDEF_InitialiseInstallationSlots (installation_t* installation)
304 {
305 int i;
306
307 for (i = 0; i < installation->installationTemplate->maxBatteries; i++) {
308 baseWeapon_t* battery = &installation->batteries[i];
309 AII_InitialiseSlot(&battery->slot, nullptr, nullptr, installation, AC_ITEM_BASE_MISSILE);
310 battery->target = nullptr;
311 battery->autofire = true;
312 }
313 }
314
315
316 /**
317 * @brief Update the installation delay of one slot.
318 * @param[in] base Pointer to the base to update the storage and capacity for
319 * @param[in] installation Pointer to the installation being installed.
320 * @param[in] aircraft Pointer to the aircraft (nullptr if a base is updated)
321 * @param[in] slot Pointer to the slot to update
322 * @sa AII_AddItemToSlot
323 */
AII_UpdateOneInstallationDelay(base_t * base,installation_t * installation,aircraft_t * aircraft,aircraftSlot_t * slot)324 static void AII_UpdateOneInstallationDelay (base_t* base, installation_t* installation, aircraft_t* aircraft, aircraftSlot_t* slot)
325 {
326 assert(base || installation);
327
328 /* if the item is already installed, nothing to do */
329 if (slot->installationTime == 0)
330 return;
331 else if (slot->installationTime > 0) {
332 /* the item is being installed */
333 slot->installationTime--;
334 /* check if installation is over */
335 if (slot->installationTime <= 0) {
336 /* Update stats values */
337 if (aircraft) {
338 AII_UpdateAircraftStats(aircraft);
339 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
340 _("%s was successfully installed into aircraft %s at %s."),
341 _(slot->item->name), aircraft->name, aircraft->homebase->name);
342 } else if (installation) {
343 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer), _("%s was successfully installed at installation %s."),
344 _(slot->item->name), installation->name);
345 } else {
346 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer), _("%s was successfully installed at %s."),
347 _(slot->item->name), base->name);
348 }
349 MSO_CheckAddNewMessage(NT_INSTALLATION_INSTALLED, _("Notice"), cp_messageBuffer);
350 }
351 } else if (slot->installationTime < 0) {
352 /* the item is being removed */
353 slot->installationTime++;
354 if (slot->installationTime >= 0) {
355 #ifdef DEBUG
356 if (aircraft && aircraft->homebase != base)
357 Sys_Error("AII_UpdateOneInstallationDelay: aircraft->homebase and base pointers are out of sync\n");
358 #endif
359 const objDef_t* olditem = slot->item;
360 AII_RemoveItemFromSlot(base, slot, false);
361 if (aircraft) {
362 AII_UpdateAircraftStats(aircraft);
363 /* Only stop time and post a notice, if no new item to install is assigned */
364 if (!slot->item) {
365 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
366 _("%s was successfully removed from aircraft %s at %s."),
367 _(olditem->name), aircraft->name, base->name);
368 MSO_CheckAddNewMessage(NT_INSTALLATION_REMOVED, _("Notice"), cp_messageBuffer);
369 } else {
370 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
371 _ ("%s was successfully removed, starting installation of %s into aircraft %s at %s"),
372 _(olditem->name), _(slot->item->name), aircraft->name, base->name);
373 MSO_CheckAddNewMessage(NT_INSTALLATION_REPLACE, _("Notice"), cp_messageBuffer);
374 }
375 } else if (!slot->item) {
376 if (installation) {
377 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
378 _("%s was successfully removed from installation %s."),
379 _(olditem->name), installation->name);
380 } else {
381 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer), _("%s was successfully removed from %s."),
382 _(olditem->name), base->name);
383 }
384 MSO_CheckAddNewMessage(NT_INSTALLATION_REMOVED, _("Notice"), cp_messageBuffer);
385 }
386 }
387 }
388 }
389
390 /**
391 * @brief Update the installation delay of all slots of a given aircraft.
392 * @note hourly called
393 * @sa CP_CampaignRun
394 * @sa AII_UpdateOneInstallationDelay
395 */
AII_UpdateInstallationDelay(void)396 void AII_UpdateInstallationDelay (void)
397 {
398 base_t* base;
399 int k;
400
401 INS_Foreach(installation) {
402 for (k = 0; k < installation->installationTemplate->maxBatteries; k++)
403 AII_UpdateOneInstallationDelay(nullptr, installation, nullptr, &installation->batteries[k].slot);
404 }
405
406 base = nullptr;
407 while ((base = B_GetNext(base)) != nullptr) {
408 /* Update base */
409 for (k = 0; k < base->numBatteries; k++)
410 AII_UpdateOneInstallationDelay(base, nullptr, nullptr, &base->batteries[k].slot);
411 for (k = 0; k < base->numLasers; k++)
412 AII_UpdateOneInstallationDelay(base, nullptr, nullptr, &base->lasers[k].slot);
413 }
414
415 /* Update each aircraft */
416 AIR_Foreach(aircraft) {
417 if (AIR_IsAircraftInBase(aircraft)) {
418 /* Update electronics delay */
419 for (k = 0; k < aircraft->maxElectronics; k++)
420 AII_UpdateOneInstallationDelay(aircraft->homebase, nullptr, aircraft, aircraft->electronics + k);
421 /* Update weapons delay */
422 for (k = 0; k < aircraft->maxWeapons; k++)
423 AII_UpdateOneInstallationDelay(aircraft->homebase, nullptr, aircraft, aircraft->weapons + k);
424
425 /* Update shield delay */
426 AII_UpdateOneInstallationDelay(aircraft->homebase, nullptr, aircraft, &aircraft->shield);
427 }
428 }
429 }
430
431 /**
432 * @brief Auto add ammo corresponding to weapon, if there is enough in storage.
433 * @param[in] slot Pointer to the slot where you want to add ammo
434 * @sa AIM_AircraftEquipAddItem_f
435 * @sa AII_RemoveItemFromSlot
436 */
AII_AutoAddAmmo(aircraftSlot_t * slot)437 void AII_AutoAddAmmo (aircraftSlot_t* slot)
438 {
439 assert(slot);
440
441 if (slot->aircraft && !AIR_IsUFO(slot->aircraft) && !AIR_IsAircraftInBase(slot->aircraft))
442 return;
443
444 /* Get the weapon (either current weapon or weapon to install after this one is removed) */
445 const objDef_t* item = slot->nextItem ? slot->nextItem : slot->item;
446 /* no items assigned */
447 if (!item)
448 return;
449 /* item not a weapon */
450 if (item->craftitem.type > AC_ITEM_WEAPON)
451 return;
452 /* don't try to add ammo to a slot that already has ammo */
453 if (slot->nextItem ? slot->nextAmmo : slot->ammo)
454 return;
455 /* Try every ammo usable with this weapon until we find one we have in storage */
456 for (int k = 0; k < item->numAmmos; k++) {
457 const objDef_t* ammo = item->ammos[k];
458 if (!ammo)
459 continue;
460 const technology_t* ammoTech = RS_GetTechForItem(ammo);
461 if (!AIM_SelectableCraftItem(slot, ammoTech))
462 continue;
463 base_t* base;
464 if (ammo->isVirtual)
465 base = nullptr;
466 else if (slot->aircraft)
467 base = slot->aircraft->homebase;
468 else
469 base = slot->base;
470 AII_AddAmmoToSlot(base, ammoTech, slot);
471 break;
472 }
473 }
474
475 /**
476 * @brief Remove the item from the slot (or optionally its ammo only) and put it the base storage.
477 * @note if there is another item to install after removal, begin this installation.
478 * @note virtual items cannot be removed!
479 * @param[in] base The base to add the item to (may be nullptr if item shouldn't be removed from any base).
480 * @param[in] slot The slot to remove the item from.
481 * @param[in] ammo true if we want to remove only ammo. false if the whole item should be removed.
482 * @sa AII_AddItemToSlot
483 * @sa AII_AddAmmoToSlot
484 * @sa AII_RemoveNextItemFromSlot
485 */
AII_RemoveItemFromSlot(base_t * base,aircraftSlot_t * slot,bool ammo)486 void AII_RemoveItemFromSlot (base_t* base, aircraftSlot_t* slot, bool ammo)
487 {
488 assert(slot);
489
490 if (ammo) {
491 /* only remove the ammo */
492 if (slot->ammo) {
493 /* Can only remove non-virtual ammo */
494 if (!slot->ammo->isVirtual) {
495 if (base)
496 B_AddToStorage(base, slot->ammo, 1);
497 slot->ammo = nullptr;
498 slot->ammoLeft = 0;
499 }
500 }
501 } else if (slot->item) {
502 /* remove ammo */
503 AII_RemoveItemFromSlot(base, slot, true);
504 if (!slot->item->isVirtual) {
505 if (base)
506 B_AddToStorage(base, slot->item, 1);
507 /* the removal is over */
508 if (slot->nextItem) {
509 /* there is anoter item to install after this one */
510 slot->item = slot->nextItem;
511 /* we already removed nextItem from storage when it has been added to slot: don't use B_AddToStorage */
512 slot->ammo = slot->nextAmmo;
513 if (slot->ammo)
514 slot->ammoLeft = slot->ammo->ammo;
515 slot->installationTime = slot->item->craftitem.installationTime;
516 slot->nextItem = nullptr;
517 slot->nextAmmo = nullptr;
518 } else {
519 slot->item = nullptr;
520 /* make sure nextAmmo is removed too
521 * virtual ammos cannot be removed without the weapon itself */
522 slot->ammo = nullptr;
523 slot->ammoLeft = 0;
524 slot->installationTime = 0;
525 }
526 }
527 }
528 }
529
530 /**
531 * @brief Cancel replacing item, move nextItem (or optionally its ammo only) back to the base storage.
532 * @param[in] base The base to add the item to (may be nullptr if item shouldn't be removed from any base).
533 * @param[in] slot The slot to remove the item from.
534 * @param[in] ammo true if we want to remove only ammo. false if the whole item should be removed.
535 * @sa AII_AddItemToSlot
536 * @sa AII_AddAmmoToSlot
537 * @sa AII_RemoveItemFromSlot
538 */
AII_RemoveNextItemFromSlot(base_t * base,aircraftSlot_t * slot,bool ammo)539 void AII_RemoveNextItemFromSlot (base_t* base, aircraftSlot_t* slot, bool ammo)
540 {
541 assert(slot);
542
543 if (ammo) {
544 /* only remove the ammo */
545 if (slot->nextAmmo) {
546 if (!slot->nextAmmo->isVirtual) {
547 if (base)
548 B_AddToStorage(base, slot->nextAmmo, 1);
549 slot->nextAmmo = nullptr;
550 }
551 }
552 } else if (slot->nextItem) {
553 /* Remove nextItem */
554 if (base)
555 B_AddToStorage(base, slot->nextItem, 1);
556 slot->nextItem = nullptr;
557 /* also remove ammo if any */
558 if (slot->nextAmmo)
559 AII_RemoveNextItemFromSlot(base, slot, true);
560 /* make sure nextAmmo is removed too
561 * virtual ammos cannot be removed without the weapon itself */
562 slot->nextAmmo = nullptr;
563 }
564 }
565
566 /**
567 * @brief Reloads an aircraft/defence-system weapon
568 * @param[in,out] slot Pointer to the aircraftSlot where weapon is attached to
569 * @returns true on success - if the weapon was reloaded OR it didn't need reload
570 */
AII_ReloadWeapon(aircraftSlot_t * slot)571 bool AII_ReloadWeapon (aircraftSlot_t* slot)
572 {
573 assert(slot);
574
575 /* no weapon assigned - failure */
576 if (!slot->item)
577 return false;
578 /* no ammo assigned - try adding one */
579 if (!slot->ammo)
580 AII_AutoAddAmmo(slot);
581 /* still no ammo assigned - failure */
582 if (!slot->ammo)
583 return false;
584 /* no reload needed - success */
585 if (slot->ammoLeft >= slot->ammo->ammo)
586 return true;
587
588 if (slot->aircraft) {
589 /* PHALANX aircraft and UFO crafts */
590 if (AIR_IsUFO(slot->aircraft)) {
591 /* UFO - can always be reloaded */
592 slot->ammoLeft = slot->ammo->ammo;
593 slot->delayNextShot = slot->ammo->craftitem.weaponDelay * UFO_RELOAD_DELAY_MULTIPLIER;
594 } else {
595 /* PHALANX aircraft */
596 /* not at home */
597 if (!AIR_IsAircraftInBase(slot->aircraft))
598 return false;
599 /* no more ammo available */
600 if (!B_BaseHasItem(slot->aircraft->homebase, slot->ammo)) {
601 slot->ammo = nullptr;
602 AII_UpdateAircraftStats(slot->aircraft);
603 return false;
604 }
605
606 B_AddToStorage(slot->aircraft->homebase, slot->ammo, -1);
607 slot->ammoLeft = slot->ammo->ammo;
608 slot->delayNextShot = slot->ammo->craftitem.weaponDelay * AIRCRAFT_RELOAD_DELAY_MULTIPLIER;
609 AII_UpdateAircraftStats(slot->aircraft);
610 }
611 } else if (slot->base) {
612 /* Base Defence weapons */
613 /* no more ammo available */
614 if (!B_BaseHasItem(slot->base, slot->ammo))
615 return false;
616
617 B_AddToStorage(slot->base, slot->ammo, -1);
618 slot->ammoLeft = slot->ammo->ammo;
619 slot->delayNextShot = slot->ammo->craftitem.weaponDelay * BASE_RELOAD_DELAY_MULTIPLIER;
620 } else if (slot->installation) {
621 /* Installations (SAM-Sites) - can always be reloaded. */
622 slot->ammoLeft = slot->ammo->ammo;
623 slot->delayNextShot = slot->ammo->craftitem.weaponDelay * INSTALLATION_RELOAD_DELAY_MULTIPLIER;
624 } else {
625 cgi->Com_Error(ERR_DROP, "AII_ReloadWeapon: AircraftSlot not linked anywhere (aircraft/base/installation)!\n");
626 }
627 return true;
628 }
629
630 /**
631 * @brief Reload the weapons of an aircraft
632 * @param[in,out] aircraft Pointer to the aircraft to reload
633 */
AII_ReloadAircraftWeapons(aircraft_t * aircraft)634 void AII_ReloadAircraftWeapons (aircraft_t* aircraft)
635 {
636 int i;
637
638 assert(aircraft);
639 /* Reload all ammos of aircraft */
640 for (i = 0; i < aircraft->maxWeapons; i++) {
641 AII_ReloadWeapon(&aircraft->weapons[i]);
642 }
643 }
644
645 /**
646 * @brief Add an ammo to an aircraft weapon slot
647 * @note No check for the _type_ of item is done here, so it must be done before.
648 * @param[in] base Pointer to the base which provides items (nullptr if items shouldn't be removed of storage)
649 * @param[in] tech Pointer to the tech to add to slot
650 * @param[in] slot Pointer to the slot where you want to add ammos
651 * @sa AII_AddItemToSlot
652 */
AII_AddAmmoToSlot(base_t * base,const technology_t * tech,aircraftSlot_t * slot)653 bool AII_AddAmmoToSlot (base_t* base, const technology_t* tech, aircraftSlot_t* slot)
654 {
655 const objDef_t* ammo;
656 const objDef_t* item;
657 int k;
658
659 if (slot == nullptr || slot->item == nullptr)
660 return false;
661
662 assert(tech);
663
664 ammo = INVSH_GetItemByID(tech->provides);
665 if (!ammo) {
666 Com_Printf("AII_AddAmmoToSlot: Could not add item (%s) to slot\n", tech->provides);
667 return false;
668 }
669
670 item = (slot->nextItem) ? slot->nextItem : slot->item;
671
672 /* Is the ammo is usable with the slot */
673 for (k = 0; k < item->numAmmos; k++) {
674 const objDef_t* usable = item->ammos[k];
675 if (usable && ammo->idx == usable->idx)
676 break;
677 }
678 if (k >= item->numAmmos)
679 return false;
680
681 /* the base pointer can be null here - e.g. in case you are equipping a UFO
682 * and base ammo defence are not stored in storage */
683 if (base && ammo->craftitem.type <= AC_ITEM_AMMO) {
684 if (!B_BaseHasItem(base, ammo)) {
685 Com_Printf("AII_AddAmmoToSlot: No more ammo of this type to equip (%s)\n", ammo->id);
686 return false;
687 }
688 }
689
690 /* remove any applied ammo in the current slot */
691 if (slot->nextItem) {
692 if (slot->nextAmmo)
693 AII_RemoveNextItemFromSlot(base, slot, true);
694 /* ammo couldn't be removed (maybe it's virtual) */
695 if (slot->nextAmmo)
696 return false;
697 slot->nextAmmo = ammo;
698 } else {
699 /* you shouldn't be able to have nextAmmo set if you don't have nextItem set */
700 assert(!slot->nextAmmo);
701 AII_RemoveItemFromSlot(base, slot, true);
702 /* ammo couldn't be removed (maybe it's virtual) */
703 if (slot->ammo)
704 return false;
705 slot->ammo = ammo;
706 }
707
708 /* proceed only if we are changing ammo of current weapon */
709 if (slot->nextItem) {
710 /* the base pointer can be null here - e.g. in case you are equipping a UFO */
711 if (base)
712 B_AddToStorage(base, ammo, -1);
713 return true;
714 }
715 AII_ReloadWeapon(slot);
716
717 return true;
718 }
719
720 /**
721 * @brief Add an item to an aircraft slot
722 * @param[in] base Pointer to the base where item will be removed (nullptr for ufos, virtual ammos or while loading game)
723 * @param[in] tech Pointer to the tech that will be added in this slot.
724 * @param[in] slot Pointer to the aircraft, base, or installation slot.
725 * @param[in] nextItem False if we are changing current item in slot, true if this is the item to install
726 * after current removal is over.
727 * @note No check for the _type_ of item is done here.
728 * @sa AII_UpdateOneInstallationDelay
729 * @sa AII_AddAmmoToSlot
730 */
AII_AddItemToSlot(base_t * base,const technology_t * tech,aircraftSlot_t * slot,bool nextItem)731 bool AII_AddItemToSlot (base_t* base, const technology_t* tech, aircraftSlot_t* slot, bool nextItem)
732 {
733 const objDef_t* item;
734
735 assert(slot);
736 assert(tech);
737
738 item = INVSH_GetItemByID(tech->provides);
739 if (!item) {
740 Com_Printf("AII_AddItemToSlot: Could not add item (%s) to slot\n", tech->provides);
741 return false;
742 }
743
744 #ifdef DEBUG
745 /* Sanity check : the type of the item cannot be an ammo */
746 /* note that this should never be reached because a slot type should never be an ammo
747 * , so the test just before should be wrong */
748 if (item->craftitem.type >= AC_ITEM_AMMO) {
749 Com_Printf("AII_AddItemToSlot: Type of the item to install (%s) should be a weapon, a shield, or electronics (no ammo)\n", item->id);
750 return false;
751 }
752 #endif
753
754 /* Sanity check : the type of the item should be the same than the slot type */
755 if (slot->type != item->craftitem.type) {
756 Com_Printf("AII_AddItemToSlot: Type of the item to install (%s -- %i) doesn't match type of the slot (%i)\n", item->id, item->craftitem.type, slot->type);
757 return false;
758 }
759
760 /* the base pointer can be null here - e.g. in case you are equipping a UFO */
761 if (base && !B_BaseHasItem(base, item)) {
762 Com_Printf("AII_AddItemToSlot: No more item of this type to equip (%s)\n", item->id);
763 return false;
764 }
765
766 if (slot->size >= AII_GetItemWeightBySize(item)) {
767 if (nextItem)
768 slot->nextItem = item;
769 else {
770 slot->item = item;
771 slot->installationTime = item->craftitem.installationTime;
772 }
773 /* the base pointer can be null here - e.g. in case you are equipping a UFO
774 * Remove item even for nextItem, this way we are sure we won't use the same item
775 * for another aircraft. */
776 if (base)
777 B_AddToStorage(base, item, -1);
778 } else {
779 Com_Printf("AII_AddItemToSlot: Could not add item '%s' to slot %i (slot-size: %i - item-weight: %i)\n",
780 item->id, slot->idx, slot->size, item->size);
781 return false;
782 }
783
784 return true;
785 }
786
787 /**
788 * @brief Auto Add weapon and ammo to an aircraft.
789 * @param[in] aircraft Pointer to the aircraft
790 * @note This is used to auto equip interceptor of first base.
791 * @sa B_SetUpBase
792 */
AIM_AutoEquipAircraft(aircraft_t * aircraft)793 void AIM_AutoEquipAircraft (aircraft_t* aircraft)
794 {
795 int i;
796 const objDef_t* item;
797 /** @todo Eliminate hardcoded techs here. */
798 const technology_t* tech = RS_GetTechByID("rs_craft_weapon_sparrowhawk");
799
800 if (!tech)
801 cgi->Com_Error(ERR_DROP, "Could not get tech rs_craft_weapon_sparrowhawk");
802
803 assert(aircraft);
804 assert(aircraft->homebase);
805
806 item = INVSH_GetItemByID(tech->provides);
807 if (!item)
808 return;
809
810 for (i = 0; i < aircraft->maxWeapons; i++) {
811 aircraftSlot_t* slot = &aircraft->weapons[i];
812 if (slot->size < AII_GetItemWeightBySize(item))
813 continue;
814 if (!B_BaseHasItem(aircraft->homebase, item))
815 continue;
816 AII_AddItemToSlot(aircraft->homebase, tech, slot, false);
817 AII_AutoAddAmmo(slot);
818 slot->installationTime = 0;
819 }
820
821 /* Fill slots too small for sparrowhawk with shiva */
822 tech = RS_GetTechByID("rs_craft_weapon_shiva");
823
824 if (!tech)
825 cgi->Com_Error(ERR_DROP, "Could not get tech rs_craft_weapon_shiva");
826
827 item = INVSH_GetItemByID(tech->provides);
828
829 if (!item)
830 return;
831
832 for (i = 0; i < aircraft->maxWeapons; i++) {
833 aircraftSlot_t* slot = &aircraft->weapons[i];
834 if (slot->size < AII_GetItemWeightBySize(item))
835 continue;
836 if (!B_BaseHasItem(aircraft->homebase, item))
837 continue;
838 if (slot->item)
839 continue;
840 AII_AddItemToSlot(aircraft->homebase, tech, slot, false);
841 AII_AutoAddAmmo(slot);
842 slot->installationTime = 0;
843 }
844
845 AII_UpdateAircraftStats(aircraft);
846 }
847
848 /**
849 * @brief Initialise values of one slot of an aircraft or basedefence common to all types of items.
850 * @param[in] slot Pointer to the slot to initialize.
851 * @param[in] aircraftTemplate Template Pointer to aircraft template.
852 * @param[in] base Pointer to base.
853 * @param[in] installation Pointer to the thing being installed.
854 * @param[in] type The type of item that can fit in this slot.
855 */
AII_InitialiseSlot(aircraftSlot_t * slot,aircraft_t * aircraftTemplate,base_t * base,installation_t * installation,aircraftItemType_t type)856 void AII_InitialiseSlot (aircraftSlot_t* slot, aircraft_t* aircraftTemplate, base_t* base, installation_t* installation, aircraftItemType_t type)
857 {
858 /* Only one of them is allowed. */
859 assert((!base && aircraftTemplate) || (base && !aircraftTemplate) || (installation && !aircraftTemplate));
860 /* Only one of them is allowed or neither. */
861 assert((!base && installation) || (base && !installation) || (!base && !installation));
862
863 OBJZERO(*slot);
864 slot->aircraft = aircraftTemplate;
865 slot->base = base;
866 slot->installation = installation;
867 slot->item = nullptr;
868 slot->ammo = nullptr;
869 slot->nextAmmo = nullptr;
870 slot->size = ITEM_HEAVY;
871 slot->nextItem = nullptr;
872 slot->type = type;
873 /** sa BDEF_AddBattery: it needs to be AMMO_STATUS_NOT_SET and not 0 @sa B_SaveBaseSlots */
874 slot->ammoLeft = AMMO_STATUS_NOT_SET;
875 slot->installationTime = 0;
876 }
877
878 /**
879 * @brief Check if item in given slot should change one aircraft stat.
880 * @param[in] slot Pointer to the slot containing the item
881 * @param[in] stat the stat that should be checked
882 * @return true if the item should change the stat.
883 */
AII_CheckUpdateAircraftStats(const aircraftSlot_t * slot,int stat)884 static bool AII_CheckUpdateAircraftStats (const aircraftSlot_t* slot, int stat)
885 {
886 assert(slot);
887
888 /* there's no item */
889 if (!slot->item)
890 return false;
891
892 /* you can not have advantages from items if it is being installed or removed, but only disavantages */
893 if (slot->installationTime != 0) {
894 const objDef_t* item = slot->item;
895 if (item->craftitem.stats[stat] > 1.0f) /* advantages for relative and absolute values */
896 return false;
897 }
898
899 return true;
900 }
901
902 /**
903 * @brief returns the aircraftSlot of a base at an index or the first free slot
904 * @param[in] base Pointer to base
905 * @param[in] type defence type, see aircraftItemType_t
906 * @param[in] idx index of aircraftslot
907 * @returns the aircraftSlot at the index idx if idx non-negative the first free slot otherwise
908 */
BDEF_GetBaseSlotByIDX(base_t * base,aircraftItemType_t type,int idx)909 aircraftSlot_t* BDEF_GetBaseSlotByIDX (base_t* base, aircraftItemType_t type, int idx)
910 {
911 switch (type) {
912 case AC_ITEM_BASE_MISSILE:
913 if (idx < 0) { /* returns the first free slot on negative */
914 int i;
915 for (i = 0; i < base->numBatteries; i++) {
916 aircraftSlot_t* slot = &base->batteries[i].slot;
917 if (!slot->item && !slot->nextItem)
918 return slot;
919 }
920 } else if (idx < base->numBatteries)
921 return &base->batteries[idx].slot;
922 break;
923 case AC_ITEM_BASE_LASER:
924 if (idx < 0) { /* returns the first free slot on negative */
925 int i;
926 for (i = 0; i < base->numLasers; i++) {
927 aircraftSlot_t* slot = &base->lasers[i].slot;
928 if (!slot->item && !slot->nextItem)
929 return slot;
930 }
931 } else if (idx < base->numLasers)
932 return &base->lasers[idx].slot;
933 break;
934 default:
935 break;
936 }
937 return nullptr;
938 }
939
940 /**
941 * @brief returns the aircraftSlot of an installaion at an index or the first free slot
942 * @param[in] installation Pointer to the installation
943 * @param[in] type defence type, see aircraftItemType_t
944 * @param[in] idx index of aircraftslot
945 * @returns the aircraftSlot at the index idx if idx non-negative the first free slot otherwise
946 */
BDEF_GetInstallationSlotByIDX(installation_t * installation,aircraftItemType_t type,int idx)947 aircraftSlot_t* BDEF_GetInstallationSlotByIDX (installation_t* installation, aircraftItemType_t type, int idx)
948 {
949 switch (type) {
950 case AC_ITEM_BASE_MISSILE:
951 if (idx < 0) { /* returns the first free slot on negative */
952 int i;
953 for (i = 0; i < installation->numBatteries; i++) {
954 aircraftSlot_t* slot = &installation->batteries[i].slot;
955 if (!slot->item && !slot->nextItem)
956 return slot;
957 }
958 } else if (idx < installation->numBatteries)
959 return &installation->batteries[idx].slot;
960 break;
961 default:
962 break;
963 }
964 return nullptr;
965 }
966
967 /**
968 * @brief returns the aircraftSlot of an aircraft at an index or the first free slot
969 * @param[in] aircraft Pointer to aircraft
970 * @param[in] type base defence type, see aircraftItemType_t
971 * @param[in] idx index of aircraftslot
972 * @returns the aircraftSlot at the index idx if idx non-negative the first free slot otherwise
973 */
AII_GetAircraftSlotByIDX(aircraft_t * aircraft,aircraftItemType_t type,int idx)974 aircraftSlot_t* AII_GetAircraftSlotByIDX (aircraft_t* aircraft, aircraftItemType_t type, int idx)
975 {
976 switch (type) {
977 case AC_ITEM_WEAPON:
978 if (idx < 0) { /* returns the first free slot on negative */
979 int i;
980 for (i = 0; i < aircraft->maxWeapons; i++) {
981 aircraftSlot_t* slot = &aircraft->weapons[i];
982 if (!slot->item && !slot->nextItem)
983 return slot;
984 }
985 } else if (idx < aircraft->maxWeapons)
986 return &aircraft->weapons[idx];
987 break;
988 case AC_ITEM_SHIELD:
989 if (idx == 0 || ((idx < 0) && !aircraft->shield.item && !aircraft->shield.nextItem)) /* returns the first free slot on negative */
990 return &aircraft->shield;
991 break;
992 case AC_ITEM_ELECTRONICS:
993 if (idx < 0) { /* returns the first free slot on negative */
994 int i;
995 for (i = 0; i < aircraft->maxElectronics; i++) {
996 aircraftSlot_t* slot = &aircraft->electronics[i];
997 if (!slot->item && !slot->nextItem)
998 return slot;
999 }
1000 } else if (idx < aircraft->maxElectronics)
1001 return &aircraft->electronics[idx];
1002 break;
1003 default:
1004 break;
1005 }
1006 return nullptr;
1007 }
1008
1009
1010 /**
1011 * @brief Get the maximum weapon range of aircraft.
1012 * @param[in] slot Pointer to the aircrafts weapon slot list.
1013 * @param[in] maxSlot maximum number of weapon slots in aircraft.
1014 * @return Maximum weapon range for this aircaft as an angle.
1015 */
AIR_GetMaxAircraftWeaponRange(const aircraftSlot_t * slot,int maxSlot)1016 float AIR_GetMaxAircraftWeaponRange (const aircraftSlot_t* slot, int maxSlot)
1017 {
1018 int i;
1019 float range = 0.0f;
1020
1021 assert(slot);
1022
1023 /* We choose the usable weapon with the biggest range */
1024 for (i = 0; i < maxSlot; i++) {
1025 const aircraftSlot_t* weapon = slot + i;
1026 const objDef_t* ammo = weapon->ammo;
1027
1028 if (!ammo)
1029 continue;
1030
1031 /* make sure this item is useable */
1032 if (!AII_CheckUpdateAircraftStats(weapon, AIR_STATS_WRANGE))
1033 continue;
1034
1035 /* select this weapon if this is the one with the longest range */
1036 if (ammo->craftitem.stats[AIR_STATS_WRANGE] > range) {
1037 range = ammo->craftitem.stats[AIR_STATS_WRANGE];
1038 }
1039 }
1040 return range;
1041 }
1042
1043 /**
1044 * @brief Repair aircraft.
1045 * @note Hourly called.
1046 * @note Repairs 1% of a craft's max damage capacity.
1047 */
AII_RepairAircraft(void)1048 void AII_RepairAircraft (void)
1049 {
1050 base_t* base = nullptr;
1051
1052 while ((base = B_GetNext(base)) != nullptr) {
1053 AIR_ForeachFromBase(aircraft, base) {
1054 if (!AIR_IsAircraftInBase(aircraft))
1055 continue;
1056 const int REPAIR_PER_HOUR = round(aircraft->stats[AIR_STATS_DAMAGE] / 100); /**< Number of damage points repaired per hour: 1% of aircraft max damage value */
1057 aircraft->damage = std::min(aircraft->damage + REPAIR_PER_HOUR, aircraft->stats[AIR_STATS_DAMAGE]);
1058 }
1059 }
1060 }
1061
1062 /**
1063 * @brief Update the value of stats array of an aircraft.
1064 * @param[in] aircraft Pointer to the aircraft
1065 * @note This should be called when an item starts to be added/removed and when addition/removal is over.
1066 */
AII_UpdateAircraftStats(aircraft_t * aircraft)1067 void AII_UpdateAircraftStats (aircraft_t* aircraft)
1068 {
1069 int i, currentStat;
1070 const aircraft_t* source;
1071
1072 assert(aircraft);
1073
1074 source = aircraft->tpl;
1075
1076 for (currentStat = 0; currentStat < AIR_STATS_MAX; currentStat++) {
1077 /* we scan all the stats except AIR_STATS_WRANGE (see below) */
1078 if (currentStat == AIR_STATS_WRANGE)
1079 continue;
1080
1081 /* initialise value */
1082 aircraft->stats[currentStat] = source->stats[currentStat];
1083
1084 /* modify by electronics (do nothing if the value of stat is 0) */
1085 for (i = 0; i < aircraft->maxElectronics; i++) {
1086 const aircraftSlot_t* slot = &aircraft->electronics[i];
1087 const objDef_t* item;
1088 if (!AII_CheckUpdateAircraftStats(slot, currentStat))
1089 continue;
1090 item = slot->item;
1091 if (fabs(item->craftitem.stats[currentStat]) > 2.0f)
1092 aircraft->stats[currentStat] += (int) item->craftitem.stats[currentStat];
1093 else if (!EQUAL(item->craftitem.stats[currentStat], 0))
1094 aircraft->stats[currentStat] *= item->craftitem.stats[currentStat];
1095 }
1096
1097 /* modify by weapons (do nothing if the value of stat is 0)
1098 * note that stats are not modified by ammos */
1099 for (i = 0; i < aircraft->maxWeapons; i++) {
1100 const aircraftSlot_t* slot = &aircraft->weapons[i];
1101 const objDef_t* item;
1102 if (!AII_CheckUpdateAircraftStats(slot, currentStat))
1103 continue;
1104 item = slot->item;
1105 if (fabs(item->craftitem.stats[currentStat]) > 2.0f)
1106 aircraft->stats[currentStat] += item->craftitem.stats[currentStat];
1107 else if (!EQUAL(item->craftitem.stats[currentStat], 0))
1108 aircraft->stats[currentStat] *= item->craftitem.stats[currentStat];
1109 }
1110
1111 /* modify by shield (do nothing if the value of stat is 0) */
1112 if (AII_CheckUpdateAircraftStats(&aircraft->shield, currentStat)) {
1113 const objDef_t* item = aircraft->shield.item;
1114 if (fabs(item->craftitem.stats[currentStat]) > 2.0f)
1115 aircraft->stats[currentStat] += item->craftitem.stats[currentStat];
1116 else if (!EQUAL(item->craftitem.stats[currentStat], 0))
1117 aircraft->stats[currentStat] *= item->craftitem.stats[currentStat];
1118 }
1119 }
1120
1121 /* now we update AIR_STATS_WRANGE (this one is the biggest range of every ammo) */
1122 aircraft->stats[AIR_STATS_WRANGE] = 1000.0f * AIR_GetMaxAircraftWeaponRange(aircraft->weapons, aircraft->maxWeapons);
1123
1124 /* check that aircraft hasn't too much fuel (caused by removal of fuel pod) */
1125 if (aircraft->fuel > aircraft->stats[AIR_STATS_FUELSIZE])
1126 aircraft->fuel = aircraft->stats[AIR_STATS_FUELSIZE];
1127
1128 /* check that aircraft hasn't too much HP (caused by removal of armour) */
1129 if (aircraft->damage > aircraft->stats[AIR_STATS_DAMAGE])
1130 aircraft->damage = aircraft->stats[AIR_STATS_DAMAGE];
1131
1132 /* check that speed of the aircraft is positive */
1133 if (aircraft->stats[AIR_STATS_SPEED] < 1)
1134 aircraft->stats[AIR_STATS_SPEED] = 1;
1135
1136 /* Update aircraft state if needed */
1137 if (aircraft->status == AIR_HOME && aircraft->fuel < aircraft->stats[AIR_STATS_FUELSIZE])
1138 aircraft->status = AIR_REFUEL;
1139 }
1140
1141 /**
1142 * @brief Check if base or installation weapon can shoot
1143 * @param[in] weapons Pointer to the weapon array of the base.
1144 * @param[in] numWeapons Pointer to the number of weapon in this base.
1145 * @return true if the base can fight, false else
1146 * @sa AII_BaseCanShoot
1147 */
AII_WeaponsCanShoot(const baseWeapon_t * weapons,int numWeapons)1148 static bool AII_WeaponsCanShoot (const baseWeapon_t* weapons, int numWeapons)
1149 {
1150 for (int i = 0; i < numWeapons; i++) {
1151 if (AIRFIGHT_CheckWeapon(&weapons[i].slot, 0) != AIRFIGHT_WEAPON_CAN_NEVER_SHOOT)
1152 return true;
1153 }
1154
1155 return false;
1156 }
1157
1158 /**
1159 * @brief Check if the base has weapon and ammo
1160 * @param[in] base Pointer to the base you want to check (may not be nullptr)
1161 * @return true if the base can shoot, qflase else
1162 * @sa AII_AircraftCanShoot
1163 */
AII_BaseCanShoot(const base_t * base)1164 int AII_BaseCanShoot (const base_t* base)
1165 {
1166 assert(base);
1167
1168 /* If we can shoot with missile defences */
1169 if (B_GetBuildingStatus(base, B_DEFENCE_MISSILE)
1170 && AII_WeaponsCanShoot(base->batteries, base->numBatteries))
1171 return true;
1172
1173 /* If we can shoot with beam defences */
1174 if (B_GetBuildingStatus(base, B_DEFENCE_LASER)
1175 && AII_WeaponsCanShoot(base->lasers, base->numLasers))
1176 return true;
1177
1178 return false;
1179 }
1180
1181 /**
1182 * @brief Check if the installation has a weapon and ammo
1183 * @param[in] installation Pointer to the installation you want to check (may not be nullptr)
1184 * @return true if the installation can shoot, qflase else
1185 * @sa AII_AircraftCanShoot
1186 */
AII_InstallationCanShoot(const installation_t * installation)1187 bool AII_InstallationCanShoot (const installation_t* installation)
1188 {
1189 assert(installation);
1190
1191 if (installation->installationStatus == INSTALLATION_WORKING
1192 && installation->installationTemplate->maxBatteries > 0) {
1193 /* installation is working and has battery */
1194 return AII_WeaponsCanShoot(installation->batteries, installation->installationTemplate->maxBatteries);
1195 }
1196
1197 return false;
1198 }
1199
1200 /**
1201 * @brief Chooses a target for surface to air defences automatically
1202 * @param[in,out] weapons Weapons array
1203 * @param[in] maxWeapons Number of weapons
1204 */
BDEF_AutoTarget(baseWeapon_t * weapons,int maxWeapons)1205 static void BDEF_AutoTarget (baseWeapon_t* weapons, int maxWeapons)
1206 {
1207 const installation_t* inst;
1208 const base_t* base;
1209 aircraft_t* closestCraft = nullptr;
1210 float minCraftDistance = -1;
1211 aircraft_t* closestAttacker = nullptr;
1212 float minAttackerDistance = -1;
1213 const aircraftSlot_t* slot;
1214 int i;
1215 aircraft_t* ufo;
1216
1217 if (maxWeapons <= 0)
1218 return;
1219
1220 slot = &weapons[0].slot;
1221 /* Check if it's a Base or an Installation */
1222 if (slot->installation) {
1223 inst = slot->installation;
1224 base = nullptr;
1225 } else if (slot->base) {
1226 base = slot->base;
1227 inst = nullptr;
1228 } else
1229 cgi->Com_Error(ERR_DROP, "BDEF_AutoSelectTarget: slot doesn't belong to any base or installation");
1230
1231 /* Get closest UFO(s) */
1232 ufo = nullptr;
1233 while ((ufo = UFO_GetNextOnGeoscape(ufo)) != nullptr) {
1234 const float distance = GetDistanceOnGlobe(inst ? inst->pos : base->pos, ufo->pos);
1235 if (minCraftDistance < 0 || minCraftDistance > distance) {
1236 minCraftDistance = distance;
1237 closestCraft = ufo;
1238 }
1239 if ((minAttackerDistance < 0 || minAttackerDistance > distance) && ufo->mission
1240 && ((base && ufo->mission->category == INTERESTCATEGORY_BASE_ATTACK && ufo->mission->data.base == base)
1241 || (inst && ufo->mission->category == INTERESTCATEGORY_INTERCEPT && ufo->mission->data.installation == inst))) {
1242 minAttackerDistance = distance;
1243 closestAttacker = ufo;
1244 }
1245 }
1246
1247 /* Loop weaponslots */
1248 for (i = 0; i < maxWeapons; i++) {
1249 baseWeapon_t* weapon = &weapons[i];
1250 slot = &weapon->slot;
1251 /* skip if autofire is disabled */
1252 if (!weapon->autofire)
1253 continue;
1254 /* skip if no weapon or ammo assigned */
1255 if (!slot->item || !slot->ammo)
1256 continue;
1257 /* skip if weapon installation not yet finished */
1258 if (slot->installationTime > 0)
1259 continue;
1260 /* skip if no more ammo left */
1261 /** @note it's not really needed but it's cheaper not to check ufos in this case */
1262 if (slot->ammoLeft <= 0)
1263 continue;
1264
1265 if (closestAttacker) {
1266 const int test = AIRFIGHT_CheckWeapon(slot, minAttackerDistance);
1267 if (test != AIRFIGHT_WEAPON_CAN_NEVER_SHOOT
1268 && test != AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT
1269 && (minAttackerDistance <= slot->ammo->craftitem.stats[AIR_STATS_WRANGE]))
1270 weapon->target = closestAttacker;
1271 } else if (closestCraft) {
1272 const int test = AIRFIGHT_CheckWeapon(slot, minCraftDistance);
1273 if (test != AIRFIGHT_WEAPON_CAN_NEVER_SHOOT
1274 && test != AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT
1275 && (minCraftDistance <= slot->ammo->craftitem.stats[AIR_STATS_WRANGE]))
1276 weapon->target = closestCraft;
1277 }
1278 }
1279 }
1280
1281 /**
1282 * @brief Chooses target for all base defences and sam sites
1283 */
BDEF_AutoSelectTarget(void)1284 void BDEF_AutoSelectTarget (void)
1285 {
1286 base_t* base;
1287
1288 base = nullptr;
1289 while ((base = B_GetNext(base)) != nullptr) {
1290 BDEF_AutoTarget(base->batteries, base->numBatteries);
1291 BDEF_AutoTarget(base->lasers, base->numLasers);
1292 }
1293
1294 INS_Foreach(inst)
1295 BDEF_AutoTarget(inst->batteries, inst->numBatteries);
1296 }
1297
1298 /**
1299 * @brief Translate a weight int to a translated string
1300 * @sa itemWeight_t
1301 * @sa AII_GetItemWeightBySize
1302 */
AII_WeightToName(itemWeight_t weight)1303 const char* AII_WeightToName (itemWeight_t weight)
1304 {
1305 switch (weight) {
1306 case ITEM_LIGHT:
1307 return _("Light weight");
1308 break;
1309 case ITEM_MEDIUM:
1310 return _("Medium weight");
1311 break;
1312 case ITEM_HEAVY:
1313 return _("Heavy weight");
1314 break;
1315 default:
1316 return _("Unknown weight");
1317 break;
1318 }
1319 }
1320
1321 /**
1322 * @brief Save callback for savegames in XML Format
1323 * @param[out] p XML Node structure, where we write the information to
1324 * @param[in] slot The aircraftslot to save
1325 * @param[in] weapon True if this is a weapon slot
1326 */
AII_SaveOneSlotXML(xmlNode_t * p,const aircraftSlot_t * slot,bool weapon)1327 void AII_SaveOneSlotXML (xmlNode_t* p, const aircraftSlot_t* slot, bool weapon)
1328 {
1329 cgi->XML_AddStringValue(p, SAVE_SLOT_ITEMID, slot->item ? slot->item->id : "");
1330 cgi->XML_AddStringValue(p, SAVE_SLOT_NEXTITEMID, slot->nextItem ? slot->nextItem->id : "");
1331 cgi->XML_AddIntValue(p, SAVE_SLOT_INSTALLATIONTIME, slot->installationTime);
1332
1333 /* everything below is only for weapon */
1334 if (!weapon)
1335 return;
1336
1337 cgi->XML_AddIntValue(p, SAVE_SLOT_AMMOLEFT, slot->ammoLeft);
1338 cgi->XML_AddStringValue(p, SAVE_SLOT_AMMOID, slot->ammo ? slot->ammo->id : "");
1339 cgi->XML_AddStringValue(p, SAVE_SLOT_NEXTAMMOID, slot->nextAmmo ? slot->nextAmmo->id : "");
1340 cgi->XML_AddIntValue(p, SAVE_SLOT_DELAYNEXTSHOT, slot->delayNextShot);
1341 }
1342
1343 /**
1344 * @brief Loads one slot (base, installation or aircraft)
1345 * @param[in] node XML Node structure, where we get the information from
1346 * @param[out] slot Pointer to the slot where item should be added.
1347 * @param[in] weapon True if the slot is a weapon slot.
1348 * @sa B_Load
1349 * @sa B_SaveAircraftSlots
1350 */
AII_LoadOneSlotXML(xmlNode_t * node,aircraftSlot_t * slot,bool weapon)1351 void AII_LoadOneSlotXML (xmlNode_t* node, aircraftSlot_t* slot, bool weapon)
1352 {
1353 const char* name;
1354 name = cgi->XML_GetString(node, SAVE_SLOT_ITEMID);
1355 if (name[0] != '\0') {
1356 const technology_t* tech = RS_GetTechByProvided(name);
1357 /* base is nullptr here to not check against the storage amounts - they
1358 * are already loaded in the campaign load function and set to the value
1359 * after the craftitem was already removed from the initial game - thus
1360 * there might not be any of these items in the storage at this point.
1361 * Furthermore, they have already be taken from storage during game. */
1362 if (tech)
1363 AII_AddItemToSlot(nullptr, tech, slot, false);
1364 }
1365
1366 /* item to install after current one is removed */
1367 name = cgi->XML_GetString(node, SAVE_SLOT_NEXTITEMID);
1368 if (name && name[0] != '\0') {
1369 const technology_t* tech = RS_GetTechByProvided(name);
1370 if (tech)
1371 AII_AddItemToSlot(nullptr, tech, slot, true);
1372 }
1373
1374 slot->installationTime = cgi->XML_GetInt(node, SAVE_SLOT_INSTALLATIONTIME, 0);
1375
1376 /* everything below is weapon specific */
1377 if (!weapon)
1378 return;
1379
1380 /* current ammo */
1381 /* load ammoLeft before adding ammo to avoid unnecessary auto-reloading */
1382 slot->ammoLeft = cgi->XML_GetInt(node, SAVE_SLOT_AMMOLEFT, 0);
1383 name = cgi->XML_GetString(node, SAVE_SLOT_AMMOID);
1384 if (name && name[0] != '\0') {
1385 const technology_t* tech = RS_GetTechByProvided(name);
1386 /* next Item must not be loaded yet in order to install ammo properly */
1387 if (tech)
1388 AII_AddAmmoToSlot(nullptr, tech, slot);
1389 }
1390 /* ammo to install after current one is removed */
1391 name = cgi->XML_GetString(node, SAVE_SLOT_NEXTAMMOID);
1392 if (name && name[0] != '\0') {
1393 const technology_t* tech = RS_GetTechByProvided(name);
1394 if (tech)
1395 AII_AddAmmoToSlot(nullptr, tech, slot);
1396 }
1397 slot->delayNextShot = cgi->XML_GetInt(node, SAVE_SLOT_DELAYNEXTSHOT, 0);
1398 }
1399