/*
 * See Licensing and Copyright notice in naev.h
 */


/**
 * @file pilot_outfit.c
 *
 * @brief Handles pilot outfits.
 */


#include "pilot.h"

#include "naev.h"

#include "nxml.h"

#include "log.h"
#include "player.h"
#include "space.h"
#include "gui.h"
#include "slots.h"
#include "nstring.h"


/*
 * Prototypes.
 */
static int pilot_hasOutfitLimit( Pilot *p, const char *limit );


/**
 * @brief Updates the lockons on the pilot's launchers
 *
 *    @param p Pilot being updated.
 *    @param o Slot being updated.
 *    @param t Pilot that is currently the target of p (or NULL if not applicable).
 *    @param a Angle to update if necessary. Should be initialized to -1 before the loop.
 *    @param dt Current delta tick.
 */
void pilot_lockUpdateSlot( Pilot *p, PilotOutfitSlot *o, Pilot *t, double *a, double dt )
{
   double max, old;
   double x,y, ang, arc;
   int locked;

   /* No target. */
   if (t == NULL)
      return;

   /* Nota  seeker. */
   if (!outfit_isSeeker(o->outfit))
      return;

   /* Check arc. */
   arc = o->outfit->u.lau.arc;
   if (arc > 0.) {

      /* We use an external variable to set and update the angle if necessary. */
      if (*a < 0.) {
         x     = t->solid->pos.x - p->solid->pos.x;
         y     = t->solid->pos.y - p->solid->pos.y;
         ang   = ANGLE( x, y );
         *a    = fabs( angle_diff( ang, p->solid->dir ) );
      }

      /* Decay if not in arc. */
      if (*a > arc) {
         /* Limit decay to the lockon time for this launcher. */
         max = o->outfit->u.lau.lockon;

         /* When a lock is lost, immediately gain half the lock timer.
          * This is meant as an incentive for the aggressor not to lose the lock,
          * and for the target to try and break the lock. */
         old = o->u.ammo.lockon_timer;
         o->u.ammo.lockon_timer += dt;
         if ((old <= 0.) && (o->u.ammo.lockon_timer > 0.))
            o->u.ammo.lockon_timer += o->outfit->u.lau.lockon / 2.;

         /* Cap at max. */
         if (o->u.ammo.lockon_timer > max)
            o->u.ammo.lockon_timer = max;

         /* Out of arc. */
         o->u.ammo.in_arc = 0;
         return;
      }
   }

   /* In arc. */
   o->u.ammo.in_arc = 1;
   locked = (o->u.ammo.lockon_timer < 0.);

   /* Lower timer. When the timer reaches zero, the lock is established. */
   max = -o->outfit->u.lau.lockon/3.;
   if (o->u.ammo.lockon_timer > max) {
      /* Compensate for enemy hide factor. */
      o->u.ammo.lockon_timer -= dt * (o->outfit->u.lau.ew_target2 / t->ew_hide);

      /* Cap at -max/3. */
      if (o->u.ammo.lockon_timer < max)
         o->u.ammo.lockon_timer = max;

      /* Trigger lockon hook. */
      if (!locked && (o->u.ammo.lockon_timer < 0.))
         pilot_runHook( p, PILOT_HOOK_LOCKON );
   }
}


/**
 * @brief Clears pilot's missile lockon timers.
 *
 *    @param p Pilot to clear missile lockon timers.
 */
void pilot_lockClear( Pilot *p )
{
   int i;
   PilotOutfitSlot *o;

   for (i=0; i<p->noutfits; i++) {
      o = p->outfits[i];
      if (o->outfit == NULL)
         continue;
      if (!outfit_isSeeker(o->outfit))
         continue;

      /* Clear timer. */
      o->u.ammo.lockon_timer = o->outfit->u.lau.lockon;

      /* Clear arc. */
      o->u.ammo.in_arc = 0;
   }
}


/**
 * @brief Gets the mount position of a pilot.
 *
 * Position is relative to the pilot.
 *
 *    @param p Pilot to get mount position of.
 *    @param id ID of the mount.
 *    @param[out] v Position of the mount.
 *    @return 0 on success.
 */
int pilot_getMount( const Pilot *p, const PilotOutfitSlot *w, Vector2d *v )
{
   double a, x, y;
   double cm, sm;
   const ShipMount *m;

   /* Calculate the sprite angle. */
   a  = (double)(p->tsy * p->ship->gfx_space->sx + p->tsx);
   a *= p->ship->mangle;

   /* 2d rotation matrix
    * [ x' ]   [  cos  sin  ]   [ x ]
    * [ y' ] = [ -sin  cos  ] * [ y ]
    *
    * dir is inverted so that rotation is counter-clockwise.
    */
   m = &w->sslot->mount;
   cm = cos(-a);
   sm = sin(-a);
   x = m->x * cm + m->y * sm;
   y = m->x *-sm + m->y * cm;

   /* Correction for ortho perspective. */
   y *= M_SQRT1_2;

   /* Don't forget to add height. */
   y += m->h;

   /* Get the mount and add the player.p offset. */
   vect_cset( v, x, y );

   return 0;
}


/**
 * @brief Docks the pilot on its target pilot.
 *
 *    @param p Pilot that wants to dock.
 *    @param target Pilot to dock on.
 *    @param deployed Was pilot already deployed?
 *    @return 0 on successful docking.
 */
int pilot_dock( Pilot *p, Pilot *target, int deployed )
{
   int i;
   Outfit *o = NULL;

   /* Must be close. */
   if (vect_dist(&p->solid->pos, &target->solid->pos) >
         target->ship->gfx_space->sw * PILOT_SIZE_APROX )
      return -1;

   /* Cannot be going much faster. */
   if ((pow2(VX(p->solid->vel)-VX(target->solid->vel)) +
            pow2(VY(p->solid->vel)-VY(target->solid->vel))) >
         (double)pow2(MAX_HYPERSPACE_VEL))
      return -1;

   /* Check to see if target has an available bay. */
   for (i=0; i<target->noutfits; i++) {

      /* Must have outfit. */
      if (target->outfits[i]->outfit == NULL)
         continue;

      /* Must be fighter bay. */
      if (!outfit_isFighterBay(target->outfits[i]->outfit))
         continue;

      /* Must have deployed. */
      if (deployed && (target->outfits[i]->u.ammo.deployed <= 0))
         continue;

      o = outfit_ammo(target->outfits[i]->outfit);

      /* Try to add fighter. */
      if (outfit_isFighter(o) &&
            (strcmp(p->ship->name,o->u.fig.ship)==0)) {
         if (deployed)
            target->outfits[i]->u.ammo.deployed -= 1;
         break;
      }
   }
   if ((o==NULL) || (i >= target->noutfits))
      return -1;

   /* Add the pilot's outfit. */
   if (pilot_addAmmo(target, target->outfits[i], o, 1) != 1)
      return -1;

   /* Remove from pilot's escort list. */
   if (deployed) {
      for (i=0; i<target->nescorts; i++) {
         if ((target->escorts[i].type == ESCORT_TYPE_BAY) &&
               (target->escorts[i].id == p->id))
            break;
      }
      /* Not found as pilot's escorts. */
      if (i >= target->nescorts)
         return -1;
      /* Free if last pilot. */
      if (target->nescorts == 1) {
         free(target->escorts);
         target->escorts   = NULL;
         target->nescorts  = 0;
      }
      else {
         memmove( &target->escorts[i], &target->escorts[i+1],
               sizeof(Escort_t) * (target->nescorts-i-1) );
         target->nescorts--;
      }
   }

   /* Destroy the pilot. */
   pilot_delete(p);

   return 0;
}


/**
 * @brief Checks to see if the pilot has deployed ships.
 *
 *    @param p Pilot to see if has deployed ships.
 *    @return 1 if pilot has deployed ships, 0 otherwise.
 */
int pilot_hasDeployed( Pilot *p )
{
   int i;
   for (i=0; i<p->noutfits; i++) {
      if (p->outfits[i]->outfit == NULL)
         continue;
      if (outfit_isFighterBay(p->outfits[i]->outfit))
         if (p->outfits[i]->u.ammo.deployed > 0)
            return 1;
   }
   return 0;
}


/**
 * @brief Adds an outfit to the pilot, ignoring CPU or other limits.
 *
 * @note Does not call pilot_calcStats().
 *
 *    @param pilot Pilot to add the outfit to.
 *    @param outfit Outfit to add to the pilot.
 *    @param s Slot to add ammo to.
 *    @return 0 on success.
 */
int pilot_addOutfitRaw( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s )
{
   Outfit *o;

   /* Set the outfit. */
   s->outfit   = outfit;

   /* Set some default parameters. */
   s->timer    = 0.;

   /* Some per-case scenarios. */
   if (outfit_isFighterBay(outfit)) {
      s->u.ammo.outfit   = NULL;
      s->u.ammo.quantity = 0;
      s->u.ammo.deployed = 0;
   }
   if (outfit_isTurret(outfit)) /* used to speed up AI */
      pilot->nturrets++;
   else if (outfit_isBolt(outfit))
      pilot->ncannons++;
   else if (outfit_isJammer(outfit))
      pilot->njammers++;
   else if (outfit_isAfterburner(outfit))
      pilot->nafterburners++;

   if (outfit_isBeam(outfit)) { /* Used to speed up some calculations. */
      s->u.beamid = 0;
      pilot->nbeams++;
   }
   if (outfit_isLauncher(outfit)) {
      s->u.ammo.outfit   = NULL;
      s->u.ammo.quantity = 0;
      s->u.ammo.deployed = 0; /* Just in case. */
   }

   /* Check if active. */
   o = s->outfit;
   s->active = outfit_isActive(o);

   /* Update heat. */
   pilot_heatCalcSlot( s );

   return 0;
}


/**
 * @brief Tests to see if an outfit can be added.
 *
 *    @param pilot Pilot to add outfit to.
 *    @param outfit Outfit to add.
 *    @param s Slot adding outfit to.
 *    @param warn Whether or not should generate a warning.
 *    @return 0 if can add, -1 if can't.
 */
int pilot_addOutfitTest( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s, int warn )
{
   const char *str;

   /* See if slot has space. */
   if (s->outfit != NULL) {
      if (warn)
         WARN( "Pilot '%s': trying to add outfit '%s' to slot that already has an outfit",
               pilot->name, outfit->name );
      return -1;
   }
   else if ((outfit_cpu(outfit) < 0) &&
         (pilot->cpu < ABS( outfit_cpu(outfit) ))) {
      if (warn)
         WARN( "Pilot '%s': Not enough CPU to add outfit '%s'",
               pilot->name, outfit->name );
      return -1;
   }
   else if ((str = pilot_canEquip( pilot, s, outfit)) != NULL) {
      if (warn)
         WARN( "Pilot '%s': Trying to add outfit but %s",
               pilot->name, str );
      return -1;
   }
   return 0;
}



/**
 * @brief Adds an outfit to the pilot.
 *
 *    @param pilot Pilot to add the outfit to.
 *    @param outfit Outfit to add to the pilot.
 *    @param s Slot to add ammo to.
 *    @return 0 on success.
 */
int pilot_addOutfit( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s )
{
   int ret;

   /* Test to see if outfit can be added. */
   ret = pilot_addOutfitTest( pilot, outfit, s, 1 );
   if (ret != 0)
      return -1;

   /* Add outfit. */
   ret = pilot_addOutfitRaw( pilot, outfit, s );

   /* Recalculate the stats */
   pilot_calcStats(pilot);

   return ret;
}


/**
 * @brief Removes an outfit from the pilot without doing any checks.
 *
 * @note Does not run pilot_calcStats().
 *
 *    @param pilot Pilot to remove the outfit from.
 *    @param s Slot to remove.
 *    @return 0 on success.
 */
int pilot_rmOutfitRaw( Pilot* pilot, PilotOutfitSlot *s )
{
   int ret;

   /* Decrement counters if necessary. */
   if (s->outfit != NULL) {
      if (outfit_isTurret(s->outfit))
         pilot->nturrets--;
      else if (outfit_isBolt(s->outfit))
         pilot->ncannons--;
      if (outfit_isBeam(s->outfit))
         pilot->nbeams--;
   }

   /* Remove the outfit. */
   ret         = (s->outfit==NULL);
   s->outfit   = NULL;

   /* Remove secondary and such if necessary. */
   if (pilot->afterburner == s)
      pilot->afterburner = NULL;

   return ret;
}


/**
 * @brief Removes an outfit from the pilot.
 *
 *    @param pilot Pilot to remove the outfit from.
 *    @param s Slot to remove.
 *    @return 0 on success.
 */
int pilot_rmOutfit( Pilot* pilot, PilotOutfitSlot *s )
{
   const char *str;
   int ret;

   str = pilot_canEquip( pilot, s, NULL );
   if (str != NULL) {
      WARN("Pilot '%s': Trying to remove outfit but %s",
            pilot->name, str );
      return -1;
   }

   ret = pilot_rmOutfitRaw( pilot, s );

   /* recalculate the stats */
   pilot_calcStats(pilot);

   return ret;
}
//TODO: fix comment to conform to Naev's style and represent changes
/**
 * @brief Pilot slot sanity check - makes sure stats are sane.
 *
 *    @param p Pilot to check.
 *    @return 0 if a slot doesn't fit, !0 otherwise.
 */
int pilot_slotsCheckSanity( Pilot *p )
{
   int i;
   for (i=0; i<p->noutfits; i++)
      if ((p->outfits[i]->outfit != NULL) &&
            !outfit_fitsSlot( p->outfits[i]->outfit, &p->outfits[i]->sslot->slot ))
         return 0;
   return 1;
}
//TODO: fix comment to conform to Naev's style and represent changes
/**
 * @brief Pilot required (core) slot filled check - makes sure they are filled.
 *
 *    @param p Pilot to check.
 *    @return 0 if a slot is missing, !0 otherwise.
 */
int pilot_slotsCheckRequired( Pilot *p )
{
   int i;

   for (i=0; i < p->outfit_nstructure; i++)
      if (p->outfit_structure[i].sslot->required && p->outfit_structure[i].outfit == NULL)
         return 0;

   for (i=0; i < p->outfit_nutility; i++)
      if (p->outfit_utility[i].sslot->required && p->outfit_utility[i].outfit == NULL)
         return 0;

   for (i=0; i < p->outfit_nweapon; i++)
      if (p->outfit_weapon[i].sslot->required && p->outfit_weapon[i].outfit == NULL)
         return 0;

   return 1;
}
//TODO: fix comment to conform to Naev's style and represent change
/**
 * @brief Pilot sanity check - makes sure stats are sane.
 *
 *    @param p Pilot to check.
 *    @return The reason why the pilot is not sane (or NULL if sane).
 */
const char* pilot_checkSpaceworthy( Pilot *p )
{
   if (!pilot_slotsCheckSanity(p))
      return "Doesn't fit slot";

   /* CPU. */
   if (p->cpu < 0)
      return "Insufficient CPU";

   /* Movement. */
   if (p->thrust < 0.)
      return "Insufficient Thrust";
   if (p->speed < 0.)
      return "Insufficient Speed";
   if (p->turn < 0.)
      return "Insufficient Turn";

   /* Health. */
   if (p->armour_max < 0.)
      return "Insufficient Armour";
   if (p->armour_regen < 0.)
      return "Insufficient Armour Regeneration";
   if (p->shield_max < 0.)
      return "Insufficient Shield";
   if (p->shield_regen < 0.)
      return "Insufficient Shield Regeneration";
   if (p->energy_max < 0.)
      return "Insufficient Energy";
   if (p->energy_regen < 0.)
      return "Insufficient Energy Regeneration";

   /* Misc. */
   if (p->fuel_max < 0.)
      return "Insufficient Fuel Maximum";
   if (p->fuel_consumption < 0.)
      return "Insufficient Fuel Consumption";
   if (p->cargo_free < 0)
      return "Insufficient Free Cargo Space";

   /* Core Slots */
   if (!pilot_slotsCheckRequired(p))
      return "Not All Core Slots are equipped";

   /* All OK. */
   return NULL;
}
/**
 * @brief Pilot sanity report - makes sure stats are sane.
 *
 *    @param p Pilot to check.
 *    @param buf Buffer to fill.
 *    @param bufSize Size of the buffer.
 *    @return Number of issues encountered.
 */
#define SPACEWORTHY_CHECK(cond,msg) \
if (cond){ ret++; \
   if (pos < bufSize) pos += snprintf( &buf[pos], bufSize-pos, (msg) ); }
int pilot_reportSpaceworthy( Pilot *p, char buf[], int bufSize )
{
   int pos = 0;
   int ret = 0;

   /* Core Slots */
   SPACEWORTHY_CHECK( !pilot_slotsCheckRequired(p), "Not All Core Slots are equipped\n" );
   /* CPU. */
   SPACEWORTHY_CHECK( p->cpu < 0, "Insufficient CPU\n" );

   /* Movement. */
   SPACEWORTHY_CHECK( p->thrust < 0, "Insufficient Thrust\n" );
   SPACEWORTHY_CHECK( p->speed < 0,  "Insufficient Speed\n" );
   SPACEWORTHY_CHECK( p->turn < 0,   "Insufficient Turn\n" );

   /* Health. */
   SPACEWORTHY_CHECK( p->armour < 0.,       "Insufficient Armour\n" );
   SPACEWORTHY_CHECK( p->armour_regen < 0., "Insufficient Armour Regeneration\n" );
   SPACEWORTHY_CHECK( p->shield < 0.,       "Insufficient Shield\n" );
   SPACEWORTHY_CHECK( p->shield_regen < 0., "Insufficient Shield Regeneration\n" );
   SPACEWORTHY_CHECK( p->energy_max < 0.,   "Insufficient Energy\n" );
   SPACEWORTHY_CHECK( p->energy_regen < 0., "Insufficient Energy Regeneration\n" );

   /* Misc. */
   SPACEWORTHY_CHECK( p->fuel_max < 0.,         "Insufficient Fuel Maximum\n" );
   SPACEWORTHY_CHECK( p->fuel_consumption < 0., "Insufficient Fuel Consumption\n" );
   SPACEWORTHY_CHECK( p->cargo_free < 0,        "Insufficient Free Cargo Space\n" );

   /*buffer is full, lets write that there is more then what's copied */
   if (pos > bufSize-1) {
      buf[bufSize-4]='.';
      buf[bufSize-3]='.';
      buf[bufSize-2]='.';
      /* buf[bufSize-1]='\0'; already done for us */
   }
   else {
      if (pos == 0)
         /*string is empty so no errors encountered */
         nsnprintf( buf, bufSize, "Spaceworthy");
      else
         /*string is not empty, so trunc the last newline */
         buf[pos-1]='\0';
   }

   return ret;
}
#undef SPACEWORTHY_CHECK

/**
 * @brief Checks to see if a pilot has an outfit with a specific outfit type.
 *
 *    @param p Pilot to check.
 *    @param t Outfit type to check.
 *    @return the amount of outfits of this type the pilot has.
 */
static int pilot_hasOutfitLimit( Pilot *p, const char *limit )
{
   int i;
   Outfit *o;
   for (i = 0; i<p->noutfits; i++) {
      o = p->outfits[i]->outfit;
      if (o == NULL)
         continue;
      if ((o->limit != NULL) && (strcmp(o->limit,limit)==0))
         return 1;
   }
   return 0;
}

/**
 * @brief Checks to see if can equip/remove an outfit from a slot.
 *
 *    @param p Pilot to check if can equip.
 *    @param s Slot being checked to see if it can equip/remove an outfit.
 *    @param o Outfit to check (NULL if being removed).
 *    @return NULL if can swap, or error message if can't.
 */
const char* pilot_canEquip( Pilot *p, PilotOutfitSlot *s, Outfit *o )
{
   /* Just in case. */
   if ((p==NULL) || (s==NULL))
      return "Nothing selected.";

   if (o!=NULL) {
      /* Check slot type. */
      if (!outfit_fitsSlot( o, &s->sslot->slot ))
         return "Does not fit slot.";
      /* Check outfit limit. */
      if ((o->limit != NULL) && pilot_hasOutfitLimit( p, o->limit ))
         return "Already have an outfit of this type installed";
   }
   else {
      /* Check fighter bay. */
      if ((o==NULL) && (s!=NULL) && (s->u.ammo.deployed > 0))
         return "Recall the fighters first";
   }

   return NULL;
}


/**
 * @brief Adds some ammo to the pilot stock.
 *
 *    @param pilot Pilot to add ammo to.
 *    @param s Slot to add ammo to.
 *    @param ammo Ammo to add.
 *    @param quantity Amount to add.
 *    @return Amount actually added.
 */
int pilot_addAmmo( Pilot* pilot, PilotOutfitSlot *s, Outfit* ammo, int quantity )
{
   int q, max;
   (void) pilot;

   /* Failure cases. */
   if (s->outfit == NULL) {
      WARN("Pilot '%s': Trying to add ammo to unequiped slot.", pilot->name );
      return 0;
   }
   else if (!outfit_isLauncher(s->outfit) && !outfit_isFighterBay(s->outfit)) {
      WARN("Pilot '%s': Trying to add ammo to non-launcher/fighterbay type outfit '%s'",
            pilot->name, s->outfit->name);
      return 0;
   }
   else if (!outfit_isAmmo(ammo) && !outfit_isFighter(ammo)) {
      WARN( "Pilot '%s': Trying to add non-ammo/fighter type outfit '%s' as ammo.",
            pilot->name, ammo->name );
      return 0;
   }
   else if (outfit_isLauncher(s->outfit) && outfit_isFighter(ammo)) {
      WARN("Pilot '%s': Trying to add fighter '%s' as launcher '%s' ammo",
            pilot->name, ammo->name, s->outfit->name );
      return 0;
   }
   else if (outfit_isFighterBay(s->outfit) && outfit_isAmmo(ammo)) {
      WARN("Pilot '%s': Trying to add ammo '%s' as fighter bay '%s' ammo",
            pilot->name, ammo->name, s->outfit->name );
      return 0;
   }
   else if ((s->u.ammo.outfit != NULL) && (s->u.ammo.quantity > 0) &&
         (s->u.ammo.outfit != ammo)) {
      WARN("Pilot '%s': Trying to add ammo to outfit that already has ammo.",
            pilot->name );
      return 0;
   }

   /* Set the ammo type. */
   s->u.ammo.outfit    = ammo;

   /* Add the ammo. */
   max                 = outfit_amount(s->outfit) - s->u.ammo.deployed;
   q                   = s->u.ammo.quantity; /* Amount have. */
   s->u.ammo.quantity += quantity;
   s->u.ammo.quantity  = MIN( max, s->u.ammo.quantity );
   q                   = s->u.ammo.quantity - q; /* Amount actually added. */
   pilot->mass_outfit += q * s->u.ammo.outfit->mass;
   pilot_updateMass( pilot );

   return q;
}


/**
 * @brief Removes some ammo from the pilot stock.
 *
 *    @param pilot Pilot to remove ammo from.
 *    @param s Slot to remove ammo from.
 *    @param quantity Amount to remove.
 *    @return Amount actually removed.
 */
int pilot_rmAmmo( Pilot* pilot, PilotOutfitSlot *s, int quantity )
{
   (void) pilot;
   int q;

   /* Failure cases. */
   if (s->outfit == NULL) {
      WARN("Pilot '%s': Trying to remove ammo from unequiped slot.", pilot->name );
      return 0;
   }
   else if (!outfit_isLauncher(s->outfit) && !outfit_isFighterBay(s->outfit)) {
      WARN("Pilot '%s': Trying to remove ammo from non-launcher/fighter bay type outfit '%s'",
            pilot->name, s->outfit->name);
      return 0;
   }

   /* No ammo already. */
   if (s->u.ammo.outfit == NULL)
      return 0;

   /* Remove ammo. */
   q                   = MIN( quantity, s->u.ammo.quantity );
   s->u.ammo.quantity -= q;
   pilot->mass_outfit -= q * s->u.ammo.outfit->mass;
   pilot_updateMass( pilot );
   /* We don't set the outfit to null so it "remembers" old ammo. */

   return q;
}


/**
 * @brief Gets the number of ammo units on the ship
 *
 *    @param pilot Pilot to count the ammo on
 *    @@return The integer count of ammo units on pilot
 */
int pilot_countAmmo( Pilot* pilot )
{
   int nammo = 0, i;
   PilotOutfitSlot* po;
   Outfit* outfit;
   for (i=0; i<pilot->noutfits; i++) {
     po = pilot->outfits[i];
     if (po == NULL)
        continue;
     outfit = po->outfit;
     if (outfit == NULL)
        continue;
     if (!outfit_isLauncher(po->outfit))
        continue;
     nammo += po->u.ammo.quantity;
   }
   return nammo;
}


/**
 * @brief The maximum amount of ammo the pilot's current ship can hold.
 *
 *    @param pilot Pilot to get the count from
 *    @@return An integer, the max amount of ammo that can be held.
 */
int pilot_maxAmmo( Pilot* pilot )
{
  int max = 0, i;
  PilotOutfitSlot* po;
  Outfit* outfit;
  for (i=0; i<pilot->noutfits; i++) {
     po = pilot->outfits[i];
     if (po == NULL)
        continue;
     outfit = po->outfit;
     if (outfit == NULL)
        continue;
     if (!outfit_isLauncher(outfit))
        continue;
     max += outfit->u.lau.amount;
  }
  return max;
}


/**
 * @brief Gets all the outfits in nice text form.
 *
 *    @param pilot Pilot to get the outfits from.
 *    @@return A list of all the outfits in a nice form.
 */
char* pilot_getOutfits( const Pilot* pilot )
{
   int i;
   char *buf;
   int p, len;

   len = 1024;

   buf = malloc(len);
   buf[0] = '\0';
   p = 0;
   for (i=1; i<pilot->noutfits; i++) {
      if (pilot->outfits[i]->outfit == NULL)
         continue;
      p += nsnprintf( &buf[p], len-p, (p==0) ? "%s" : ", %s",
            pilot->outfits[i]->outfit->name );
   }

   if (p==0)
      p += nsnprintf( &buf[p], len-p, "None" );

   return buf;
}


/**
 * @brief Recalculates the pilot's stats based on his outfits.
 *
 *    @param pilot Pilot to recalculate his stats.
 */
void pilot_calcStats( Pilot* pilot )
{
   int i;
   Outfit* o;
   PilotOutfitSlot *slot;
   double ac, sc, ec, fc; /* temporary health coefficients to set */
   ShipStats amount, *s, *default_s;

   /*
    * set up the basic stuff
    */
   /* mass */
   pilot->solid->mass   = pilot->ship->mass;
   pilot->base_mass     = pilot->solid->mass;
   /* cpu */
   pilot->cpu           = 0.;
   /* movement */
   pilot->thrust_base   = pilot->ship->thrust;
   pilot->turn_base     = pilot->ship->turn;
   pilot->speed_base    = pilot->ship->speed;
   /* crew */
   pilot->crew          = pilot->ship->crew;
   /* cargo */
   pilot->cap_cargo     = pilot->ship->cap_cargo;
   /* fuel_consumption. */
   pilot->fuel_consumption = pilot->ship->fuel_consumption;
   /* health */
   ac = (pilot->armour_max > 0.) ? pilot->armour / pilot->armour_max : 0.;
   sc = (pilot->shield_max > 0.) ? pilot->shield / pilot->shield_max : 0.;
   ec = (pilot->energy_max > 0.) ? pilot->energy / pilot->energy_max : 0.;
   fc = (pilot->fuel_max   > 0.) ? pilot->fuel   / pilot->fuel_max   : 0.;
   pilot->armour_max    = pilot->ship->armour;
   pilot->shield_max    = pilot->ship->shield;
   pilot->fuel_max      = pilot->ship->fuel;
   pilot->armour_regen  = pilot->ship->armour_regen;
   pilot->shield_regen  = pilot->ship->shield_regen;
   /* Absorption. */
   pilot->dmg_absorb    = pilot->ship->dmg_absorb;
   /* Energy. */
   pilot->energy_max    = pilot->ship->energy;
   pilot->energy_regen  = pilot->ship->energy_regen;
   pilot->energy_loss   = 0.; /* Initially no net loss. */
   /* Stats. */
   s = &pilot->stats;
   *s = pilot->ship->stats_array;
   memset( &amount, 0, sizeof(ShipStats) );

   /*
    * Now add outfit changes
    */
   pilot->mass_outfit   = 0.;
   pilot->jamming       = 0;
   for (i=0; i<pilot->noutfits; i++) {
      slot = pilot->outfits[i];
      o    = slot->outfit;

      /* Outfit must exist. */
      if (o==NULL)
         continue;

      /* Modify CPU. */
      pilot->cpu           += outfit_cpu(o);

      /* Add mass. */
      pilot->mass_outfit   += o->mass;

      /* Keep a separate counter for required (core) outfits. */
      if (sp_required( o->slot.spid ))
         pilot->base_mass += o->mass;

      /* Add ammo mass. */
      if (outfit_ammo(o) != NULL)
         if (slot->u.ammo.outfit != NULL)
            pilot->mass_outfit += slot->u.ammo.quantity * slot->u.ammo.outfit->mass;

      if (outfit_isAfterburner(o)) /* Afterburner */
         pilot->afterburner = pilot->outfits[i]; /* Set afterburner */

      /* Active outfits must be on to affect stuff. */
      if (slot->active && !(slot->state==PILOT_OUTFIT_ON))
         continue;

      if (outfit_isMod(o)) { /* Modification */
         /* Movement. */
         pilot->thrust_base   += o->u.mod.thrust;
         pilot->turn_base     += o->u.mod.turn;
         pilot->speed_base    += o->u.mod.speed;
         /* Health. */
         pilot->dmg_absorb    += o->u.mod.absorb;
         pilot->armour_max    += o->u.mod.armour;
         pilot->armour_regen  += o->u.mod.armour_regen;
         pilot->shield_max    += o->u.mod.shield;
         pilot->shield_regen  += o->u.mod.shield_regen;
         pilot->energy_max    += o->u.mod.energy;
         pilot->energy_regen  += o->u.mod.energy_regen;
         pilot->energy_loss   += o->u.mod.energy_loss;
         /* Fuel. */
         pilot->fuel_max      += o->u.mod.fuel;
         /* Misc. */
         pilot->cap_cargo     += o->u.mod.cargo;
         pilot->mass_outfit   += o->u.mod.mass_rel * pilot->ship->mass;
         pilot->crew          += o->u.mod.crew_rel * pilot->ship->crew;
         /*
          * Stats.
          */
         ss_statsModFromList( s, o->u.mod.stats, &amount );

      }
      else if (outfit_isAfterburner(o)) { /* Afterburner */
         pilot_setFlag( pilot, PILOT_AFTERBURNER ); /* We use old school flags for this still... */
         pilot->energy_loss += pilot->afterburner->outfit->u.afb.energy; /* energy loss */
      }
      else if (outfit_isJammer(o)) { /* Jammer */
         pilot->jamming        = 1;
         pilot->energy_loss   += o->u.jam.energy;
      }
   }

   if (!pilot_isFlag( pilot, PILOT_AFTERBURNER ))
      pilot->solid->speed_max = pilot->speed;

   /* Slot voodoo. */
   s = &pilot->stats;
   default_s = &pilot->ship->stats_array;

   /* Fire rate:
    *  amount = p * exp( -0.15 * (n-1) )
    *  1x 15% -> 15%
    *  2x 15% -> 25.82%
    *  3x 15% -> 33.33%
    *  6x 15% -> 42.51%
    */
   if (amount.fwd_firerate > 0) {
      s->fwd_firerate = default_s->fwd_firerate + (s->fwd_firerate-default_s->fwd_firerate) * exp( -0.15 * (double)(MAX(amount.fwd_firerate-1.,0)) );
   }
   /* Cruiser. */
   if (amount.tur_firerate > 0) {
      s->tur_firerate = default_s->tur_firerate + (s->tur_firerate-default_s->tur_firerate) * exp( -0.15 * (double)(MAX(amount.tur_firerate-1.,0)) );
   }
   /*
    * Electronic warfare setting base parameters.
    */
   s->ew_hide           = default_s->ew_hide + (s->ew_hide-default_s->ew_hide)                      * exp( -0.2 * (double)(MAX(amount.ew_hide-1.,0)) );
   s->ew_detect         = default_s->ew_detect + (s->ew_detect-default_s->ew_detect)                * exp( -0.2 * (double)(MAX(amount.ew_detect-1.,0)) );
   s->ew_jump_detect    = default_s->ew_jump_detect + (s->ew_jump_detect-default_s->ew_jump_detect) * exp( -0.2 * (double)(MAX(amount.ew_jump_detect-1.,0)) );

   /* Square the internal values to speed up comparisons. */
   pilot->ew_base_hide   = pow2( s->ew_hide );
   pilot->ew_detect      = pow2( s->ew_detect );
   pilot->ew_jump_detect = pow2( s->ew_jump_detect );

   /*
    * Relative increases.
    */
   /* Movement. */
   pilot->thrust_base  *= s->thrust_mod;
   pilot->turn_base    *= s->turn_mod;
   pilot->speed_base   *= s->speed_mod;
   /* Health. */
   pilot->armour_max   *= s->armour_mod;
   pilot->armour_regen *= s->armour_regen_mod;
   pilot->shield_max   *= s->shield_mod;
   pilot->shield_regen *= s->shield_regen_mod;
   pilot->energy_max   *= s->energy_mod;
   pilot->energy_regen *= s->energy_regen_mod;
   /* cpu */
   pilot->cpu_max       = (int)floor((float)(pilot->ship->cpu + s->cpu_max)*s->cpu_mod);
   pilot->cpu          += pilot->cpu_max; /* CPU is negative, this just sets it so it's based off of cpu_max. */
   /* Misc. */
   pilot->dmg_absorb    = MAX( 0., pilot->dmg_absorb );
   pilot->crew         *= s->crew_mod;
   pilot->cap_cargo    *= s->cargo_mod;
   s->engine_limit     *= s->engine_limit_rel;

   /*
    * Flat increases.
    */
   pilot->energy_max   += s->energy_flat;
   pilot->energy       += s->energy_flat;
   pilot->energy_regen -= s->energy_usage;

   /* Give the pilot his health proportion back */
   pilot->armour = ac * pilot->armour_max;
   pilot->shield = sc * pilot->shield_max;
   pilot->energy = ec * pilot->energy_max;
   pilot->fuel   = fc * pilot->fuel_max;

   /* Set final energy tau. */
   pilot->energy_tau = pilot->energy_max / pilot->energy_regen;

   /* Cargo has to be reset. */
   pilot_cargoCalc(pilot);

   /* Calculate mass. */
   pilot->solid->mass = s->mass_mod*pilot->ship->mass + pilot->stats.cargo_inertia*pilot->mass_cargo + pilot->mass_outfit;

   /* Calculate the heat. */
   pilot_heatCalc( pilot );

   /* Modulate by mass. */
   pilot_updateMass( pilot );

   /* Update GUI as necessary. */
   gui_setGeneric( pilot );
}


/**
 * @brief Cures the pilot as if he was landed.
 */
void pilot_healLanded( Pilot *pilot )
{
   pilot->armour = pilot->armour_max;
   pilot->shield = pilot->shield_max;
   pilot->energy = pilot->energy_max;

   pilot->stress = 0.;
   pilot->stimer = 0.;
   pilot->sbonus = 0.;
}


/**
 * @brief Updates the pilot stats after mass change.
 *
 *    @param pilot Pilot to update his mass.
 */
void pilot_updateMass( Pilot *pilot )
{
   double mass, factor;

   /* Set limit. */
   mass = pilot->solid->mass;
   if ((pilot->stats.engine_limit > 0.) && (mass > pilot->stats.engine_limit))
      factor = pilot->stats.engine_limit / mass;
   else
      factor = 1.;

   pilot->thrust  = factor * pilot->thrust_base * mass;
   pilot->turn    = factor * pilot->turn_base;
   pilot->speed   = factor * pilot->speed_base;

/* limit the maximum speed if limiter is active */
   if (pilot_isFlag(pilot, PILOT_HASSPEEDLIMIT)) {
      pilot->speed = pilot->speed_limit - pilot->thrust / (mass * 3.);
      /* Sanity: speed must never go negative. */
      if (pilot->speed < 0.) {
         /* If speed DOES go negative, we have to lower thrust. */
         pilot->thrust = 3 * pilot->speed_limit * mass;
         pilot->speed = 0.;
      }
   }
   /* Need to recalculate electronic warfare mass change. */
   pilot_ewUpdateStatic( pilot );
}


