UFO: Alien Invasion
Loading...
Searching...
No Matches
cp_aircraft.cpp
Go to the documentation of this file.
1
9
10/*
11Copyright (C) 2002-2025 UFO: Alien Invasion.
12
13This program is free software; you can redistribute it and/or
14modify it under the terms of the GNU General Public License
15as published by the Free Software Foundation; either version 2
16of the License, or (at your option) any later version.
17
18This program is distributed in the hope that it will be useful,
19but WITHOUT ANY WARRANTY; without even the implied warranty of
20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21
22See the GNU General Public License for more details.
23
24You should have received a copy of the GNU General Public License
25along with this program; if not, write to the Free Software
26Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27*/
28
29#include "../../DateTime.h"
30#include "../../cl_shared.h"
31#include "../../ui/ui_dataids.h"
33#include "cp_campaign.h"
34#include "cp_mapfightequip.h"
35#include "cp_geoscape.h"
36#include "cp_ufo.h"
37#include "cp_alienbase.h"
38#include "cp_time.h"
39#include "cp_missions.h"
41#include "save/save_aircraft.h"
42#include "cp_popup.h"
43#include "aliencargo.h"
44#include "itemcargo.h"
45#include "cp_building.h"
46
52{
53 if (b) {
54 AIR_ForeachFromBase(aircraft, b)
55 return aircraft;
56 }
57
58 return nullptr;
59}
60
66bool AIR_BaseHasAircraft (const base_t* base)
67{
68 return base != nullptr && AIR_GetFirstFromBase(base) != nullptr;
69}
70
76{
77 int count = 0;
78
79 AIR_ForeachFromBase(aircraft, base) {
80 count++;
81 }
82
83 return count;
84}
85
86#ifdef DEBUG
92static void AIR_ListAircraft_f (void)
93{
94 base_t* base = nullptr;
95
96 if (cgi->Cmd_Argc() == 2) {
97 int baseIdx = atoi(cgi->Cmd_Argv(1));
98 base = B_GetFoundedBaseByIDX(baseIdx);
99 }
100
101 AIR_Foreach(aircraft) {
102 if (base && aircraft->homebase != base)
103 continue;
104
105 cgi->Com_Printf("Aircraft %s\n", aircraft->name);
106 cgi->Com_Printf("...idx global %i\n", aircraft->idx);
107 cgi->Com_Printf("...homebase: %s\n", aircraft->homebase ? aircraft->homebase->name : "NO HOMEBASE");
108 for (int i = 0; i < aircraft->maxWeapons; i++) {
109 aircraftSlot_t* slot = &aircraft->weapons[i];
110 if (slot->item) {
111 cgi->Com_Printf("...weapon slot %i contains %s", i, slot->item->id);
112
113 if (!slot->installationTime) {
114 cgi->Com_Printf(" (functional)\n");
115 } else if (slot->installationTime > 0) {
116 cgi->Com_Printf(" (%i hours before installation is finished)\n", slot->installationTime);
117 } else {
118 cgi->Com_Printf(" (%i hours before removing is finished)\n", slot->installationTime);
119 }
120
121 if (slot->ammo) {
122 if (slot->ammoLeft > 1) {
123 cgi->Com_Printf("......this weapon is loaded with ammo %s\n", slot->ammo->id);
124 } else {
125 cgi->Com_Printf("......no more ammo (%s)\n", slot->ammo->id);
126 }
127 } else {
128 cgi->Com_Printf("......this weapon isn't loaded with ammo\n");
129 }
130 } else {
131 cgi->Com_Printf("...weapon slot %i is empty\n", i);
132 }
133 }
134
135 if (aircraft->shield.item) {
136 cgi->Com_Printf("...armour slot contains %s", aircraft->shield.item->id);
137 if (!aircraft->shield.installationTime) {
138 cgi->Com_Printf(" (functional)\n");
139 } else if (aircraft->shield.installationTime > 0) {
140 cgi->Com_Printf(" (%i hours before installation is finished)\n", aircraft->shield.installationTime);
141 } else {
142 cgi->Com_Printf(" (%i hours before removing is finished)\n", aircraft->shield.installationTime);
143 }
144 } else {
145 cgi->Com_Printf("...armour slot is empty\n");
146 }
147
148 for (int j = 0; j < aircraft->maxElectronics; j++) {
149 aircraftSlot_t* slot = &aircraft->weapons[j];
150 if (slot->item) {
151 cgi->Com_Printf("...electronics slot %i contains %s", j, slot->item->id);
152
153 if (!slot->installationTime) {
154 cgi->Com_Printf(" (functional)\n");
155 } else if (slot->installationTime > 0) {
156 cgi->Com_Printf(" (%i hours before installation is finished)\n", slot->installationTime);
157 } else {
158 cgi->Com_Printf(" (%i hours before removing is finished)\n", slot->installationTime);
159 }
160 } else {
161 cgi->Com_Printf("...electronics slot %i is empty\n", j);
162 }
163 }
164
165 if (aircraft->pilot) {
166 character_t* chr = &aircraft->pilot->chr;
167 cgi->Com_Printf("...pilot: ucn: %i name: %s\n", chr->ucn, chr->name);
168 } else {
169 cgi->Com_Printf("...no pilot assigned\n");
170 }
171
172 cgi->Com_Printf("...damage: %i\n", aircraft->damage);
173 cgi->Com_Printf("...stats: ");
174 for (int k = 0; k < AIR_STATS_MAX; k++) {
175 if (k == AIR_STATS_WRANGE) {
176 cgi->Com_Printf("%.2f ", aircraft->stats[k] / 1000.0f);
177 } else {
178 cgi->Com_Printf("%i ", aircraft->stats[k]);
179 }
180 }
181 cgi->Com_Printf("\n");
182 cgi->Com_Printf("...name %s\n", aircraft->id);
183 cgi->Com_Printf("...team size %i\n", aircraft->maxTeamSize);
184 cgi->Com_Printf("...fuel %i\n", aircraft->fuel);
185 cgi->Com_Printf("...status %s\n", (aircraft->status == AIR_CRASHED) ? "crashed" : AIR_AircraftStatusToName(aircraft));
186 cgi->Com_Printf("...pos %.0f:%.0f\n", aircraft->pos[0], aircraft->pos[1]);
187 cgi->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 cgi->Com_Printf(".........name: %s (ucn: %i)\n", chr->name, chr->ucn);
191 }
192
193 if (aircraft->itemCargo) {
194 cgi->Com_Printf("...itemCargo:\n");
195 linkedList_t* cargo = aircraft->itemCargo->list();
196 LIST_Foreach(cargo, itemCargo_t, item) {
197 cgi->Com_Printf("......item: %s amount: %d loose amount: %d\n", item->objDef->id, item->amount, item->looseAmount);
198 }
199 cgi->LIST_Delete(&cargo);
200 }
201
202 if (aircraft->alienCargo) {
203 cgi->Com_Printf("...alienCargo:\n");
204 linkedList_t* cargo = aircraft->alienCargo->list();
205 LIST_Foreach(cargo, alienCargo_t, item) {
206 cgi->Com_Printf("......team: %s alive: %d dead: %d\n", item->teamDef->id, item->alive, item->dead);
207 }
208 cgi->LIST_Delete(&cargo);
209 }
210 }
211}
212#endif
213
219static void AII_CollectAmmo (void* data, const Item* magazine)
220{
221 aircraft_t* aircraft = (aircraft_t*)data;
222 if (aircraft == nullptr)
223 return;
224 if (aircraft->itemCargo == nullptr)
225 aircraft->itemCargo = new ItemCargo();
226 aircraft->itemCargo->add(magazine->ammoDef(), 0, magazine->getAmmoLeft());
227}
228
237void AII_CollectItem (aircraft_t* aircraft, const objDef_t* item, int amount)
238{
239 if (aircraft == nullptr)
240 return;
241 if (aircraft->itemCargo == nullptr)
242 aircraft->itemCargo = new ItemCargo();
243 aircraft->itemCargo->add(item, amount, 0);
244}
245
246static inline void AII_CollectItem_ (void* data, const objDef_t* item, int amount)
247{
248 AII_CollectItem((aircraft_t*)data, item, amount);
249}
250
256static void AII_CarriedItems (const Inventory* soldierInventory)
257{
258 Item* item;
259 equipDef_t* ed = &ccs.eMission;
260
261 const Container* cont = nullptr;
262 while ((cont = soldierInventory->getNextCont(cont))) {
263 /* Items on the ground are collected as ET_ITEM */
264 for (item = cont->_invList; item; item = item->getNext()) {
265 const objDef_t* itemType = item->def();
266 technology_t* tech = RS_GetTechForItem(itemType);
267 RS_MarkCollected(tech);
268 if (item->def()->isVirtual)
269 continue;
270 ed->numItems[itemType->idx]++;
271
272 if (!itemType->isReloadable() || item->getAmmoLeft() == 0)
273 continue;
274
275 ed->addClip(item);
276 /* The guys keep their weapons (half-)loaded. Auto-reload
277 * will happen at equip screen or at the start of next mission,
278 * but fully loaded weapons will not be reloaded even then. */
279 }
280 }
281}
282
294void AII_CollectingItems (aircraft_t* aircraft, int won)
295{
297 if (aircraft->itemCargo == nullptr) {
298 aircraft->itemCargo = new ItemCargo();
299 }
300 ItemCargo* previousCargo = new ItemCargo(*aircraft->itemCargo);
301 aircraft->itemCargo->empty();
302 cgi->CollectItems(aircraft, won, AII_CollectItem_, AII_CollectAmmo, AII_CarriedItems);
303
304 linkedList_t* items = aircraft->itemCargo->list();
305 LIST_Foreach(items, itemCargo_t, item) {
306 aircraft->mission->missionResults.itemTypes++;
307 aircraft->mission->missionResults.itemAmount += item->amount;
308#ifdef DEBUG
309 if (item->amount > 0)
310 cgi->Com_DPrintf(DEBUG_CLIENT, "Collected item: %s amount: %i\n", item->objDef->id, item->amount);
311#endif
312 }
313 cgi->LIST_Delete(&items);
314
315 items = previousCargo->list();
316 LIST_Foreach(items, itemCargo_t, item) {
317 aircraft->itemCargo->add(item->objDef, item->amount, item->looseAmount);
318 }
319 cgi->LIST_Delete(&items);
320 delete previousCargo;
321}
322
328const char* AIR_AircraftStatusToName (const aircraft_t* aircraft)
329{
330 assert(aircraft);
331 assert(aircraft->homebase);
332
333 /* display special status if base-attack affects aircraft */
334 if (B_IsUnderAttack(aircraft->homebase) && AIR_IsAircraftInBase(aircraft))
335 return _("ON RED ALERT");
336
337 switch (aircraft->status) {
338 case AIR_NONE:
339 return _("Nothing - should not be displayed");
340 case AIR_HOME:
341 return va(_("at %s"), aircraft->homebase->name);
342 case AIR_REFUEL:
343 return _("refuelling");
344 case AIR_IDLE:
345 return _("idle");
346 case AIR_TRANSIT:
347 return _("in transit");
348 case AIR_MISSION:
349 return _("enroute to mission");
350 case AIR_UFO:
351 return _("pursuing a UFO");
352 case AIR_DROP:
353 return _("ready to drop soldiers");
354 case AIR_INTERCEPT:
355 return _("intercepting a UFO");
356 case AIR_TRANSFER:
357 return _("enroute to new home base");
358 case AIR_RETURNING:
359 return _("returning to base");
360 case AIR_CRASHED:
361 cgi->Com_Error(ERR_DROP, "AIR_CRASHED should not be visible anywhere");
362 }
363 return nullptr;
364}
365
372bool AIR_IsAircraftInBase (const aircraft_t* aircraft)
373{
374 if (aircraft->status == AIR_HOME || aircraft->status == AIR_REFUEL)
375 return true;
376 return false;
377}
378
387{
388 switch (aircraft->status) {
389 case AIR_IDLE:
390 case AIR_TRANSIT:
391 case AIR_MISSION:
392 case AIR_UFO:
393 case AIR_DROP:
394 case AIR_INTERCEPT:
395 case AIR_RETURNING:
396 return true;
397 case AIR_NONE:
398 case AIR_REFUEL:
399 case AIR_HOME:
400 case AIR_TRANSFER:
401 case AIR_CRASHED:
402 return false;
403 }
404
405 cgi->Com_Error(ERR_FATAL, "Unknown aircraft status %i", aircraft->status);
406}
407
413int AIR_CountInBaseByTemplate (const base_t* base, const aircraft_t* aircraftTemplate)
414{
415 int count = 0;
416
417 AIR_ForeachFromBase(aircraft, base) {
418 if (aircraft->tpl == aircraftTemplate)
419 count++;
420 }
421 return count;
422}
423
429int AIR_AircraftMenuStatsValues (const int value, const int stat)
430{
431 switch (stat) {
432 case AIR_STATS_SPEED:
434 /* Convert into km/h, and round this value */
435 return 10 * (int) (111.2 * value / 10.0f);
437 return value / 1000;
438 default:
439 return value;
440 }
441}
442
449{
450 const int range = aircraft->stats[AIR_STATS_SPEED] * aircraft->stats[AIR_STATS_FUELSIZE];
451 /* the 2.0f factor is for going to destination and then come back */
452 return 100 * (int) (KILOMETER_PER_DEGREE * range / (2.0f * (float)DateTime::SECONDS_PER_HOUR * 100.0f));
453}
454
461{
462 return aircraft->stats[AIR_STATS_SPEED] * aircraft->fuel;
463}
464
472bool AIR_AircraftHasEnoughFuel (const aircraft_t* aircraft, const vec2_t destination)
473{
474 const base_t* base;
475 float distance;
476
477 assert(aircraft);
478 base = aircraft->homebase;
479 assert(base);
480
481 /* Calculate the line that the aircraft should follow to go to destination */
482 distance = GetDistanceOnGlobe(aircraft->pos, destination);
483
484 /* Calculate the line that the aircraft should then follow to go back home */
485 distance += GetDistanceOnGlobe(destination, base->pos);
486
487 /* Check if the aircraft has enough fuel to go to destination and then go back home */
488 return (distance <= AIR_GetRemainingRange(aircraft) / (float)DateTime::SECONDS_PER_HOUR);
489}
490
498bool AIR_AircraftHasEnoughFuelOneWay (const aircraft_t* aircraft, const vec2_t destination)
499{
500 float distance;
501
502 assert(aircraft);
503
504 /* Calculate the line that the aircraft should follow to go to destination */
505 distance = GetDistanceOnGlobe(aircraft->pos, destination);
506
507 /* Check if the aircraft has enough fuel to go to destination */
508 return (distance <= AIR_GetRemainingRange(aircraft) / (float)DateTime::SECONDS_PER_HOUR);
509}
510
517{
518 if (aircraft && AIR_IsAircraftOnGeoscape(aircraft)) {
519 const base_t* base = aircraft->homebase;
520 GEO_CalcLine(aircraft->pos, base->pos, &aircraft->route);
521 aircraft->status = AIR_RETURNING;
522 aircraft->time = 0;
523 aircraft->point = 0;
524 aircraft->mission = nullptr;
525 aircraft->aircraftTarget = nullptr;
526 }
527}
528
536{
537 int i;
538
539 i = 0;
540 AIR_ForeachFromBase(aircraft, base) {
541 if (index == i)
542 return aircraft;
543 i++;
544 }
545
546 return nullptr;
547}
548
556{
557 if (!name)
558 return nullptr;
559 for (int i = 0; i < ccs.numAircraftTemplates; i++) {
560 const aircraft_t* aircraftTemplate = &ccs.aircraftTemplates[i];
561 if (Q_streq(aircraftTemplate->id, name))
562 return aircraftTemplate;
563 }
564 return nullptr;
565}
566
572const aircraft_t* AIR_GetAircraft (const char* name)
573{
574 const aircraft_t* aircraft = AIR_GetAircraftSilent(name);
575 if (Q_strnull(name))
576 cgi->Com_Error(ERR_DROP, "AIR_GetAircraft called with invalid name!");
577 else if (aircraft == nullptr)
578 cgi->Com_Error(ERR_DROP, "Aircraft '%s' not found", name);
579
580 return aircraft;
581}
582
587static void AII_SetAircraftInSlots (aircraft_t* aircraft)
588{
589 /* initialise weapon slots */
590 for (int i = 0; i < MAX_AIRCRAFTSLOT; i++) {
591 aircraft->weapons[i].aircraft = aircraft;
592 aircraft->electronics[i].aircraft = aircraft;
593 }
594 aircraft->shield.aircraft = aircraft;
595}
596
605aircraft_t* AIR_Add (base_t* base, const aircraft_t* aircraftTemplate)
606{
607 const baseCapacities_t capType = AIR_GetHangarCapacityType(aircraftTemplate);
608 aircraft_t& aircraft = LIST_Add(&ccs.aircraft, *aircraftTemplate);
609 aircraft.homebase = base;
610 if (base && capType != MAX_CAP && aircraft.status != AIR_CRASHED)
611 CAP_AddCurrent(base, capType, 1);
612 return &aircraft;
613}
614
622bool AIR_Delete (base_t* base, aircraft_t* aircraft)
623{
624 const baseCapacities_t capType = AIR_GetHangarCapacityType(aircraft);
625 const bool crashed = (aircraft->status == AIR_CRASHED);
626
627 if (aircraft->alienCargo != nullptr) {
628 delete aircraft->alienCargo;
629 aircraft->alienCargo = nullptr;
630 }
631
632 if (cgi->LIST_Remove(&ccs.aircraft, (const void*)aircraft)) {
633 if (base && capType != MAX_CAP && !crashed)
634 CAP_AddCurrent(base, capType, -1);
635 return true;
636 }
637 return false;
638}
639
646aircraft_t* AIR_NewAircraft (base_t* base, const aircraft_t* aircraftTemplate)
647{
648 /* copy generic aircraft description to individual aircraft in base
649 * we do this because every aircraft can have its own parameters
650 * now lets use the aircraft array for the base to set some parameters */
651 aircraft_t* aircraft = AIR_Add(base, aircraftTemplate);
652 aircraft->idx = ccs.campaignStats.aircraftHad++;
653 aircraft->homebase = base;
654 /* Update the values of its stats */
655 AII_UpdateAircraftStats(aircraft);
656 /* initialise aircraft pointer in slots */
657 AII_SetAircraftInSlots(aircraft);
658 /* Set HP to maximum */
659 aircraft->damage = aircraft->stats[AIR_STATS_DAMAGE];
660 /* Set Default Name */
661 Q_strncpyz(aircraft->name, _(aircraft->defaultName), lengthof(aircraft->name));
662
663 /* set initial direction of the aircraft */
664 VectorSet(aircraft->direction, 1, 0, 0);
665
666 AIR_ResetAircraftTeam(aircraft);
667
668 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new %s is ready in %s"), _(aircraft->tpl->name), base->name);
670 cgi->Com_DPrintf(DEBUG_CLIENT, "Setting aircraft to pos: %.0f:%.0f\n", base->pos[0], base->pos[1]);
671 Vector2Copy(base->pos, aircraft->pos);
672 RADAR_Initialise(&aircraft->radar, aircraftTemplate->radar.range, aircraftTemplate->radar.trackingRange, 1.0f, false);
673 aircraft->radar.ufoDetectionProbability = aircraftTemplate->radar.ufoDetectionProbability;
674
675 cgi->Com_DPrintf(DEBUG_CLIENT, "idx_sample: %i name: %s hangar: %s\n", aircraft->tpl->idx, aircraft->id, aircraft->building);
676 cgi->Com_DPrintf(DEBUG_CLIENT, "Adding new aircraft %s with IDX %i for %s\n", aircraft->id, aircraft->idx, base->name);
677 if (!base->aircraftCurrent)
678 base->aircraftCurrent = aircraft;
679
680 /* also update the base menu buttons */
681 cgi->Cmd_ExecuteString("base_init %d", base->idx);
682 return aircraft;
683}
684
690{
691 if (aircraft == nullptr)
692 return MAX_CAP;
693
694 building_t* building = B_GetBuildingTemplateSilent(aircraft->building);
695 if (building == nullptr)
696 return MAX_CAP;
697
699}
700
705static int AIR_GetStorageRoom (const aircraft_t* aircraft)
706{
707 int size = 0;
708
709 LIST_Foreach(aircraft->acTeam, Employee, employee) {
710 const Container* cont = nullptr;
711 while ((cont = employee->chr.inv.getNextCont(cont, true))) {
712 Item* item = nullptr;
713 while ((item = cont->getNextItem(item))) {
714 const objDef_t* obj = item->def();
715 size += obj->size;
716
717 obj = item->ammoDef();
718 if (obj)
719 size += obj->size;
720 }
721 }
722 }
723
724 return size;
725}
726
732const char* AIR_CheckMoveIntoNewHomebase (const aircraft_t* aircraft, const base_t* base)
733{
734 const baseCapacities_t capacity = AIR_GetHangarCapacityType(aircraft);
735
737 return _("No operational hangars at that base.");
738
739 /* not enough capacity */
740 if (CAP_GetFreeCapacity(base, capacity) <= 0)
741 return _("No free hangars at that base.");
742
743 if (CAP_GetFreeCapacity(base, CAP_EMPLOYEES) < AIR_GetTeamSize(aircraft) + (AIR_GetPilot(aircraft) ? 1 : 0))
744 return _("Insufficient free crew quarter space at that base.");
745
746 if (aircraft->maxTeamSize && CAP_GetFreeCapacity(base, CAP_ITEMS) < AIR_GetStorageRoom(aircraft))
747 return _("Insufficient storage space at that base.");
748
749 /* check aircraft fuel, because the aircraft has to travel to the new base */
750 if (!AIR_AircraftHasEnoughFuelOneWay(aircraft, base->pos))
751 return _("That base is beyond this aircraft's range.");
752
753 return nullptr;
754}
755
762static void AIR_TransferItemsCarriedByCharacterToBase (character_t* chr, base_t* sourceBase, base_t* destBase)
763{
764 const Container* cont = nullptr;
765 while ((cont = chr->inv.getNextCont(cont, true))) {
766 Item* item = nullptr;
767 while ((item = cont->getNextItem(item))) {
768 const objDef_t* obj = item->def();
769 B_AddToStorage(sourceBase, obj, -1);
770 B_AddToStorage(destBase, obj, 1);
771
772 obj = item->ammoDef();
773 if (obj) {
774 B_AddToStorage(sourceBase, obj, -1);
775 B_AddToStorage(destBase, obj, 1);
776 }
777 }
778 }
779}
780
787{
788 base_t* oldBase;
789
790 assert(aircraft);
791 assert(base);
792 assert(base != aircraft->homebase);
793
794 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_MoveAircraftIntoNewHomebase: Change homebase of '%s' to '%s'\n", aircraft->id, base->name);
795
796 oldBase = aircraft->homebase;
797 assert(oldBase);
798
799 /* Transfer employees */
800 E_MoveIntoNewBase(AIR_GetPilot(aircraft), base);
801
802 LIST_Foreach(aircraft->acTeam, Employee, employee) {
803 E_MoveIntoNewBase(employee, base);
804 /* Transfer items carried by soldiers from oldBase to base */
805 AIR_TransferItemsCarriedByCharacterToBase(&employee->chr, oldBase, base);
806 }
807
808 /* Move aircraft to new base */
809 const baseCapacities_t capacity = AIR_GetHangarCapacityType(aircraft);
810 CAP_AddCurrent(oldBase, capacity, -1);
811 aircraft->homebase = base;
812 CAP_AddCurrent(base, capacity, 1);
813
814 if (oldBase->aircraftCurrent == aircraft)
815 oldBase->aircraftCurrent = AIR_GetFirstFromBase(oldBase);
816 if (!base->aircraftCurrent)
817 base->aircraftCurrent = aircraft;
818
819 /* No need to update global IDX of every aircraft: the global IDX of this aircraft did not change */
820 /* Redirect selectedAircraft */
821 GEO_SelectAircraft(aircraft);
822
823 if (aircraft->status == AIR_RETURNING) {
824 /* redirect to the new base */
825 AIR_AircraftReturnToBase(aircraft);
826 }
827}
828
838{
839 /* Check if aircraft is on geoscape while it's not destroyed yet */
840 const bool aircraftIsOnGeoscape = AIR_IsAircraftOnGeoscape(aircraft);
841
842 assert(aircraft);
843 base_t* base = aircraft->homebase;
844 assert(base);
845
847 TR_NotifyAircraftRemoved(aircraft);
848
849 /* Remove pilot and all soldiers from the aircraft (the employees are still hired after this). */
850 AIR_RemoveEmployees(*aircraft);
851
852 /* base is nullptr here because we don't want to readd this to the inventory
853 * If you want this in the inventory you really have to call these functions
854 * before you are destroying a craft */
855 for (int i = 0; i < MAX_AIRCRAFTSLOT; i++) {
856 AII_RemoveItemFromSlot(nullptr, aircraft->weapons, false);
857 AII_RemoveItemFromSlot(nullptr, aircraft->electronics, false);
858 }
859 AII_RemoveItemFromSlot(nullptr, &aircraft->shield, false);
860
861 if (base->aircraftCurrent == aircraft)
862 base->aircraftCurrent = nullptr;
863
864 AIR_Delete(base, aircraft);
865
866 if (!AIR_BaseHasAircraft(base)) {
867 cgi->Cvar_Set("mn_aircraftinbase", "0");
868 cgi->Cvar_Set("mn_aircraftname", "");
869 cgi->Cvar_Set("mn_aircraft_model", "");
870 } else if (base->aircraftCurrent == nullptr) {
872 }
873
874 /* also update the base menu buttons */
875 cgi->Cmd_ExecuteString("base_init %d", base->idx);
876
877 /* Update Radar overlay */
878 if (aircraftIsOnGeoscape)
880}
881
889void AIR_DestroyAircraft (aircraft_t* aircraft, bool killPilot)
890{
891 Employee* pilot;
892
893 assert(aircraft);
894
895 LIST_Foreach(aircraft->acTeam, Employee, employee) {
897 E_DeleteEmployee(employee);
898 }
899 /* the craft may no longer have any employees assigned */
900 /* remove the pilot */
901 pilot = AIR_GetPilot(aircraft);
902 if (pilot) {
903 if (killPilot) {
904 if (E_DeleteEmployee(pilot))
905 AIR_SetPilot(aircraft, nullptr);
906 else
907 cgi->Com_Error(ERR_DROP, "AIR_DestroyAircraft: Could not remove pilot from game: %s (ucn: %i)\n",
908 pilot->chr.name, pilot->chr.ucn);
909 } else {
910 AIR_SetPilot(aircraft, nullptr);
911 }
912 } else {
913 if (aircraft->status != AIR_CRASHED)
914 cgi->Com_Error(ERR_DROP, "AIR_DestroyAircraft: aircraft id %s had no pilot\n", aircraft->id);
915 }
916
917 AIR_DeleteAircraft(aircraft);
918}
919
926bool AIR_AircraftMakeMove (int dt, aircraft_t* aircraft)
927{
928 float dist;
929
930 /* calc distance */
931 aircraft->time += dt;
932 aircraft->fuel -= dt;
933
934 dist = (float) aircraft->stats[AIR_STATS_SPEED] * aircraft->time / (float)DateTime::SECONDS_PER_HOUR;
935
936 /* Check if destination reached */
937 if (dist >= aircraft->route.distance * (aircraft->route.numPoints - 1)) {
938 return true;
939 } else {
940 /* calc new position */
941 float frac = dist / aircraft->route.distance;
942 const int p = (int) frac;
943 frac -= p;
944 aircraft->point = p;
945 aircraft->pos[0] = (1 - frac) * aircraft->route.point[p][0] + frac * aircraft->route.point[p + 1][0];
946 aircraft->pos[1] = (1 - frac) * aircraft->route.point[p][1] + frac * aircraft->route.point[p + 1][1];
947
949 }
950
951 dist = (float) aircraft->stats[AIR_STATS_SPEED] * (aircraft->time + dt) / (float)DateTime::SECONDS_PER_HOUR;
952
953 /* Now calculate the projected position. This is the position that the aircraft should get on
954 * next frame if its route didn't change in meantime. */
955 if (dist >= aircraft->route.distance * (aircraft->route.numPoints - 1)) {
956 VectorSet(aircraft->projectedPos, 0.0f, 0.0f, 0.0f);
957 } else {
958 /* calc new position */
959 float frac = dist / aircraft->route.distance;
960 const int p = (int) frac;
961 frac -= p;
962 aircraft->projectedPos[0] = (1 - frac) * aircraft->route.point[p][0] + frac * aircraft->route.point[p + 1][0];
963 aircraft->projectedPos[1] = (1 - frac) * aircraft->route.point[p][1] + frac * aircraft->route.point[p + 1][1];
964
966 }
967
968 return false;
969}
970
971static void AIR_Move (aircraft_t* aircraft, int deltaTime)
972{
973 /* Aircraft is moving */
974 if (AIR_AircraftMakeMove(deltaTime, aircraft)) {
975 /* aircraft reach its destination */
976 const float* end = aircraft->route.point[aircraft->route.numPoints - 1];
977 Vector2Copy(end, aircraft->pos);
979
980 switch (aircraft->status) {
981 case AIR_MISSION:
982 /* Aircraft reached its mission */
983 assert(aircraft->mission);
984 aircraft->status = AIR_DROP;
985 /* Fall thru */
986 case AIR_DROP:
987 aircraft->mission->active = true;
988 GEO_SetMissionAircraft(aircraft);
989 GEO_SelectMission(aircraft->mission);
992 cgi->UI_PushWindow("popup_intercept_ready");
993 cgi->UI_ExecuteConfunc("pop_intready_aircraft \"%s\" \"%s\"", aircraft->name,
994 MIS_GetName(aircraft->mission));
995 break;
996 case AIR_RETURNING:
997 /* aircraft entered in homebase */
998 aircraft->status = AIR_REFUEL;
1001 _("Craft %s has returned to %s."), aircraft->name, aircraft->homebase->name);
1003 break;
1004 case AIR_TRANSFER:
1005 case AIR_UFO:
1006 break;
1007 default:
1008 aircraft->status = AIR_IDLE;
1009 break;
1010 }
1011 }
1012}
1013
1014static void AIR_Refuel (aircraft_t* aircraft, int deltaTime)
1015{
1016 /* Aircraft is refuelling at base */
1017 int fillup;
1018
1019 if (aircraft->fuel < 0)
1020 aircraft->fuel = 0;
1021 /* amount of fuel we would like to load */
1022 fillup = std::min(deltaTime * AIRCRAFT_REFUEL_FACTOR, aircraft->stats[AIR_STATS_FUELSIZE] - aircraft->fuel);
1023 /* This craft uses antimatter as fuel */
1024 assert(aircraft->homebase);
1025 if (aircraft->stats[AIR_STATS_ANTIMATTER] > 0 && fillup > 0) {
1026 /* the antimatter we have */
1027 const int amAvailable = B_ItemInBase(INVSH_GetItemByID(ANTIMATTER_ITEM_ID), aircraft->homebase);
1028 /* current antimatter level in craft */
1029 const int amCurrentLevel = aircraft->stats[AIR_STATS_ANTIMATTER] * (aircraft->fuel / (float) aircraft->stats[AIR_STATS_FUELSIZE]);
1030 /* next antimatter level in craft */
1031 const int amNextLevel = aircraft->stats[AIR_STATS_ANTIMATTER] * ((aircraft->fuel + fillup) / (float) aircraft->stats[AIR_STATS_FUELSIZE]);
1032 /* antimatter needed */
1033 int amLoad = amNextLevel - amCurrentLevel;
1034
1035 if (amLoad > amAvailable) {
1036 /* amount of fuel we can load */
1037 fillup = aircraft->stats[AIR_STATS_FUELSIZE] * ((amCurrentLevel + amAvailable) / (float) aircraft->stats[AIR_STATS_ANTIMATTER]) - aircraft->fuel;
1038 amLoad = amAvailable;
1039
1040 if (!aircraft->notifySent[AIR_CANNOT_REFUEL]) {
1042 _("Craft %s couldn't be completely refueled at %s. Not enough antimatter."), aircraft->name, aircraft->homebase->name);
1044 aircraft->notifySent[AIR_CANNOT_REFUEL] = true;
1045 }
1046 }
1047
1048 if (amLoad > 0)
1049 B_AddAntimatter(aircraft->homebase, -amLoad);
1050 }
1051
1052 aircraft->fuel += fillup;
1053
1054 if (aircraft->fuel >= aircraft->stats[AIR_STATS_FUELSIZE]) {
1055 aircraft->fuel = aircraft->stats[AIR_STATS_FUELSIZE];
1056 aircraft->status = AIR_HOME;
1058 _("Craft %s has refueled at %s."), aircraft->name, aircraft->homebase->name);
1060 aircraft->notifySent[AIR_CANNOT_REFUEL] = false;
1061 }
1062
1063}
1064
1072void AIR_CampaignRun (const campaign_t* campaign, int dt, bool updateRadarOverlay)
1073{
1074 /* true if at least one aircraft moved: radar overlay must be updated
1075 * This is static because aircraft can move without radar being
1076 * updated (sa CP_CampaignRun) */
1077 static bool radarOverlayReset = false;
1078
1079 /* Run each aircraft */
1080 AIR_Foreach(aircraft) {
1081 if (aircraft->status == AIR_CRASHED)
1082 continue;
1083
1084 assert(aircraft->homebase);
1085 if (aircraft->status == AIR_IDLE) {
1086 /* Aircraft idle out of base */
1087 aircraft->fuel -= dt;
1088 } else if (AIR_IsAircraftOnGeoscape(aircraft)) {
1089 AIR_Move(aircraft, dt);
1090 /* radar overlay should be updated */
1091 radarOverlayReset = true;
1092 } else if (aircraft->status == AIR_REFUEL) {
1093 AIR_Refuel(aircraft, dt);
1094 }
1095
1096 /* Check aircraft low fuel (only if aircraft is not already returning to base or in base) */
1097 if (aircraft->status != AIR_RETURNING && AIR_IsAircraftOnGeoscape(aircraft) &&
1098 !AIR_AircraftHasEnoughFuel(aircraft, aircraft->pos)) {
1100 MS_AddNewMessage(_("Notice"), va(_("Craft %s is low on fuel and must return to base."), aircraft->name));
1101 AIR_AircraftReturnToBase(aircraft);
1102 }
1103
1104 /* Aircraft purchasing ufo */
1105 if (aircraft->status == AIR_UFO) {
1106 /* Solve the fight */
1107 AIRFIGHT_ExecuteActions(campaign, aircraft, aircraft->aircraftTarget);
1108 }
1109
1110 for (int k = 0; k < aircraft->maxWeapons; k++) {
1111 aircraftSlot_t* slot = &aircraft->weapons[k];
1112 /* Update delay to launch next projectile */
1113 if (AIR_IsAircraftOnGeoscape(aircraft) && slot->delayNextShot > 0)
1114 slot->delayNextShot -= dt;
1115 /* Reload if needed */
1116 if (slot->ammoLeft <= 0)
1117 AII_ReloadWeapon(slot);
1118 }
1119 }
1120
1121 if (updateRadarOverlay && radarOverlayReset && GEO_IsRadarOverlayActivated()) {
1123 radarOverlayReset = false;
1124 }
1125}
1126
1133{
1134 AIR_Foreach(aircraft) {
1135 if (aircraft->idx == aircraftIdx) {
1136 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_AircraftGetFromIDX: aircraft idx: %i\n", aircraft->idx);
1137 return aircraft;
1138 }
1139 }
1140
1141 return nullptr;
1142}
1143
1151{
1152 if (!aircraft || !mission)
1153 return false;
1154
1155 if (AIR_GetTeamSize(aircraft) == 0) {
1156 CP_Popup(_("Notice"), _("Assign one or more soldiers to this aircraft first."));
1157 return false;
1158 }
1159
1160 /* if aircraft was in base */
1161 if (AIR_IsAircraftInBase(aircraft)) {
1162 /* reload its ammunition */
1163 AII_ReloadAircraftWeapons(aircraft);
1164 }
1165
1166 /* ensure interceptAircraft is set correctly */
1168
1169 /* if mission is a base-attack and aircraft already in base, launch
1170 * mission immediately */
1171 if (B_IsUnderAttack(aircraft->homebase) && AIR_IsAircraftInBase(aircraft)) {
1172 aircraft->mission = mission;
1173 mission->active = true;
1174 cgi->UI_PushWindow("popup_baseattack");
1175 return true;
1176 }
1177
1178 if (!AIR_AircraftHasEnoughFuel(aircraft, mission->pos)) {
1179 MS_AddNewMessage(_("Notice"), _("Insufficient fuel."));
1180 return false;
1181 }
1182
1183 GEO_CalcLine(aircraft->pos, mission->pos, &aircraft->route);
1184 aircraft->status = AIR_MISSION;
1185 aircraft->time = 0;
1186 aircraft->point = 0;
1187 aircraft->mission = mission;
1188
1189 return true;
1190}
1191
1196static void AII_InitialiseAircraftSlots (aircraft_t* aircraftTemplate)
1197{
1198 /* initialise weapon slots */
1199 for (int i = 0; i < MAX_AIRCRAFTSLOT; i++) {
1200 AII_InitialiseSlot(aircraftTemplate->weapons + i, aircraftTemplate, nullptr, nullptr, AC_ITEM_WEAPON);
1201 AII_InitialiseSlot(aircraftTemplate->electronics + i, aircraftTemplate, nullptr, nullptr, AC_ITEM_ELECTRONICS);
1202 }
1203 AII_InitialiseSlot(&aircraftTemplate->shield, aircraftTemplate, nullptr, nullptr, AC_ITEM_SHIELD);
1204}
1205
1210static char const* const air_position_strings[] = {
1211 "nose_left",
1212 "nose_center",
1213 "nose_right",
1214 "wing_left",
1215 "wing_right",
1216 "rear_left",
1217 "rear_center",
1218 "rear_right"
1219};
1220
1225 {"speed", V_INT, offsetof(aircraft_t, stats[AIR_STATS_SPEED]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1226 {"maxspeed", V_INT, offsetof(aircraft_t, stats[AIR_STATS_MAXSPEED]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1227 {"shield", V_INT, offsetof(aircraft_t, stats[AIR_STATS_SHIELD]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1228 {"ecm", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ECM]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1229 {"damage", V_INT, offsetof(aircraft_t, stats[AIR_STATS_DAMAGE]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1230 {"accuracy", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ACCURACY]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1231 {"antimatter", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ANTIMATTER]), MEMBER_SIZEOF(aircraft_t, stats[0])},
1232
1233 {nullptr, V_NULL, 0, 0}
1234};
1235
1237static const value_t aircraft_vals[] = {
1238 {"name", V_STRING, offsetof(aircraft_t, name), 0},
1239 {"defaultname", V_TRANSLATION_STRING, offsetof(aircraft_t, defaultName), 0},
1240 {"numteam", V_INT, offsetof(aircraft_t, maxTeamSize), MEMBER_SIZEOF(aircraft_t, maxTeamSize)},
1241 {"nogeoscape", V_BOOL, offsetof(aircraft_t, notOnGeoscape), MEMBER_SIZEOF(aircraft_t, notOnGeoscape)},
1242 {"interestlevel", V_INT, offsetof(aircraft_t, ufoInterestOnGeoscape), MEMBER_SIZEOF(aircraft_t, ufoInterestOnGeoscape)},
1243
1244 {"leader", V_BOOL, offsetof(aircraft_t, leader), MEMBER_SIZEOF(aircraft_t, leader)},
1245 {"image", V_HUNK_STRING, offsetof(aircraft_t, image), 0},
1246 {"model", V_HUNK_STRING, offsetof(aircraft_t, model), 0},
1247 /* price for selling/buying */
1248 {"price", V_INT, offsetof(aircraft_t, price), MEMBER_SIZEOF(aircraft_t, price)},
1249 /* this is needed to let the buy and sell screen look for the needed building */
1250 /* to place the aircraft in */
1251 {"productioncost", V_INT, offsetof(aircraft_t, productionCost), MEMBER_SIZEOF(aircraft_t, productionCost)},
1252 {"building", V_HUNK_STRING, offsetof(aircraft_t, building), 0},
1253 {"missiontypes", V_LIST, offsetof(aircraft_t, missionTypes), 0},
1254
1255 {nullptr, V_NULL, 0, 0}
1256};
1257
1260 {"range", V_INT, offsetof(aircraft_t, radar.range), MEMBER_SIZEOF(aircraft_t, radar.range)},
1261 {"trackingrange", V_INT, offsetof(aircraft_t, radar.trackingRange), MEMBER_SIZEOF(aircraft_t, radar.trackingRange)},
1262 {"detectionprobability", V_FLOAT, offsetof(aircraft_t, radar.ufoDetectionProbability), MEMBER_SIZEOF(aircraft_t, radar.ufoDetectionProbability)},
1263
1264 {nullptr, V_NULL, 0, 0}
1265};
1266
1269
1276void AIR_ParseAircraft (const char* name, const char** text, bool assignAircraftItems)
1277{
1278 const char* errhead = "AIR_ParseAircraft: unexpected end of file (aircraft ";
1279 aircraft_t* aircraftTemplate;
1280 const char* token;
1281 int i;
1282 technology_t* tech;
1284
1285 if (ccs.numAircraftTemplates >= MAX_AIRCRAFT) {
1286 cgi->Com_Printf("AIR_ParseAircraft: too many aircraft definitions; def \"%s\" ignored\n", name);
1287 return;
1288 }
1289
1290 if (!assignAircraftItems) {
1291 const aircraft_t* aircraftTemplateCheck = AIR_GetAircraftSilent(name);
1292 if (aircraftTemplateCheck) {
1293 cgi->Com_Printf("AIR_ParseAircraft: Second aircraft with same name found (%s) - second ignored\n", name);
1294 return;
1295 }
1296
1297 /* initialize the menu */
1298 aircraftTemplate = &ccs.aircraftTemplates[ccs.numAircraftTemplates];
1299 OBJZERO(*aircraftTemplate);
1300
1301 cgi->Com_DPrintf(DEBUG_CLIENT, "...found aircraft %s\n", name);
1302 aircraftTemplate->tpl = aircraftTemplate;
1303 aircraftTemplate->id = cgi->PoolStrDup(name, cp_campaignPool, 0);
1304 aircraftTemplate->status = AIR_HOME;
1305 /* default is no ufo */
1306 aircraftTemplate->setUfoType(UFO_NONE);
1307 aircraftTemplate->maxWeapons = 0;
1308 aircraftTemplate->maxElectronics = 0;
1309 AII_InitialiseAircraftSlots(aircraftTemplate);
1310 /* Initialise UFO sensored */
1311 RADAR_InitialiseUFOs(&aircraftTemplate->radar);
1312 /* Default detection probability remains 100% for now */
1313 aircraftTemplate->radar.ufoDetectionProbability = 1.0f;
1314
1315 ccs.numAircraftTemplates++;
1316 } else {
1317 aircraftTemplate = nullptr;
1318 for (i = 0; i < ccs.numAircraftTemplates; i++) {
1319 aircraft_t* aircraft = &ccs.aircraftTemplates[i];
1320 if (Q_streq(aircraft->id, name)) {
1321 aircraftTemplate = aircraft;
1322 break;
1323 }
1324 }
1325 if (!aircraftTemplate)
1326 Sys_Error("Could not find aircraft '%s'", name);
1327 }
1328
1329 /* get it's body */
1330 token = Com_Parse(text);
1331
1332 if (!*text || *token != '{') {
1333 cgi->Com_Printf("AIR_ParseAircraft: aircraft def \"%s\" without body ignored\n", name);
1334 return;
1335 }
1336
1337 do {
1338 token = cgi->Com_EParse(text, errhead, name);
1339 if (!*text)
1340 break;
1341 if (*token == '}')
1342 break;
1343
1344 if (Q_streq(token, "name")) {
1345 token = cgi->Com_EParse(text, errhead, name);
1346 if (!*text)
1347 return;
1348 if (token[0] == '_')
1349 token++;
1350 Q_strncpyz(aircraftTemplate->name, token, sizeof(aircraftTemplate->name));
1351 continue;
1352 } else if (Q_streq(token, "radar")) {
1353 token = cgi->Com_EParse(text, errhead, name);
1354 if (!*text || *token != '{') {
1355 cgi->Com_Printf("AIR_ParseAircraft: Invalid radar value for aircraft: %s\n", name);
1356 return;
1357 }
1358 do {
1359 token = cgi->Com_EParse(text, errhead, name);
1360 if (!*text)
1361 break;
1362 if (*token == '}')
1363 break;
1364
1365 if (!cgi->Com_ParseBlockToken(name, text, aircraftTemplate, aircraft_radar_vals, cp_campaignPool, token))
1366 cgi->Com_Printf("AIR_ParseAircraft: Ignoring unknown radar value '%s'\n", token);
1367 } while (*text); /* dummy condition */
1368 continue;
1369 }
1370 if (assignAircraftItems) {
1371 /* write into cp_campaignPool - this data is reparsed on every new game */
1372 /* blocks like param { [..] } - otherwise we would leave the loop too early */
1373 if (*token == '{') {
1374 Com_SkipBlock(text);
1375 } else if (Q_streq(token, "shield")) {
1376 token = cgi->Com_EParse(text, errhead, name);
1377 if (!*text)
1378 return;
1379 cgi->Com_DPrintf(DEBUG_CLIENT, "use shield %s for aircraft %s\n", token, aircraftTemplate->id);
1380 tech = RS_GetTechByID(token);
1381 if (tech)
1382 aircraftTemplate->shield.item = INVSH_GetItemByID(tech->provides);
1383 } else if (Q_streq(token, "slot")) {
1384 token = cgi->Com_EParse(text, errhead, name);
1385 if (!*text || *token != '{') {
1386 cgi->Com_Printf("AIR_ParseAircraft: Invalid slot value for aircraft: %s\n", name);
1387 return;
1388 }
1389 do {
1390 token = cgi->Com_EParse(text, errhead, name);
1391 if (!*text)
1392 break;
1393 if (*token == '}')
1394 break;
1395
1396 if (Q_streq(token, "type")) {
1397 token = cgi->Com_EParse(text, errhead, name);
1398 if (!*text)
1399 return;
1400 for (i = 0; i < MAX_ACITEMS; i++) {
1401 if (Q_streq(token, air_slot_type_strings[i])) {
1402 itemType = (aircraftItemType_t)i;
1403 switch (itemType) {
1404 case AC_ITEM_WEAPON:
1405 aircraftTemplate->maxWeapons++;
1406 break;
1408 aircraftTemplate->maxElectronics++;
1409 break;
1410 default:
1411 itemType = MAX_ACITEMS;
1412 break;
1413 }
1414 break;
1415 }
1416 }
1417 if (i == MAX_ACITEMS)
1418 cgi->Com_Error(ERR_DROP, "Unknown value '%s' for slot type\n", token);
1419 } else if (Q_streq(token, "position")) {
1420 token = cgi->Com_EParse(text, errhead, name);
1421 if (!*text)
1422 return;
1423 for (i = 0; i < AIR_POSITIONS_MAX; i++) {
1424 if (Q_streq(token, air_position_strings[i])) {
1425 switch (itemType) {
1426 case AC_ITEM_WEAPON:
1427 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].pos = (itemPos_t)i;
1428 break;
1430 aircraftTemplate->electronics[aircraftTemplate->maxElectronics - 1].pos = (itemPos_t)i;
1431 break;
1432 default:
1434 break;
1435 }
1436 break;
1437 }
1438 }
1439 if (i == AIR_POSITIONS_MAX)
1440 cgi->Com_Error(ERR_DROP, "Unknown value '%s' for slot position\n", token);
1441 } else if (Q_streq(token, "contains")) {
1442 token = cgi->Com_EParse(text, errhead, name);
1443 if (!*text)
1444 return;
1445 tech = RS_GetTechByID(token);
1446 if (tech) {
1447 switch (itemType) {
1448 case AC_ITEM_WEAPON:
1449 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].item = INVSH_GetItemByID(tech->provides);
1450 cgi->Com_DPrintf(DEBUG_CLIENT, "use weapon %s for aircraft %s\n", token, aircraftTemplate->id);
1451 break;
1453 aircraftTemplate->electronics[aircraftTemplate->maxElectronics - 1].item = INVSH_GetItemByID(tech->provides);
1454 cgi->Com_DPrintf(DEBUG_CLIENT, "use electronics %s for aircraft %s\n", token, aircraftTemplate->id);
1455 break;
1456 default:
1457 cgi->Com_Printf("Ignoring item value '%s' due to unknown slot type\n", token);
1458 break;
1459 }
1460 }
1461 } else if (Q_streq(token, "ammo")) {
1462 token = cgi->Com_EParse(text, errhead, name);
1463 if (!*text)
1464 return;
1465 tech = RS_GetTechByID(token);
1466 if (tech) {
1467 if (itemType == AC_ITEM_WEAPON) {
1468 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].ammo = INVSH_GetItemByID(tech->provides);
1469 cgi->Com_DPrintf(DEBUG_CLIENT, "use ammo %s for aircraft %s\n", token, aircraftTemplate->id);
1470 } else
1471 cgi->Com_Printf("Ignoring ammo value '%s' due to unknown slot type\n", token);
1472 }
1473 } else if (Q_streq(token, "size")) {
1474 token = cgi->Com_EParse(text, errhead, name);
1475 if (!*text)
1476 return;
1477 if (itemType == AC_ITEM_WEAPON) {
1478 if (Q_streq(token, "light"))
1479 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_LIGHT;
1480 else if (Q_streq(token, "medium"))
1481 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_MEDIUM;
1482 else if (Q_streq(token, "heavy"))
1483 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_HEAVY;
1484 else
1485 cgi->Com_Printf("Unknown size value for aircraft slot: '%s'\n", token);
1486 } else
1487 cgi->Com_Printf("Ignoring size parameter '%s' for non-weapon aircraft slots\n", token);
1488 } else
1489 cgi->Com_Printf("AIR_ParseAircraft: Ignoring unknown slot value '%s'\n", token);
1490 } while (*text); /* dummy condition */
1491 }
1492 } else {
1493 if (Q_streq(token, "shield")) {
1494 cgi->Com_EParse(text, errhead, name);
1495 continue;
1496 }
1497 /* check for some standard values */
1498 if (cgi->Com_ParseBlockToken(name, text, aircraftTemplate, aircraft_vals, cp_campaignPool, token))
1499 continue;
1500
1501 if (Q_streq(token, "slot")) {
1502 token = cgi->Com_EParse(text, errhead, name);
1503 if (!*text || *token != '{') {
1504 cgi->Com_Printf("AIR_ParseAircraft: Invalid slot value for aircraft: %s\n", name);
1505 return;
1506 }
1507 Com_SkipBlock(text);
1508 } else if (Q_streq(token, "param")) {
1509 token = cgi->Com_EParse(text, errhead, name);
1510 if (!*text || *token != '{') {
1511 cgi->Com_Printf("AIR_ParseAircraft: Invalid param value for aircraft: %s\n", name);
1512 return;
1513 }
1514 do {
1515 token = cgi->Com_EParse(text, errhead, name);
1516 if (!*text)
1517 break;
1518 if (*token == '}')
1519 break;
1520
1521 if (Q_streq(token, "range")) {
1522 /* this is the range of aircraft, must be translated into fuel */
1523 token = cgi->Com_EParse(text, errhead, name);
1524 if (!*text)
1525 return;
1526 cgi->Com_EParseValue(aircraftTemplate, token, V_INT, offsetof(aircraft_t, stats[AIR_STATS_FUELSIZE]), MEMBER_SIZEOF(aircraft_t, stats[0]));
1527 if (aircraftTemplate->stats[AIR_STATS_SPEED] == 0)
1528 cgi->Com_Error(ERR_DROP, "AIR_ParseAircraft: speed value must be entered before range value");
1529 aircraftTemplate->stats[AIR_STATS_FUELSIZE] = (int) (2.0f * (float)DateTime::SECONDS_PER_HOUR * aircraftTemplate->stats[AIR_STATS_FUELSIZE]) /
1530 ((float) aircraftTemplate->stats[AIR_STATS_SPEED]);
1531 } else {
1532 if (!cgi->Com_ParseBlockToken(name, text, aircraftTemplate, aircraft_param_vals, cp_campaignPool, token))
1533 cgi->Com_Printf("AIR_ParseAircraft: Ignoring unknown param value '%s'\n", token);
1534 }
1535 } while (*text); /* dummy condition */
1536 } else {
1537 cgi->Com_Printf("AIR_ParseAircraft: unknown token \"%s\" ignored (aircraft %s)\n", token, name);
1538 cgi->Com_EParse(text, errhead, name);
1539 }
1540 } /* assignAircraftItems */
1541 } while (*text);
1542
1543 if (aircraftTemplate->building == nullptr)
1544 aircraftTemplate->setUfoType(cgi->Com_UFOShortNameToID(aircraftTemplate->id));
1545
1546 if (aircraftTemplate->productionCost == 0)
1547 aircraftTemplate->productionCost = aircraftTemplate->price;
1548}
1549
1550#ifdef DEBUG
1551static void AIR_ListCraftIndexes_f (void)
1552{
1553 cgi->Com_Printf("globalIDX\t(Craftname)\n");
1554 AIR_Foreach(aircraft) {
1555 cgi->Com_Printf("%i\t(%s)\n", aircraft->idx, aircraft->name);
1556 }
1557}
1558
1562static void AIR_ListAircraftSamples_f (void)
1563{
1564 int i = 0, max = ccs.numAircraftTemplates;
1565 const value_t* vp;
1566
1567 cgi->Com_Printf("%i aircraft\n", max);
1568 if (cgi->Cmd_Argc() == 2) {
1569 max = atoi(cgi->Cmd_Argv(1));
1570 if (max >= ccs.numAircraftTemplates || max < 0)
1571 return;
1572 i = max - 1;
1573 }
1574 for (; i < max; i++) {
1575 aircraft_t* aircraftTemplate = &ccs.aircraftTemplates[i];
1576 cgi->Com_Printf("aircraft: '%s'\n", aircraftTemplate->id);
1577 for (vp = aircraft_vals; vp->string; vp++) {
1578 cgi->Com_Printf("..%s: %s\n", vp->string, cgi->Com_ValueToStr(aircraftTemplate, vp->type, vp->ofs));
1579 }
1580 for (vp = aircraft_param_vals; vp->string; vp++) {
1581 cgi->Com_Printf("..%s: %s\n", vp->string, cgi->Com_ValueToStr(aircraftTemplate, vp->type, vp->ofs));
1582 }
1583 }
1584}
1585#endif
1586
1587/*===============================================
1588Aircraft functions related to UFOs or missions.
1589===============================================*/
1590
1597{
1598 AIR_Foreach(aircraft) {
1599 if (aircraft->mission == mission)
1600 AIR_AircraftReturnToBase(aircraft);
1601 }
1602}
1603
1609void AIR_AircraftsNotifyUFORemoved (const aircraft_t* const ufo, bool destroyed)
1610{
1611 base_t* base;
1612
1613 assert(ufo);
1614
1615 /* Aircraft currently purchasing the specified ufo will be redirect to base */
1616 AIR_Foreach(aircraft) {
1617 if (ufo == aircraft->aircraftTarget) {
1618 AIR_AircraftReturnToBase(aircraft);
1619 } else if (destroyed && (ufo < aircraft->aircraftTarget)) {
1620 aircraft->aircraftTarget--;
1621 }
1622 }
1623
1625 base = nullptr;
1626 while ((base = B_GetNext(base)) != nullptr) {
1627 int i;
1628 /* Base currently targeting the specified ufo loose their target */
1629 for (i = 0; i < base->numBatteries; i++) {
1630 baseWeapon_t* baseWeapon = &base->batteries[i];
1631 if (baseWeapon->target == ufo)
1632 baseWeapon->target = nullptr;
1633 else if (destroyed && (baseWeapon->target > ufo))
1634 baseWeapon->target--;
1635 }
1636 for (i = 0; i < base->numLasers; i++) {
1637 baseWeapon_t* baseWeapon = &base->lasers[i];
1638 if (baseWeapon->target == ufo)
1639 baseWeapon->target = nullptr;
1640 else if (destroyed && (baseWeapon->target > ufo))
1641 baseWeapon->target--;
1642 }
1643 }
1644}
1645
1652{
1653 AIR_Foreach(aircraft) {
1654 if (aircraft->status == AIR_UFO && ufo == aircraft->aircraftTarget)
1655 AIR_AircraftReturnToBase(aircraft);
1656 }
1657}
1658
1669static inline float AIR_GetDestinationFunction (const float c, const float B, const float speedRatio, float a)
1670{
1671 return pow(cos(a) - cos(speedRatio * a) * cos(c), 2.0f)
1672 - sin(c) * sin(c) * (sin(speedRatio * a) * sin(speedRatio * a) - sin(a) * sin(a) * sin(B) * sin(B));
1673}
1674
1685static inline float AIR_GetDestinationDerivativeFunction (const float c, const float B, const float speedRatio, float a)
1686{
1687 return 2. * (cos(a) - cos(speedRatio * a) * cos(c)) * (- sin(a) + speedRatio * sin(speedRatio * a) * cos(c))
1688 - sin(c) * sin(c) * (speedRatio * sin(2. * speedRatio * a) - sin(2. * a) * sin(B) * sin(B));
1689}
1690
1701static float AIR_GetDestinationFindRoot (const float c, const float B, const float speedRatio, float start)
1702{
1703 const float BIG_STEP = 0.05f;
1705 const float PRECISION_ROOT = 0.000001f;
1706 const float MAXIMUM_VALUE_ROOT = 2.0f * M_PI;
1707 float epsilon;
1708 float begin, end, middle;
1709 float fBegin, fEnd, fMiddle;
1710 float fdBegin, fdEnd, fdMiddle;
1711
1712 /* there may be several solution, first try to find roughly the smallest one */
1713 end = start + PRECISION_ROOT / 10.0f; /* don't start at 0: derivative is 0 */
1714 fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
1715 fdEnd = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, end);
1716
1717 do {
1718 begin = end;
1719 fBegin = fEnd;
1720 fdBegin = fdEnd;
1721 end = begin + BIG_STEP;
1722 if (end > MAXIMUM_VALUE_ROOT) {
1723 end = MAXIMUM_VALUE_ROOT;
1724 fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
1725 break;
1726 }
1727 fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
1728 fdEnd = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, end);
1729 } while (fBegin * fEnd > 0 && fdBegin * fdEnd > 0);
1730
1731 if (fBegin * fEnd > 0) {
1732 if (fdBegin * fdEnd < 0) {
1733 /* the sign of derivative changed: we could have a root somewhere
1734 * between begin and end: try to narrow down the root until fBegin * fEnd < 0 */
1735 middle = (begin + end) / 2.;
1736 fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1737 fdMiddle = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, middle);
1738 do {
1739 if (fdEnd * fdMiddle < 0) {
1740 /* root is bigger than middle */
1741 begin = middle;
1742 fBegin = fMiddle;
1743 fdBegin = fdMiddle;
1744 } else if (fdBegin * fdMiddle < 0) {
1745 /* root is smaller than middle */
1746 end = middle;
1747 fEnd = fMiddle;
1748 fdEnd = fdMiddle;
1749 } else {
1750 cgi->Com_Error(ERR_DROP, "AIR_GetDestinationFindRoot: Error in calculation, can't find root");
1751 }
1752 middle = (begin + end) / 2.;
1753 fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1754 fdMiddle = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, middle);
1755
1756 epsilon = end - middle ;
1757
1758 if (epsilon < PRECISION_ROOT) {
1759 /* this is only a root of the derivative: no root of the function itself
1760 * proceed with next value */
1761 return AIR_GetDestinationFindRoot(c, B, speedRatio, end);
1762 }
1763 } while (fBegin * fEnd > 0);
1764 } else {
1765 /* there's no solution, return default value */
1766 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationFindRoot: Did not find solution is range %.2f, %.2f\n", start, MAXIMUM_VALUE_ROOT);
1767 return -10.;
1768 }
1769 }
1770
1771 /* now use dichotomy to get more precision on the solution */
1772
1773 middle = (begin + end) / 2.;
1774 fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1775
1776 do {
1777 if (fEnd * fMiddle < 0) {
1778 /* root is bigger than middle */
1779 begin = middle;
1780 fBegin = fMiddle;
1781 } else if (fBegin * fMiddle < 0) {
1782 /* root is smaller than middle */
1783 end = middle;
1784 fEnd = fMiddle;
1785 } else {
1786 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationFindRoot: Error in calculation, one of the value is nan\n");
1787 return -10.;
1788 }
1789 middle = (begin + end) / 2.;
1790 fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
1791
1792 epsilon = end - middle ;
1793 } while (epsilon > PRECISION_ROOT);
1794 return middle;
1795}
1796
1807{
1808 vec3_t shooterPos, targetPos, targetDestPos, shooterDestPos, rotationAxis;
1809 vec3_t tangentVectTS, tangentVectTD;
1810 float a, b, c, B;
1811
1812 const float speedRatio = (float)(shooter->stats[AIR_STATS_SPEED]) / target->stats[AIR_STATS_SPEED];
1813
1814 c = GetDistanceOnGlobe(shooter->pos, target->pos) * torad;
1815
1816 /* Convert aircraft position into cartesian frame */
1817 PolarToVec(shooter->pos, shooterPos);
1818 PolarToVec(target->pos, targetPos);
1819 PolarToVec(target->route.point[target->route.numPoints - 1], targetDestPos);
1820
1837
1838 /* Get first vector (tangent to triangle in T, in the direction of D) */
1839 CrossProduct(targetPos, shooterPos, rotationAxis);
1840 VectorNormalize(rotationAxis);
1841 RotatePointAroundVector(tangentVectTS, rotationAxis, targetPos, 90.0f);
1842 /* Get second vector (tangent to triangle in T, in the direction of S) */
1843 CrossProduct(targetPos, targetDestPos, rotationAxis);
1844 VectorNormalize(rotationAxis);
1845 RotatePointAroundVector(tangentVectTD, rotationAxis, targetPos, 90.0f);
1846
1847 /* Get angle B of the triangle (in radian) */
1848 B = acos(DotProduct(tangentVectTS, tangentVectTD));
1849
1850 /* Look for a value, as long as we don't have a proper value */
1851 for (a = 0;;) {
1852 a = AIR_GetDestinationFindRoot(c, B, speedRatio, a);
1853
1854 if (a < 0.) {
1855 /* we couldn't find a root on the whole range */
1856 break;
1857 }
1858
1859 /* Get rotation vector */
1860 CrossProduct(targetPos, targetDestPos, rotationAxis);
1861 VectorNormalize(rotationAxis);
1862
1863 /* Rotate target position of dist to find destination point */
1864 RotatePointAroundVector(shooterDestPos, rotationAxis, targetPos, a * todeg);
1865 VecToPolar(shooterDestPos, dest);
1866
1867 b = GetDistanceOnGlobe(shooter->pos, dest) * torad;
1868
1869 if (fabs(b - speedRatio * a) < .1)
1870 break;
1871
1872 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationWhilePursuing: reject solution: doesn't fit %.2f == %.2f\n", b, speedRatio * a);
1873 }
1874
1875 if (a < 0.) {
1876 /* did not find solution, go directly to target direction */
1877 Vector2Copy(target->pos, dest);
1878 return;
1879 }
1880
1882 /* make sure we don't get a NAN value */
1883 assert(dest[0] <= 180.0f && dest[0] >= -180.0f && dest[1] <= 90.0f && dest[1] >= -90.0f);
1884}
1885
1892{
1893 vec2_t dest;
1894
1895 if (!aircraft)
1896 return false;
1897
1898 /* if aircraft was in base */
1899 if (AIR_IsAircraftInBase(aircraft)) {
1900 /* reload its ammunition */
1901 AII_ReloadAircraftWeapons(aircraft);
1902 }
1903
1904 AIR_GetDestinationWhilePursuing(aircraft, ufo, dest);
1905 /* check if aircraft has enough fuel */
1906 if (!AIR_AircraftHasEnoughFuel(aircraft, dest)) {
1907 /* did not find solution, go directly to target direction if enough fuel */
1908 if (AIR_AircraftHasEnoughFuel(aircraft, ufo->pos)) {
1909 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_SendAircraftPursuingUFO: not enough fuel to anticipate target movement: go directly to target position\n");
1910 Vector2Copy(ufo->pos, dest);
1911 } else {
1912 MS_AddNewMessage(_("Notice"), va(_("Craft %s has not enough fuel to intercept UFO: fly back to %s."), aircraft->name, aircraft->homebase->name));
1913 AIR_AircraftReturnToBase(aircraft);
1914 return false;
1915 }
1916 }
1917
1918 GEO_CalcLine(aircraft->pos, dest, &aircraft->route);
1919 aircraft->status = AIR_UFO;
1920 aircraft->time = 0;
1921 aircraft->point = 0;
1922 aircraft->aircraftTarget = ufo;
1923 return true;
1924}
1925
1926/*============================================
1927Aircraft functions related to team handling.
1928============================================*/
1929
1935{
1936 cgi->LIST_Delete(&aircraft->acTeam);
1937}
1938
1945bool AIR_AddToAircraftTeam (aircraft_t* aircraft, Employee* employee)
1946{
1947 if (!employee)
1948 return false;
1949
1950 if (!aircraft)
1951 return false;
1952
1953 if (AIR_GetTeamSize(aircraft) < aircraft->maxTeamSize) {
1954 cgi->LIST_AddPointer(&aircraft->acTeam, employee);
1955 return true;
1956 }
1957
1958 return false;
1959}
1960
1967bool AIR_IsInAircraftTeam (const aircraft_t* aircraft, const Employee* employee)
1968{
1969 assert(aircraft);
1970 assert(employee);
1971 return cgi->LIST_GetPointer(aircraft->acTeam, employee) != nullptr;
1972}
1973
1979int AIR_GetTeamSize (const aircraft_t* aircraft)
1980{
1981 assert(aircraft);
1982 return cgi->LIST_Count(aircraft->acTeam);
1983}
1984
1993bool AIR_SetPilot (aircraft_t* aircraft, Employee* pilot)
1994{
1995 if (aircraft->pilot == nullptr || pilot == nullptr) {
1996 aircraft->pilot = pilot;
1997 return true;
1998 }
1999
2000 return false;
2001}
2002
2009{
2010 const Employee* e = aircraft->pilot;
2011
2012 if (!e)
2013 return nullptr;
2014
2016}
2017
2024{
2025 if (aircraft == nullptr)
2026 return false;
2027
2028 if (aircraft->pilot == nullptr)
2029 return false;
2030
2031 const int pilotSkill = aircraft->pilot->chr.score.skills[SKILL_PILOTING];
2032 float baseProbability = (float) pilotSkill;
2033
2034 const byte* color = GEO_GetColor(aircraft->pos, MAPTYPE_TERRAIN, nullptr);
2035
2036 baseProbability *= cgi->csi->terrainDefs.getSurvivalChance(color);
2037
2038 /* Add a random factor to our probability */
2039 float randomProbability = crand() * (float) pilotSkill;
2040 if (randomProbability > 0.25f * baseProbability) {
2041 while (randomProbability > 0.25f * baseProbability)
2042 randomProbability /= 2.0f;
2043 }
2044
2045 const float survivalProbability = baseProbability + randomProbability;
2046 return survivalProbability >= (float) pilotSkill;
2047}
2048
2055{
2056 AIR_ForeachFromBase(aircraft, base) {
2057 if (AIR_SetPilot(aircraft, pilot))
2058 break;
2059 }
2060}
2061
2069{
2070 AIR_ForeachFromBase(aircraft, base) {
2071 if (AIR_GetPilot(aircraft) == pilot) {
2072 AIR_SetPilot(aircraft, nullptr);
2073 break;
2074 }
2075 }
2076}
2077
2085int AIR_GetAircraftWeaponRanges (const aircraftSlot_t* slot, int maxSlot, float* weaponRanges)
2086{
2087 int idxSlot;
2088 float allWeaponRanges[MAX_AIRCRAFTSLOT];
2089 int numAllWeaponRanges = 0;
2090 int numUniqueWeaponRanges = 0;
2091
2092 assert(slot);
2093
2094 /* We choose the usable weapon to add to the weapons array */
2095 for (idxSlot = 0; idxSlot < maxSlot; idxSlot++) {
2096 const aircraftSlot_t* weapon = slot + idxSlot;
2097 const objDef_t* ammo = weapon->ammo;
2098
2099 if (!ammo)
2100 continue;
2101
2102 allWeaponRanges[numAllWeaponRanges] = ammo->craftitem.stats[AIR_STATS_WRANGE];
2103 numAllWeaponRanges++;
2104 }
2105
2106 if (numAllWeaponRanges > 0) {
2107 /* sort the list of all weapon ranges and create an array with only the unique ranges */
2108 qsort(allWeaponRanges, numAllWeaponRanges, sizeof(allWeaponRanges[0]), Q_FloatSort);
2109
2110 int idxAllWeap;
2111 for (idxAllWeap = 0; idxAllWeap < numAllWeaponRanges; idxAllWeap++) {
2112 if (allWeaponRanges[idxAllWeap] != weaponRanges[numUniqueWeaponRanges - 1] || idxAllWeap == 0) {
2113 weaponRanges[numUniqueWeaponRanges] = allWeaponRanges[idxAllWeap];
2114 numUniqueWeaponRanges++;
2115 }
2116 }
2117 }
2118
2119 return numUniqueWeaponRanges;
2120}
2121
2127static void AIR_SaveRouteXML (xmlNode_t* node, const mapline_t* route)
2128{
2129 xmlNode_t* subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_ROUTE);
2130 cgi->XML_AddFloatValue(subnode, SAVE_AIRCRAFT_ROUTE_DISTANCE, route->distance);
2131 for (int j = 0; j < route->numPoints; j++) {
2132 cgi->XML_AddPos2(subnode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[j]);
2133 }
2134}
2135
2146static void AIR_SaveAircraftSlotsXML (const aircraftSlot_t* slot, const int num, xmlNode_t* p, bool weapon)
2147{
2148 for (int i = 0; i < num; i++) {
2149 xmlNode_t* sub = cgi->XML_AddNode(p, SAVE_AIRCRAFT_SLOT);
2150 AII_SaveOneSlotXML(sub, &slot[i], weapon);
2151 }
2152}
2153
2160static bool AIR_SaveAircraftXML (xmlNode_t* p, const aircraft_t* const aircraft, bool const isUfo)
2161{
2162 xmlNode_t* node;
2163 xmlNode_t* subnode;
2164 const Employee* pilot;
2165
2166 cgi->Com_RegisterConstList(saveAircraftConstants);
2167
2168 node = cgi->XML_AddNode(p, SAVE_AIRCRAFT_AIRCRAFT);
2169
2170 cgi->XML_AddInt(node, SAVE_AIRCRAFT_IDX, aircraft->idx);
2171 cgi->XML_AddString(node, SAVE_AIRCRAFT_ID, aircraft->id);
2172 cgi->XML_AddString(node, SAVE_AIRCRAFT_NAME, aircraft->name);
2173
2174 cgi->XML_AddString(node, SAVE_AIRCRAFT_STATUS, cgi->Com_GetConstVariable(SAVE_AIRCRAFTSTATUS_NAMESPACE, aircraft->status));
2175 cgi->XML_AddInt(node, SAVE_AIRCRAFT_FUEL, aircraft->fuel);
2176 cgi->XML_AddInt(node, SAVE_AIRCRAFT_DAMAGE, aircraft->damage);
2177 cgi->XML_AddPos3(node, SAVE_AIRCRAFT_POS, aircraft->pos);
2178 cgi->XML_AddPos3(node, SAVE_AIRCRAFT_DIRECTION, aircraft->direction);
2179 cgi->XML_AddInt(node, SAVE_AIRCRAFT_POINT, aircraft->point);
2180 cgi->XML_AddInt(node, SAVE_AIRCRAFT_TIME, aircraft->time);
2181
2182 subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_WEAPONS);
2183 AIR_SaveAircraftSlotsXML(aircraft->weapons, aircraft->maxWeapons, subnode, true);
2184 subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_SHIELDS);
2185 AIR_SaveAircraftSlotsXML(&aircraft->shield, 1, subnode, false);
2186 subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_ELECTRONICS);
2187 AIR_SaveAircraftSlotsXML(aircraft->electronics, aircraft->maxElectronics, subnode, false);
2188
2189 AIR_SaveRouteXML(node, &aircraft->route);
2190
2191 if (isUfo) {
2192#ifdef DEBUG
2193 if (!aircraft->mission)
2194 cgi->Com_Printf("Error: UFO '%s'is not linked to any mission\n", aircraft->id);
2195#endif
2196 cgi->XML_AddString(node, SAVE_AIRCRAFT_MISSIONID, aircraft->mission->id);
2198 cgi->XML_AddInt(node, SAVE_AIRCRAFT_DETECTIONIDX, aircraft->detectionIdx);
2199 cgi->XML_AddDate(node, SAVE_AIRCRAFT_LASTSPOTTED_DATE, aircraft->lastSpotted.getDateAsDays(), aircraft->lastSpotted.getTimeAsSeconds());
2200 } else {
2201 if (aircraft->status == AIR_MISSION || aircraft->status == AIR_DROP) {
2202 assert(aircraft->mission);
2203 cgi->XML_AddString(node, SAVE_AIRCRAFT_MISSIONID, aircraft->mission->id);
2204 }
2205 if (aircraft->homebase) {
2206 cgi->XML_AddInt(node, SAVE_AIRCRAFT_HOMEBASE, aircraft->homebase->idx);
2207 }
2208 }
2209
2210 if (aircraft->aircraftTarget) {
2211 if (isUfo)
2212 cgi->XML_AddInt(node, SAVE_AIRCRAFT_AIRCRAFTTARGET, aircraft->aircraftTarget->idx);
2213 else
2215 }
2216
2217 subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_AIRSTATS);
2218 for (int i = 0; i < AIR_STATS_MAX; i++) {
2219#ifdef DEBUG
2220 /* UFO HP can be < 0 if the UFO has been destroyed */
2221 if (!(isUfo && i == AIR_STATS_DAMAGE) && aircraft->stats[i] < 0)
2222 cgi->Com_Printf("Warning: ufo '%s' stats %i: %i is smaller than 0\n", aircraft->id, i, aircraft->stats[i]);
2223#endif
2224 if (aircraft->stats[i] != 0) {
2225 xmlNode_t* statNode = cgi->XML_AddNode(subnode, SAVE_AIRCRAFT_AIRSTAT);
2226 cgi->XML_AddString(statNode, SAVE_AIRCRAFT_AIRSTATID, cgi->Com_GetConstVariable(SAVE_AIRCRAFTSTAT_NAMESPACE, i));
2227 cgi->XML_AddLong(statNode, SAVE_AIRCRAFT_VAL, aircraft->stats[i]);
2228 }
2229 }
2230
2231 cgi->XML_AddBoolValue(node, SAVE_AIRCRAFT_DETECTED, aircraft->detected);
2232 cgi->XML_AddBoolValue(node, SAVE_AIRCRAFT_LANDED, aircraft->landed);
2233
2234 cgi->Com_UnregisterConstList(saveAircraftConstants);
2235
2236 /* All other information is not needed for ufos */
2237 if (isUfo)
2238 return true;
2239
2240 subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_AIRCRAFTTEAM);
2241 LIST_Foreach(aircraft->acTeam, Employee, employee) {
2242 xmlNode_t* ssnode = cgi->XML_AddNode(subnode, SAVE_AIRCRAFT_MEMBER);
2243 cgi->XML_AddInt(ssnode, SAVE_AIRCRAFT_TEAM_UCN, employee->chr.ucn);
2244 }
2245
2246 pilot = AIR_GetPilot(aircraft);
2247 if (pilot)
2248 cgi->XML_AddInt(node, SAVE_AIRCRAFT_PILOTUCN, pilot->chr.ucn);
2249
2250 /* itemcargo */
2251 if (aircraft->itemCargo != nullptr) {
2252 subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_CARGO);
2253 if (!subnode)
2254 return false;
2255 aircraft->itemCargo->save(subnode);
2256 }
2257
2258 /* aliencargo */
2259 if (aircraft->alienCargo != nullptr) {
2260 subnode = cgi->XML_AddNode(node, SAVE_AIRCRAFT_ALIENCARGO);
2261 if (!subnode)
2262 return false;
2263 aircraft->alienCargo->save(subnode);
2264 }
2265
2266 return true;
2267}
2268
2276{
2277 /* save phalanx aircraft */
2278 xmlNode_t* snode = cgi->XML_AddNode(parent, SAVE_AIRCRAFT_PHALANX);
2279 AIR_Foreach(aircraft) {
2280 AIR_SaveAircraftXML(snode, aircraft, false);
2281 }
2282
2283 /* save the ufos on geoscape */
2284 snode = cgi->XML_AddNode(parent, SAVE_AIRCRAFT_UFOS);
2285 for (int i = 0; i < MAX_UFOONGEOSCAPE; i++) {
2286 const aircraft_t* ufo = UFO_GetByIDX(i);
2287 if (!ufo || (ufo->id == nullptr))
2288 continue;
2289 AIR_SaveAircraftXML(snode, ufo, true);
2290 }
2291
2292 /* Save projectiles. */
2293 xmlNode_t* node = cgi->XML_AddNode(parent, SAVE_AIRCRAFT_PROJECTILES);
2294 if (!AIRFIGHT_SaveXML(node))
2295 return false;
2296
2297 return true;
2298}
2299
2310static void AIR_LoadAircraftSlotsXML (aircraft_t* aircraft, aircraftSlot_t* slot, xmlNode_t* p, bool weapon, const int max)
2311{
2312 xmlNode_t* act;
2313 int i;
2314 for (i = 0, act = cgi->XML_GetNode(p, SAVE_AIRCRAFT_SLOT); act && i <= max; act = cgi->XML_GetNextNode(act, p, SAVE_AIRCRAFT_SLOT), i++) {
2315 slot[i].aircraft = aircraft;
2316 AII_LoadOneSlotXML(act, &slot[i], weapon);
2317 }
2318 if (i > max)
2319 cgi->Com_Printf("Error: Trying to assign more than max (%d) Aircraft Slots (cur is %d)\n", max, i);
2320
2321}
2322
2328static bool AIR_LoadRouteXML (xmlNode_t* p, mapline_t* route)
2329{
2330 xmlNode_t* actual;
2331 xmlNode_t* snode;
2332 int count = 0;
2333
2334 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_ROUTE);
2335 if (!snode)
2336 return false;
2337
2338 for (actual = cgi->XML_GetPos2(snode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[count]); actual && count <= LINE_MAXPTS;
2339 actual = cgi->XML_GetNextPos2(actual, snode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[++count]))
2340 ;
2341 if (count > LINE_MAXPTS) {
2342 cgi->Com_Printf("AIR_Load: number of points (%i) for UFO route exceed maximum value (%i)\n", count, LINE_MAXPTS);
2343 return false;
2344 }
2345 route->numPoints = count;
2346 route->distance = cgi->XML_GetFloat(snode, SAVE_AIRCRAFT_ROUTE_DISTANCE, 0.0);
2347 return true;
2348}
2349
2356{
2357 xmlNode_t* snode;
2358 xmlNode_t* ssnode;
2359 const char* statusId;
2360 /* vars, if aircraft wasn't found */
2361 int tmpInt;
2362 int status;
2363 const char* s = cgi->XML_GetString(p, SAVE_AIRCRAFT_ID);
2364 const aircraft_t* crafttype = AIR_GetAircraft(s);
2365
2366 /* Copy all datas that don't need to be saved (tpl, hangar, ...) */
2367 *craft = *crafttype;
2368
2369 tmpInt = cgi->XML_GetInt(p, SAVE_AIRCRAFT_HOMEBASE, MAX_BASES);
2370 craft->homebase = (tmpInt != MAX_BASES) ? B_GetBaseByIDX(tmpInt) : nullptr;
2371
2372 craft->idx = cgi->XML_GetInt(p, SAVE_AIRCRAFT_IDX, -1);
2373 if (craft->idx < 0) {
2374 cgi->Com_Printf("Invalid (or no) aircraft index %i\n", craft->idx);
2375 return false;
2376 }
2377
2378 cgi->Com_RegisterConstList(saveAircraftConstants);
2379
2380 statusId = cgi->XML_GetString(p, SAVE_AIRCRAFT_STATUS);
2381 if (!cgi->Com_GetConstIntFromNamespace(SAVE_AIRCRAFTSTATUS_NAMESPACE, statusId, &status)) {
2382 cgi->Com_Printf("Invalid aircraft status '%s'\n", statusId);
2383 cgi->Com_UnregisterConstList(saveAircraftConstants);
2384 return false;
2385 }
2386
2387 craft->status = (aircraftStatus_t)status;
2388 craft->fuel = cgi->XML_GetInt(p, SAVE_AIRCRAFT_FUEL, 0);
2389 craft->damage = cgi->XML_GetInt(p, SAVE_AIRCRAFT_DAMAGE, 0);
2390 cgi->XML_GetPos3(p, SAVE_AIRCRAFT_POS, craft->pos);
2391
2392 cgi->XML_GetPos3(p, SAVE_AIRCRAFT_DIRECTION, craft->direction);
2393 craft->point = cgi->XML_GetInt(p, SAVE_AIRCRAFT_POINT, 0);
2394 craft->time = cgi->XML_GetInt(p, SAVE_AIRCRAFT_TIME, 0);
2395
2396 if (!AIR_LoadRouteXML(p, &craft->route)) {
2397 cgi->Com_UnregisterConstList(saveAircraftConstants);
2398 return false;
2399 }
2400
2401 s = cgi->XML_GetString(p, SAVE_AIRCRAFT_NAME);
2402 if (s[0] == '\0')
2403 s = _(craft->defaultName);
2404 Q_strncpyz(craft->name, s, sizeof(craft->name));
2405
2406 s = cgi->XML_GetString(p, SAVE_AIRCRAFT_MISSIONID);
2407 craft->missionID = cgi->PoolStrDup(s, cp_campaignPool, 0);
2408
2409 if (!craft->homebase) {
2410 /* detection id and time */
2411 craft->detectionIdx = cgi->XML_GetInt(p, SAVE_AIRCRAFT_DETECTIONIDX, 0);
2412 int date;
2413 int time;
2414 cgi->XML_GetDate(p, SAVE_AIRCRAFT_LASTSPOTTED_DATE, &date, &time);
2415 craft->lastSpotted = DateTime(date, time);
2416 }
2417
2418 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_AIRSTATS);
2419 for (ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_AIRSTAT); ssnode; ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRSTAT)) {
2420 const char* statId = cgi->XML_GetString(ssnode, SAVE_AIRCRAFT_AIRSTATID);
2421 int idx;
2422
2423 if (!cgi->Com_GetConstIntFromNamespace(SAVE_AIRCRAFTSTAT_NAMESPACE, statId, &idx)) {
2424 cgi->Com_Printf("Invalid aircraft stat '%s'\n", statId);
2425 cgi->Com_UnregisterConstList(saveAircraftConstants);
2426 return false;
2427 }
2428 craft->stats[idx] = cgi->XML_GetLong(ssnode, SAVE_AIRCRAFT_VAL, 0);
2429#ifdef DEBUG
2430 /* UFO HP can be < 0 if the UFO has been destroyed */
2431 if (!(!craft->homebase && idx == AIR_STATS_DAMAGE) && craft->stats[idx] < 0)
2432 cgi->Com_Printf("Warning: ufo '%s' stats %i: %i is smaller than 0\n", craft->id, idx, craft->stats[idx]);
2433#endif
2434 }
2435
2436 craft->detected = cgi->XML_GetBool(p, SAVE_AIRCRAFT_DETECTED, false);
2437 craft->landed = cgi->XML_GetBool(p, SAVE_AIRCRAFT_LANDED, false);
2438
2439 tmpInt = cgi->XML_GetInt(p, SAVE_AIRCRAFT_AIRCRAFTTARGET, -1);
2440 if (tmpInt == -1)
2441 craft->aircraftTarget = nullptr;
2442 else if (!craft->homebase)
2443 craft->aircraftTarget = AIR_AircraftGetFromIDX(tmpInt);
2444 else
2445 craft->aircraftTarget = ccs.ufos + tmpInt;
2446
2447 /* read equipment slots */
2448 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_WEAPONS);
2449 AIR_LoadAircraftSlotsXML(craft, craft->weapons, snode, true, craft->maxWeapons);
2450 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_SHIELDS);
2451 AIR_LoadAircraftSlotsXML(craft, &craft->shield, snode, false, 1);
2452 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_ELECTRONICS);
2453 AIR_LoadAircraftSlotsXML(craft, craft->electronics, snode, false, craft->maxElectronics);
2454
2455 cgi->Com_UnregisterConstList(saveAircraftConstants);
2456
2457 /* All other information is not needed for ufos */
2458 if (!craft->homebase)
2459 return true;
2460
2461 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_AIRCRAFTTEAM);
2462 for (ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_MEMBER); AIR_GetTeamSize(craft) < craft->maxTeamSize && ssnode;
2463 ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_MEMBER)) {
2464 const int ucn = cgi->XML_GetInt(ssnode, SAVE_AIRCRAFT_TEAM_UCN, -1);
2465 if (ucn != -1)
2466 cgi->LIST_AddPointer(&craft->acTeam, E_GetEmployeeFromChrUCN(ucn));
2467 }
2468
2469 tmpInt = cgi->XML_GetInt(p, SAVE_AIRCRAFT_PILOTUCN, -1);
2470 /* the employee subsystem is loaded after the base subsystem
2471 * this means, that the pilot pointer is not (really) valid until
2472 * E_Load was called, too */
2473 if (tmpInt != -1)
2474 AIR_SetPilot(craft, E_GetEmployeeFromChrUCN(tmpInt));
2475 else
2476 AIR_SetPilot(craft, nullptr);
2477
2478 RADAR_Initialise(&craft->radar, crafttype->radar.range, crafttype->radar.trackingRange, 1.0f, false);
2479 RADAR_InitialiseUFOs(&craft->radar);
2480 craft->radar.ufoDetectionProbability = crafttype->radar.ufoDetectionProbability;
2481
2482 /* itemcargo */
2483 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_CARGO);
2484 if (snode) {
2485 craft->itemCargo = new ItemCargo();
2486 if (craft->itemCargo == nullptr)
2487 cgi->Com_Error(ERR_DROP, "AIR_LoadAircraftXML: Cannot create ItemCargo object\n");
2488 craft->itemCargo->load(snode);
2489 }
2490
2491 /* aliencargo */
2492 snode = cgi->XML_GetNode(p, SAVE_AIRCRAFT_ALIENCARGO);
2493 if (snode) {
2494 craft->alienCargo = new AlienCargo();
2495 if (craft->alienCargo == nullptr)
2496 cgi->Com_Error(ERR_DROP, "AIR_LoadAircraftXML: Cannot create AlienCargo object\n");
2497 craft->alienCargo->load(snode);
2498 }
2499
2500 return true;
2501}
2502
2508{
2509 int i;
2510
2511 assert(aircraft);
2512
2513 for (i = 0; i < aircraft->maxWeapons; i++) {
2514 aircraft->weapons[i].aircraft = aircraft;
2515 aircraft->weapons[i].base = nullptr;
2516 aircraft->weapons[i].installation = nullptr;
2517 }
2518 for (i = 0; i < aircraft->maxElectronics; i++) {
2519 aircraft->electronics[i].aircraft = aircraft;
2520 aircraft->electronics[i].base = nullptr;
2521 aircraft->electronics[i].installation = nullptr;
2522 }
2523 aircraft->shield.aircraft = aircraft;
2524 aircraft->shield.base = nullptr;
2525 aircraft->shield.installation = nullptr;
2526}
2527
2529{
2530 xmlNode_t* snode, *ssnode;
2531 xmlNode_t* projectiles;
2532 int i;
2533
2534 /* load phalanx aircraft */
2535 snode = cgi->XML_GetNode(parent, SAVE_AIRCRAFT_PHALANX);
2536 for (ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_AIRCRAFT); ssnode;
2537 ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRCRAFT)) {
2538 aircraft_t craft;
2539 if (!AIR_LoadAircraftXML(ssnode, &craft))
2540 return false;
2541 assert(craft.homebase);
2543 }
2544
2545 /* load the ufos on geoscape */
2546 snode = cgi->XML_GetNode(parent, SAVE_AIRCRAFT_UFOS);
2547
2548 for (i = 0, ssnode = cgi->XML_GetNode(snode, SAVE_AIRCRAFT_AIRCRAFT); i < MAX_UFOONGEOSCAPE && ssnode;
2549 ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRCRAFT), i++) {
2550 if (!AIR_LoadAircraftXML(ssnode, UFO_GetByIDX(i)))
2551 return false;
2552 ccs.numUFOs++;
2553 }
2554
2555 /* Load projectiles. */
2556 projectiles = cgi->XML_GetNode(parent, SAVE_AIRCRAFT_PROJECTILES);
2557 if (!AIRFIGHT_LoadXML(projectiles))
2558 return false;
2559
2560 /* check UFOs - backwards */
2561 for (i = ccs.numUFOs - 1; i >= 0; i--) {
2562 aircraft_t* ufo = UFO_GetByIDX(i);
2563 if (ufo->time < 0 || ufo->stats[AIR_STATS_SPEED] <= 0) {
2564 cgi->Com_Printf("AIR_Load: Found invalid ufo entry - remove it - time: %i - speed: %i\n",
2565 ufo->time, ufo->stats[AIR_STATS_SPEED]);
2567 }
2568 }
2569
2570 return true;
2571}
2572
2577{
2578 bool success = true;
2579 aircraft_t* prevUfo;
2580 aircraft_t* ufo;
2581
2582 /* PHALANX aircraft */
2583 AIR_Foreach(aircraft) {
2584 if (Q_strnull(aircraft->missionID)) {
2585 /* Save compatibility (03/Jul/2015) */
2586 if (aircraft->status == AIR_DROP)
2587 aircraft->status = AIR_IDLE;
2588 continue;
2589 }
2590 aircraft->mission = CP_GetMissionByID(aircraft->missionID);
2591 if (!aircraft->mission) {
2592 cgi->Com_Printf("Aircraft %s (idx: %i) is linked to an invalid mission: %s\n", aircraft->name, aircraft->idx, aircraft->missionID);
2593 if (aircraft->status == AIR_MISSION)
2594 AIR_AircraftReturnToBase(aircraft);
2595 }
2596 cgi->Free(aircraft->missionID);
2597 aircraft->missionID = nullptr;
2598 }
2599
2600 /* UFOs */
2605 prevUfo = nullptr;
2606 while ((ufo = UFO_GetNext(prevUfo)) != nullptr) {
2607 if (Q_strnull(ufo->missionID)) {
2608 cgi->Com_Printf("Warning: %s (idx: %i) has no mission assigned, removing it\n", ufo->name, ufo->idx);
2610 continue;
2611 }
2612 ufo->mission = CP_GetMissionByID(ufo->missionID);
2613 if (!ufo->mission) {
2614 cgi->Com_Printf("Warning: %s (idx: %i) is linked to an invalid mission %s, removing it\n", ufo->name, ufo->idx, ufo->missionID);
2616 continue;
2617 }
2618 ufo->mission->ufo = ufo;
2619 cgi->Free(ufo->missionID);
2620 ufo->missionID = nullptr;
2621 prevUfo = ufo;
2622 }
2623
2624 return success;
2625}
2626
2632{
2633 return AIR_PostLoadInitMissions();
2634}
2635
2641bool AIR_AircraftAllowed (const base_t* base)
2642{
2644}
2645
2650bool AIR_CanIntercept (const aircraft_t* aircraft)
2651{
2652 if (aircraft->status == AIR_NONE || aircraft->status == AIR_CRASHED)
2653 return false;
2654
2655 /* if dependencies of hangar are missing, you can't send aircraft */
2656 const building_t* hangar = B_GetBuildingTemplateSilent(aircraft->building);
2657 if (!B_GetBuildingStatus(aircraft->homebase, hangar->buildingType))
2658 return false;
2659
2660 /* we need a pilot to intercept */
2661 if (AIR_GetPilot(aircraft) == nullptr)
2662 return false;
2663
2664 return true;
2665}
2666
2672{
2673 int i, j, k, error = 0;
2674 aircraft_t* a;
2675
2676 for (i = 0, a = ccs.aircraftTemplates; i < ccs.numAircraftTemplates; i++, a++) {
2677 if (a->name[0] == '\0') {
2678 error++;
2679 cgi->Com_Printf("...... aircraft '%s' has no name\n", a->id);
2680 }
2681 if (!a->defaultName) {
2682 error++;
2683 cgi->Com_Printf("...... aircraft '%s' has no defaultName\n", a->id);
2684 }
2685
2686 /* check that every weapons fits slot */
2687 for (j = 0; j < a->maxWeapons - 1; j++)
2688 if (a->weapons[j].item && AII_GetItemWeightBySize(a->weapons[j].item) > a->weapons[j].size) {
2689 error++;
2690 cgi->Com_Printf("...... aircraft '%s' has an item (%s) too heavy for its slot\n", a->id, a->weapons[j].item->id);
2691 }
2692
2693 /* check that every slots has a different location for PHALANX aircraft (not needed for UFOs) */
2694 if (!AIR_IsUFO(a)) {
2695 for (j = 0; j < a->maxWeapons - 1; j++) {
2696 const itemPos_t var = a->weapons[j].pos;
2697 for (k = j + 1; k < a->maxWeapons; k++)
2698 if (var == a->weapons[k].pos) {
2699 error++;
2700 cgi->Com_Printf("...... aircraft '%s' has 2 weapons slots at the same location\n", a->id);
2701 }
2702 }
2703 for (j = 0; j < a->maxElectronics - 1; j++) {
2704 const itemPos_t var = a->electronics[j].pos;
2705 for (k = j + 1; k < a->maxElectronics; k++)
2706 if (var == a->electronics[k].pos) {
2707 error++;
2708 cgi->Com_Printf("...... aircraft '%s' has 2 electronics slots at the same location\n", a->id);
2709 }
2710 }
2711 }
2712 }
2713
2714 return !error;
2715}
2716
2724bool AIR_RemoveEmployee (Employee* employee, aircraft_t* aircraft)
2725{
2726 if (!employee)
2727 return false;
2728
2729 /* If no aircraft is given we search if he is in _any_ aircraft and set
2730 * the aircraft pointer to it. */
2731 if (!aircraft) {
2732 AIR_Foreach(acTemp) {
2733 if (AIR_IsEmployeeInAircraft(employee, acTemp)) {
2734 aircraft = acTemp;
2735 break;
2736 }
2737 }
2738 if (!aircraft)
2739 return false;
2740 }
2741
2742 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_RemoveEmployee: base: %i - aircraft->idx: %i\n",
2743 aircraft->homebase ? aircraft->homebase->idx : -1, aircraft->idx);
2744
2745 if (AIR_GetPilot(aircraft) == employee) {
2746#ifdef DEBUG
2747 if (employee->getType() != EMPL_PILOT)
2748 cgi->Com_Printf("Warning: pilot of aircraft %i is not a qualified pilot (ucn: %i)\n", aircraft->idx, employee->chr.ucn);
2749#endif
2750 return AIR_SetPilot(aircraft, nullptr);
2751 }
2752
2753 return cgi->LIST_Remove(&aircraft->acTeam, employee);
2754}
2755
2763const aircraft_t* AIR_IsEmployeeInAircraft (const Employee* employee, const aircraft_t* aircraft)
2764{
2765 if (!employee)
2766 return nullptr;
2767
2768 if (employee->transfer)
2769 return nullptr;
2770
2771 /* If no aircraft is given we search if he is in _any_ aircraft and return true if that's the case. */
2772 if (!aircraft) {
2773 AIR_Foreach(anyAircraft) {
2774 if (AIR_IsEmployeeInAircraft(employee, anyAircraft))
2775 return anyAircraft;
2776 }
2777 return nullptr;
2778 }
2779
2780 if (employee->isPilot()) {
2781 if (AIR_GetPilot(aircraft) == employee)
2782 return aircraft;
2783 return nullptr;
2784 }
2785
2786 if (AIR_IsInAircraftTeam(aircraft, employee))
2787 return aircraft;
2788
2789 return nullptr;
2790}
2791
2798{
2799 LIST_Foreach(aircraft.acTeam, Employee, employee) {
2800 /* use global aircraft index here */
2801 AIR_RemoveEmployee(employee, &aircraft);
2802 }
2803
2804 /* Remove pilot */
2805 AIR_SetPilot(&aircraft, nullptr);
2806
2807 if (AIR_GetTeamSize(&aircraft) > 0)
2808 cgi->Com_Error(ERR_DROP, "AIR_RemoveEmployees: Error, there went something wrong with soldier-removing from aircraft.");
2809}
2810
2811
2818{
2819 if (AIR_GetTeamSize(&aircraft) == 0) {
2820 cgi->Com_DPrintf(DEBUG_CLIENT, "AIR_MoveEmployeeInventoryIntoStorage: No team to remove equipment from.\n");
2821 return;
2822 }
2823
2824 LIST_Foreach(aircraft.acTeam, Employee, employee) {
2825 const Container* cont = nullptr;
2826 while ((cont = employee->chr.inv.getNextCont(cont, true))) {
2827 Item* ic = cont->getNextItem(nullptr);
2828 while (ic) {
2829 const Item item = *ic;
2830 const objDef_t* type = item.def();
2831 Item* next = ic->getNext();
2832
2833 ed.numItems[type->idx]++;
2834 if (item.getAmmoLeft() && type->isReloadable()) {
2835 assert(item.ammoDef());
2836 /* Accumulate loose ammo into clips */
2837 ed.addClip(&item); /* does not delete the item */
2838 }
2839 ic = next;
2840 }
2841 }
2842 }
2843}
2844
2853bool AIR_AddEmployee (Employee* employee, aircraft_t* aircraft)
2854{
2855 if (!employee || !aircraft)
2856 return false;
2857
2858 if (AIR_GetTeamSize(aircraft) < aircraft->maxTeamSize) {
2859 /* Check whether the soldier is already on another aircraft */
2860 if (AIR_IsEmployeeInAircraft(employee, nullptr))
2861 return false;
2862
2863 /* Assign the soldier to the aircraft. */
2864 return AIR_AddToAircraftTeam(aircraft, employee);
2865 }
2866 return false;
2867}
2868
2874{
2875 int count;
2876 base_t* base;
2877
2878 if (!aircraft) {
2879 cgi->Com_Printf("AIR_AssignInitial: No aircraft given\n");
2880 return;
2881 }
2882
2883 base = aircraft->homebase;
2884 assert(base);
2885
2886 count = 0;
2887 E_Foreach(EMPL_SOLDIER, employee) {
2888 if (count >= aircraft->maxTeamSize)
2889 break;
2890 if (employee->baseHired != base)
2891 continue;
2892 if (AIR_AddEmployee(employee, aircraft))
2893 count++;
2894 }
2895}
2896
2898#ifdef DEBUG
2899 {"debug_listaircraftsample", AIR_ListAircraftSamples_f, "Show aircraft parameter on game console"},
2900 {"debug_listaircraft", AIR_ListAircraft_f, "Debug function to list all aircraft in all bases"},
2901 {"debug_listaircraftidx", AIR_ListCraftIndexes_f, "Debug function to list local/global aircraft indexes"},
2902#endif
2903 {nullptr, nullptr, nullptr}
2904};
2905
2910{
2912 cgi->Cmd_TableAddList(aircraftDebugCmds);
2913}
2914
2918void AIR_Shutdown (void)
2919{
2920 AIR_Foreach(craft) {
2921 AIR_ResetAircraftTeam(craft);
2922 if (craft->alienCargo != nullptr) {
2923 delete craft->alienCargo;
2924 craft->alienCargo = nullptr;
2925 }
2926 if (craft->itemCargo != nullptr) {
2927 delete craft->itemCargo;
2928 craft->itemCargo = nullptr;
2929 }
2930 }
2931 cgi->LIST_Delete(&ccs.aircraft);
2932
2934 cgi->Cmd_TableRemoveList(aircraftDebugCmds);
2935}
DateTime class definition.
Alien cargo class header.
@ SKILL_PILOTING
Definition chr_shared.h:48
CGAME_HARD_LINKED_FUNCTIONS linkedList_t * LIST_Add(linkedList_t **listDest, void const *data, size_t length)
Share stuff between the different cgame implementations.
#define _(String)
Definition cl_shared.h:44
Alien cargo class.
Definition aliencargo.h:41
bool load(xmlNode_t *root)
Load alien cargo from xml savegame.
bool save(xmlNode_t *root) const
Save alien cargo to xml savegame.
Item * _invList
Definition inv_shared.h:516
Item * getNextItem(const Item *prev) const
Class describing a point of time.
Definition DateTime.h:31
int getTimeAsSeconds() const
Return the time part of the DateTime as seconds.
Definition DateTime.cpp:54
static const short SECONDS_PER_HOUR
Definition DateTime.h:42
int getDateAsDays() const
Return the date part of the DateTime as days.
Definition DateTime.cpp:46
character_t chr
employeeType_t getType() const
Definition cp_employee.h:99
bool transfer
bool isPilot() const
Definition cp_employee.h:66
inventory definition with all its containers
Definition inv_shared.h:525
const Container * getNextCont(const Container *prev, bool inclTemp=false) const
Item cargo class.
Definition itemcargo.h:41
void empty(void)
Empties the cargo.
Definition itemcargo.cpp:99
bool load(xmlNode_t *root)
Load item cargo from xml savegame.
bool save(xmlNode_t *root) const
Save item cargo to xml savegame.
linkedList_t * list(void) const
Returns a copy of the cargo list.
virtual bool add(const objDef_t *od, int amount, int looseAmount)
Add items to the cargo.
Definition itemcargo.cpp:39
item instance data, with linked list capability
Definition inv_shared.h:402
const objDef_t * ammoDef(void) const
Definition inv_shared.h:460
int getAmmoLeft() const
Definition inv_shared.h:466
const objDef_t * def(void) const
Definition inv_shared.h:469
Item * getNext() const
Definition inv_shared.h:451
#define ERR_DROP
Definition common.h:211
#define ERR_FATAL
Definition common.h:210
void AIR_AssignInitial(aircraft_t *aircraft)
Assigns initial team of soldiers to aircraft.
void AIR_AircraftReturnToBase(aircraft_t *aircraft)
Calculates the way back to homebase for given aircraft and returns it.
bool AIR_CanIntercept(const aircraft_t *aircraft)
void AIR_RemovePilotFromAssignedAircraft(const base_t *base, const Employee *pilot)
Checks to see if the pilot is in any aircraft at this base. If he is then he is removed from that air...
Employee * AIR_GetPilot(const aircraft_t *aircraft)
Get pilot of an aircraft.
void AIR_DeleteAircraft(aircraft_t *aircraft)
Removes an aircraft from its base and the game.
static void AIR_SaveAircraftSlotsXML(const aircraftSlot_t *slot, const int num, xmlNode_t *p, bool weapon)
Saves an item slot.
const char * AIR_AircraftStatusToName(const aircraft_t *aircraft)
Translates the aircraft status id to a translatable string.
void AIR_CampaignRun(const campaign_t *campaign, int dt, bool updateRadarOverlay)
Handles aircraft movement and actions in geoscape mode.
void AIR_ParseAircraft(const char *name, const char **text, bool assignAircraftItems)
Parses all aircraft that are defined in our UFO-scripts.
bool AIR_AircraftHasEnoughFuel(const aircraft_t *aircraft, const vec2_t destination)
check if aircraft has enough fuel to go to destination, and then come back home
bool AIR_SendAircraftToMission(aircraft_t *aircraft, mission_t *mission)
Sends the specified aircraft to specified mission.
static void AII_CarriedItems(const Inventory *soldierInventory)
Process items carried by soldiers.
static void AII_SetAircraftInSlots(aircraft_t *aircraft)
Initialise aircraft pointer in each slot of an aircraft.
int AIR_BaseCountAircraft(const base_t *base)
Returns the number of aircraft on the given base.
static const cmdList_t aircraftDebugCmds[]
void AII_CollectingItems(aircraft_t *aircraft, int won)
Collect items from the battlefield.
int AIR_GetOperationRange(const aircraft_t *aircraft)
Calculates the range an aircraft can fly on the geoscape.
int AIR_CountInBaseByTemplate(const base_t *base, const aircraft_t *aircraftTemplate)
Calculates the amount of aircraft (of the given type) in the selected base.
static void AIR_CorrectAircraftSlotPointers(aircraft_t *aircraft)
resets aircraftSlots' backreference pointers for aircraft
void AIR_AircraftsNotifyUFORemoved(const aircraft_t *const ufo, bool destroyed)
Notify that a UFO has been removed.
aircraft_t * AIR_NewAircraft(base_t *base, const aircraft_t *aircraftTemplate)
Places a new aircraft in the given base.
bool AIR_SetPilot(aircraft_t *aircraft, Employee *pilot)
Assign a pilot to an aircraft.
int AIR_GetAircraftWeaponRanges(const aircraftSlot_t *slot, int maxSlot, float *weaponRanges)
Get the all the unique weapon ranges of this aircraft.
int AIR_GetRemainingRange(const aircraft_t *aircraft)
Calculates the remaining range the aircraft can fly.
bool AIR_AddToAircraftTeam(aircraft_t *aircraft, Employee *employee)
Adds given employee to given aircraft.
bool AIR_AircraftHasEnoughFuelOneWay(const aircraft_t *aircraft, const vec2_t destination)
check if aircraft has enough fuel to go to destination
bool AIR_AircraftAllowed(const base_t *base)
Returns true if the current base is able to handle aircraft.
static void AIR_TransferItemsCarriedByCharacterToBase(character_t *chr, base_t *sourceBase, base_t *destBase)
Transfer items carried by a soldier from one base to another.
static bool AIR_SaveAircraftXML(xmlNode_t *p, const aircraft_t *const aircraft, bool const isUfo)
Saves an aircraft.
bool AIR_IsInAircraftTeam(const aircraft_t *aircraft, const Employee *employee)
Checks whether given employee is in given aircraft.
void AII_CollectItem(aircraft_t *aircraft, const objDef_t *item, int amount)
Add an item to aircraft inventory.
void AIR_AircraftsNotifyMissionRemoved(const mission_t *const mission)
Notify aircraft that a mission has been removed.
int AIR_AircraftMenuStatsValues(const int value, const int stat)
Some of the aircraft values needs special calculations when they are shown in the menus.
void AIR_MoveAircraftIntoNewHomebase(aircraft_t *aircraft, base_t *base)
Moves a given aircraft to a new base (also the employees and inventory).
bool AIR_PilotSurvivedCrash(const aircraft_t *aircraft)
Determine if an aircraft's pilot survived a crash, based on his piloting skill (and a bit of randomne...
static void AIR_LoadAircraftSlotsXML(aircraft_t *aircraft, aircraftSlot_t *slot, xmlNode_t *p, bool weapon, const int max)
Loads the weapon slots of an aircraft.
static float AIR_GetDestinationFunction(const float c, const float B, const float speedRatio, float a)
funtion we need to find roots.
bool AIR_IsAircraftOnGeoscape(const aircraft_t *aircraft)
Checks whether given aircraft is on geoscape.
bool AIR_SendAircraftPursuingUFO(aircraft_t *aircraft, aircraft_t *ufo)
Make the specified aircraft purchasing a UFO.
bool AIR_RemoveEmployee(Employee *employee, aircraft_t *aircraft)
Removes a soldier from an aircraft.
void AIR_ResetAircraftTeam(aircraft_t *aircraft)
Resets team in given aircraft.
static bool AIR_PostLoadInitMissions(void)
Set the mission pointers for all the aircraft after loading a savegame.
int AIR_GetTeamSize(const aircraft_t *aircraft)
Counts the number of soldiers in given aircraft.
static const value_t aircraft_param_vals[]
Valid aircraft parameter definitions from script files.
bool AIR_ScriptSanityCheck(void)
Checks the parsed aircraft for errors.
void AIR_AutoAddPilotToAircraft(const base_t *base, Employee *pilot)
Adds the pilot to the first available aircraft at the specified base.
void AIR_MoveEmployeeInventoryIntoStorage(const aircraft_t &aircraft, equipDef_t &ed)
Move all the equipment carried by the team on the aircraft into the given equipment.
static void AIR_Refuel(aircraft_t *aircraft, int deltaTime)
const char *const air_slot_type_strings[]
static float AIR_GetDestinationFindRoot(const float c, const float B, const float speedRatio, float start)
Find the roots of a function.
static bool AIR_LoadAircraftXML(xmlNode_t *p, aircraft_t *craft)
Loads an Aircraft from the savegame.
bool AIR_LoadXML(xmlNode_t *parent)
static void AIR_Move(aircraft_t *aircraft, int deltaTime)
static void AII_CollectItem_(void *data, const objDef_t *item, int amount)
bool AIR_SaveXML(xmlNode_t *parent)
Save callback for savegames in xml format.
bool AIR_AddEmployee(Employee *employee, aircraft_t *aircraft)
Assigns a soldier to an aircraft.
void AIR_AircraftsUFODisappear(const aircraft_t *const ufo)
Notify that a UFO disappear from radars.
static const value_t aircraft_vals[]
Valid aircraft definition values from script files.
const char * AIR_CheckMoveIntoNewHomebase(const aircraft_t *aircraft, const base_t *base)
Checks if destination base can store an aircraft and its team.
bool AIR_PostLoadInit(void)
Actions needs to be done after loading the savegame.
const aircraft_t * AIR_GetAircraft(const char *name)
Searches the global array of aircraft types for a given aircraft.
void AIR_GetDestinationWhilePursuing(const aircraft_t *shooter, const aircraft_t *target, vec2_t dest)
Calculates the point where aircraft should go to intecept a moving target.
bool AIR_BaseHasAircraft(const base_t *base)
Checks whether there is any aircraft assigned to the given base.
static int AIR_GetStorageRoom(const aircraft_t *aircraft)
Calculate used storage room corresponding to items in an aircraft.
static char const *const air_position_strings[]
List of valid strings for itemPos_t.
void AIR_DestroyAircraft(aircraft_t *aircraft, bool killPilot)
Removes an aircraft from its base and the game.
void AIR_InitStartup(void)
Init actions for aircraft-subsystem.
static const value_t aircraft_radar_vals[]
Valid radar definition values for an aircraft from script files.
static float AIR_GetDestinationDerivativeFunction(const float c, const float B, const float speedRatio, float a)
derivative of the funtion we need to find roots.
aircraft_t * AIR_GetAircraftFromBaseByIDXSafe(const base_t *base, int index)
static void AII_CollectAmmo(void *data, const Item *magazine)
Count and collect ammo from gun magazine.
static void AIR_SaveRouteXML(xmlNode_t *node, const mapline_t *route)
Saves an route plan of an aircraft.
bool AIR_IsAircraftInBase(const aircraft_t *aircraft)
Checks whether given aircraft is in its homebase.
const aircraft_t * AIR_GetAircraftSilent(const char *name)
Searches the global array of aircraft types for a given aircraft.
bool AIR_Delete(base_t *base, aircraft_t *aircraft)
Will remove the given aircraft from the base.
baseCapacities_t AIR_GetHangarCapacityType(const aircraft_t *aircraft)
Returns capacity type needed for an aircraft.
static void AII_InitialiseAircraftSlots(aircraft_t *aircraftTemplate)
Initialise all values of an aircraft slot.
aircraft_t * AIR_GetFirstFromBase(const base_t *b)
Iterates through the aircraft of a base.
static bool AIR_LoadRouteXML(xmlNode_t *p, mapline_t *route)
Loads the route of an aircraft.
const aircraft_t * AIR_IsEmployeeInAircraft(const Employee *employee, const aircraft_t *aircraft)
Tells you if an employee is assigned to an aircraft.
void AIR_Shutdown(void)
Closing actions for aircraft-subsystem.
bool AIR_AircraftMakeMove(int dt, aircraft_t *aircraft)
Moves given aircraft.
aircraft_t * AIR_AircraftGetFromIDX(int aircraftIdx)
Returns aircraft for a given global index.
void AIR_RemoveEmployees(aircraft_t &aircraft)
Removes all soldiers from an aircraft.
aircraft_t * AIR_Add(base_t *base, const aircraft_t *aircraftTemplate)
Adds a new aircraft from a given aircraft template to the base and sets the homebase for the new airc...
@ ITEM_HEAVY
Definition cp_aircraft.h:51
@ ITEM_MEDIUM
Definition cp_aircraft.h:50
@ ITEM_LIGHT
Definition cp_aircraft.h:49
#define LINE_MAXPTS
Definition cp_aircraft.h:33
#define AIR_Foreach(var)
iterates trough all aircraft
#define AIRCRAFT_REFUEL_FACTOR
Definition cp_aircraft.h:36
#define AIR_ForeachFromBase(var, base)
iterates trough all aircraft from a specific homebase
#define MAX_AIRCRAFTSLOT
Definition cp_aircraft.h:75
aircraftStatus_t
Definition cp_aircraft.h:99
@ AIR_MISSION
@ AIR_HOME
@ AIR_RETURNING
@ AIR_DROP
@ AIR_REFUEL
@ AIR_UFO
@ AIR_IDLE
@ AIR_NONE
@ AIR_INTERCEPT
@ AIR_TRANSIT
@ AIR_CRASHED
@ AIR_TRANSFER
#define MAX_AIRCRAFT
Definition cp_aircraft.h:31
itemPos_t
different positions for aircraft items
Definition cp_aircraft.h:55
@ AIR_POSITIONS_MAX
Definition cp_aircraft.h:65
@ AIR_CANNOT_REFUEL
Definition cp_aircraft.h:70
#define AIR_IsUFO(aircraft)
void AIR_ShutdownCallbacks(void)
void AIR_InitCallbacks(void)
Header file for menu related console command callbacks.
void AIRFIGHT_ExecuteActions(const campaign_t *campaign, aircraft_t *shooter, aircraft_t *target)
Decide what an attacking aircraft can do.
bool AIRFIGHT_SaveXML(xmlNode_t *parent)
Save callback for savegames in XML Format.
bool AIRFIGHT_LoadXML(xmlNode_t *parent)
Load callback for savegames in XML Format.
baseCapacities_t B_GetCapacityFromBuildingType(buildingType_t type)
Get the capacity associated to a building type.
Definition cp_base.cpp:416
base_t * B_GetFoundedBaseByIDX(int baseIdx)
Array bound check for the base index.
Definition cp_base.cpp:326
base_t * B_GetNext(base_t *lastBase)
Iterates through founded bases.
Definition cp_base.cpp:286
void B_AircraftReturnedToHomeBase(aircraft_t *aircraft)
Do anything when dropship returns to base.
Definition cp_base.cpp:2102
int B_AddAntimatter(base_t *base, int amount)
Manages antimatter (adding, removing) through Antimatter Storage Facility.
Definition cp_base.cpp:2635
base_t * B_GetBaseByIDX(int baseIdx)
Array bound check for the base index. Will also return unfounded bases as long as the index is in the...
Definition cp_base.cpp:313
buildingType_t B_GetBuildingTypeByCapacity(baseCapacities_t cap)
Get building type by base capacity.
Definition cp_base.cpp:447
int B_ItemInBase(const objDef_t *item, const base_t *base)
Check if the item has been collected (i.e it is in the storage) in the given base.
Definition cp_base.cpp:2133
int B_AddToStorage(base_t *base, const objDef_t *obj, int amount)
Add/remove items to/from the storage.
Definition cp_base.cpp:2576
bool B_GetBuildingStatus(const base_t *const base, const buildingType_t buildingType)
Get the status associated to a building.
Definition cp_base.cpp:478
#define B_IsUnderAttack(base)
Definition cp_base.h:53
#define MAX_BASES
Definition cp_base.h:32
building_t * B_GetBuildingTemplateSilent(const char *buildingName)
Returns the building in the global building-types list that has the unique name buildingID.
Header for base building related stuff.
@ B_HANGAR
Definition cp_building.h:58
@ B_SMALL_HANGAR
Definition cp_building.h:60
memPool_t * cp_campaignPool
ccs_t ccs
Header file for single player campaign control.
const cgame_import_t * cgi
@ MAPTYPE_TERRAIN
Definition cp_campaign.h:93
void CAP_AddCurrent(base_t *base, baseCapacities_t capacity, int value)
Changes the current (used) capacity on a base.
int CAP_GetFreeCapacity(const base_t *base, baseCapacities_t capacityType)
Returns the free capacity of a type.
baseCapacities_t
All possible capacities in base.
Definition cp_capacity.h:27
@ CAP_ITEMS
Definition cp_capacity.h:32
@ MAX_CAP
Definition cp_capacity.h:37
@ CAP_EMPLOYEES
Definition cp_capacity.h:31
bool E_DeleteEmployee(Employee *employee)
Removes the employee completely from the game (buildings + global list).
Employee * E_GetEmployeeFromChrUCN(int uniqueCharacterNumber)
Searches all employee for the ucn (character id).
bool E_MoveIntoNewBase(Employee *employee, base_t *newBase)
Employee * E_GetEmployeeByTypeFromChrUCN(employeeType_t type, int uniqueCharacterNumber)
Searches employee from a type for the ucn (character id).
void E_RemoveInventoryFromStorage(Employee *employee)
Removes the items of an employee (soldier) from the base storage (s)he is hired at.
@ EMPL_SOLDIER
Definition cp_employee.h:31
@ EMPL_PILOT
Definition cp_employee.h:34
#define E_Foreach(employeeType, var)
mission_t * GEO_SelectMission(mission_t *mission)
Select the specified mission.
const byte * GEO_GetColor(const vec2_t pos, mapType_t type, bool *coast)
Returns the color value from geoscape of a certain mask (terrain, culture or population) at a given p...
void GEO_NotifyAircraftRemoved(const aircraft_t *aircraft)
Notify that an aircraft has been removed from game.
void GEO_SelectAircraft(aircraft_t *aircraft)
Select the specified aircraft on the geoscape.
bool GEO_IsRadarOverlayActivated(void)
void GEO_CalcLine(const vec2_t start, const vec2_t end, mapline_t *line)
Calculate the shortest way to go from start to end on a sphere.
void GEO_CheckPositionBoundaries(float *pos)
Check that a position (in latitude / longitude) is within boundaries.
Header for Geoscape management.
#define GEO_SetInterceptorAircraft(interceptor)
Definition cp_geoscape.h:63
#define KILOMETER_PER_DEGREE
Definition cp_geoscape.h:28
#define GEO_SetMissionAircraft(aircraft)
Definition cp_geoscape.h:66
bool AII_ReloadWeapon(aircraftSlot_t *slot)
Reloads an aircraft/defence-system weapon.
void AII_LoadOneSlotXML(xmlNode_t *node, aircraftSlot_t *slot, bool weapon)
Loads one slot (base, installation or aircraft).
itemWeight_t AII_GetItemWeightBySize(const objDef_t *od)
Returns craftitem weight based on size.
void AII_RemoveItemFromSlot(base_t *base, aircraftSlot_t *slot, bool ammo)
Remove the item from the slot (or optionally its ammo only) and put it the base storage.
void AII_ReloadAircraftWeapons(aircraft_t *aircraft)
Reload the weapons of an aircraft.
void AII_UpdateAircraftStats(aircraft_t *aircraft)
Update the value of stats array of an aircraft.
void AII_InitialiseSlot(aircraftSlot_t *slot, aircraft_t *aircraftTemplate, base_t *base, installation_t *installation, aircraftItemType_t type)
Initialise values of one slot of an aircraft or basedefence common to all types of items.
void AII_SaveOneSlotXML(xmlNode_t *p, const aircraftSlot_t *slot, bool weapon)
Save callback for savegames in XML Format.
Header for slot management related stuff.
uiMessageListNodeMessage_t * MSO_CheckAddNewMessage(const notify_t messagecategory, const char *title, const char *text, messageType_t type, technology_t *pedia, bool popup)
Adds a new message to message stack. It uses message settings to verify whether sound should be playe...
@ NT_AIRCRAFT_CANNOTREFUEL
@ NT_AIRCRAFT_ARRIVEDHOME
@ NT_AIRCRAFT_REFUELED
uiMessageListNodeMessage_t * MS_AddNewMessage(const char *title, const char *text, messageType_t type, technology_t *pedia, bool popup, bool playSound)
Adds a new message to message stack.
char cp_messageBuffer[MAX_MESSAGE_TEXT]
mission_t * CP_GetMissionByID(const char *missionId)
Get a mission in ccs.missions by Id.
const char * MIS_GetName(const mission_t *mission)
Returns a short translated name for a mission.
Campaign missions headers.
void CP_Popup(const char *title, const char *text,...)
Wrapper around UI_Popup.
Definition cp_popup.cpp:474
void RADAR_Initialise(radar_t *radar, float range, float trackingRange, float level, bool updateSourceRadarMap)
Set radar range to new value.
Definition cp_radar.cpp:239
void RADAR_UpdateWholeRadarOverlay(void)
Update radar overlay of base, installation and aircraft range.
Definition cp_radar.cpp:89
void RADAR_InitialiseUFOs(radar_t *radar)
Reset UFO sensored on radar.
Definition cp_radar.cpp:265
#define MAX_UFOONGEOSCAPE
Definition cp_radar.h:27
void RS_MarkCollected(technology_t *tech)
Marks a give technology as collected.
technology_t * RS_GetTechForItem(const objDef_t *item)
Returns technology entry for an item.
technology_t * RS_GetTechByID(const char *id)
return a pointer to the technology identified by given id string
#define ANTIMATTER_ITEM_ID
Definition cp_research.h:37
void CP_GameTimeStop(void)
Stop game time speed.
Definition cp_time.cpp:126
Campaign geoscape time header.
void TR_NotifyAircraftRemoved(const aircraft_t *aircraft)
Notify that an aircraft has been removed.
aircraft_t * UFO_GetByIDX(const int idx)
returns the UFO on the geoscape with a certain index
Definition cp_ufo.cpp:85
aircraft_t * UFO_GetNext(aircraft_t *lastUFO)
Iterates through the UFOs.
Definition cp_ufo.cpp:41
void UFO_RemoveFromGeoscape(aircraft_t *ufo)
Remove the specified ufo from geoscape.
Definition cp_ufo.cpp:817
#define UFO_GetGeoscapeIDX(ufo)
Definition cp_ufo.h:33
#define DEBUG_CLIENT
Definition defines.h:59
void Sys_Error(const char *error,...)
Definition g_main.cpp:421
const objDef_t * INVSH_GetItemByID(const char *id)
Returns the item that belongs to the given id or nullptr if it wasn't found.
@ AIR_STATS_MAX
Definition inv_shared.h:237
@ AIR_STATS_ECM
Definition inv_shared.h:229
@ AIR_STATS_ANTIMATTER
Definition inv_shared.h:235
@ AIR_STATS_FUELSIZE
Definition inv_shared.h:232
@ AIR_STATS_WRANGE
Definition inv_shared.h:233
@ AIR_STATS_ACCURACY
Definition inv_shared.h:231
@ AIR_STATS_SPEED
Definition inv_shared.h:226
@ AIR_STATS_DAMAGE
Definition inv_shared.h:230
@ AIR_STATS_SHIELD
Definition inv_shared.h:228
@ AIR_STATS_MAXSPEED
Definition inv_shared.h:227
aircraftItemType_t
All different types of craft items.
Definition inv_shared.h:197
@ AC_ITEM_WEAPON
Definition inv_shared.h:201
@ MAX_ACITEMS
Definition inv_shared.h:215
@ AC_ITEM_ELECTRONICS
Definition inv_shared.h:205
@ AC_ITEM_SHIELD
Definition inv_shared.h:204
voidpf void uLong size
Definition ioapi.h:42
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
Item cargo class header.
#define LIST_Foreach(list, type, var)
Iterates over a linked list, it's safe to delete the returned entry from the list while looping over ...
Definition list.h:41
vec_t VectorNormalize(vec3_t v)
Calculate unit vector for a given vec3_t.
Definition mathlib.cpp:745
float crand(void)
Return random values between -1 and 1.
Definition mathlib.cpp:517
void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees)
Rotate a point around a given vector.
Definition mathlib.cpp:849
void PolarToVec(const vec2_t a, vec3_t v)
Converts longitude and latitude to a 3D vector in Euclidean coordinates.
Definition mathlib.cpp:910
double GetDistanceOnGlobe(const vec2_t pos1, const vec2_t pos2)
Calculate distance on the geoscape.
Definition mathlib.cpp:171
void VecToPolar(const vec3_t v, vec2_t a)
Converts vector coordinates into polar coordinates.
Definition mathlib.cpp:922
void CrossProduct(const vec3_t v1, const vec3_t v2, vec3_t cross)
binary operation on vectors in a three-dimensional space
Definition mathlib.cpp:820
#define torad
Definition mathlib.h:50
#define todeg
Definition mathlib.h:51
#define M_PI
Definition mathlib.h:34
const char * Com_Parse(const char *data_p[], char *target, size_t size, bool replaceWhitespaces)
Parse a token out of a string.
Definition parse.cpp:107
void Com_SkipBlock(const char **text)
Skips a block of {} in our script files.
Definition parse.cpp:253
Shared parsing functions.
QGL_EXTERN GLenum GLuint * dest
Definition r_gl.h:101
QGL_EXTERN GLuint count
Definition r_gl.h:99
QGL_EXTERN GLsizei const GLvoid * data
Definition r_gl.h:89
QGL_EXTERN GLuint index
Definition r_gl.h:110
QGL_EXTERN GLint i
Definition r_gl.h:113
QGL_EXTERN GLint GLenum type
Definition r_gl.h:94
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition r_gl.h:110
XML tag constants for savegame.
#define SAVE_AIRCRAFT_DAMAGE
#define SAVE_AIRCRAFT_HOMEBASE
#define SAVE_AIRCRAFT_VAL
#define SAVE_AIRCRAFT_UFOS
#define SAVE_AIRCRAFT_PILOTUCN
#define SAVE_AIRCRAFT_AIRCRAFTTEAM
#define SAVE_AIRCRAFTSTATUS_NAMESPACE
#define SAVE_AIRCRAFT_ROUTE
#define SAVE_AIRCRAFT_ID
#define SAVE_AIRCRAFT_ROUTE_DISTANCE
#define SAVE_AIRCRAFT_ALIENCARGO
#define SAVE_AIRCRAFT_FUEL
#define SAVE_AIRCRAFT_ELECTRONICS
#define SAVE_AIRCRAFT_PHALANX
#define SAVE_AIRCRAFT_AIRCRAFT
#define SAVE_AIRCRAFT_DETECTIONIDX
#define SAVE_AIRCRAFT_POS
#define SAVE_AIRCRAFT_LANDED
#define SAVE_AIRCRAFT_POINT
#define SAVE_AIRCRAFT_AIRSTATID
#define SAVE_AIRCRAFT_SHIELDS
#define SAVE_AIRCRAFT_DETECTED
#define SAVE_AIRCRAFT_MEMBER
#define SAVE_AIRCRAFT_TEAM_UCN
static const constListEntry_t saveAircraftConstants[]
#define SAVE_AIRCRAFT_ROUTE_POINT
#define SAVE_AIRCRAFT_WEAPONS
#define SAVE_AIRCRAFT_CARGO
#define SAVE_AIRCRAFT_AIRCRAFTTARGET
#define SAVE_AIRCRAFT_MISSIONID
#define SAVE_AIRCRAFT_AIRSTATS
#define SAVE_AIRCRAFT_NAME
#define SAVE_AIRCRAFT_AIRSTAT
#define SAVE_AIRCRAFT_LASTSPOTTED_DATE
#define SAVE_AIRCRAFT_SLOT
#define SAVE_AIRCRAFT_TIME
#define SAVE_AIRCRAFT_PROJECTILES
#define SAVE_AIRCRAFT_IDX
#define SAVE_AIRCRAFT_DIRECTION
#define SAVE_AIRCRAFT_STATUS
#define SAVE_AIRCRAFTSTAT_NAMESPACE
@ V_BOOL
Definition scripts.h:50
@ V_FLOAT
Definition scripts.h:54
@ V_TRANSLATION_STRING
Definition scripts.h:59
@ V_HUNK_STRING
Definition scripts.h:69
@ V_NULL
Definition scripts.h:49
@ V_LIST
Definition scripts.h:78
@ V_STRING
Definition scripts.h:58
@ V_INT
Definition scripts.h:52
#define MEMBER_SIZEOF(TYPE, MEMBER)
Definition scripts.h:34
#define UFO_NONE
Definition scripts.h:148
#define AIR_SLOT_TYPE_STRINGS
Definition scripts.h:154
#define Q_streq(a, b)
Definition shared.h:136
bool Q_strnull(const char *string)
Definition shared.h:138
#define OBJZERO(obj)
Definition shared.h:178
#define lengthof(x)
Definition shared.h:105
#define CASSERT(x)
Definition shared.h:107
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition shared.cpp:457
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition shared.cpp:494
int Q_FloatSort(const void *float1, const void *float2)
Compare two floats.
Definition shared.cpp:372
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functi...
Definition shared.cpp:410
An aircraft with all it's data.
aircraftSlot_t weapons[MAX_AIRCRAFTSLOT]
int maxElectronics
struct aircraft_s * aircraftTarget
class AlienCargo * alienCargo
struct base_s * homebase
aircraftStatus_t status
const char * building
vec3_t projectedPos
int productionCost
int stats[AIR_STATS_MAX]
aircraftSlot_t shield
char * missionID
struct mission_s * mission
class DateTime lastSpotted
mapline_t route
linkedList_t * acTeam
class Employee * pilot
class ItemCargo * itemCargo
void setUfoType(ufoType_t ufoT)
struct aircraft_s * tpl
char name[MAX_VAR]
struct radar_s radar
aircraftSlot_t electronics[MAX_AIRCRAFTSLOT]
bool notifySent[MAX_AIR_NOTIFICATIONS]
vec3_t direction
char * defaultName
slot of aircraft
Definition cp_aircraft.h:78
const objDef_t * item
Definition cp_aircraft.h:85
const objDef_t * ammo
Definition cp_aircraft.h:86
struct installation_s * installation
Definition cp_aircraft.h:81
struct aircraft_s * aircraft
Definition cp_aircraft.h:82
itemPos_t pos
Definition cp_aircraft.h:95
struct base_s * base
Definition cp_aircraft.h:80
itemWeight_t size
Definition cp_aircraft.h:87
alien cargo entry
Definition aliencargo.h:32
A base with all it's data.
Definition cp_base.h:84
int numLasers
Definition cp_base.h:120
aircraft_t * aircraftCurrent
Definition cp_base.h:100
baseWeapon_t batteries[MAX_BASE_SLOT]
Definition cp_base.h:116
baseWeapon_t lasers[MAX_BASE_SLOT]
Definition cp_base.h:119
int idx
Definition cp_base.h:85
char name[MAX_VAR]
Definition cp_base.h:86
vec3_t pos
Definition cp_base.h:91
int numBatteries
Definition cp_base.h:117
aircraft_t * target
Definition cp_base.h:79
A building with all it's data.
Definition cp_building.h:73
buildingType_t buildingType
Describes a character with all its attributes.
Definition chr_shared.h:388
chrScoreGlobal_t score
Definition chr_shared.h:406
char name[MAX_VAR]
Definition chr_shared.h:390
Inventory inv
Definition chr_shared.h:411
int skills[SKILL_NUM_TYPES]
Definition chr_shared.h:122
float stats[AIR_STATS_MAX]
Definition inv_shared.h:248
int numItems[MAX_OBJDEFS]
Definition inv_shared.h:608
void addClip(const Item *item)
Combine the rounds of partially used clips.
item cargo entry
Definition itemcargo.h:32
A path on the map described by 2D points.
Definition cp_aircraft.h:39
float distance
Definition cp_aircraft.h:41
vec2_t point[LINE_MAXPTS]
Definition cp_aircraft.h:43
int numPoints
Definition cp_aircraft.h:40
mission definition
Definition cp_missions.h:86
bool active
Definition cp_missions.h:90
vec2_t pos
Defines all attributes of objects used in the inventory.
Definition inv_shared.h:264
bool isVirtual
Definition inv_shared.h:284
bool isReloadable() const
Definition inv_shared.h:352
craftItem craftitem
Definition inv_shared.h:331
const char * id
Definition inv_shared.h:268
This is the technology parsed from research.ufo.
char * provides
size_t ofs
Definition scripts.h:170
const char * string
Definition scripts.h:168
valueTypes_t type
Definition scripts.h:169
vec_t vec3_t[3]
Definition ufotypes.h:39
vec_t vec2_t[2]
Definition ufotypes.h:38
static hudRadar_t radar
#define DotProduct(x, y)
Returns the distance between two 3-dimensional vectors.
Definition vector.h:44
#define VectorSet(v, x, y, z)
Definition vector.h:59
#define Vector2Copy(src, dest)
Definition vector.h:52
xmlNode_t * XML_GetNextNode(xmlNode_t *current, xmlNode_t *parent, const char *name)
Get next Node of the XML tree by name.
Definition xml.cpp:508
#define xmlNode_t
Definition xml.h:24