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