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