UFO: Alien Invasion
cp_aircraft.cpp
Go to the documentation of this file.
1 
10 /*
11 Copyright (C) 2002-2022 UFO: Alien Invasion.
12 
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License
15 as published by the Free Software Foundation; either version 2
16 of the License, or (at your option) any later version.
17 
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 
22 See the GNU General Public License for more details.
23 
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28 
29 #include "../../DateTime.h"
30 #include "../../cl_shared.h"
31 #include "../../ui/ui_dataids.h"
32 #include "../../../shared/parse.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"
40 #include "cp_aircraft_callbacks.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 
66 bool AIR_BaseHasAircraft (const base_t* base)
67 {
68  return base != nullptr && AIR_GetFirstFromBase(base) != nullptr;
69 }
70 
75 int AIR_BaseCountAircraft (const base_t* base)
76 {
77  int count = 0;
78 
79  AIR_ForeachFromBase(aircraft, base) {
80  count++;
81  }
82 
83  return count;
84 }
85 
86 #ifdef DEBUG
87 
92 static 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 
219 static 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 
237 void 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 
246 static inline void AII_CollectItem_ (void* data, const objDef_t* item, int amount)
247 {
248  AII_CollectItem((aircraft_t*)data, item, amount);
249 }
250 
256 static 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 
294 void 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 
328 const 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 
372 bool 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 
386 bool AIR_IsAircraftOnGeoscape (const aircraft_t* aircraft)
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 
413 int 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 
429 int AIR_AircraftMenuStatsValues (const int value, const int stat)
430 {
431  switch (stat) {
432  case AIR_STATS_SPEED:
433  case AIR_STATS_MAXSPEED:
434  /* Convert into km/h, and round this value */
435  return 10 * (int) (111.2 * value / 10.0f);
436  case AIR_STATS_FUELSIZE:
437  return value / 1000;
438  default:
439  return value;
440  }
441 }
442 
448 int AIR_GetOperationRange (const aircraft_t* aircraft)
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 
460 int AIR_GetRemainingRange (const aircraft_t* aircraft)
461 {
462  return aircraft->stats[AIR_STATS_SPEED] * aircraft->fuel;
463 }
464 
472 bool 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 
498 bool 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 
572 const 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 
587 static 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 
605 aircraft_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 
622 bool 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 
646 aircraft_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);
669  MS_AddNewMessage(_("Notice"), cp_messageBuffer);
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 
705 static 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 
732 const char* AIR_CheckMoveIntoNewHomebase (const aircraft_t* aircraft, const base_t* base)
733 {
734  const baseCapacities_t capacity = AIR_GetHangarCapacityType(aircraft);
735 
736  if (!B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(capacity)))
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 
762 static 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 
846  GEO_NotifyAircraftRemoved(aircraft);
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 
889 void 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 
926 bool 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 
948  GEO_CheckPositionBoundaries(aircraft->pos);
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 
971 static 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);
978  GEO_CheckPositionBoundaries(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);
990  GEO_SetInterceptorAircraft(aircraft);
991  CP_GameTimeStop();
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 
1014 static 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 
1072 void 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 */
1167  GEO_SetInterceptorAircraft(aircraft);
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 
1196 static 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 
1210 static 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 
1224 static const value_t aircraft_param_vals[] = {
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 
1237 static 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 
1259 static const value_t aircraft_radar_vals[] = {
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 
1276 void 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;
1283  aircraftItemType_t itemType = MAX_ACITEMS;
1284 
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 
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;
1407  case AC_ITEM_ELECTRONICS:
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;
1429  case AC_ITEM_ELECTRONICS:
1430  aircraftTemplate->electronics[aircraftTemplate->maxElectronics - 1].pos = (itemPos_t)i;
1431  break;
1432  default:
1433  i = AIR_POSITIONS_MAX;
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;
1452  case AC_ITEM_ELECTRONICS:
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
1551 static 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 
1562 static 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 /*===============================================
1588 Aircraft 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 
1609 void 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 
1651 void AIR_AircraftsUFODisappear (const aircraft_t* const ufo)
1652 {
1653  AIR_Foreach(aircraft) {
1654  if (aircraft->status == AIR_UFO && ufo == aircraft->aircraftTarget)
1655  AIR_AircraftReturnToBase(aircraft);
1656  }
1657 }
1658 
1669 static 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 
1685 static 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 
1701 static 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;
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 
1806 void AIR_GetDestinationWhilePursuing (const aircraft_t* shooter, const aircraft_t* target, vec2_t dest)
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 
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 /*============================================
1927 Aircraft functions related to team handling.
1928 ============================================*/
1929 
1935 {
1936  cgi->LIST_Delete(&aircraft->acTeam);
1937 }
1938 
1945 bool 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 
1967 bool 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 
1979 int AIR_GetTeamSize (const aircraft_t* aircraft)
1980 {
1981  assert(aircraft);
1982  return cgi->LIST_Count(aircraft->acTeam);
1983 }
1984 
1993 bool 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 
2015  return E_GetEmployeeByTypeFromChrUCN(e->getType(), e->chr.ucn);
2016 }
2017 
2023 bool AIR_PilotSurvivedCrash (const aircraft_t* aircraft)
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 
2054 void AIR_AutoAddPilotToAircraft (const base_t* base, Employee* pilot)
2055 {
2056  AIR_ForeachFromBase(aircraft, base) {
2057  if (AIR_SetPilot(aircraft, pilot))
2058  break;
2059  }
2060 }
2061 
2068 void AIR_RemovePilotFromAssignedAircraft (const base_t* base, const Employee* pilot)
2069 {
2070  AIR_ForeachFromBase(aircraft, base) {
2071  if (AIR_GetPilot(aircraft) == pilot) {
2072  AIR_SetPilot(aircraft, nullptr);
2073  break;
2074  }
2075  }
2076 }
2077 
2085 int 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 
2127 static 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 
2146 static void AIR_SaveAircraftSlotsXML (const aircraftSlot_t* slot, const int num, xmlNode_t* p, bool weapon)
2147 {
2148  for (int i = 0; i < num; i++) {
2150  AII_SaveOneSlotXML(sub, &slot[i], weapon);
2151  }
2152 }
2153 
2160 static 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 
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 
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
2214  cgi->XML_AddInt(node, SAVE_AIRCRAFT_AIRCRAFTTARGET, UFO_GetGeoscapeIDX(aircraft->aircraftTarget));
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);
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 
2275 bool AIR_SaveXML (xmlNode_t* parent)
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. */
2294  if (!AIRFIGHT_SaveXML(node))
2295  return false;
2296 
2297  return true;
2298 }
2299 
2310 static 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 
2328 static 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 
2355 static bool AIR_LoadAircraftXML (xmlNode_t* p, aircraft_t* craft)
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 
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 
2402  if (s[0] == '\0')
2403  s = _(craft->defaultName);
2404  Q_strncpyz(craft->name, s, sizeof(craft->name));
2405 
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);
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 
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 */
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 
2528 bool AIR_LoadXML (xmlNode_t* parent)
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 
2576 static bool AIR_PostLoadInitMissions (void)
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 
2631 bool AIR_PostLoadInit (void)
2632 {
2633  return AIR_PostLoadInitMissions();
2634 }
2635 
2641 bool AIR_AircraftAllowed (const base_t* base)
2642 {
2644 }
2645 
2650 bool 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 
2724 bool 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 
2763 const 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 
2853 bool 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 
2897 static const cmdList_t aircraftDebugCmds[] = {
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 
2909 void AIR_InitStartup (void)
2910 {
2912  cgi->Cmd_TableAddList(aircraftDebugCmds);
2913 }
2914 
2918 void 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 }
#define SAVE_AIRCRAFT_AIRSTATS
Definition: save_aircraft.h:52
void AIR_AircraftsNotifyUFORemoved(const aircraft_t *const ufo, bool destroyed)
Notify that a UFO has been removed.
#define SAVE_AIRCRAFT_LASTSPOTTED_DATE
Definition: save_aircraft.h:48
static void AIR_TransferItemsCarriedByCharacterToBase(character_t *chr, base_t *sourceBase, base_t *destBase)
Transfer items carried by a soldier from one base to another.
bool Q_strnull(const char *string)
Definition: shared.h:138
#define SAVE_AIRCRAFT_MEMBER
Definition: save_aircraft.h:61
bool AIR_Delete(base_t *base, aircraft_t *aircraft)
Will remove the given aircraft from the base.
aircraft_t ufos[MAX_UFOONGEOSCAPE]
Definition: cp_campaign.h:355
craftItem craftitem
Definition: inv_shared.h:331
struct base_s * homebase
Definition: cp_aircraft.h:150
xmlNode_t *IMPORT * XML_GetNode(xmlNode_t *parent, const char *name)
#define SAVE_AIRCRAFT_WEAPONS
Definition: save_aircraft.h:78
void GEO_NotifyAircraftRemoved(const aircraft_t *aircraft)
Notify that an aircraft has been removed from game.
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
aircraftStatus_t status
Definition: cp_aircraft.h:126
float getSurvivalChance(const byte *const color) const
Translate color value to terrain type and then to survival probability.
Definition: q_shared.h:435
bool notifySent[MAX_AIR_NOTIFICATIONS]
Definition: cp_aircraft.h:165
void Sys_Error(const char *error,...)
Definition: g_main.cpp:421
bool AIR_IsAircraftInBase(const aircraft_t *aircraft)
Checks whether given aircraft is in its homebase.
#define LINE_MAXPTS
Definition: cp_aircraft.h:33
#define MAX_UFOONGEOSCAPE
Definition: cp_radar.h:27
#define VectorSet(v, x, y, z)
Definition: vector.h:59
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
virtual bool add(const objDef_t *od, int amount, int looseAmount)
Add items to the cargo.
Definition: itemcargo.cpp:39
csi_t * csi
Definition: cgame.h:100
#define GEO_SetInterceptorAircraft(interceptor)
Definition: cp_geoscape.h:63
int numAircraftTemplates
Definition: cp_campaign.h:385
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...
mission definition
Definition: cp_missions.h:86
const objDef_t * INVSH_GetItemByID(const char *id)
Returns the item that belongs to the given id or nullptr if it wasn&#39;t found.
Definition: inv_shared.cpp:282
QGL_EXTERN GLint GLenum type
Definition: r_gl.h:94
itemPos_t pos
Definition: cp_aircraft.h:95
Defines all attributes of objects used in the inventory.
Definition: inv_shared.h:264
void AII_CollectingItems(aircraft_t *aircraft, int won)
Collect items from the battlefield.
bool B_GetBuildingStatus(const base_t *const base, const buildingType_t buildingType)
Get the status associated to a building.
Definition: cp_base.cpp:478
Item * getNextItem(const Item *prev) const
Definition: inv_shared.cpp:671
static const value_t aircraft_radar_vals[]
Valid radar definition values for an aircraft from script files.
const char * building
Definition: cp_aircraft.h:151
bool AIR_SendAircraftPursuingUFO(aircraft_t *aircraft, aircraft_t *ufo)
Make the specified aircraft purchasing a UFO.
aircraft_t * AIR_NewAircraft(base_t *base, const aircraft_t *aircraftTemplate)
Places a new aircraft in the given base.
bool AIR_AddEmployee(Employee *employee, aircraft_t *aircraft)
Assigns a soldier to an aircraft.
#define SAVE_AIRCRAFT_SLOT
Definition: save_aircraft.h:81
#define SAVE_AIRCRAFT_ROUTE_DISTANCE
Definition: save_aircraft.h:75
int skills[SKILL_NUM_TYPES]
Definition: chr_shared.h:122
#define SAVE_AIRCRAFT_LANDED
Definition: save_aircraft.h:58
cvar_t *IMPORT * Cvar_Set(const char *varName, const char *value,...) __attribute__((format(__printf__
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don&#39;t need to have varargs versions of all text functi...
Definition: shared.cpp:410
A base with all it&#39;s data.
Definition: cp_base.h:84
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...
int detectionIdx
Definition: cp_aircraft.h:174
void CAP_AddCurrent(base_t *base, baseCapacities_t capacity, int value)
Changes the current (used) capacity on a base.
#define E_Foreach(employeeType, var)
Definition: cp_employee.h:122
const aircraft_t * AIR_IsEmployeeInAircraft(const Employee *employee, const aircraft_t *aircraft)
Tells you if an employee is assigned to an aircraft.
aircraft_t * UFO_GetNext(aircraft_t *lastUFO)
Iterates through the UFOs.
Definition: cp_ufo.cpp:41
void VecToPolar(const vec3_t v, vec2_t a)
Converts vector coordinates into polar coordinates.
Definition: mathlib.cpp:922
void AII_CollectItem(aircraft_t *aircraft, const objDef_t *item, int amount)
Add an item to aircraft inventory.
void AII_UpdateAircraftStats(aircraft_t *aircraft)
Update the value of stats array of an aircraft.
int AIR_AircraftMenuStatsValues(const int value, const int stat)
Some of the aircraft values needs special calculations when they are shown in the menus...
const char * AIR_AircraftStatusToName(const aircraft_t *aircraft)
Translates the aircraft status id to a translatable string.
bool AIR_PostLoadInit(void)
Actions needs to be done after loading the savegame.
#define _(String)
Definition: cl_shared.h:44
int maxWeapons
Definition: cp_aircraft.h:145
A path on the map described by 2D points.
Definition: cp_aircraft.h:39
char * id
Definition: cp_aircraft.h:120
#define SAVE_AIRCRAFT_AIRCRAFTTARGET
Definition: save_aircraft.h:50
baseWeapon_t lasers[MAX_BASE_SLOT]
Definition: cp_base.h:119
#define SAVE_AIRCRAFT_VAL
Definition: save_aircraft.h:55
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition: shared.cpp:494
int B_AddToStorage(base_t *base, const objDef_t *obj, int amount)
Add/remove items to/from the storage.
Definition: cp_base.cpp:2576
#define B_IsUnderAttack(base)
Definition: cp_base.h:53
const objDef_t * ammoDef(void) const
Definition: inv_shared.h:460
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.
static bool AIR_LoadAircraftXML(xmlNode_t *p, aircraft_t *craft)
Loads an Aircraft from the savegame.
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.
void empty(void)
Empties the cargo.
Definition: itemcargo.cpp:99
#define SAVE_AIRCRAFT_PROJECTILES
Definition: save_aircraft.h:83
const aircraft_t * AIR_GetAircraftSilent(const char *name)
Searches the global array of aircraft types for a given aircraft.
#define SAVE_AIRCRAFT_HOMEBASE
Definition: save_aircraft.h:37
mission_t * CP_GetMissionByID(const char *missionId)
Get a mission in ccs.missions by Id.
Describes a character with all its attributes.
Definition: chr_shared.h:388
#define AIRCRAFT_REFUEL_FACTOR
Definition: cp_aircraft.h:36
bool AIR_RemoveEmployee(Employee *employee, aircraft_t *aircraft)
Removes a soldier from an aircraft.
Class describing a point of time.
Definition: DateTime.h:30
#define SAVE_AIRCRAFT_DIRECTION
Definition: save_aircraft.h:42
character_t chr
Definition: cp_employee.h:119
voidpf void uLong size
Definition: ioapi.h:42
equipDef_t eMission
Definition: cp_campaign.h:230
const objDef_t * ammo
Definition: cp_aircraft.h:86
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
static void AIR_SaveRouteXML(xmlNode_t *node, const mapline_t *route)
Saves an route plan of an aircraft.
float distance
Definition: cp_aircraft.h:41
char name[MAX_VAR]
Definition: cp_base.h:86
aircraft_t * AIR_AircraftGetFromIDX(int aircraftIdx)
Returns aircraft for a given global index.
bool AIR_IsInAircraftTeam(const aircraft_t *aircraft, const Employee *employee)
Checks whether given employee is in given aircraft.
bool AIR_AircraftHasEnoughFuelOneWay(const aircraft_t *aircraft, const vec2_t destination)
check if aircraft has enough fuel to go to destination
#define SAVE_AIRCRAFT_ROUTE
Definition: save_aircraft.h:74
void AIR_MoveAircraftIntoNewHomebase(aircraft_t *aircraft, base_t *base)
Moves a given aircraft to a new base (also the employees and inventory)
void AIR_AircraftsNotifyMissionRemoved(const mission_t *const mission)
Notify aircraft that a mission has been removed.
QGL_EXTERN GLsizei const GLvoid * data
Definition: r_gl.h:89
#define SAVE_AIRCRAFT_AIRCRAFT
Definition: save_aircraft.h:31
#define SAVE_AIRCRAFT_TIME
Definition: save_aircraft.h:44
const aircraft_t * AIR_GetAircraft(const char *name)
Searches the global array of aircraft types for a given aircraft.
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
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...
static float AIR_GetDestinationFunction(const float c, const float B, const float speedRatio, float a)
funtion we need to find roots.
struct radar_s radar
Definition: cp_aircraft.h:159
static void AII_InitialiseAircraftSlots(aircraft_t *aircraftTemplate)
Initialise all values of an aircraft slot.
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
static const constListEntry_t saveAircraftConstants[]
Definition: save_aircraft.h:87
bool isVirtual
Definition: inv_shared.h:284
bool GEO_IsRadarOverlayActivated(void)
Definition: cp_geoscape.cpp:85
#define SAVE_AIRCRAFT_STATUS
Definition: save_aircraft.h:36
const char *IMPORT * Cmd_Argv(int n)
#define SAVE_AIRCRAFT_CARGO
Definition: save_aircraft.h:66
char name[MAX_VAR]
Definition: cp_aircraft.h:121
int getTimeAsSeconds() const
Return the time part of the DateTime as seconds.
Definition: DateTime.cpp:54
bool load(xmlNode_t *root)
Load item cargo from xml savegame.
Definition: itemcargo.cpp:197
static void AII_CarriedItems(const Inventory *soldierInventory)
Process items carried by soldiers.
int installationTime
Definition: cp_aircraft.h:90
#define SAVE_AIRCRAFT_POS
Definition: save_aircraft.h:41
bool AIRFIGHT_SaveXML(xmlNode_t *parent)
Save callback for savegames in XML Format.
Item cargo class header.
void AIRFIGHT_ExecuteActions(const campaign_t *campaign, aircraft_t *shooter, aircraft_t *target)
Decide what an attacking aircraft can do.
itemWeight_t AII_GetItemWeightBySize(const objDef_t *od)
Returns craftitem weight based on size.
baseCapacities_t AIR_GetHangarCapacityType(const aircraft_t *aircraft)
Returns capacity type needed for an aircraft.
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.
Definition: cp_messages.cpp:63
memPool_t * cp_campaignPool
Definition: cp_campaign.cpp:62
void CP_GameTimeStop(void)
Stop game time speed.
Definition: cp_time.cpp:126
#define ERR_FATAL
Definition: common.h:210
const char * id
Definition: inv_shared.h:268
struct installation_s * installation
Definition: cp_aircraft.h:81
base_t * B_GetFoundedBaseByIDX(int baseIdx)
Array bound check for the base index.
Definition: cp_base.cpp:326
vec3_t projectedPos
Definition: cp_aircraft.h:134
int maxElectronics
Definition: cp_aircraft.h:148
int CAP_GetFreeCapacity(const base_t *base, baseCapacities_t capacityType)
Returns the free capacity of a type.
char *IMPORT * PoolStrDup(const char *in, memPool_t *pool, const int tagNum)
Header file for menu related console command callbacks.
int size
Definition: inv_shared.h:334
#define SAVE_AIRCRAFT_TEAM_UCN
Definition: save_aircraft.h:62
int idx
Definition: cp_base.h:85
#define SAVE_AIRCRAFTSTAT_NAMESPACE
Definition: save_aircraft.h:86
struct base_s * base
Definition: cp_aircraft.h:80
#define xmlNode_t
Definition: xml.h:24
int AIR_GetAircraftWeaponRanges(const aircraftSlot_t *slot, int maxSlot, float *weaponRanges)
Get the all the unique weapon ranges of this 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...
void RADAR_InitialiseUFOs(radar_t *radar)
Reset UFO sensored on radar.
Definition: cp_radar.cpp:265
char * defaultName
Definition: cp_aircraft.h:122
void AIR_InitStartup(void)
Init actions for aircraft-subsystem.
item instance data, with linked list capability
Definition: inv_shared.h:402
#define todeg
Definition: mathlib.h:51
bool E_DeleteEmployee(Employee *employee)
Removes the employee completely from the game (buildings + global list).
mapline_t route
Definition: cp_aircraft.h:135
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.
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
int aircraftHad
Definition: cp_statistics.h:49
void AII_ReloadAircraftWeapons(aircraft_t *aircraft)
Reload the weapons of an aircraft.
void AIR_AutoAddPilotToAircraft(const base_t *base, Employee *pilot)
Adds the pilot to the first available aircraft at the specified base.
inventory definition with all its containers
Definition: inv_shared.h:525
#define SAVE_AIRCRAFT_POINT
Definition: save_aircraft.h:43
int numLasers
Definition: cp_base.h:120
#define DotProduct(x, y)
Returns the distance between two 3-dimensional vectors.
Definition: vector.h:44
static int AIR_GetStorageRoom(const aircraft_t *aircraft)
Calculate used storage room corresponding to items in an aircraft.
#define ERR_DROP
Definition: common.h:211
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
#define DEBUG_CLIENT
Definition: defines.h:59
xmlNode_t *IMPORT * XML_GetPos3(xmlNode_t *parent, const char *name, vec3_t pos)
static void AIR_Move(aircraft_t *aircraft, int deltaTime)
bool AIR_ScriptSanityCheck(void)
Checks the parsed aircraft for errors.
aircraft_t * aircraftCurrent
Definition: cp_base.h:100
size_t ofs
Definition: scripts.h:170
aircraftSlot_t weapons[MAX_AIRCRAFTSLOT]
Definition: cp_aircraft.h:144
#define UFO_GetGeoscapeIDX(ufo)
Definition: cp_ufo.h:33
#define OBJZERO(obj)
Definition: shared.h:178
employeeType_t getType() const
Definition: cp_employee.h:99
int AIR_GetOperationRange(const aircraft_t *aircraft)
Calculates the range an aircraft can fly on the geoscape.
Definition: scripts.h:78
bool AIR_BaseHasAircraft(const base_t *base)
Checks whether there is any aircraft assigned to the given base.
Definition: cp_aircraft.cpp:66
vec2_t point[LINE_MAXPTS]
Definition: cp_aircraft.h:43
static void AIR_CorrectAircraftSlotPointers(aircraft_t *aircraft)
resets aircraftSlots&#39; backreference pointers for aircraft
xmlNode_t *IMPORT * XML_GetPos2(xmlNode_t *parent, const char *name, vec2_t pos)
static bool AIR_LoadRouteXML(xmlNode_t *p, mapline_t *route)
Loads the route of an aircraft.
aircraft_t * AIR_GetFirstFromBase(const base_t *b)
Iterates through the aircraft of a base.
Definition: cp_aircraft.cpp:51
Item cargo class.
Definition: itemcargo.h:41
Item * getNext() const
Definition: inv_shared.h:451
xmlNode_t *IMPORT * XML_GetDate(xmlNode_t *parent, const char *name, int *day, int *sec)
#define M_PI
Definition: mathlib.h:34
static const value_t aircraft_param_vals[]
Valid aircraft parameter definitions from script files.
bool AIR_PilotSurvivedCrash(const aircraft_t *aircraft)
Determine if an aircraft&#39;s pilot survived a crash, based on his piloting skill (and a bit of randomne...
void AIR_AircraftReturnToBase(aircraft_t *aircraft)
Calculates the way back to homebase for given aircraft and returns it.
void CP_Popup(const char *title, const char *text,...)
Wrapper around UI_Popup.
Definition: cp_popup.cpp:474
Campaign missions headers.
int numItems[MAX_OBJDEFS]
Definition: inv_shared.h:608
struct aircraft_s * aircraft
Definition: cp_aircraft.h:82
bool AIRFIGHT_LoadXML(xmlNode_t *parent)
Load callback for savegames in XML Format.
#define SAVE_AIRCRAFT_NAME
Definition: save_aircraft.h:33
int getDateAsDays() const
Return the date part of the DateTime as days.
Definition: DateTime.cpp:46
base_t * B_GetNext(base_t *lastBase)
Iterates through founded bases.
Definition: cp_base.cpp:286
itemWeight_t size
Definition: cp_aircraft.h:87
void AIR_ParseAircraft(const char *name, const char **text, bool assignAircraftItems)
Parses all aircraft that are defined in our UFO-scripts.
void PolarToVec(const vec2_t a, vec3_t v)
Converts longitude and latitude to a 3D vector in Euclidean coordinates.
Definition: mathlib.cpp:910
void RS_MarkCollected(technology_t *tech)
Marks a give technology as collected.
const char *IMPORT * XML_GetString(xmlNode_t *parent, const char *name)
#define SAVE_AIRCRAFT_MISSIONID
Definition: save_aircraft.h:46
bool E_MoveIntoNewBase(Employee *employee, base_t *newBase)
const cgame_import_t * cgi
const char * string
Definition: scripts.h:168
void AIR_ShutdownCallbacks(void)
void B_AircraftReturnedToHomeBase(aircraft_t *aircraft)
Do anything when dropship returns to base.
Definition: cp_base.cpp:2102
aircraft_t * target
Definition: cp_base.h:79
struct aircraft_s * tpl
Definition: cp_aircraft.h:119
ccs_t ccs
Definition: cp_campaign.cpp:63
class AlienCargo * alienCargo
Definition: cp_aircraft.h:177
vec3_t pos
Definition: cp_aircraft.h:132
Employee * AIR_GetPilot(const aircraft_t *aircraft)
Get pilot of an aircraft.
aircraft_t aircraftTemplates[MAX_AIRCRAFT]
Definition: cp_campaign.h:384
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
Campaign geoscape time header.
#define SAVE_AIRCRAFT_ELECTRONICS
Definition: save_aircraft.h:80
vec2_t pos
Definition: cp_missions.h:105
XML tag constants for savegame.
int AIR_BaseCountAircraft(const base_t *base)
Returns the number of aircraft on the given base.
Definition: cp_aircraft.cpp:75
linkedList_t * acTeam
Definition: cp_aircraft.h:140
xmlNode_t *IMPORT * XML_GetNextNode(xmlNode_t *current, xmlNode_t *parent, const char *name)
Header for Geoscape management.
bool isReloadable() const
Definition: inv_shared.h:352
Alien cargo class.
Definition: aliencargo.h:41
Header for base building related stuff.
Item * _invList
Definition: inv_shared.h:516
TerrainDefs terrainDefs
Definition: q_shared.h:574
bool active
Definition: cp_missions.h:90
static float AIR_GetDestinationFindRoot(const float c, const float B, const float speedRatio, float start)
Find the roots of a function.
building_t * B_GetBuildingTemplateSilent(const char *buildingName)
Returns the building in the global building-types list that has the unique name buildingID.
technology_t * RS_GetTechByID(const char *id)
return a pointer to the technology identified by given id string
QGL_EXTERN GLenum GLuint * dest
Definition: r_gl.h:101
void setUfoType(ufoType_t ufoT)
Definition: cp_aircraft.h:183
void AIR_CampaignRun(const campaign_t *campaign, int dt, bool updateRadarOverlay)
Handles aircraft movement and actions in geoscape mode.
#define SAVE_AIRCRAFT_DETECTED
Definition: save_aircraft.h:57
int stats[AIR_STATS_MAX]
Definition: cp_aircraft.h:160
char cp_messageBuffer[MAX_MESSAGE_TEXT]
Definition: cp_messages.cpp:33
void E_RemoveInventoryFromStorage(Employee *employee)
Removes the items of an employee (soldier) from the base storage (s)he is hired at.
bool AIR_IsAircraftOnGeoscape(const aircraft_t *aircraft)
Checks whether given aircraft is on geoscape.
bool save(xmlNode_t *root) const
Save item cargo to xml savegame.
Definition: itemcargo.cpp:218
QGL_EXTERN GLuint index
Definition: r_gl.h:110
bool AIR_AircraftAllowed(const base_t *base)
Returns true if the current base is able to handle aircraft.
#define MAX_AIRCRAFT
Definition: cp_aircraft.h:31
QGL_EXTERN GLuint count
Definition: r_gl.h:99
void AIR_AircraftsUFODisappear(const aircraft_t *const ufo)
Notify that a UFO disappear from radars.
CGAME_HARD_LINKED_FUNCTIONS linkedList_t * LIST_Add(linkedList_t **listDest, void const *data, size_t length)
const char *IMPORT * Com_ValueToStr(const void *base, const valueTypes_t type, const int ofs)
QGL_EXTERN GLfloat f
Definition: r_gl.h:114
#define AIR_ForeachFromBase(var, base)
iterates trough all aircraft from a specific homebase
Definition: cp_aircraft.h:202
alien cargo entry
Definition: aliencargo.h:32
int AIR_GetRemainingRange(const aircraft_t *aircraft)
Calculates the remaining range the aircraft can fly.
linkedList_t *IMPORT * LIST_GetPointer(linkedList_t *list, const void *data)
static float AIR_GetDestinationDerivativeFunction(const float c, const float B, const float speedRatio, float a)
derivative of the funtion we need to find roots.
char * provides
Definition: cp_research.h:156
class Employee * pilot
Definition: cp_aircraft.h:142
baseCapacities_t
All possible capacities in base.
Definition: cp_capacity.h:27
baseWeapon_t batteries[MAX_BASE_SLOT]
Definition: cp_base.h:116
An aircraft with all it&#39;s data.
Definition: cp_aircraft.h:115
void AIR_DestroyAircraft(aircraft_t *aircraft, bool killPilot)
Removes an aircraft from its base and the game.
linkedList_t * aircraft
Definition: cp_campaign.h:290
#define SAVE_AIRCRAFT_FUEL
Definition: save_aircraft.h:39
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
aircraft_t * UFO_GetByIDX(const int idx)
returns the UFO on the geoscape with a certain index
Definition: cp_ufo.cpp:85
bool AIR_CanIntercept(const aircraft_t *aircraft)
static char const *const air_position_strings[]
List of valid strings for itemPos_t.
int Q_FloatSort(const void *float1, const void *float2)
Compare two floats.
Definition: shared.cpp:372
Definition: scripts.h:49
#define SAVE_AIRCRAFT_DAMAGE
Definition: save_aircraft.h:40
struct mission_s * mission
Definition: cp_aircraft.h:153
static const short SECONDS_PER_HOUR
Definition: DateTime.h:42
#define AIR_SLOT_TYPE_STRINGS
Definition: scripts.h:154
stats_t campaignStats
Definition: cp_campaign.h:379
#define SAVE_AIRCRAFT_PHALANX
Definition: save_aircraft.h:28
QGL_EXTERN GLint i
Definition: r_gl.h:113
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_LoadOneSlotXML(xmlNode_t *node, aircraftSlot_t *slot, bool weapon)
Loads one slot (base, installation or aircraft)
#define SAVE_AIRCRAFT_ID
Definition: save_aircraft.h:32
xmlNode_t *IMPORT * XML_GetNextPos2(xmlNode_t *actual, xmlNode_t *parent, const char *name, vec2_t pos)
static hudRadar_t radar
Definition: scripts.h:50
#define SAVE_AIRCRAFT_UFOS
Definition: save_aircraft.h:27
#define MAX_AIRCRAFTSLOT
Definition: cp_aircraft.h:75
aircraftSlot_t shield
Definition: cp_aircraft.h:146
#define AIR_IsUFO(aircraft)
Definition: cp_aircraft.h:206
item cargo entry
Definition: itemcargo.h:32
#define SAVE_AIRCRAFT_AIRSTATID
Definition: save_aircraft.h:54
#define SAVE_AIRCRAFT_IDX
Definition: save_aircraft.h:34
int AIR_GetTeamSize(const aircraft_t *aircraft)
Counts the number of soldiers in given aircraft.
Header for slot management related stuff.
class DateTime lastSpotted
Definition: cp_aircraft.h:175
This is the technology parsed from research.ufo.
Definition: cp_research.h:139
void AIR_AssignInitial(aircraft_t *aircraft)
Assigns initial team of soldiers to aircraft.
aircraftItemType_t
All different types of craft items.
Definition: inv_shared.h:197
vec_t VectorNormalize(vec3_t v)
Calculate unit vector for a given vec3_t.
Definition: mathlib.cpp:745
#define SAVE_AIRCRAFT_SHIELDS
Definition: save_aircraft.h:79
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
vec3_t direction
Definition: cp_aircraft.h:133
CASSERT(lengthof(air_slot_type_strings)==MAX_ACITEMS)
int maxTeamSize
Definition: cp_aircraft.h:139
chrScoreGlobal_t score
Definition: chr_shared.h:406
#define SAVE_AIRCRAFT_AIRCRAFTTEAM
Definition: save_aircraft.h:60
Definition: cmd.h:86
void GEO_CheckPositionBoundaries(float *pos)
Check that a position (in latitude / longitude) is within boundaries.
static bool AIR_SaveAircraftXML(xmlNode_t *p, const aircraft_t *const aircraft, bool const isUfo)
Saves an aircraft.
int B_AddAntimatter(base_t *base, int amount)
Manages antimatter (adding, removing) through Antimatter Storage Facility.
Definition: cp_base.cpp:2635
Employee * E_GetEmployeeFromChrUCN(int uniqueCharacterNumber)
Searches all employee for the ucn (character id)
#define SAVE_AIRCRAFT_AIRSTAT
Definition: save_aircraft.h:53
Alien cargo class header.
#define MEMBER_SIZEOF(TYPE, MEMBER)
Definition: scripts.h:34
float crand(void)
Return random values between -1 and 1.
Definition: mathlib.cpp:517
#define LIST_Foreach(list, type, var)
Iterates over a linked list, it&#39;s safe to delete the returned entry from the list while looping over ...
Definition: list.h:41
vec_t vec3_t[3]
Definition: ufotypes.h:39
#define torad
Definition: mathlib.h:50
char * missionID
Definition: cp_aircraft.h:155
#define Vector2Copy(src, dest)
Definition: vector.h:52
bool AIR_SendAircraftToMission(aircraft_t *aircraft, mission_t *mission)
Sends the specified aircraft to specified mission.
vec_t vec2_t[2]
Definition: ufotypes.h:38
buildingType_t buildingType
Definition: cp_building.h:110
Header file for single player campaign control.
void RADAR_UpdateWholeRadarOverlay(void)
Update radar overlay of base, installation and aircraft range.
Definition: cp_radar.cpp:89
static void AIR_SaveAircraftSlotsXML(const aircraftSlot_t *slot, const int num, xmlNode_t *p, bool weapon)
Saves an item slot.
Definition: scripts.h:52
void AIR_InitCallbacks(void)
bool load(xmlNode_t *root)
Load alien cargo from xml savegame.
Definition: aliencargo.cpp:167
#define SAVE_AIRCRAFT_DETECTIONIDX
Definition: save_aircraft.h:47
aircraft_t * AIR_GetAircraftFromBaseByIDXSafe(const base_t *base, int index)
static void AII_SetAircraftInSlots(aircraft_t *aircraft)
Initialise aircraft pointer in each slot of an aircraft.
slot of aircraft
Definition: cp_aircraft.h:78
#define SAVE_AIRCRAFT_ALIENCARGO
Definition: save_aircraft.h:72
char name[MAX_VAR]
Definition: chr_shared.h:390
const char *const air_slot_type_strings[]
aircraftSlot_t electronics[MAX_AIRCRAFTSLOT]
Definition: cp_aircraft.h:147
#define lengthof(x)
Definition: shared.h:105
xmlNode_t *IMPORT * XML_AddNode(xmlNode_t *parent, const char *name)
A building with all it&#39;s data.
Definition: cp_building.h:73
bool AIR_SetPilot(aircraft_t *aircraft, Employee *pilot)
Assign a pilot to an aircraft.
#define Q_streq(a, b)
Definition: shared.h:136
valueTypes_t type
Definition: scripts.h:169
bool AIR_LoadXML(xmlNode_t *parent)
int numUFOs
Definition: cp_campaign.h:356
const Container * getNextCont(const Container *prev, bool inclTemp=false) const
Definition: inv_shared.cpp:722
#define AIR_Foreach(var)
iterates trough all aircraft
Definition: cp_aircraft.h:193
const char * AIR_CheckMoveIntoNewHomebase(const aircraft_t *aircraft, const base_t *base)
Checks if destination base can store an aircraft and its team.
static const value_t aircraft_vals[]
Valid aircraft definition values from script files.
const char * MIS_GetName(const mission_t *mission)
Returns a short translated name for a mission.
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 AII_CollectAmmo(void *data, const Item *magazine)
Count and collect ammo from gun magazine.
#define MAX_BASES
Definition: cp_base.h:32
bool isPilot() const
Definition: cp_employee.h:66
technology_t * RS_GetTechForItem(const objDef_t *item)
Returns technology entry for an item.
const objDef_t * def(void) const
Definition: inv_shared.h:469
vec3_t pos
Definition: cp_base.h:91
int numPoints
Definition: cp_aircraft.h:40
bool save(xmlNode_t *root) const
Save alien cargo to xml savegame.
Definition: aliencargo.cpp:188
void UFO_RemoveFromGeoscape(aircraft_t *ufo)
Remove the specified ufo from geoscape.
Definition: cp_ufo.cpp:817
static bool AIR_PostLoadInitMissions(void)
Set the mission pointers for all the aircraft after loading a savegame.
const char *IMPORT * Com_GetConstVariable(const char *space, int value)
struct aircraft_s * aircraftTarget
Definition: cp_aircraft.h:157
bool AIR_AddToAircraftTeam(aircraft_t *aircraft, Employee *employee)
Adds given employee to given aircraft.
#define ANTIMATTER_ITEM_ID
Definition: cp_research.h:37
void AIR_RemoveEmployees(aircraft_t &aircraft)
Removes all soldiers from an aircraft.
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.
const char *IMPORT * Com_EParse(const char **text, const char *errhead, const char *errinfo)
const objDef_t * item
Definition: cp_aircraft.h:85
int numBatteries
Definition: cp_base.h:117
uint8_t byte
Definition: ufotypes.h:34
void TR_NotifyAircraftRemoved(const aircraft_t *aircraft)
Notify that an aircraft has been removed.
baseCapacities_t B_GetCapacityFromBuildingType(buildingType_t type)
Get the capacity associated to a building type.
Definition: cp_base.cpp:416
itemPos_t
different positions for aircraft items
Definition: cp_aircraft.h:55
linkedList_t * list(void) const
Returns a copy of the cargo list.
Definition: itemcargo.cpp:156
aircraftStatus_t
Definition: cp_aircraft.h:99
#define SAVE_AIRCRAFT_ROUTE_POINT
Definition: save_aircraft.h:76
void AIR_ResetAircraftTeam(aircraft_t *aircraft)
Resets team in given aircraft.
static const cmdList_t aircraftDebugCmds[]
void AII_SaveOneSlotXML(xmlNode_t *p, const aircraftSlot_t *slot, bool weapon)
Save callback for savegames in XML Format.
#define GEO_SetMissionAircraft(aircraft)
Definition: cp_geoscape.h:66
static void AIR_Refuel(aircraft_t *aircraft, int deltaTime)
int getAmmoLeft() const
Definition: inv_shared.h:466
void addClip(const Item *item)
Combine the rounds of partially used clips.
float stats[AIR_STATS_MAX]
Definition: inv_shared.h:248
Employee * E_GetEmployeeByTypeFromChrUCN(employeeType_t type, int uniqueCharacterNumber)
Searches employee from a type for the ucn (character id)
#define SAVE_AIRCRAFTSTATUS_NAMESPACE
Definition: save_aircraft.h:85
bool AIR_SaveXML(xmlNode_t *parent)
Save callback for savegames in xml format.
int productionCost
Definition: cp_aircraft.h:129
mission_t * GEO_SelectMission(mission_t *mission)
Select the specified mission.
static void AII_CollectItem_(void *data, const objDef_t *item, int amount)
bool transfer
Definition: cp_employee.h:118
class ItemCargo * itemCargo
Definition: cp_aircraft.h:178
#define KILOMETER_PER_DEGREE
Definition: cp_geoscape.h:28
void GEO_SelectAircraft(aircraft_t *aircraft)
Select the specified aircraft on the geoscape.
#define SAVE_AIRCRAFT_PILOTUCN
Definition: save_aircraft.h:64
#define UFO_NONE
Definition: scripts.h:148
void AIR_Shutdown(void)
Closing actions for aircraft-subsystem.
buildingType_t B_GetBuildingTypeByCapacity(baseCapacities_t cap)
Get building type by base capacity.
Definition: cp_base.cpp:447
void Com_SkipBlock(const char **text)
Skips a block of {} in our script files.
Definition: parse.cpp:253
bool AII_ReloadWeapon(aircraftSlot_t *slot)
Reloads an aircraft/defence-system weapon.
double GetDistanceOnGlobe(const vec2_t pos1, const vec2_t pos2)
Calculate distance on the geoscape.
Definition: mathlib.cpp:171
int AIR_CountInBaseByTemplate(const base_t *base, const aircraft_t *aircraftTemplate)
Calculates the amount of aircraft (of the given type) in the selected base.
bool AIR_AircraftMakeMove(int dt, aircraft_t *aircraft)
Moves given aircraft.
Inventory inv
Definition: chr_shared.h:411
bool detected
Definition: cp_aircraft.h:167
void AIR_DeleteAircraft(aircraft_t *aircraft)
Removes an aircraft from its base and the game.