cp_aircraft.c

Go to the documentation of this file.
00001 
00010 /*
00011 Copyright (C) 2002-2010 UFO: Alien Invasion.
00012 
00013 This program is free software; you can redistribute it and/or
00014 modify it under the terms of the GNU General Public License
00015 as published by the Free Software Foundation; either version 2
00016 of the License, or (at your option) any later version.
00017 
00018 This program is distributed in the hope that it will be useful,
00019 but WITHOUT ANY WARRANTY; without even the implied warranty of
00020 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00021 
00022 See the GNU General Public License for more details.
00023 
00024 You should have received a copy of the GNU General Public License
00025 along with this program; if not, write to the Free Software
00026 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00027 
00028 */
00029 
00030 #include "../client.h"
00031 #include "../ui/ui_main.h"
00032 #include "../ui/ui_popup.h"
00033 #include "../../shared/parse.h"
00034 #include "cp_campaign.h"
00035 #include "cp_mapfightequip.h"
00036 #include "cp_map.h"
00037 #include "cp_ufo.h"
00038 #include "cp_alienbase.h"
00039 #include "cp_team.h"
00040 #include "cp_time.h"
00041 #include "cp_missions.h"
00042 #include "save/save_aircraft.h"
00043 
00044 
00049 aircraft_t* AIR_GetNextFromBase (const base_t *b, aircraft_t *lastAircraft)
00050 {
00051     if (b) {
00052         aircraft_t *aircraft = lastAircraft;
00053         while ((aircraft = (aircraft_t *)LIST_GetNext(b->aircraft, (void*)aircraft)) != NULL) {
00054             if (aircraft->status != AIR_CRASHED)
00055                 return aircraft;
00056         }
00057         return aircraft;
00058     }
00059 
00060     return NULL;
00061 }
00062 
00068 qboolean AIR_BaseHasAircraft (const base_t *base)
00069 {
00070     return base != NULL && AIR_GetNextFromBase(base, NULL) != NULL;
00071 }
00072 
00083 static int AIR_UpdateHangarCapForOne (const aircraft_t const *aircraftTemplate, base_t *base)
00084 {
00085     int freeSpace = 0;
00086 
00087     assert(aircraftTemplate);
00088     assert(aircraftTemplate == aircraftTemplate->tpl);  /* Make sure it's an aircraft template. */
00089 
00090     if (!base)
00091         return AIRCRAFT_HANGAR_ERROR;
00092 
00093     if (!B_GetBuildingStatus(base, B_HANGAR) && !B_GetBuildingStatus(base, B_SMALL_HANGAR)) {
00094         Com_Printf("AIR_UpdateHangarCapForOne: base does not have any hangar - error!\n");
00095         return AIRCRAFT_HANGAR_ERROR;
00096     }
00097 
00098     if (aircraftTemplate->size >= AIRCRAFT_LARGE) {
00099         if (!B_GetBuildingStatus(base, B_HANGAR)) {
00100             Com_Printf("AIR_UpdateHangarCapForOne: base does not have big hangar - error!\n");
00101             return AIRCRAFT_HANGAR_ERROR;
00102         }
00103         freeSpace = base->capacities[CAP_AIRCRAFT_BIG].max - base->capacities[CAP_AIRCRAFT_BIG].cur;
00104         if (freeSpace > 0) {
00105             base->capacities[CAP_AIRCRAFT_BIG].cur++;
00106             return AIRCRAFT_HANGAR_BIG;
00107         } else {
00108             /* No free space for this aircraft. This should never happen here. */
00109             Com_Printf("AIR_UpdateHangarCapForOne: no free space!\n");
00110             return AIRCRAFT_HANGAR_ERROR;
00111         }
00112     } else {
00113         if (!B_GetBuildingStatus(base, B_SMALL_HANGAR)) {
00114             Com_Printf("AIR_UpdateHangarCapForOne: base does not have small hangar - error!\n");
00115             return AIRCRAFT_HANGAR_ERROR;
00116         }
00117         freeSpace = base->capacities[CAP_AIRCRAFT_SMALL].max - base->capacities[CAP_AIRCRAFT_SMALL].cur;
00118         if (freeSpace > 0) {
00119             base->capacities[CAP_AIRCRAFT_SMALL].cur++;
00120             return AIRCRAFT_HANGAR_SMALL;
00121         } else {
00122             /* No free space for this aircraft. This should never happen here. */
00123             Com_Printf("AIR_UpdateHangarCapForOne: no free space!\n");
00124             return AIRCRAFT_HANGAR_ERROR;
00125         }
00126     }
00127 }
00128 
00135 void AIR_UpdateHangarCapForAll (base_t *base)
00136 {
00137     aircraft_t *aircraft;
00138 
00139     if (!base)
00140         return;
00141 
00142     /* Reset current capacities for hangar. */
00143     base->capacities[CAP_AIRCRAFT_BIG].cur = 0;
00144     base->capacities[CAP_AIRCRAFT_SMALL].cur = 0;
00145 
00146     aircraft = NULL;
00147     while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
00148         Com_DPrintf(DEBUG_CLIENT, "AIR_UpdateHangarCapForAll: base: %s, aircraft: %s\n", base->name, aircraft->id);
00149         AIR_UpdateHangarCapForOne(aircraft->tpl, base);
00150     }
00151     Com_DPrintf(DEBUG_CLIENT, "AIR_UpdateHangarCapForAll: base capacities.cur: small: %i big: %i\n", base->capacities[CAP_AIRCRAFT_SMALL].cur, base->capacities[CAP_AIRCRAFT_BIG].cur);
00152 }
00153 
00154 #ifdef DEBUG
00155 
00160 void AIR_ListAircraft_f (void)
00161 {
00162     int k, baseIdx;
00163 
00164     if (Cmd_Argc() == 2)
00165         baseIdx = atoi(Cmd_Argv(1));
00166 
00167     for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
00168         const base_t *base = B_GetFoundedBaseByIDX(baseIdx);
00169         aircraft_t *aircraft;
00170         if (!base)
00171             continue;
00172 
00173         Com_Printf("Aircraft in %s: %i\n", base->name, LIST_Count(base->aircraft));
00174 
00175         aircraft = NULL;
00176         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
00177             linkedList_t *l;
00178             Com_Printf("Aircraft %s\n", aircraft->name);
00179             Com_Printf("...idx global %i\n", aircraft->idx);
00180             Com_Printf("...homebase: %s\n", aircraft->homebase ? aircraft->homebase->name : "NO HOMEBASE");
00181             for (k = 0; k < aircraft->maxWeapons; k++) {
00182                 if (aircraft->weapons[k].item) {
00183                     Com_Printf("...weapon slot %i contains %s", k, aircraft->weapons[k].item->id);
00184                     if (!aircraft->weapons[k].installationTime)
00185                         Com_Printf(" (functional)\n");
00186                     else if (aircraft->weapons[k].installationTime > 0)
00187                         Com_Printf(" (%i hours before installation is finished)\n",aircraft->weapons[k].installationTime);
00188                     else
00189                         Com_Printf(" (%i hours before removing is finished)\n",aircraft->weapons[k].installationTime);
00190                     if (aircraft->weapons[k].ammo)
00191                         if (aircraft->weapons[k].ammoLeft > 1)
00192                             Com_Printf("......this weapon is loaded with ammo %s\n", aircraft->weapons[k].ammo->id);
00193                         else
00194                             Com_Printf("......no more ammo (%s)\n", aircraft->weapons[k].ammo->id);
00195                     else
00196                         Com_Printf("......this weapon isn't loaded with ammo\n");
00197                 }
00198                 else
00199                     Com_Printf("...weapon slot %i is empty\n", k);
00200             }
00201             if (aircraft->shield.item) {
00202                 Com_Printf("...armour slot contains %s", aircraft->shield.item->id);
00203                 if (!aircraft->shield.installationTime)
00204                         Com_Printf(" (functional)\n");
00205                     else if (aircraft->shield.installationTime > 0)
00206                         Com_Printf(" (%i hours before installation is finished)\n",aircraft->shield.installationTime);
00207                     else
00208                         Com_Printf(" (%i hours before removing is finished)\n",aircraft->shield.installationTime);
00209             } else
00210                 Com_Printf("...armour slot is empty\n");
00211             for (k = 0; k < aircraft->maxElectronics; k++) {
00212                 if (aircraft->electronics[k].item) {
00213                     Com_Printf("...electronics slot %i contains %s", k, aircraft->electronics[k].item->id);
00214                     if (!aircraft->electronics[k].installationTime)
00215                         Com_Printf(" (functional)\n");
00216                     else if (aircraft->electronics[k].installationTime > 0)
00217                         Com_Printf(" (%i hours before installation is finished)\n",aircraft->electronics[k].installationTime);
00218                     else
00219                         Com_Printf(" (%i hours before removing is finished)\n",aircraft->electronics[k].installationTime);
00220                 }
00221                 else
00222                     Com_Printf("...electronics slot %i is empty\n", k);
00223             }
00224             if (aircraft->pilot) {
00225                 Com_Printf("...pilot: idx: %i name: %s\n", aircraft->pilot->idx, aircraft->pilot->chr.name);
00226             } else {
00227                 Com_Printf("...no pilot assigned\n");
00228             }
00229             Com_Printf("...damage: %i\n", aircraft->damage);
00230             Com_Printf("...stats: ");
00231             for (k = 0; k < AIR_STATS_MAX; k++) {
00232                 if (k == AIR_STATS_WRANGE)
00233                     Com_Printf("%.2f ", aircraft->stats[k] / 1000.0f);
00234                 else
00235                     Com_Printf("%i ", aircraft->stats[k]);
00236             }
00237             Com_Printf("\n");
00238             Com_Printf("...name %s\n", aircraft->id);
00239             Com_Printf("...type %i\n", aircraft->type);
00240             Com_Printf("...size %i\n", aircraft->maxTeamSize);
00241             Com_Printf("...fuel %i\n", aircraft->fuel);
00242             Com_Printf("...status %s\n", AIR_AircraftStatusToName(aircraft));
00243             Com_Printf("...pos %.0f:%.0f\n", aircraft->pos[0], aircraft->pos[1]);
00244             Com_Printf("...team: (%i/%i)\n", LIST_Count(aircraft->acTeam), aircraft->maxTeamSize);
00245             for (l = aircraft->acTeam; l != NULL; l = l->next) {
00246                 const employee_t const *employee = (const employee_t *)l->data;
00247                 const character_t *chr = &employee->chr;
00248                 Com_Printf("......idx (in global array): %i\n", employee->idx);
00249                 if (chr)
00250                     Com_Printf(".........name: %s\n", chr->name);
00251                 else
00252                     Com_Printf(".........ERROR: Could not get character for employee %i\n", employee->idx);
00253             }
00254         }
00255     }
00256 }
00257 #endif
00258 
00259 static equipDef_t eTempEq;      
00267 static void AII_CollectingAmmo (aircraft_t *aircraft, const invList_t *magazine)
00268 {
00269     /* Let's add remaining ammo to market. */
00270     eTempEq.numItemsLoose[magazine->item.m->idx] += magazine->item.a;
00271     if (eTempEq.numItemsLoose[magazine->item.m->idx] >= magazine->item.t->ammo) {
00272         /* There are more or equal ammo on the market than magazine needs - collect magazine. */
00273         eTempEq.numItemsLoose[magazine->item.m->idx] -= magazine->item.t->ammo;
00274         AII_CollectItem(aircraft, magazine->item.m, 1);
00275     }
00276 }
00277 
00286 void AII_CollectItem (aircraft_t *aircraft, const objDef_t *item, int amount)
00287 {
00288     int i;
00289     itemsTmp_t *cargo = aircraft->itemcargo;
00290 
00291     for (i = 0; i < aircraft->itemTypes; i++) {
00292         if (cargo[i].item == item) {
00293             Com_DPrintf(DEBUG_CLIENT, "AII_CollectItem: collecting %s (%i) amount %i -> %i\n", item->name, item->idx, cargo[i].amount, cargo[i].amount + amount);
00294             cargo[i].amount += amount;
00295             return;
00296         }
00297     }
00298     Com_DPrintf(DEBUG_CLIENT, "AII_CollectItem: adding %s (%i) amount %i\n", item->name, item->idx, amount);
00299     cargo[i].item = item;
00300     cargo[i].amount = amount;
00301     aircraft->itemTypes++;
00302 }
00303 
00309 static void AII_CarriedItems (const le_t *soldier)
00310 {
00311     containerIndex_t container;
00312     invList_t *invList;
00313 
00314     for (container = 0; container < csi.numIDs; container++) {
00315         /* Items on the ground are collected as ET_ITEM */
00316         if (INVDEF(container)->temp)
00317             continue;
00318         for (invList = CONTAINER(soldier, container); invList; invList = invList->next) {
00319             const objDef_t *item = invList->item.t;
00320             technology_t *tech = RS_GetTechForItem(item);
00321             ccs.eMission.numItems[item->idx]++;
00322             RS_MarkCollected(tech);
00323 
00324             if (!item->reload || invList->item.a == 0)
00325                 continue;
00326             ccs.eMission.numItemsLoose[invList->item.m->idx] += invList->item.a;
00327             if (ccs.eMission.numItemsLoose[invList->item.m->idx] >= item->ammo) {
00328                 ccs.eMission.numItemsLoose[invList->item.m->idx] -= item->ammo;
00329                 ccs.eMission.numItems[invList->item.m->idx]++;
00330             }
00331             /* The guys keep their weapons (half-)loaded. Auto-reload
00332              * will happen at equip screen or at the start of next mission,
00333              * but fully loaded weapons will not be reloaded even then. */
00334         }
00335     }
00336 }
00337 
00349 void AII_CollectingItems (aircraft_t *aircraft, int won)
00350 {
00351     int i, j;
00352     le_t *le = NULL;
00353     itemsTmp_t *cargo;
00354     itemsTmp_t prevItemCargo[MAX_CARGO];
00355     int prevItemTypes;
00356 
00357     /* Save previous cargo */
00358     memcpy(prevItemCargo, aircraft->itemcargo, sizeof(aircraft->itemcargo));
00359     prevItemTypes = aircraft->itemTypes;
00360     /* Make sure itemcargo is empty. */
00361     memset(aircraft->itemcargo, 0, sizeof(aircraft->itemcargo));
00362 
00363     /* Make sure eTempEq is empty as well. */
00364     memset(&eTempEq, 0, sizeof(eTempEq));
00365 
00366     cargo = aircraft->itemcargo;
00367     aircraft->itemTypes = 0;
00368 
00369     while ((le = LE_GetNextInUse(le))) {
00370         /* Winner collects everything on the floor, and everything carried
00371          * by surviving actors. Loser only gets what their living team
00372          * members carry. */
00373         if (LE_IsItem(le)) {
00374             if (won) {
00375                 invList_t *item;
00376                 for (item = FLOOR(le); item; item = item->next) {
00377                     AII_CollectItem(aircraft, item->item.t, 1);
00378                     if (item->item.t->reload && item->item.a > 0)
00379                         AII_CollectingAmmo(aircraft, item);
00380                 }
00381             }
00382         } else if (LE_IsActor(le)) {
00383             /* The items are already dropped to floor and are available
00384              * as ET_ITEM if the actor is dead; or the actor is not ours. */
00385             /* First of all collect armour of dead or stunned actors (if won). */
00386             if (won && LE_IsDead(le)) {
00387                 invList_t *item = ARMOUR(le);
00388                 if (item)
00389                     AII_CollectItem(aircraft, item->item.t, 1);
00390             } else if (le->team == cls.team && !LE_IsDead(le)) {
00391                 /* Finally, the living actor from our team. */
00392                 AII_CarriedItems(le);
00393             }
00394         }
00395     }
00396     /* Fill the missionResults array. */
00397     ccs.missionResults.itemTypes = aircraft->itemTypes;
00398     for (i = 0; i < aircraft->itemTypes; i++)
00399         ccs.missionResults.itemAmount += cargo[i].amount;
00400 
00401 #ifdef DEBUG
00402     /* Print all of collected items. */
00403     for (i = 0; i < aircraft->itemTypes; i++) {
00404         if (cargo[i].amount > 0)
00405             Com_DPrintf(DEBUG_CLIENT, "Collected items: idx: %i name: %s amount: %i\n", cargo[i].item->idx, cargo[i].item->name, cargo[i].amount);
00406     }
00407 #endif
00408 
00409     /* Put previous cargo back */
00410     for (i = 0; i < prevItemTypes; i++) {
00411         for (j = 0; j < aircraft->itemTypes; j++) {
00412             if (cargo[j].item == prevItemCargo[i].item) {
00413                 cargo[j].amount += prevItemCargo[i].amount;
00414                 break;
00415             }
00416         }
00417         if (j == aircraft->itemTypes) {
00418             cargo[j] = prevItemCargo[i];
00419             aircraft->itemTypes++;
00420         }
00421     }
00422 }
00423 
00429 const char *AIR_AircraftStatusToName (const aircraft_t * aircraft)
00430 {
00431     assert(aircraft);
00432     assert(aircraft->homebase);
00433 
00434     /* display special status if base-attack affects aircraft */
00435     if (aircraft->homebase->baseStatus == BASE_UNDER_ATTACK &&
00436         AIR_IsAircraftInBase(aircraft))
00437         return _("ON RED ALERT");
00438 
00439     switch (aircraft->status) {
00440     case AIR_NONE:
00441         return _("Nothing - should not be displayed");
00442     case AIR_HOME:
00443         return _("at home base");
00444     case AIR_REFUEL:
00445         return _("refuelling");
00446     case AIR_IDLE:
00447         return _("idle");
00448     case AIR_TRANSIT:
00449         return _("in transit");
00450     case AIR_MISSION:
00451         return _("enroute to mission");
00452     case AIR_UFO:
00453         return _("pursuing a UFO");
00454     case AIR_DROP:
00455         return _("ready to drop soldiers");
00456     case AIR_INTERCEPT:
00457         return _("intercepting a UFO");
00458     case AIR_TRANSFER:
00459         return _("enroute to new home base");
00460     case AIR_RETURNING:
00461         return _("returning to base");
00462     case AIR_CRASHED:
00463         Com_Error(ERR_DROP, "AIR_CRASHED should not be visible anywhere");
00464     }
00465     return NULL;
00466 }
00467 
00474 qboolean AIR_IsAircraftInBase (const aircraft_t * aircraft)
00475 {
00476     if (aircraft->status == AIR_HOME || aircraft->status == AIR_REFUEL)
00477         return qtrue;
00478     return qfalse;
00479 }
00480 
00488 qboolean AIR_IsAircraftOnGeoscape (const aircraft_t * aircraft)
00489 {
00490     switch (aircraft->status) {
00491     case AIR_IDLE:
00492     case AIR_TRANSIT:
00493     case AIR_MISSION:
00494     case AIR_UFO:
00495     case AIR_DROP:
00496     case AIR_INTERCEPT:
00497     case AIR_RETURNING:
00498         return qtrue;
00499     case AIR_NONE:
00500     case AIR_REFUEL:
00501     case AIR_HOME:
00502     case AIR_TRANSFER:
00503     case AIR_CRASHED:
00504         return qfalse;
00505     }
00506 
00507     Com_Error(ERR_FATAL, "Unknown aircraft status %i", aircraft->status);
00508 }
00509 
00515 int AIR_CountTypeInBase (const base_t *base, aircraftType_t aircraftType)
00516 {
00517     int count = 0;
00518     aircraft_t *aircraft;
00519 
00520     aircraft = NULL;
00521     while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL)
00522         if (aircraft->type == aircraftType)
00523             count++;
00524 
00525     return count;
00526 }
00527 
00531 const char *AIR_GetAircraftString (aircraftType_t aircraftType)
00532 {
00533     switch (aircraftType) {
00534     case AIRCRAFT_INTERCEPTOR:
00535         return _("Interceptor");
00536     case AIRCRAFT_TRANSPORTER:
00537         return _("Transporter");
00538     case AIRCRAFT_UFO:
00539         return _("UFO");
00540     }
00541     return "";
00542 }
00543 
00549 int CL_AircraftMenuStatsValues (const int value, const int stat)
00550 {
00551     switch (stat) {
00552     case AIR_STATS_SPEED:
00553     case AIR_STATS_MAXSPEED:
00554         /* Convert into km/h, and round this value */
00555         return 10 * (int) (111.2 * value / 10.0f);
00556     case AIR_STATS_FUELSIZE:
00557         return value / 1000;
00558     default:
00559         return value;
00560     }
00561 }
00562 
00568 int AIR_GetOperationRange (const aircraft_t *aircraft)
00569 {
00570     const int range = aircraft->stats[AIR_STATS_SPEED] * aircraft->stats[AIR_STATS_FUELSIZE];
00571     /* the 2.0f factor is for going to destination and then come back */
00572     return 100 * (int) (KILOMETER_PER_DEGREE * range / (2.0f * (float)SECONDS_PER_HOUR * 100.0f));
00573 }
00574 
00580 int AIR_GetRemainingRange (const aircraft_t *aircraft)
00581 {
00582     return aircraft->stats[AIR_STATS_SPEED] * aircraft->fuel;
00583 }
00584 
00592 qboolean AIR_AircraftHasEnoughFuel (const aircraft_t *aircraft, const vec2_t destination)
00593 {
00594     base_t *base;
00595     float distance;
00596 
00597     assert(aircraft);
00598     base = (base_t *) aircraft->homebase;
00599     assert(base);
00600 
00601     /* Calculate the line that the aircraft should follow to go to destination */
00602     distance = GetDistanceOnGlobe(aircraft->pos, destination);
00603 
00604     /* Calculate the line that the aircraft should then follow to go back home */
00605     distance += GetDistanceOnGlobe(destination, base->pos);
00606 
00607     /* Check if the aircraft has enough fuel to go to destination and then go back home */
00608     return (distance <= AIR_GetRemainingRange(aircraft) / (float)SECONDS_PER_HOUR);
00609 }
00610 
00618 qboolean AIR_AircraftHasEnoughFuelOneWay (const aircraft_t const *aircraft, const vec2_t destination)
00619 {
00620     float distance;
00621 
00622     assert(aircraft);
00623 
00624     /* Calculate the line that the aircraft should follow to go to destination */
00625     distance = GetDistanceOnGlobe(aircraft->pos, destination);
00626 
00627     /* Check if the aircraft has enough fuel to go to destination */
00628     return (distance <= AIR_GetRemainingRange(aircraft) / (float)SECONDS_PER_HOUR);
00629 }
00630 
00636 void AIR_AircraftReturnToBase (aircraft_t *aircraft)
00637 {
00638     if (aircraft && AIR_IsAircraftOnGeoscape(aircraft)) {
00639         const base_t *base = aircraft->homebase;
00640         assert(base);
00641         Com_DPrintf(DEBUG_CLIENT, "return '%s' (%i) to base ('%s').\n", aircraft->id, aircraft->idx, base->name);
00642         MAP_MapCalcLine(aircraft->pos, base->pos, &aircraft->route);
00643         aircraft->status = AIR_RETURNING;
00644         aircraft->time = 0;
00645         aircraft->point = 0;
00646         aircraft->mission = NULL;
00647     }
00648 }
00649 
00655 int AIR_GetAircraftIDXInBase (const aircraft_t* aircraft)
00656 {
00657     int i;
00658     base_t *base;
00659     aircraft_t *aircraftInBase;
00660 
00661     if (!aircraft || !aircraft->homebase)
00662         return AIRCRAFT_INBASE_INVALID;
00663 
00664     base = aircraft->homebase;
00665 
00666     i = 0;
00667     aircraftInBase = NULL;
00668     while ((aircraftInBase = AIR_GetNextFromBase(base, aircraftInBase)) != NULL) {
00669         if (aircraftInBase == aircraft)
00670             return i;
00671         i++;
00672     }
00673 
00674     /* Aircraft not found in base */
00675     return AIRCRAFT_INBASE_INVALID;
00676 }
00677 
00683 aircraft_t *AIR_GetAircraftFromBaseByIDXSafe (base_t* base, int index)
00684 {
00685     aircraft_t *aircraft;
00686     int i;
00687 
00688     i = 0;
00689     aircraft = NULL;
00690     while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
00691         if (index == i)
00692             return aircraft;
00693         i++;
00694     }
00695 
00696     return NULL;
00697 }
00698 
00705 aircraft_t *AIR_GetAircraftSilent (const char *name)
00706 {
00707     int i;
00708 
00709     if (!name)
00710         return NULL;
00711     for (i = 0; i < ccs.numAircraftTemplates; i++) {
00712         aircraft_t *aircraftTemplate = &ccs.aircraftTemplates[i];
00713         if (!strcmp(aircraftTemplate->id, name))
00714             return aircraftTemplate;
00715     }
00716     return NULL;
00717 }
00718 
00724 aircraft_t *AIR_GetAircraft (const char *name)
00725 {
00726     aircraft_t *aircraft = AIR_GetAircraftSilent(name);
00727     if (name == NULL || name[0] == '\0')
00728         Com_Error(ERR_DROP, "AIR_GetAircraft called with NULL name!");
00729     else if (aircraft == NULL)
00730         Com_Error(ERR_DROP, "Aircraft '%s' not found", name);
00731 
00732     return aircraft;
00733 }
00734 
00739 static void AII_SetAircraftInSlots (aircraft_t *aircraft)
00740 {
00741     int i;
00742 
00743     /* initialise weapon slots */
00744     for (i = 0; i < MAX_AIRCRAFTSLOT; i++) {
00745         aircraft->weapons[i].aircraft = aircraft;
00746         aircraft->electronics[i].aircraft = aircraft;
00747     }
00748     aircraft->shield.aircraft = aircraft;
00749 }
00750 
00759 aircraft_t *AIR_Add (base_t *base, const aircraft_t *aircraftTemplate)
00760 {
00761     aircraft_t *aircraft = (aircraft_t *)LIST_Add(&base->aircraft, (const byte *)aircraftTemplate, sizeof(*aircraftTemplate))->data;
00762     aircraft->homebase = base;
00763     ccs.numAircraft++;
00764     return aircraft;
00765 }
00766 
00774 qboolean AIR_Delete (base_t *base, const aircraft_t *aircraft)
00775 {
00776     ccs.numAircraft--;
00777     return LIST_Remove(&base->aircraft, (const void *)aircraft);
00778 }
00779 
00786 aircraft_t* AIR_NewAircraft (base_t *base, const char *name)
00787 {
00788     const aircraft_t *aircraftTemplate = AIR_GetAircraft(name);
00789 
00790     /* copy generic aircraft description to individual aircraft in base
00791      * we do this because every aircraft can have its own parameters
00792      * now lets use the aircraft array for the base to set some parameters */
00793     aircraft_t *aircraft = AIR_Add(base, aircraftTemplate);
00794     aircraft->idx = ccs.campaignStats.aircraftHad++;    
00795     aircraft->homebase = base;
00796     /* Update the values of its stats */
00797     AII_UpdateAircraftStats(aircraft);
00798     /* initialise aircraft pointer in slots */
00799     AII_SetAircraftInSlots(aircraft);
00800     /* give him some fuel */
00801     aircraft->fuel = aircraft->stats[AIR_STATS_FUELSIZE];
00802     /* Set HP to maximum */
00803     aircraft->damage = aircraft->stats[AIR_STATS_DAMAGE];
00804     /* Set Default Name */
00805     Q_strncpyz(aircraft->name, _(aircraft->defaultName), lengthof(aircraft->name));
00806 
00807     /* set initial direction of the aircraft */
00808     VectorSet(aircraft->direction, 1, 0, 0);
00809 
00810     AIR_ResetAircraftTeam(aircraft);
00811 
00812     Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new %s is ready in %s"), _(aircraft->tpl->name), base->name);
00813     MS_AddNewMessage(_("Notice"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL);
00814     Com_DPrintf(DEBUG_CLIENT, "Setting aircraft to pos: %.0f:%.0f\n", base->pos[0], base->pos[1]);
00815     Vector2Copy(base->pos, aircraft->pos);
00816     RADAR_Initialise(&aircraft->radar, RADAR_AIRCRAFTRANGE, RADAR_AIRCRAFTTRACKINGRANGE, 1.0f, qfalse);
00817 
00818     /* Update base capacities. */
00819     Com_DPrintf(DEBUG_CLIENT, "idx_sample: %i name: %s weight: %i\n", aircraft->tpl->idx, aircraft->id, aircraft->size);
00820     Com_DPrintf(DEBUG_CLIENT, "Adding new aircraft %s with IDX %i for %s\n", aircraft->id, aircraft->idx, base->name);
00821     if (!base->aircraftCurrent)
00822         base->aircraftCurrent = aircraft;
00823     aircraft->hangar = AIR_UpdateHangarCapForOne(aircraft->tpl, base);
00824     if (aircraft->hangar == AIRCRAFT_HANGAR_ERROR)
00825         Com_Printf("AIR_NewAircraft: ERROR, new aircraft but no free space in hangars!\n");
00826     /* also update the base menu buttons */
00827     Cmd_ExecuteString("base_init");
00828     return aircraft;
00829 }
00830 
00831 int AIR_GetCapacityByAircraftWeight (const aircraft_t *aircraft)
00832 {
00833     switch (aircraft->size) {
00834     case AIRCRAFT_SMALL:
00835         return CAP_AIRCRAFT_SMALL;
00836     case AIRCRAFT_LARGE:
00837         return CAP_AIRCRAFT_BIG;
00838     }
00839     Com_Error(ERR_DROP, "AIR_GetCapacityByAircraftWeight: Unknown weight of aircraft '%i'\n", aircraft->size);
00840 }
00841 
00846 static int AIR_GetStorageRoom (const aircraft_t *aircraft)
00847 {
00848     int size = 0;
00849     linkedList_t* l;
00850 
00851     for (l = aircraft->acTeam; l != NULL; l = l->next) {
00852         const employee_t const *employee = (const employee_t *)l->data;
00853         containerIndex_t container;
00854         for (container = 0; container < csi.numIDs; container++) {
00855             invList_t *ic;
00856 #if 0
00857             /* ignore items linked from any temp container */
00858             if (INVDEF(container)->temp)
00859                 continue;
00860 #endif
00861             for (ic = CONTAINER(&employee->chr, container); ic; ic = ic->next) {
00862                 const objDef_t *obj = ic->item.t;
00863                 size += obj->size;
00864 
00865                 obj = ic->item.m;
00866                 if (obj)
00867                     size += obj->size;
00868             }
00869         }
00870     }
00871 
00872     return size;
00873 }
00874 
00875 const char *AIR_CheckMoveIntoNewHomebase (const aircraft_t *aircraft, const base_t* base, const int capacity)
00876 {
00877     if (!B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(capacity)))
00878         return _("No operational hangars at that base.");
00879 
00880     /* not enough capacity */
00881     if (base->capacities[capacity].cur >= base->capacities[capacity].max)
00882         return _("No free hangars at that base.");
00883 
00884     if (aircraft->maxTeamSize + ((aircraft->pilot) ? 1 : 0) + base->capacities[CAP_EMPLOYEES].cur >  base->capacities[CAP_EMPLOYEES].max)
00885         return _("Insufficient free crew quarter space at that base.");
00886 
00887     if (aircraft->maxTeamSize && base->capacities[CAP_ITEMS].cur + AIR_GetStorageRoom(aircraft) > base->capacities[CAP_ITEMS].max)
00888         return _("Insufficient storage space at that base.");
00889 
00890     /* check aircraft fuel, because the aircraft has to travel to the new base */
00891     if (!AIR_AircraftHasEnoughFuelOneWay(aircraft, base->pos))
00892         return _("That base is beyond this aircraft's range.");
00893 
00894     return NULL;
00895 }
00896 
00903 static void AIR_TransferItemsCarriedByCharacterToBase (character_t *chr, base_t *sourceBase, base_t* destBase)
00904 {
00905     const invList_t *ic;
00906     containerIndex_t container;
00907 
00908     for (container = 0; container < csi.numIDs; container++) {
00909 #if 0
00910         /* ignore items linked from any temp container */
00911         if (INVDEF(container)->temp)
00912             continue;
00913 #endif
00914         for (ic = CONTAINER(chr, container); ic; ic = ic->next) {
00915             const objDef_t *obj = ic->item.t;
00916             B_UpdateStorageAndCapacity(sourceBase, obj, -1, qfalse, qfalse);
00917             B_UpdateStorageAndCapacity(destBase, obj, 1, qfalse, qfalse);
00918 
00919             obj = ic->item.m;
00920             if (obj) {
00921                 B_UpdateStorageAndCapacity(sourceBase, obj, -1, qfalse, qfalse);
00922                 B_UpdateStorageAndCapacity(destBase, obj, 1, qfalse, qfalse);
00923             }
00924         }
00925     }
00926 }
00927 
00934 qboolean AIR_MoveAircraftIntoNewHomebase (aircraft_t *aircraft, base_t *base)
00935 {
00936     base_t *oldBase;
00937     baseCapacities_t capacity;
00938     aircraft_t *aircraftDest;
00939     linkedList_t* l;
00940     int i;
00941 
00942     assert(aircraft);
00943     assert(base);
00944     assert(base != aircraft->homebase);
00945 
00946     Com_DPrintf(DEBUG_CLIENT, "AIR_MoveAircraftIntoNewHomebase: Change homebase of '%s' to '%s'\n", aircraft->id, base->name);
00947 
00948     /* Is aircraft being transfered? */
00949     if (aircraft->status == AIR_TRANSFER) {
00950         /* Move the aircraft to the new base to avoid fuel problems */
00951         VectorCopy(base->pos, aircraft->pos);
00952         aircraft->status = AIR_HOME;
00953     }
00954 
00955     capacity = AIR_GetCapacityByAircraftWeight(aircraft);
00956     if (AIR_CheckMoveIntoNewHomebase(aircraft, base, capacity))
00957         return qfalse;
00958 
00959     oldBase = aircraft->homebase;
00960     assert(oldBase);
00961 
00962     /* Transfer employees */
00963     E_MoveIntoNewBase(aircraft->pilot, base);
00964 
00965     for (l = aircraft->acTeam; l != NULL; l = l->next) {
00966         employee_t *employee = (employee_t *)l->data;
00967         E_MoveIntoNewBase(employee, base);
00968         /* Transfer items carried by soldiers from oldBase to base */
00969         AIR_TransferItemsCarriedByCharacterToBase(&employee->chr, oldBase, base);
00970     }
00971 
00972     /* Move aircraft to new base */
00973     aircraftDest = AIR_Add(base, aircraft);
00974     base->capacities[capacity].cur++;
00975 
00976     /* Correct aircraftSlots' backreference */
00977     for (i = 0; i < aircraftDest->maxWeapons; i++) {
00978         aircraftDest->weapons[i].aircraft = aircraftDest;
00979     }
00980     for (i = 0; i < aircraftDest->maxElectronics; i++) {
00981         aircraftDest->electronics[i].aircraft = aircraftDest;
00982     }
00983     aircraftDest->shield.aircraft = aircraftDest;
00984 
00985     /* Remove aircraft from old base */
00986     AIR_Delete(oldBase, aircraft);
00987     oldBase->capacities[capacity].cur--;
00988 
00989     if (oldBase->aircraftCurrent == aircraft)
00990         oldBase->aircraftCurrent = AIR_GetNextFromBase(oldBase, NULL);
00991 
00992     if (!base->aircraftCurrent)
00993         base->aircraftCurrent = aircraftDest;
00994 
00995     /* No need to update global IDX of every aircraft: the global IDX of this aircraft did not change */
00996     /* Redirect selectedAircraft */
00997     MAP_SelectAircraft(aircraftDest);
00998 
00999     if (aircraftDest->status == AIR_RETURNING) {
01000         /* redirect to the new base */
01001         AIR_AircraftReturnToBase(aircraftDest);
01002     }
01003 
01004     return qtrue;
01005 }
01006 
01020 void AIR_DeleteAircraft (aircraft_t *aircraft)
01021 {
01022     int i;
01023     base_t *base;
01024     /* Check if aircraft is on geoscape while it's not destroyed yet */
01025     const qboolean aircraftIsOnGeoscape = AIR_IsAircraftOnGeoscape(aircraft);
01026 
01027     assert(aircraft);
01028     base = aircraft->homebase;
01029     assert(base);
01030 
01031     MAP_NotifyAircraftRemoved(aircraft);
01032     TR_NotifyAircraftRemoved(aircraft);
01033 
01034     /* Remove pilot and all soldiers from the aircraft (the employees are still hired after this). */
01035     AIR_RemoveEmployees(aircraft);
01036 
01037     /* base is NULL here because we don't want to readd this to the inventory
01038      * If you want this in the inventory you really have to call these functions
01039      * before you are destroying a craft */
01040     for (i = 0; i < MAX_AIRCRAFTSLOT; i++) {
01041         AII_RemoveItemFromSlot(NULL, aircraft->weapons, qfalse);
01042         AII_RemoveItemFromSlot(NULL, aircraft->electronics, qfalse);
01043     }
01044     AII_RemoveItemFromSlot(NULL, &aircraft->shield, qfalse);
01045 
01046     if (base->aircraftCurrent == aircraft)
01047         base->aircraftCurrent = NULL;
01048 
01049     AIR_Delete(base, aircraft);
01050 
01051     if (!AIR_BaseHasAircraft(base)) {
01052         Cvar_SetValue("mn_equipsoldierstate", 0);
01053         Cvar_Set("mn_aircraftstatus", "");
01054         Cvar_Set("mn_aircraftinbase", "0");
01055         Cvar_Set("mn_aircraftname", "");
01056         Cvar_Set("mn_aircraft_model", "");
01057     } else if (base->aircraftCurrent == NULL) {
01058         base->aircraftCurrent = AIR_GetNextFromBase(base, NULL);
01059     }
01060 
01061     /* also update the base menu buttons */
01062     Cmd_ExecuteString("base_init");
01063 
01064     /* update hangar capacities */
01065     AIR_UpdateHangarCapForAll(base);
01066 
01067     /* Update Radar overlay */
01068     if (aircraftIsOnGeoscape)
01069         RADAR_UpdateWholeRadarOverlay();
01070 }
01071 
01078 void AIR_DestroyAircraft (aircraft_t *aircraft)
01079 {
01080     linkedList_t* l;
01081 
01082     assert(aircraft);
01083 
01084     for (l = aircraft->acTeam; l != NULL;) {
01085         employee_t *employee = (employee_t *)l->data;
01086         l = l->next;
01087         E_RemoveInventoryFromStorage(employee);
01088         E_DeleteEmployee(employee, employee->type);
01089     }
01090     /* the craft may no longer have any employees assigned */
01091     /* remove the pilot */
01092     if (aircraft->pilot && E_DeleteEmployee(aircraft->pilot, aircraft->pilot->type)) {
01093         aircraft->pilot = NULL;
01094     } else {
01095         /* This shouldn't ever happen. */
01096         Com_Error(ERR_DROP, "AIR_DestroyAircraft: aircraft id %s had no pilot\n", aircraft->id);
01097     }
01098 
01099     AIR_DeleteAircraft(aircraft);
01100 }
01101 
01108 qboolean AIR_AircraftMakeMove (int dt, aircraft_t* aircraft)
01109 {
01110     float dist;
01111 
01112     /* calc distance */
01113     aircraft->time += dt;
01114     aircraft->fuel -= dt;
01115 
01116     dist = (float) aircraft->stats[AIR_STATS_SPEED] * aircraft->time / (float)SECONDS_PER_HOUR;
01117 
01118     /* Check if destination reached */
01119     if (dist >= aircraft->route.distance * (aircraft->route.numPoints - 1)) {
01120         return qtrue;
01121     } else {
01122         /* calc new position */
01123         float frac = dist / aircraft->route.distance;
01124         const int p = (int) frac;
01125         frac -= p;
01126         aircraft->point = p;
01127         aircraft->pos[0] = (1 - frac) * aircraft->route.point[p][0] + frac * aircraft->route.point[p + 1][0];
01128         aircraft->pos[1] = (1 - frac) * aircraft->route.point[p][1] + frac * aircraft->route.point[p + 1][1];
01129 
01130         MAP_CheckPositionBoundaries(aircraft->pos);
01131     }
01132 
01133     aircraft->hasMoved = qtrue;
01134     aircraft->numInterpolationPoints = 0;
01135 
01136     dist = (float) aircraft->stats[AIR_STATS_SPEED] * (aircraft->time + dt) / (float)SECONDS_PER_HOUR;
01137 
01138     /* Now calculate the projected position. This is the position that the aircraft should get on
01139      * next frame if its route didn't change in meantime. */
01140     if (dist >= aircraft->route.distance * (aircraft->route.numPoints - 1)) {
01141         VectorSet(aircraft->projectedPos, 0.0f, 0.0f, 0.0f);
01142     } else {
01143         /* calc new position */
01144         float frac = dist / aircraft->route.distance;
01145         const int p = (int) frac;
01146         frac -= p;
01147         aircraft->projectedPos[0] = (1 - frac) * aircraft->route.point[p][0] + frac * aircraft->route.point[p + 1][0];
01148         aircraft->projectedPos[1] = (1 - frac) * aircraft->route.point[p][1] + frac * aircraft->route.point[p + 1][1];
01149 
01150         MAP_CheckPositionBoundaries(aircraft->projectedPos);
01151     }
01152 
01153     return qfalse;
01154 }
01155 
01156 static void AIR_Move (aircraft_t* aircraft, int deltaTime)
01157 {
01158     /* Aircraft is moving */
01159     if (AIR_AircraftMakeMove(deltaTime, aircraft)) {
01160         /* aircraft reach its destination */
01161         const float *end = aircraft->route.point[aircraft->route.numPoints - 1];
01162         Vector2Copy(end, aircraft->pos);
01163         MAP_CheckPositionBoundaries(aircraft->pos);
01164 
01165         switch (aircraft->status) {
01166         case AIR_MISSION:
01167             /* Aircraft reached its mission */
01168             assert(aircraft->mission);
01169             aircraft->mission->active = qtrue;
01170             aircraft->status = AIR_DROP;
01171             ccs.missionAircraft = aircraft;
01172             MAP_SelectMission(ccs.missionAircraft->mission);
01173             ccs.interceptAircraft = ccs.missionAircraft;
01174             Com_DPrintf(DEBUG_CLIENT, "ccs.interceptAircraft: %i\n", ccs.interceptAircraft->idx);
01175             CL_GameTimeStop();
01176             UI_PushWindow("popup_intercept_ready", NULL);
01177             break;
01178         case AIR_RETURNING:
01179             /* aircraft entered in homebase */
01180             aircraft->status = AIR_REFUEL;
01181             B_AircraftReturnedToHomeBase(aircraft);
01182             break;
01183         case AIR_TRANSFER:
01184         case AIR_UFO:
01185             break;
01186         default:
01187             aircraft->status = AIR_IDLE;
01188             break;
01189         }
01190     }
01191 }
01192 
01193 static void AIR_Refuel (aircraft_t *aircraft, int deltaTime)
01194 {
01195     /* Aircraft is refuelling at base */
01196     int fillup;
01197 
01198     if (aircraft->fuel < 0)
01199         aircraft->fuel = 0;
01200     /* amount of fuel we would like to load */
01201     fillup = min(deltaTime * AIRCRAFT_REFUEL_FACTOR, aircraft->stats[AIR_STATS_FUELSIZE] - aircraft->fuel);
01202     /* This craft uses antimatter as fuel */
01203     assert(aircraft->homebase);
01204     if (aircraft->stats[AIR_STATS_ANTIMATTER] > 0 && fillup > 0) {
01205         /* the antimatter we have */
01206         const int amAvailable = B_ItemInBase(INVSH_GetItemByID(ANTIMATTER_TECH_ID), aircraft->homebase);
01207         /* current antimatter level in craft */
01208         const int amCurrentLevel = aircraft->stats[AIR_STATS_ANTIMATTER] * (aircraft->fuel / (float) aircraft->stats[AIR_STATS_FUELSIZE]);
01209         /* next antimatter level in craft */
01210         const int amNextLevel = aircraft->stats[AIR_STATS_ANTIMATTER] * ((aircraft->fuel + fillup) / (float) aircraft->stats[AIR_STATS_FUELSIZE]);
01211         /* antimatter needed */
01212         int amLoad = amNextLevel - amCurrentLevel;
01213 
01214         if (amLoad > amAvailable) {
01215             /* amount of fuel we can load */
01216             fillup = aircraft->stats[AIR_STATS_FUELSIZE] * ((amCurrentLevel + amAvailable) / (float) aircraft->stats[AIR_STATS_ANTIMATTER]) - aircraft->fuel;
01217             amLoad = amAvailable;
01218 
01219             if (!aircraft->notifySent[AIR_CANNOT_REFUEL]) {
01220                 Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
01221                         _("Craft %s couldn't be completely refueled at %s. Not enough antimatter."), aircraft->name, aircraft->homebase->name);
01222                 MSO_CheckAddNewMessage(NT_AIRCRAFT_CANNOTREFUEL, _("Notice"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL);
01223                 aircraft->notifySent[AIR_CANNOT_REFUEL] = qtrue;
01224             }
01225         }
01226 
01227         if (amLoad > 0)
01228             B_ManageAntimatter(aircraft->homebase, amLoad, qfalse);
01229     }
01230 
01231     aircraft->fuel += fillup;
01232 
01233     if (aircraft->fuel >= aircraft->stats[AIR_STATS_FUELSIZE]) {
01234         aircraft->fuel = aircraft->stats[AIR_STATS_FUELSIZE];
01235         aircraft->status = AIR_HOME;
01236         Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
01237                 _("Craft %s has refueled at %s."), aircraft->name, aircraft->homebase->name);
01238         MSO_CheckAddNewMessage(NT_AIRCRAFT_REFUELED, _("Notice"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL);
01239         aircraft->notifySent[AIR_CANNOT_REFUEL] = qfalse;
01240     }
01241 
01242 }
01243 
01250 void CL_CampaignRunAircraft (int dt, qboolean updateRadarOverlay)
01251 {
01252     /* true if at least one aircraft moved: radar overlay must be updated
01253      * This is static because aircraft can move without radar being
01254      * updated (sa CL_CampaignRun) */
01255     static qboolean radarOverlayReset = qfalse;
01256 
01257     assert(dt >= 0);
01258 
01259     if (dt > 0) {
01260         int j;
01261         for (j = 0; j < MAX_BASES; j++) {
01262             base_t *base = B_GetBaseByIDX(j);
01263             aircraft_t *aircraft;
01264 
01265             /* Run each aircraft */
01266             aircraft = NULL;
01267             while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01268                 int k;
01269                 assert(aircraft->homebase);
01270                 if (aircraft->status == AIR_IDLE) {
01271                     /* Aircraft idle out of base */
01272                     aircraft->fuel -= dt;
01273                 } else if (AIR_IsAircraftOnGeoscape(aircraft)) {
01274                     AIR_Move(aircraft, dt);
01275                     /* radar overlay should be updated */
01276                     radarOverlayReset = qtrue;
01277                 } else if (aircraft->status == AIR_REFUEL) {
01278                     AIR_Refuel(aircraft, dt);
01279                 }
01280 
01281                 /* Check aircraft low fuel (only if aircraft is not already returning to base or in base) */
01282                 if (aircraft->status != AIR_RETURNING && AIR_IsAircraftOnGeoscape(aircraft) &&
01283                     !AIR_AircraftHasEnoughFuel(aircraft, aircraft->pos)) {
01285                     MS_AddNewMessage(_("Notice"), va(_("Craft %s is low on fuel and must return to base."), aircraft->name), qfalse, MSG_STANDARD, NULL);
01286                     AIR_AircraftReturnToBase(aircraft);
01287                 }
01288 
01289                 /* Aircraft purchasing ufo */
01290                 if (aircraft->status == AIR_UFO) {
01291                     /* Solve the fight */
01292                     AIRFIGHT_ExecuteActions(aircraft, aircraft->aircraftTarget);
01293                 }
01294 
01295                 for (k = 0; k < aircraft->maxWeapons; k++) {
01296                     /* Update delay to launch next projectile */
01297                     if (AIR_IsAircraftOnGeoscape(aircraft) && (aircraft->weapons[k].delayNextShot > 0))
01298                         aircraft->weapons[k].delayNextShot -= dt;
01299                     /* Reload if needed */
01300                     if (aircraft->weapons[k].ammoLeft <= 0)
01301                         AII_ReloadWeapon(&aircraft->weapons[k]);
01302                 }
01303             }
01304         }
01305     }
01306 
01307     if (updateRadarOverlay && radarOverlayReset && MAP_IsRadarOverlayActivated()) {
01308         RADAR_UpdateWholeRadarOverlay();
01309         radarOverlayReset = qfalse;
01310     }
01311 }
01312 
01318 aircraft_t* AIR_AircraftGetFromIDX (int idx)
01319 {
01320     int baseIdx;
01321 
01322 #ifdef PARANOID
01323     if (ccs.numBases < 1) {
01324         Com_DPrintf(DEBUG_CLIENT, "AIR_AircraftGetFromIDX: no base(s) found!\n");
01325     }
01326 #endif
01327 
01328     for (baseIdx = 0; baseIdx < ccs.numBases; baseIdx++) {
01329         base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01330         aircraft_t* aircraft = NULL;
01331 
01332         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01333             if (aircraft->idx == idx) {
01334                 Com_DPrintf(DEBUG_CLIENT, "AIR_AircraftGetFromIDX: aircraft idx: %i - base idx: %i (%s)\n",
01335                         aircraft->idx, base->idx, base->name);
01336                 return aircraft;
01337             }
01338         }
01339     }
01340 
01341     return NULL;
01342 }
01343 
01350 qboolean AIR_SendAircraftToMission (aircraft_t *aircraft, mission_t *mission)
01351 {
01352     if (!aircraft || !mission)
01353         return qfalse;
01354 
01355     if (AIR_GetTeamSize(aircraft) == 0) {
01356         UI_Popup(_("Notice"), _("Assign one or more soldiers to this aircraft first."));
01357         return qfalse;
01358     }
01359 
01360     /* if aircraft was in base */
01361     if (AIR_IsAircraftInBase(aircraft)) {
01362         /* reload its ammunition */
01363         AII_ReloadAircraftWeapons(aircraft);
01364     }
01365 
01366     /* ensure interceptAircraft is set correctly */
01367     ccs.interceptAircraft = aircraft;
01368 
01369     /* if mission is a base-attack and aircraft already in base, launch
01370      * mission immediately */
01371     if (aircraft->homebase->baseStatus == BASE_UNDER_ATTACK &&
01372         AIR_IsAircraftInBase(aircraft)) {
01373         aircraft->mission = mission;
01374         mission->active = qtrue;
01375         UI_PushWindow("popup_baseattack", NULL);
01376         return qtrue;
01377     }
01378 
01379     if (!AIR_AircraftHasEnoughFuel(aircraft, mission->pos)) {
01380         MS_AddNewMessage(_("Notice"), _("Insufficient fuel."), qfalse, MSG_STANDARD, NULL);
01381         return qfalse;
01382     }
01383 
01384     MAP_MapCalcLine(aircraft->pos, mission->pos, &aircraft->route);
01385     aircraft->status = AIR_MISSION;
01386     aircraft->time = 0;
01387     aircraft->point = 0;
01388     aircraft->mission = mission;
01389 
01390     return qtrue;
01391 }
01392 
01397 static void AII_InitialiseAircraftSlots (aircraft_t *aircraftTemplate)
01398 {
01399     int i;
01400 
01401     /* initialise weapon slots */
01402     for (i = 0; i < MAX_AIRCRAFTSLOT; i++) {
01403         AII_InitialiseSlot(aircraftTemplate->weapons + i, aircraftTemplate, NULL, NULL, AC_ITEM_WEAPON);
01404         AII_InitialiseSlot(aircraftTemplate->electronics + i, aircraftTemplate, NULL, NULL, AC_ITEM_ELECTRONICS);
01405     }
01406     AII_InitialiseSlot(&aircraftTemplate->shield, aircraftTemplate, NULL, NULL, AC_ITEM_SHIELD);
01407 }
01408 
01413 static const char *air_position_strings[AIR_POSITIONS_MAX] = {
01414     "nose_left",
01415     "nose_center",
01416     "nose_right",
01417     "wing_left",
01418     "wing_right",
01419     "rear_left",
01420     "rear_center",
01421     "rear_right"
01422 };
01423 
01427 static const value_t aircraft_param_vals[] = {
01428     {"speed", V_INT, offsetof(aircraft_t, stats[AIR_STATS_SPEED]), MEMBER_SIZEOF(aircraft_t, stats[0])},
01429     {"maxspeed", V_INT, offsetof(aircraft_t, stats[AIR_STATS_MAXSPEED]), MEMBER_SIZEOF(aircraft_t, stats[0])},
01430     {"shield", V_INT, offsetof(aircraft_t, stats[AIR_STATS_SHIELD]), MEMBER_SIZEOF(aircraft_t, stats[0])},
01431     {"ecm", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ECM]), MEMBER_SIZEOF(aircraft_t, stats[0])},
01432     {"damage", V_INT, offsetof(aircraft_t, stats[AIR_STATS_DAMAGE]), MEMBER_SIZEOF(aircraft_t, stats[0])},
01433     {"accuracy", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ACCURACY]), MEMBER_SIZEOF(aircraft_t, stats[0])},
01434     {"antimatter", V_INT, offsetof(aircraft_t, stats[AIR_STATS_ANTIMATTER]), MEMBER_SIZEOF(aircraft_t, stats[0])},
01435 
01436     {NULL, 0, 0, 0}
01437 };
01438 
01440 static const value_t aircraft_vals[] = {
01441     {"name", V_STRING, offsetof(aircraft_t, name), 0},
01442     {"defaultname", V_TRANSLATION_STRING, offsetof(aircraft_t, defaultName), 0},
01443     {"numteam", V_INT, offsetof(aircraft_t, maxTeamSize), MEMBER_SIZEOF(aircraft_t, maxTeamSize)},
01444     {"size", V_INT, offsetof(aircraft_t, size), MEMBER_SIZEOF(aircraft_t, size)},
01445     {"nogeoscape", V_BOOL, offsetof(aircraft_t, notOnGeoscape), MEMBER_SIZEOF(aircraft_t, notOnGeoscape)},
01446     {"interestlevel", V_INT, offsetof(aircraft_t, ufoInterestOnGeoscape), MEMBER_SIZEOF(aircraft_t, ufoInterestOnGeoscape)},
01447 
01448     {"image", V_CLIENT_HUNK_STRING, offsetof(aircraft_t, image), 0},
01449     {"model", V_CLIENT_HUNK_STRING, offsetof(aircraft_t, model), 0},
01450     /* price for selling/buying */
01451     {"price", V_INT, offsetof(aircraft_t, price), MEMBER_SIZEOF(aircraft_t, price)},
01452     /* this is needed to let the buy and sell screen look for the needed building */
01453     /* to place the aircraft in */
01454     {"building", V_CLIENT_HUNK_STRING, offsetof(aircraft_t, building), 0},
01455 
01456     {NULL, 0, 0, 0}
01457 };
01458 
01468 void AIR_ParseAircraft (const char *name, const char **text, qboolean assignAircraftItems)
01469 {
01470     const char *errhead = "AIR_ParseAircraft: unexpected end of file (aircraft ";
01471     aircraft_t *aircraftTemplate;
01472     const value_t *vp;
01473     const char *token;
01474     int i;
01475     technology_t *tech;
01476     aircraftItemType_t itemType = MAX_ACITEMS;
01477 
01478     if (ccs.numAircraftTemplates >= MAX_AIRCRAFT) {
01479         Com_Printf("AIR_ParseAircraft: too many aircraft definitions; def \"%s\" ignored\n", name);
01480         return;
01481     }
01482 
01483     if (!assignAircraftItems) {
01484         for (i = 0; i < ccs.numAircraftTemplates; i++) {
01485             if (!strcmp(ccs.aircraftTemplates[i].id, name)) {
01486                 Com_Printf("AIR_ParseAircraft: Second aircraft with same name found (%s) - second ignored\n", name);
01487                 return;
01488             }
01489         }
01490 
01491         /* initialize the menu */
01492         aircraftTemplate = &ccs.aircraftTemplates[ccs.numAircraftTemplates];
01493         memset(aircraftTemplate, 0, sizeof(*aircraftTemplate));
01494 
01495         Com_DPrintf(DEBUG_CLIENT, "...found aircraft %s\n", name);
01496         aircraftTemplate->tpl = aircraftTemplate;
01497         aircraftTemplate->id = Mem_PoolStrDup(name, cl_genericPool, 0);
01498         aircraftTemplate->status = AIR_HOME;
01499         /* default is no ufo */
01500         aircraftTemplate->ufotype = UFO_MAX;
01501         AII_InitialiseAircraftSlots(aircraftTemplate);
01502         /* Initialise UFO sensored */
01503         RADAR_InitialiseUFOs(&aircraftTemplate->radar);
01504 
01505         ccs.numAircraftTemplates++;
01506     } else {
01507         aircraftTemplate = AIR_GetAircraft(name);
01508         /* initialize slot numbers (useful when restarting a single campaign) */
01509         aircraftTemplate->maxWeapons = 0;
01510         aircraftTemplate->maxElectronics = 0;
01511 
01512         if (aircraftTemplate->type == AIRCRAFT_UFO)
01513             aircraftTemplate->ufotype = Com_UFOShortNameToID(aircraftTemplate->id);
01514     }
01515 
01516     /* get it's body */
01517     token = Com_Parse(text);
01518 
01519     if (!*text || *token != '{') {
01520         Com_Printf("AIR_ParseAircraft: aircraft def \"%s\" without body ignored\n", name);
01521         return;
01522     }
01523 
01524     do {
01525         token = Com_EParse(text, errhead, name);
01526         if (!*text)
01527             break;
01528         if (*token == '}')
01529             break;
01530 
01531         if (!strcmp(token, "name")) {
01532             token = Com_EParse(text, errhead, name);
01533             if (!*text)
01534                 return;
01535             if (token[0] == '_')
01536                 token++;
01537             Q_strncpyz(aircraftTemplate->name, token, sizeof(aircraftTemplate->name));
01538             continue;
01539         }
01540         if (assignAircraftItems) {
01541             /* write into cp_campaignPool - this data is reparsed on every new game */
01542             /* blocks like param { [..] } - otherwise we would leave the loop too early */
01543             if (*token == '{') {
01544                 FS_SkipBlock(text);
01545             } else if (!strcmp(token, "shield")) {
01546                 token = Com_EParse(text, errhead, name);
01547                 if (!*text)
01548                     return;
01549                 Com_DPrintf(DEBUG_CLIENT, "use shield %s for aircraft %s\n", token, aircraftTemplate->id);
01550                 tech = RS_GetTechByID(token);
01551                 if (tech)
01552                     aircraftTemplate->shield.item = INVSH_GetItemByID(tech->provides);
01553             } else if (!strcmp(token, "slot")) {
01554                 token = Com_EParse(text, errhead, name);
01555                 if (!*text || *token != '{') {
01556                     Com_Printf("AIR_ParseAircraft: Invalid slot value for aircraft: %s\n", name);
01557                     return;
01558                 }
01559                 do {
01560                     token = Com_EParse(text, errhead, name);
01561                     if (!*text)
01562                         break;
01563                     if (*token == '}')
01564                         break;
01565 
01566                     if (!strcmp(token, "type")) {
01567                         token = Com_EParse(text, errhead, name);
01568                         if (!*text)
01569                             return;
01570                         for (i = 0; i < MAX_ACITEMS; i++) {
01571                             if (!strcmp(token, air_slot_type_strings[i])) {
01572                                 itemType = i;
01573                                 switch (itemType) {
01574                                 case AC_ITEM_WEAPON:
01575                                     aircraftTemplate->maxWeapons++;
01576                                     break;
01577                                 case AC_ITEM_ELECTRONICS:
01578                                     aircraftTemplate->maxElectronics++;
01579                                     break;
01580                                 default:
01581                                     itemType = MAX_ACITEMS;
01582                                 }
01583                                 break;
01584                             }
01585                         }
01586                         if (i == MAX_ACITEMS)
01587                             Com_Error(ERR_DROP, "Unknown value '%s' for slot type\n", token);
01588                     } else if (!strcmp(token, "position")) {
01589                         token = Com_EParse(text, errhead, name);
01590                         if (!*text)
01591                             return;
01592                         for (i = 0; i < AIR_POSITIONS_MAX; i++) {
01593                             if (!strcmp(token, air_position_strings[i])) {
01594                                 switch (itemType) {
01595                                 case AC_ITEM_WEAPON:
01596                                     aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].pos = i;
01597                                     break;
01598                                 case AC_ITEM_ELECTRONICS:
01599                                     aircraftTemplate->electronics[aircraftTemplate->maxElectronics - 1].pos = i;
01600                                     break;
01601                                 default:
01602                                     i = AIR_POSITIONS_MAX;
01603                                 }
01604                                 break;
01605                             }
01606                         }
01607                         if (i == AIR_POSITIONS_MAX)
01608                             Com_Error(ERR_DROP, "Unknown value '%s' for slot position\n", token);
01609                     } else if (!strcmp(token, "contains")) {
01610                         token = Com_EParse(text, errhead, name);
01611                         if (!*text)
01612                             return;
01613                         tech = RS_GetTechByID(token);
01614                         if (tech) {
01615                             switch (itemType) {
01616                             case AC_ITEM_WEAPON:
01617                                 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].item = INVSH_GetItemByID(tech->provides);
01618                                 Com_DPrintf(DEBUG_CLIENT, "use weapon %s for aircraft %s\n", token, aircraftTemplate->id);
01619                                 break;
01620                             case AC_ITEM_ELECTRONICS:
01621                                 aircraftTemplate->electronics[aircraftTemplate->maxElectronics - 1].item = INVSH_GetItemByID(tech->provides);
01622                                 Com_DPrintf(DEBUG_CLIENT, "use electronics %s for aircraft %s\n", token, aircraftTemplate->id);
01623                                 break;
01624                             default:
01625                                 Com_Printf("Ignoring item value '%s' due to unknown slot type\n", token);
01626                             }
01627                         }
01628                     } else if (!strcmp(token, "ammo")) {
01629                         token = Com_EParse(text, errhead, name);
01630                         if (!*text)
01631                             return;
01632                         tech = RS_GetTechByID(token);
01633                         if (tech) {
01634                             if (itemType == AC_ITEM_WEAPON) {
01635                                 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].ammo = INVSH_GetItemByID(tech->provides);
01636                                 Com_DPrintf(DEBUG_CLIENT, "use ammo %s for aircraft %s\n", token, aircraftTemplate->id);
01637                             } else
01638                                 Com_Printf("Ignoring ammo value '%s' due to unknown slot type\n", token);
01639                         }
01640                     } else if (!strcmp(token, "size")) {
01641                         token = Com_EParse(text, errhead, name);
01642                         if (!*text)
01643                             return;
01644                         if (itemType == AC_ITEM_WEAPON) {
01645                             if (!strcmp(token, "light"))
01646                                 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_LIGHT;
01647                             else if (!strcmp(token, "medium"))
01648                                 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_MEDIUM;
01649                             else if (!strcmp(token, "heavy"))
01650                                 aircraftTemplate->weapons[aircraftTemplate->maxWeapons - 1].size = ITEM_HEAVY;
01651                             else
01652                                 Com_Printf("Unknown size value for aircraft slot: '%s'\n", token);
01653                         } else
01654                             Com_Printf("Ignoring size parameter '%s' for non-weapon aircraft slots\n", token);
01655                     } else
01656                         Com_Printf("AIR_ParseAircraft: Ignoring unknown slot value '%s'\n", token);
01657                 } while (*text); /* dummy condition */
01658             }
01659         } else {
01660             if (!strcmp(token, "shield")) {
01661                 Com_EParse(text, errhead, name);
01662                 continue;
01663             }
01664             /* check for some standard values */
01665             for (vp = aircraft_vals; vp->string; vp++)
01666                 if (!strcmp(token, vp->string)) {
01667                     /* found a definition */
01668                     token = Com_EParse(text, errhead, name);
01669                     if (!*text)
01670                         return;
01671                     switch (vp->type) {
01672                     case V_TRANSLATION_STRING:
01673                         token++;
01674                     case V_CLIENT_HUNK_STRING:
01675                         Mem_PoolStrDupTo(token, (char**) ((char*)aircraftTemplate + (int)vp->ofs), cl_genericPool, 0);
01676                         break;
01677                     default:
01678                         Com_EParseValue(aircraftTemplate, token, vp->type, vp->ofs, vp->size);
01679                     }
01680 
01681                     break;
01682                 }
01683 
01684             if (!strcmp(token, "type")) {
01685                 token = Com_EParse(text, errhead, name);
01686                 if (!*text)
01687                     return;
01688                 if (!strcmp(token, "transporter"))
01689                     aircraftTemplate->type = AIRCRAFT_TRANSPORTER;
01690                 else if (!strcmp(token, "interceptor"))
01691                     aircraftTemplate->type = AIRCRAFT_INTERCEPTOR;
01692                 else if (!strcmp(token, "ufo"))
01693                     aircraftTemplate->type = AIRCRAFT_UFO;
01694             } else if (!strcmp(token, "slot")) {
01695                 token = Com_EParse(text, errhead, name);
01696                 if (!*text || *token != '{') {
01697                     Com_Printf("AIR_ParseAircraft: Invalid slot value for aircraft: %s\n", name);
01698                     return;
01699                 }
01700                 FS_SkipBlock(text);
01701             } else if (!strcmp(token, "param")) {
01702                 token = Com_EParse(text, errhead, name);
01703                 if (!*text || *token != '{') {
01704                     Com_Printf("AIR_ParseAircraft: Invalid param value for aircraft: %s\n", name);
01705                     return;
01706                 }
01707                 do {
01708                     token = Com_EParse(text, errhead, name);
01709                     if (!*text)
01710                         break;
01711                     if (*token == '}')
01712                         break;
01713 
01714                     if (!strcmp(token, "range")) {
01715                         /* this is the range of aircraft, must be translated into fuel */
01716                         token = Com_EParse(text, errhead, name);
01717                         if (!*text)
01718                             return;
01719                         Com_EParseValue(aircraftTemplate, token, V_INT, offsetof(aircraft_t, stats[AIR_STATS_FUELSIZE]), MEMBER_SIZEOF(aircraft_t, stats[0]));
01720                         if (aircraftTemplate->stats[AIR_STATS_SPEED] == 0)
01721                             Com_Error(ERR_DROP, "AIR_ParseAircraft: speed value must be entered before range value");
01722                         aircraftTemplate->stats[AIR_STATS_FUELSIZE] = (int) (2.0f * (float)SECONDS_PER_HOUR * aircraftTemplate->stats[AIR_STATS_FUELSIZE]) /
01723                             ((float) aircraftTemplate->stats[AIR_STATS_SPEED]);
01724                     } else {
01725                         for (vp = aircraft_param_vals; vp->string; vp++)
01726                             if (!strcmp(token, vp->string)) {
01727                                 /* found a definition */
01728                                 token = Com_EParse(text, errhead, name);
01729                                 if (!*text)
01730                                     return;
01731                                 switch (vp->type) {
01732                                 case V_TRANSLATION_STRING:
01733                                     token++;
01734                                 case V_CLIENT_HUNK_STRING:
01735                                     Mem_PoolStrDupTo(token, (char**) ((char*)aircraftTemplate + (int)vp->ofs), cl_genericPool, 0);
01736                                     break;
01737                                 default:
01738                                     Com_EParseValue(aircraftTemplate, token, vp->type, vp->ofs, vp->size);
01739                                 }
01740                                 break;
01741                             }
01742                     }
01743                     if (!vp->string)
01744                         Com_Printf("AIR_ParseAircraft: Ignoring unknown param value '%s'\n", token);
01745                 } while (*text); /* dummy condition */
01746             } else if (!vp->string) {
01747                 Com_Printf("AIR_ParseAircraft: unknown token \"%s\" ignored (aircraft %s)\n", token, name);
01748                 Com_EParse(text, errhead, name);
01749             }
01750         } /* assignAircraftItems */
01751     } while (*text);
01752 
01753     if (aircraftTemplate->size < AIRCRAFT_SMALL || aircraftTemplate->size > AIRCRAFT_LARGE)
01754         Sys_Error("Invalid aircraft size given for '%s'", aircraftTemplate->id);
01755 }
01756 
01757 #ifdef DEBUG
01758 void AIR_ListCraftIndexes_f (void)
01759 {
01760     int i;
01761 
01762     Com_Printf("Base\tlocalIDX\t(Craftname)\n");
01763     for (i = 0; i < ccs.numBases; i++) {
01764         const base_t *base = B_GetBaseByIDX(i);
01765         aircraft_t *aircraft;
01766 
01767         aircraft = NULL;
01768         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01769             Com_Printf("%i (%s)\t%i\t(%s)\n", i, base->name, aircraft->idx, aircraft->name);
01770         }
01771     }
01772 }
01773 
01777 void AIR_ListAircraftSamples_f (void)
01778 {
01779     int i = 0, max = ccs.numAircraftTemplates;
01780     const value_t *vp;
01781 
01782     Com_Printf("%i aircraft\n", max);
01783     if (Cmd_Argc() == 2) {
01784         max = atoi(Cmd_Argv(1));
01785         if (max >= ccs.numAircraftTemplates || max < 0)
01786             return;
01787         i = max - 1;
01788     }
01789     for (; i < max; i++) {
01790         aircraft_t *aircraftTemplate = &ccs.aircraftTemplates[i];
01791         Com_Printf("aircraft: '%s'\n", aircraftTemplate->id);
01792         for (vp = aircraft_vals; vp->string; vp++) {
01793             Com_Printf("..%s: %s\n", vp->string, Com_ValueToStr(aircraftTemplate, vp->type, vp->ofs));
01794         }
01795         for (vp = aircraft_param_vals; vp->string; vp++) {
01796             Com_Printf("..%s: %s\n", vp->string, Com_ValueToStr(aircraftTemplate, vp->type, vp->ofs));
01797         }
01798     }
01799 }
01800 #endif
01801 
01802 /*===============================================
01803 Aircraft functions related to UFOs or missions.
01804 ===============================================*/
01805 
01810 void AIR_AircraftsNotifyMissionRemoved (const mission_t *const mission)
01811 {
01812     int baseIdx;
01813     aircraft_t* aircraft;
01814 
01815     /* Aircraft currently moving to the mission will be redirect to base */
01816     for (baseIdx = 0; baseIdx < ccs.numBases; baseIdx++) {
01817         base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01818 
01819         aircraft = NULL;
01820         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01821             if (aircraft->status == AIR_MISSION && aircraft->mission == mission)
01822                 AIR_AircraftReturnToBase(aircraft);
01823         }
01824     }
01825 }
01826 
01832 void AIR_AircraftsNotifyUFORemoved (const aircraft_t *const ufo, qboolean destroyed)
01833 {
01834     int baseIdx;
01835     aircraft_t* aircraft;
01836     int i;
01837 
01838     assert(ufo);
01839 
01840     for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01841         base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01842         if (!base)
01843             continue;
01844 
01845         /* Base currently targeting the specified ufo loose their target */
01846         for (i = 0; i < base->numBatteries; i++) {
01847             if (base->batteries[i].target == ufo)
01848                 base->batteries[i].target = NULL;
01849             else if (destroyed && (base->batteries[i].target > ufo))
01850                 base->batteries[i].target--;
01851         }
01852         for (i = 0; i < base->numLasers; i++) {
01853             if (base->lasers[i].target == ufo)
01854                 base->lasers[i].target = NULL;
01855             else if (destroyed && (base->lasers[i].target > ufo))
01856                 base->lasers[i].target--;
01857         }
01858         /* Aircraft currently purchasing the specified ufo will be redirect to base */
01859         aircraft = NULL;
01860         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL)
01861             if (aircraft->status == AIR_UFO) {
01862                 if (ufo == aircraft->aircraftTarget)
01863                     AIR_AircraftReturnToBase(aircraft);
01864                 else if (destroyed && (ufo < aircraft->aircraftTarget)) {
01865                     aircraft->aircraftTarget--;
01866                 }
01867             }
01868     }
01869 }
01870 
01875 void AIR_AircraftsUFODisappear (const aircraft_t *const ufo)
01876 {
01877     int baseIdx;
01878 
01879     /* Aircraft currently pursuing the specified UFO will be redirected to base */
01880     for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01881         const base_t *base = B_GetBaseByIDX(baseIdx);
01882         aircraft_t *aircraft;
01883 
01884         aircraft = NULL;
01885         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01886             if (aircraft->status == AIR_UFO)
01887                 if (ufo == aircraft->aircraftTarget)
01888                     AIR_AircraftReturnToBase(aircraft);
01889         }
01890     }
01891 }
01892 
01903 static inline float AIR_GetDestinationFunction (const float c, const float B, const float speedRatio, float a)
01904 {
01905     return pow(cos(a) - cos(speedRatio * a) * cos(c), 2.)
01906         - sin(c) * sin(c) * (sin(speedRatio * a) * sin(speedRatio * a) - sin(a) * sin(a) * sin(B) * sin(B));
01907 }
01908 
01919 static inline float AIR_GetDestinationDerivativeFunction (const float c, const float B, const float speedRatio, float a)
01920 {
01921     return 2. * (cos(a) - cos(speedRatio * a) * cos(c)) * (- sin(a) + speedRatio * sin(speedRatio * a) * cos(c))
01922         - sin(c) * sin(c) * (speedRatio * sin(2. * speedRatio * a) - sin(2. * a) * sin(B) * sin(B));
01923 }
01924 
01935 static float AIR_GetDestinationFindRoot (const float c, const float B, const float speedRatio, float start)
01936 {
01937     const float BIG_STEP = .05;             
01939     const float PRECISION_ROOT = 0.000001;      
01940     const float MAXIMUM_VALUE_ROOT = 2. * M_PI; 
01941     float epsilon;                          
01942     float begin, end, middle;               
01943     float fBegin, fEnd, fMiddle;            
01944     float fdBegin, fdEnd, fdMiddle;         
01946     /* there may be several solution, first try to find roughly the smallest one */
01947     end = start + PRECISION_ROOT / 10.;     /* don't start at 0: derivative is 0 */
01948     fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
01949     fdEnd = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, end);
01950 
01951     do {
01952         begin = end;
01953         fBegin = fEnd;
01954         fdBegin = fdEnd;
01955         end = begin + BIG_STEP;
01956         if (end > MAXIMUM_VALUE_ROOT) {
01957             end = MAXIMUM_VALUE_ROOT;
01958             fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
01959             break;
01960         }
01961         fEnd = AIR_GetDestinationFunction(c, B, speedRatio, end);
01962         fdEnd = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, end);
01963     } while  (fBegin * fEnd > 0 && fdBegin * fdEnd > 0);
01964 
01965     if (fBegin * fEnd > 0) {
01966         if (fdBegin * fdEnd < 0) {
01967             /* the sign of derivative changed: we could have a root somewhere
01968              * between begin and end: try to narrow down the root until fBegin * fEnd < 0 */
01969             middle = (begin + end) / 2.;
01970             fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
01971             fdMiddle = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, middle);
01972             do {
01973                 if (fdEnd * fdMiddle < 0) {
01974                     /* root is bigger than middle */
01975                     begin = middle;
01976                     fBegin = fMiddle;
01977                     fdBegin = fdMiddle;
01978                 } else if (fdBegin * fdMiddle < 0) {
01979                     /* root is smaller than middle */
01980                     end = middle;
01981                     fEnd = fMiddle;
01982                     fdEnd = fdMiddle;
01983                 } else {
01984                     Com_Error(ERR_DROP, "AIR_GetDestinationFindRoot: Error in calculation, can't find root");
01985                 }
01986                 middle = (begin + end) / 2.;
01987                 fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
01988                 fdMiddle = AIR_GetDestinationDerivativeFunction(c, B, speedRatio, middle);
01989 
01990                 epsilon = end - middle ;
01991 
01992                 if (epsilon < PRECISION_ROOT) {
01993                     /* this is only a root of the derivative: no root of the function itself
01994                      * proceed with next value */
01995                     return AIR_GetDestinationFindRoot(c, B, speedRatio, end);
01996                 }
01997             } while  (fBegin * fEnd > 0);
01998         } else {
01999             /* there's no solution, return default value */
02000             Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationFindRoot: Did not find solution is range %.2f, %.2f\n", start, MAXIMUM_VALUE_ROOT);
02001             return -10.;
02002         }
02003     }
02004 
02005     /* now use dichotomy to get more precision on the solution */
02006 
02007     middle = (begin + end) / 2.;
02008     fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
02009 
02010     do {
02011         if (fEnd * fMiddle < 0) {
02012             /* root is bigger than middle */
02013             begin = middle;
02014             fBegin = fMiddle;
02015         } else if (fBegin * fMiddle < 0) {
02016             /* root is smaller than middle */
02017             end = middle;
02018             fEnd = fMiddle;
02019         } else {
02020             Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationFindRoot: Error in calculation, one of the value is nan\n");
02021             return -10.;
02022         }
02023         middle = (begin + end) / 2.;
02024         fMiddle = AIR_GetDestinationFunction(c, B, speedRatio, middle);
02025 
02026         epsilon = end - middle ;
02027     } while (epsilon > PRECISION_ROOT);
02028     return middle;
02029 }
02030 
02040 void AIR_GetDestinationWhilePursuing (const aircraft_t const *shooter, const aircraft_t const *target, vec2_t *dest)
02041 {
02042     vec3_t shooterPos, targetPos, targetDestPos, shooterDestPos, rotationAxis;
02043     vec3_t tangentVectTS, tangentVectTD;
02044     float a, b, c, B;
02045 
02046     const float speedRatio = (float)(shooter->stats[AIR_STATS_SPEED]) / target->stats[AIR_STATS_SPEED];
02047 
02048     c = GetDistanceOnGlobe(shooter->pos, target->pos) * torad;
02049 
02050     /* Convert aircraft position into cartesian frame */
02051     PolarToVec(shooter->pos, shooterPos);
02052     PolarToVec(target->pos, targetPos);
02053     PolarToVec(target->route.point[target->route.numPoints - 1], targetDestPos);
02054 
02072     /* Get first vector (tangent to triangle in T, in the direction of D) */
02073     CrossProduct(targetPos, shooterPos, rotationAxis);
02074     VectorNormalize(rotationAxis);
02075     RotatePointAroundVector(tangentVectTS, rotationAxis, targetPos, 90.0f);
02076     /* Get second vector (tangent to triangle in T, in the direction of S) */
02077     CrossProduct(targetPos, targetDestPos, rotationAxis);
02078     VectorNormalize(rotationAxis);
02079     RotatePointAroundVector(tangentVectTD, rotationAxis, targetPos, 90.0f);
02080 
02081     /* Get angle B of the triangle (in radian) */
02082     B = acos(DotProduct(tangentVectTS, tangentVectTD));
02083 
02084     /* Look for a value, as long as we don't have a proper value */
02085     for (a = 0;;) {
02086         a = AIR_GetDestinationFindRoot(c, B, speedRatio, a);
02087 
02088         if (a < 0.) {
02089             /* we couldn't find a root on the whole range */
02090             break;
02091         }
02092 
02093         /* Get rotation vector */
02094         CrossProduct(targetPos, targetDestPos, rotationAxis);
02095         VectorNormalize(rotationAxis);
02096 
02097         /* Rotate target position of dist to find destination point */
02098         RotatePointAroundVector(shooterDestPos, rotationAxis, targetPos, a * todeg);
02099         VecToPolar(shooterDestPos, *dest);
02100 
02101         b = GetDistanceOnGlobe(shooter->pos, *dest) * torad;
02102 
02103         if (fabs(b - speedRatio * a) < .1)
02104             break;
02105 
02106         Com_DPrintf(DEBUG_CLIENT, "AIR_GetDestinationWhilePursuing: reject solution: doesn't fit %.2f == %.2f\n", b, speedRatio * a);
02107     }
02108 
02109     if (a < 0.) {
02110         /* did not find solution, go directly to target direction */
02111         Vector2Copy(target->pos, (*dest));
02112         return;
02113     }
02114 
02116     /* make sure we don't get a NAN value */
02117     assert((*dest)[0] <= 180.0f && (*dest)[0] >= -180.0f && (*dest)[1] <= 90.0f && (*dest)[1] >= -90.0f);
02118 }
02119 
02125 qboolean AIR_SendAircraftPursuingUFO (aircraft_t* aircraft, aircraft_t* ufo)
02126 {
02127     const int num = ufo - ccs.ufos;
02128     vec2_t dest;
02129 
02130     if (num < 0 || num >= ccs.numUFOs || !aircraft || !ufo)
02131         return qfalse;
02132 
02133     /* if aircraft was in base */
02134     if (AIR_IsAircraftInBase(aircraft)) {
02135         /* reload its ammunition */
02136         AII_ReloadAircraftWeapons(aircraft);
02137     }
02138 
02139     AIR_GetDestinationWhilePursuing(aircraft, ufo, &dest);
02140     /* check if aircraft has enough fuel */
02141     if (!AIR_AircraftHasEnoughFuel(aircraft, dest)) {
02142         /* did not find solution, go directly to target direction if enough fuel */
02143         if (AIR_AircraftHasEnoughFuel(aircraft, ufo->pos)) {
02144             Com_DPrintf(DEBUG_CLIENT, "AIR_SendAircraftPursuingUFO: not enough fuel to anticipate target movement: go directly to target position\n");
02145             Vector2Copy(ufo->pos, dest);
02146         } else {
02147             MS_AddNewMessage(_("Notice"), va(_("Craft %s has not enough fuel to intercept UFO: fly back to %s."), aircraft->name, aircraft->homebase->name), qfalse, MSG_STANDARD, NULL);
02148             AIR_AircraftReturnToBase(aircraft);
02149             return qfalse;
02150         }
02151     }
02152 
02153     MAP_MapCalcLine(aircraft->pos, dest, &aircraft->route);
02154     aircraft->status = AIR_UFO;
02155     aircraft->time = 0;
02156     aircraft->point = 0;
02157     aircraft->aircraftTarget = ufo;
02158     return qtrue;
02159 }
02160 
02161 /*============================================
02162 Aircraft functions related to team handling.
02163 ============================================*/
02164 
02169 void AIR_ResetAircraftTeam (aircraft_t *aircraft)
02170 {
02171     LIST_Delete(&aircraft->acTeam);
02172 }
02173 
02180 qboolean AIR_AddToAircraftTeam (aircraft_t *aircraft, employee_t* employee)
02181 {
02182     if (!employee) {
02183         Com_DPrintf(DEBUG_CLIENT, "AIR_AddToAircraftTeam: No employee given!\n");
02184         return qfalse;
02185     }
02186 
02187     if (!aircraft) {
02188         Com_DPrintf(DEBUG_CLIENT, "AIR_AddToAircraftTeam: No aircraft given!\n");
02189         return qfalse;
02190     }
02191     if (AIR_GetTeamSize(aircraft) < aircraft->maxTeamSize) {
02192         LIST_AddPointer(&aircraft->acTeam, employee);
02193         Com_DPrintf(DEBUG_CLIENT, "AIR_AddToAircraftTeam: added idx '%d'\n",
02194             employee->idx);
02195         return qtrue;
02196     }
02197 
02198     Com_DPrintf(DEBUG_CLIENT, "AIR_AddToAircraftTeam: No space in aircraft\n");
02199     return qfalse;
02200 }
02201 
02208 qboolean AIR_RemoveFromAircraftTeam (aircraft_t *aircraft, const employee_t *employee)
02209 {
02210     linkedList_t* l;
02211 
02212     assert(aircraft);
02213     assert(employee);
02214 
02215     if (AIR_GetTeamSize(aircraft) == 0)
02216         return qfalse;
02217 
02218     for (l = aircraft->acTeam; l != NULL; l = l->next) {
02219         const employee_t const *employeeInCraft = (const employee_t *)l->data;
02220         /* Search for this exact employee in the aircraft and remove him from the team. */
02221         if (employeeInCraft == employee) {
02222             LIST_RemoveEntry(&aircraft->acTeam, l);
02223             Com_DPrintf(DEBUG_CLIENT, "AIR_RemoveFromAircraftTeam: removed idx '%d' \n",
02224                 employee->idx);
02225             return qtrue;
02226         }
02227     }
02228     /* there must be a homebase when there are employees - otherwise this
02229      * functions should not be called */
02230     assert(aircraft->homebase);
02231     Com_Printf("AIR_RemoveFromAircraftTeam: error: idx '%d' (type: %i) not on aircraft %i (size: %i) (base: %i) in base %i\n",
02232         employee->idx, employee->type, aircraft->idx, aircraft->maxTeamSize,
02233         AIR_GetAircraftIDXInBase(aircraft), aircraft->homebase->idx);
02234     return qfalse;
02235 }
02236 
02243 qboolean AIR_IsInAircraftTeam (const aircraft_t *aircraft, const employee_t *employee)
02244 {
02245     if (!aircraft) {
02246         Com_DPrintf(DEBUG_CLIENT, "AIR_IsInAircraftTeam: No aircraft given\n");
02247         return qfalse;
02248     }
02249 
02250     if (!employee) {
02251         Com_Printf("AIR_IsInAircraftTeam: No employee given.\n");
02252         return qfalse;
02253     }
02254 
02255     if (AIR_GetTeamSize(aircraft) == 0)
02256         return qfalse;
02257 
02258     return LIST_GetPointer(aircraft->acTeam, employee) != NULL;
02259 }
02260 
02266 int AIR_GetTeamSize (const aircraft_t *aircraft)
02267 {
02268     assert(aircraft);
02269     return LIST_Count(aircraft->acTeam);
02270 }
02271 
02277 void AIR_AutoAddPilotToAircraft (base_t* base, employee_t* pilot)
02278 {
02279     aircraft_t *aircraft;
02280 
02281     aircraft = NULL;
02282     while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
02283         if (!aircraft->pilot) {
02284             aircraft->pilot = pilot;
02285             break;
02286         }
02287     }
02288 }
02289 
02296 void AIR_RemovePilotFromAssignedAircraft (base_t* base, const employee_t* pilot)
02297 {
02298     aircraft_t *aircraft;
02299 
02300     aircraft = NULL;
02301     while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
02302         if (aircraft->pilot == pilot) {
02303             aircraft->pilot = NULL;
02304             break;
02305         }
02306     }
02307 }
02308 
02316 int AIR_GetAircraftWeaponRanges (const aircraftSlot_t *slot, int maxSlot, float *weaponRanges)
02317 {
02318     int idxSlot;
02319     int idxAllWeap;
02320     float allWeaponRanges[MAX_AIRCRAFTSLOT];
02321     int numAllWeaponRanges = 0;
02322     int numUniqueWeaponRanges = 0;
02323 
02324     assert(slot);
02325 
02326     /* We choose the usable weapon to add to the weapons array */
02327     for (idxSlot = 0; idxSlot < maxSlot; idxSlot++) {
02328         const aircraftSlot_t *weapon = slot + idxSlot;
02329         const objDef_t *ammo = weapon->ammo;
02330 
02331         if (!ammo)
02332             continue;
02333 
02334         allWeaponRanges[numAllWeaponRanges] = ammo->craftitem.stats[AIR_STATS_WRANGE];
02335         numAllWeaponRanges++;
02336     }
02337 
02338     if (numAllWeaponRanges > 0) {
02339         /* sort the list of all weapon ranges and create an array with only the unique ranges */
02340         qsort(allWeaponRanges, numAllWeaponRanges, sizeof(allWeaponRanges[0]), Q_FloatSort);
02341 
02342         for (idxAllWeap = 0; idxAllWeap < numAllWeaponRanges; idxAllWeap++) {
02343             if (allWeaponRanges[idxAllWeap] != weaponRanges[numUniqueWeaponRanges - 1] || idxAllWeap == 0) {
02344                 weaponRanges[numUniqueWeaponRanges] = allWeaponRanges[idxAllWeap];
02345                 numUniqueWeaponRanges++;
02346             }
02347         }
02348     }
02349 
02350     return numUniqueWeaponRanges;
02351 }
02352 
02358 static void AIR_SaveRouteXML (mxml_node_t *node, const mapline_t *route)
02359 {
02360     int j;
02361     mxml_node_t *subnode;
02362 
02363     subnode = mxml_AddNode(node, SAVE_AIRCRAFT_ROUTE);
02364     mxml_AddFloatValue(subnode, SAVE_AIRCRAFT_ROUTE_DISTANCE, route->distance);
02365     for (j = 0; j < route->numPoints; j++) {
02366         mxml_AddPos2(subnode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[j]);
02367     }
02368 }
02369 
02380 static void AIR_SaveAircraftSlotsXML (const aircraftSlot_t* slot, const int num, mxml_node_t *p, qboolean weapon)
02381 {
02382     int i;
02383     mxml_node_t *sub;
02384 
02385     for (i = 0; i < num; i++) {
02386         sub = mxml_AddNode(p, SAVE_AIRCRAFT_SLOT);
02387         AII_SaveOneSlotXML(sub, &slot[i], weapon);
02388     }
02389 }
02390 
02397 static qboolean AIR_SaveAircraftXML (mxml_node_t *p, const aircraft_t* const aircraft, qboolean const isUfo)
02398 {
02399     mxml_node_t *node;
02400     mxml_node_t *subnode;
02401     int l;
02402     linkedList_t *k;
02403 
02404     Com_RegisterConstList(saveAircraftConstants);
02405 
02406     node = mxml_AddNode(p, SAVE_AIRCRAFT_AIRCRAFT);
02407 
02408     mxml_AddString(node, SAVE_AIRCRAFT_ID, aircraft->id);
02409     mxml_AddString(node, SAVE_AIRCRAFT_NAME, aircraft->name);
02410 
02411     mxml_AddString(node, SAVE_AIRCRAFT_STATUS, Com_GetConstVariable(SAVE_AIRCRAFTSTATUS_NAMESPACE, aircraft->status));
02412     mxml_AddInt(node, SAVE_AIRCRAFT_FUEL, aircraft->fuel);
02413     mxml_AddInt(node, SAVE_AIRCRAFT_DAMAGE, aircraft->damage);
02414     mxml_AddPos3(node, SAVE_AIRCRAFT_POS, aircraft->pos);
02415     mxml_AddPos3(node, SAVE_AIRCRAFT_DIRECTION, aircraft->direction);
02416     mxml_AddInt(node, SAVE_AIRCRAFT_POINT, aircraft->point);
02417     mxml_AddInt(node, SAVE_AIRCRAFT_TIME, aircraft->time);
02418 
02419     subnode = mxml_AddNode(node, SAVE_AIRCRAFT_WEAPONS);
02420     AIR_SaveAircraftSlotsXML(aircraft->weapons, aircraft->maxWeapons, subnode, qtrue);
02421     subnode = mxml_AddNode(node, SAVE_AIRCRAFT_SHIELDS);
02422     AIR_SaveAircraftSlotsXML(&aircraft->shield, 1, subnode, qfalse);
02423     subnode = mxml_AddNode(node, SAVE_AIRCRAFT_ELECTRONICS);
02424     AIR_SaveAircraftSlotsXML(aircraft->electronics, aircraft->maxElectronics, subnode, qfalse);
02425 
02426     AIR_SaveRouteXML(node, &aircraft->route);
02427 
02428     if (isUfo) {
02429 #ifdef DEBUG
02430         if (!aircraft->mission)
02431             Com_Printf("Error: UFO '%s'is not linked to any mission\n", aircraft->id);
02432 #endif
02433         mxml_AddString(node, SAVE_AIRCRAFT_MISSIONID, aircraft->mission->id);
02435         mxml_AddInt(node, SAVE_AIRCRAFT_DETECTIONIDX, aircraft->detectionIdx);
02436         mxml_AddDate(node, SAVE_AIRCRAFT_LASTSPOTTED_DATE, aircraft->lastSpotted.day, aircraft->lastSpotted.sec);
02437     } else {
02438         if (aircraft->status == AIR_MISSION) {
02439             assert(aircraft->mission);
02440             mxml_AddString(node, SAVE_AIRCRAFT_MISSIONID, aircraft->mission->id);
02441         }
02442         if (aircraft->homebase) {
02443             mxml_AddInt(node, SAVE_AIRCRAFT_HOMEBASE, aircraft->homebase->idx);
02444         }
02445     }
02446 
02447     if (aircraft->aircraftTarget) {
02448         if (isUfo)
02449             mxml_AddInt(node, SAVE_AIRCRAFT_AIRCRAFTTARGET, aircraft->aircraftTarget->idx);
02450         else
02451             mxml_AddInt(node, SAVE_AIRCRAFT_AIRCRAFTTARGET, aircraft->aircraftTarget - ccs.ufos);
02452     }
02453 
02454     subnode = mxml_AddNode(node, SAVE_AIRCRAFT_AIRSTATS);
02455     for (l = 0; l < AIR_STATS_MAX; l++) {
02456         mxml_node_t *statNode;
02457 #ifdef DEBUG
02458         /* UFO HP can be < 0 if the UFO has been destroyed */
02459         if (!(isUfo && l == AIR_STATS_DAMAGE) && aircraft->stats[l] < 0)
02460             Com_Printf("Warning: ufo '%s' stats %i: %i is smaller than 0\n", aircraft->id, l, aircraft->stats[l]);
02461 #endif
02462         if (aircraft->stats[l] != 0) {
02463             statNode = mxml_AddNode(subnode, SAVE_AIRCRAFT_AIRSTAT);
02464             mxml_AddString(statNode, SAVE_AIRCRAFT_AIRSTATID, Com_GetConstVariable(SAVE_AIRCRAFTSTAT_NAMESPACE, l));
02465             mxml_AddLong(statNode, SAVE_AIRCRAFT_VAL, aircraft->stats[l]);
02466         }
02467     }
02468 
02469     mxml_AddBoolValue(node, SAVE_AIRCRAFT_DETECTED, aircraft->detected);
02470     mxml_AddBoolValue(node, SAVE_AIRCRAFT_LANDED, aircraft->landed);
02471 
02472     Com_UnregisterConstList(saveAircraftConstants);
02473 
02474     /* All other informations are not needed for ufos */
02475     if (isUfo)
02476         return qtrue;
02477 
02478     mxml_AddInt(node, SAVE_AIRCRAFT_IDX, aircraft->idx);
02479 
02480     mxml_AddIntValue(node, SAVE_AIRCRAFT_RADAR_RANGE, aircraft->radar.range);
02481     mxml_AddIntValue(node, SAVE_AIRCRAFT_RADAR_TRACKINGRANGE, aircraft->radar.trackingRange);
02482     mxml_AddInt(node, SAVE_AIRCRAFT_HANGAR, aircraft->hangar);
02483 
02484     subnode = mxml_AddNode(node, SAVE_AIRCRAFT_AIRCRAFTTEAM);
02485 
02486     for (k = aircraft->acTeam; k != NULL; k = k->next) {
02487         const employee_t const *employee = (const employee_t *)k->data;
02488         mxml_node_t *ssnode = mxml_AddNode(subnode, SAVE_AIRCRAFT_MEMBER);
02489         mxml_AddInt(ssnode, SAVE_AIRCRAFT_TEAM_UCN, employee->chr.ucn);
02490     }
02491 
02492     if (aircraft->pilot)
02493         mxml_AddInt(node, SAVE_AIRCRAFT_PILOTUCN, aircraft->pilot->chr.ucn);
02494 
02495     /* itemcargo */
02496     subnode = mxml_AddNode(node, SAVE_AIRCRAFT_CARGO);
02497     for (l = 0; l < aircraft->itemTypes; l++) {
02498         mxml_node_t *ssnode = mxml_AddNode(subnode, SAVE_AIRCRAFT_ITEM);
02499         assert(aircraft->itemcargo[l].item);
02500         mxml_AddString(ssnode, SAVE_AIRCRAFT_ITEMID, aircraft->itemcargo[l].item->id);
02501         mxml_AddInt(ssnode, SAVE_AIRCRAFT_AMOUNT, aircraft->itemcargo[l].amount);
02502     }
02503 
02504     /* aliencargo */
02505     {
02506         const int alienCargoTypes = AL_GetAircraftAlienCargoTypes(aircraft);
02507         const aliensTmp_t *cargo  = AL_GetAircraftAlienCargo(aircraft);
02508         subnode = mxml_AddNode(node, SAVE_AIRCRAFT_ALIENCARGO);
02509         for (l = 0; l < alienCargoTypes; l++) {
02510             mxml_node_t *ssnode = mxml_AddNode(subnode, SAVE_AIRCRAFT_CARGO);
02511             assert(cargo[l].teamDef);
02512             mxml_AddString(ssnode, SAVE_AIRCRAFT_TEAMDEFID, cargo[l].teamDef->id);
02513             mxml_AddIntValue(ssnode, SAVE_AIRCRAFT_ALIVE, cargo[l].amountAlive);
02514             mxml_AddIntValue(ssnode, SAVE_AIRCRAFT_DEAD, cargo[l].amountDead);
02515         }
02516     }
02517 
02518     return qtrue;
02519 }
02520 
02527 qboolean AIR_SaveXML (mxml_node_t *parent)
02528 {
02529     int i;
02530     mxml_node_t * node, *snode;
02531 
02532     /* save phalanx aircraft */
02533     snode = mxml_AddNode(parent, SAVE_AIRCRAFT_PHALANX);
02534     for (i = 0; i < MAX_BASES; i++) {
02535         base_t *base = B_GetFoundedBaseByIDX(i);
02536         aircraft_t *aircraft;
02537 
02538         if (!base)
02539             continue;
02540 
02541         aircraft = NULL;
02542         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL)
02543             AIR_SaveAircraftXML(snode, aircraft, qfalse);
02544     }
02545 
02546     /* save the ufos on geoscape */
02547     snode = mxml_AddNode(parent, SAVE_AIRCRAFT_UFOS);
02548     for (i = 0; i < MAX_UFOONGEOSCAPE; i++) {
02549         aircraft_t *ufo = UFO_GetByIDX(i);
02550         if (!ufo || (ufo->id == NULL))
02551             continue;
02552         AIR_SaveAircraftXML(snode, ufo, qtrue);
02553     }
02554 
02555     /* Save projectiles. */
02556     node = mxml_AddNode(parent, SAVE_AIRCRAFT_PROJECTILES);
02557     if (!AIRFIGHT_SaveXML(node))
02558         return qfalse;
02559 
02560     return qtrue;
02561 }
02562 
02573 static void AIR_LoadAircraftSlotsXML (aircraft_t *aircraft, aircraftSlot_t* slot, mxml_node_t *p, qboolean weapon, const int max)
02574 {
02575     mxml_node_t *act;
02576     int i;
02577     for (i = 0, act = mxml_GetNode(p, SAVE_AIRCRAFT_SLOT); act && i <= max; act = mxml_GetNextNode(act, p, SAVE_AIRCRAFT_SLOT), i++) {
02578         slot[i].aircraft = aircraft;
02579         AII_LoadOneSlotXML(act, &slot[i], weapon);
02580     }
02581     if (i > max)
02582         Com_Printf("Error: Trying to assign more than max (%d) Aircraft Slots (cur is %d)\n", max, i);
02583 
02584 }
02585 
02591 static qboolean AIR_LoadRouteXML (mxml_node_t *p, mapline_t *route)
02592 {
02593     mxml_node_t *actual;
02594     mxml_node_t *snode;
02595     int count = 0;
02596 
02597     snode = mxml_GetNode(p, SAVE_AIRCRAFT_ROUTE);
02598     if (!snode)
02599         return qfalse;
02600 
02601     for (actual = mxml_GetPos2(snode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[count]); actual && count <= LINE_MAXPTS;
02602             actual = mxml_GetNextPos2(actual, snode, SAVE_AIRCRAFT_ROUTE_POINT, route->point[++count]))
02603         ;
02604     if (count > LINE_MAXPTS) {
02605         Com_Printf("AIR_Load: number of points (%i) for UFO route exceed maximum value (%i)\n", count, LINE_MAXPTS);
02606         return qfalse;
02607     }
02608     route->numPoints = count;
02609     route->distance = mxml_GetFloat(snode, SAVE_AIRCRAFT_ROUTE_DISTANCE, 0.0);
02610     return qtrue;
02611 }
02612 
02618 static qboolean AIR_LoadAircraftXML (mxml_node_t *p, aircraft_t *craft)
02619 {
02620     mxml_node_t *snode;
02621     mxml_node_t *ssnode;
02622     const char *statusId;
02623     /* vars, if aircraft wasn't found */
02624     int tmpInt;
02625     int l, status;
02626     const char *s = mxml_GetString(p, SAVE_AIRCRAFT_ID);
02627     const aircraft_t *crafttype = AIR_GetAircraft(s);
02628 
02629     /* Copy all datas that don't need to be saved (tpl, hangar,...) */
02630     *craft = *crafttype;
02631 
02632     tmpInt = mxml_GetInt(p, SAVE_AIRCRAFT_HOMEBASE, MAX_BASES);
02633     craft->homebase = (tmpInt != MAX_BASES) ? B_GetBaseByIDX(tmpInt) : NULL;
02634 
02635     Com_RegisterConstList(saveAircraftConstants);
02636 
02637     statusId = mxml_GetString(p, SAVE_AIRCRAFT_STATUS);
02638     if (!Com_GetConstIntFromNamespace(SAVE_AIRCRAFTSTATUS_NAMESPACE, statusId, &status)) {
02639         Com_Printf("Invalid aircraft status '%s'\n", statusId);
02640         Com_UnregisterConstList(saveAircraftConstants);
02641         return qfalse;
02642     }
02643 
02644     craft->status = status;
02645     craft->fuel = mxml_GetInt(p, SAVE_AIRCRAFT_FUEL, 0);
02646     craft->damage = mxml_GetInt(p, SAVE_AIRCRAFT_DAMAGE, 0);
02647     mxml_GetPos3(p, SAVE_AIRCRAFT_POS, craft->pos);
02648 
02649     mxml_GetPos3(p, SAVE_AIRCRAFT_DIRECTION, craft->direction);
02650     craft->point = mxml_GetInt(p, SAVE_AIRCRAFT_POINT, 0);
02651     craft->time = mxml_GetInt(p, SAVE_AIRCRAFT_TIME, 0);
02652 
02653     if (!AIR_LoadRouteXML(p, &craft->route)) {
02654         Com_UnregisterConstList(saveAircraftConstants);
02655         return qfalse;
02656     }
02657 
02658     s = mxml_GetString(p, SAVE_AIRCRAFT_NAME);
02659     if (s[0] == '\0')
02660         s = _(craft->defaultName);
02661     Q_strncpyz(craft->name, s, sizeof(craft->name));
02662 
02663     s = mxml_GetString(p, SAVE_AIRCRAFT_MISSIONID);
02664     if (s[0] == '\0' && !craft->homebase) {
02665         Com_Printf("Error: UFO '%s' is not linked to any mission\n", craft->id);
02666         Com_UnregisterConstList(saveAircraftConstants);
02667         return qfalse;
02668     }
02669     craft->missionID = Mem_PoolStrDup(s, cp_campaignPool, 0);
02670 
02671     if (!craft->homebase) {
02672         craft->idx = ccs.numUFOs;
02673         /* detection id and time */
02674         craft->detectionIdx = mxml_GetInt(p, SAVE_AIRCRAFT_DETECTIONIDX, 0);
02675         mxml_GetDate(p, SAVE_AIRCRAFT_LASTSPOTTED_DATE, &craft->lastSpotted.day, &craft->lastSpotted.sec);
02676     }
02677 
02678     snode = mxml_GetNode(p, SAVE_AIRCRAFT_AIRSTATS);
02679     for (ssnode = mxml_GetNode(snode, SAVE_AIRCRAFT_AIRSTAT); ssnode; ssnode = mxml_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRSTAT)) {
02680         const char *statId =  mxml_GetString(ssnode, SAVE_AIRCRAFT_AIRSTATID);
02681         int idx;
02682 
02683         if (!Com_GetConstIntFromNamespace(SAVE_AIRCRAFTSTAT_NAMESPACE, statId, &idx)) {
02684             Com_Printf("Invaild aircraft stat '%s'\n", statId);
02685             Com_UnregisterConstList(saveAircraftConstants);
02686             return qfalse;
02687         }
02688         craft->stats[idx] = mxml_GetLong(ssnode, SAVE_AIRCRAFT_VAL, 0);
02689 #ifdef DEBUG
02690         /* UFO HP can be < 0 if the UFO has been destroyed */
02691         if (!(!craft->homebase && idx == AIR_STATS_DAMAGE) && craft->stats[idx] < 0)
02692             Com_Printf("Warning: ufo '%s' stats %i: %i is smaller than 0\n", craft->id, idx, craft->stats[idx]);
02693 #endif
02694     }
02695 
02696     craft->detected = mxml_GetBool(p, SAVE_AIRCRAFT_DETECTED, qfalse);
02697     craft->landed = mxml_GetBool(p, SAVE_AIRCRAFT_LANDED, qfalse);
02698 
02699     tmpInt = mxml_GetInt(p, SAVE_AIRCRAFT_AIRCRAFTTARGET, -1);
02700     if (tmpInt == -1)
02701         craft->aircraftTarget = NULL;
02702     else if (!craft->homebase)
02703         craft->aircraftTarget = AIR_AircraftGetFromIDX(tmpInt);
02704     else
02705         craft->aircraftTarget = ccs.ufos + tmpInt;
02706 
02707     /* read equipment slots */
02708     snode = mxml_GetNode(p, SAVE_AIRCRAFT_WEAPONS);
02709     AIR_LoadAircraftSlotsXML(craft, craft->weapons, snode, qtrue, craft->maxWeapons);
02710     snode = mxml_GetNode(p, SAVE_AIRCRAFT_SHIELDS);
02711     AIR_LoadAircraftSlotsXML(craft, &craft->shield, snode, qfalse, 1);
02712     snode = mxml_GetNode(p, SAVE_AIRCRAFT_ELECTRONICS);
02713     AIR_LoadAircraftSlotsXML(craft, craft->electronics, snode, qfalse, craft->maxElectronics);
02714 
02715     Com_UnregisterConstList(saveAircraftConstants);
02716 
02717     /* All other informations are not needed for ufos */
02718     if (!craft->homebase)
02719         return qtrue;
02720 
02721     craft->idx = mxml_GetInt(p, SAVE_AIRCRAFT_IDX, -1);
02722     if (craft->idx == -1)
02723         return qfalse;
02724     craft->hangar = mxml_GetInt(p, SAVE_AIRCRAFT_HANGAR, 0);
02725 
02726     snode = mxml_GetNode(p, SAVE_AIRCRAFT_AIRCRAFTTEAM);
02727     for (ssnode = mxml_GetNode(snode, SAVE_AIRCRAFT_MEMBER); AIR_GetTeamSize(craft) < craft->maxTeamSize && ssnode;
02728             ssnode = mxml_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_MEMBER)) {
02729         const int ucn = mxml_GetInt(ssnode, SAVE_AIRCRAFT_TEAM_UCN, -1);
02730         if (ucn != -1)
02731             LIST_AddPointer(&craft->acTeam, E_GetEmployeeFromChrUCN(ucn));
02732     }
02733 
02734     tmpInt = mxml_GetInt(p, SAVE_AIRCRAFT_PILOTUCN, -1);
02735     /* the employee subsystem is loaded after the base subsystem
02736      * this means, that the pilot pointer is not (really) valid until
02737      * E_Load was called, too */
02738     if (tmpInt != -1)
02739         craft->pilot = E_GetEmployeeFromChrUCN(tmpInt);
02740     else
02741         craft->pilot = NULL;
02742 
02743     RADAR_InitialiseUFOs(&craft->radar);
02744     craft->radar.range = mxml_GetInt(p, SAVE_AIRCRAFT_RADAR_RANGE, 0);
02745     craft->radar.trackingRange = mxml_GetInt(p, SAVE_AIRCRAFT_RADAR_TRACKINGRANGE, 0);
02746 
02747     /* itemcargo */
02748     snode = mxml_GetNode(p, SAVE_AIRCRAFT_CARGO);
02749     for (l = 0, ssnode = mxml_GetNode(snode, SAVE_AIRCRAFT_ITEM); l < MAX_CARGO && ssnode;
02750             l++, ssnode = mxml_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_ITEM)) {
02751         const char *const str = mxml_GetString(ssnode, SAVE_AIRCRAFT_ITEMID);
02752         const objDef_t *od = INVSH_GetItemByID(str);
02753 
02754         if (!od) {
02755             Com_Printf("AIR_LoadAircraftXML: Could not find aircraftitem '%s'\n", str);
02756             l--;
02757             continue;
02758         }
02759 
02760         craft->itemcargo[l].item = od;
02761         craft->itemcargo[l].amount = mxml_GetInt(ssnode, SAVE_AIRCRAFT_AMOUNT, 0);
02762     }
02763     craft->itemTypes = l;
02764 
02765     /* aliencargo */
02766     snode = mxml_GetNode(p, SAVE_AIRCRAFT_ALIENCARGO);
02767     for (l = 0, ssnode = mxml_GetNode(snode, SAVE_AIRCRAFT_CARGO); l < MAX_CARGO && ssnode;
02768             l++, ssnode = mxml_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_CARGO)) {
02769         aliensTmp_t *cargo = AL_GetAircraftAlienCargo(craft);
02770         const char *const str = mxml_GetString(ssnode, SAVE_AIRCRAFT_TEAMDEFID);
02771 
02772         cargo[l].teamDef = Com_GetTeamDefinitionByID(str);
02773         if (!cargo[l].teamDef) {
02774             Com_Printf("AIR_LoadAircraftXML: Could not find teamDef '%s'\n", str);
02775             l--;
02776             continue;
02777         }
02778 
02779         cargo[l].amountAlive = mxml_GetInt(ssnode, SAVE_AIRCRAFT_ALIVE, 0);
02780         cargo[l].amountDead  =  mxml_GetInt(ssnode, SAVE_AIRCRAFT_DEAD, 0);
02781     }
02782     AL_SetAircraftAlienCargoTypes(craft, l);
02783 
02784     return qtrue;
02785 }
02786 
02791 static void AIR_CorrectAircraftSlotPointers (aircraft_t *aircraft)
02792 {
02793     int i;
02794 
02795     assert(aircraft);
02796 
02797     for (i = 0; i < aircraft->maxWeapons; i++) {
02798         aircraft->weapons[i].aircraft = aircraft;
02799         aircraft->weapons[i].base = NULL;
02800         aircraft->weapons[i].installation = NULL;
02801     }
02802     for (i = 0; i < aircraft->maxElectronics; i++) {
02803         aircraft->electronics[i].aircraft = aircraft;
02804         aircraft->electronics[i].base = NULL;
02805         aircraft->electronics[i].installation = NULL;
02806     }
02807     aircraft->shield.aircraft = aircraft;
02808     aircraft->shield.base = NULL;
02809     aircraft->shield.installation = NULL;
02810 }
02811 
02812 qboolean AIR_LoadXML (mxml_node_t *parent)
02813 {
02814     mxml_node_t *snode, *ssnode;
02815     mxml_node_t *projectiles;
02816     int i;
02817 
02818     /* load phalanx aircraft */
02819     snode = mxml_GetNode(parent, SAVE_AIRCRAFT_PHALANX);
02820     for (i = 0, ssnode = mxml_GetNode(snode, SAVE_AIRCRAFT_AIRCRAFT); ssnode;
02821             ssnode = mxml_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRCRAFT), i++) {
02822         aircraft_t craft;
02823         if (!AIR_LoadAircraftXML(ssnode, &craft))
02824             return qfalse;
02825         assert(craft.homebase);
02826         AIR_CorrectAircraftSlotPointers(AIR_Add(craft.homebase, &craft));
02827     }
02828     ccs.numAircraft = i;
02829 
02830     /* load the ufos on geoscape */
02831     snode = mxml_GetNode(parent, SAVE_AIRCRAFT_UFOS);
02832 
02833     for (i = 0, ssnode = mxml_GetNode(snode, SAVE_AIRCRAFT_AIRCRAFT); i < MAX_UFOONGEOSCAPE && ssnode;
02834             ssnode = mxml_GetNextNode(ssnode, snode, SAVE_AIRCRAFT_AIRCRAFT), i++) {
02835         if (!AIR_LoadAircraftXML(ssnode, &ccs.ufos[i]))
02836             return qfalse;
02837         ccs.numUFOs++;
02838     }
02839 
02840     /* Load projectiles. */
02841     projectiles = mxml_GetNode(parent, SAVE_AIRCRAFT_PROJECTILES);
02842     if (!AIRFIGHT_LoadXML(projectiles))
02843         return qfalse;
02844 
02845     /* check UFOs */
02846     for (i = ccs.numUFOs - 1; i >= 0; i--) {
02847         aircraft_t *ufo = UFO_GetByIDX(i);
02848         if (ufo->time < 0 || ufo->stats[AIR_STATS_SPEED] <= 0) {
02849             Com_Printf("AIR_Load: Found invalid ufo entry - remove it - time: %i - speed: %i\n",
02850                     ufo->time, ufo->stats[AIR_STATS_SPEED]);
02851             UFO_RemoveFromGeoscape(ufo);
02852         }
02853     }
02854 
02855     return qtrue;
02856 }
02857 
02861 static void AIR_PostLoadInitMissions (void)
02862 {
02863     int i;
02864 
02865     /* PHALANX aircraft */
02866     for (i = 0; i < ccs.numBases; i++) {
02867         const base_t *base = B_GetFoundedBaseByIDX(i);
02868         aircraft_t *aircraft = NULL;
02869 
02870         while ((aircraft = AIR_GetNextFromBase(base, aircraft))) {
02871             if (!aircraft->missionID || aircraft->missionID[0] == '\0')
02872                 continue;
02873             aircraft->mission = CP_GetMissionByID(aircraft->missionID);
02874             Mem_Free(aircraft->missionID);
02875             aircraft->missionID = NULL;
02876         }
02877     }
02878 
02879     /* UFOs */
02880     for (i = 0; i < ccs.numUFOs; i++) {
02881         aircraft_t *aircraft = UFO_GetByIDX(i);
02882 
02883         if (!aircraft || !aircraft->missionID || aircraft->missionID[0] == '\0')
02884             continue;
02885         aircraft->mission = CP_GetMissionByID(aircraft->missionID);
02886         Mem_Free(aircraft->missionID);
02887         aircraft->missionID = NULL;
02888     }
02889 }
02890 
02895 void AIR_PostLoadInit (void)
02896 {
02897     AIR_PostLoadInitMissions();
02898 }
02899 
02905 qboolean AIR_AircraftAllowed (const base_t* base)
02906 {
02907     return B_GetBuildingStatus(base, B_HANGAR) || B_GetBuildingStatus(base, B_SMALL_HANGAR);
02908 }
02909 
02914 qboolean AIR_CanIntercept (const aircraft_t *aircraft)
02915 {
02916     /* if dependencies of hangar are missing, you can't send aircraft */
02917     if (aircraft->size == AIRCRAFT_SMALL && !B_GetBuildingStatus(aircraft->homebase, B_SMALL_HANGAR))
02918         return qfalse;
02919     if (aircraft->size == AIRCRAFT_LARGE && !B_GetBuildingStatus(aircraft->homebase, B_HANGAR))
02920         return qfalse;
02921 
02922     /* we need a pilot to intercept */
02923     if (!aircraft->pilot)
02924         return qfalse;
02925 
02926     return qtrue;
02927 }
02928 
02933 qboolean AIR_ScriptSanityCheck (void)
02934 {
02935     int i, j, k, error = 0;
02936     int var;
02937     aircraft_t* a;
02938 
02939     for (i = 0, a = ccs.aircraftTemplates; i < ccs.numAircraftTemplates; i++, a++) {
02940         if (a->name[0] == '\0') {
02941             error++;
02942             Com_Printf("...... aircraft '%s' has no name\n", a->id);
02943         }
02944         if (!a->defaultName) {
02945             error++;
02946             Com_Printf("...... aircraft '%s' has no defaultName\n", a->id);
02947         }
02948 
02949         /* check that every weapons fits slot */
02950         for (j = 0; j < a->maxWeapons - 1; j++)
02951             if (a->weapons[j].item && AII_GetItemWeightBySize(a->weapons[j].item) > a->weapons[j].size) {
02952                 error++;
02953                 Com_Printf("...... aircraft '%s' has an item (%s) too heavy for its slot\n", a->id, a->weapons[j].item->id);
02954             }
02955 
02956         /* check that every slots has a different location for PHALANX aircraft (not needed for UFOs) */
02957         if (a->type != AIRCRAFT_UFO) {
02958             for (j = 0; j < a->maxWeapons - 1; j++) {
02959                 var = a->weapons[j].pos;
02960                 for (k = j + 1; k < a->maxWeapons; k++)
02961                     if (var == a->weapons[k].pos) {
02962                         error++;
02963                         Com_Printf("...... aircraft '%s' has 2 weapons slots at the same location\n", a->id);
02964                     }
02965             }
02966             for (j = 0; j < a->maxElectronics - 1; j++) {
02967                 var = a->electronics[j].pos;
02968                 for (k = j + 1; k < a->maxElectronics; k++)
02969                     if (var == a->electronics[k].pos) {
02970                         error++;
02971                         Com_Printf("...... aircraft '%s' has 2 electronics slots at the same location\n", a->id);
02972                     }
02973             }
02974         }
02975     }
02976 
02977     return !error;
02978 }
02979 
02988 int AIR_CalculateHangarStorage (const aircraft_t *aircraftTemplate, const base_t *base, int used)
02989 {
02990     assert(base);
02991     assert(aircraftTemplate);
02992     assert(aircraftTemplate == aircraftTemplate->tpl);  /* Make sure it's an aircraft template. */
02993 
02994     if (!base->founded)
02995         return -1;
02996     else {
02997         const int aircraftCapacity = AIR_GetCapacityByAircraftWeight(aircraftTemplate);
02998         /* If this is a small aircraft, we will check space in small hangar.
02999          * If this is a large aircraft, we will check space in big hangar. */
03000         const int freespace = base->capacities[aircraftCapacity].max - base->capacities[aircraftCapacity].cur - used;
03001         return max(freespace, 0);
03002     }
03003 }
03004 
03012 qboolean AIR_RemoveEmployee (employee_t *employee, aircraft_t *aircraft)
03013 {
03014     if (!employee)
03015         return qfalse;
03016 
03017     /* If no aircraft is given we search if he is in _any_ aircraft and set
03018      * the aircraft pointer to it. */
03019     if (!aircraft) {
03020         int i;
03021 
03022         for (i = 0; i < ccs.numBases; i++) {
03023             base_t *base = B_GetFoundedBaseByIDX(i);
03024             aircraft_t *acTemp = NULL;
03025 
03026             if (!base)
03027                 continue;
03028 
03029             while ((acTemp = AIR_GetNextFromBase(base, acTemp))) { 
03030                 if (AIR_IsEmployeeInAircraft(employee, acTemp)) {
03031                     aircraft = acTemp;
03032                     break;
03033                 }
03034             }
03035             if (aircraft)
03036                 break;
03037         }
03038         if (!aircraft)
03039             return qfalse;
03040     }
03041 
03042     Com_DPrintf(DEBUG_CLIENT, "AIR_RemoveEmployee: base: %i - aircraft->idx: %i\n",
03043         aircraft->homebase ? aircraft->homebase->idx : -1, aircraft->idx);
03044 
03045     cls.i.DestroyInventory(&cls.i, &employee->chr.i);
03046     return AIR_RemoveFromAircraftTeam(aircraft, employee);
03047 }
03048 
03056 const aircraft_t *AIR_IsEmployeeInAircraft (const employee_t *employee, const aircraft_t* aircraft)
03057 {
03058     if (!employee)
03059         return NULL;
03060 
03061     if (employee->transfer)
03062         return NULL;
03063 
03064     /* If no aircraft is given we search if he is in _any_ aircraft and return true if that's the case. */
03065     if (!aircraft) {
03066         int i;
03067         for (i = 0; i < ccs.numBases; i++) {
03068             base_t *base = B_GetFoundedBaseByIDX(i);
03069             aircraft_t *aircraftByIDX = NULL;
03070 
03071             if (!base)
03072                 continue;
03073         
03074             while ((aircraftByIDX = AIR_GetNextFromBase(base, aircraftByIDX))) {
03075                 if (AIR_IsEmployeeInAircraft(employee, aircraftByIDX))
03076                     return aircraftByIDX;
03077             }
03078         }
03079         return NULL;
03080     }
03081 
03082     if (employee->type == EMPL_PILOT) {
03083         if (aircraft->pilot == employee)
03084             return aircraft;
03085         return NULL;
03086     }
03087 
03088     if (AIR_IsInAircraftTeam(aircraft, employee))
03089         return aircraft;
03090     else
03091         return NULL;
03092 }
03093 
03099 void AIR_RemoveEmployees (aircraft_t *aircraft)
03100 {
03101     linkedList_t* l;
03102 
03103     if (!aircraft)
03104         return;
03105 
03106     for (l = aircraft->acTeam; l != NULL;) {
03107         employee_t *employee = (employee_t *)l->data;
03108         l = l->next;
03109         /* use global aircraft index here */
03110         AIR_RemoveEmployee(employee, aircraft);
03111     }
03112 
03113     /* Remove pilot */
03114     aircraft->pilot = NULL;
03115 
03116     if (AIR_GetTeamSize(aircraft) > 0)
03117         Com_Error(ERR_DROP, "AIR_RemoveEmployees: Error, there went something wrong with soldier-removing from aircraft.");
03118 }
03119 
03120 
03126 void AIR_MoveEmployeeInventoryIntoStorage (const aircraft_t *aircraft, equipDef_t *ed)
03127 {
03128     containerIndex_t container;
03129     linkedList_t *l;
03130 
03131     if (!aircraft) {
03132         Com_Printf("AIR_MoveEmployeeInventoryIntoStorage: Warning: Called with no aircraft (and thus no carried equipment to add).\n");
03133         return;
03134     }
03135     if (!ed) {
03136         Com_Printf("AIR_MoveEmployeeInventoryIntoStorage: Warning: Called with no equipment definition at add stuff to.\n");
03137         return;
03138     }
03139 
03140     if (AIR_GetTeamSize(aircraft) == 0) {
03141         Com_DPrintf(DEBUG_CLIENT, "AIR_MoveEmployeeInventoryIntoStorage: No team to remove equipment from.\n");
03142         return;
03143     }
03144 
03145     for (container = 0; container < csi.numIDs; container++) {
03146         for (l = aircraft->acTeam; l != NULL; l = l->next) {
03147             employee_t *employee = (employee_t *)l->data;
03148             if (employee != NULL) {
03149                 character_t *chr = &employee->chr;
03150                 invList_t *ic = CONTAINER(chr, container);
03151 #if 0
03152                 if (INVDEF(container)->temp)
03153                     continue;
03154 #endif
03155                 while (ic) {
03156                     const item_t item = ic->item;
03157                     const objDef_t *type = item.t;
03158                     invList_t *next = ic->next;
03159 
03160                     ed->numItems[type->idx]++;
03161                     if (item.a) {
03162                         assert(type->reload);
03163                         assert(item.m);
03164                         ed->numItemsLoose[item.m->idx] += item.a;
03165                         /* Accumulate loose ammo into clips */
03166                         if (ed->numItemsLoose[item.m->idx] >= type->ammo) {
03167                             ed->numItemsLoose[item.m->idx] -= type->ammo;
03168                             ed->numItems[item.m->idx]++;
03169                         }
03170                     }
03171                     ic = next;
03172                 }
03173             }
03174         }
03175     }
03176 }
03177 
03186 static qboolean AIR_AddEmployee (employee_t *employee, aircraft_t *aircraft)
03187 {
03188     if (!employee || !aircraft)
03189         return qfalse;
03190 
03191     if (AIR_GetTeamSize(aircraft) < aircraft->maxTeamSize) {
03192         /* Check whether the soldier is already on another aircraft */
03193         if (AIR_IsEmployeeInAircraft(employee, NULL))
03194             return qfalse;
03195 
03196         /* Assign the soldier to the aircraft. */
03197         return AIR_AddToAircraftTeam(aircraft, employee);
03198     }
03199     return qfalse;
03200 }
03201 
03208 void AIM_AddEmployeeFromMenu (aircraft_t *aircraft, const int num)
03209 {
03210     employee_t *employee;
03211 
03212     Com_DPrintf(DEBUG_CLIENT, "AIM_AddEmployeeFromMenu: Trying to get employee with hired-idx %i.\n", num);
03213 
03214     /* If this fails it's very likely that employeeList is not filled. */
03215     employee = E_GetEmployeeByMenuIndex(num);
03216     if (!employee)
03217         Com_Error(ERR_DROP, "AIM_AddEmployeeFromMenu: Could not get employee %i", num);
03218 
03219     Com_DPrintf(DEBUG_CLIENT, "AIM_AddEmployeeFromMenu: employee with idx %i selected\n", employee->idx);
03220 
03221     assert(aircraft);
03222 
03223     if (AIR_IsEmployeeInAircraft(employee, aircraft)) {
03224         /* use the global aircraft index here */
03225         AIR_RemoveEmployee(employee, aircraft);
03226     } else {
03227         /* Assign soldier to aircraft/team if aircraft is not full */
03228         AIR_AddEmployee(employee, aircraft);
03229     }
03230 }
03231 
03236 void AIR_AssignInitial (aircraft_t *aircraft)
03237 {
03238     int i, num;
03239     base_t *base;
03240 
03241     if (!aircraft) {
03242         Com_Printf("AIR_AssignInitial: No aircraft given\n");
03243         return;
03244     }
03245 
03246     base = aircraft->homebase;
03247     assert(base);
03248 
03249     num = min(E_GenerateHiredEmployeesList(base), aircraft->maxTeamSize);
03250     for (i = 0; i < num; i++)
03251         AIM_AddEmployeeFromMenu(aircraft, i);
03252 }
03253 

Generated by  doxygen 1.6.2