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