1 /**
2  * @file
3  * @brief Most of the aircraft related stuff.
4  * @sa cl_airfight.c
5  * @note Aircraft management functions prefix: AIR_
6  * @note Aircraft menu(s) functions prefix: AIM_
7  * @note Aircraft equipment handling functions prefix: AII_
8  */
9 
10 /*
11 Copyright (C) 2002-2013 UFO: Alien Invasion.
12 
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License
15 as published by the Free Software Foundation; either version 2
16 of the License, or (at your option) any later version.
17 
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 
22 See the GNU General Public License for more details.
23 
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27 */
28 
29 #include "../../cl_shared.h"
30 #include "../../ui/ui_dataids.h"
31 #include "../../../shared/parse.h"
32 #include "cp_campaign.h"
33 #include "cp_mapfightequip.h"
34 #include "cp_geoscape.h"
35 #include "cp_ufo.h"
36 #include "cp_alienbase.h"
37 #include "cp_time.h"
38 #include "cp_missions.h"
39 #include "cp_aircraft_callbacks.h"
40 #include "save/save_aircraft.h"
41 #include "cp_popup.h"
42 #include "aliencargo.h"
43 
44 /**
45  * @brief Iterates through the aircraft of a base
46  * @param[in] b The base to get the craft from
47  */
AIR_GetFirstFromBase(const base_t * b)48 aircraft_t* AIR_GetFirstFromBase (const base_t* b)
49 {
50 	if (b) {
51 		AIR_ForeachFromBase(aircraft, b)
52 			return aircraft;
53 	}
54 
55 	return nullptr;
56 }
57 
58 /**
59  * @brief Checks whether there is any aircraft assigned to the given base
60  * @param[in] base The base to check
61  * @return @c true if there is at least one aircraft, @c false otherwise (or when the @c base pointer is @c nullptr)
62  */
AIR_BaseHasAircraft(const base_t * base)63 bool AIR_BaseHasAircraft (const base_t* base)
64 {
65 	return base != nullptr && AIR_GetFirstFromBase(base) != nullptr;
66 }
67 
68 /**
69  * @brief Returns the number of aircraft on the given base
70  * @param[in] base The base to check
71  */
AIR_BaseCountAircraft(const base_t * base)72 int AIR_BaseCountAircraft (const base_t* base)
73 {
74 	int count = 0;
75 
76 	AIR_ForeachFromBase(aircraft, base) {
77 		count++;
78 	}
79 
80 	return count;
81 }
82 
83 #ifdef DEBUG
84 /**
85  * @brief Debug function which lists all aircraft in all bases.
86  * @note Use with baseIdx as a parameter to display all aircraft in given base.
87  * @note called with debug_listaircraft
88  */
AIR_ListAircraft_f(void)89 void AIR_ListAircraft_f (void)
90 {
91 	base_t* base = nullptr;
92 
93 	if (cgi->Cmd_Argc() == 2) {
94 		int baseIdx = atoi(cgi->Cmd_Argv(1));
95 		base = B_GetFoundedBaseByIDX(baseIdx);
96 	}
97 
98 	AIR_Foreach(aircraft) {
99 		int k;
100 
101 		if (base && aircraft->homebase != base)
102 			continue;
103 
104 		Com_Printf("Aircraft %s\n", aircraft->name);
105 		Com_Printf("...idx global %i\n", aircraft->idx);
106 		Com_Printf("...homebase: %s\n", aircraft->homebase ? aircraft->homebase->name : "NO HOMEBASE");
107 		for (k = 0; k < aircraft->maxWeapons; k++) {
108 			aircraftSlot_t* slot = &aircraft->weapons[k];
109 			if (slot->item) {
110 				Com_Printf("...weapon slot %i contains %s", k, slot->item->id);
111 
112 				if (!slot->installationTime) {
113 					Com_Printf(" (functional)\n");
114 				} else if (slot->installationTime > 0) {
115 					Com_Printf(" (%i hours before installation is finished)\n", slot->installationTime);
116 				} else {
117 					Com_Printf(" (%i hours before removing is finished)\n", slot->installationTime);
118 				}
119 
120 				if (slot->ammo) {
121 					if (slot->ammoLeft > 1) {
122 						Com_Printf("......this weapon is loaded with ammo %s\n", slot->ammo->id);
123 					} else {
124 						Com_Printf("......no more ammo (%s)\n", slot->ammo->id);
125 					}
126 				} else {
127 					Com_Printf("......this weapon isn't loaded with ammo\n");
128 				}
129 			} else {
130 				Com_Printf("...weapon slot %i is empty\n", k);
131 			}
132 		}
133 
134 		if (aircraft->shield.item) {
135 			Com_Printf("...armour slot contains %s", aircraft->shield.item->id);
136 			if (!aircraft->shield.installationTime) {
137 				Com_Printf(" (functional)\n");
138 			} else if (aircraft->shield.installationTime > 0) {
139 				Com_Printf(" (%i hours before installation is finished)\n", aircraft->shield.installationTime);
140 			} else {
141 				Com_Printf(" (%i hours before removing is finished)\n", aircraft->shield.installationTime);
142 			}
143 		} else {
144 			Com_Printf("...armour slot is empty\n");
145 		}
146 
147 		for (k = 0; k < aircraft->maxElectronics; k++) {
148 			aircraftSlot_t* slot = &aircraft->weapons[k];
149 			if (slot->item) {
150 				Com_Printf("...electronics slot %i contains %s", k, slot->item->id);
151 
152 				if (!slot->installationTime) {
153 					Com_Printf(" (functional)\n");
154 				} else if (slot->installationTime > 0) {
155 					Com_Printf(" (%i hours before installation is finished)\n", slot->installationTime);
156 				} else {
157 					Com_Printf(" (%i hours before removing is finished)\n", slot->installationTime);
158 				}
159 			} else {
160 				Com_Printf("...electronics slot %i is empty\n", k);
161 			}
162 		}
163 
164 		if (aircraft->pilot) {
165 			character_t* chr = &aircraft->pilot->chr;
166 			Com_Printf("...pilot: ucn: %i name: %s\n", chr->ucn, chr->name);
167 		} else {
168 			Com_Printf("...no pilot assigned\n");
169 		}
170 
171 		Com_Printf("...damage: %i\n", aircraft->damage);
172 		Com_Printf("...stats: ");
173 		for (k = 0; k < AIR_STATS_MAX; k++) {
174 			if (k == AIR_STATS_WRANGE) {
175 				Com_Printf("%.2f ", aircraft->stats[k] / 1000.0f);
176 			} else {
177 				Com_Printf("%i ", aircraft->stats[k]);
178 			}
179 		}
180 		Com_Printf("\n");
181 		Com_Printf("...name %s\n", aircraft->id);
182 		Com_Printf("...type %i\n", aircraft->type);
183 		Com_Printf("...size %i\n", aircraft->maxTeamSize);
184 		Com_Printf("...fuel %i\n", aircraft->fuel);
185 		Com_Printf("...status %s\n", (aircraft->status == AIR_CRASHED) ? "crashed" : AIR_AircraftStatusToName(aircraft));
186 		Com_Printf("...pos %.0f:%.0f\n", aircraft->pos[0], aircraft->pos[1]);
187 		Com_Printf("...team: (%i/%i)\n", cgi->LIST_Count(aircraft->acTeam), aircraft->maxTeamSize);
188 		LIST_Foreach(aircraft->acTeam, Employee, employee) {
189 			character_t* chr = &employee->chr;
190 			Com_Printf(".........name: %s (ucn: %i)\n", chr->name, chr->ucn);
191 		}
192 
193 		if (aircraft->alienCargo) {
194 			Com_Printf("...alienCargo:\n");
195 			linkedList_t* cargo = aircraft->alienCargo->list();
196 			LIST_Foreach(cargo, alienCargo_t, item) {
197 				Com_Printf("......team: %s alive: %d dead: %d\n", item->teamDef->id, item->alive, item->dead);
198 			}
199 			cgi->LIST_Delete(&cargo);
200 		}
201 	}
202 }
203 #endif
204 
205 static equipDef_t eTempEq;		/**< Used to count ammo in magazines. */
206 
207 /**
208  * @brief Count and collect ammo from gun magazine.
209  * @param[in] data Pointer to aircraft used in this mission.
210  * @param[in] magazine Pointer to Item being magazine.
211  */
AII_CollectingAmmo(void * data,const Item * magazine)212 static void AII_CollectingAmmo (void* data, const Item* magazine)
213 {
214 	aircraft_t* aircraft = (aircraft_t*)data;
215 	/* Let's add remaining ammo to market. */
216 	eTempEq.numItemsLoose[magazine->ammoDef()->idx] += magazine->getAmmoLeft();
217 	if (eTempEq.numItemsLoose[magazine->ammoDef()->idx] >= magazine->def()->ammo) {
218 		/* There are more or equal ammo on the market than magazine needs - collect magazine. */
219 		eTempEq.numItemsLoose[magazine->ammoDef()->idx] -= magazine->def()->ammo;
220 		AII_CollectItem(aircraft, magazine->ammoDef(), 1);
221 	}
222 }
223 
224 /**
225  * @brief Add an item to aircraft inventory.
226  * @param[in,out] aircraft Aircraft to load to
227  * @param[in] item Item to add
228  * @param amount Number of items to add
229  * @sa AL_AddAliens
230  * @sa AII_CollectingItems
231  */
AII_CollectItem(aircraft_t * aircraft,const objDef_t * item,int amount)232 void AII_CollectItem (aircraft_t* aircraft, const objDef_t* item, int amount)
233 {
234 	int i;
235 	itemsTmp_t* cargo = aircraft->itemcargo;
236 
237 	for (i = 0; i < aircraft->itemTypes; i++) {
238 		if (cargo[i].item == item) {
239 			Com_DPrintf(DEBUG_CLIENT, "AII_CollectItem: collecting %s (%i) amount %i -> %i\n", item->name, item->idx, cargo[i].amount, cargo[i].amount + amount);
240 			cargo[i].amount += amount;
241 			return;
242 		}
243 	}
244 
245 	if (aircraft->itemTypes >= MAX_CARGO) {
246 		Com_Printf("AII_CollectItem: Cannot add item to cargobay it's full!\n");
247 		return;
248 	}
249 
250 	Com_DPrintf(DEBUG_CLIENT, "AII_CollectItem: adding %s (%i) amount %i\n", item->name, item->idx, amount);
251 	cargo[aircraft->itemTypes].item = item;
252 	cargo[aircraft->itemTypes].amount = amount;
253 	aircraft->itemTypes++;
254 }
255 
AII_CollectItem_(void * data,const objDef_t * item,int amount)256 static inline void AII_CollectItem_ (void* data, const objDef_t* item, int amount)
257 {
258 	AII_CollectItem((aircraft_t*)data, item, amount);
259 }
260 
261 /**
262  * @brief Process items carried by soldiers.
263  * @param[in] soldierInventory Pointer to inventory from a soldier of our team.
264  * @sa AII_CollectingItems
265  */
AII_CarriedItems(const Inventory * soldierInventory)266 static void AII_CarriedItems (const Inventory* soldierInventory)
267 {
268 	Item* item;
269 	equipDef_t* ed = &ccs.eMission;
270 
271 	const Container* cont = nullptr;
272 	while ((cont = soldierInventory->getNextCont(cont))) {
273 		/* Items on the ground are collected as ET_ITEM */
274 		for (item = cont->_invList; item; item = item->getNext()) {
275 			const objDef_t* itemType = item->def();
276 			technology_t* tech = RS_GetTechForItem(itemType);
277 			ed->numItems[itemType->idx]++;
278 			RS_MarkCollected(tech);
279 
280 			if (!itemType->isReloadable() || item->getAmmoLeft() == 0)
281 				continue;
282 
283 			ed->addClip(item);
284 			/* The guys keep their weapons (half-)loaded. Auto-reload
285 			 * will happen at equip screen or at the start of next mission,
286 			 * but fully loaded weapons will not be reloaded even then. */
287 		}
288 	}
289 }
290 
291 /**
292  * @brief Collect items from the battlefield.
293  * @note The way we are collecting items:
294  * @note 1. Grab everything from the floor and add it to the aircraft cargo here.
295  * @note 2. When collecting gun, collect it's remaining ammo as well
296  * @note 3. Sell everything or add to base storage when dropship lands in base.
297  * @note 4. Items carried by soldiers are processed nothing is being sold.
298  * @sa CL_ParseResults
299  * @sa AII_CollectingAmmo
300  * @sa AII_CarriedItems
301  */
AII_CollectingItems(aircraft_t * aircraft,int won)302 void AII_CollectingItems (aircraft_t* aircraft, int won)
303 {
304 	int i, j;
305 	itemsTmp_t* cargo;
306 	itemsTmp_t prevItemCargo[MAX_CARGO];
307 	int prevItemTypes;
308 
309 	/* Save previous cargo */
310 	memcpy(prevItemCargo, aircraft->itemcargo, sizeof(aircraft->itemcargo));
311 	prevItemTypes = aircraft->itemTypes;
312 	/* Make sure itemcargo is empty. */
313 	OBJZERO(aircraft->itemcargo);
314 
315 	/* Make sure eTempEq is empty as well. */
316 	OBJZERO(eTempEq);
317 
318 	cargo = aircraft->itemcargo;
319 	aircraft->itemTypes = 0;
320 
321 	cgi->CollectItems(aircraft, won, AII_CollectItem_, AII_CollectingAmmo, AII_CarriedItems);
322 
323 	/* Fill the missionResults array. */
324 	ccs.missionResults.itemTypes = aircraft->itemTypes;
325 	for (i = 0; i < aircraft->itemTypes; i++)
326 		ccs.missionResults.itemAmount += cargo[i].amount;
327 
328 #ifdef DEBUG
329 	/* Print all of collected items. */
330 	for (i = 0; i < aircraft->itemTypes; i++) {
331 		if (cargo[i].amount > 0)
332 			Com_DPrintf(DEBUG_CLIENT, "Collected items: idx: %i name: %s amount: %i\n", cargo[i].item->idx, cargo[i].item->name, cargo[i].amount);
333 	}
334 #endif
335 
336 	/* Put previous cargo back */
337 	for (i = 0; i < prevItemTypes; i++) {
338 		for (j = 0; j < aircraft->itemTypes; j++) {
339 			if (cargo[j].item == prevItemCargo[i].item) {
340 				cargo[j].amount += prevItemCargo[i].amount;
341 				break;
342 			}
343 		}
344 		if (j == aircraft->itemTypes) {
345 			cargo[j] = prevItemCargo[i];
346 			aircraft->itemTypes++;
347 		}
348 	}
349 }
350 
351 /**
352  * @brief Translates the aircraft status id to a translatable string
353  * @param[in] aircraft Aircraft to translate the status of
354  * @return Translation string of given status.
355  */
AIR_AircraftStatusToName(const aircraft_t * aircraft)356 const char* AIR_AircraftStatusToName (const aircraft_t* aircraft)
357 {
358 	assert(aircraft);
359 	assert(aircraft->homebase);
360 
361 	/* display special status if base-attack affects aircraft */
362 	if (B_IsUnderAttack(aircraft->homebase) && AIR_IsAircraftInBase(aircraft))
363 		return _("ON RED ALERT");
364 
365 	switch (aircraft->status) {
366 	case AIR_NONE:
367 		return _("Nothing - should not be displayed");
368 	case AIR_HOME:
369 		return _("at home base");
370 	case AIR_REFUEL:
371 		return _("refuelling");
372 	case AIR_IDLE:
373 		return _("idle");
374 	case AIR_TRANSIT:
375 		return _("in transit");
376 	case AIR_MISSION:
377 		return _("enroute to mission");
378 	case AIR_UFO:
379 		return _("pursuing a UFO");
380 	case AIR_DROP:
381 		return _("ready to drop soldiers");
382 	case AIR_INTERCEPT:
383 		return _("intercepting a UFO");
384 	case AIR_TRANSFER:
385 		return _("enroute to new home base");
386 	case AIR_RETURNING:
387 		return _("returning to base");
388 	case AIR_CRASHED:
389 		cgi->Com_Error(ERR_DROP, "AIR_CRASHED should not be visible anywhere");
390 	}
391 	return nullptr;
392 }
393 
394 /**
395  * @brief Checks whether given aircraft is in its homebase.
396  * @param[in] aircraft Pointer to an aircraft.
397  * @return true if given aircraft is in its homebase.
398  * @return false if given aircraft is not in its homebase.
399  */
AIR_IsAircraftInBase(const aircraft_t * aircraft)400 bool AIR_IsAircraftInBase (const aircraft_t* aircraft)
401 {
402 	if (aircraft->status == AIR_HOME || aircraft->status == AIR_REFUEL)
403 		return true;
404 	return false;
405 }
406 
407 /**
408  * @brief Checks whether given aircraft is on geoscape.
409  * @param[in] aircraft Pointer to an aircraft.
410  * @note aircraft may be neither on geoscape, nor in base (like when it's transferred)
411  * @return @c false if given aircraft is not on geoscape, @c true if given aircraft is on geoscape.
412  * @sa UFO_IsUFOSeenOnGeoscape
413  */
AIR_IsAircraftOnGeoscape(const aircraft_t * aircraft)414 bool AIR_IsAircraftOnGeoscape (const aircraft_t* aircraft)
415 {
416 	switch (aircraft->status) {
417 	case AIR_IDLE:
418 	case AIR_TRANSIT:
419 	case AIR_MISSION:
420 	case AIR_UFO:
421 	case AIR_DROP:
422 	case AIR_INTERCEPT:
423 	case AIR_RETURNING:
424 		return true;
425 	case AIR_NONE:
426 	case AIR_REFUEL:
427 	case AIR_HOME:
428 	case AIR_TRANSFER:
429 	case AIR_CRASHED:
430 		return false;
431 	}
432 
433 	cgi->Com_Error(ERR_FATAL, "Unknown aircraft status %i", aircraft->status);
434 }
435 
436 /**
437  * @brief Calculates the amount of aircraft (of the given type) in the selected base
438  * @param[in] base The base to count the aircraft in
439  * @param[in] aircraftType The type of the aircraft you want
440  * @note that this type is just transporter/interceptor/ufo category
441  */
AIR_CountTypeInBase(const base_t * base,aircraftType_t aircraftType)442 int AIR_CountTypeInBase (const base_t* base, aircraftType_t aircraftType)
443 {
444 	int count = 0;
445 
446 	AIR_ForeachFromBase(aircraft, base) {
447 		if (aircraft->type == aircraftType)
448 			count++;
449 	}
450 	return count;
451 }
452 
453 /**
454  * @brief Calculates the amount of aircraft (of the given type) in the selected base
455  * @param[in] base The base to count the aircraft in
456  * @param[in] aircraftTemplate The type of the aircraft you want
457  */
AIR_CountInBaseByTemplate(const base_t * base,const aircraft_t * aircraftTemplate)458 int AIR_CountInBaseByTemplate (const base_t* base, const aircraft_t* aircraftTemplate)
459 {
460 	int count = 0;
461 
462 	AIR_ForeachFromBase(aircraft, base) {
463 		if (aircraft->tpl == aircraftTemplate)
464 			count++;
465 	}
466 	return count;
467 }
468 
469 /**
470  * @brief Returns the string that matches the given aircraft type
471  */
AIR_GetAircraftString(aircraftType_t aircraftType)472 const char* AIR_GetAircraftString (aircraftType_t aircraftType)
473 {
474 	switch (aircraftType) {
475 	case AIRCRAFT_INTERCEPTOR:
476 		return _("Interceptor");
477 	case AIRCRAFT_TRANSPORTER:
478 		return _("Transporter");
479 	case AIRCRAFT_UFO:
480 		return _("UFO");
481 	}
482 	return "";
483 }
484 
485 /**
486  * @brief Some of the aircraft values needs special calculations when they
487  * are shown in the menus
488  * @sa UP_AircraftStatToName
489  */
AIR_AircraftMenuStatsValues(const int value,const int stat)490 int AIR_AircraftMenuStatsValues (const int value, const int stat)
491 {
492 	switch (stat) {
493 	case AIR_STATS_SPEED:
494 	case AIR_STATS_MAXSPEED:
495 		/* Convert into km/h, and round this value */
496 		return 10 * (int) (111.2 * value / 10.0f);
497 	case AIR_STATS_FUELSIZE:
498 		return value / 1000;
499 	default:
500 		return value;
501 	}
502 }
503 
504 /**
505  * @brief Calculates the range an aircraft can fly on the geoscape
506  * @param aircraft The aircraft to calculate the range for
507  * @return The range
508  */
AIR_GetOperationRange(const aircraft_t * aircraft)509 int AIR_GetOperationRange (const aircraft_t* aircraft)
510 {
511 	const int range = aircraft->stats[AIR_STATS_SPEED] * aircraft->stats[AIR_STATS_FUELSIZE];
512 	/* the 2.0f factor is for going to destination and then come back */
513 	return 100 * (int) (KILOMETER_PER_DEGREE * range / (2.0f * (float)SECONDS_PER_HOUR * 100.0f));
514 }
515 
516 /**
517  * @brief Calculates the remaining range the aircraft can fly
518  * @param aircraft The aircraft to calculate the remaining range for
519  * @return The remaining range
520  */
AIR_GetRemainingRange(const aircraft_t * aircraft)521 int AIR_GetRemainingRange (const aircraft_t* aircraft)
522 {
523 	return aircraft->stats[AIR_STATS_SPEED] * aircraft->fuel;
524 }
525 
526 /**
527  * @brief check if aircraft has enough fuel to go to destination, and then come back home
528  * @param[in] aircraft Pointer to the aircraft
529  * @param[in] destination Pointer to the position the aircraft should go to
530  * @sa GEO_CalcLine
531  * @return true if the aircraft can go to the position, false else
532  */
AIR_AircraftHasEnoughFuel(const aircraft_t * aircraft,const vec2_t destination)533 bool AIR_AircraftHasEnoughFuel (const aircraft_t* aircraft, const vec2_t destination)
534 {
535 	const base_t* base;
536 	float distance;
537 
538 	assert(aircraft);
539 	base = aircraft->homebase;
540 	assert(base);
541 
542 	/* Calculate the line that the aircraft should follow to go to destination */
543 	distance = GetDistanceOnGlobe(aircraft->pos, destination);
544 
545 	/* Calculate the line that the aircraft should then follow to go back home */
546 	distance += GetDistanceOnGlobe(destination, base->pos);
547 
548 	/* Check if the aircraft has enough fuel to go to destination and then go back home */
549 	return (distance <= AIR_GetRemainingRange(aircraft) / (float)SECONDS_PER_HOUR);
550 }
551 
552 /**
553  * @brief check if aircraft has enough fuel to go to destination
554  * @param[in] aircraft Pointer to the aircraft
555  * @param[in] destination Pointer to the position the aircraft should go to
556  * @sa GEO_CalcLine
557  * @return true if the aircraft can go to the position, false else
558  */
AIR_AircraftHasEnoughFuelOneWay(const aircraft_t * aircraft,const vec2_t destination)559 bool AIR_AircraftHasEnoughFuelOneWay (const aircraft_t* aircraft, const vec2_t destination)
560 {
561 	float distance;
562 
563 	assert(aircraft);
564 
565 	/* Calculate the line that the aircraft should follow to go to destination */
566 	distance = GetDistanceOnGlobe(aircraft->pos, destination);
567 
568 	/* Check if the aircraft has enough fuel to go to destination */
569 	return (distance <= AIR_GetRemainingRange(aircraft) / (float)SECONDS_PER_HOUR);
570 }
571 
572 /**
573  * @brief Calculates the way back to homebase for given aircraft and returns it.
574  * @param[in] aircraft Pointer to aircraft, which should return to base.
575  * @note Command to call this: "aircraft_return".
576  */
AIR_AircraftReturnToBase(aircraft_t * aircraft)577 void AIR_AircraftReturnToBase (aircraft_t* aircraft)
578 {
579 	if (aircraft && AIR_IsAircraftOnGeoscape(aircraft)) {
580 		const base_t* base = aircraft->homebase;
581 		GEO_CalcLine(aircraft->pos, base->pos, &aircraft->route);
582 		aircraft->status = AIR_RETURNING;
583 		aircraft->time = 0;
584 		aircraft->point = 0;
585 		aircraft->mission = nullptr;
586 		aircraft->aircraftTarget = nullptr;
587 	}
588 }
589 
590 /**
591  * @param base The base to get the aircraft from
592  * @param index The index of the aircraft in the given base
593  * @return @c nullptr if there is no such aircraft in the given base, or the aircraft pointer that belongs to the given index.
594  * @todo Remove this! Aircraft no longer have local index per base
595  */
AIR_GetAircraftFromBaseByIDXSafe(const base_t * base,int index)596 aircraft_t* AIR_GetAircraftFromBaseByIDXSafe (const base_t* base, int index)
597 {
598 	int i;
599 
600 	i = 0;
601 	AIR_ForeachFromBase(aircraft, base) {
602 		if (index == i)
603 			return aircraft;
604 		i++;
605 	}
606 
607 	return nullptr;
608 }
609 
610 /**
611  * @brief Searches the global array of aircraft types for a given aircraft.
612  * @param[in] name Aircraft id.
613  * @return aircraft_t pointer or nullptr if not found.
614  * @note This function gives no warning on null name or if no aircraft found
615  */
AIR_GetAircraftSilent(const char * name)616 const aircraft_t* AIR_GetAircraftSilent (const char* name)
617 {
618 	int i;
619 
620 	if (!name)
621 		return nullptr;
622 	for (i = 0; i < ccs.numAircraftTemplates; i++) {
623 		const aircraft_t* aircraftTemplate = &ccs.aircraftTemplates[i];
624 		if (Q_streq(aircraftTemplate->id, name))
625 			return aircraftTemplate;
626 	}
627 	return nullptr;
628 }
629 
630 /**
631  * @brief Searches the global array of aircraft types for a given aircraft.
632  * @param[in] name Aircraft id.
633  * @return aircraft_t pointer or errors out (ERR_DROP)
634  */
AIR_GetAircraft(const char * name)635 const aircraft_t* AIR_GetAircraft (const char* name)
636 {
637 	const aircraft_t* aircraft = AIR_GetAircraftSilent(name);
638 	if (Q_strnull(name))
639 		cgi->Com_Error(ERR_DROP, "AIR_GetAircraft called with invaliad name!");
640 	else if (aircraft == nullptr)
641 		cgi->Com_Error(ERR_DROP, "Aircraft '%s' not found", name);
642 
643 	return aircraft;
644 }
645 
646 /**
647  * @brief Initialise aircraft pointer in each slot of an aircraft.
648  * @param[in] aircraft Pointer to the aircraft where slots are.
649  */
AII_SetAircraftInSlots(aircraft_t * aircraft)650 static void AII_SetAircraftInSlots (aircraft_t* aircraft)
651 {
652 	int i;
653 
654 	/* initialise weapon slots */
655 	for (i = 0; i < MAX_AIRCRAFTSLOT; i++) {
656 		aircraft->weapons[i].aircraft = aircraft;
657 		aircraft->electronics[i].aircraft = aircraft;
658 	}
659 	aircraft->shield.aircraft = aircraft;
660 }
661 
662 /**
663  * @brief Adds a new aircraft from a given aircraft template to the base and sets the homebase for the new aircraft
664  * to the given base
665  * @param[out] base The base to add the aircraft to
666  * @param[in] aircraftTemplate The template to create the new aircraft from
667  * @return the newly added aircraft
668  * @sa AIR_Delete
669  */
AIR_Add(base_t * base,const aircraft_t * aircraftTemplate)670 aircraft_t* AIR_Add (base_t* base, const aircraft_t* aircraftTemplate)
671 {
672 	const baseCapacities_t capType = AIR_GetCapacityByAircraftWeight(aircraftTemplate);
673 	aircraft_t& aircraft = LIST_Add(&ccs.aircraft, *aircraftTemplate);
674 	aircraft.homebase = base;
675 	if (base && capType != MAX_CAP && aircraft.status != AIR_CRASHED)
676 		CAP_AddCurrent(base, capType, 1);
677 	return &aircraft;
678 }
679 
680 /**
681  * @brief Will remove the given aircraft from the base
682  * @param[out] base The base to remove the aircraft from
683  * @param aircraft The aircraft to remove
684  * @return @c true if the aircraft was removed, @c false otherwise
685  * @sa AIR_Add
686  */
AIR_Delete(base_t * base,const aircraft_t * aircraft)687 bool AIR_Delete (base_t* base, const aircraft_t* aircraft)
688 {
689 	const baseCapacities_t capType = AIR_GetCapacityByAircraftWeight(aircraft);
690 	const bool crashed = (aircraft->status == AIR_CRASHED);
691 	if (cgi->LIST_Remove(&ccs.aircraft, (const void*)aircraft)) {
692 		if (base && capType != MAX_CAP && !crashed)
693 			CAP_AddCurrent(base, capType, -1);
694 		return true;
695 	}
696 	return false;
697 }
698 
699 /**
700  * @brief Places a new aircraft in the given base.
701  * @param[in] base Pointer to base where aircraft should be added.
702  * @param[in] aircraftTemplate The aircraft template to create the new aircraft from.
703  * @sa B_Load
704  */
AIR_NewAircraft(base_t * base,const aircraft_t * aircraftTemplate)705 aircraft_t* AIR_NewAircraft (base_t* base, const aircraft_t* aircraftTemplate)
706 {
707 	/* copy generic aircraft description to individual aircraft in base
708 	 * we do this because every aircraft can have its own parameters
709 	 * now lets use the aircraft array for the base to set some parameters */
710 	aircraft_t* aircraft = AIR_Add(base, aircraftTemplate);
711 	aircraft->idx = ccs.campaignStats.aircraftHad++;	/**< set a unique index to this aircraft. */
712 	aircraft->homebase = base;
713 	/* Update the values of its stats */
714 	AII_UpdateAircraftStats(aircraft);
715 	/* initialise aircraft pointer in slots */
716 	AII_SetAircraftInSlots(aircraft);
717 	/* Set HP to maximum */
718 	aircraft->damage = aircraft->stats[AIR_STATS_DAMAGE];
719 	/* Set Default Name */
720 	Q_strncpyz(aircraft->name, _(aircraft->defaultName), lengthof(aircraft->name));
721 
722 	/* set initial direction of the aircraft */
723 	VectorSet(aircraft->direction, 1, 0, 0);
724 
725 	AIR_ResetAircraftTeam(aircraft);
726 
727 	Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new %s is ready in %s"), _(aircraft->tpl->name), base->name);
728 	MS_AddNewMessage(_("Notice"), cp_messageBuffer);
729 	Com_DPrintf(DEBUG_CLIENT, "Setting aircraft to pos: %.0f:%.0f\n", base->pos[0], base->pos[1]);
730 	Vector2Copy(base->pos, aircraft->pos);
731 	RADAR_Initialise(&aircraft->radar, aircraftTemplate->radar.range, aircraftTemplate->radar.trackingRange, 1.0f, false);
732 	aircraft->radar.ufoDetectionProbability = aircraftTemplate->radar.ufoDetectionProbability;
733 
734 	/* Update base capacities. */
735 	Com_DPrintf(DEBUG_CLIENT, "idx_sample: %i name: %s weight: %i\n", aircraft->tpl->idx, aircraft->id, aircraft->size);
736 	Com_DPrintf(DEBUG_CLIENT, "Adding new aircraft %s with IDX %i for %s\n", aircraft->id, aircraft->idx, base->name);
737 	if (!base->aircraftCurrent)
738 		base->aircraftCurrent = aircraft;
739 
740 	/* also update the base menu buttons */
741 	cgi->Cmd_ExecuteString("base_init");
742 	return aircraft;
743 }
744 
745 /**
746  * @brief Returns capacity type needed for an aircraft
747  * @param[in] aircraft Aircraft to check
748  */
AIR_GetCapacityByAircraftWeight(const aircraft_t * aircraft)749 baseCapacities_t AIR_GetCapacityByAircraftWeight (const aircraft_t* aircraft)
750 {
751 	if (!aircraft)
752 		return MAX_CAP;
753 	switch (aircraft->size) {
754 	case AIRCRAFT_SMALL:
755 		return CAP_AIRCRAFT_SMALL;
756 	case AIRCRAFT_LARGE:
757 		return CAP_AIRCRAFT_BIG;
758 	}
759 	return MAX_CAP;
760 }
761 
762 /**
763  * @brief Calculate used storage room corresponding to items in an aircraft.
764  * @param[in] aircraft Pointer to the aircraft.
765  */
AIR_GetStorageRoom(const aircraft_t * aircraft)766 static int AIR_GetStorageRoom (const aircraft_t* aircraft)
767 {
768 	int size = 0;
769 
770 	LIST_Foreach(aircraft->acTeam, Employee, employee) {
771 		const Container* cont = nullptr;
772 		while ((cont = employee->chr.inv.getNextCont(cont, true))) {
773 			Item* item = nullptr;
774 			while ((item = cont->getNextItem(item))) {
775 				const objDef_t* obj = item->def();
776 				size += obj->size;
777 
778 				obj = item->ammoDef();
779 				if (obj)
780 					size += obj->size;
781 			}
782 		}
783 	}
784 
785 	return size;
786 }
787 
788 /**
789  * @brief Checks if destination base can store an aircraft and its team
790  * @param[in] aircraft Pointer to the aircraft to check
791  * @param[in] base Pointer to the base to store aircraft
792  */
AIR_CheckMoveIntoNewHomebase(const aircraft_t * aircraft,const base_t * base)793 const char* AIR_CheckMoveIntoNewHomebase (const aircraft_t* aircraft, const base_t* base)
794 {
795 	const baseCapacities_t capacity = AIR_GetCapacityByAircraftWeight(aircraft);
796 
797 	if (!B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(capacity)))
798 		return _("No operational hangars at that base.");
799 
800 	/* not enough capacity */
801 	if (CAP_GetFreeCapacity(base, capacity) <= 0)
802 		return _("No free hangars at that base.");
803 
804 	if (CAP_GetFreeCapacity(base, CAP_EMPLOYEES) < AIR_GetTeamSize(aircraft) + (AIR_GetPilot(aircraft) ? 1 : 0))
805 		return _("Insufficient free crew quarter space at that base.");
806 
807 	if (aircraft->maxTeamSize && CAP_GetFreeCapacity(base, CAP_ITEMS) < AIR_GetStorageRoom(aircraft))
808 		return _("Insufficient storage space at that base.");
809 
810 	/* check aircraft fuel, because the aircraft has to travel to the new base */
811 	if (!AIR_AircraftHasEnoughFuelOneWay(aircraft, base->pos))
812 		return _("That base is beyond this aircraft's range.");
813 
814 	return nullptr;
815 }
816 
817 /**
818  * @brief Transfer items carried by a soldier from one base to another.
819  * @param[in] chr Pointer to the character.
820  * @param[in] sourceBase Base where employee comes from.
821  * @param[in] destBase Base where employee is going.
822  */
AIR_TransferItemsCarriedByCharacterToBase(character_t * chr,base_t * sourceBase,base_t * destBase)823 static void AIR_TransferItemsCarriedByCharacterToBase (character_t* chr, base_t* sourceBase, base_t* destBase)
824 {
825 	const Container* cont = nullptr;
826 	while ((cont = chr->inv.getNextCont(cont, true))) {
827 		Item* item = nullptr;
828 		while ((item = cont->getNextItem(item))) {
829 			const objDef_t* obj = item->def();
830 			B_AddToStorage(sourceBase, obj, -1);
831 			B_AddToStorage(destBase, obj, 1);
832 
833 			obj = item->ammoDef();
834 			if (obj) {
835 				B_AddToStorage(sourceBase, obj, -1);
836 				B_AddToStorage(destBase, obj, 1);
837 			}
838 		}
839 	}
840 }
841 
842 /**
843  * @brief Moves a given aircraft to a new base (also the employees and inventory)
844  * @param[in] aircraft The aircraft to move into a new base
845  * @param[in] base The base to move the aircraft into
846  */
AIR_MoveAircraftIntoNewHomebase(aircraft_t * aircraft,base_t * base)847 void AIR_MoveAircraftIntoNewHomebase (aircraft_t* aircraft, base_t* base)
848 {
849 	base_t* oldBase;
850 
851 	assert(aircraft);
852 	assert(base);
853 	assert(base != aircraft->homebase);
854 
855 	Com_DPrintf(DEBUG_CLIENT, "AIR_MoveAircraftIntoNewHomebase: Change homebase of '%s' to '%s'\n", aircraft->id, base->name);
856 
857 	oldBase = aircraft->homebase;
858 	assert(oldBase);
859 
860 	/* Transfer employees */
861 	E_MoveIntoNewBase(AIR_GetPilot(aircraft), base);
862 
863 	LIST_Foreach(aircraft->acTeam, Employee, employee) {
864 		E_MoveIntoNewBase(employee, base);
865 		/* Transfer items carried by soldiers from oldBase to base */
866 		AIR_TransferItemsCarriedByCharacterToBase(&employee->chr, oldBase, base);
867 	}
868 
869 	/* Move aircraft to new base */
870 	const baseCapacities_t capacity = AIR_GetCapacityByAircraftWeight(aircraft);
871 	CAP_AddCurrent(oldBase, capacity, -1);
872 	aircraft->homebase = base;
873 	CAP_AddCurrent(base, capacity, 1);
874 
875 	if (oldBase->aircraftCurrent == aircraft)
876 		oldBase->aircraftCurrent = AIR_GetFirstFromBase(oldBase);
877 	if (!base->aircraftCurrent)
878 		base->aircraftCurrent = aircraft;
879 
880 	/* No need to update global IDX of every aircraft: the global IDX of this aircraft did not change */
881 	/* Redirect selectedAircraft */
882 	GEO_SelectAircraft(aircraft);
883 
884 	if (aircraft->status == AIR_RETURNING) {
885 		/* redirect to the new base */
886 		AIR_AircraftReturnToBase(aircraft);
887 	}
888 }
889 
890 /**
891  * @brief Removes an aircraft from its base and the game.
892  * @param[in] aircraft Pointer to aircraft that should be removed.
893  * @note The assigned soldiers (if any) are unassinged from the aircraft - not fired.
894  * @sa AIR_DestroyAircraft
895  * @note If you want to do something different (kill, fire, etc...) do it before calling this function.
896  * @todo Return status of deletion for better error handling.
897  */
AIR_DeleteAircraft(aircraft_t * aircraft)898 void AIR_DeleteAircraft (aircraft_t* aircraft)
899 {
900 	int i;
901 	base_t* base;
902 	/* Check if aircraft is on geoscape while it's not destroyed yet */
903 	const bool aircraftIsOnGeoscape = AIR_IsAircraftOnGeoscape(aircraft);
904 
905 	assert(aircraft);
906 	base = aircraft->homebase;
907 	assert(base);
908 
909 	GEO_NotifyAircraftRemoved(aircraft);
910 	TR_NotifyAircraftRemoved(aircraft);
911 
912 	/* Remove pilot and all soldiers from the aircraft (the employees are still hired after this). */
913 	AIR_RemoveEmployees(*aircraft);
914 
915 	/* base is nullptr here because we don't want to readd this to the inventory
916 	 * If you want this in the inventory you really have to call these functions
917 	 * before you are destroying a craft */
918 	for (i = 0; i < MAX_AIRCRAFTSLOT; i++) {
919 		AII_RemoveItemFromSlot(nullptr, aircraft->weapons, false);
920 		AII_RemoveItemFromSlot(nullptr, aircraft->electronics, false);
921 	}
922 	AII_RemoveItemFromSlot(nullptr, &aircraft->shield, false);
923 
924 	if (base->aircraftCurrent == aircraft)
925 		base->aircraftCurrent = nullptr;
926 
927 	if (aircraft->alienCargo != nullptr) {
928 		delete aircraft->alienCargo;
929 		aircraft->alienCargo = nullptr;
930 	}
931 
932 	AIR_Delete(base, aircraft);
933 
934 	if (!AIR_BaseHasAircraft(base)) {
935 		cgi->Cvar_Set("mn_aircraftstatus", "");
936 		cgi->Cvar_Set("mn_aircraftinbase", "0");
937 		cgi->Cvar_Set("mn_aircraftname", "");
938 		cgi->Cvar_Set("mn_aircraft_model", "");
939 	} else if (base->aircraftCurrent == nullptr) {
940 		base->aircraftCurrent = AIR_GetFirstFromBase(base);
941 	}
942 
943 	/* also update the base menu buttons */
944 	cgi->Cmd_ExecuteString("base_init");
945 
946 	/* Update Radar overlay */
947 	if (aircraftIsOnGeoscape)
948 		RADAR_UpdateWholeRadarOverlay();
949 }
950 
951 /**
952  * @brief Removes an aircraft from its base and the game.
953  * @param[in] aircraft Pointer to aircraft that should be removed.
954  * @param[in] killPilot Should pilot be removed from game or not?
955  * @note aircraft and assigned soldiers (if any) are removed from game.
956  * @todo Return status of deletion for better error handling.
957  */
AIR_DestroyAircraft(aircraft_t * aircraft,bool killPilot)958 void AIR_DestroyAircraft (aircraft_t* aircraft, bool killPilot)
959 {
960 	Employee* pilot;
961 
962 	assert(aircraft);
963 
964 	LIST_Foreach(aircraft->acTeam, Employee, employee) {
965 		E_RemoveInventoryFromStorage(employee);
966 		E_DeleteEmployee(employee);
967 	}
968 	/* the craft may no longer have any employees assigned */
969 	/* remove the pilot */
970 	pilot = AIR_GetPilot(aircraft);
971 	if (pilot) {
972 		if (killPilot) {
973 			if (E_DeleteEmployee(pilot))
974 				AIR_SetPilot(aircraft, nullptr);
975 			else
976 				cgi->Com_Error(ERR_DROP, "AIR_DestroyAircraft: Could not remove pilot from game: %s (ucn: %i)\n",
977 						pilot->chr.name, pilot->chr.ucn);
978 		} else {
979 			AIR_SetPilot(aircraft, nullptr);
980 		}
981 	} else {
982 		if (aircraft->status != AIR_CRASHED)
983 			cgi->Com_Error(ERR_DROP, "AIR_DestroyAircraft: aircraft id %s had no pilot\n", aircraft->id);
984 	}
985 
986 	AIR_DeleteAircraft(aircraft);
987 }
988 
989 /**
990  * @brief Moves given aircraft.
991  * @param[in] dt time delta
992  * @param[in] aircraft Pointer to aircraft on its way.
993  * @return true if the aircraft reached its destination.
994  */
AIR_AircraftMakeMove(int dt,aircraft_t * aircraft)995 bool AIR_AircraftMakeMove (int dt, aircraft_t* aircraft)
996 {
997 	float dist;
998 
999 	/* calc distance */
1000 	aircraft->time += dt;
1001 	aircraft->fuel -= dt;
1002 
1003 	dist = (float) aircraft->stats[AIR_STATS_SPEED] * aircraft->time / (float)SECONDS_PER_HOUR;
1004 
1005 	/* Check if destination reached */
1006 	if (dist >= aircraft->route.distance * (aircraft->route.numPoints - 1)) {
1007 		return true;
1008 	} else {
1009 		/* calc new position */
1010 		float frac = dist / aircraft->route.distance;
1011 		const int p = (int) frac;
1012 		frac -= p;
1013 		aircraft->point = p;
1014 		aircraft->pos[0] = (1 - frac) * aircraft->route.point[p][0] + frac * aircraft->route.point[p + 1][0];
1015 		aircraft->pos[1] = (1 - frac) * aircraft->route.point[p][1] + frac * aircraft->route.point[p + 1][1];
1016 
1017 		GEO_CheckPositionBoundaries(aircraft->pos);
1018 	}
1019 
1020 	dist = (float) aircraft->stats[AIR_STATS_SPEED] * (aircraft->time + dt) / (float)SECONDS_PER_HOUR;
1021 
1022 	/* Now calculate the projected position. This is the position that the aircraft should get on
1023 	 * next frame if its route didn't change in meantime. */
1024 	if (dist >= aircraft->route.distance * (aircraft->route.numPoints - 1)) {
1025 		VectorSet(aircraft->projectedPos, 0.0f, 0.0f, 0.0f);
1026 	} else {
1027 		/* calc new position */
1028 		float frac = dist / aircraft->route.distance;
1029 		const int p = (int) frac;
1030 		frac -= p;
1031 		aircraft->projectedPos[0] = (1 - frac) * aircraft->route.point[p][0] + frac * aircraft->route.point[p + 1][0];
1032 		aircraft->projectedPos[1] = (1 - frac) * aircraft->route.point[p][1] + frac * aircraft->route.point[p + 1][1];
1033 
1034 		GEO_CheckPositionBoundaries(aircraft->projectedPos);
1035 	}
1036 
1037 	return false;
1038 }
1039 
AIR_Move(aircraft_t * aircraft,int deltaTime)1040 static void AIR_Move (aircraft_t* aircraft, int deltaTime)
1041 {
1042 	/* Aircraft is moving */
1043 	if (AIR_AircraftMakeMove(deltaTime, aircraft)) {
1044 		/* aircraft reach its destination */
1045 		const float* end = aircraft->route.point[aircraft->route.numPoints - 1];
1046 		Vector2Copy(end, aircraft->pos);
1047 		GEO_CheckPositionBoundaries(aircraft->pos);
1048 
1049 		switch (aircraft->status) {
1050 		case AIR_MISSION:
1051 			/* Aircraft reached its mission */
1052 			assert(aircraft->mission);
1053 			aircraft->mission->active = true;
1054 			aircraft->status = AIR_DROP;
1055 			GEO_SetMissionAircraft(aircraft);
1056 			GEO_SelectMission(aircraft->mission);
1057 			GEO_SetInterceptorAircraft(aircraft);
1058 			CP_GameTimeStop();
1059 			cgi->UI_PushWindow("popup_intercept_ready");
1060 			cgi->UI_ExecuteConfunc("pop_intready_aircraft \"%s\" \"%s\"", aircraft->name,
1061 				MIS_GetName(aircraft->mission));
1062 			break;
1063 		case AIR_RETURNING:
1064 			/* aircraft entered in homebase */
1065 			aircraft->status = AIR_REFUEL;
1066 			B_AircraftReturnedToHomeBase(aircraft);
1067 			Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
1068 				_("Craft %s has returned to %s."), aircraft->name, aircraft->homebase->name);
1069 			MSO_CheckAddNewMessage(NT_AIRCRAFT_ARRIVEDHOME, _("Notice"), cp_messageBuffer);
1070 			break;
1071 		case AIR_TRANSFER:
1072 		case AIR_UFO:
1073 			break;
1074 		default:
1075 			aircraft->status = AIR_IDLE;
1076 			break;
1077 		}
1078 	}
1079 }
1080 
AIR_Refuel(aircraft_t * aircraft,int deltaTime)1081 static void AIR_Refuel (aircraft_t* aircraft, int deltaTime)
1082 {
1083 	/* Aircraft is refuelling at base */
1084 	int fillup;
1085 
1086 	if (aircraft->fuel < 0)
1087 		aircraft->fuel = 0;
1088 	/* amount of fuel we would like to load */
1089 	fillup = std::min(deltaTime * AIRCRAFT_REFUEL_FACTOR, aircraft->stats[AIR_STATS_FUELSIZE] - aircraft->fuel);
1090 	/* This craft uses antimatter as fuel */
1091 	assert(aircraft->homebase);
1092 	if (aircraft->stats[AIR_STATS_ANTIMATTER] > 0 && fillup > 0) {
1093 		/* the antimatter we have */
1094 		const int amAvailable = B_ItemInBase(INVSH_GetItemByID(ANTIMATTER_TECH_ID), aircraft->homebase);
1095 		/* current antimatter level in craft */
1096 		const int amCurrentLevel = aircraft->stats[AIR_STATS_ANTIMATTER] * (aircraft->fuel / (float) aircraft->stats[AIR_STATS_FUELSIZE]);
1097 		/* next antimatter level in craft */
1098 		const int amNextLevel = aircraft->stats[AIR_STATS_ANTIMATTER] * ((aircraft->fuel + fillup) / (float) aircraft->stats[AIR_STATS_FUELSIZE]);
1099 		/* antimatter needed */
1100 		int amLoad = amNextLevel - amCurrentLevel;
1101 
1102 		if (amLoad > amAvailable) {
1103 			/* amount of fuel we can load */
1104 			fillup = aircraft->stats[AIR_STATS_FUELSIZE] * ((amCurrentLevel + amAvailable) / (float) aircraft->stats[AIR_STATS_ANTIMATTER]) - aircraft->fuel;
1105 			amLoad = amAvailable;
1106 
1107 			if (!aircraft->notifySent[AIR_CANNOT_REFUEL]) {
1108 				Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
1109 						_("Craft %s couldn't be completely refueled at %s. Not enough antimatter."), aircraft->name, aircraft->homebase->name);
1110 				MSO_CheckAddNewMessage(NT_AIRCRAFT_CANNOTREFUEL, _("Notice"), cp_messageBuffer);
1111 				aircraft->notifySent[AIR_CANNOT_REFUEL] = true;
1112 			}
1113 		}
1114 
1115 		if (amLoad > 0)
1116 			B_ManageAntimatter(aircraft->homebase, amLoad, false);
1117 	}
1118 
1119 	aircraft->fuel += fillup;
1120 
1121 	if (aircraft->fuel >= aircraft->stats[AIR_STATS_FUELSIZE]) {
1122 		aircraft->fuel = aircraft->stats[AIR_STATS_FUELSIZE];
1123 		aircraft->status = AIR_HOME;
1124 		Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
1125 				_("Craft %s has refueled at %s."), aircraft->name, aircraft->homebase->name);
1126 		MSO_CheckAddNewMessage(NT_AIRCRAFT_REFUELED, _("Notice"), cp_messageBuffer);
1127 		aircraft->notifySent[AIR_CANNOT_REFUEL] = false;
1128 	}
1129 
1130 }
1131 
1132 /**
1133  * @brief Handles aircraft movement and actions in geoscape mode
1134  * @sa CP_CampaignRun
1135  * @param[in] campaign The campaign data structure
1136  * @param[in] dt time delta (may be 0 if radar overlay should be updated but no aircraft moves)
1137  * @param[in] updateRadarOverlay True if radar overlay should be updated (not needed if geoscape isn't updated)
1138  */
AIR_CampaignRun(const campaign_t * campaign,int dt,bool updateRadarOverlay)1139 void AIR_CampaignRun (const campaign_t* campaign, int dt, bool updateRadarOverlay)
1140 {
1141 	/* true if at least one aircraft moved: radar overlay must be updated
1142 	 * This is static because aircraft can move without radar being
1143 	 * updated (sa CP_CampaignRun) */
1144 	static bool radarOverlayReset = false;
1145 
1146 	/* Run each aircraft */
1147 	AIR_Foreach(aircraft) {
1148 		int k;
1149 
1150 		if (aircraft->status == AIR_CRASHED)
1151 			continue;
1152 
1153 		assert(aircraft->homebase);
1154 		if (aircraft->status == AIR_IDLE) {
1155 			/* Aircraft idle out of base */
1156 			aircraft->fuel -= dt;
1157 		} else if (AIR_IsAircraftOnGeoscape(aircraft)) {
1158 			AIR_Move(aircraft, dt);
1159 			/* radar overlay should be updated */
1160 			radarOverlayReset = true;
1161 		} else if (aircraft->status == AIR_REFUEL) {
1162 			AIR_Refuel(aircraft, dt);
1163 		}
1164 
1165 		/* Check aircraft low fuel (only if aircraft is not already returning to base or in base) */
1166 		if (aircraft->status != AIR_RETURNING && AIR_IsAircraftOnGeoscape(aircraft) &&
1167 			!AIR_AircraftHasEnoughFuel(aircraft, aircraft->pos)) {
1168 			/** @todo check if aircraft can go to a closer base with free space */
1169 			MS_AddNewMessage(_("Notice"), va(_("Craft %s is low on fuel and must return to base."), aircraft->name));
1170 			AIR_AircraftReturnToBase(aircraft);
1171 		}
1172 
1173 		/* Aircraft purchasing ufo */
1174 		if (aircraft->status == AIR_UFO) {
1175 			/* Solve the fight */
1176 			AIRFIGHT_ExecuteActions(campaign, aircraft, aircraft->aircraftTarget);
1177 		}
1178 
1179 		for (k = 0; k < aircraft->maxWeapons; k++) {
1180 			aircraftSlot_t* slot = &aircraft->weapons[k];
1181 			/* Update delay to launch next projectile */
1182 			if (AIR_IsAircraftOnGeoscape(aircraft) && slot->delayNextShot > 0)
1183 				slot->delayNextShot -= dt;
1184 			/* Reload if needed */
1185 			if (slot->ammoLeft <= 0)
1186 				AII_ReloadWeapon(slot);
1187 		}
1188 	}
1189 
1190 	if (updateRadarOverlay && radarOverlayReset && GEO_IsRadarOverlayActivated()) {
1191 		RADAR_UpdateWholeRadarOverlay();
1192 		radarOverlayReset = false;
1193 	}
1194 }
1195 
1196 /**
1197  * @brief Returns aircraft for a given global index.
1198  * @param[in] aircraftIdx Global aircraft index.
1199  * @return An aircraft pointer (to a struct in a base) that has the given index or nullptr if no aircraft found.
1200  */
AIR_AircraftGetFromIDX(int aircraftIdx)1201 aircraft_t* AIR_AircraftGetFromIDX (int aircraftIdx)
1202 {
1203 	AIR_Foreach(aircraft) {
1204 		if (aircraft->idx == aircraftIdx) {
1205 			Com_DPrintf(DEBUG_CLIENT, "AIR_AircraftGetFromIDX: aircraft idx: %i\n",	aircraft->idx);
1206 			return aircraft;
1207 		}
1208 	}
1209 
1210 	return nullptr;
1211 }
1212 
1213 /**
1214  * @brief Sends the specified aircraft to specified mission.
1215  * @param[in] aircraft Pointer to aircraft to send to mission.
1216  * @param[in] mission Pointer to given mission.
1217  * @return true if sending an aircraft to specified mission is possible.
1218  */
AIR_SendAircraftToMission(aircraft_t * aircraft,mission_t * mission)1219 bool AIR_SendAircraftToMission (aircraft_t* aircraft, mission_t* mission)
1220 {
1221 	if (!aircraft || !mission)
1222 		return false;
1223 
1224 	if (AIR_GetTeamSize(aircraft) == 0) {
1225 		CP_Popup(_("Notice"), _("Assign one or more soldiers to this aircraft first."));
1226 		return false;
1227 	}
1228 
1229 	/* if aircraft was in base */
1230 	if (AIR_IsAircraftInBase(aircraft)) {
1231 		/* reload its ammunition */
1232 		AII_ReloadAircraftWeapons(aircraft);
1233 	}
1234 
1235 	/* ensure interceptAircraft is set correctly */
1236 	GEO_SetInterceptorAircraft(aircraft);
1237 
1238 	/* if mission is a base-attack and aircraft already in base, launch
1239 	 * mission immediately */
1240 	if (B_IsUnderAttack(aircraft->homebase) && AIR_IsAircraftInBase(aircraft)) {
1241 		aircraft->mission = mission;
1242 		mission->active = true;
1243 		cgi->UI_PushWindow("popup_baseattack");
1244 		return true;
1245 	}
1246 
1247 	if (!AIR_AircraftHasEnoughFuel(aircraft, mission->pos)) {
1248 		MS_AddNewMessage(_("Notice"), _("Insufficient fuel."));
1249 		return false;
1250 	}
1251 
1252 	GEO_CalcLine(aircraft->pos, mission->pos, &aircraft->route);
1253 	aircraft->status = AIR_MISSION;
1254 	aircraft->time = 0;
1255 	aircraft->point = 0;
1256 	aircraft->mission = mission;
1257 
1258 	return true;
1259 }
1260 
1261 /**
1262  * @brief Initialise all values of an aircraft slot.
1263  * @param[in] aircraftTemplate Pointer to the aircraft which needs initalisation of its slots.
1264  */
AII_InitialiseAircraftSlots(aircraft_t * aircraftTemplate)1265 static void AII_InitialiseAircraftSlots (aircraft_t* aircraftTemplate)
1266 {
1267 	int i;
1268 
1269 	/* initialise weapon slots */
1270 	for (i = 0; i < MAX_AIRCRAFTSLOT; i++) {
1271 		AII_InitialiseSlot(aircraftTemplate->weapons + i, aircraftTemplate, nullptr, nullptr, AC_ITEM_WEAPON);
1272 		AII_InitialiseSlot(aircraftTemplate->electronics + i, aircraftTemplate, nullptr, nullptr, AC_ITEM_ELECTRONICS);
1273 	}
1274 	AII_InitialiseSlot(&aircraftTemplate->shield, aircraftTemplate, nullptr, nullptr, AC_ITEM_SHIELD);
1275 }
1276 
1277 /**
1278  * @brief List of valid strings for itemPos_t
1279  * @note must be in the same order than @c itemPos_t
1280  */
1281 static char const* const air_position_strings[] = {
1282 	"nose_left",
1283 	"nose_center",
1284 	"nose_right",
1285 	"wing_left",
1286 	"wing_right",
1287 	"rear_left",
1288 	"rear_center",
1289 	"rear_right"
1290 };
1291 
1292 /** @brief Valid aircraft parameter definitions from script files.
1293  * @note wrange can't be parsed in aircraft definition: this is a property
1294  * of weapon */
1295 static const value_t aircraft_param_vals[] = {
1296 	{"speed", V_INT, offsetof(aircraft_t, stats[AIR_STATS_SPEED]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1297 	{"maxspeed", V_INT, offsetof(aircraft_t, stats[AIR_STATS_MAXSPEED]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1298 	{"shield", V_INT, offsetof(aircraft_t, stats[AIR_STATS_SHIELD]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1299 	{"ecm", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ECM]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1300 	{"damage", V_INT, offsetof(aircraft_t, stats[AIR_STATS_DAMAGE]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1301 	{"accuracy", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ACCURACY]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1302 	{"antimatter", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ANTIMATTER]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1303 
1304 	{nullptr, V_NULL, 0, 0}
1305 };
1306 
1307 /** @brief Valid aircraft definition values from script files. */
1308 static const value_t aircraft_vals[] = {
1309 	{"name", V_STRING, offsetof(aircraft_t, name), 0},
1310 	{"defaultname", V_TRANSLATION_STRING, offsetof(aircraft_t, defaultName), 0},
1311 	{"numteam", V_INT, offsetof(aircraft_t, maxTeamSize), MEMBER_SIZEOF(aircraft_t, maxTeamSize)},
1312 	{"size", V_INT, offsetof(aircraft_t, size), MEMBER_SIZEOF(aircraft_t, size)},
1313 	{"nogeoscape", V_BOOL, offsetof(aircraft_t, notOnGeoscape), MEMBER_SIZEOF(aircraft_t, notOnGeoscape)},
1314 	{"interestlevel", V_INT, offsetof(aircraft_t, ufoInterestOnGeoscape), MEMBER_SIZEOF(aircraft_t, ufoInterestOnGeoscape)},
1315 
1316 	{"leader", V_BOOL, offsetof(aircraft_t, leader), MEMBER_SIZEOF(aircraft_t, leader)},
1317 	{"image", V_HUNK_STRING, offsetof(aircraft_t, image), 0},
1318 	{"model", V_HUNK_STRING, offsetof(aircraft_t, model), 0},
1319 	/* price for selling/buying */
1320 	{"price", V_INT, offsetof(aircraft_t, price), MEMBER_SIZEOF(aircraft_t, price)},
1321 	/* this is needed to let the buy and sell screen look for the needed building */
1322 	/* to place the aircraft in */
1323 	{"productioncost", V_INT, offsetof(aircraft_t, productionCost), MEMBER_SIZEOF(aircraft_t, productionCost)},
1324 	{"building", V_HUNK_STRING, offsetof(aircraft_t, building), 0},
1325 
1326 	{nullptr, V_NULL, 0, 0}
1327 };
1328 
1329 /** @brief Valid radar definition values for an airtcraft from script files. */
1330 static const value_t aircraft_radar_vals[] = {
1331 	{"range", V_INT, offsetof(aircraft_t, radar.range), MEMBER_SIZEOF(aircraft_t, radar.range)},
1332 	{"trackingrange", V_INT, offsetof(aircraft_t, radar.trackingRange), MEMBER_SIZEOF(aircraft_t, radar.trackingRange)},
1333 	{"detectionprobability", V_FLOAT, offsetof(aircraft_t, radar.ufoDetectionProbability), MEMBER_SIZEOF(aircraft_t, radar.ufoDetectionProbability)},
1334 
1335 	{nullptr, V_NULL, 0, 0}
1336 };
1337 
1338 /**
1339  * @brief Parses all aircraft that are defined in our UFO-scripts.
1340  * @sa CL_ParseClientData
1341  * @sa CL_ParseScriptSecond
1342  * @note parses the aircraft into our aircraft_sample array to use as reference
1343  */
AIR_ParseAircraft(const char * name,const char ** text,bool assignAircraftItems)1344 void AIR_ParseAircraft (const char* name, const char** text, bool assignAircraftItems)
1345 {
1346 	const char* errhead = "AIR_ParseAircraft: unexpected end of file (aircraft ";
1347 	aircraft_t* aircraftTemplate;
1348 	const char* token;
1349 	int i;
1350 	technology_t* tech;
1351 	aircraftItemType_t itemType = MAX_ACITEMS;
1352 
1353 	if (ccs.numAircraftTemplates >= MAX_AIRCRAFT) {
1354 		Com_Printf("AIR_ParseAircraft: too many aircraft definitions; def \"%s\" ignored\n", name);
1355 		return;
1356 	}
1357 
1358 	if (!assignAircraftItems) {
1359 		aircraftTemplate = nullptr;
1360 		for (i = 0; i < ccs.numAircraftTemplates; i++) {
1361 			aircraft_t* aircraft = &ccs.aircraftTemplates[i];
1362 			if (Q_streq(aircraft->id, name)) {
1363 				aircraftTemplate = aircraft;
1364 				break;
1365 			}
1366 		}
1367 
1368 		if (aircraftTemplate) {
1369 			Com_Printf("AIR_ParseAircraft: Second aircraft with same name found (%s) - second ignored\n", name);
1370 			return;
1371 		}
1372 
1373 		/* initialize the menu */
1374 		aircraftTemplate = &ccs.aircraftTemplates[ccs.numAircraftTemplates];
1375 		OBJZERO(*aircraftTemplate);
1376 
1377 		Com_DPrintf(DEBUG_CLIENT, "...found aircraft %s\n", name);
1378 		aircraftTemplate->tpl = aircraftTemplate;
1379 		aircraftTemplate->id = Mem_PoolStrDup(name, cp_campaignPool, 0);
1380 		aircraftTemplate->status = AIR_HOME;
1381 		/* default is no ufo */
1382 		aircraftTemplate->ufotype = UFO_MAX;
1383 		aircraftTemplate->maxWeapons = 0;
1384 		aircraftTemplate->maxElectronics = 0;
1385 		AII_InitialiseAircraftSlots(aircraftTemplate);
1386 		/* Initialise UFO sensored */
1387 		RADAR_InitialiseUFOs(&aircraftTemplate->radar);
1388 		/* Default detection probability remains 100% for now */
1389 		aircraftTemplate->radar.ufoDetectionProbability = 1.0f;
1390 
1391 		ccs.numAircraftTemplates++;
1392 	} else {
1393 		aircraftTemplate = nullptr;
1394 		for (i = 0; i < ccs.numAircraftTemplates; i++) {
1395 			aircraft_t* aircraft = &ccs.aircraftTemplates[i];
1396 			if (Q_streq(aircraft->id, name)) {
1397 				aircraftTemplate = aircraft;
1398 				break;
1399 			}
1400 		}
1401 		if (!aircraftTemplate)
1402 			Sys_Error("Could not find aircraft '%s'", name);
1403 	}
1404 
1405 	/* get it's body */
1406 	token = Com_Parse(text);
1407 
1408 	if (!*text || *token != '{') {
1409 		Com_Printf("AIR_ParseAircraft: aircraft def \"%s\" without body ignored\n", name);
1410 		return;
1411 	}
1412 
1413 	do {
1414 		token = cgi->Com_EParse(text, errhead, name);
1415 		if (!*text)
1416 			break;
1417 		if (*token == '}')
1418 			break;
1419 
1420 		if (Q_streq(token, "name")) {
1421 			token = cgi->Com_EParse(text, errhead, name);
1422 			if (!*text)
1423 				return;
1424 			if (token[0] == '_')
1425 				token++;
1426 			Q_strncpyz(aircraftTemplate->name, token, sizeof(aircraftTemplate->name));
1427 			continue;
1428 		} else if (Q_streq(token, "radar")) {
1429 			token = cgi->Com_EParse(text, errhead, name);
1430 			if (!*text || *token != '{') {
1431 				Com_Printf("AIR_ParseAircraft: Invalid radar value for aircraft: %s\n", name);
1432 				return;
1433 			}
1434 			do {
1435 				token = cgi->Com_EParse(text, errhead, name);
1436 				if (!*text)
1437 					break;
1438 				if (*token == '}')
1439 					break;
1440 
1441 				if (!Com_ParseBlockToken(name, text, aircraftTemplate, aircraft_radar_vals, cp_campaignPool, token))
1442 					Com_Printf("AIR_ParseAircraft: Ignoring unknown radar value '%s'\n", token);
1443 			} while (*text); /* dummy condition */
1444 			continue;
1445 		}
1446 		if (assignAircraftItems) {
1447 			/* write into cp_campaignPool - this data is reparsed on every new game */
1448 			/* blocks like param { [..] } - otherwise we would leave the loop too early */
1449 			if (*token == '{') {
1450 				Com_SkipBlock(text);
1451 			} else if (Q_streq(token, "shield")) {
1452 				token = cgi->Com_EParse(text, errhead, name);
1453 				if (!*text)
1454 					return;
1455 				Com_DPrintf(DEBUG_CLIENT, "use shield %s for aircraft %s\n", token, aircraftTemplate->id);
1456 				tech = RS_GetTechByID(token);
1457 				if (tech)
1458 					aircraftTemplate->shield.item = INVSH_GetItemByID(tech->provides);
1459 			} else if (Q_streq(token, "slot")) {
1460 				token = cgi->Com_EParse(text, errhead, name);
1461 				if (!*text || *token != '{') {
1462 					Com_Printf("AIR_ParseAircraft: Invalid slot value for aircraft: %s\n", name);
1463 					return;
1464 				}
1465 				do {
1466 					token = cgi->Com_EParse(text, errhead, name);
1467 					if (!*text)
1468 						break;
1469 					if (*token == '}')
1470 						break;
1471 
1472 					if (Q_streq(token, "type")) {
1473 						token = cgi->Com_EParse(text, errhead, name);
1474 						if (!*text)
1475 							return;
1476 						for (i = 0; i < MAX_ACITEMS; i++) {
1477 							if (Q_streq(token, air_slot_type_strings[i])) {
1478 								itemType = (aircraftItemType_t)i;
1479 								switch (itemType) {
1480 								case AC_ITEM_WEAPON:
1481 									aircraftTemplate->maxWeapons++;
1482 									break;
1483 								case AC_ITEM_ELECTRONICS:
1484 									aircraftTemplate->maxElectronics++;
1485 									break;
1486 								default:
1487 									itemType = MAX_ACITEMS;
1488 									break;
1489 								}
1490 								break;
1491 							}
1492 						}
1493 						if (i == MAX_ACITEMS)
1494 							cgi->Com_Error(ERR_DROP, "Unknown value '%s' for slot type\n", token);
1495 					} else if (Q_streq(token, "position")) {
1496 						token = cgi->Com_EParse(text, errhead, name);
1497 						if (!*text)
1498 							return;
1499 						for (i = 0; i < AIR_POSITIONS_MAX; i++) {
1500 							if (Q_streq(token, air_position_strings[i])) {
1501 								switch (itemType) {
1502 								case AC_ITEM_WEAPON:
1503 									aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].pos = (itemPos_t)i;
1504 									break;
1505 								case AC_ITEM_ELECTRONICS:
1506 									aircraftTemplate->electronics[aircraftTemplate->maxElectronics - 1].pos = (itemPos_t)i;
1507 									break;
1508 								default:
1509 									i = AIR_POSITIONS_MAX;
1510 									break;
1511 								}
1512 								break;
1513 							}
1514 						}
1515 						if (i == AIR_POSITIONS_MAX)
1516 							cgi->Com_Error(ERR_DROP, "Unknown value '%s' for slot position\n", token);
1517 					} else if (Q_streq(token, "contains")) {
1518 						token = cgi->Com_EParse(text, errhead, name);
1519 						if (!*text)
1520 							return;
1521 						tech = RS_GetTechByID(token);
1522 						if (tech) {
1523 							switch (itemType) {
1524 							case AC_ITEM_WEAPON:
1525 								aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].item = INVSH_GetItemByID(tech->provides);
1526 								Com_DPrintf(DEBUG_CLIENT, "use weapon %s for aircraft %s\n", token, aircraftTemplate->id);
1527 								break;
1528 							case AC_ITEM_ELECTRONICS:
1529 								aircraftTemplate->electronics[aircraftTemplate->maxElectronics - 1].item = INVSH_GetItemByID(tech->provides);
1530 								Com_DPrintf(DEBUG_CLIENT, "use electronics %s for aircraft %s\n", token, aircraftTemplate->id);
1531 								break;
1532 							default:
1533 								Com_Printf("Ignoring item value '%s' due to unknown slot type\n", token);
1534 								break;
1535 							}
1536 						}
1537 					} else if (Q_streq(token, "ammo")) {
1538 						token = cgi->Com_EParse(text, errhead, name);
1539 						if (!*text)
1540 							return;
1541 						tech = RS_GetTechByID(token);
1542 						if (tech) {
1543 							if (itemType == AC_ITEM_WEAPON) {
1544 								aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].ammo = INVSH_GetItemByID(tech->provides);
1545 								Com_DPrintf(DEBUG_CLIENT, "use ammo %s for aircraft %s\n", token, aircraftTemplate->id);
1546 							} else
1547 								Com_Printf("Ignoring ammo value '%s' due to unknown slot type\n", token);
1548 						}
1549 					} else if (Q_streq(token, "size")) {
1550 						token = cgi->Com_EParse(text, errhead, name);
1551 						if (!*text)
1552 							return;
1553 						if (itemType == AC_ITEM_WEAPON) {
1554 							if (Q_streq(token, "light"))
1555 								aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_LIGHT;
1556 							else if (Q_streq(token, "medium"))
1557 								aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_MEDIUM;
1558 							else if (Q_streq(token, "heavy"))
1559 								aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_HEAVY;
1560 							else
1561 								Com_Printf("Unknown size value for aircraft slot: '%s'\n", token);
1562 						} else
1563 							Com_Printf("Ignoring size parameter '%s' for non-weapon aircraft slots\n", token);
1564 					} else
1565 						Com_Printf("AIR_ParseAircraft: Ignoring unknown slot value '%s'\n", token);
1566 				} while (*text); /* dummy condition */
1567 			}
1568 		} else {
1569 			if (Q_streq(token, "shield")) {
1570 				cgi->Com_EParse(text, errhead, name);
1571 				continue;
1572 			}
1573 			/* check for some standard values */
1574 			if (Com_ParseBlockToken(name, text, aircraftTemplate, aircraft_vals, cp_campaignPool, token))
1575 				continue;
1576 
1577 			if (Q_streq(token, "type")) {
1578 				token = cgi->Com_EParse(text, errhead, name);
1579 				if (!*text)
1580 					return;
1581 				if (Q_streq(token, "transporter"))
1582 					aircraftTemplate->type = AIRCRAFT_TRANSPORTER;
1583 				else if (Q_streq(token, "interceptor"))
1584 					aircraftTemplate->type = AIRCRAFT_INTERCEPTOR;
1585 				else if (Q_streq(token, "ufo")) {
1586 					aircraftTemplate->type = AIRCRAFT_UFO;
1587 					aircraftTemplate->ufotype = cgi->Com_UFOShortNameToID(aircraftTemplate->id);
1588 				}
1589 			} else if (Q_streq(token, "slot")) {
1590 				token = cgi->Com_EParse(text, errhead, name);
1591 				if (!*text || *token != '{') {
1592 					Com_Printf("AIR_ParseAircraft: Invalid slot value for aircraft: %s\n", name);
1593 					return;
1594 				}
1595 				Com_SkipBlock(text);
1596 			} else if (Q_streq(token, "param")) {
1597 				token = cgi->Com_EParse(text, errhead, name);
1598 				if (!*text || *token != '{') {
1599 					Com_Printf("AIR_ParseAircraft: Invalid param value for aircraft: %s\n", name);
1600 					return;
1601 				}
1602 				do {
1603 					token = cgi->Com_EParse(text, errhead, name);
1604 					if (!*text)
1605 						break;
1606 					if (*token == '}')
1607 						break;
1608 
1609 					if (Q_streq(token, "range")) {
1610 						/* this is the range of aircraft, must be translated into fuel */
1611 						token = cgi->Com_EParse(text, errhead, name);
1612 						if (!*text)
1613 							return;
1614 						cgi->Com_EParseValue(aircraftTemplate, token, V_INT, offsetof(aircraft_t, stats[AIR_STATS_FUELSIZE]), MEMBER_SIZEOF(aircraft_t, stats[0]));
1615 						if (aircraftTemplate->stats[AIR_STATS_SPEED] == 0)
1616 							cgi->Com_Error(ERR_DROP, "AIR_ParseAircraft: speed value must be entered before range value");
1617 						aircraftTemplate->stats[AIR_STATS_FUELSIZE] = (int) (2.0f * (float)SECONDS_PER_HOUR * aircraftTemplate->stats[AIR_STATS_FUELSIZE]) /
1618 							((float) aircraftTemplate->stats[AIR_STATS_SPEED]);
1619 					} else {
1620 						if (!Com_ParseBlockToken(name, text, aircraftTemplate, aircraft_param_vals, cp_campaignPool, token))
1621 							Com_Printf("AIR_ParseAircraft: Ignoring unknown param value '%s'\n", token);
1622 					}
1623 				} while (*text); /* dummy condition */
1624 			} else {
1625 				Com_Printf("AIR_ParseAircraft: unknown token \"%s\" ignored (aircraft %s)\n", token, name);
1626 				cgi->Com_EParse(text, errhead, name);
1627 			}
1628 		} /* assignAircraftItems */
1629 	} while (*text);
1630 
1631 	if (aircraftTemplate->productionCost == 0)
1632 		aircraftTemplate->productionCost = aircraftTemplate->price;
1633 
1634 	if (aircraftTemplate->size < AIRCRAFT_SMALL || aircraftTemplate->size > AIRCRAFT_LARGE)
1635 		Sys_Error("Invalid aircraft size given for '%s'", aircraftTemplate->id);
1636 }
1637 
1638 #ifdef DEBUG
AIR_ListCraftIndexes_f(void)1639 void AIR_ListCraftIndexes_f (void)
1640 {
1641 	Com_Printf("globalIDX\t(Craftname)\n");
1642 	AIR_Foreach(aircraft) {
1643 		Com_Printf("%i\t(%s)\n", aircraft->idx, aircraft->name);
1644 	}
1645 }
1646 
1647 /**
1648  * @brief Debug function that prints aircraft to game console
1649  */
AIR_ListAircraftSamples_f(void)1650 void AIR_ListAircraftSamples_f (void)
1651 {
1652 	int i = 0, max = ccs.numAircraftTemplates;
1653 	const value_t* vp;
1654 
1655 	Com_Printf("%i aircraft\n", max);
1656 	if (cgi->Cmd_Argc() == 2) {
1657 		max = atoi(cgi->Cmd_Argv(1));
1658 		if (max >= ccs.numAircraftTemplates || max < 0)
1659 			return;
1660 		i = max - 1;
1661 	}
1662 	for (; i < max; i++) {
1663 		aircraft_t* aircraftTemplate = &ccs.aircraftTemplates[i];
1664 		Com_Printf("aircraft: '%s'\n", aircraftTemplate->id);
1665 		for (vp = aircraft_vals; vp->string; vp++) {
1666 			Com_Printf("..%s: %s\n", vp->string, cgi->Com_ValueToStr(aircraftTemplate, vp->type, vp->ofs));
1667 		}
1668 		for (vp = aircraft_param_vals; vp->string; vp++) {
1669 			Com_Printf("..%s: %s\n", vp->string, cgi->Com_ValueToStr(aircraftTemplate, vp->type, vp->ofs));
1670 		}
1671 	}
1672 }
1673 #endif
1674 
1675 /*===============================================
1676 Aircraft functions related to UFOs or missions.
1677 ===============================================*/
1678 
1679 /**
1680  * @brief Notify aircraft that a mission has been removed.
1681  * @param[in] mission Pointer to the mission that has been removed.
1682  * @note Aircraft currently moving to the mission will be redirect to base
1683  */
AIR_AircraftsNotifyMissionRemoved(const mission_t * const mission)1684 void AIR_AircraftsNotifyMissionRemoved (const mission_t* const mission)
1685 {
1686 	AIR_Foreach(aircraft) {
1687 		if (aircraft->mission == mission)
1688 			AIR_AircraftReturnToBase(aircraft);
1689 	}
1690 }
1691 
1692 /**
1693  * @brief Notify that a UFO has been removed.
1694  * @param[in] ufo Pointer to UFO that has been removed.
1695  * @param[in] destroyed True if the UFO has been destroyed, false if it only landed.
1696  */
AIR_AircraftsNotifyUFORemoved(const aircraft_t * const ufo,bool destroyed)1697 void AIR_AircraftsNotifyUFORemoved (const aircraft_t* const ufo, bool destroyed)
1698 {
1699 	base_t* base;
1700 
1701 	assert(ufo);
1702 
1703 	/* Aircraft currently purchasing the specified ufo will be redirect to base */
1704 	AIR_Foreach(aircraft) {
1705 		if (ufo == aircraft->aircraftTarget) {
1706 			AIR_AircraftReturnToBase(aircraft);
1707 		} else if (destroyed && (ufo < aircraft->aircraftTarget)) {
1708 			aircraft->aircraftTarget--;
1709 		}
1710 	}
1711 
1712 	/** @todo this should be in a BDEF_NotifyUFORemoved callback */
1713 	base = nullptr;
1714 	while ((base = B_GetNext(base)) != nullptr) {
1715 		int i;
1716 		/* Base currently targeting the specified ufo loose their target */
1717 		for (i = 0; i < base->numBatteries; i++) {
1718 			baseWeapon_t* baseWeapon = &base->batteries[i];
1719 			if (baseWeapon->target == ufo)
1720 				baseWeapon->target = nullptr;
1721 			else if (destroyed && (baseWeapon->target > ufo))
1722 				baseWeapon->target--;
1723 		}
1724 		for (i = 0; i < base->numLasers; i++) {
1725 			baseWeapon_t* baseWeapon = &base->lasers[i];
1726 			if (baseWeapon->target == ufo)
1727 				baseWeapon->target = nullptr;
1728 			else if (destroyed && (baseWeapon->target > ufo))
1729 				baseWeapon->target--;
1730 		}
1731 	}
1732 }
1733 
1734 /**
1735  * @brief Notify that a UFO disappear from radars.
1736  * @param[in] ufo Pointer to a UFO that has disappeared.
1737  * @note Aircraft currently pursuing the specified UFO will be redirected to base
1738  */
AIR_AircraftsUFODisappear(const aircraft_t * const ufo)1739 void AIR_AircraftsUFODisappear (const aircraft_t* const ufo)
1740 {
1741 	AIR_Foreach(aircraft) {
1742 		if (aircraft->status == AIR_UFO && ufo == aircraft->aircraftTarget)
1743 			AIR_AircraftReturnToBase(aircraft);
1744 	}
1745 }
1746 
1747 /**
1748  * @brief funtion we need to find roots.
1749  * @param[in] c angle SOT
1750  * @param[in] B angle STI
1751  * @param[in] speedRatio ratio of speed of shooter divided by speed of target.
1752  * @param[in] a angle TOI
1753  * @note S is the position of the shooter, T the position of the target,
1754  * D the destination of the target and I the interception point where shooter should reach target.
1755  * @return value of the function.
1756  */
AIR_GetDestinationFunction(const float c,const float B,const float speedRatio,float a)1757 static inline float AIR_GetDestinationFunction (const float c, const float B, const float speedRatio, float a)
1758 {
1759 	return pow(cos(a) - cos(speedRatio * a) * cos(c), 2.)
1760 		- sin(c) * sin(c) * (sin(speedRatio * a) * sin(speedRatio * a) - sin(a) * sin(a) * sin(B) * sin(B));
1761 }
1762 
1763 /**
1764  * @brief derivative of the funtion we need to find roots.
1765  * @param[in] c angle SOT
1766  * @param[in] B angle STI
1767  * @param[in] speedRatio ratio of speed of shooter divided by speed of target.
1768  * @param[in] a angle TOI
1769  * @note S is the position of the shooter, T the position of the target,
1770  * D the destination of the target and I the interception point where shooter should reach target.
1771  * @return value of the derivative function.
1772  */
AIR_GetDestinationDerivativeFunction(const float c,const float B,const float speedRatio,float a)1773 static inline float AIR_GetDestinationDerivativeFunction (const float c, const float B, const float speedRatio, float a)
1774 {
1775 	return 2. * (cos(a) - cos(speedRatio * a) * cos(c)) * (- sin(a) + speedRatio * sin(speedRatio * a) * cos(c))
1776 		- sin(c) * sin(c) * (speedRatio * sin(2. * speedRatio * a) - sin(2. * a) * sin(B) * sin(B));
1777 }
1778 
1779 /**
1780  * @brief Find the roots of a function.
1781  * @param[in] c angle SOT
1782  * @param[in] B angle STI
1783  * @param[in] speedRatio ratio of speed of shooter divided by speed of target.
1784  * @param[in] start minimum value of the root to find
1785  * @note S is the position of the shooter, T the position of the target,
1786  * D the destination of the target and I the interception point where shooter should reach target.
1787  * @return root of the function.
1788  */
AIR_GetDestinationFindRoot(const float c,const float B,const float speedRatio,float start)1789 static float AIR_GetDestinationFindRoot (const float c, const float B, const float speedRatio, float start)
1790 {
1791 	const float BIG_STEP = .05;				/**< step for rough calculation. this value must be short enough so
1792 											 * that we're sure there's only 1 root in this range. */
1793 	const float PRECISION_ROOT = 0.000001;		/**< precision of the calculation */
1794 	const float MAXIMUM_VALUE_ROOT = 2. * M_PI;	/**< maximum value of the root to search */
1795 	float epsilon;							/**< precision of current point */
1796 	float begin, end, middle;				/**< abscissa of the point */
1797 	float fBegin, fEnd, fMiddle;			/**< ordinate of the point */
1798 	float fdBegin, fdEnd, fdMiddle;			/**< derivative of the point */
1799 
1800 	/* there may be several solution, first try to find roughly the smallest one */
1801 	end = start + PRECISION_ROOT / 10.;		/* don't start at 0: derivative is 0 */
1802 	fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
1803 	fdEnd = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, end);
1804 
1805 	do {
1806 		begin = end;
1807 		fBegin = fEnd;
1808 		fdBegin = fdEnd;
1809 		end = begin + BIG_STEP;
1810 		if (end > MAXIMUM_VALUE_ROOT) {
1811 			end = MAXIMUM_VALUE_ROOT;
1812 			fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
1813 			break;
1814 		}
1815 		fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
1816 		fdEnd = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, end);
1817 	} while  (fBegin * fEnd > 0 && fdBegin * fdEnd > 0);
1818 
1819 	if (fBegin * fEnd > 0) {
1820 		if (fdBegin * fdEnd < 0) {
1821 			/* the sign of derivative changed: we could have a root somewhere
1822 			 * between begin and end: try to narrow down the root until fBegin * fEnd < 0 */
1823 			middle = (begin + end) / 2.;
1824 			fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1825 			fdMiddle = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, middle);
1826 			do {
1827 				if (fdEnd * fdMiddle < 0) {
1828 					/* root is bigger than middle */
1829 					begin = middle;
1830 					fBegin = fMiddle;
1831 					fdBegin = fdMiddle;
1832 				} else if (fdBegin * fdMiddle < 0) {
1833 					/* root is smaller than middle */
1834 					end = middle;
1835 					fEnd = fMiddle;
1836 					fdEnd = fdMiddle;
1837 				} else {
1838 					cgi->Com_Error(ERR_DROP, "AIR_GetDestinationFindRoot: Error in calculation, can't find root");
1839 				}
1840 				middle = (begin + end) / 2.;
1841 				fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1842 				fdMiddle = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, middle);
1843 
1844 				epsilon = end - middle ;
1845 
1846 				if (epsilon < PRECISION_ROOT) {
1847 					/* this is only a root of the derivative: no root of the function itself
1848 					 * proceed with next value */
1849 					return AIR_GetDestinationFindRoot(c, B, speedRatio, end);
1850 				}
1851 			} while  (fBegin * fEnd > 0);
1852 		} else {
1853 			/* there's no solution, return default value */
1854 			Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationFindRoot: Did not find solution is range %.2f, %.2f\n", start, MAXIMUM_VALUE_ROOT);
1855 			return -10.;
1856 		}
1857 	}
1858 
1859 	/* now use dichotomy to get more precision on the solution */
1860 
1861 	middle = (begin + end) / 2.;
1862 	fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1863 
1864 	do {
1865 		if (fEnd * fMiddle < 0) {
1866 			/* root is bigger than middle */
1867 			begin = middle;
1868 			fBegin = fMiddle;
1869 		} else if (fBegin * fMiddle < 0) {
1870 			/* root is smaller than middle */
1871 			end = middle;
1872 			fEnd = fMiddle;
1873 		} else {
1874 			Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationFindRoot: Error in calculation, one of the value is nan\n");
1875 			return -10.;
1876 		}
1877 		middle = (begin + end) / 2.;
1878 		fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1879 
1880 		epsilon = end - middle ;
1881 	} while (epsilon > PRECISION_ROOT);
1882 	return middle;
1883 }
1884 
1885 /**
1886  * @brief Calculates the point where aircraft should go to intecept a moving target.
1887  * @param[in] shooter Pointer to shooting aircraft.
1888  * @param[in] target Pointer to target aircraft.
1889  * @param[out] dest Destination that shooting aircraft should aim to intercept target aircraft.
1890  * @todo only compute this calculation every time target changes destination, or one of the aircraft speed changes.
1891  * @sa AIR_SendAircraftPursuingUFO
1892  * @sa UFO_SendPursuingAircraft
1893  */
AIR_GetDestinationWhilePursuing(const aircraft_t * shooter,const aircraft_t * target,vec2_t dest)1894 void AIR_GetDestinationWhilePursuing (const aircraft_t* shooter, const aircraft_t* target, vec2_t dest)
1895 {
1896 	vec3_t shooterPos, targetPos, targetDestPos, shooterDestPos, rotationAxis;
1897 	vec3_t tangentVectTS, tangentVectTD;
1898 	float a, b, c, B;
1899 
1900 	const float speedRatio = (float)(shooter->stats[AIR_STATS_SPEED]) / target->stats[AIR_STATS_SPEED];
1901 
1902 	c = GetDistanceOnGlobe(shooter->pos, target->pos) * torad;
1903 
1904 	/* Convert aircraft position into cartesian frame */
1905 	PolarToVec(shooter->pos, shooterPos);
1906 	PolarToVec(target->pos, targetPos);
1907 	PolarToVec(target->route.point[target->route.numPoints - 1], targetDestPos);
1908 
1909 	/** In the following, we note S the position of the shooter, T the position of the target,
1910 	 * D the destination of the target and I the interception point where shooter should reach target
1911 	 * O is the center of earth.
1912 	 * A, B and C are the angles TSI, STI and SIT
1913 	 * a, b, and c are the angles TOI, SOI and SOT
1914 	 *
1915 	 * According to geometry on a sphere, the values defined above must be solutions of both equations:
1916 	 *		sin(A) / sin(a) = sin(B) / sin(b)
1917 	 *		cos(a) = cos(b) * cos(c) + sin(b) * sin(c) * cos(A)
1918 	 * And we have another equation, given by the fact that shooter and target must reach I at the same time:
1919 	 *		shooterSpeed * a = targetSpeed * b
1920 	 * We the want to find and equation linking a, c and B (we know the last 2 values). We therefore
1921 	 * eliminate b, then A, to get the equation we need to solve:
1922 	 *		pow(cos(a) - cos(speedRatio * a) * cos(c), 2.)
1923 	 *		- sin(c) * sin(c) * (sin(speedRatio * a) * sin(speedRatio * a) - sin(a) * sin(a) * sin(B) * sin(B)) = 0
1924 	 */
1925 
1926 	/* Get first vector (tangent to triangle in T, in the direction of D) */
1927 	CrossProduct(targetPos, shooterPos, rotationAxis);
1928 	VectorNormalize(rotationAxis);
1929 	RotatePointAroundVector(tangentVectTS, rotationAxis, targetPos, 90.0f);
1930 	/* Get second vector (tangent to triangle in T, in the direction of S) */
1931 	CrossProduct(targetPos, targetDestPos, rotationAxis);
1932 	VectorNormalize(rotationAxis);
1933 	RotatePointAroundVector(tangentVectTD, rotationAxis, targetPos, 90.0f);
1934 
1935 	/* Get angle B of the triangle (in radian) */
1936 	B = acos(DotProduct(tangentVectTS, tangentVectTD));
1937 
1938 	/* Look for a value, as long as we don't have a proper value */
1939 	for (a = 0;;) {
1940 		a = AIR_GetDestinationFindRoot(c, B, speedRatio, a);
1941 
1942 		if (a < 0.) {
1943 			/* we couldn't find a root on the whole range */
1944 			break;
1945 		}
1946 
1947 		/* Get rotation vector */
1948 		CrossProduct(targetPos, targetDestPos, rotationAxis);
1949 		VectorNormalize(rotationAxis);
1950 
1951 		/* Rotate target position of dist to find destination point */
1952 		RotatePointAroundVector(shooterDestPos, rotationAxis, targetPos, a * todeg);
1953 		VecToPolar(shooterDestPos, dest);
1954 
1955 		b = GetDistanceOnGlobe(shooter->pos, dest) * torad;
1956 
1957 		if (fabs(b - speedRatio * a) < .1)
1958 			break;
1959 
1960 		Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationWhilePursuing: reject solution: doesn't fit %.2f == %.2f\n", b, speedRatio * a);
1961 	}
1962 
1963 	if (a < 0.) {
1964 		/* did not find solution, go directly to target direction */
1965 		Vector2Copy(target->pos, dest);
1966 		return;
1967 	}
1968 
1969 	/** @todo add EQUAL_EPSILON here? */
1970 	/* make sure we don't get a NAN value */
1971 	assert(dest[0] <= 180.0f && dest[0] >= -180.0f && dest[1] <= 90.0f && dest[1] >= -90.0f);
1972 }
1973 
1974 /**
1975  * @brief Make the specified aircraft purchasing a UFO.
1976  * @param[in] aircraft Pointer to an aircraft which will hunt for a UFO.
1977  * @param[in] ufo Pointer to a UFO.
1978  */
AIR_SendAircraftPursuingUFO(aircraft_t * aircraft,aircraft_t * ufo)1979 bool AIR_SendAircraftPursuingUFO (aircraft_t* aircraft, aircraft_t* ufo)
1980 {
1981 	vec2_t dest;
1982 
1983 	if (!aircraft)
1984 		return false;
1985 
1986 	/* if aircraft was in base */
1987 	if (AIR_IsAircraftInBase(aircraft)) {
1988 		/* reload its ammunition */
1989 		AII_ReloadAircraftWeapons(aircraft);
1990 	}
1991 
1992 	AIR_GetDestinationWhilePursuing(aircraft, ufo, dest);
1993 	/* check if aircraft has enough fuel */
1994 	if (!AIR_AircraftHasEnoughFuel(aircraft, dest)) {
1995 		/* did not find solution, go directly to target direction if enough fuel */
1996 		if (AIR_AircraftHasEnoughFuel(aircraft, ufo->pos)) {
1997 			Com_DPrintf(DEBUG_CLIENT, "AIR_SendAircraftPursuingUFO: not enough fuel to anticipate target movement: go directly to target position\n");
1998 			Vector2Copy(ufo->pos, dest);
1999 		} else {
2000 			MS_AddNewMessage(_("Notice"), va(_("Craft %s has not enough fuel to intercept UFO: fly back to %s."), aircraft->name, aircraft->homebase->name));
2001 			AIR_AircraftReturnToBase(aircraft);
2002 			return false;
2003 		}
2004 	}
2005 
2006 	GEO_CalcLine(aircraft->pos, dest, &aircraft->route);
2007 	aircraft->status = AIR_UFO;
2008 	aircraft->time = 0;
2009 	aircraft->point = 0;
2010 	aircraft->aircraftTarget = ufo;
2011 	return true;
2012 }
2013 
2014 /*============================================
2015 Aircraft functions related to team handling.
2016 ============================================*/
2017 
2018 /**
2019  * @brief Resets team in given aircraft.
2020  * @param[in] aircraft Pointer to an aircraft, where the team will be reset.
2021  */
AIR_ResetAircraftTeam(aircraft_t * aircraft)2022 void AIR_ResetAircraftTeam (aircraft_t* aircraft)
2023 {
2024 	cgi->LIST_Delete(&aircraft->acTeam);
2025 }
2026 
2027 /**
2028  * @brief Adds given employee to given aircraft.
2029  * @param[in] aircraft Pointer to an aircraft, to which we will add employee.
2030  * @param[in] employee The employee to add to the aircraft.
2031  * @note this is responsible for adding soldiers to a team in dropship
2032  */
AIR_AddToAircraftTeam(aircraft_t * aircraft,Employee * employee)2033 bool AIR_AddToAircraftTeam (aircraft_t* aircraft, Employee* employee)
2034 {
2035 	if (!employee)
2036 		return false;
2037 
2038 	if (!aircraft)
2039 		return false;
2040 
2041 	if (AIR_GetTeamSize(aircraft) < aircraft->maxTeamSize) {
2042 		cgi->LIST_AddPointer(&aircraft->acTeam, employee);
2043 		return true;
2044 	}
2045 
2046 	return false;
2047 }
2048 
2049 /**
2050  * @brief Checks whether given employee is in given aircraft
2051  * @param[in] aircraft The aircraft to check
2052  * @param[in] employee Employee to check.
2053  * @return @c true if the given employee is assigned to the given aircraft.
2054  */
AIR_IsInAircraftTeam(const aircraft_t * aircraft,const Employee * employee)2055 bool AIR_IsInAircraftTeam (const aircraft_t* aircraft, const Employee* employee)
2056 {
2057 	assert(aircraft);
2058 	assert(employee);
2059 	return cgi->LIST_GetPointer(aircraft->acTeam, employee) != nullptr;
2060 }
2061 
2062 /**
2063  * @brief Counts the number of soldiers in given aircraft.
2064  * @param[in] aircraft Pointer to the aircraft, for which we return the amount of soldiers.
2065  * @return Amount of soldiers.
2066  */
AIR_GetTeamSize(const aircraft_t * aircraft)2067 int AIR_GetTeamSize (const aircraft_t* aircraft)
2068 {
2069 	assert(aircraft);
2070 	return cgi->LIST_Count(aircraft->acTeam);
2071 }
2072 
2073 /**
2074  * @brief Assign a pilot to an aircraft
2075  * @param[out] aircraft Pointer to the aircraft to add pilot to
2076  * @param[in] pilot Pointer to the pilot to add
2077  * @return @c true if the assignment was successful (there wasn't a pilot
2078  * assigned), @c false if there was already a pilot assigned and we tried
2079  * to assign a new one (@c pilot isn't @c nullptr).
2080  */
AIR_SetPilot(aircraft_t * aircraft,Employee * pilot)2081 bool AIR_SetPilot (aircraft_t* aircraft, Employee* pilot)
2082 {
2083 	if (aircraft->pilot == nullptr || pilot == nullptr) {
2084 		aircraft->pilot = pilot;
2085 		return true;
2086 	}
2087 
2088 	return false;
2089 }
2090 
2091 /**
2092  * @brief Get pilot of an aircraft
2093  * @param[in] aircraft Pointer to the aircraft
2094  * @return @c nullptr if there is no pilot assigned to this craft, the employee pointer otherwise
2095  */
AIR_GetPilot(const aircraft_t * aircraft)2096 Employee* AIR_GetPilot (const aircraft_t* aircraft)
2097 {
2098 	const Employee* e = aircraft->pilot;
2099 
2100 	if (!e)
2101 		return nullptr;
2102 
2103 	return E_GetEmployeeByTypeFromChrUCN(e->getType(), e->chr.ucn);
2104 }
2105 
2106 /**
2107  * @brief Determine if an aircraft's pilot survived a crash, based on his piloting skill (and a bit of randomness)
2108  * @param[in] aircraft Pointer to crashed aircraft
2109  * @return true if survived, false if not
2110  */
AIR_PilotSurvivedCrash(const aircraft_t * aircraft)2111 bool AIR_PilotSurvivedCrash (const aircraft_t* aircraft)
2112 {
2113 	if (aircraft == nullptr)
2114 		return false;
2115 
2116 	if (aircraft->pilot == nullptr)
2117 		return false;
2118 
2119 	const int pilotSkill = aircraft->pilot->chr.score.skills[SKILL_PILOTING];
2120 	float baseProbability = (float) pilotSkill;
2121 
2122 	const byte* color = GEO_GetColor(aircraft->pos, MAPTYPE_TERRAIN, nullptr);
2123 	/* Crash over water: chances for survival are very bad */
2124 	if (MapIsWater(color)) {
2125 		baseProbability /= 10.0f;
2126 	}
2127 
2128 	/* Crash over arctic or wasted terrain; fare a little better */
2129 	if (MapIsArctic(color) || MapIsWasted(color)) {
2130 		baseProbability /= 8.0f;
2131 	}
2132 
2133 	/* Crash over mountain, desert or cold terrain: survive a little longer */
2134 	if (MapIsCold(color) || MapIsDesert(color) || MapIsMountain(color)) {
2135 		baseProbability /= 3.0f;
2136 	}
2137 
2138 	/* Crash over tropical or fertile are: This is the good life :) */
2139 	if (MapIsGrass(color) || MapIsTropical(color)) {
2140 		baseProbability *= 2.5f;
2141 	}
2142 
2143 	/* Add a random factor to our probability */
2144 	float randomProbability = crand() * (float) pilotSkill;
2145 	if (randomProbability > 0.25f * baseProbability) {
2146 		while (randomProbability > 0.25f * baseProbability)
2147 			randomProbability /= 2.0f;
2148 	}
2149 
2150 	const float survivalProbability = baseProbability + randomProbability;
2151 	return survivalProbability >= (float) pilotSkill;
2152 }
2153 
2154 /**
2155  * @brief Adds the pilot to the first available aircraft at the specified base.
2156  * @param[in] base Which base has aircraft to add the pilot to.
2157  * @param[in] pilot Which pilot to add.
2158  */
AIR_AutoAddPilotToAircraft(const base_t * base,Employee * pilot)2159 void AIR_AutoAddPilotToAircraft (const base_t* base, Employee* pilot)
2160 {
2161 	AIR_ForeachFromBase(aircraft, base) {
2162 		if (AIR_SetPilot(aircraft, pilot))
2163 			break;
2164 	}
2165 }
2166 
2167 /**
2168  * @brief Checks to see if the pilot is in any aircraft at this base.
2169  * If he is then he is removed from that aircraft.
2170  * @param[in] base Which base has the aircraft to search for the employee in.
2171  * @param[in] pilot Which pilot to search for.
2172  */
AIR_RemovePilotFromAssignedAircraft(const base_t * base,const Employee * pilot)2173 void AIR_RemovePilotFromAssignedAircraft (const base_t* base, const Employee* pilot)
2174 {
2175 	AIR_ForeachFromBase(aircraft, base) {
2176 		if (AIR_GetPilot(aircraft) == pilot) {
2177 			AIR_SetPilot(aircraft, nullptr);
2178 			break;
2179 		}
2180 	}
2181 }
2182 
2183 /**
2184  * @brief Get the all the unique weapon ranges of this aircraft.
2185  * @param[in] slot Pointer to the aircrafts weapon slot list.
2186  * @param[in] maxSlot maximum number of weapon slots in aircraft.
2187  * @param[out] weaponRanges An array containing a unique list of weapons ranges.
2188  * @return Number of unique weapons ranges.
2189  */
AIR_GetAircraftWeaponRanges(const aircraftSlot_t * slot,int maxSlot,float * weaponRanges)2190 int AIR_GetAircraftWeaponRanges (const aircraftSlot_t* slot, int maxSlot, float* weaponRanges)
2191 {
2192 	int idxSlot;
2193 	float allWeaponRanges[MAX_AIRCRAFTSLOT];
2194 	int numAllWeaponRanges = 0;
2195 	int numUniqueWeaponRanges = 0;
2196 
2197 	assert(slot);
2198 
2199 	/* We choose the usable weapon to add to the weapons array */
2200 	for (idxSlot = 0; idxSlot < maxSlot; idxSlot++) {
2201 		const aircraftSlot_t* weapon = slot + idxSlot;
2202 		const objDef_t* ammo = weapon->ammo;
2203 
2204 		if (!ammo)
2205 			continue;
2206 
2207 		allWeaponRanges[numAllWeaponRanges] = ammo->craftitem.stats[AIR_STATS_WRANGE];
2208 		numAllWeaponRanges++;
2209 	}
2210 
2211 	if (numAllWeaponRanges > 0) {
2212 		/* sort the list of all weapon ranges and create an array with only the unique ranges */
2213 		qsort(allWeaponRanges, numAllWeaponRanges, sizeof(allWeaponRanges[0]), Q_FloatSort);
2214 
2215 		int idxAllWeap;
2216 		for (idxAllWeap = 0; idxAllWeap < numAllWeaponRanges; idxAllWeap++) {
2217 			if (allWeaponRanges[idxAllWeap] != weaponRanges[numUniqueWeaponRanges - 1] || idxAllWeap == 0) {
2218 				weaponRanges[numUniqueWeaponRanges] = allWeaponRanges[idxAllWeap];
2219 				numUniqueWeaponRanges++;
2220 			}
2221 		}
2222 	}
2223 
2224 	return numUniqueWeaponRanges;
2225 }
2226 
2227 /**
2228  * @brief Saves an route plan of an aircraft
2229  * @param[out] node XML Node structure, where we write the information to
2230  * @param[in] route Aircraft route plan
2231  */
AIR_SaveRouteXML(xmlNode_t * node,const mapline_t * route)2232 static void AIR_SaveRouteXML (xmlNode_t* node, const mapline_t* route)
2233 {
2234 	int j;
2235 	xmlNode_t* subnode;
2236 
2237 	subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_ROUTE);
2238 	cgi->XML_AddFloatValue(subnode, SAVE_AIRCRAFT_ROUTE_DISTANCE, route->distance);
2239 	for (j = 0; j < route->numPoints; j++) {
2240 		cgi->XML_AddPos2(subnode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[j]);
2241 	}
2242 }
2243 
2244 /**
2245  * @brief Saves an item slot
2246  * @param[in] slot Pointer to the slot where item is.
2247  * @param[in] num Number of slots for this aircraft.
2248  * @param[out] p XML Node structure, where we write the information to
2249  * @param[in] p pointer where information are written.
2250  * @param[in] weapon True if the slot is a weapon slot.
2251  * @sa B_Save
2252  * @sa AII_InitialiseSlot
2253  */
AIR_SaveAircraftSlotsXML(const aircraftSlot_t * slot,const int num,xmlNode_t * p,bool weapon)2254 static void AIR_SaveAircraftSlotsXML (const aircraftSlot_t* slot, const int num, xmlNode_t* p, bool weapon)
2255 {
2256 	int i;
2257 
2258 	for (i = 0; i < num; i++) {
2259 		xmlNode_t* sub = cgi->XML_AddNode(p, SAVE_AIRCRAFT_SLOT);
2260 		AII_SaveOneSlotXML(sub, &slot[i], weapon);
2261 	}
2262 }
2263 
2264 /**
2265  * @brief Saves an aircraft
2266  * @param[out] p XML Node structure, where we write the information to
2267  * @param[in] aircraft Aircraft we save
2268  * @param[in] isUfo If this aircraft is a UFO
2269  */
AIR_SaveAircraftXML(xmlNode_t * p,const aircraft_t * const aircraft,bool const isUfo)2270 static bool AIR_SaveAircraftXML (xmlNode_t* p, const aircraft_t* const aircraft, bool const isUfo)
2271 {
2272 	xmlNode_t* node;
2273 	xmlNode_t* subnode;
2274 	int l;
2275 	const Employee* pilot;
2276 
2277 	cgi->Com_RegisterConstList(saveAircraftConstants);
2278 
2279 	node = cgi->XML_AddNode(p, SAVE_AIRCRAFT_AIRCRAFT);
2280 
2281 	cgi->XML_AddInt(node, SAVE_AIRCRAFT_IDX, aircraft->idx);
2282 	cgi->XML_AddString(node, SAVE_AIRCRAFT_ID, aircraft->id);
2283 	cgi->XML_AddString(node, SAVE_AIRCRAFT_NAME, aircraft->name);
2284 
2285 	cgi->XML_AddString(node, SAVE_AIRCRAFT_STATUS, cgi->Com_GetConstVariable(SAVE_AIRCRAFTSTATUS_NAMESPACE, aircraft->status));
2286 	cgi->XML_AddInt(node, SAVE_AIRCRAFT_FUEL, aircraft->fuel);
2287 	cgi->XML_AddInt(node, SAVE_AIRCRAFT_DAMAGE, aircraft->damage);
2288 	cgi->XML_AddPos3(node, SAVE_AIRCRAFT_POS, aircraft->pos);
2289 	cgi->XML_AddPos3(node, SAVE_AIRCRAFT_DIRECTION, aircraft->direction);
2290 	cgi->XML_AddInt(node, SAVE_AIRCRAFT_POINT, aircraft->point);
2291 	cgi->XML_AddInt(node, SAVE_AIRCRAFT_TIME, aircraft->time);
2292 
2293 	subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_WEAPONS);
2294 	AIR_SaveAircraftSlotsXML(aircraft->weapons, aircraft->maxWeapons, subnode, true);
2295 	subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_SHIELDS);
2296 	AIR_SaveAircraftSlotsXML(&aircraft->shield, 1, subnode, false);
2297 	subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_ELECTRONICS);
2298 	AIR_SaveAircraftSlotsXML(aircraft->electronics, aircraft->maxElectronics, subnode, false);
2299 
2300 	AIR_SaveRouteXML(node, &aircraft->route);
2301 
2302 	if (isUfo) {
2303 #ifdef DEBUG
2304 		if (!aircraft->mission)
2305 			Com_Printf("Error: UFO '%s'is not linked to any mission\n", aircraft->id);
2306 #endif
2307 		cgi->XML_AddString(node, SAVE_AIRCRAFT_MISSIONID, aircraft->mission->id);
2308 		/** detection id and time */
2309 		cgi->XML_AddInt(node, SAVE_AIRCRAFT_DETECTIONIDX, aircraft->detectionIdx);
2310 		cgi->XML_AddDate(node, SAVE_AIRCRAFT_LASTSPOTTED_DATE, aircraft->lastSpotted.day, aircraft->lastSpotted.sec);
2311 	} else {
2312 		if (aircraft->status == AIR_MISSION) {
2313 			assert(aircraft->mission);
2314 			cgi->XML_AddString(node, SAVE_AIRCRAFT_MISSIONID, aircraft->mission->id);
2315 		}
2316 		if (aircraft->homebase) {
2317 			cgi->XML_AddInt(node, SAVE_AIRCRAFT_HOMEBASE, aircraft->homebase->idx);
2318 		}
2319 	}
2320 
2321 	if (aircraft->aircraftTarget) {
2322 		if (isUfo)
2323 			cgi->XML_AddInt(node, SAVE_AIRCRAFT_AIRCRAFTTARGET, aircraft->aircraftTarget->idx);
2324 		else
2325 			cgi->XML_AddInt(node, SAVE_AIRCRAFT_AIRCRAFTTARGET, UFO_GetGeoscapeIDX(aircraft->aircraftTarget));
2326 	}
2327 
2328 	subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_AIRSTATS);
2329 	for (l = 0; l < AIR_STATS_MAX; l++) {
2330 #ifdef DEBUG
2331 		/* UFO HP can be < 0 if the UFO has been destroyed */
2332 		if (!(isUfo && l == AIR_STATS_DAMAGE) && aircraft->stats[l] < 0)
2333 			Com_Printf("Warning: ufo '%s' stats %i: %i is smaller than 0\n", aircraft->id, l, aircraft->stats[l]);
2334 #endif
2335 		if (aircraft->stats[l] != 0) {
2336 			xmlNode_t* statNode = cgi->XML_AddNode(subnode, SAVE_AIRCRAFT_AIRSTAT);
2337 			cgi->XML_AddString(statNode, SAVE_AIRCRAFT_AIRSTATID, cgi->Com_GetConstVariable(SAVE_AIRCRAFTSTAT_NAMESPACE, l));
2338 			cgi->XML_AddLong(statNode, SAVE_AIRCRAFT_VAL, aircraft->stats[l]);
2339 		}
2340 	}
2341 
2342 	cgi->XML_AddBoolValue(node, SAVE_AIRCRAFT_DETECTED, aircraft->detected);
2343 	cgi->XML_AddBoolValue(node, SAVE_AIRCRAFT_LANDED, aircraft->landed);
2344 
2345 	cgi->Com_UnregisterConstList(saveAircraftConstants);
2346 
2347 	/* All other informations are not needed for ufos */
2348 	if (isUfo)
2349 		return true;
2350 
2351 	subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_AIRCRAFTTEAM);
2352 	LIST_Foreach(aircraft->acTeam, Employee, employee) {
2353 		xmlNode_t* ssnode = cgi->XML_AddNode(subnode, SAVE_AIRCRAFT_MEMBER);
2354 		cgi->XML_AddInt(ssnode, SAVE_AIRCRAFT_TEAM_UCN, employee->chr.ucn);
2355 	}
2356 
2357 	pilot = AIR_GetPilot(aircraft);
2358 	if (pilot)
2359 		cgi->XML_AddInt(node, SAVE_AIRCRAFT_PILOTUCN, pilot->chr.ucn);
2360 
2361 	/* itemcargo */
2362 	subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_CARGO);
2363 	for (l = 0; l < aircraft->itemTypes; l++) {
2364 		xmlNode_t* ssnode = cgi->XML_AddNode(subnode, SAVE_AIRCRAFT_ITEM);
2365 		assert(aircraft->itemcargo[l].item);
2366 		cgi->XML_AddString(ssnode, SAVE_AIRCRAFT_ITEMID, aircraft->itemcargo[l].item->id);
2367 		cgi->XML_AddInt(ssnode, SAVE_AIRCRAFT_AMOUNT, aircraft->itemcargo[l].amount);
2368 	}
2369 
2370 	/* aliencargo */
2371 	if (aircraft->alienCargo != nullptr) {
2372 		subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_ALIENCARGO);
2373 		if (!subnode)
2374 			return false;
2375 		aircraft->alienCargo->save(subnode);
2376 	}
2377 
2378 	return true;
2379 }
2380 
2381 /**
2382  * @brief Save callback for savegames in xml format
2383  * @sa AIR_LoadXML
2384  * @sa B_SaveXML
2385  * @sa SAV_GameSaveXML
2386  */
AIR_SaveXML(xmlNode_t * parent)2387 bool AIR_SaveXML (xmlNode_t* parent)
2388 {
2389 	int i;
2390 	xmlNode_t* node, *snode;
2391 
2392 	/* save phalanx aircraft */
2393 	snode = cgi->XML_AddNode(parent, SAVE_AIRCRAFT_PHALANX);
2394 	AIR_Foreach(aircraft) {
2395 		AIR_SaveAircraftXML(snode, aircraft, false);
2396 	}
2397 
2398 	/* save the ufos on geoscape */
2399 	snode = cgi->XML_AddNode(parent, SAVE_AIRCRAFT_UFOS);
2400 	for (i = 0; i < MAX_UFOONGEOSCAPE; i++) {
2401 		const aircraft_t* ufo = UFO_GetByIDX(i);
2402 		if (!ufo || (ufo->id == nullptr))
2403 			continue;
2404 		AIR_SaveAircraftXML(snode, ufo, true);
2405 	}
2406 
2407 	/* Save projectiles. */
2408 	node = cgi->XML_AddNode(parent, SAVE_AIRCRAFT_PROJECTILES);
2409 	if (!AIRFIGHT_SaveXML(node))
2410 		return false;
2411 
2412 	return true;
2413 }
2414 
2415 /**
2416  * @brief Loads the weapon slots of an aircraft.
2417  * @param[in] aircraft Pointer to the aircraft.
2418  * @param[out] slot Pointer to the slot where item should be added.
2419  * @param[in] p XML Node structure, where we get the information from
2420  * @param[in] weapon True if the slot is a weapon slot.
2421  * @param[in] max Maximum number of slots for this aircraft that should be loaded.
2422  * @sa B_Load
2423  * @sa B_SaveAircraftSlots
2424  */
AIR_LoadAircraftSlotsXML(aircraft_t * aircraft,aircraftSlot_t * slot,xmlNode_t * p,bool weapon,const int max)2425 static void AIR_LoadAircraftSlotsXML (aircraft_t* aircraft, aircraftSlot_t* slot, xmlNode_t* p, bool weapon, const int max)
2426 {
2427 	xmlNode_t* act;
2428 	int i;
2429 	for (i = 0, act = cgi->XML_GetNode(p, SAVE_AIRCRAFT_SLOT); act && i <= max; act = cgi->XML_GetNextNode(act, p, SAVE_AIRCRAFT_SLOT), i++) {
2430 		slot[i].aircraft = aircraft;
2431 		AII_LoadOneSlotXML(act, &slot[i], weapon);
2432 	}
2433 	if (i > max)
2434 		Com_Printf("Error: Trying to assign more than max (%d) Aircraft Slots (cur is %d)\n", max, i);
2435 
2436 }
2437 
2438 /**
2439  * @brief Loads the route of an aircraft
2440  * @param[in] p XML Node structure, where we get the information from
2441  * @param[out] route Route points of the aircraft
2442  */
AIR_LoadRouteXML(xmlNode_t * p,mapline_t * route)2443 static bool AIR_LoadRouteXML (xmlNode_t* p, mapline_t* route)
2444 {
2445 	xmlNode_t* actual;
2446 	xmlNode_t* snode;
2447 	int count = 0;
2448 
2449 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_ROUTE);
2450 	if (!snode)
2451 		return false;
2452 
2453 	for (actual = cgi->XML_GetPos2(snode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[count]); actual && count <= LINE_MAXPTS;
2454 			actual = cgi->XML_GetNextPos2(actual, snode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[++count]))
2455 		;
2456 	if (count > LINE_MAXPTS) {
2457 		Com_Printf("AIR_Load: number of points (%i) for UFO route exceed maximum value (%i)\n", count, LINE_MAXPTS);
2458 		return false;
2459 	}
2460 	route->numPoints = count;
2461 	route->distance = cgi->XML_GetFloat(snode, SAVE_AIRCRAFT_ROUTE_DISTANCE, 0.0);
2462 	return true;
2463 }
2464 
2465 /**
2466  * @brief Loads an Aircraft from the savegame
2467  * @param[in] p XML Node structure, where we get the information from
2468  * @param[out] craft Pointer to the aircraft
2469  */
AIR_LoadAircraftXML(xmlNode_t * p,aircraft_t * craft)2470 static bool AIR_LoadAircraftXML (xmlNode_t* p, aircraft_t* craft)
2471 {
2472 	xmlNode_t* snode;
2473 	xmlNode_t* ssnode;
2474 	const char* statusId;
2475 	/* vars, if aircraft wasn't found */
2476 	int tmpInt;
2477 	int l, status;
2478 	const char* s = cgi->XML_GetString(p, SAVE_AIRCRAFT_ID);
2479 	const aircraft_t* crafttype = AIR_GetAircraft(s);
2480 
2481 	/* Copy all datas that don't need to be saved (tpl, hangar,...) */
2482 	*craft = *crafttype;
2483 
2484 	tmpInt = cgi->XML_GetInt(p, SAVE_AIRCRAFT_HOMEBASE, MAX_BASES);
2485 	craft->homebase = (tmpInt != MAX_BASES) ? B_GetBaseByIDX(tmpInt) : nullptr;
2486 
2487 	craft->idx = cgi->XML_GetInt(p, SAVE_AIRCRAFT_IDX, -1);
2488 	if (craft->idx < 0) {
2489 		Com_Printf("Invalid (or no) aircraft index %i\n", craft->idx);
2490 		return false;
2491 	}
2492 
2493 	cgi->Com_RegisterConstList(saveAircraftConstants);
2494 
2495 	statusId = cgi->XML_GetString(p, SAVE_AIRCRAFT_STATUS);
2496 	if (!cgi->Com_GetConstIntFromNamespace(SAVE_AIRCRAFTSTATUS_NAMESPACE, statusId, &status)) {
2497 		Com_Printf("Invalid aircraft status '%s'\n", statusId);
2498 		cgi->Com_UnregisterConstList(saveAircraftConstants);
2499 		return false;
2500 	}
2501 
2502 	craft->status = (aircraftStatus_t)status;
2503 	craft->fuel = cgi->XML_GetInt(p, SAVE_AIRCRAFT_FUEL, 0);
2504 	craft->damage = cgi->XML_GetInt(p, SAVE_AIRCRAFT_DAMAGE, 0);
2505 	cgi->XML_GetPos3(p, SAVE_AIRCRAFT_POS, craft->pos);
2506 
2507 	cgi->XML_GetPos3(p, SAVE_AIRCRAFT_DIRECTION, craft->direction);
2508 	craft->point = cgi->XML_GetInt(p, SAVE_AIRCRAFT_POINT, 0);
2509 	craft->time = cgi->XML_GetInt(p, SAVE_AIRCRAFT_TIME, 0);
2510 
2511 	if (!AIR_LoadRouteXML(p, &craft->route)) {
2512 		cgi->Com_UnregisterConstList(saveAircraftConstants);
2513 		return false;
2514 	}
2515 
2516 	s = cgi->XML_GetString(p, SAVE_AIRCRAFT_NAME);
2517 	if (s[0] == '\0')
2518 		s = _(craft->defaultName);
2519 	Q_strncpyz(craft->name, s, sizeof(craft->name));
2520 
2521 	s = cgi->XML_GetString(p, SAVE_AIRCRAFT_MISSIONID);
2522 	craft->missionID = Mem_PoolStrDup(s, cp_campaignPool, 0);
2523 
2524 	if (!craft->homebase) {
2525 		/* detection id and time */
2526 		craft->detectionIdx = cgi->XML_GetInt(p, SAVE_AIRCRAFT_DETECTIONIDX, 0);
2527 		cgi->XML_GetDate(p, SAVE_AIRCRAFT_LASTSPOTTED_DATE, &craft->lastSpotted.day, &craft->lastSpotted.sec);
2528 	}
2529 
2530 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_AIRSTATS);
2531 	for (ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_AIRSTAT); ssnode; ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRSTAT)) {
2532 		const char* statId =  cgi->XML_GetString(ssnode, SAVE_AIRCRAFT_AIRSTATID);
2533 		int idx;
2534 
2535 		if (!cgi->Com_GetConstIntFromNamespace(SAVE_AIRCRAFTSTAT_NAMESPACE, statId, &idx)) {
2536 			Com_Printf("Invalid aircraft stat '%s'\n", statId);
2537 			cgi->Com_UnregisterConstList(saveAircraftConstants);
2538 			return false;
2539 		}
2540 		craft->stats[idx] = cgi->XML_GetLong(ssnode, SAVE_AIRCRAFT_VAL, 0);
2541 #ifdef DEBUG
2542 		/* UFO HP can be < 0 if the UFO has been destroyed */
2543 		if (!(!craft->homebase && idx == AIR_STATS_DAMAGE) && craft->stats[idx] < 0)
2544 			Com_Printf("Warning: ufo '%s' stats %i: %i is smaller than 0\n", craft->id, idx, craft->stats[idx]);
2545 #endif
2546 	}
2547 
2548 	craft->detected = cgi->XML_GetBool(p, SAVE_AIRCRAFT_DETECTED, false);
2549 	craft->landed = cgi->XML_GetBool(p, SAVE_AIRCRAFT_LANDED, false);
2550 
2551 	tmpInt = cgi->XML_GetInt(p, SAVE_AIRCRAFT_AIRCRAFTTARGET, -1);
2552 	if (tmpInt == -1)
2553 		craft->aircraftTarget = nullptr;
2554 	else if (!craft->homebase)
2555 		craft->aircraftTarget = AIR_AircraftGetFromIDX(tmpInt);
2556 	else
2557 		craft->aircraftTarget = ccs.ufos + tmpInt;
2558 
2559 	/* read equipment slots */
2560 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_WEAPONS);
2561 	AIR_LoadAircraftSlotsXML(craft, craft->weapons, snode, true, craft->maxWeapons);
2562 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_SHIELDS);
2563 	AIR_LoadAircraftSlotsXML(craft, &craft->shield, snode, false, 1);
2564 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_ELECTRONICS);
2565 	AIR_LoadAircraftSlotsXML(craft, craft->electronics, snode, false, craft->maxElectronics);
2566 
2567 	cgi->Com_UnregisterConstList(saveAircraftConstants);
2568 
2569 	/* All other informations are not needed for ufos */
2570 	if (!craft->homebase)
2571 		return true;
2572 
2573 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_AIRCRAFTTEAM);
2574 	for (ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_MEMBER); AIR_GetTeamSize(craft) < craft->maxTeamSize && ssnode;
2575 			ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_MEMBER)) {
2576 		const int ucn = cgi->XML_GetInt(ssnode, SAVE_AIRCRAFT_TEAM_UCN, -1);
2577 		if (ucn != -1)
2578 			cgi->LIST_AddPointer(&craft->acTeam, E_GetEmployeeFromChrUCN(ucn));
2579 	}
2580 
2581 	tmpInt = cgi->XML_GetInt(p, SAVE_AIRCRAFT_PILOTUCN, -1);
2582 	/* the employee subsystem is loaded after the base subsystem
2583 	 * this means, that the pilot pointer is not (really) valid until
2584 	 * E_Load was called, too */
2585 	if (tmpInt != -1)
2586 		AIR_SetPilot(craft, E_GetEmployeeFromChrUCN(tmpInt));
2587 	else
2588 		AIR_SetPilot(craft, nullptr);
2589 
2590 	RADAR_Initialise(&craft->radar, crafttype->radar.range, crafttype->radar.trackingRange, 1.0f, false);
2591 	RADAR_InitialiseUFOs(&craft->radar);
2592 	craft->radar.ufoDetectionProbability = 	crafttype->radar.ufoDetectionProbability;
2593 
2594 	/* itemcargo */
2595 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_CARGO);
2596 	for (l = 0, ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_ITEM); l < MAX_CARGO && ssnode;
2597 			l++, ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_ITEM)) {
2598 		const char* const str = cgi->XML_GetString(ssnode, SAVE_AIRCRAFT_ITEMID);
2599 		const objDef_t* od = INVSH_GetItemByID(str);
2600 
2601 		if (!od) {
2602 			Com_Printf("AIR_LoadAircraftXML: Could not find aircraftitem '%s'\n", str);
2603 			l--;
2604 			continue;
2605 		}
2606 
2607 		craft->itemcargo[l].item = od;
2608 		craft->itemcargo[l].amount = cgi->XML_GetInt(ssnode, SAVE_AIRCRAFT_AMOUNT, 0);
2609 	}
2610 	craft->itemTypes = l;
2611 
2612 	/* aliencargo */
2613 	snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_ALIENCARGO);
2614 	if (snode) {
2615 		craft->alienCargo = new AlienCargo();
2616 		if (craft->alienCargo == nullptr)
2617 			cgi->Com_Error(ERR_DROP, "AIR_LoadAircraftXML: Cannot create AlienCargo object\n");
2618 		craft->alienCargo->load(snode);
2619 	}
2620 
2621 	return true;
2622 }
2623 
2624 /**
2625  * @brief resets aircraftSlots' backreference pointers for aircraft
2626  * @param[in] aircraft Pointer to the aircraft
2627  */
AIR_CorrectAircraftSlotPointers(aircraft_t * aircraft)2628 static void AIR_CorrectAircraftSlotPointers (aircraft_t* aircraft)
2629 {
2630 	int i;
2631 
2632 	assert(aircraft);
2633 
2634 	for (i = 0; i < aircraft->maxWeapons; i++) {
2635 		aircraft->weapons[i].aircraft = aircraft;
2636 		aircraft->weapons[i].base = nullptr;
2637 		aircraft->weapons[i].installation = nullptr;
2638 	}
2639 	for (i = 0; i < aircraft->maxElectronics; i++) {
2640 		aircraft->electronics[i].aircraft = aircraft;
2641 		aircraft->electronics[i].base = nullptr;
2642 		aircraft->electronics[i].installation = nullptr;
2643 	}
2644 	aircraft->shield.aircraft = aircraft;
2645 	aircraft->shield.base = nullptr;
2646 	aircraft->shield.installation = nullptr;
2647 }
2648 
AIR_LoadXML(xmlNode_t * parent)2649 bool AIR_LoadXML (xmlNode_t* parent)
2650 {
2651 	xmlNode_t* snode, *ssnode;
2652 	xmlNode_t* projectiles;
2653 	int i;
2654 
2655 	/* load phalanx aircraft */
2656 	snode = cgi->XML_GetNode(parent, SAVE_AIRCRAFT_PHALANX);
2657 	for (ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_AIRCRAFT); ssnode;
2658 			ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRCRAFT)) {
2659 		aircraft_t craft;
2660 		if (!AIR_LoadAircraftXML(ssnode, &craft))
2661 			return false;
2662 		assert(craft.homebase);
2663 		AIR_CorrectAircraftSlotPointers(AIR_Add(craft.homebase, &craft));
2664 	}
2665 
2666 	/* load the ufos on geoscape */
2667 	snode = cgi->XML_GetNode(parent, SAVE_AIRCRAFT_UFOS);
2668 
2669 	for (i = 0, ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_AIRCRAFT); i < MAX_UFOONGEOSCAPE && ssnode;
2670 			ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRCRAFT), i++) {
2671 		if (!AIR_LoadAircraftXML(ssnode, UFO_GetByIDX(i)))
2672 			return false;
2673 		ccs.numUFOs++;
2674 	}
2675 
2676 	/* Load projectiles. */
2677 	projectiles = cgi->XML_GetNode(parent, SAVE_AIRCRAFT_PROJECTILES);
2678 	if (!AIRFIGHT_LoadXML(projectiles))
2679 		return false;
2680 
2681 	/* check UFOs - backwards */
2682 	for (i = ccs.numUFOs - 1; i >= 0; i--) {
2683 		aircraft_t* ufo = UFO_GetByIDX(i);
2684 		if (ufo->time < 0 || ufo->stats[AIR_STATS_SPEED] <= 0) {
2685 			Com_Printf("AIR_Load: Found invalid ufo entry - remove it - time: %i - speed: %i\n",
2686 					ufo->time, ufo->stats[AIR_STATS_SPEED]);
2687 			UFO_RemoveFromGeoscape(ufo);
2688 		}
2689 	}
2690 
2691 	return true;
2692 }
2693 
2694 /**
2695  * @brief Set the mission pointers for all the aircraft after loading a savegame
2696  */
AIR_PostLoadInitMissions(void)2697 static bool AIR_PostLoadInitMissions (void)
2698 {
2699 	bool success = true;
2700 	aircraft_t* prevUfo;
2701 	aircraft_t* ufo;
2702 
2703 	/* PHALANX aircraft */
2704 	AIR_Foreach(aircraft) {
2705 		if (Q_strnull(aircraft->missionID))
2706 			continue;
2707 		aircraft->mission = CP_GetMissionByID(aircraft->missionID);
2708 		if (!aircraft->mission) {
2709 			Com_Printf("Aircraft %s (idx: %i) is linked to an invalid mission: %s\n", aircraft->name, aircraft->idx, aircraft->missionID);
2710 			if (aircraft->status == AIR_MISSION)
2711 				AIR_AircraftReturnToBase(aircraft);
2712 		}
2713 		Mem_Free(aircraft->missionID);
2714 		aircraft->missionID = nullptr;
2715 	}
2716 
2717 	/* UFOs */
2718 	/**
2719 	 * @todo UFO_RemoveFromGeoscape call doesn't notify other systems (aircraft, base defences, sam sites, radar)
2720 	 * about the removal of the UFO. Destroying UFOs should get a dedicated function with all necessary notify-callbacks called
2721 	 */
2722 	prevUfo = nullptr;
2723 	while ((ufo = UFO_GetNext(prevUfo)) != nullptr) {
2724 		if (Q_strnull(ufo->missionID)) {
2725 			Com_Printf("Warning: %s (idx: %i) has no mission assigned, removing it\n", ufo->name, ufo->idx);
2726 			UFO_RemoveFromGeoscape(ufo);
2727 			continue;
2728 		}
2729 		ufo->mission = CP_GetMissionByID(ufo->missionID);
2730 		if (!ufo->mission) {
2731 			Com_Printf("Warning: %s (idx: %i) is linked to an invalid mission %s, removing it\n", ufo->name, ufo->idx, ufo->missionID);
2732 			UFO_RemoveFromGeoscape(ufo);
2733 			continue;
2734 		}
2735 		ufo->mission->ufo = ufo;
2736 		Mem_Free(ufo->missionID);
2737 		ufo->missionID = nullptr;
2738 		prevUfo = ufo;
2739 	}
2740 
2741 	return success;
2742 }
2743 
2744 /**
2745  * @brief Actions needs to be done after loading the savegame
2746  * @sa SAV_GameActionsAfterLoad
2747  */
AIR_PostLoadInit(void)2748 bool AIR_PostLoadInit (void)
2749 {
2750 	return AIR_PostLoadInitMissions();
2751 }
2752 
2753 /**
2754  * @brief Returns true if the current base is able to handle aircraft
2755  * @sa B_BaseInit_f
2756  * @note Hangar must be accessible during base attack to make aircraft lift off and to equip soldiers.
2757  */
AIR_AircraftAllowed(const base_t * base)2758 bool AIR_AircraftAllowed (const base_t* base)
2759 {
2760 	return B_GetBuildingStatus(base, B_HANGAR) || B_GetBuildingStatus(base, B_SMALL_HANGAR);
2761 }
2762 
2763 /**
2764  * @param aircraft The aircraft to check
2765  * @return @c true if the given aircraft can go on interceptions
2766  */
AIR_CanIntercept(const aircraft_t * aircraft)2767 bool AIR_CanIntercept (const aircraft_t* aircraft)
2768 {
2769 	if (aircraft->status == AIR_NONE || aircraft->status == AIR_CRASHED)
2770 		return false;
2771 
2772 	/* if dependencies of hangar are missing, you can't send aircraft */
2773 	if (aircraft->size == AIRCRAFT_SMALL && !B_GetBuildingStatus(aircraft->homebase, B_SMALL_HANGAR))
2774 		return false;
2775 	if (aircraft->size == AIRCRAFT_LARGE && !B_GetBuildingStatus(aircraft->homebase, B_HANGAR))
2776 		return false;
2777 
2778 	/* we need a pilot to intercept */
2779 	if (AIR_GetPilot(aircraft) == nullptr)
2780 		return false;
2781 
2782 	return true;
2783 }
2784 
2785 /**
2786  * @brief Checks the parsed aircraft for errors
2787  * @return false if there are errors - true otherwise
2788  */
AIR_ScriptSanityCheck(void)2789 bool AIR_ScriptSanityCheck (void)
2790 {
2791 	int i, j, k, error = 0;
2792 	aircraft_t* a;
2793 
2794 	for (i = 0, a = ccs.aircraftTemplates; i < ccs.numAircraftTemplates; i++, a++) {
2795 		if (a->name[0] == '\0') {
2796 			error++;
2797 			Com_Printf("...... aircraft '%s' has no name\n", a->id);
2798 		}
2799 		if (!a->defaultName) {
2800 			error++;
2801 			Com_Printf("...... aircraft '%s' has no defaultName\n", a->id);
2802 		}
2803 
2804 		/* check that every weapons fits slot */
2805 		for (j = 0; j < a->maxWeapons - 1; j++)
2806 			if (a->weapons[j].item && AII_GetItemWeightBySize(a->weapons[j].item) > a->weapons[j].size) {
2807 				error++;
2808 				Com_Printf("...... aircraft '%s' has an item (%s) too heavy for its slot\n", a->id, a->weapons[j].item->id);
2809 			}
2810 
2811 		/* check that every slots has a different location for PHALANX aircraft (not needed for UFOs) */
2812 		if (a->type != AIRCRAFT_UFO) {
2813 			for (j = 0; j < a->maxWeapons - 1; j++) {
2814 				const itemPos_t var = a->weapons[j].pos;
2815 				for (k = j + 1; k < a->maxWeapons; k++)
2816 					if (var == a->weapons[k].pos) {
2817 						error++;
2818 						Com_Printf("...... aircraft '%s' has 2 weapons slots at the same location\n", a->id);
2819 					}
2820 			}
2821 			for (j = 0; j < a->maxElectronics - 1; j++) {
2822 				const itemPos_t var = a->electronics[j].pos;
2823 				for (k = j + 1; k < a->maxElectronics; k++)
2824 					if (var == a->electronics[k].pos) {
2825 						error++;
2826 						Com_Printf("...... aircraft '%s' has 2 electronics slots at the same location\n", a->id);
2827 					}
2828 			}
2829 		}
2830 	}
2831 
2832 	return !error;
2833 }
2834 
2835 /**
2836  * @brief Removes a soldier from an aircraft.
2837  * @param[in,out] employee The soldier to be removed from the aircraft.
2838  * @param[in,out] aircraft The aircraft to remove the soldier from.
2839  * Use @c nullptr to remove the soldier from any aircraft.
2840  * @sa AIR_AddEmployee
2841  */
AIR_RemoveEmployee(Employee * employee,aircraft_t * aircraft)2842 bool AIR_RemoveEmployee (Employee* employee, aircraft_t* aircraft)
2843 {
2844 	if (!employee)
2845 		return false;
2846 
2847 	/* If no aircraft is given we search if he is in _any_ aircraft and set
2848 	 * the aircraft pointer to it. */
2849 	if (!aircraft) {
2850 		AIR_Foreach(acTemp) {
2851 			if (AIR_IsEmployeeInAircraft(employee, acTemp)) {
2852 				aircraft = acTemp;
2853 				break;
2854 			}
2855 		}
2856 		if (!aircraft)
2857 			return false;
2858 	}
2859 
2860 	Com_DPrintf(DEBUG_CLIENT, "AIR_RemoveEmployee: base: %i - aircraft->idx: %i\n",
2861 		aircraft->homebase ? aircraft->homebase->idx : -1, aircraft->idx);
2862 
2863 	if (AIR_GetPilot(aircraft) == employee) {
2864 #ifdef DEBUG
2865 		if (employee->getType() != EMPL_PILOT)
2866 			Com_Printf("Warning: pilot of aircraf %i is not a qualified pilot (ucn: %i)\n", aircraft->idx, employee->chr.ucn);
2867 #endif
2868 		return AIR_SetPilot(aircraft, nullptr);
2869 	}
2870 
2871 	return cgi->LIST_Remove(&aircraft->acTeam, employee);
2872 }
2873 
2874 /**
2875  * @brief Tells you if an employee is assigned to an aircraft.
2876  * @param[in] employee The employee to search for.
2877  * @param[in] aircraft The aircraft to search the employee in. Use @c nullptr to
2878  * check if the soldier is in @b any aircraft.
2879  * @return true if the soldier was found in the aircraft otherwise false.
2880  */
AIR_IsEmployeeInAircraft(const Employee * employee,const aircraft_t * aircraft)2881 const aircraft_t* AIR_IsEmployeeInAircraft (const Employee* employee, const aircraft_t* aircraft)
2882 {
2883 	if (!employee)
2884 		return nullptr;
2885 
2886 	if (employee->transfer)
2887 		return nullptr;
2888 
2889 	/* If no aircraft is given we search if he is in _any_ aircraft and return true if that's the case. */
2890 	if (!aircraft) {
2891 		AIR_Foreach(anyAircraft) {
2892 			if (AIR_IsEmployeeInAircraft(employee, anyAircraft))
2893 				return anyAircraft;
2894 		}
2895 		return nullptr;
2896 	}
2897 
2898 	if (employee->isPilot()) {
2899 		if (AIR_GetPilot(aircraft) == employee)
2900 			return aircraft;
2901 		return nullptr;
2902 	}
2903 
2904 	if (AIR_IsInAircraftTeam(aircraft, employee))
2905 		return aircraft;
2906 
2907 	return nullptr;
2908 }
2909 
2910 /**
2911  * @brief Removes all soldiers from an aircraft.
2912  * @param[in,out] aircraft The aircraft to remove the soldiers from.
2913  * @sa AIR_RemoveEmployee
2914  */
AIR_RemoveEmployees(aircraft_t & aircraft)2915 void AIR_RemoveEmployees (aircraft_t &aircraft)
2916 {
2917 	LIST_Foreach(aircraft.acTeam, Employee, employee) {
2918 		/* use global aircraft index here */
2919 		AIR_RemoveEmployee(employee, &aircraft);
2920 	}
2921 
2922 	/* Remove pilot */
2923 	AIR_SetPilot(&aircraft, nullptr);
2924 
2925 	if (AIR_GetTeamSize(&aircraft) > 0)
2926 		cgi->Com_Error(ERR_DROP, "AIR_RemoveEmployees: Error, there went something wrong with soldier-removing from aircraft.");
2927 }
2928 
2929 
2930 /**
2931  * @brief Move all the equipment carried by the team on the aircraft into the given equipment
2932  * @param[in] aircraft The craft with the team (and thus equipment) onboard.
2933  * @param[out] ed The equipment definition which will receive all the stuff from the aircraft-team.
2934  */
AIR_MoveEmployeeInventoryIntoStorage(const aircraft_t & aircraft,equipDef_t & ed)2935 void AIR_MoveEmployeeInventoryIntoStorage (const aircraft_t &aircraft, equipDef_t &ed)
2936 {
2937 	if (AIR_GetTeamSize(&aircraft) == 0) {
2938 		Com_DPrintf(DEBUG_CLIENT, "AIR_MoveEmployeeInventoryIntoStorage: No team to remove equipment from.\n");
2939 		return;
2940 	}
2941 
2942 	LIST_Foreach(aircraft.acTeam, Employee, employee) {
2943 		const Container* cont = nullptr;
2944 		while ((cont = employee->chr.inv.getNextCont(cont, true))) {
2945 			Item* ic = cont->getNextItem(nullptr);
2946 			while (ic) {
2947 				const Item item = *ic;
2948 				const objDef_t* type = item.def();
2949 				Item* next = ic->getNext();
2950 
2951 				ed.numItems[type->idx]++;
2952 				if (item.getAmmoLeft() && type->isReloadable()) {
2953 					assert(item.ammoDef());
2954 					/* Accumulate loose ammo into clips */
2955 					ed.addClip(&item);	/* does not delete the item */
2956 				}
2957 				ic = next;
2958 			}
2959 		}
2960 	}
2961 }
2962 
2963 /**
2964  * @brief Assigns a soldier to an aircraft.
2965  * @param[in] employee The employee to be assigned to the aircraft.
2966  * @param[in] aircraft What aircraft to assign employee to.
2967  * @return returns true if a soldier could be assigned to the aircraft.
2968  * @sa AIR_RemoveEmployee
2969  * @sa AIR_AddToAircraftTeam
2970  */
AIR_AddEmployee(Employee * employee,aircraft_t * aircraft)2971 bool AIR_AddEmployee (Employee* employee, aircraft_t* aircraft)
2972 {
2973 	if (!employee || !aircraft)
2974 		return false;
2975 
2976 	if (AIR_GetTeamSize(aircraft) < aircraft->maxTeamSize) {
2977 		/* Check whether the soldier is already on another aircraft */
2978 		if (AIR_IsEmployeeInAircraft(employee, nullptr))
2979 			return false;
2980 
2981 		/* Assign the soldier to the aircraft. */
2982 		return AIR_AddToAircraftTeam(aircraft, employee);
2983 	}
2984 	return false;
2985 }
2986 
2987 /**
2988  * @brief Assigns initial team of soldiers to aircraft
2989  * @param[in,out] aircraft soldiers to add to
2990  */
AIR_AssignInitial(aircraft_t * aircraft)2991 void AIR_AssignInitial (aircraft_t* aircraft)
2992 {
2993 	int count;
2994 	base_t* base;
2995 
2996 	if (!aircraft) {
2997 		Com_Printf("AIR_AssignInitial: No aircraft given\n");
2998 		return;
2999 	}
3000 
3001 	base = aircraft->homebase;
3002 	assert(base);
3003 
3004 	count = 0;
3005 	E_Foreach(EMPL_SOLDIER, employee) {
3006 		if (count >= aircraft->maxTeamSize)
3007 			break;
3008 		if (employee->baseHired != base)
3009 			continue;
3010 		if (AIR_AddEmployee(employee, aircraft))
3011 			count++;
3012 	}
3013 }
3014 
3015 /**
3016  * @brief Init actions for aircraft-subsystem
3017  */
AIR_InitStartup(void)3018 void AIR_InitStartup (void)
3019 {
3020 	AIR_InitCallbacks();
3021 #ifdef DEBUG
3022 	cgi->Cmd_AddCommand("debug_listaircraftsample", AIR_ListAircraftSamples_f, "Show aircraft parameter on game console");
3023 	cgi->Cmd_AddCommand("debug_listaircraft", AIR_ListAircraft_f, "Debug function to list all aircraft in all bases");
3024 	cgi->Cmd_AddCommand("debug_listaircraftidx", AIR_ListCraftIndexes_f, "Debug function to list local/global aircraft indexes");
3025 #endif
3026 }
3027 
3028 /**
3029  * @brief Closing actions for aircraft-subsystem
3030  */
AIR_Shutdown(void)3031 void AIR_Shutdown (void)
3032 {
3033 	AIR_Foreach(craft) {
3034 		AIR_ResetAircraftTeam(craft);
3035 		if (craft->alienCargo != nullptr) {
3036 			delete craft->alienCargo;
3037 			craft->alienCargo = nullptr;
3038 		}
3039 	}
3040 	cgi->LIST_Delete(&ccs.aircraft);
3041 
3042 	AIR_ShutdownCallbacks();
3043 #ifdef DEBUG
3044 	cgi->Cmd_RemoveCommand("debug_listaircraftsample");
3045 	cgi->Cmd_RemoveCommand("debug_listaircraft");
3046 	cgi->Cmd_RemoveCommand("debug_listaircraftidx");
3047 #endif
3048 }
3049