/**
 * @file player.c
 *
 * @brief Contains all the player related stuff.
 */

#include <malloc.h>

#include "lephisto.h"
#include "pilot.h"
#include "log.h"
#include "opengl.h"
#include "font.h"
#include "pack.h"
#include "xml.h"
#include "space.h"
#include "rng.h"
#include "land.h"
#include "sound.h"
#include "economy.h"
#include "pause.h"
#include "menu.h"
#include "toolkit.h"
#include "dialogue.h"
#include "mission.h"
#include "misn_lua.h"
#include "ltime.h"
#include "hook.h"
#include "map.h"
#include "lfile.h"
#include "spfx.h"
#include "unidiff.h"
#include "player.h"

#define XML_GUI_ID    "GUIs"    /**< XML section identifier for GUI document. */
#define XML_GUI_TAG   "gui"     /**< XML section identifier for GUI tags. */

#define XML_START_ID  "Start"   /**< Module start xml document identifier. */

#define GUI_DATA      "../dat/gui.xml"    /**< Global GUI configuration file. */
#define GUI_GFX       "../gfx/gui/"       /**< Location of the GUI graphics. */

#define START_DATA    "../dat/start.xml"  /**< Module start information file. */

#define TARGET_WIDTH  128             /**< Width of target graphics. */
#define TARGET_HEIGHT 96              /**< Height of target graphics. */

#define PLAYER_RESERVED_CHANNELS 6    /**< Number of channels to reserve for player sounds. */
#define PLAYER_ENGINE_CHANNEL    9    /**< Player channel for engine noises. */
#define PLAYER_GUI_CHANNEL       9    /**< Player channel. */

/* Player stuff. */
Pilot* player = NULL;                   /**< The player. */
static Ship* player_ship = NULL;        /**< Temp ship to hold when naming it. */
static double player_px, player_py, player_vx, player_vy, player_dir; /**< More hack. */
static int player_credits = 0;          /**< Temp hack on create. */

/*
 * Player sounds.
 */
static int snd_target = -1;
static int snd_jump = -1;

/* Player pilot stack - Ships she owns. */
static Pilot** player_stack = NULL;     /**< Stack of ships player has. */
static char** player_lstack = NULL;     /**< Names of the planet the ships are at. */
static int player_nstack    = 0;        /**< Number of ships player has. */

/* Player global properties. */
char* player_name = NULL;               /**< Player name. */
int player_crating = 0;                 /**< Player combar rating. */
unsigned int player_flags = 0;          /**< Player flags. */

/* Input.c */
double player_turn = 0.;                /**< Turn velocity from input. */
static double player_acc  = 0.;         /**< Accel velocity from input. */

/* Internal */
int planet_target   = -1;               /**< Targetted planet. -1 is none. */
int hyperspace_target = -1;             /**< Target hyperspace route. -1 is none. */

/* For death etc. */
static unsigned int player_timer = 0;   /**< For death and such. */
static Vec2 player_cam;                 /**< Again, for death etc. */

static int* missions_done = NULL; /**< Saves position of completed missions. */
static int  missions_mdone = 0;   /**< Memory size of completed missions. */
static int  missions_ndone = 0;   /**< Number of completed missions. */

/* Pilot stuff for GUI. */
extern Pilot** pilot_stack;
extern int pilot_nstack;

/* Space stuff for GUI. */
extern StarSystem* systems_stack;

/* Map stuff for autonav. */
extern int map_npath;

/**
 * @struct Radar
 *
 * @brief Represents the player's radar.
 */
typedef struct Radar_ {
  double x;           /**< X position. */
  double y;           /**< Y position. */
  double w;           /**< Width. */
  double h;           /**< Height. */
  RadarShape shape;   /**< Shape. */
  double res;         /**< Resolution. */
} Radar;

/* Radar res. */
#define RADAR_RES_MAX       100.  /**< Max radar resolution. */
#define RADAR_RES_MIN       10.   /**< Min radar resolution. */
#define RADAR_RES_INTERVAL  10.   /**< Steps used to increase/decrease resolution. */
#define RADAR_RES_DEFAULT   40.   /**< Default resolution. */

/**
 * @struct Rect
 *
 * @brief Represents a rectangle.
 */
typedef struct Rect_ {
  double x;   /**< X position. */
  double y;   /**< Y position. */
  double w;   /**< Width. */
  double h;   /**< Height. */
} Rect;

/**
 * @struct GUI
 *
 * @brief Represents the ingame player graphical user interface.
 */
typedef struct GUI_ {
  /* Graphics. */
  glTexture* gfx_frame;                           /**< Frame of the GUI. */
  glTexture* gfx_targetPilot, *gfx_targetPlanet;  /**< Graphics used to target planets. */

  Radar radar;                                      /**< The radar. */
  Rect nav;                                         /**< Navigation computer. */
  Rect shield;                                      /**< Shield bar. */
  Rect armour;                                      /**< Armour bar. */
  Rect energy;                                      /**< Energy bar. */
  Rect weapon;                                      /**< Weapon targeting system. */
  Rect target_health;                               /**< Target health. */
  Rect target_name;                                 /**< Name of the target. */
  Rect target_faction;                              /**< Faction of the target. */
  Rect misc;                                        /**< Misc stuff: credits, cargo.. */
  Rect msg;                                         /**< Where messages go. */

  /* Positions. */
  Vec2 frame;   /**< Global frame position. */
  Vec2 target;  /**< Global target position. */
} GUI;

static GUI gui = {
  .gfx_frame = NULL,
  .gfx_targetPilot = NULL,
  .gfx_targetPlanet = NULL
}; /**< The GUI. */

/* Needed to render properly. */
double gui_xoff = 0.;   /**< X offset that GUI introduces. */
double gui_yoff = 0.;   /**< Y offset that GUI introduces. */

/* Messages. */
#define MSG_SIZE_MAX 120  /**< Max message length. */
int msg_timeout = 5000;   /**< How long it takes for a message to timeout. */
int msg_max = 5;          /**< Max messages on screen. */

/**
 * @struct Mesg
 *
 * @brief On screen player message.
 */
typedef struct Msg_ {
  char str[MSG_SIZE_MAX];   /**< The message. */
  unsigned int t;           /**< Time of creation. */
} Msg;
static Msg* msg_stack; /**< Stack of messages,  will be of mesg_max size. */

/* External. */
extern void pilot_render(const Pilot* pilot); /* Extern is in Pilot.* */
extern void weapon_minimap(const double res,
                           const double w, const double h, const RadarShape shape); /* weapon.c */
extern void planets_minimap(const double res,
                            const double w, const double h, const RadarShape shape); /* space.c */
/* Internal. */

/* Creation. */
static void player_newMake(void);
static void player_newShipMake(char* name);
/* Sound. */
static void player_initSound(void);
/*static void player_stopSound(void) */
/* Gui. */
static void rect_parse(const xmlNodePtr parent,
                       double* x, double* y, double* w, double* h);
static int gui_parse(const xmlNodePtr parent, const char* name);
static void gui_renderPilot(const Pilot* p);
static void gui_renderBar(const glColour* c, const Rect* r, const double w);
/* Save/Load. */
static int player_saveShip(xmlTextWriterPtr writer, Pilot* ship, char* loc);
static int player_parse(xmlNodePtr parent);
static int player_parseDone(xmlNodePtr parent);
static int player_parseShip(xmlNodePtr parent, int is_player);

/* Externed. */
void player_dead(void);
void player_destroyed(void);
int player_save(xmlTextWriterPtr writer);
int player_load(xmlNodePtr parent);
void player_think(Pilot* player);
void player_brokeHyperspace(void);
double player_faceHyperspace(void);

/**
 * @fn void player_new(void)
 *
 * @brief Create a new player.
 *
 *    - Cleans up after old player.
 *    - Prompts for name.
 *
 * @sa player_newMake
 */
void player_new(void) {
  int r;
  /* Let's not seg fault due to a lack of environment. */
  player_flags = 0;
  player_setFlag(PLAYER_CREATING);
  vectnull(&player_cam);
  gl_bindCamera(&player_cam);

  /* Setup sound. */
  player_initSound();

  /* Cleanup player stuff if we'll be re-creating. */
  player_cleanup();
  var_cleanup();
  missions_cleanup();
  space_clearKnown();
  land_cleanup();
  diff_clear();
  factions_reset();

  player_name = dialogue_input("Player Name", 3, 20,
                               "Please tell me your name:");

  /* Player cancelled dialogue. */
  if(player_name == NULL) {
    menu_main();
    return;
  }

  if(lfile_fileExists("saves/%s.ls", player_name)) {
    r = dialogue_YesNo("Overwrite",
        "You already have a pilot named %s. Overwrite?", player_name);
    if(r == 0) {
      /* Nupe. */
      player_new();
      return;
    }
  }

  player_newMake();
}

/**
 * @fn static void player_newMake(void)
 *
 * @brief Actually create a new player.
 */
static void player_newMake(void) {
  Ship* ship;
  char* sysname;
  uint32_t bufsize;
  char* buf;
  int l, h, tl, th;
  double x, y;

  sysname = NULL;

  buf = pack_readfile(DATA, START_DATA, &bufsize);

  xmlNodePtr node, cur, tmp;
  xmlDocPtr doc = xmlParseMemory(buf, bufsize);

  node = doc->xmlChildrenNode;
  if(!xml_isNode(node, XML_START_ID)) {
    ERR("Malformed '"START_DATA"' file: missing root element '"XML_START_ID"'");
    return;
  }

  node = node->xmlChildrenNode; /* First system node. */
  if(node == NULL) {
    ERR("Malformed '"START_DATA"' file: does not contain elements");
    return;
  }
  do {
    /* We are interested in the player. */
    if(xml_isNode(node, "player")) {
      cur = node->children;
      do {
        if(xml_isNode(cur, "ship")) ship = ship_get(xml_get(cur));
        else if(xml_isNode(cur, "credits")) {
          /* MONIEZZ!!!! -- We call these SCred AKA SaraCred. Lame right? */
          tmp = cur->children;
          do {
            xmlr_int(tmp, "low", l);
            xmlr_int(tmp, "high", h);
          } while(xml_nextNode(tmp));
        }
        else if(xml_isNode(cur, "system")) {
          tmp = cur->children;
          do {
            /* System name. @todo Chance based on percentage. */
            if(xml_isNode(tmp, "name"))
              sysname = strdup(xml_get(tmp));
            /* Position. */
            xmlr_float(tmp, "x", x);
            xmlr_float(tmp, "y", y);
          } while(xml_nextNode(tmp));
        }
        xmlr_int(cur, "player_crating", player_crating);
        if(xml_isNode(cur, "date")) {
          tmp = cur->children;
          do {
            xmlr_int(tmp, "low", tl);
            xmlr_int(tmp, "high", th);
          } while(xml_nextNode(tmp));
        }
      } while((cur = cur->next));
    }
  }while((node = node->next));

  xmlFreeDoc(doc);
  free(buf);

  /* Money. */
  player_credits = RNG(l, h);

  /* Time. */
  ltime_set(RNG(tl*1000*LTIME_UNIT_LENGTH, th*1000*LTIME_UNIT_LENGTH));

  /* Welcome message. */
  player_message("Welcome to "APPNAME"!");
  player_message("v%d.%d.%d", VMAJOR, VMINOR, VREV);

  /* Try to create the pilot, if fails re-ask for player name. */
  if(player_newShip(ship, x, y, 0., 0., RNG(0, 369)/180.*M_PI) != 0) {
    player_new();
    return;
  }

  space_init(sysname);
  free(sysname);

  /* Clear the map. */
  map_clear();
}

/**
 * @fn int player_newShip(Ship* ship, double px, double py,
 *                        double vx, double vy, double dir)
 *
 * @brief Create a new ship for player.
 *    @return 0 indicates success, -1 means dialogue was cancelled.
 *
 * @sa player_newShipMake
 */
int player_newShip(Ship* ship, double px, double py,
                    double vx, double vy, double dir) {

  char* ship_name;

  /* Temp values while player doesn't exist. */
  player_ship = ship;
  player_px   = px;
  player_py   = py;
  player_vx   = vx;
  player_vy   = vy;
  player_dir  = dir;

  ship_name = dialogue_input("Ship Name", 3, 20,
                             "Please name your shiny new %s", ship->name);

  /* Dialogue cancelled. */
  if(ship_name == NULL)
    return -1;

  player_newShipMake(ship_name);

  free(ship_name);

  return 0;
}

/**
 * @fn static void player_newShipMake(char* name)
 *
 * @brief Actually create the new ship.
 */
static void player_newShipMake(char* name) {
  Vec2 vp, vv;

  /* Store the current ship if it exists. */
  if(player != NULL) {
    player_stack = realloc(player_stack, sizeof(Pilot*)*(player_nstack+1));
    player_stack[player_nstack] = pilot_copy(player);
    player_lstack = realloc(player_lstack, sizeof(char*)*(player_nstack+1));
    player_lstack[player_nstack] = strdup(land_planet->name);
    player_nstack++;
    if(!player_credits) player_credits = player->credits;
    pilot_destroy(player);
  }

  /* In case we're respawning. */
  player_rmFlag(PLAYER_CREATING);

  /* Hackish position setting. */
  vect_cset(&vp, player_px, player_py);
  vect_cset(&vv, player_vx, player_vy);

  /* Create the player. */
  pilot_create(player_ship, name, faction_get("Player"), NULL,
               player_dir, &vp, &vv, PILOT_PLAYER);

  gl_bindCamera(&player->solid->pos); /* Set opengl camera. */

  /* Moniez!! */
  player->credits = player_credits;
  player_credits = 0;
}

/**
 * @fn void player_swapShip(char* shipname)
 *
 * @brief Swap players current ship with her ship named 'shipname'.
 *    @param shipname Ship to change to.
 */
void player_swapShip(char* shipname) {
  int i, j;
  Pilot* ship;

  for(i = 0; i < player_nstack; i++) {
    if(strcmp(shipname, player_stack[i]->name)==0) {
      /* Swap player and ship. */
      ship = player_stack[i];

      /* Move credits over. */
      ship->credits = player->credits;

      /* Move cargo over. */
      for(j = 0; j < player->ncommodities; j++) {
        pilot_addCargo(ship, player->commodities[j].commodity,
            player->commodities[j].quantity);
        pilot_rmCargo(player, player->commodities[j].commodity,
            player->commodities[j].quantity);
      }

      /* Extra pass to calculate stats. */
      pilot_calcStats(ship);
      pilot_calcStats(player);

      /* Now swap the players. */
      player_stack[i] = player;
      for(j = 0; j < pilot_nstack; j++) /* Find pilot in stack to swap. */
        if(pilot_stack[j] == player) {
          pilot_stack[j] = player = ship;
          break;
        }
      gl_bindCamera(&player->solid->pos); /* Let's not forget the camera. */
      return;
    }
  }
  WARN("Unable to swap player with ship '%s': Ship does not exist!", shipname);
}

/**
 * @fn int player_shipPrice(char* shipname)
 *
 * @brief Calculate the price of one of the players ships.
 *    @param shipname Name of the ship.
 *    @return The price of the ship in credits.
 */
int player_shipPrice(char* shipname) {
  int i;
  int price;
  Pilot* ship;

  /* Find ship. */
  for(i = 0; i < player_nstack; i++) {
    if(strcmp(shipname, player_stack[i]->name)==0) {
      ship = player_stack[i];

      /* Ship price is base price + outfit prices. */
      price = ship_basePrice(ship->ship);
      for(i = 0; i < ship->noutfits; i++)
        price += ship->outfits[i].quantity * ship->outfits[i].outfit->price;

      return price;
    }
  }

  WARN("Unable to find price for players ship '%s': ship does not exist!", shipname);
  return -1;
}

/**
 * @fn void player_rmShip(char* shipname)
 *
 * @brief Remove one of the players ships.
 *    @param shipname Name of the ship to remove.
 */
void player_rmShip(char* shipname) {
  int i;

  for(i = 0; i < player_nstack; i++) {
    if(strcmp(shipname, player_stack[i]->name)==0) {
      /* Free player ship and location. */
      pilot_free(player_stack[i]);
      free(player_lstack[i]);

      /* Move memory to make adjacent. */
      memmove(player_stack+i, player_stack+i+1,
          sizeof(Pilot*) * (player_nstack-i-1));
      memmove(player_lstack+i, player_lstack+i+1,
          sizeof(char*) * (player_nstack-i-1));

      player_nstack--;  /* Shrink stack. */

      /* Realloc memory to smaller size. */
      player_stack = realloc(player_stack,
          sizeof(Pilot*) * (player_nstack));
      player_lstack = realloc(player_lstack,
          sizeof(char*) * (player_nstack));
    }
  }
}

/**
 * @brief void player_cleanup(void
 *
 * @brief Clean up player stuff like player_stack.
 */
void player_cleanup(void) {
  int i;

  player_clear();

  /* Clean up name. */
  if(player_name != NULL) free(player_name);

  /* Clean up messages. */
  for(i = 0; i < msg_max; i++)
    memset(msg_stack[i].str, '\0', MSG_SIZE_MAX);

  /* Clean up the stack. */
  if(player_stack != NULL) {
    for(i = 0; i < player_nstack; i++) {
      pilot_free(player_stack[i]);
      free(player_lstack[i]);
    }
    free(player_stack);
    player_stack = NULL;
    free(player_lstack);
    player_lstack = NULL;
    /* Nothing left. */
    player_nstack = 0;
  }

  /* Clean up missions. */
  if(missions_done != NULL) {
    free(missions_done);
    missions_done   = NULL;
    missions_ndone  = 0;
    missions_mdone  = 0;
  }

  pilots_cleanAll();
  if(player != NULL) {
    pilot_free(player);
    player = NULL;
  }
}

/**
 * @fn static void player_initSound(void)
 *
 * @brief Initialize the player sounds.
 */
static int player_soundReserved = 0; /**< Has the player already reserved sound? */
static void player_initSound(void) {
  if(player_soundReserved) return;

  /* Allocated channels. */
  sound_reserve(PLAYER_RESERVED_CHANNELS);
  sound_createGroup(PLAYER_ENGINE_CHANNEL, 0, 1); /* Channel for engine noises. */
  sound_createGroup(PLAYER_GUI_CHANNEL, 1, PLAYER_RESERVED_CHANNELS-1);
  player_soundReserved = 1;

  /* Get sounds. */
  snd_target = sound_get("target");
  snd_jump = sound_get("jump");
}

/**
 * @fn static void player_playSound(int sound, int once)
 *
 * @brief Play a sound at the player.
 *    @param sound ID of the sound to play.
 *    @param once Play only once?
 */
void player_playSound(int sound, int once) {
  sound_playGroup(PLAYER_GUI_CHANNEL, sound, once);
}

#if 0
/**
 * @fn static void player_stopSound(void)
 *
 * @brief Stop playing player sounds.
 */
static void player_stopSound(void) {
  sound_stopGroup(PLAYER_GUI_CHANNEL);
}
#endif

/**
 * @fn void player_message(const char* fmt, ...)
 *
 * @brief Add a msg to the queue to be displayed on screen.
 *    @param fmt String with formatting like prinf.
 */
void player_message(const char* fmt, ...) {
  va_list ap;
  int i;

  if(fmt == NULL) return; /* Message not valid. */

  /* Copy old messages back. */
  for(i = 1; i < msg_max; i++) {
    if(msg_stack[msg_max-i-1].str[0] != '\0') {
      strcpy(msg_stack[msg_max-i].str, msg_stack[msg_max-i-1].str);
      msg_stack[msg_max-i].t = msg_stack[msg_max-i-1].t;
    }
  }
  /* Add the new one. */
  va_start(ap, fmt);
  vsprintf(msg_stack[0].str, fmt, ap);
  va_end(ap);

  msg_stack[0].t = SDL_GetTicks() + msg_timeout;
}

/**
 * @fn void player_warp(const double x, const double y)
 *
 * @brief Warp the player to the new position.
 *    @param x X value of the position to warp to.
 *    @param y Y value of the position to warp to.
 */
void player_warp(const double x, const double y) {
  vect_cset(&player->solid->pos, x, y);
}

/**
 * @fn player_clear(void)
 *
 * @brief Clear the targets.
 */
void player_clear(void) {
  if(player != NULL)
    player->target = PLAYER_ID;
  planet_target = -1;
  hyperspace_target = -1;
}

/**
 * @fn const char* player_rating(void)
 *
 * @brief Get the players combat rating in a human readable string.
 *
 *    @return The players combat rating in a human readable string.
 */
static char* player_ratings[] = {
  "None",
  "Smallfry",
  "Minor",
  "Average",
  "Major",
  "Fearsome",
  "Godlike"
};

const char* player_rating(void) {
  if(player_crating == 0)           return player_ratings[0];
  else if(player_crating < 50)      return player_ratings[1];
  else if(player_crating < 200)     return player_ratings[2];
  else if(player_crating < 500)     return player_ratings[3];
  else if(player_crating < 1000)    return player_ratings[4];
  else if(player_crating < 2500)    return player_ratings[5];
  else if(player_crating < 10000)   return player_ratings[6];
  else                              return player_ratings[7];
}

/**
 * @fn int player_outfitOwned(const char* outfitname)
 *
 * @brief Get how many of the outfits the player owns.
 *    @param outfitname Outfit to check how many the player owns.
 *    @return The number of outfits matching outfitname owned.
 */
int player_outfitOwned(const char* outfitname) {
  int i;

  for(i = 0; i < player->noutfits; i++)
    if(strcmp(outfitname, player->outfits[i].outfit->name)==0)
      return player->outfits[i].quantity;

  return 0;
}

/**
 * @fn int player_cargoOwned(const char* commodityname)
 *
 * @brief Get how many of the commodity the player has.
 *    @param commodityname Commodity to check how many the player has.
 *    @return The number of commodities owned matching commodityname.
 */
int player_cargoOwned(const char* commodityname) {
  int i;

  for(i = 0; i < player->ncommodities; i++)
    if(!player->commodities[i].id &&
        strcmp(commodityname, player->commodities[i].commodity->name)==0)
      return player->commodities[i].quantity;

  return 0;
}

void player_rmMissionCargo(unsigned int cargo_id) {
  int i;

  /* Check if already done. */
  if((player != NULL) && !pilot_rmMissionCargo(player, cargo_id)) return;

  for(i = 0; i < player_nstack; i++)
    if(!pilot_rmMissionCargo(player_stack[i], cargo_id))
      return; /* Success. */
}

/**
 * @fn void player_renderBG(void)
 *
 * @brief Render the background player stuff, namely planet target gfx.
 */
void player_renderBG(void) {
  double x, y;
  glColour* c;
  Planet* planet;

  if(player_isFlag(PLAYER_DESTROYED) || player_isFlag(PLAYER_CREATING) ||
      pilot_isFlag(player, PLAYER_DESTROYED)) return;

  if(planet_target >= 0) {
    planet = cur_system->planets[planet_target];

    c = faction_getColour(planet->faction);

    x = planet->pos.x - planet->gfx_space->sw/2.;
    y = planet->pos.y + planet->gfx_space->sh/2.;
    gl_blitSprite(gui.gfx_targetPlanet, x, y, 0, 0, c); /* Top left. */

    x += planet->gfx_space->sw;
    gl_blitSprite(gui.gfx_targetPlanet, x, y, 1, 0, c); /* Top right. */

    y -= planet->gfx_space->sh;
    gl_blitSprite(gui.gfx_targetPlanet, x, y, 1, 1, c); /* Bottom right. */

    x -= planet->gfx_space->sw;
    gl_blitSprite(gui.gfx_targetPlanet, x, y, 0, 1, c); /* Bottom left. */
  }
}

/**
 * @fn void player_render(void)
 *
 * @brief Render the player.
 */
static int can_jump = 0; /**< Store whether or not the player is able to jump. */
void player_render(void) {
  Pilot* p;
  glColour* c;
  double x, y;

  if((player != NULL) && !player_isFlag(PLAYER_CREATING)) {
    /* Render the player target graphics. */
    if(player->target != PLAYER_ID) p = pilot_get(player->target);
    else p = NULL;
    if((p == NULL) || pilot_isFlag(p, PILOT_DEAD))
      player->target = PLAYER_ID; /* No more pilot target. */
    else {
      /* There is still a pilot target. */
      if(pilot_isDisabled(p)) c = &cInert;
      else if(pilot_isFlag(p, PILOT_HOSTILE)) c = &cHostile;
      else c = faction_getColour(p->faction);

      x = p->solid->pos.x - p->ship->gfx_space->sw * PILOT_SIZE_APROX/2.;
      y = p->solid->pos.y + p->ship->gfx_space->sh * PILOT_SIZE_APROX/2.;
      gl_blitSprite(gui.gfx_targetPilot, x, y, 0, 0, c); /* Top left. */

      x += p->ship->gfx_space->sw * PILOT_SIZE_APROX;
      gl_blitSprite(gui.gfx_targetPilot, x, y, 1, 0, c); /* Top right. */

      y -= p->ship->gfx_space->sh * PILOT_SIZE_APROX;
      gl_blitSprite(gui.gfx_targetPilot, x, y, 1, 1, c); /* Bottom right. */

      x -= p->ship->gfx_space->sw * PILOT_SIZE_APROX;
      gl_blitSprite(gui.gfx_targetPilot, x, y, 0, 1, c); /* Bottom left. */
    }

    /* Player is ontop of targeting graphic. */
    pilot_render(player);
  }
}

/**
 * @fn void player_renderGUI(void)
 *
 * @brief Render the players GUI.
 */
void player_renderGUI(void) {
  int i, j;
  double x, y;
  char str[10];
  Pilot* p;
  glColour* c, c2;
  glFont* f;
  StarSystem* sys;
  unsigned int t;
  int quantity, delay;

  t = SDL_GetTicks();

  /* Pilot is dead or being created, just render her and stop. */
  if(player_isFlag(PLAYER_DESTROYED) || player_isFlag(PLAYER_CREATING) ||
      pilot_isFlag(player, PILOT_DEAD)) {

    if(player_isFlag(PLAYER_DESTROYED)) {
      if(!toolkit && !player_isFlag(PLAYER_CREATING) &&
          (t > player_timer)) {
        menu_death();
      }
    }
    /* Fancy cinematic scene borders. */
    spfx_cinematic();

    return;
  }

  if(player == NULL) return;

  /* Lockon warning. */
  if(player->lockons > 0)
    gl_printMid(NULL, SCREEN_W - gui_xoff, 0., SCREEN_H-gl_defFont.h-25.,
        &cRed, "LOCKON DETECTED");

  /* Volatile Environment. */
  if(cur_system->nebu_volatility > 0.)
    gl_printMid(NULL, SCREEN_W - gui_xoff, 0., SCREEN_H-gl_defFont.h*2.-35.,
        &cRed, "VOLATILE ENVIRONMENT DETECTED");

  /* GUI! */
  /* -- Frame. */
  gl_blitStatic(gui.gfx_frame, gui.frame.x, gui.frame.y, NULL);
  /* -- Radar. */
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  if(gui.radar.shape == RADAR_RECT)
    glTranslated(gui.radar.x - SCREEN_W/2. + gui.radar.w/2.,
                 gui.radar.y - SCREEN_H/2. - gui.radar.h/2., 0.);
  else if(gui.radar.shape == RADAR_CIRCLE)
    glTranslated(gui.radar.x - SCREEN_W/2.,
                 gui.radar.y - SCREEN_H/2., 0.);

  /* Planets. */
  planets_minimap(gui.radar.res, gui.radar.w, gui.radar.h, gui.radar.shape);

  /* Weapons. */
  glBegin(GL_POINTS);
  COLOUR(cRadar_weap);
  weapon_minimap(gui.radar.res, gui.radar.w, gui.radar.h, gui.radar.shape);
  glEnd();

  /* Render the pilots. */
  for(j = 0, i = 1; i < pilot_nstack; i++) {
    /* Skip the player. */
    if(pilot_stack[i]->id == player->target) j = i;
    else gui_renderPilot(pilot_stack[i]);
  }
  /* Render the targetted pilot. */
  if(j != 0) gui_renderPilot(pilot_stack[j]);

  /* The + sign in the middle of the radar represents the player. */
  glBegin(GL_LINES);
    COLOUR(cRadar_player);
    glVertex2d( 0., -3.);
    glVertex2d( 0.,  3.);
    glVertex2d(-3.,  0.);
    glVertex2d( 3.,  0.);
  glEnd();

  glPopMatrix(); /* GL_PROJECTION. */

  /* Nav. */
  if(planet_target >= 0) {
    /* Planet landing target. */
    gl_printMid(NULL, (int)gui.nav.w,
                gui.nav.x, gui.nav.y - 5, &cConsole, "Land");

    gl_printMid(&gl_smallFont, (int)gui.nav.w, gui.nav.x,
                gui.nav.y - 10 - gl_smallFont.h,  NULL, "%s",
                cur_system->planets[planet_target]->name);
  }
  else if(hyperspace_target >= 0) {
    /* Hyperspace target. */
    sys = &systems_stack[cur_system->jumps[hyperspace_target]];

    /* Determine if we have to play the "enter hyperspace range" sound. */
    i = space_canHyperspace(player);
    if((i != 0) && (i != can_jump))
      if(!pilot_isFlag(player, PILOT_HYPERSPACE))
        player_playSound(snd_jump, 1);
    can_jump = i;

    /* Determine the colour of the NAV text. */
    if(can_jump || pilot_isFlag(player, PILOT_HYPERSPACE) ||
        pilot_isFlag(player, PILOT_HYP_PREP) ||
        pilot_isFlag(player, PILOT_HYP_BEGIN))
      c = &cConsole;
    else c = NULL;
    gl_printMid(NULL, (int)gui.nav.w, gui.nav.x, gui.nav.y - 5,
                c, "Hyperspace");

    gl_printMid(&gl_smallFont, (int)gui.nav.w, gui.nav.x,
                gui.nav.y - 10 - gl_smallFont.h,
                NULL, "%d - %s", pilot_getJumps(player),
                (sys_isKnown(sys)) ? sys->name : "Unknown");
  }
  else {
    /* No NAV target. */
    gl_printMid(NULL, (int)gui.nav.w, gui.nav.x,
                gui.nav.y - 5, &cConsole, "Navigation");
    gl_printMid(&gl_smallFont, (int)gui.nav.w, gui.nav.x,
                gui.nav.y - 10 - gl_smallFont.h, &cGrey, "Off");
  }

  /* Health */
  gui_renderBar(&cShield, &gui.shield, player->shield / player->shield_max);
  gui_renderBar(&cArmour, &gui.armour, player->armour / player->armour_max);
  gui_renderBar(&cEnergy, &gui.energy, player->energy / player->energy_max);

  /* Weapon. */
  if(player->secondary == NULL) {
    gl_printMid(NULL, (int)gui.weapon.w, gui.weapon.x,
                gui.weapon.y - 5, &cConsole, "Secondary");
    gl_printMid(&gl_smallFont, (int)gui.weapon.w,  gui.weapon.x,
                gui.weapon.y - 10 - gl_defFont.h, &cGrey, "None");
  } else {
    f = &gl_defFont;
    
    quantity = pilot_oquantity(player, player->secondary);
    delay = outfit_delay(player->secondary->outfit);

    /* Check to see if weapon is ready. */
    if((player->secondary->timer > 0) &&
        (SDL_GetTicks() - player->secondary->timer) < (unsigned int)(delay/quantity))
      c = &cGrey;
    else
      c = &cConsole;

    /* Launcher. */
    if(outfit_isLauncher(player->secondary->outfit)) {
      /* Use the ammunitions name. */
      i = gl_printWidth(f, "%s", player->secondary->outfit->u.lau.ammo);
      if(i > gui.weapon.w)
        /* Font is too big. */
        f = &gl_smallFont;

      /* Weapon name. */
      gl_printMid(f, (int)gui.weapon.w, gui.weapon.x,
                  gui.weapon.y - 5,
                  (player->ammo) ? c : &cGrey, "%s",
                  player->secondary->outfit->u.lau.ammo);

      /* Print ammo underneath to the left. */
      gl_printMid(&gl_smallFont, (int)gui.weapon.w, gui.weapon.x,
                  gui.weapon.y - 10 - gl_defFont.h,
                  NULL, "%d", (player->ammo) ? player->ammo->quantity : 0);
      /* Other. */
    } else {
      /* Just print the item name. */
      i = gl_printWidth(f, "%s", player->secondary->outfit->name);
      if(i > (int)gui.weapon.w)
        /* Font is too big. */
        f = &gl_smallFont;
      gl_printMid(f, (int)gui.weapon.w,
                  gui.weapon.x, gui.weapon.y - (gui.weapon.h - f->h)/2.,
                  c, "%s", player->secondary->outfit->name);
    }
  }

  /* Target. */
  if(player->target != PLAYER_ID) {
    p = pilot_get(player->target);

    /* Blit the pilot target. */
    gl_blitStatic(p->ship->gfx_target, gui.target.x, gui.target.y, NULL);
    /* Blit the pilot space image. */
    /*x = gui.target.x + (TARGET_WIDTH - p->ship->gfx_space->sw)/2.;
    y = gui.target.y + (TARGET_HEIGHT - p->ship->gfx_space->sh)/2.;
    gl_blitStaticSprite(p->ship->gfx_space,
        x, y, p->tsx, p->tsy, NULL);*/

    /* Target name. */
    gl_print(NULL, gui.target_name.x, gui.target_name.y,
             NULL, "%s", p->name);
    gl_print(&gl_smallFont, gui.target_faction.x, gui.target_faction.y,
             NULL, "%s", faction_name(p->faction));

    /* Target status. */
    if(pilot_isDisabled(p))
      /* Disable the pilot. */
      gl_print(&gl_smallFont, gui.target_health.x, gui.target_health.y,
               NULL, "Disabled");
    else if(p->shield > p->shield_max / 100.)
      /* On shields. */
      gl_print(&gl_smallFont, gui.target_health.x, gui.target_health.y,
               NULL, "%s: %.0f%%", "Shield", p->shield/p->shield_max*100.);
    else
      /* On armour. */
      gl_print(&gl_smallFont, gui.target_health.x, gui.target_health.y,
               NULL, "%s: %.0f%%", "Armor", p->armour/p->armour_max*100.);
  } else {
    /* No target. */
    gl_printMid(NULL, SHIP_TARGET_W, gui.target.x,
                gui.target.y + (SHIP_TARGET_H - gl_defFont.h)/2.,
                &cGrey80, "No Target");
  }

  /* Misc. */
  j = gui.misc.y - 8 - gl_smallFont.h;
  gl_print(NULL, gui.misc.x + 8, j,
           &cConsole, "SCreds:");

  credits2str(str, player->credits, 2);

  i = gl_printWidth(&gl_smallFont, str);
  gl_print(&gl_smallFont, gui.misc.x + gui.misc.w - 8 - i, j,
           NULL, str);

  /* Cargo and co. */
  if(player->ncommodities > 0) {
    j -= gl_smallFont.h + 5;
    gl_print(&gl_smallFont,
             gui.misc.x + 8, j, &cConsole, "Cargo");

    for(i = 0; i < MIN(player->ncommodities,3); i++) {
      j -= gl_smallFont.h + 3;
      if(player->commodities[i].quantity) /* Quantity is over. */
        gl_printMax(&gl_smallFont, gui.misc.w - 15,
                gui.misc.x + 13, j,
                NULL, "%d %s%s", player->commodities[i].quantity,
                player->commodities[i].commodity->name,
                (player->commodities[i].id) ? "*" : "");
      else /* Basically for weightless mission stuff. */
        gl_printMax(&gl_smallFont, gui.misc.w - 15,
            gui.misc.x + 13, j,
            NULL, "%s%s", player->commodities[i].commodity->name,
            (player->commodities[i].id) ? "*" : "");
    }
  }

  j -= gl_smallFont.h + 5;
  gl_print(&gl_smallFont,
           gui.misc.x + 8, j, &cConsole, "Free:");

  i = gl_printWidth(&gl_smallFont, "%d", player->ship->cap_cargo);
  gl_print(&gl_smallFont,
           gui.misc.x + gui.misc.w - 8 - i, j,
           NULL, "%d", pilot_cargoFree(player));

  /* Messages. */
  x = gui.msg.x;
  y = gui.msg.y + (double)(gl_defFont.h * msg_max)*1.2;
  c2.r = c2.g = c2.b = 1.;
  for(i = 0; i < msg_max; i++) {
    y -= (double)gl_defFont.h*1.2;
    if(msg_stack[msg_max-i-1].str[0] != '\0') {
      if(msg_stack[msg_max-i-1].t < t)
        msg_stack[msg_max-i-1].str[0] = '\0';
      else {
        if(msg_stack[msg_max-i-1].t - msg_timeout/2 < t)
          c2.a = (double)(msg_stack[msg_max-i-1].t - t) / (double)(msg_timeout/2);
        else
          c2.a = 1.;
        gl_print(NULL, x, y, &c2, "%s", msg_stack[msg_max-i-1].str);
      }
    }
  }
  /* Hyperspace FLASH BANG!!! */
  if(pilot_isFlag(player, PILOT_HYPERSPACE)) {
    i = (int)player->ptimer - HYPERSPACE_FADEOUT;
    if(paused) i += t;
    j = (int) t;
    if(i < j) {
      x = (double)(j-i) / HYPERSPACE_FADEOUT;
      glColor4d(1.,1.,1., x); /* We'll | I'll, make this more effiecent later. */
      glBegin(GL_QUADS);
      glVertex2d(-SCREEN_W/2., -SCREEN_H/2.);
      glVertex2d(-SCREEN_W/2.,  SCREEN_H/2.);
      glVertex2d( SCREEN_W/2.,  SCREEN_H/2.);
      glVertex2d( SCREEN_W/2., -SCREEN_H/2.);
      glEnd();
    }
  }
}

/* Renders a pilot. */
static void gui_renderPilot(const Pilot* p) {
  int x, y, sx, sy;
  double w, h;
  glColour* col;

  x = (p->solid->pos.x - player->solid->pos.x) / gui.radar.res;
  y = (p->solid->pos.y - player->solid->pos.y) / gui.radar.res;
  sx = PILOT_SIZE_APROX/2. * p->ship->gfx_space->sw / gui.radar.res;
  sy = PILOT_SIZE_APROX/2. * p->ship->gfx_space->sh / gui.radar.res;
  if(sx < 1.) sx = 1.;
  if(sy < 1.) sy = 1.;

  if(((gui.radar.shape == RADAR_RECT) && ((ABS(x) > gui.radar.w/2.+sx)
          || (ABS(y) > gui.radar.h/2.+sy))) ||
          ((gui.radar.shape == RADAR_CIRCLE) &&
          ((x*x + y*y) > (int)(gui.radar.w*gui.radar.w))))
    return; /* Pilot isn't in range. */

  if(gui.radar.shape == RADAR_RECT) {
    w = gui.radar.w/2.;
    h = gui.radar.h/2.;
  }
  else if(gui.radar.shape == RADAR_CIRCLE) {
    w = gui.radar.w;
    h = gui.radar.w;
  }

  glBegin(GL_QUADS);
  /* Colours. */
  if(p->id == player->target) col = &cRadar_targ;
  else if(pilot_isDisabled(p)) col = &cInert;
  else if(pilot_isFlag(p, PILOT_HOSTILE)) col = &cHostile;
  else col = faction_getColour(p->faction);
  COLOUR(*col);

  /* Image. */
  glVertex2d(MAX(x-sx, -w), MIN(y+sy,  h)); /* Top left. */
  glVertex2d(MIN(x+sx,  w), MIN(y+sy,  h)); /* Top right. */
  glVertex2d(MIN(x+sx,  w), MAX(y-sy, -h)); /* Bottom right. */
  glVertex2d(MAX(x-sx, -w), MAX(y-sy, -h)); /* Bottom left. */
  glEnd();
}

/* Render a bar. */
static void gui_renderBar(const glColour* c, const Rect* r, const double w) {
  int x, y, sx, sy;

  glBegin(GL_QUADS);
  COLOUR(*c);
  x = r->x - SCREEN_W/2.;
  y = r->y - SCREEN_H/2.;
  sx = w * r->w;
  sy = r->h;
  glVertex2d(x, y);
  glVertex2d(x+sx, y);
  glVertex2d(x+sx, y-sy);
  glVertex2d(x, y-sy);
  glEnd();
}

/**
 * @fn int gui_init(void)
 *
 * @brief Initialize the GUI system.
 *    @return 0 on success.
 */
int gui_init(void) {
  /* Set graphics to NULL. */
  gui.gfx_frame = NULL;
  gui.gfx_targetPilot = NULL;
  gui.gfx_targetPlanet = NULL;

  /* -- Radar. */
  gui.radar.res = RADAR_RES_DEFAULT;
  /* -- messages. */
  gui.msg.x = 20;
  gui.msg.y = 30;
  msg_stack = calloc(msg_max, sizeof(Msg));

  if(msg_stack == NULL) {
    ERR("Out of memory!");
    return -1;
  }

  return 0;
}

/**
 * @fn int gui_load(const char* name)
 *
 * @brief Attempt to load the actual GUI.
 *    @brief name Name of the GUI to load.
 *    @brief 0 on success.
 */
int gui_load(const char* name) {
  uint32_t bufsize;
  char* buf = pack_readfile(DATA, GUI_DATA, &bufsize);
  char* tmp;
  int found = 0;

  xmlNodePtr node;
  xmlDocPtr doc = xmlParseMemory(buf, bufsize);

  node = doc->xmlChildrenNode;
  if(!xml_isNode(node, XML_GUI_ID)) {
    ERR("Malformed '"GUI_DATA"' file: missing root element '"XML_GUI_ID"'");
    return -1;
  }

  node = node->xmlChildrenNode; /* First system node. */
  if(node == NULL) {
    ERR("Malformed '"GUI_DATA"' file: does not contain elements");
    return -1;
  }

  do {
    if(xml_isNode(node, XML_GUI_TAG)) {
      tmp = xml_nodeProp(node, "name"); /* Mallocs. */

      /* Is this the gui we are looking for? */
      if(strcmp(tmp, name)==0) {
        found = 1;

        /* Parse the xml node. */
        if(gui_parse(node, name)) WARN("Trouble loading GUI '%s'", name);
        free(tmp);
        break;
      }
      free(tmp);
    }
  } while((node = node->next));

  xmlFreeDoc(doc);
  free(buf);

  if(!found) {
    WARN("GUI '%s' not found in '"GUI_DATA"'",name);
    return -1;
  }
  return 0;
}

static void rect_parse(const xmlNodePtr parent, double* x, double* y,
                       double* w, double* h) {
  xmlNodePtr cur;
  int param;

  param = 0;

  cur = parent->children;

  do {
    if(xml_isNode(cur, "x")) {
      if(x != NULL) {
        *x = xml_getFloat(cur);
        param |= (1<<0);
      } else
        WARN("Extra parameter 'x' found for GUI node '%s'", parent->name);
    }
    else if(xml_isNode(cur, "y")) {
      if(y != NULL) {
        *y = xml_getFloat(cur);
        param |= (1<<1);
      } else
        WARN("Extra parameter 'y' found for GUI node '%s'", parent->name);
    }
    else if(xml_isNode(cur, "w")) {
      if(w != NULL) {
        *w = xml_getFloat(cur);
        param |= (1<<2);
      } else
        WARN("Extra parameter 'w' found for GUI node '%s'", parent->name);
    }
    else if(xml_isNode(cur, "h")) {
      if(h != NULL) {
        *h = xml_getFloat(cur);
        param |= (1<<3);
      } else
        WARN("Extra parameter 'h' found for GUI node '%s'", parent->name);
    }
  } while((cur = cur->next));

  /* Check to see if we got everything we asked for. */
  if(x && !(param & (1<<0)))
    WARN("Missing parameter 'x' for GUI node '%s'", parent->name);
  else if(y && !(param & (1<<1)))
    WARN("Missing parameter 'y' for GUI node '%s'", parent->name);
  else if(w && !(param & (1<<2)))
    WARN("Missing parameter 'w' for GUI node '%s'", parent->name);
  else if(h && !(param & (1<<3)))
    WARN("Missing parameter 'h' for GUI node '%s'", parent->name);
}

/* Parse a gui node. */
#define RELATIVIZE(a) \
{ (a).x += VX(gui.frame); (a).y = VY(gui.frame) + gui.gfx_frame->h-(a).y; }
static int gui_parse(const xmlNodePtr parent, const char* name) {
  xmlNodePtr cur, node;
  char* tmp, buf[PATH_MAX];

  /* Gfx. */
  /* Set a property and not a node because it must be loaded first. */
  tmp = xml_nodeProp(parent, "gfx");
  if(tmp == NULL) {
    ERR("GUI '%s' has no gfx property", name);
    return -1;
  }
  /* Load gfx. */

  /* Frame. */
  snprintf(buf, PATH_MAX, GUI_GFX"%s.png", tmp);
  if(gui.gfx_frame) gl_freeTexture(gui.gfx_frame); /* Free if needed. */
  gui.gfx_frame = gl_newImage(buf);
  /* Pilot. */
  snprintf(buf, PATH_MAX, GUI_GFX"%s_pilot.png", tmp);
  if(gui.gfx_targetPilot) gl_freeTexture(gui.gfx_targetPilot); /* Free if needed. */
  gui.gfx_targetPilot = gl_newSprite(buf, 2, 2);
  /* Planet. */
  snprintf(buf, PATH_MAX, GUI_GFX"%s_planet.png", tmp);
  if(gui.gfx_targetPlanet) gl_freeTexture(gui.gfx_targetPlanet); /* Free if needed. */
  gui.gfx_targetPlanet = gl_newSprite(buf, 2, 2);
  free(tmp);

  /* Frame (based on gfx). */
  vect_csetmin(&gui.frame,
               SCREEN_W - gui.gfx_frame->w,  /* x. */
               SCREEN_H - gui.gfx_frame->h); /* h. */

  /* Let's parse the data now. */
  node = parent->children;
  do {
    /* Offset. */
    if(xml_isNode(node, "offset"))
      rect_parse(node, &gui_xoff, &gui_yoff, NULL, NULL);
    /* Radar. */
    else if(xml_isNode(node, "radar")) {
      tmp = xml_nodeProp(node,"type");
      /* Make sure type is valid. */
      if(strcmp(tmp, "rectangle")==0) gui.radar.shape = RADAR_RECT;
      else if(strcmp(tmp, "circle")==0) gui.radar.shape = RADAR_CIRCLE;
      else {
        WARN("Radar for GUI '%s' is missing 'type' tag or has invalid 'type' tag", name);
        gui.radar.shape = RADAR_RECT;
      }
      free(tmp);

      /* Load the appropriate measurements. */
      if(gui.radar.shape == RADAR_RECT)
        rect_parse(node, &gui.radar.x, &gui.radar.y, &gui.radar.w, &gui.radar.h);
      else if(gui.radar.shape == RADAR_CIRCLE)
        rect_parse(node, &gui.radar.x, &gui.radar.y, &gui.radar.w, NULL);
      RELATIVIZE(gui.radar);
    }
    /* Nav computer. */
    else if(xml_isNode(node, "nav")) {
      rect_parse(node, &gui.nav.x, &gui.nav.y, &gui.nav.w, &gui.nav.h);
      RELATIVIZE(gui.nav);
      gui.nav.y -= gl_defFont.h;
    }
    /* Health bars. */
    else if(xml_isNode(node, "health")) {
      cur = node->children;
      do {
        if(xml_isNode(cur, "shield")) {
          rect_parse(cur, &gui.shield.x, &gui.shield.y,
                     &gui.shield.w, &gui.shield.h);
          RELATIVIZE(gui.shield);
        }

        if(xml_isNode(cur, "armour")) {
          rect_parse(cur, &gui.armour.x, &gui.armour.y,
                     &gui.armour.w, &gui.armour.h);
          RELATIVIZE(gui.armour);
        }

        if(xml_isNode(cur, "energy")) {
          rect_parse(cur, &gui.energy.x, &gui.energy.y,
                     &gui.energy.w, &gui.energy.h);
          RELATIVIZE(gui.energy);
        }
      } while((cur = cur->next));
    }
    /* Secondary weapon. */
    else if(xml_isNode(node, "weapon")) {
      rect_parse(node, &gui.weapon.x, &gui.weapon.y,
                 &gui.weapon.w, &gui.weapon.h);
      RELATIVIZE(gui.weapon);
      gui.weapon.y -= gl_defFont.h;
    }
    /* Target. */
    else if(xml_isNode(node, "target")) {
      cur = node->children;
      do {
        if(xml_isNode(cur, "gfx")) {
          rect_parse(cur, &gui.target.x, &gui.target.y, NULL, NULL);
          RELATIVIZE(gui.target);
          gui.target.y -= SHIP_TARGET_H;
        }

        if(xml_isNode(cur, "name")) {
          rect_parse(cur, &gui.target_name.x, &gui.target_name.y, NULL, NULL);
          RELATIVIZE(gui.target_name);
          gui.target_name.y -= gl_defFont.h;
        }

        if(xml_isNode(cur, "faction")) {
          rect_parse(cur, &gui.target_faction.x, &gui.target_faction.y,
                     NULL, NULL);
          RELATIVIZE(gui.target_faction);
          gui.target_faction.y -= gl_smallFont.h;
        }

        if(xml_isNode(cur, "health")) {
          rect_parse(cur, &gui.target_health.x, &gui.target_health.y,
                     NULL, NULL);
          RELATIVIZE(gui.target_health);
          gui.target_health.y -= gl_smallFont.h;
        }
      } while((cur = cur->next));
    } else if(xml_isNode(node, "misc")) {
      rect_parse(node, &gui.misc.x, &gui.misc.y, &gui.misc.w, &gui.misc.h);
      RELATIVIZE(gui.misc);
    }
  } while((node = node->next));

  return 0;
}
#undef RELATIVIZE

/**
 * @fn void gui_free(void)
 *
 * @brief Free the GUI.
 */
void gui_free(void) {
  if(gui.gfx_frame)     gl_freeTexture(gui.gfx_frame);
  if(gui.gfx_targetPilot)  gl_freeTexture(gui.gfx_targetPilot);
  if(gui.gfx_targetPlanet) gl_freeTexture(gui.gfx_targetPlanet);

  free(msg_stack);
}

/**
 * @fn void player_startAutonav(void)
 *
 * @brief Start autonav.
 */
void player_startAutonav(void) {
  if(hyperspace_target == -1)
  	return;

  player_message("Autonav initialized");
  player_setFlag(PLAYER_AUTONAV);
}

/**
 * @fn void player_abortAutonav(void)
 *
 * @brief Aborts autonav
 */
void player_abortAutonav(void) {
  if(player_isFlag(PLAYER_AUTONAV)) {
    player_message("Autonav aborted!");
    player_rmFlag(PLAYER_AUTONAV);

    /* Get rid of acceleration. */
    player_accelOver();

    /* Break possible hyperspacing. */
    if(pilot_isFlag(player, PILOT_HYP_PREP)) {
      pilot_hyperspaceAbort(player);
      player_message("Aborting hyperspace sequence.");
    }
  }
}

/**
 * @fn void player_think(Pilot* pplayer)
 *
 * @brief Basically uses keyboard input instead of AI input. Used in pilot.c.
 *    @param pplayer Player to think.
 */
void player_think(Pilot* pplayer) {
  /* Last I checked, the dead didn't think.. */
  if(pilot_isFlag(pplayer, PILOT_DEAD)) {
    /* No point in accelerating or turning. */
    pplayer->solid->dir_vel = 0.;
    vect_pset(&player->solid->force, 0., 0.);
    return;
  }

  /* Autonav takes over normal controls. */
  if(player_isFlag(PLAYER_AUTONAV)) {
    if(pplayer->lockons > 0)
      player_abortAutonav();

    if(space_canHyperspace(pplayer)) {
      player_jump();
    }	else {
      pilot_face(pplayer, VANGLE(pplayer->solid->pos));
      if(player_acc < 1.)
        player_accel(1.);
  	}
  }

  /* PLAYER_FACE will take over navigation. */
  if(player_isFlag(PLAYER_FACE)) {
    if(player->target != PLAYER_ID)
      pilot_face(pplayer,
                 vect_angle(&player->solid->pos,
                            &pilot_get(player->target)->solid->pos));
    else if(planet_target != -1)
      pilot_face(pplayer,
          vect_angle(&player->solid->pos,
            &cur_system->planets[planet_target]->pos));
  }

  /* PLAYER_REVERSE will take over navigation. */
  else if(player_isFlag(PLAYER_REVERSE) && (VMOD(pplayer->solid->vel) > 0.))
    pilot_face(pplayer, VANGLE(player->solid->vel) + M_PI);

  /* Normal navigation sheme. */
  else {
    pplayer->solid->dir_vel = 0.;
    if(player_turn)
      pplayer->solid->dir_vel -= player->turn * player_turn;
  }

  /* Weapon shooting stuff. */

  /* Primary weapon. */
  if(player_isFlag(PLAYER_PRIMARY)) {
    pilot_shoot(pplayer, player->target, 0);
    player_setFlag(PLAYER_PRIMARY_L);
  }
  else if(player_isFlag(PLAYER_PRIMARY_L)) {
    pilot_shootStop(pplayer, 0);
    player_rmFlag(PLAYER_PRIMARY_L);
  }

  /* Secondary. */
   if(player_isFlag(PLAYER_SECONDARY)) {
     /* Double tap stops beams. */
     if(!player_isFlag(PLAYER_SECONDARY_L) &&
         (pplayer->secondary != NULL) &&
         outfit_isBeam(pplayer->secondary->outfit)) {
       pilot_shootStop(pplayer, 1);
     } else
       pilot_shoot(pplayer, player->target, 1);
    player_setFlag(PLAYER_SECONDARY_L);
  }
  else if(player_isFlag(PLAYER_SECONDARY_L)) {
    player_rmFlag(PLAYER_SECONDARY_L);
  }

  /* Afterburn! */
  if(player_isFlag(PLAYER_AFTERBURNER)) {
    if(pilot_isFlag(player, PILOT_AFTERBURNER))
      vect_pset(&pplayer->solid->force,
                pplayer->thrust * pplayer->afterburner->outfit->u.afb.thrust_perc +
                pplayer->afterburner->outfit->u.afb.thrust_abs, pplayer->solid->dir);
    else /* Ran out of energy. */
      player_afterburnOver();
  } else
    vect_pset(&pplayer->solid->force, pplayer->thrust * player_acc,
              pplayer->solid->dir);

  /* Sound. */
  sound_updateListener(pplayer->solid->dir,
      pplayer->solid->pos.x, pplayer->solid->pos.y);

}

/*
 * For use in keybindings.
 */
/**
 * @fn void player_setRadarRel(int mod)
 *
 * @brief Modify the radar resolution.
 *    @param mod Number of intervals to jump (up or down).
 */

void player_setRadarRel(int mod) {
  gui.radar.res += mod * RADAR_RES_INTERVAL;
  if(gui.radar.res > RADAR_RES_MAX) gui.radar.res = RADAR_RES_MAX;
  else if(gui.radar.res < RADAR_RES_MIN) gui.radar.res = RADAR_RES_MIN;

  player_message("Radar set to %dx.", (int)gui.radar.res);
}

/**
 * @fn void player_secondaryNext(void)
 *
 * @brief Get the next secondary weapon.
 */
void player_secondaryNext(void) {
  int i = 0;

  /* Get the current secondary weapon pos. */
  if(player->secondary != NULL)
    for(i = 0; i < player->noutfits; i++)
      if(&player->outfits[i] == player->secondary) {
        i++;
        break;
      }
  /* Get the next secondary weapon. */
  for(; i < player->noutfits; i++)
    if(outfit_isProp(player->outfits[i].outfit, OUTFIT_PROP_WEAP_SECONDARY)) {
      pilot_switchSecondary(player, i);
      break;
    }
  /* We didn't find an outfit. */
  if(i >= player->noutfits)
    pilot_switchSecondary(player, -1);
  /* Set ammo. */
  pilot_setAmmo(player);
}

/**
 * @fn void player_targetPlanet(void)
 *
 * @brief Cycle through planet targets.
 */
void player_targetPlanet(void) {
  hyperspace_target = -1;
  player_rmFlag(PLAYER_LANDACK);

  if((planet_target == -1) && (cur_system->nplanets > 0)) {
    /* No target. */
    planet_target = 0;
    return;
  }

  planet_target++;

  if(planet_target >= cur_system->nplanets)
    /* Last system. */
    planet_target = -1;
}

/**
 * fn void player_land(void)
 *
 * @brief Try to land or target closest planet if no land target.
 */
void player_land(void) {
  int i;
  int tp;
  double td, d;

  if(landed) {
    /* Player is already landed. */
    takeoff();
    return;
  }

  Planet* planet = cur_system->planets[planet_target];
  if(planet_target >= 0) {
    if(!planet_hasService(planet, PLANET_SERVICE_LAND)) {
      player_message("You can't land here.");
      return;
    }
    else if(!player_isFlag(PLAYER_LANDACK)) {
      /* No landing authorization. */
      if(planet_hasService(planet, PLANET_SERVICE_BASIC)) { /* Basic services. */
        if(!areEnemies(player->faction, planet->faction)) { /* Friendly. */
          player_message("%s> Permission to land granted.", planet->name);
          player_setFlag(PLAYER_LANDACK);
        } else /* Hostile. */
          player_message("%s> Land request denied.", planet->name);
      } else { /* No shoes, no shirt, no lifeforms, no services. */
        player_message("Ready to land on %s.", planet->name);
        player_setFlag(PLAYER_LANDACK);
      }
      return;
    }
    else if(vect_dist(&player->solid->pos, &planet->pos) > planet->gfx_space->sw) {
      player_message("You are too far away to land on %s.", planet->name);
      return;
    }
    else if((pow2(VX(player->solid->vel)) + pow2(VY(player->solid->vel))) >
            (double)pow2(MAX_HYPERSPACE_VEL))  {
      player_message("You are going too fast to land on %s.", planet->name);
      return;
    }
    land(planet); /* Land the player. */
  } else {
    /* Get nearest planet target. */
    if(cur_system->nplanets == 0) {
      player_message("There are no planets to land on.");
      return;
    }

    td = -1; /* Temp distance. */
    tp = -1; /* Temp planet. */
    for(i = 0; i < cur_system->nplanets; i++) {
      d = vect_dist(&player->solid->pos, &cur_system->planets[i]->pos);
      if(planet_hasService(cur_system->planets[i], PLANET_SERVICE_LAND) &&
         ((tp == -1) || ((td == -1) || (td > d)))) {
        tp = i;
        td = d;
      }
    }
    planet_target = tp;
    player_rmFlag(PLAYER_LANDACK);

    /* No landable planet. */
    if(planet_target < 0) return;

    player_land(); /* Re-run land protocol. */
  }
}

/**
 * @fn void player_targetHyperspace(void)
 *
 * @brief Get a hyperspace target.
 */
void player_targetHyperspace(void) {
  planet_target = -1; /* Remove planet target. */
  player_rmFlag(PLAYER_LANDACK); /* Get rid of landing permission. */
  hyperspace_target++;
  map_clear(); /* Clear the current map path. */

  if(hyperspace_target >= cur_system->njumps)
    hyperspace_target = -1;
}

/**
 * @fn void player_jump(void)
 *
 * @brief Actually attempt to jump in hyperspace.
 */
void player_jump(void) {
  int i;
  /* Must have a jump target and not be already jumping. */
  if((hyperspace_target == -1) || pilot_isFlag(player, PILOT_HYPERSPACE))
    return;

  /* Already jumping, so we break jump. */
  if(pilot_isFlag(player, PILOT_HYP_PREP)) {
    pilot_hyperspaceAbort(player);
    player_message("Aborting hyperspace sequence.");
    return;
  }

  i = space_hyperspace(player);

  if(i == -1)
    player_message("You are too close to gravity centers to initiate hyperspace.");
  else if(i == -2)
    player_message("You are moving too fast to enter hyperspace.");
  else if(i == -3)
    player_message("You do not have enough fuel to hyperspace jump.");
  else {
    player_message("Preparing for hyperspace.");
    /* Stop acceleration noise. */
    player_accelOver();
    /* Stop possible shooting. */
    pilot_shootStop(player, 0);
    pilot_shootStop(player, 1);
  }
}

/**
 * @fn void player_brokeHyperspace(void)
 *
 * @brief Player actually broke hyperspace (entering new system).
 */
void player_brokeHyperspace(void) {
  unsigned int tl, th;

  /* Calculate the time it takes, call before space_init. */
  tl = (unsigned int) floor(sqrt((double)player->solid->mass)/5.);
  th = (unsigned int) ceil(sqrt((double)player->solid->mass)/5.);
  tl *= LTIME_UNIT_LENGTH;
  th *= LTIME_UNIT_LENGTH;
  ltime_inc(RNG(tl, th));

  /* Enter the new system. */
  space_init(systems_stack[cur_system->jumps[hyperspace_target]].name);

  /* Set position, pilot_update will handle the lowering of velocity. */
  player_warp(-cos(player->solid->dir) * MIN_HYPERSPACE_DIST * 2.5,
              -sin(player->solid->dir) * MIN_HYPERSPACE_DIST * 2.5);

  /* Reduce fuel. */
  player->fuel -= HYPERSPACE_FUEL;

  /* Stop hyperspace. */
  pilot_rmFlag(player, PILOT_HYPERSPACE | PILOT_HYP_BEGIN | PILOT_HYP_PREP);

  /* Update the map. */
  map_jump();

  /* Disable autonavigation if arrived. */
  if(player_isFlag(PLAYER_AUTONAV)) {
    if(hyperspace_target == -1) {
      player_message("Autonav arrived at destination.");
      player_rmFlag(PLAYER_AUTONAV);
    } else {
      player_message("Autonav continuing until destination (%d jump%s left).",
          map_npath, (map_npath==1) ? "" : "s");
    }
  }
  
  /* Run the jump hooks. */
  hooks_run("jump");
  hooks_run("enter");

  player_message("BANG!");
}

/**
 * @fn double player_faceHyperspace(void)
 *
 * @brief Make player face her hyperspace target.
 *    @return direction to face.
 */
double player_faceHyperspace(void) {
  double a;
  a = ANGLE(systems_stack[cur_system->jumps[hyperspace_target]].pos.x -
            cur_system->pos.x,
            systems_stack[cur_system->jumps[hyperspace_target]].pos.y -
            cur_system->pos.y);

  return pilot_face(player, a);
}

/**
 * @fn void player_afterburn(void)
 *
 * @brief Activate the afterburner.
 */
void player_afterburn(void) {
  /* @todo Fancy effects. */
  if((player != NULL) && (player->afterburner != NULL)) {
    player_setFlag(PLAYER_AFTERBURNER);
    pilot_setFlag(player, PILOT_AFTERBURNER);
    spfx_shake(player->afterburner->outfit->u.afb.rumble * SHAKE_MAX);
    sound_stopGroup(PLAYER_ENGINE_CHANNEL);
    sound_playGroup(PLAYER_ENGINE_CHANNEL,
        player->afterburner->outfit->u.afb.sound, 0);
  }
}

/**
 * @fn void player_afterburnOver(void)
 *
 * @brief Deactivate the afterburner.
 */
void player_afterburnOver(void) {
  if((player != NULL) && (player->afterburner != NULL)) {
    player_rmFlag(PLAYER_AFTERBURNER);
    pilot_rmFlag(player, PILOT_AFTERBURNER);
    sound_stopGroup(PLAYER_ENGINE_CHANNEL);
  }
}

/**
 * @fn void player_accel(double acc)
 *
 * @brief Start accelerating.
 *    @param acc How much thrust should be applied of maximum (0 - 1).
 */
void player_accel(double acc) {
  if(player != NULL) {
    player_acc = acc;
    sound_stopGroup(PLAYER_ENGINE_CHANNEL);
    sound_playGroup(PLAYER_ENGINE_CHANNEL,
        player->ship->sound, 0);
  }
}

/**
 * @fn void player_accelOver(void)
 *
 * @brief Done accelerating.
 */
void player_accelOver(void) {
  player_acc = 0;
  sound_stopGroup(PLAYER_ENGINE_CHANNEL);
}

/**
 * @fn void player_targetHostile(void)
 *
 * @brief Target the nearest hostile enemy to the player.
 */
void player_targetHostile(void) {
  unsigned int tp;
  int i;
  double d, td;

  tp = PLAYER_ID;
  d = 0;
  for(i = 0; i < pilot_nstack; i++)
    if(pilot_isFlag(pilot_stack[i], PILOT_HOSTILE) ||
        areEnemies(FACTION_PLAYER, pilot_stack[i]->faction)) {
      td = vect_dist(&pilot_stack[i]->solid->pos, &player->solid->pos);
      if(!pilot_isDisabled(pilot_stack[i]) && ((tp == PLAYER_ID) || (td < d))) {
        d = td;
        tp = pilot_stack[i]->id;
      }
    }

  if((tp != PLAYER_ID) && (tp != player->target))
    player_playSound(snd_target, 1);

  player->target = tp;
}

/**
 * @fn void player_targetNext(void)
 *
 * @brief Cycles to next target.
 */
void player_targetNext(void) {
  player->target = pilot_getNextID(player->target);

  if(player->target != PLAYER_ID)
    player_playSound(snd_target, 1);
}

/**
 * @fn player_targetNearest(void)
 *
 * @brief Player targets nearest pilot.
 */
void player_targetNearest(void) {
  unsigned int t;

  t = player->target;
  player->target = pilot_getNearestPilot(player);

  if((player->target != PLAYER_ID) && (t != player->target))
    player_playSound(snd_target, 1);
}

/**
 * @fn void player_screenshot(void)
 *
 * @brief Take a screenshot.
 */
static int screenshot_cur = 0;  /**< Current screenshot at. */
void player_screenshot(void) {
  FILE* fp;
  int done;
  char filename[PATH_MAX];

  if(lfile_dirMakeExist("screenshots")) {
    WARN("Aborting screenshots");
    return;
  }

  done = 0;

  do {
    if(screenshot_cur >= 999) {
      /* Just incase I fucked up. :) */
      WARN("You have reached the maximum amount of screenshots [999]");
      return;
    }
    snprintf(filename, PATH_MAX, "%sscreenshots/screenshot%03d.png",
             lfile_basePath(), screenshot_cur);
    fp = fopen(filename, "r"); /* Myeah, I know it's a horrible way to check. */
    if(fp == NULL) done = 1;
    else {
      /* Next. */
      screenshot_cur++;
      fclose(fp);
    }
    fp = NULL;
  } while(!done);

  /* Now gief me that screenshot. */
  DEBUG("Taking screenshot [%03d]..", screenshot_cur);
  gl_screenshot(filename);
}

/**
 * @fn void player_dead(void)
 *
 * @brief Player got killed.
 */
void player_dead(void) {
  gui_xoff = 0.;
  gui_yoff = 0.;
}

/**
 * @fn void player_destroyed(void)
 *
 * @brief Player blew up in a nice fireball.
 */
void player_destroyed(void) {
  if(player_isFlag(PLAYER_DESTROYED)) return;

  vectcpy(&player_cam, &player->solid->pos);
  gl_bindCamera(&player_cam);
  player_setFlag(PLAYER_DESTROYED);
  player_timer = SDL_GetTicks() + 5000;
}

/**
 * @fn void player_ships(char** ssships, glTexture** tships)
 *
 * @brief Return a buffer with all the players ship names
 *        or "None" if there are no ships.
 *
 *    @param sships Fills sships with player_nships ship names.
 *    @param tships Fills sships with player_nships ship target textures.
 */
void player_ships(char** sships, glTexture** tships) {
  int i;
  if(player_nstack==0) {
    sships[0] = strdup("None");
    tships[0] = NULL; 
  } else {
    for(i = 0; i < player_nstack; i++) {
      sships[i] = strdup(player_stack[i]->name);
      tships[i] = player_stack[i]->ship->gfx_target;
    }
  }
}

/**
 * @fn int player_nships(void)
 *
 * @brief Get the ammount of ships player has in storage.
 *    @return The number of ships the player has.
 */
int player_nships(void) {
  return player_nstack;
}

/* Return a specific ship. */
Pilot* player_getShip(char* shipname) {
  int i;

  for(i = 0; i < player_nstack; i++)
    if(strcmp(player_stack[i]->name, shipname)==0)
      return player_stack[i];

  WARN("Player ship '%s' not found in stack", shipname);
  return NULL;
}

/* Return location of a specific ship. */
char* player_getLoc(char* shipname) {
  int i;

  for(i = 0; i < player_nstack; i++)
    if(strcmp(player_stack[i]->name, shipname)==0)
      return player_lstack[i];

  WARN("Player ship '%s' not found in stack", shipname);
  return NULL;
}

void player_setLoc(char* shipname, char* loc) {
  int i;

  for(i = 0; i < player_nstack; i++) {
    if(strcmp(player_stack[i]->name, shipname)==0) {
      free(player_lstack[i]);
      player_lstack[i] = strdup(loc);
      return;
    }
  }
  WARN("Player ship '%s' not found in stack", shipname);
}

/* Marks a mission as completed. */
void player_missionFinished(int id) {
  missions_ndone++;
  if(missions_ndone > missions_mdone) {
    /* Need to grow. */
    missions_mdone += 25;
    missions_done = realloc(missions_done, sizeof(int) * missions_mdone);
  }
  missions_done[missions_ndone-1] = id;
}

/* has player already completed a mission? */
int player_missionAlreadyDone(int id) {
  int i;

  for(i = 0; i < missions_ndone; i++)
    if(missions_done[i] == id)
      return 1;
  return 0;
}

/* Save the player in a freaking xmlfile. */
int player_save(xmlTextWriterPtr writer) {
  int i;
  MissionData* m;

  xmlw_startElem(writer, "player");
  xmlw_attr(writer, "name", player_name);

  xmlw_elem(writer, "rating", "%d", player_crating);
  xmlw_elem(writer, "scred", "%d", player->credits);
  xmlw_elem(writer, "time", "%d", ltime_get());

  xmlw_elem(writer, "location", land_planet->name);
  player_saveShip(writer, player, NULL); /* Current ship. */

  xmlw_startElem(writer, "ships");
  for(i = 0; i < player_nstack; i++)
    player_saveShip(writer, player_stack[i], player_lstack[i]);

  xmlw_endElem(writer); /* Player. */

  /* Mission the player has done. */
  xmlw_startElem(writer, "missions_done");

  for(i = 0; i < missions_ndone; i++) {
    m = mission_get(missions_done[i]);
    if(m != NULL) /* In case mission name changes between versions. */
      xmlw_elem(writer, "done", mission_get(missions_done[i])->name);
  }

  xmlw_endElem(writer); /* missions_done. */

  return 0;
}

static int player_saveShip(xmlTextWriterPtr writer, Pilot* ship, char* loc) {
  int i;

  xmlw_startElem(writer, "ship");
  xmlw_attr(writer, "name", ship->name);
  xmlw_attr(writer, "model", ship->ship->name);

  if(loc != NULL) xmlw_elem(writer, "location", loc);

  /* Save the fuel. */
  xmlw_elem(writer, "fuel", "%f", ship->fuel);

  /* Save the outfits. */
  xmlw_startElem(writer, "outfits");
  for(i = 0; i < ship->noutfits; i++) {
    xmlw_startElem(writer, "outfit");

    xmlw_attr(writer, "quantity", "%d", ship->outfits[i].quantity);
    xmlw_str(writer, ship->outfits[i].outfit->name);

    xmlw_endElem(writer); /* Outfit. */
  }
  xmlw_endElem(writer); /* Outfits. */

  /* Save the commodities. */
  xmlw_startElem(writer, "commodities");
  for(i = 0; i < ship->ncommodities; i++) {

    xmlw_startElem(writer, "commodity");

    xmlw_attr(writer, "quantity", "%d", ship->commodities[i].quantity);
    if(ship->commodities[i].id > 0)
      xmlw_attr(writer, "id", "%d", ship->commodities[i].id);
    xmlw_str(writer, ship->commodities[i].commodity->name);

    xmlw_endElem(writer); /* Commodity. */
  }
  xmlw_endElem(writer); /* Commodities. */
  xmlw_endElem(writer); /* Ship. */

  return 0;
}

int player_load(xmlNodePtr parent) {
  xmlNodePtr node;

  /* Some cleanup. */
  player_flags = 0;
  player_cleanup();
  var_cleanup();
  missions_cleanup();

  node = parent->xmlChildrenNode;

  do {
    if(xml_isNode(node, "player"))
      player_parse(node);
    else if(xml_isNode(node, "missions_done"))
      player_parseDone(node);
  } while(xml_nextNode(node));

  return 0;
}

static int player_parse(xmlNodePtr parent) {
  unsigned int player_time;
  char* planet;
  Planet* pnt;
  int sw, sh;
  xmlNodePtr node, cur;

  node = parent->xmlChildrenNode;

  xmlr_attr(parent, "name", player_name);

  do {
    /* Global stuff. */
    xmlr_int(node, "rating", player_crating);
    xmlr_int(node, "credits", player_credits);
    xmlr_long(node, "time", player_time);
    xmlr_str(node, "location", planet);

    if(xml_isNode(node, "ship"))
      player_parseShip(node, 1);

    if(xml_isNode(node, "ships")) {
      cur = node->xmlChildrenNode;
      do {
        if(xml_isNode(cur, "ship"))
          player_parseShip(cur, 0);
      } while(xml_nextNode(cur));
    }
    if(xml_isNode(node, "missions_done"))
      player_parseDone(node);
  } while(xml_nextNode(node));

  /* Set global thingies. */
  player->credits = player_credits;
  ltime_set(player_time);

  /* Set player in system. */
  pnt = planet_get(planet);
  sw = pnt->gfx_space->sw;
  sh = pnt->gfx_space->sh;
  player_warp(pnt->pos.x + RNG(-sw/2, sw/2),
      pnt->pos.y + RNG(-sh/2, sh/2));
  player->solid->dir = RNG(0, 359) * M_PI/180.;
  gl_bindCamera(&player->solid->pos);

  /* Initialize the system. */
  space_init(planet_getSystem(planet));
  map_clear(); /* Sets the map up. */

  /* Initialize the sound. */
  player_initSound();

  return 0;
}

static int player_parseDone(xmlNodePtr parent) {
  xmlNodePtr node;

  node = parent->xmlChildrenNode;

  do {
    if(xml_isNode(node, "done"))
      player_missionFinished(mission_getID(xml_get(node)));
  } while(xml_nextNode(node));

  return 0;
}

static int player_parseShip(xmlNodePtr parent, int is_player) {
  char* name, *model, *loc, *q, *id;
  int i, n;
  double fuel;
  Pilot* ship;
  Outfit* o;
  xmlNodePtr node, cur;

  xmlr_attr(parent, "name", name);
  xmlr_attr(parent, "model", model);

  /* Player is currently on this ship. */
  if(is_player != 0) {
    pilot_create(ship_get(model), name, faction_get("Player"), NULL, 0., NULL,
        NULL, PILOT_PLAYER | PILOT_NO_OUTFITS);
    ship = player;
  } else
    ship = pilot_createEmpty(ship_get(model), name, faction_get("Player"), NULL,
        PILOT_PLAYER | PILOT_NO_OUTFITS);

  free(name);
  free(model);

  node = parent->xmlChildrenNode;

  fuel = 0;

  do {
    if(!is_player == 0) xmlr_str(node, "location", loc);

    /* Get fuel. */
    xmlr_float(node, "fuel", fuel);

    if(xml_isNode(node, "outfits")) {
      cur = node->xmlChildrenNode;
      do {
        /* Load each outfit. */
        if(xml_isNode(cur, "outfits")) {
          xmlr_attr(cur, "quantity", q);
          n = atoi(q);
          free(q);
          /* Add the outfit. */
          o = outfit_get(xml_get(cur));
          if(o != NULL)
            pilot_addOutfit(ship, o, n);
        }
      } while(xml_nextNode(cur));
    }
    if(xml_isNode(node, "commodities")) {
      cur = node->xmlChildrenNode;
      do {
        if(xml_isNode(cur, "commodity")) {
          xmlr_attr(cur, "quantity", q);
          xmlr_attr(cur, "id", id);
          n = atoi(q);
          if(id == NULL) i = 0;
          else i = atoi(id);
          free(q);
          if(id != NULL) free(id);

          /* Actually add the cargo with id hack. */
          pilot_addCargo(ship, commodity_get(xml_get(cur)), n);
          if(i != 0) ship->commodities[ship->ncommodities-1].id = i;
        }
      } while(xml_nextNode(node));
    }
  } while(xml_nextNode(node));

  /* Set fuel. */
  if(fuel != 0)
    ship->fuel = MIN(ship->fuel_max, fuel);

  /* Add it to the stack if it's not what the player is in. */
  if(is_player == 0) {
    player_stack = realloc(player_stack, sizeof(Pilot*)*(player_nstack+1));
    player_stack[player_nstack] = ship;
    player_lstack = realloc(player_lstack, sizeof(char*)*(player_nstack+1));
    player_lstack[player_nstack] = strdup(loc);
    player_nstack++;
  }

  return 0;
}