Permalink
Fetching contributors…
Cannot retrieve contributors at this time
3853 lines (3220 sloc) 96.4 KB
/*
* See Licensing and Copyright notice in naev.h
*/
/**
* @file player.c
*
* @brief Contains all the player related stuff.
*/
#include "player.h"
#include "naev.h"
#include <stdlib.h>
#include "nxml.h"
#include "pilot.h"
#include "log.h"
#include "opengl.h"
#include "font.h"
#include "ndata.h"
#include "space.h"
#include "rng.h"
#include "land.h"
#include "land_outfits.h"
#include "sound.h"
#include "economy.h"
#include "pause.h"
#include "menu.h"
#include "toolkit.h"
#include "dialogue.h"
#include "mission.h"
#include "nlua_misn.h"
#include "ntime.h"
#include "hook.h"
#include "map.h"
#include "map_overlay.h"
#include "nfile.h"
#include "spfx.h"
#include "unidiff.h"
#include "comm.h"
#include "intro.h"
#include "perlin.h"
#include "ai.h"
#include "music.h"
#include "gui.h"
#include "gui_omsg.h"
#include "nlua_var.h"
#include "escort.h"
#include "event.h"
#include "conf.h"
#include "nebula.h"
#include "equipment.h"
#include "camera.h"
#include "claim.h"
#include "player_gui.h"
#include "start.h"
#include "input.h"
#include "news.h"
#include "nstring.h"
/*
* player stuff
*/
Player_t player; /**< Local player. */
static Ship* player_ship = NULL; /**< Temporary ship to hold when naming it */
static credits_t player_creds = 0; /**< Temporary hack for when creating. */
static const char *player_message_noland = NULL; /**< No landing message (when PLAYER_NOLAND is set). */
/*
* Licenses.
*/
static char **player_licenses = NULL; /**< Licenses player has. */
static int player_nlicenses = 0; /**< Number of licenses player has. */
/*
* player sounds.
*/
static int player_engine_group = -1; /**< Player engine sound group. */
static int player_hyper_group = -1; /**< Player hyperspace sound group. */
static int player_gui_group = -1; /**< Player GUI sound group. */
int snd_target = -1; /**< Sound when targeting. */
int snd_jump = -1; /**< Sound when can jump. */
int snd_nav = -1; /**< Sound when changing nav computer. */
int snd_hail = -1; /**< Sound when being hailed. */
/* Hyperspace sounds. */
int snd_hypPowUp = -1; /**< Hyperspace power up sound. */
int snd_hypEng = -1; /**< Hyperspace engine sound. */
int snd_hypPowDown = -1; /**< Hyperspace power down sound. */
int snd_hypPowUpJump = -1; /**< Hyperspace Power up to jump sound. */
int snd_hypJump = -1; /**< Hyperspace jump sound. */
static int player_lastEngineSound = -1; /**< Last engine sound. */
static int player_hailCounter = 0; /**< Number of times to play the hail. */
static double player_hailTimer = 0.; /**< Timer for hailing. */
/*
* player pilot stack - ships he has
*/
static PlayerShip_t* player_stack = NULL; /**< Stack of ships player has. */
static int player_nstack = 0; /**< Number of ships player has. */
/*
* player outfit stack - outfits he has
*/
static PlayerOutfit_t *player_outfits = NULL; /**< Outfits player has. */
static int player_noutfits = 0; /**< Number of outfits player has. */
static int player_moutfits = 0; /**< Current allocated memory. */
#define OUTFIT_CHUNKSIZE 32 /**< Allocation chunk size. */
/*
* player global properties
*/
/* used in input.c */
double player_left = 0.; /**< Player left turn velocity from input. */
double player_right = 0.; /**< Player right turn velocity from input. */
double player_acc = 0.; /**< Accel velocity from input. */
/* for death and such */
static double player_timer = 0.; /**< For death and such. */
/*
* unique mission stack.
*/
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. */
/*
* unique event stack.
*/
static int* events_done = NULL; /**< Saves position of completed events. */
static int events_mdone = 0; /**< Memory size of completed events. */
static int events_ndone = 0; /**< Number of completed events. */
/*
* Extern stuff for player ships.
*/
extern Pilot** pilot_stack;
extern int pilot_nstack;
/*
* map stuff for autonav
*/
extern int map_npath;
/*
* prototypes
*/
/*
* internal
*/
static void player_checkHail (void);
/* creation */
static void player_newSetup( int tutorial );
static int player_newMake (void);
static Pilot* player_newShipMake( const char* name );
/* sound */
static void player_initSound (void);
/* save/load */
static int player_saveEscorts( xmlTextWriterPtr writer );
static int player_saveShipSlot( xmlTextWriterPtr writer, PilotOutfitSlot *slot, int i );
static int player_saveShip( xmlTextWriterPtr writer,
Pilot* ship, char* loc );
static Planet* player_parse( xmlNodePtr parent );
static int player_parseDoneMissions( xmlNodePtr parent );
static int player_parseDoneEvents( xmlNodePtr parent );
static int player_parseLicenses( xmlNodePtr parent );
static void player_parseShipSlot( xmlNodePtr node, Pilot *ship, PilotOutfitSlot *slot );
static int player_parseShip( xmlNodePtr parent, int is_player, char *planet );
static int player_parseEscorts( xmlNodePtr parent );
static void player_addOutfitToPilot( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s );
/* Misc. */
static int player_filterSuitablePlanet( Planet *p );
static void player_planetOutOfRangeMsg (void);
static int player_outfitCompare( const void *arg1, const void *arg2 );
static int player_thinkMouseFly(void);
static int preemption = 0; /* Hyperspace target/untarget preemption. */
/*
* externed
*/
int player_save( xmlTextWriterPtr writer ); /* save.c */
Planet* player_load( xmlNodePtr parent ); /* save.c */
/**
* @brief Initializes player stuff.
*/
int player_init (void)
{
player_initSound();
return 0;
}
/**
* @brief Sets up a new player.
*/
static void player_newSetup( int tutorial )
{
double x, y;
/* Set up GUI. */
gui_setDefaults();
/* Setup sound */
player_initSound();
/* Clean up player stuff if we'll be recreating. */
player_cleanup();
/* For pretty background. */
pilots_cleanAll();
if (!tutorial) {
space_init( start_system() );
start_position( &x, &y );
}
else
start_tutPosition( &x, &y );
cam_setTargetPos( x, y, 0 );
cam_setZoom( conf.zoom_far );
/* Clear the init message for new game. */
gui_clearMessages();
}
/**
* @brief Creates a new tutorial player.
*
* - Cleans up after old players.
* - Prompts for name.
*/
void player_newTutorial (void)
{
int ret;
double x, y;
/* Set up new player. */
player_newSetup( 1 );
/* New name. */
player.name = strdup( "John Doe" );
/* Time. */
ntime_set( start_date() );
/* Welcome message - must be before space_init. */
player_message( "\egWelcome to "APPNAME" Tutorial!" );
player_message( "\eg v%s", naev_version(0) );
/* Try to create the pilot, if fails reask for player name. */
player_ship = ship_get( start_ship() );
if (player_ship==NULL) {
WARN("Ship not properly set by module.");
return;
}
player_creds = 0;
player_newShipMake( "Star Voyager" );
start_tutPosition( &x, &y );
vect_cset( &player.p->solid->pos, x, y );
vectnull( &player.p->solid->vel );
player.p->solid->dir = RNGF() * 2.*M_PI;
space_init( start_tutSystem() );
/* Monies. */
player.p->credits = start_credits();
/* clear the map */
map_clear();
/* Start the economy. */
economy_init();
/* Start the news */
news_init();
/* Play music. */
music_choose( "ambient" );
/* Load the GUI. */
gui_load( gui_pick() );
/* It's the tutorial. */
player_setFlag( PLAYER_TUTORIAL );
/* Add the mission if found. */
if (start_tutMission() != NULL) {
ret = mission_start(start_tutMission(), NULL);
if (ret < 0)
WARN("Failed to run start tutorial mission '%s'.", start_tutMission());
}
/* Add the event if found. */
if (!menu_isOpen(MENU_MAIN) && (start_tutEvent() != NULL)) {
if (event_start( start_tutEvent(), NULL ))
WARN("Failed to run start event '%s'.", start_tutEvent());
}
}
/**
* @brief Creates a new player.
*
* - Cleans up after old players.
* - Prompts for name.
*
* @sa player_newMake
*/
void player_new (void)
{
int r;
/* Set up new player. */
player_newSetup( 0 );
/* Get the name. */
player.name = dialogue_input( "Player Name", 2, 20,
"Please write your name:" );
/* Player cancelled dialogue. */
if (player.name == NULL) {
menu_main();
return;
}
if (nfile_fileExists("%ssaves/%s.ns", nfile_dataPath(), player.name)) {
r = dialogue_YesNo("Overwrite",
"You already have a pilot named %s. Overwrite?",player.name);
if (r==0) { /* no */
player_new();
return;
}
}
if (player_newMake())
return;
/* Display the intro. */
intro_display( "dat/intro", "intro" );
/* Play music. */
music_choose( "ambient" );
/* Add the mission if found. */
if (start_mission() != NULL) {
if (mission_start(start_mission(), NULL) < 0)
WARN("Failed to run start mission '%s'.", start_mission());
}
/* Add the event if found. */
if (start_event() != NULL) {
if (event_start( start_event(), NULL ))
WARN("Failed to run start event '%s'.", start_event());
}
/* Run the load event trigger. */
events_trigger( EVENT_TRIGGER_LOAD );
/* Load the GUI. */
gui_load( gui_pick() );
}
/**
* @brief Actually creates a new player.
*
* @return 0 on success.
*/
static int player_newMake (void)
{
Ship *ship;
const char *shipname;
double x,y;
/* Time. */
ntime_set( start_date() );
/* Welcome message - must be before space_init. */
player_message( "\egWelcome to "APPNAME"!" );
player_message( "\eg v%s", naev_version(0) );
/* Try to create the pilot, if fails reask for player name. */
ship = ship_get( start_ship() );
shipname = start_shipname();
if (ship==NULL) {
WARN("Ship not properly set by module.");
return -1;
}
/* Setting a default name in the XML prevents naming prompt. */
if (player_newShip( ship, shipname, 0, (shipname==NULL) ? 0 : 1 ) == NULL) {
player_new();
return -1;
}
start_position( &x, &y );
vect_cset( &player.p->solid->pos, x, y );
vectnull( &player.p->solid->vel );
player.p->solid->dir = RNGF() * 2.*M_PI;
space_init( start_system() );
/* Monies. */
player.p->credits = start_credits();
/* clear the map */
map_clear();
/* Start the economy. */
economy_init();
/* Start the news */
news_init();
return 0;
}
/**
* @brief Creates a new ship for player.
*
* @param ship New ship to get.
* @param def_name Default name to give it if cancelled.
* @param trade Whether or not to trade player's current ship with the new ship.
* @param noname Whether or not to let the player name it.
* @return Newly created pilot on success or NULL if dialogue was cancelled.
*
* @sa player_newShipMake
*/
Pilot* player_newShip( Ship* ship, const char *def_name,
int trade, int noname )
{
char *ship_name, *old_name;
int i, len, w;
Pilot *new_ship;
/* temporary values while player doesn't exist */
player_creds = (player.p != NULL) ? player.p->credits : 0;
player_ship = ship;
if (!noname)
ship_name = dialogue_input( "Ship Name", 3, 20,
"Please name your new ship:" );
else
ship_name = NULL;
/* Dialogue cancelled. */
if (ship_name == NULL) {
/* No default name, fail. */
if (def_name == NULL)
return NULL;
/* Add default name. */
i = 2;
len = strlen(def_name)+10;
ship_name = malloc( len );
strncpy( ship_name, def_name, len );
while (player_hasShip(ship_name)) {
nsnprintf( ship_name, len, "%s %d", def_name, i );
i++;
}
}
/* Must not have same name. */
if (player_hasShip(ship_name)) {
dialogue_msg( "Name collision",
"Please do not give the ship the same name as another of your ships.");
return NULL;
}
new_ship = player_newShipMake( ship_name );
/* Player is trading ship in. */
if (trade) {
if (player.p == NULL)
ERR("Player ship isn't valid... This shouldn't happen!");
old_name = player.p->name;
player_swapShip( ship_name ); /* Move to the new ship. */
player_rmShip( old_name );
}
free(ship_name);
/* Update ship list if landed. */
if (landed) {
w = land_getWid( LAND_WINDOW_EQUIPMENT );
equipment_regenLists( w, 0, 1 );
}
return new_ship;
}
/**
* @brief Actually creates the new ship.
*/
static Pilot* player_newShipMake( const char* name )
{
Vector2d vp, vv;
PilotFlags flags;
PlayerShip_t *ship;
Pilot *new_pilot;
double px, py, dir;
unsigned int id;
/* store the current ship if it exists */
pilot_clearFlagsRaw( flags );
pilot_setFlagRaw( flags, PILOT_PLAYER );
/* in case we're respawning */
player_rmFlag( PLAYER_CREATING );
/* create the player */
if (player.p == NULL) {
/* Set position to defaults. */
if (player.p != NULL) {
px = player.p->solid->pos.x;
py = player.p->solid->pos.y;
dir = player.p->solid->dir;
}
else {
px = 0.;
py = 0.;
dir = 0.;
}
vect_cset( &vp, px, py );
vect_cset( &vv, 0., 0. );
/* Create the player. */
id = pilot_create( player_ship, name, faction_get("Player"), "player",
dir, &vp, &vv, flags, -1 );
cam_setTargetPilot( id, 0 );
new_pilot = pilot_get( id );
}
else {
/* Grow memory. */
player_stack = realloc(player_stack, sizeof(PlayerShip_t)*(player_nstack+1));
ship = &player_stack[player_nstack];
/* Create the ship. */
ship->p = pilot_createEmpty( player_ship, name, faction_get("Player"), "player", flags );
ship->loc = strdup( land_planet->name );
player_nstack++;
new_pilot = ship->p;
}
if (player.p == NULL)
ERR("Something seriously wonky went on, newly created player does not exist, bailing!");
/* Add GUI. */
player_guiAdd( player_ship->gui );
/* money. */
player.p->credits = player_creds;
player_creds = 0;
return new_pilot;
}
/**
* @brief Swaps player's current ship with their ship named shipname.
*
* @param shipname Ship to change to.
*/
void player_swapShip( char* shipname )
{
int i, j;
Pilot* ship;
Vector2d v;
double dir;
for (i=0; i<player_nstack; i++) {
if (strcmp(shipname,player_stack[i].p->name)!=0)
continue;
/* swap player and ship */
ship = player_stack[i].p;
/* move credits over */
ship->credits = player.p->credits;
/* move cargo over */
pilot_cargoMove( ship, player.p );
/* Store position. */
v = player.p->solid->pos;
dir = player.p->solid->dir;
/* extra pass to calculate stats */
pilot_calcStats( ship );
pilot_calcStats( player.p );
/* now swap the players */
player_stack[i].p = player.p;
for (j=0; j<pilot_nstack; j++) /* find pilot in stack to swap */
if (pilot_stack[j] == player.p) {
player.p = ship;
pilot_stack[j] = ship;
break;
}
/* Copy position back. */
player.p->solid->pos = v;
player.p->solid->dir = dir;
/* Fill the tank. */
if (landed)
land_refuel();
/* Set some gui stuff. */
gui_load( gui_pick() );
/* Bind camera. */
cam_setTargetPilot( player.p->id, 0 );
return;
}
WARN( "Unable to swap player.p with ship '%s': ship does not exist!", shipname );
}
/**
* @brief Calculates the price of one of the player's ships.
*
* @param shipname Name of the ship.
* @return The price of the ship in credits.
*/
credits_t player_shipPrice( char* shipname )
{
int i;
Pilot *ship = NULL;
if (strcmp(shipname,player.p->name)==0)
ship = player.p;
else {
/* Find the ship. */
for (i=0; i<player_nstack; i++) {
if (strcmp(shipname,player_stack[i].p->name)==0) {
ship = player_stack[i].p;
break;
}
}
}
/* Not found. */
if (ship == NULL) {
WARN( "Unable to find price for player's ship '%s': ship does not exist!", shipname );
return -1;
}
return pilot_worth( ship );
}
/**
* @brief Removes one of the player's ships.
*
* @param shipname Name of the ship to remove.
*/
void player_rmShip( char* shipname )
{
int i, w;
for (i=0; i<player_nstack; i++) {
/* Not the ship we are looking for. */
if (strcmp(shipname,player_stack[i].p->name)!=0)
continue;
/* Free player ship and location. */
pilot_free(player_stack[i].p);
free(player_stack[i].loc);
/* Move memory to make adjacent. */
memmove( player_stack+i, player_stack+i+1,
sizeof(PlayerShip_t) * (player_nstack-i-1) );
player_nstack--; /* Shrink stack. */
/* Realloc memory to smaller size. */
player_stack = realloc( player_stack,
sizeof(PlayerShip_t) * (player_nstack) );
}
/* Update ship list if landed. */
if (landed) {
w = land_getWid( LAND_WINDOW_EQUIPMENT );
equipment_regenLists( w, 0, 1 );
}
}
/**
* @brief Cleans up player stuff like player_stack.
*/
void player_cleanup (void)
{
int i;
/* Enable all input. */
input_enableAll();
/* Clean up other stuff. */
diff_clear();
var_cleanup();
missions_cleanup();
events_cleanup();
space_clearKnown();
land_cleanup();
map_cleanup();
/* Reset controls. */
player_accelOver();
player_left = 0.;
player_right = 0.;
/* Clear player. */
player_clear();
/* Clear hail timer. */
player_hailCounter = 0;
player_hailTimer = 0.;
/* Clear messages. */
gui_clearMessages();
/* Reset factions. */
factions_reset();
/* clean up name */
if (player.name != NULL) {
free(player.name);
player.name = NULL;
}
/* Clean up gui. */
gui_cleanup();
player_guiCleanup();
ovr_setOpen(0);
/* clean up the stack */
for (i=0; i<player_nstack; i++) {
pilot_free(player_stack[i].p);
free(player_stack[i].loc);
}
if (player_stack != NULL)
free(player_stack);
player_stack = NULL;
/* nothing left */
player_nstack = 0;
/* Free outfits. */
if (player_outfits != NULL)
free(player_outfits);
player_outfits = NULL;
player_noutfits = 0;
player_moutfits = 0;
/* Clean up missions */
if (missions_done != NULL)
free(missions_done);
missions_done = NULL;
missions_ndone = 0;
missions_mdone = 0;
/* Clean up events. */
if (events_done != NULL)
free(events_done);
events_done = NULL;
events_ndone = 0;
events_mdone = 0;
/* Clean up licenses. */
if (player_nlicenses > 0) {
for (i=0; i<player_nlicenses; i++)
free(player_licenses[i]);
free(player_licenses);
player_licenses = NULL;
player_nlicenses = 0;
}
/* Clear claims. */
claim_clear();
/* just in case purge the pilot stack */
pilots_cleanAll();
/* Reset some player stuff. */
player_creds = 0;
player.crating = 0;
if (player.gui != NULL)
free( player.gui );
player.gui = NULL;
/* Clear omsg. */
omsg_cleanup();
/* Stop the sounds. */
sound_stopAll();
/* Reset time compression. */
pause_setSpeed( 1.0 );
/* Clean up. */
memset( &player, 0, sizeof(Player_t) );
player_setFlag(PLAYER_CREATING);
}
static int player_soundReserved = 0; /**< Has the player already reserved sound? */
/**
* @brief Initializes the player sounds.
*/
static void player_initSound (void)
{
if (player_soundReserved)
return;
/* Allocate channels. */
player_engine_group = sound_createGroup(1); /* Channel for engine noises. */
player_gui_group = sound_createGroup(4);
player_hyper_group = sound_createGroup(4);
sound_speedGroup( player_gui_group, 0 ); /* Disable pitch shift. */
player_soundReserved = 1;
/* Get sounds. */
snd_target = sound_get("target");
snd_jump = sound_get("jump");
snd_nav = sound_get("nav");
snd_hail = sound_get("hail");
snd_hypPowUp = sound_get("hyperspace_powerup");
snd_hypEng = sound_get("hyperspace_engine");
snd_hypPowDown = sound_get("hyperspace_powerdown");
snd_hypPowUpJump = sound_get("hyperspace_powerupjump");
snd_hypJump = sound_get("hyperspace_jump");
}
/**
* @brief Plays a GUI sound (unaffected by time accel).
*
* @param sound ID of the sound to play.
* @param once Play only once?
*/
void player_soundPlayGUI( int sound, int once )
{
sound_playGroup( player_gui_group, sound, once );
}
/**
* @brief Plays a sound at the player.
*
* @param sound ID of the sound to play.
* @param once Play only once?
*/
void player_soundPlay( int sound, int once )
{
sound_playGroup( player_hyper_group, sound, once );
}
/**
* @brief Stops playing player sounds.
*/
void player_soundStop (void)
{
if (player_gui_group >= 0)
sound_stopGroup( player_gui_group );
if (player_engine_group >= 0)
sound_stopGroup( player_engine_group );
if (player_hyper_group >= 0)
sound_stopGroup( player_hyper_group );
/* No last engine sound. */
player_lastEngineSound = -1;
}
/**
* @brief Pauses the ship's sounds.
*/
void player_soundPause (void)
{
if (player_engine_group >= 0)
sound_pauseGroup(player_engine_group);
if (player_hyper_group >= 0)
sound_pauseGroup(player_hyper_group);
}
/**
* @brief Resumes the ship's sounds.
*/
void player_soundResume (void)
{
if (player_engine_group >= 0)
sound_resumeGroup(player_engine_group);
if (player_hyper_group >= 0)
sound_resumeGroup(player_hyper_group);
}
/**
* @brief Warps 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.p->solid->pos, x, y );
}
/**
* @brief Clears the targets.
*/
void player_clear (void)
{
if (player.p != NULL) {
pilot_setTarget( player.p, player.p->id );
gui_setTarget();
}
/* Clear the noland flag. */
player_rmFlag( PLAYER_NOLAND );
}
static char* player_ratings[] = {
"Harmless",
"Mostly Harmless",
"Smallfry",
"Average",
"Above Average",
"Major",
"Intimidating",
"Fearsome",
"Terrifying",
"Unstoppable",
"Godlike"
}; /**< Combat ratings. */
/**
* @brief Gets the player's combat rating in a human-readable string.
*
* @return The player's combat rating in a human readable string.
*/
const char* player_rating (void)
{
if (player.crating == 0.) return player_ratings[0];
else if (player.crating < 25.) return player_ratings[1];
else if (player.crating < 50.) return player_ratings[2];
else if (player.crating < 100.) return player_ratings[3];
else if (player.crating < 200.) return player_ratings[4];
else if (player.crating < 500.) return player_ratings[5];
else if (player.crating < 1000.) return player_ratings[6];
else if (player.crating < 2000.) return player_ratings[7];
else if (player.crating < 5000.) return player_ratings[8];
else if (player.crating < 10000.) return player_ratings[9];
else return player_ratings[10];
}
/**
* @brief Checks to see if the player has enough credits.
*
* @param amount Amount of credits to check to see if the player has.
* @return 1 if the player has enough credits.
*/
int player_hasCredits( credits_t amount )
{
return pilot_hasCredits( player.p, amount );
}
/**
* @brief Modifies the amount of credits the player has.
*
* @param amount Quantity to modify player's credits by.
* @return Amount of credits the player has.
*/
credits_t player_modCredits( credits_t amount )
{
return pilot_modCredits( player.p, amount );
}
/**
* @brief Renders the player
*/
void player_render( double dt )
{
/*
* Check to see if the death menu should pop up.
*/
if (player_isFlag(PLAYER_DESTROYED)) {
player_timer -= dt;
if (!toolkit_isOpen() && !player_isFlag(PLAYER_CREATING) &&
(player_timer < 0.))
menu_death();
}
/*
* Render the player.
*/
if ((player.p != NULL) && !player_isFlag(PLAYER_CREATING) &&
!pilot_isFlag( player.p, PILOT_INVISIBLE))
pilot_render(player.p, dt);
}
/**
* @brief Basically uses keyboard input instead of AI input. Used in pilot.c.
*
* @param pplayer Player to think.
*/
void player_think( Pilot* pplayer, const double dt )
{
(void) dt;
Pilot *target;
double turn;
int facing, fired;
/* last i heard, the dead don't think */
if (pilot_isFlag(pplayer,PILOT_DEAD)) {
/* no sense in accelerating or turning */
pilot_setThrust( pplayer, 0. );
pilot_setTurn( pplayer, 0. );
return;
}
/* Under manual control is special. */
if (pilot_isFlag( pplayer, PILOT_MANUAL_CONTROL )) {
ai_think( pplayer, dt );
return;
}
/* Not facing anything yet. */
facing = 0;
/* Autonav takes over normal controls. */
if (player_isFlag(PLAYER_AUTONAV)) {
player_thinkAutonav( pplayer, dt );
/* Disable turning. */
facing = 1;
}
/* Mouse-flying is enabled. */
if (!facing && player_isFlag(PLAYER_MFLY))
facing = player_thinkMouseFly();
/* turning taken over by PLAYER_FACE */
if (!facing && player_isFlag(PLAYER_FACE)) {
/* Try to face pilot target. */
if (player.p->target != PLAYER_ID) {
target = pilot_get(player.p->target);
if (target != NULL) {
pilot_face( pplayer,
vect_angle( &player.p->solid->pos, &target->solid->pos ));
/* Disable turning. */
facing = 1;
}
}
/* If not try to face planet target. */
else if ((player.p->nav_planet != -1) && ((preemption == 0) || (player.p->nav_hyperspace == -1))) {
pilot_face( pplayer,
vect_angle( &player.p->solid->pos,
&cur_system->planets[ player.p->nav_planet ]->pos ));
/* Disable turning. */
facing = 1;
}
else if (player.p->nav_hyperspace != -1) {
pilot_face( pplayer,
vect_angle( &player.p->solid->pos,
&cur_system->jumps[ player.p->nav_hyperspace ].pos ));
/* Disable turning. */
facing = 1;
}
}
/* turning taken over by PLAYER_REVERSE */
if (player_isFlag(PLAYER_REVERSE)) {
/* Check to see if already stopped. */
/*
if (VMOD(pplayer->solid->vel) < MIN_VEL_ERR)
player_accel( 0. );
else {
d = pilot_face( pplayer, VANGLE(player.p->solid->vel) + M_PI );
if ((player_acc < 1.) && (d < MAX_DIR_ERR))
player_accel( 1. );
}
*/
/*
* If the player has reverse thrusters, fire those.
*/
if (player.p->stats.misc_reverse_thrust)
player_accel( -PILOT_REVERSE_THRUST );
else if (!facing){
pilot_face( pplayer, VANGLE(player.p->solid->vel) + M_PI );
/* Disable turning. */
facing = 1;
}
}
/* normal turning scheme */
if (!facing) {
turn = 0;
if (player_isFlag(PLAYER_TURN_LEFT))
turn -= player_left;
if (player_isFlag(PLAYER_TURN_RIGHT))
turn += player_right;
turn = CLAMP( -1., 1., turn );
pilot_setTurn( pplayer, -turn );
}
/*
* Weapon shooting stuff
*/
fired = 0;
/* Primary weapon. */
if (player_isFlag(PLAYER_PRIMARY)) {
fired |= pilot_shoot( pplayer, 0 );
player_setFlag(PLAYER_PRIMARY_L);
}
else if (player_isFlag(PLAYER_PRIMARY_L)) {
pilot_shootStop( pplayer, 0 );
player_rmFlag(PLAYER_PRIMARY_L);
}
/* Secondary weapon - we use PLAYER_SECONDARY_L to track last frame. */
if (player_isFlag(PLAYER_SECONDARY)) { /* needs target */
fired |= pilot_shoot( pplayer, 1 );
player_setFlag(PLAYER_SECONDARY_L);
}
else if (player_isFlag(PLAYER_SECONDARY_L)) {
pilot_shootStop( pplayer, 1 );
player_rmFlag(PLAYER_SECONDARY_L);
}
if (fired) {
player.autonav_timer = MAX( player.autonav_timer, 1. );
player_autonavResetSpeed();
}
pilot_setThrust( pplayer, player_acc );
}
/**
* @brief Player update function.
*
* @param pplayer Player to update.
* @param dt Current deltatick.
*/
void player_update( Pilot *pplayer, const double dt )
{
/* Update normally. */
pilot_update( pplayer, dt );
/* Update player.p specific stuff. */
if (!player_isFlag(PLAYER_DESTROYED))
player_updateSpecific( pplayer, dt );
}
/**
* @brief Does a player specific update.
*
* @param pplayer Player to update.
* @param dt Current deltatick.
*/
void player_updateSpecific( Pilot *pplayer, const double dt )
{
int engsound;
/* Calculate engine sound to use. */
if (pilot_isFlag(pplayer, PILOT_AFTERBURNER))
engsound = pplayer->afterburner->outfit->u.afb.sound;
else if ((pplayer->solid->thrust > 1e-3) || (pplayer->solid->thrust < -1e-3)) {
/* See if is in hyperspace. */
if (pilot_isFlag(pplayer, PILOT_HYPERSPACE))
engsound = snd_hypEng;
else
engsound = pplayer->ship->sound;
}
else
engsound = -1;
/* See if sound must change. */
if (player_lastEngineSound != engsound) {
sound_stopGroup( player_engine_group );
if (engsound >= 0)
sound_playGroup( player_engine_group, engsound, 0 );
}
player_lastEngineSound = engsound;
/* Sound. */
/*
* Sound is now camera-specific and thus not player specific. A bit sad really.
sound_updateListener( pplayer->solid->dir,
pplayer->solid->pos.x, pplayer->solid->pos.y,
pplayer->solid->vel.x, pplayer->solid->vel.y );
*/
/* See if must play hail sound. */
if (player_hailCounter > 0) {
player_hailTimer -= dt;
if (player_hailTimer < 0.) {
player_soundPlayGUI( snd_hail, 1 );
player_hailCounter--;
player_hailTimer = 3.;
}
}
}
/*
* For use in keybindings
*/
/**
* @brief Activates a player's weapon set.
*/
void player_weapSetPress( int id, int type, int repeat )
{
if (repeat)
return;
if ((type > 0) && ((player.p == NULL) || toolkit_isOpen()))
return;
if (player.p == NULL)
return;
if (pilot_isFlag(player.p, PILOT_HYP_PREP) ||
pilot_isFlag(player.p, PILOT_HYPERSPACE) ||
pilot_isFlag(player.p, PILOT_LANDING) ||
pilot_isFlag(player.p, PILOT_TAKEOFF))
return;
pilot_weapSetPress( player.p, id, type );
}
/**
* @brief Aborts autonav and other states that take control of the ship.
*
* @param reason Reason for aborting (see player.h)
* @param str String accompanying the reason.
*/
void player_restoreControl( int reason, char *str )
{
if (player.p==NULL)
return;
if (reason != PINPUT_AUTONAV) {
/* Autonav should be harder to abort when paused. */
if (!paused || reason != PINPUT_MOVEMENT)
player_autonavAbort(str);
}
if (reason != PINPUT_BRAKING) {
pilot_rmFlag(player.p, PILOT_BRAKING);
pilot_rmFlag(player.p, PILOT_COOLDOWN_BRAKE);
if (pilot_isFlag(player.p, PILOT_COOLDOWN))
pilot_cooldownEnd(player.p, str);
}
}
/**
* @brief Sets the player's target planet.
*
* @param id Target planet or -1 if none should be selected.
*/
void player_targetPlanetSet( int id )
{
int old;
if (id >= cur_system->nplanets) {
WARN("Trying to set player's planet target to invalid ID '%d'", id);
return;
}
if ((player.p == NULL) || pilot_isFlag( player.p, PILOT_LANDING ))
return;
old = player.p->nav_planet;
player.p->nav_planet = id;
player_hyperspacePreempt((id < 0) ? 1 : 0);
if (old != id) {
player_rmFlag(PLAYER_LANDACK);
if (id >= 0)
player_soundPlayGUI(snd_nav, 1);
}
gui_forceBlink();
gui_setNav();
}
/**
* @brief Cycle through planet targets.
*/
void player_targetPlanet (void)
{
int id, i;
/* Not under manual control. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
return;
/* Find next planet target. */
for (id=player.p->nav_planet+1; id<cur_system->nplanets; id++)
if (planet_isKnown( cur_system->planets[id] ))
break;
/* Try to select the lowest-indexed valid planet. */
if (id >= cur_system->nplanets ) {
id = -1;
for (i=0; i<cur_system->nplanets; i++)
if (planet_isKnown( cur_system->planets[i] )) {
id = i;
break;
}
}
/* Untarget if out of range. */
player_targetPlanetSet( id );
}
/**
* @brief Try to land or target closest planet if no land target.
*/
void player_land (void)
{
int i;
int tp, silent;
double td, d;
Planet *planet;
silent = 0; /* Whether to suppress the land ack noise. */
if (landed) { /* player is already landed */
takeoff(1);
return;
}
/* Not under manual control or disabled. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
pilot_isDisabled(player.p))
return;
/* Already landing. */
if ((pilot_isFlag( player.p, PILOT_LANDING) ||
pilot_isFlag( player.p, PILOT_TAKEOFF)))
return;
/* Check if there are planets to land on. */
if (cur_system->nplanets == 0) {
player_messageRaw( "\erThere are no planets to land on." );
return;
}
if (player.p->nav_planet == -1) { /* get nearest planet target */
td = -1; /* temporary distance */
tp = -1; /* temporary planet */
for (i=0; i<cur_system->nplanets; i++) {
planet = cur_system->planets[i];
d = vect_dist(&player.p->solid->pos,&planet->pos);
if (pilot_inRangePlanet( player.p, i ) &&
planet_hasService(planet,PLANET_SERVICE_LAND) &&
((tp==-1) || ((td == -1) || (td > d)))) {
tp = i;
td = d;
}
}
player_targetPlanetSet( tp );
player_hyperspacePreempt(0);
/* no landable planet */
if (player.p->nav_planet < 0)
return;
silent = 1; /* Suppress further targeting noises. */
}
/*check if planet is in range*/
else if (!pilot_inRangePlanet( player.p, player.p->nav_planet)) {
player_planetOutOfRangeMsg();
return;
}
if (player_isFlag(PLAYER_NOLAND)) {
player_message( "\er%s", player_message_noland );
return;
}
else if (pilot_isFlag( player.p, PILOT_NOLAND)) {
player_message( "\erDocking stabilizers malfunctioning, cannot land." );
return;
}
/* attempt to land at selected planet */
planet = cur_system->planets[player.p->nav_planet];
if (!planet_hasService(planet, PLANET_SERVICE_LAND)) {
player_messageRaw( "\erYou can't land here." );
return;
}
else if (!player_isFlag(PLAYER_LANDACK)) { /* no landing authorization */
if (planet_hasService(planet,PLANET_SERVICE_INHABITED)) { /* Basic services */
if (planet->can_land || (planet->land_override > 0))
player_message( "\e%c%s>\e0 %s", planet_getColourChar(planet),
planet->name, planet->land_msg );
else if (planet->bribed && (planet->land_override >= 0))
player_message( "\e%c%s>\e0 %s", planet_getColourChar(planet),
planet->name, planet->bribe_ack_msg );
else { /* Hostile */
player_message( "\e%c%s>\e0 %s", planet_getColourChar(planet),
planet->name, planet->land_msg );
return;
}
}
else /* No shoes, no shirt, no lifeforms, no service. */
player_message( "\epReady to land on %s.", planet->name );
player_setFlag(PLAYER_LANDACK);
if (!silent)
player_soundPlayGUI(snd_nav, 1);
return;
}
else if (vect_dist2(&player.p->solid->pos,&planet->pos) > pow2(planet->radius)) {
player_message("\erYou are too far away to land on %s.", planet->name);
return;
}
else if ((pow2(VX(player.p->solid->vel)) + pow2(VY(player.p->solid->vel))) >
(double)pow2(MAX_HYPERSPACE_VEL)) {
player_message("\erYou are going too fast to land on %s.", planet->name);
return;
}
/* Abort autonav. */
player_restoreControl(0, NULL);
/* Stop afterburning. */
pilot_afterburnOver( player.p );
/* Stop accelerating. */
player_accelOver();
/* Stop all on outfits. */
if (pilot_outfitOffAll( player.p ) > 0)
pilot_calcStats( player.p );
/* Start landing. */
player_soundPause();
player.p->ptimer = PILOT_LANDING_DELAY;
pilot_setFlag( player.p, PILOT_LANDING );
pilot_setThrust( player.p, 0. );
pilot_setTurn( player.p, 0. );
}
/**
* @brief Revokes landing authorization if the player's reputation is too low.
*/
void player_checkLandAck( void )
{
Planet *p;
/* No authorization to revoke. */
if ((player.p == NULL) || !player_isFlag(PLAYER_LANDACK))
return;
/* Avoid a potential crash if PLAYER_LANDACK is set inappropriately. */
if (player.p->nav_planet < 0) {
WARN("Player has landing permission, but no valid planet targeted.");
return;
}
p = cur_system->planets[ player.p->nav_planet ];
/* Player can still land. */
if (p->can_land || (p->land_override > 0) || p->bribed)
return;
player_rmFlag(PLAYER_LANDACK);
player_message( "\e%c%s>\e0 Landing permission revoked.",
planet_getColourChar(p), p->name );
}
/**
* @brief Checks whether the player's ship is able to takeoff.
*/
int player_canTakeoff(void)
{
return !pilot_checkSpaceworthy(player.p);
}
/**
* @brief Sets the no land message.
*
* @brief str Message to set when the player is not allowed to land temporarily.
*/
void player_nolandMsg( const char *str )
{
if (str != NULL)
player_message_noland = str;
else
player_message_noland = "You are not allowed to land at this moment.";
}
/**
* @brief Sets the player's hyperspace target.
*
* @param id ID of the hyperspace target.
*/
void player_targetHyperspaceSet( int id )
{
int old;
if (id >= cur_system->njumps) {
WARN("Trying to set player's hyperspace target to invalid ID '%d'", id);
return;
}
if (pilot_isFlag(player.p, PILOT_HYP_PREP) ||
pilot_isFlag(player.p, PILOT_HYP_BEGIN) ||
pilot_isFlag(player.p, PILOT_HYPERSPACE))
return;
old = player.p->nav_hyperspace;
player.p->nav_hyperspace = id;
player_hyperspacePreempt((id < 0) ? 0 : 1);
if ((old != id) && (id >= 0))
player_soundPlayGUI(snd_nav,1);
gui_setNav();
}
/**
* @brief Gets a hyperspace target.
*/
void player_targetHyperspace (void)
{
int id, i;
/* Not under manual control. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
return;
map_clear(); /* clear the current map path */
for (id=player.p->nav_hyperspace+1; id<cur_system->njumps; id++)
if (jp_isKnown( &cur_system->jumps[id]))
break;
/* Try to find the lowest-indexed valid jump. */
if (id >= cur_system->njumps) {
id = -1;
for (i=0; i<cur_system->njumps; i++)
if (jp_isUsable( &cur_system->jumps[i])) {
id = i;
break;
}
}
player_targetHyperspaceSet( id );
/* Map gets special treatment if open. */
if (id == -1)
map_select( NULL , 0);
else
map_select( cur_system->jumps[ id ].target, 0 );
}
/**
* @brief Enables or disables jump points preempting planets in autoface and target clearing.
*
* @param preempt Boolean; 1 preempts planet target.
*/
void player_hyperspacePreempt( int preempt )
{
preemption = preempt;
}
/**
* @brief Returns whether the jump point target should preempt the planet target.
*
* @return Boolean; 1 preempts planet target.
*/
int player_getHypPreempt(void)
{
return preemption;
}
/**
* @brief Starts the hail sounds and aborts autoNav
*/
void player_hailStart (void)
{
player_hailCounter = 5;
/* Abort autonav. */
player_messageRaw("\erReceiving hail!");
player_autonavResetSpeed();
player.autonav_timer = MAX( player.autonav_timer, 10. );
}
/**
* @brief Actually attempts to jump in hyperspace.
*
* @return 1 if actually started a jump, 0 otherwise.
*/
int player_jump (void)
{
int i, j;
double dist, mindist;
/* Must have a jump target and not be already jumping. */
if (pilot_isFlag(player.p, PILOT_HYPERSPACE))
return 0;
/* Not under manual control or disabled. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
pilot_isDisabled(player.p))
return 0;
/* Select nearest jump if not target. */
if (player.p->nav_hyperspace == -1) {
j = -1;
mindist = INFINITY;
for (i=0; i<cur_system->njumps; i++) {
dist = vect_dist2( &player.p->solid->pos, &cur_system->jumps[i].pos );
if (dist < mindist && jp_isUsable(&cur_system->jumps[i])) {
mindist = dist;
j = i;
}
}
if (j < 0)
return 0;
player.p->nav_hyperspace = j;
player_soundPlayGUI(snd_nav,1);
map_select( cur_system->jumps[player.p->nav_hyperspace].target, 0 );
gui_setNav();
/* Only follow through if within range. */
if (mindist > pow2( cur_system->jumps[j].radius ))
return 0;
}
/* Already jumping, so we break jump. */
if (pilot_isFlag(player.p, PILOT_HYP_PREP)) {
pilot_hyperspaceAbort(player.p);
player_message("\erAborting hyperspace sequence.");
return 0;
}
/* Try to hyperspace. */
i = space_hyperspace(player.p);
if (i == -1)
player_message("\erYou are too far from a jump point to initiate hyperspace.");
else if (i == -2)
player_message("\erHyperspace drive is offline.");
else if (i == -3)
player_message("\erYou do not have enough fuel to hyperspace jump.");
else {
player_message("\epPreparing for hyperspace.");
/* Stop acceleration noise. */
player_accelOver();
/* Stop possible shooting. */
pilot_shootStop( player.p, 0 );
pilot_shootStop( player.p, 1 );
return 1;
}
return 0;
}
/**
* @brief Player actually broke hyperspace (entering new system).
*/
void player_brokeHyperspace (void)
{
ntime_t t;
StarSystem *sys;
JumpPoint *jp;
/* First run jump hook. */
hooks_run( "jumpout" );
/* Prevent targeted planet # from carrying over. */
gui_setNav();
gui_setTarget();
player_targetPlanetSet( -1 );
/* calculates the time it takes, call before space_init */
t = pilot_hyperspaceDelay( player.p );
ntime_inc( t );
/* Save old system. */
sys = cur_system;
/* Free old graphics. */
space_gfxUnload( sys );
/* enter the new system */
jp = &cur_system->jumps[player.p->nav_hyperspace];
space_init( jp->target->name );
/* set position, the pilot_update will handle lowering vel */
space_calcJumpInPos( cur_system, sys, &player.p->solid->pos, &player.p->solid->vel, &player.p->solid->dir );
cam_setTargetPilot( player.p->id, 0 );
/* reduce fuel */
player.p->fuel -= player.p->fuel_consumption;
/* stop hyperspace */
pilot_rmFlag( player.p, PILOT_HYPERSPACE );
pilot_rmFlag( player.p, PILOT_HYP_BEGIN );
pilot_rmFlag( player.p, PILOT_HYP_BRAKE );
pilot_rmFlag( player.p, PILOT_HYP_PREP );
/* Update the map */
map_jump();
/* Add the escorts. */
player_addEscorts();
/* Disable autonavigation if arrived. */
if (player_isFlag(PLAYER_AUTONAV)) {
if (player.p->nav_hyperspace == -1) {
player_message( "\epAutonav arrived at the %s system.", cur_system->name);
player_autonavEnd();
}
else {
player_message( "\epAutonav continuing until destination (%d jump%s left).",
map_npath, (map_npath==1) ? "" : "s" );
}
}
/* Safe since this is run in the player hook section. */
hooks_run( "jumpin" );
hooks_run( "enter" );
events_trigger( EVENT_TRIGGER_ENTER );
missions_run( MIS_AVAIL_SPACE, -1, NULL, NULL );
/* Player sound. */
player_soundPlay( snd_hypJump, 1 );
}
/**
* @brief Start accelerating.
*
* @param acc How much thrust should be applied of maximum (0 - 1).
*/
void player_accel( double acc )
{
if ((player.p == NULL) || pilot_isFlag(player.p, PILOT_HYP_PREP) ||
pilot_isFlag(player.p, PILOT_HYPERSPACE))
return;
player_acc = acc;
if (toolkit_isOpen() || paused)
player_soundPause();
}
/**
* @brief Done accelerating.
*/
void player_accelOver (void)
{
player_acc = 0.;
}
/**
* @brief Sets the player's target.
*
* @param id Target to set for the player.
*/
void player_targetSet( unsigned int id )
{
unsigned int old;
old = player.p->target;
pilot_setTarget( player.p, id );
if ((old != id) && (player.p->target != PLAYER_ID)) {
gui_forceBlink();
player_soundPlayGUI( snd_target, 1 );
}
gui_setTarget();
}
/**
* @brief Targets the nearest hostile enemy to the player.
*
* @note This function largely duplicates pilot_getNearestEnemy, because the
* player's hostility with AIs is more nuanced than AI vs AI.
*/
void player_targetHostile (void)
{
unsigned int tp;
int i;
double d, td;
tp = PLAYER_ID;
d = 0;
for (i=0; i<pilot_nstack; i++) {
/* Don't get if is bribed. */
if (pilot_isFlag(pilot_stack[i],PILOT_BRIBED))
continue;
/* Shouldn't be disabled. */
if (pilot_isDisabled(pilot_stack[i]))
continue;
/* Must be a valid target. */
if (!pilot_validTarget( player.p, pilot_stack[i] ))
continue;
/* Normal unbribed check. */
if (pilot_isHostile(pilot_stack[i])) {
td = vect_dist2(&pilot_stack[i]->solid->pos, &player.p->solid->pos);
if ((tp==PLAYER_ID) || (td < d)) {
d = td;
tp = pilot_stack[i]->id;
}
}
}
player_targetSet( tp );
}
/**
* @brief Cycles to next target.
*
* @param mode Mode to target. 0 is normal, 1 is hostiles.
*/
void player_targetNext( int mode )
{
player_targetSet( pilot_getNextID(player.p->target, mode) );
}
/**
* @brief Cycles to previous target.
*
* @param mode Mode to target. 0 is normal, 1 is hostiles.
*/
void player_targetPrev( int mode )
{
player_targetSet( pilot_getPrevID(player.p->target, mode) );
}
/**
* @brief Clears the player's ship, planet or hyperspace target, in that order.
*/
void player_targetClear (void)
{
gui_forceBlink();
if (player.p->target == PLAYER_ID && (preemption == 1 || player.p->nav_planet == -1)
&& !pilot_isFlag(player.p, PILOT_HYP_PREP)) {
player.p->nav_hyperspace = -1;
player_hyperspacePreempt(0);
map_clear();
}
else if (player.p->target == PLAYER_ID)
player_targetPlanetSet( -1 );
else
player_targetSet( PLAYER_ID );
gui_setNav();
}
/**
* @brief Targets the pilot.
*
* @param prev 1 if is cycling backwards.
*/
void player_targetEscort( int prev )
{
int i;
/* Check if current target is an escort. */
for (i=0; i<player.p->nescorts; i++) {
if (player.p->target == player.p->escorts[i].id) {
/* Cycle targets. */
if (prev)
pilot_setTarget( player.p, (i > 0) ?
player.p->escorts[i-1].id : player.p->id );
else
pilot_setTarget( player.p, (i < player.p->nescorts-1) ?
player.p->escorts[i+1].id : player.p->id );
break;
}
}
/* Not found in loop. */
if (i >= player.p->nescorts) {
/* Check to see if he actually has escorts. */
if (player.p->nescorts > 0) {
/* Cycle forward or backwards. */
if (prev)
pilot_setTarget( player.p, player.p->escorts[player.p->nescorts-1].id );
else
pilot_setTarget( player.p, player.p->escorts[0].id );
}
else
pilot_setTarget( player.p, player.p->id );
}
if (player.p->target != PLAYER_ID) {
gui_forceBlink();
player_soundPlayGUI( snd_target, 1 );
}
gui_setTarget();
}
/**
* @brief Player targets nearest pilot.
*/
void player_targetNearest (void)
{
unsigned int t, dt;
double d;
d = pilot_getNearestPos( player.p, &dt, player.p->solid->pos.x,
player.p->solid->pos.y, 1 );
t = dt;
/* Disabled ships are typically only valid if within 500 px of the player. */
if ((d > 250000) && (pilot_isDisabled( pilot_get(dt) ))) {
t = pilot_getNearestPilot(player.p);
/* Try to target a disabled ship if there are no active ships in range. */
if (t == PLAYER_ID)
t = dt;
}
player_targetSet( t );
}
static int screenshot_cur = 0; /**< Current screenshot at. */
/**
* @brief Takes a screenshot.
*/
void player_screenshot (void)
{
char filename[PATH_MAX];
if (nfile_dirMakeExist("%s", nfile_dataPath()) < 0 || nfile_dirMakeExist("%sscreenshots", nfile_dataPath()) < 0) {
WARN("Aborting screenshot");
return;
}
/* Try to find current screenshots. */
for ( ; screenshot_cur < 1000; screenshot_cur++) {
nsnprintf( filename, PATH_MAX, "%sscreenshots/screenshot%03d.png",
nfile_dataPath(), screenshot_cur );
if (!nfile_fileExists( filename ))
break;
}
if (screenshot_cur >= 999) { /* in case the crap system breaks :) */
WARN("You have reached the maximum amount of screenshots [999]");
return;
}
/* now proceed to take the screenshot */
DEBUG( "Taking screenshot [%03d]...", screenshot_cur );
gl_screenshot(filename);
}
/**
* @brief Checks to see if player is still being hailed and clears hail counters
* if he isn't.
*/
static void player_checkHail (void)
{
int i;
Pilot *p;
/* See if a pilot is hailing. */
for (i=0; i<pilot_nstack; i++) {
p = pilot_stack[i];
/* Must be hailing. */
if (pilot_isFlag(p, PILOT_HAILING))
return;
}
/* Clear hail timer. */
player_hailCounter = 0;
player_hailTimer = 0.;
}
/**
* @brief Displays an out of range message for the player's currently selected planet.
*/
static void player_planetOutOfRangeMsg (void)
{
player_message( "\er%s is out of comm range, unable to contact.",
cur_system->planets[player.p->nav_planet]->name );
}
/**
* @brief Opens communication with the player's target.
*/
void player_hail (void)
{
/* Not under manual control or disabled. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
pilot_isDisabled(player.p))
return;
if (player.p->target != player.p->id)
comm_openPilot(player.p->target);
else if(player.p->nav_planet != -1) {
if (pilot_inRangePlanet( player.p, player.p->nav_planet ))
comm_openPlanet( cur_system->planets[ player.p->nav_planet ] );
else
player_planetOutOfRangeMsg();
}
else
player_message("\erNo target selected to hail.");
/* Clear hails if none found. */
player_checkHail();
}
/**
* @brief Opens communication with the player's planet target.
*/
void player_hailPlanet (void)
{
/* Not under manual control. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
return;
if (player.p->nav_planet != -1) {
if (pilot_inRangePlanet( player.p, player.p->nav_planet ))
comm_openPlanet( cur_system->planets[ player.p->nav_planet ] );
else
player_planetOutOfRangeMsg();
}
else
player_message("\erNo target selected to hail.");
}
/**
* @brief Automatically tries to hail a pilot that hailed the player.
*/
void player_autohail (void)
{
int i;
Pilot *p;
/* Not under manual control or disabled. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
pilot_isDisabled(player.p))
return;
/* Find pilot to autohail. */
for (i=0; i<pilot_nstack; i++) {
p = pilot_stack[i];
/* Must be hailing. */
if (pilot_isFlag(p, PILOT_HAILING))
break;
}
/* Not found any. */
if (i >= pilot_nstack) {
player_message("\erYou haven't been hailed by any pilots.");
return;
}
/* Try to hail. */
pilot_setTarget( player.p, p->id );
gui_setTarget();
player_hail();
/* Clear hails if none found. */
player_checkHail();
}
/**
* @brief Toggles mouse flying.
*/
void player_toggleMouseFly(void)
{
if (!player_isFlag(PLAYER_MFLY)) {
input_mouseShow();
player_message("\epMouse flying enabled.");
player_setFlag(PLAYER_MFLY);
}
else {
input_mouseHide();
player_rmFlag(PLAYER_MFLY);
player_message("\erMouse flying disabled.");
if (conf.mouse_thrust)
player_accelOver();
}
}
/**
* @brief Starts braking or active cooldown.
*/
void player_brake(void)
{
int stopped;
if (pilot_isFlag(player.p, PILOT_TAKEOFF))
return;
/* Not under manual control or disabled. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
pilot_isDisabled(player.p))
return;
stopped = pilot_isStopped(player.p);
if (stopped && !pilot_isFlag(player.p, PILOT_COOLDOWN))
pilot_cooldown(player.p);
else if (pilot_isFlag(player.p, PILOT_BRAKING))
pilot_setFlag(player.p, PILOT_COOLDOWN_BRAKE);
else if (!stopped)
pilot_setFlag(player.p, PILOT_BRAKING);
}
/**
* @brief Handles mouse flying based on cursor position.
*
* @return 1 if cursor is outside the dead zone, 0 if it isn't.
*/
static int player_thinkMouseFly(void)
{
double px, py, r, x, y, acc;
px = player.p->solid->pos.x;
py = player.p->solid->pos.y;
gl_screenToGameCoords( &x, &y, player.mousex, player.mousey );
r = sqrt(pow2(x-px) + pow2(y-py));
if (r > 50.) { /* Ignore mouse input within a 50 px radius of the centre. */
pilot_face(player.p, atan2( y - py, x - px));
if (conf.mouse_thrust) { /* Only alter thrust if option is enabled. */
acc = CLAMP(0., 1., (r - 100) / 200.);
acc = 3 * pow2(acc) - 2 * pow(acc, 3);
/* Only accelerate when within 180 degrees of the intended direction. */
if (ABS(angle_diff(atan2( y - py, x - px), player.p->solid->dir)) < M_PI_2 )
player_accel(acc);
else
player_accel(0.);
}
return 1;
}
else
return 0;
}
/**
* @brief Player got pwned.
*/
void player_dead (void)
{
/* Explode at normal speed. */
pause_setSpeed(1.);
gui_cleanup();
/* Close the overlay. */
ovr_setOpen(0);
}
/**
* @brief Player blew up in a fireball.
*/
void player_destroyed (void)
{
if (player_isFlag(PLAYER_DESTROYED))
return;
/* Mark as destroyed. */
player_setFlag(PLAYER_DESTROYED);
/* Set timer for death menu. */
player_timer = 5.;
/* Stop sounds. */
player_soundStop();
/* Stop autonav */
player_autonavEnd();
/* Reset time compression when player dies. */
pause_setSpeed( 1. );
}
/**
* @brief PlayerShip_t compare function for qsort().
*/
static int player_shipsCompare( const void *arg1, const void *arg2 )
{
PlayerShip_t *ps1, *ps2;
credits_t p1, p2;
/* Get the arguments. */
ps1 = (PlayerShip_t*) arg1;
ps2 = (PlayerShip_t*) arg2;
/* Get prices. */
p1 = pilot_worth( ps1->p );
p2 = pilot_worth( ps2->p );
/* Compare price INVERSELY */
if (p1 < p2)
return +1;
else if (p1 > p2)
return -1;
/* In case of tie sort by name so they don't flip or something. */
return strcmp( ps1->p->name, ps2->p->name );
}
/**
* @brief Returns a buffer with all the player's ships names.
*
* @param sships Fills sships with player_nships ship names.
* @param tships Fills sships with player_nships ship target textures.
* @return Freshly allocated array with allocated ship names.
* @return The number of ships the player has.
*/
int player_ships( char** sships, glTexture** tships )
{
int i;
if (player_nstack == 0)
return 0;
/* Sort. */
qsort( player_stack, player_nstack, sizeof(PlayerShip_t), player_shipsCompare );
/* Create the struct. */
for (i=0; i < player_nstack; i++) {
sships[i] = strdup(player_stack[i].p->name);
tships[i] = player_stack[i].p->ship->gfx_store;
}
return player_nstack;
}
/**
* @brief Gets all of the player's ships.
*
* @param[out] Number of star systems gotten.
* @return The player's ships.
*/
const PlayerShip_t* player_getShipStack( int *n )
{
*n = player_nstack;
return player_stack;
}
/**
* @brief Gets the amount of ships player has in storage.
*
* @return The number of ships the player has.
*/
int player_nships (void)
{
return player_nstack;
}
/**
* @brief Sees if player has a ship of a name.
*
* @param shipname Nome of the ship to get.
* @return 1 if ship exists.
*/
int player_hasShip( char* shipname )
{
int i;
/* Check current ship. */
if ((player.p != NULL) && (strcmp(player.p->name,shipname)==0))
return 1;
/* Check stocked ships. */
for (i=0; i < player_nstack; i++)
if (strcmp(player_stack[i].p->name, shipname)==0)
return 1;
return 0;
}
/**
* @brief Gets a specific ship.
*
* @param shipname Nome of the ship to get.
* @return The ship matching name.
*/
Pilot* player_getShip( char* shipname )
{
int i;
if ((player.p != NULL) && (strcmp(shipname,player.p->name)==0))
return player.p;
for (i=0; i < player_nstack; i++)
if (strcmp(player_stack[i].p->name, shipname)==0)
return player_stack[i].p;
WARN("Player ship '%s' not found in stack", shipname);
return NULL;
}
/**
* @brief Gets where a specific ship is.
*
* @param shipname Ship to check where it is.
* @return The location of the ship.
*/
char* player_getLoc( char* shipname )
{
int i;
if (strcmp(player.p->name,shipname)==0)
return land_planet->name;
for (i=0; i < player_nstack; i++)
if (strcmp(player_stack[i].p->name, shipname)==0)
return player_stack[i].loc;
WARN("Player ship '%s' not found in stack", shipname);
return NULL;
}
/**
* @brief Sets the location of a specific ship.
*
* @param shipname Name of the ship to change location of.
* @param loc Location of the ship to change to.
*/
void player_setLoc( char* shipname, char* loc )
{
int i;
for (i=0; i < player_nstack; i++) {
if (strcmp(player_stack[i].p->name, shipname)==0) {
free(player_stack[i].loc);
player_stack[i].loc = strdup(loc);
return;
}
}
WARN("Player ship '%s' not found in stack", shipname);
}
/**
* @brief Gets how many of the outfit 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 Outfit* o )
{
int i;
/* Special case map. */
if ((outfit_isMap(o) && map_isMapped(o)) ||
(outfit_isLocalMap(o) && localmap_isMapped(o)))
return 1;
/* Special case license. */
if (outfit_isLicense(o) &&
player_hasLicense(o->name))
return 1;
/* Special case GUI. */
if (outfit_isGUI(o) &&
player_guiCheck(o->u.gui.gui))
return 1;
/* Try to find it. */
for (i=0; i<player_noutfits; i++)
if (player_outfits[i].o == o)
return player_outfits[i].q;
return 0;
}
/**
* @brief qsort() compare function for PlayerOutfit_t sorting.
*/
static int player_outfitCompare( const void *arg1, const void *arg2 )
{
PlayerOutfit_t *po1, *po2;
/* Get type. */
po1 = (PlayerOutfit_t*) arg1;
po2 = (PlayerOutfit_t*) arg2;
/* Compare. */
return outfit_compareTech( &po1->o, &po2->o );
}
/**
* @brief Returns the player's outfits.
*
* @param[out] n Number of distinct outfits (not total quantity).
* @return Outfits the player owns.
*/
const PlayerOutfit_t* player_getOutfits( int *n )
{
*n = player_noutfits;
return (const PlayerOutfit_t*) player_outfits;
}
/**
* @brief Prepares two arrays for displaying in an image array.
*
* @param[out] outfits Outfits the player owns.
* @param[out] toutfits Optional store textures for the image array.
* @param[in] filter Function to filter which outfits to get.
* @param[in] name Name fragment that each outfit must contain.
* @return Number of outfits.
*/
int player_getOutfitsFiltered( Outfit **outfits, glTexture** toutfits,
int(*filter)( const Outfit *o ), char *name )
{
int i;
if (player_noutfits == 0)
return 0;
/* We'll sort. */
qsort( player_outfits, player_noutfits,
sizeof(PlayerOutfit_t), player_outfitCompare );
for (i=0; i<player_noutfits; i++)
outfits[i] = (Outfit*)player_outfits[i].o;
return outfits_filter( outfits, toutfits, player_noutfits, filter, name );
}
/**
* @brief Gets the amount of different outfits in the player outfit stack.
*
* @return Amount of different outfits.
*/
int player_numOutfits (void)
{
return player_noutfits;
}
/**
* @brief Adds an outfit to the player outfit stack.
*
* @param o Outfit to add.
* @param quantity Amount to add.
* @return Amount added.
*/
int player_addOutfit( const Outfit *o, int quantity )
{
int i;
/* Sanity check. */
if (quantity == 0)
return 0;
/* special case if it's a map */
if (outfit_isMap(o)) {
map_map(o);
return 1; /* Success. */
}
else if (outfit_isLocalMap(o)) {
localmap_map(o);
return 1;
}
/* special case if it's an outfit */
else if (outfit_isGUI(o)) {
player_guiAdd(o->u.gui.gui);
return 1; /* Success. */
}
/* special case if it's a license. */
else if (outfit_isLicense(o)) {
player_addLicense(o->name);
return 1; /* Success. */
}
/* Try to find it. */
for (i=0; i<player_noutfits; i++) {
if (player_outfits[i].o == o) {
player_outfits[i].q += quantity;
return quantity;
}
}
/* Allocate if needed. */
player_noutfits++;
if (player_noutfits > player_moutfits) {
if (player_moutfits == 0)
player_moutfits = OUTFIT_CHUNKSIZE;
else
player_moutfits *= 2;
player_outfits = realloc( player_outfits,
sizeof(PlayerOutfit_t) * player_moutfits );
}
/* Add the outfit. */
player_outfits[player_noutfits-1].o = o;
player_outfits[player_noutfits-1].q = quantity;
return quantity;
}
/**
* @brief Remove an outfit from the player's outfit stack.
*
* @param o Outfit to remove.
* @param quantity Amount to remove.
* @return Amount removed.
*/
int player_rmOutfit( const Outfit *o, int quantity )
{
int i, q;
/* Try to find it. */
for (i=0; i<player_noutfits; i++) {
if (player_outfits[i].o == o) {
/* See how many to remove. */
q = MIN( player_outfits[i].q, quantity );
player_outfits[i].q -= q;
/* See if must remove element. */
if (player_outfits[i].q <= 0) {
player_noutfits--;
memmove( &player_outfits[i], &player_outfits[i+1],
sizeof(PlayerOutfit_t) * (player_noutfits-i) );
}
/* Return removed outfits. */
return q;
}
}
/* Nothing removed. */
return 0;
}
/**
* @brief Marks a mission as completed.
*
* @param id ID of the mission to mark as completed.
*/
void player_missionFinished( int id )
{
/* Make sure not already marked. */
if (player_missionAlreadyDone(id))
return;
/* Mark as done. */
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;
}
/**
* @brief Checks to see if player has already completed a mission.
*
* @param id ID of the mission to see if player has completed.
* @return 1 if player has completed the mission, 0 otherwise.
*/
int player_missionAlreadyDone( int id )
{
int i;
for (i=0; i<missions_ndone; i++)
if (missions_done[i] == id)
return 1;
return 0;
}
/**
* @brief Marks a event as completed.
*
* @param id ID of the event to mark as completed.
*/
void player_eventFinished( int id )
{
/* Make sure not already done. */
if (player_eventAlreadyDone(id))
return;
/* Add to done. */
events_ndone++;
if (events_ndone > events_mdone) { /* need to grow */
events_mdone += 25;
events_done = realloc( events_done, sizeof(int) * events_mdone);
}
events_done[ events_ndone-1 ] = id;
}
/**
* @brief Checks to see if player has already completed a event.
*
* @param id ID of the event to see if player has completed.
* @return 1 if player has completed the event, 0 otherwise.
*/
int player_eventAlreadyDone( int id )
{
int i;
for (i=0; i<events_ndone; i++)
if (events_done[i] == id)
return 1;
return 0;
}
/**
* @brief Checks to see if player has license.
*
* @param license License to check to see if the player has.
* @return 1 if has license (or none needed), 0 if doesn't.
*/
int player_hasLicense( char *license )
{
int i;
if (!license) /* Null input. */
return 1;
for (i=0; i<player_nlicenses; i++)
if (strcmp(license, player_licenses[i])==0)
return 1;
return 0;
}
/**
* @brief Gives the player a license.
*
* @brief license License to give the player.
*/
void player_addLicense( char *license )
{
/* Player already has license. */
if (player_hasLicense(license))
return;
/* Add the license. */
player_nlicenses++;
player_licenses = realloc( player_licenses, sizeof(char*)*player_nlicenses );
player_licenses[player_nlicenses-1] = strdup(license);
}
/**
* @brief Gets the player's licenses.
*
* @param nlicenses Amount of licenses the player has.
* @return Name of the licenses he has.
*/
char **player_getLicenses( int *nlicenses )
{
*nlicenses = player_nlicenses;
return player_licenses;
}
/**
* @brief Runs hooks for the player.
*/
void player_runHooks (void)
{
if (player_isFlag( PLAYER_HOOK_HYPER )) {
player_brokeHyperspace();
player_rmFlag( PLAYER_HOOK_HYPER );
}
if (player_isFlag( PLAYER_HOOK_JUMPIN)) {
hooks_run( "jumpin" );
hooks_run( "enter" );
events_trigger( EVENT_TRIGGER_ENTER );
missions_run( MIS_AVAIL_SPACE, -1, NULL, NULL );
player_rmFlag( PLAYER_HOOK_JUMPIN );
}
if (player_isFlag( PLAYER_HOOK_LAND )) {
land( cur_system->planets[ player.p->nav_planet ], 0 );
player_rmFlag( PLAYER_HOOK_LAND );
}
}
/**
* @brief Clears escorts to make sure deployment is sane.
*/
void player_clearEscorts (void)
{
int i;
for (i=0; i<player.p->noutfits; i++) {
if (player.p->outfits[i]->outfit == NULL)
continue;
if (outfit_isFighterBay(player.p->outfits[i]->outfit))
player.p->outfits[i]->u.ammo.deployed = 0;
}
}
/**
* @brief Adds the player's escorts.
*
* @return 0 on success.
*/
int player_addEscorts (void)
{
int i, j;
double a;
Vector2d v;
unsigned int e;
Outfit *o;
int q;
/* Clear escorts first. */
player_clearEscorts();
for (i=0; i<player.p->nescorts; i++) {
a = RNGF() * 2 * M_PI;
vect_cset( &v, player.p->solid->pos.x + 50.*cos(a),
player.p->solid->pos.y + 50.*sin(a) );
e = escort_create( player.p, player.p->escorts[i].ship,
&v, &player.p->solid->vel, player.p->solid->dir,
player.p->escorts[i].type, 0 );
player.p->escorts[i].id = e; /* Important to update ID. */
/* Update outfit if needed. */
if (player.p->escorts[i].type != ESCORT_TYPE_BAY)
continue;
for (j=0; j<player.p->noutfits; j++) {
/* Must have outfit. */
if (player.p->outfits[j]->outfit == NULL)
continue;
/* Must be fighter bay. */
if (!outfit_isFighterBay(player.p->outfits[j]->outfit))
continue;
/* Ship must match. */
o = outfit_ammo(player.p->outfits[j]->outfit);
if (!outfit_isFighter(o) ||
(strcmp(player.p->escorts[i].ship,o->u.fig.ship)!=0))
continue;
/* Must not have all deployed. */
q = player.p->outfits[j]->u.ammo.deployed + player.p->outfits[j]->u.ammo.quantity;
if (q >= outfit_amount(player.p->outfits[j]->outfit))
continue;
/* Mark as deployed. */
player.p->outfits[j]->u.ammo.deployed += 1;
break;
}
if (j >= player.p->noutfits)
WARN("Unable to mark escort as deployed");
}
return 0;
}
/**
* @brief Saves the player's escorts.
*/
static int player_saveEscorts( xmlTextWriterPtr writer )
{
int i;
for (i=0; i<player.p->nescorts; i++) {
xmlw_startElem(writer, "escort");
xmlw_attr(writer,"type","bay"); /**< @todo other types. */
xmlw_str(writer, "%s", player.p->escorts[i].ship);
xmlw_endElem(writer); /* "escort" */
}
return 0;
}
/**
* @brief Save the freaking player in a freaking xmlfile.
*
* @param writer xml Writer to use.
* @return 0 on success.
*/
int player_save( xmlTextWriterPtr writer )
{
char **guis;
int i, n;
MissionData *m;
const char *ev;
int scu, stp, stu;
double rem;
xmlw_startElem(writer,"player");
/* Standard player details. */
xmlw_attr(writer,"name","%s",player.name);
xmlw_elem(writer,"rating","%f",player.crating);
xmlw_elem(writer,"credits","%"CREDITS_PRI,player.p->credits);
if (player.gui != NULL)
xmlw_elem(writer,"gui","%s",player.gui);
xmlw_elem(writer,"guiOverride","%d",player.guiOverride);
xmlw_elem(writer,"mapOverlay","%d",ovr_isOpen());
/* Time. */
xmlw_startElem(writer,"time");
ntime_getR( &scu, &stp, &stu, &rem );
xmlw_elem(writer,"SCU","%d", scu);
xmlw_elem(writer,"STP","%d", stp);
xmlw_elem(writer,"STU","%d", stu);
xmlw_elem(writer,"Remainder","%lf", rem);
xmlw_endElem(writer); /* "time" */
/* Current ship. */
xmlw_elem(writer,"location","%s",land_planet->name);
player_saveShip( writer, player.p, NULL ); /* current ship */
/* Ships. */
xmlw_startElem(writer,"ships");
for (i=0; i<player_nstack; i++)
player_saveShip( writer, player_stack[i].p, player_stack[i].loc );
xmlw_endElem(writer); /* "ships" */
/* GUIs. */
xmlw_startElem(writer,"guis");
guis = player_guiList( &n );
for (i=0; i<n; i++)
xmlw_elem(writer,"gui","%s",guis[i]);
xmlw_endElem(writer); /* "guis" */
/* Outfits. */
xmlw_startElem(writer,"outfits");
for (i=0; i<player_noutfits; i++) {
xmlw_startElem(writer,"outfit");
xmlw_attr(writer,"quantity","%d",player_outfits[i].q);
xmlw_str(writer,"%s",player_outfits[i].o->name);
xmlw_endElem(writer); /* "outfit" */
}
xmlw_endElem(writer); /* "outfits" */
/* Licenses. */
xmlw_startElem(writer,"licenses");
for (i=0; i<player_nlicenses; i++)
xmlw_elem(writer,"license","%s",player_licenses[i]);
xmlw_endElem(writer); /* "licenses" */
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","%s",m->name);
}
xmlw_endElem(writer); /* "missions_done" */
/* Events the player has done. */
xmlw_startElem(writer,"events_done");
for (i=0; i<events_ndone; i++) {
ev = event_dataName(events_done[i]);
if (ev != NULL) /* In case mission name changes between versions */
xmlw_elem(writer,"done","%s",ev);
}
xmlw_endElem(writer); /* "events_done" */
/* Escorts. */
xmlw_startElem(writer, "escorts");
player_saveEscorts(writer);
xmlw_endElem(writer); /* "escorts" */
return 0;
}
/**
* @brief Saves an outfit slot.
*/
static int player_saveShipSlot( xmlTextWriterPtr writer, PilotOutfitSlot *slot, int i )
{
Outfit *o;
o = slot->outfit;
xmlw_startElem(writer,"outfit");
xmlw_attr(writer,"slot","%d",i);
if ((outfit_ammo(o) != NULL) &&
(slot->u.ammo.outfit != NULL)) {
xmlw_attr(writer,"ammo","%s",slot->u.ammo.outfit->name);
xmlw_attr(writer,"quantity","%d", slot->u.ammo.quantity);
}
xmlw_str(writer,"%s",o->name);
xmlw_endElem(writer); /* "outfit" */
return 0;
}
/**
* @brief Saves a ship.
*
* @param writer XML writer.
* @param ship Ship to save.
* @param loc Location of the ship.
* @return 0 on success.
*/
static int player_saveShip( xmlTextWriterPtr writer,
Pilot* ship, char* loc )
{
int i, j, k, n;
int found;
const char *name;
PilotWeaponSetOutfit *weaps;
xmlw_startElem(writer,"ship");
xmlw_attr(writer,"name","%s",ship->name);
xmlw_attr(writer,"model","%s",ship->ship->name);
if (loc != NULL)
xmlw_elem(writer,"location","%s",loc);
/* save the fuel */
xmlw_elem(writer,"fuel","%f",ship->fuel);
/* save the outfits */
xmlw_startElem(writer,"outfits_structure");
for (i=0; i<ship->outfit_nstructure; i++) {
if (ship->outfit_structure[i].outfit==NULL)
continue;
player_saveShipSlot( writer, &ship->outfit_structure[i], i );
}
xmlw_endElem(writer); /* "outfits_structure" */
xmlw_startElem(writer,"outfits_utility");
for (i=0; i<ship->outfit_nutility; i++) {
if (ship->outfit_utility[i].outfit==NULL)
continue;
player_saveShipSlot( writer, &ship->outfit_utility[i], i );
}
xmlw_endElem(writer); /* "outfits_utility" */
xmlw_startElem(writer,"outfits_weapon");
for (i=0; i<ship->outfit_nweapon; i++) {
if (ship->outfit_weapon[i].outfit==NULL)
continue;
player_saveShipSlot( writer, &ship->outfit_weapon[i], i );
}
xmlw_endElem(writer); /* "outfits_weapon" */
/* save the commodities */
xmlw_startElem(writer,"commodities");
for (i=0; i<ship->ncommodities; i++) {
/* Remove cargo with id and no mission. */
if (ship->commodities[i].id > 0) {
found = 0;
for (j=0; j<MISSION_MAX; j++) {
/* Only check active missions. */
if (player_missions[j]->id > 0) {
/* Now check if it's in the cargo list. */
for (k=0; k<player_missions[j]->ncargo; k++) {
/* See if it matches a cargo. */
if (player_missions[j]->cargo[k] == ship->commodities[i].id) {
found = 1;
break;
}
}
}
if (found)
break;
}
if (!found) {
WARN("Found mission cargo without associated mission.");
WARN("Please reload save game to remove the dead cargo.");
continue;
}
}
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,"%s",ship->commodities[i].commodity->name);
xmlw_endElem(writer); /* commodity */
}
xmlw_endElem(writer); /* "commodities" */
xmlw_startElem(writer,"weaponsets");
xmlw_attr(writer,"autoweap","%d",ship->autoweap);
xmlw_attr(writer,"active_set","%d",ship->active_set);
for (i=0; i<PILOT_WEAPON_SETS; i++) {
weaps = pilot_weapSetList( ship, i, &n );
xmlw_startElem(writer,"weaponset");
/* Inrange isn't handled by autoweap for the player. */
xmlw_attr(writer,"inrange","%d",pilot_weapSetInrangeCheck(ship,i));
xmlw_attr(writer,"id","%d",i);
if (!ship->autoweap) {
name = pilot_weapSetName(ship,i);
if (name != NULL)
xmlw_attr(writer,"name","%s",name);
xmlw_attr(writer,"type","%d",pilot_weapSetTypeCheck(ship,i));
for (j=0; j<n;j++) {
xmlw_startElem(writer,"weapon");
xmlw_attr(writer,"level","%d",weaps[j].level);
xmlw_str(writer,"%d",weaps[j].slot->id);
xmlw_endElem(writer); /* "weapon" */
}
}
xmlw_endElem(writer); /* "weaponset" */
}
xmlw_endElem(writer); /* "weaponsets" */
xmlw_endElem(writer); /* "ship" */
return 0;
}
/**
* @brief Loads the player stuff.
*
* @param parent Node where the player stuff is to be found.
* @return 0 on success.
*/
Planet* player_load( xmlNodePtr parent )
{
xmlNodePtr node;
Planet *pnt;
/* some cleaning up */
memset( &player, 0, sizeof(Player_t) );
pnt = NULL;
map_cleanup();
node = parent->xmlChildrenNode;
do {
if (xml_isNode(node,"player"))
pnt = player_parse( node );
else if (xml_isNode(node,"missions_done"))
player_parseDoneMissions( node );
else if (xml_isNode(node,"events_done"))
player_parseDoneEvents( node );
else if (xml_isNode(node,"escorts"))
player_parseEscorts(node);
} while (xml_nextNode(node));
return pnt;
}
/**
* @brief Parses the player node.
*
* @param parent The player node.
* @return Planet to start on on success.
*/
static Planet* player_parse( xmlNodePtr parent )
{
char *planet, *found, *str;
unsigned int services;
Planet *pnt;
xmlNodePtr node, cur;
int q;
Outfit *o;
int i, map_overlay;
StarSystem *sys;
double a, r;
Pilot *old_ship;
PilotFlags flags;
int scu, stp, stu, time_set;
double rem;
xmlr_attr(parent,"name",player.name);
/* Make sure player.p is NULL. */
player.p = NULL;
pnt = NULL;
/* Sane defaults. */
planet = NULL;
time_set = 0;
map_overlay = 0;
/* Must get planet first. */
node = parent->xmlChildrenNode;
do {
xmlr_str(node,"location",planet);
} while (xml_nextNode(node));
/* Parse rest. */
node = parent->xmlChildrenNode;
do {
/* global stuff */
xmlr_float(node,"rating",player.crating);
xmlr_ulong(node,"credits",player_creds);
xmlr_strd(node,"gui",player.gui);
xmlr_int(node,"guiOverride",player.guiOverride);
xmlr_int(node,"mapOverlay",map_overlay);
ovr_setOpen(map_overlay);
/* Time. */
if (xml_isNode(node,"time")) {
cur = node->xmlChildrenNode;
scu = stp = stu = -1;
rem = -1.;
do {
xmlr_int(cur,"SCU",scu);
xmlr_int(cur,"STP",stp);
xmlr_int(cur,"STU",stu);
xmlr_float(cur,"Remainder",rem);
} while (xml_nextNode(cur));
if ((scu < 0) || (stp < 0) || (stu < 0) || (rem<0.))
WARN("Malformed time in save game!");
ntime_setR( scu, stp, stu, rem );
if ((scu >= 0) || (stp >= 0) || (stu >= 0))
time_set = 1;
}
if (xml_isNode(node,"ship"))
player_parseShip(node, 1, planet);
/* Parse ships. */
else if (xml_isNode(node,"ships")) {
cur = node->xmlChildrenNode;
do {
if (xml_isNode(cur,"ship"))
player_parseShip(cur, 0, planet);
} while (xml_nextNode(cur));
}
/* Parse GUIs. */
else if (xml_isNode(node,"guis")) {
cur = node->xmlChildrenNode;
do {
if (xml_isNode(cur,"gui"))
player_guiAdd( xml_get(cur) );
} while (xml_nextNode(cur));
}
/* Parse outfits. */
else if (xml_isNode(node,"outfits")) {
cur = node->xmlChildrenNode;
do {
if (xml_isNode(cur,"outfit")) {
o = outfit_get( xml_get(cur) );
if (o == NULL) {
WARN("Outfit '%s' was saved but does not exist!", xml_get(cur));
continue;
}
xmlr_attr( cur, "quantity", str );
if (str != NULL) {
q = atof(str);
free(str);
}
else {
WARN("Outfit '%s' was saved without quantity!", o->name);
continue;
}
player_addOutfit( o, q );
}
} while (xml_nextNode(cur));
}
/* Parse licenses. */
else if (xml_isNode(node,"licenses"))
player_parseLicenses(node);
} while (xml_nextNode(node));
/* Handle cases where ship is missing. */
if (player.p == NULL) {
pilot_clearFlagsRaw( flags );
pilot_setFlagRaw( flags, PILOT_PLAYER );
pilot_setFlagRaw( flags, PILOT_NO_OUTFITS );
WARN("Player ship does not exist!");
if (player_nstack == 0) {
WARN("Player has no other ships, giving starting ship.");
pilot_create( ship_get(start_ship()), "MIA",
faction_get("Player"), "player", 0., NULL, NULL, flags, -1 );
}
else {
/* Just give player.p a random ship in the stack. */
old_ship = player_stack[player_nstack-1].p;
pilot_create( old_ship->ship, old_ship->name,
faction_get("Player"), "player", 0., NULL, NULL, flags, -1 );
player_rmShip( old_ship->name );
WARN("Giving player ship '%s'.", player.p->name );
}
}
/* Check. */
if (player.p == NULL) {
ERR("Something went horribly wrong, player does not exist after load...");
return NULL;
}
/* set global thingies */
player.p->credits = player_creds;
if (!time_set) {
WARN("Save has no time information, setting to start information.");
ntime_set( start_date() );
}
/* set player in system */
pnt = planet_get( planet );
/* Get random planet if it's NULL. */
if ((pnt == NULL) || (planet_getSystem(planet) == NULL) ||
!planet_hasService(pnt, PLANET_SERVICE_LAND)) {
WARN("Player starts out in non-existent or invalid planet '%s',"
"trying to find a suitable one instead.",
planet );
/* Find a landable, inhabited planet that's in a system, offers refueling
* and meets the following additional criteria:
*
* 0: Shipyard, outfitter, non-hostile
* 1: Outfitter, non-hostile
* 2: None
*
* If no planet meeting the current criteria can be found, the next
* set of criteria is tried until none remain.
*/
found = NULL;
for (i=0; i<3; i++) {
services = PLANET_SERVICE_LAND | PLANET_SERVICE_INHABITED |
PLANET_SERVICE_REFUEL;
if (i == 0)
services |= PLANET_SERVICE_SHIPYARD;
if (i != 2)
services |= PLANET_SERVICE_OUTFITS;
found = space_getRndPlanet( 1, services,
(i != 2) ? player_filterSuitablePlanet : NULL );
if (found != NULL)
break;
WARN("Could not find a planet satisfying criteria %d.", i);
}
if (found == NULL) {
WARN("Could not find a suitable planet. Choosing a random planet.");
found = space_getRndPlanet(0, 0, NULL); /* This should never, ever fail. */
}
pnt = planet_get( found );
}
sys = system_get( planet_getSystem( pnt->name ) );
space_gfxLoad( sys );
a = RNGF() * 2.*M_PI;
r = RNGF() * pnt->radius * 0.8;
player_warp( pnt->pos.x + r*cos(a), pnt->pos.y + r*sin(a) );
player.p->solid->dir = RNG(0,359) * M_PI/180.;
/* initialize the system */
space_init( sys->name );
map_clear(); /* sets the map up */
/* initialize the sound */
player_initSound();
return pnt;
}
/**
* @brief Filter function for space_getRndPlanet
*
* @param p Planet.
* @return Whether the planet is suitable for teleporting to.
*/
static int player_filterSuitablePlanet( Planet *p )
{
return !areEnemies(p->faction, FACTION_PLAYER);
}
/**
* @brief Parses player's done missions.
*
* @param parent Node of the missions.
* @return 0 on success.
*/
static int player_parseDoneMissions( xmlNodePtr parent )
{
xmlNodePtr node;
int id;
node = parent->xmlChildrenNode;
do {
if (xml_isNode(node,"done")) {
id = mission_getID( xml_get(node) );
if (id < 0)
DEBUG("Mission '%s' doesn't seem to exist anymore, removing from save.",
xml_get(node));
else
player_missionFinished( id );
}
} while (xml_nextNode(node));
return 0;
}
/**
* @brief Parses player's done missions.
*
* @param parent Node of the missions.
* @return 0 on success.
*/
static int player_parseDoneEvents( xmlNodePtr parent )
{
xmlNodePtr node;
int id;
node = parent->xmlChildrenNode;
do {
if (xml_isNode(node,"done")) {
id = event_dataID( xml_get(node) );
if (id < 0)
DEBUG("Event '%s' doesn't seem to exist anymore, removing from save.",
xml_get(node));
else
player_eventFinished( id );
}
} while (xml_nextNode(node));
return 0;
}
/**
* @brief Parses player's licenses.
*
* @param parent Node of the licenses.
* @return 0 on success.
*/
static int player_parseLicenses( xmlNodePtr parent )
{
xmlNodePtr node;
node = parent->xmlChildrenNode;
do {
if (xml_isNode(node,"license"))
player_addLicense( xml_get(node) );
} while (xml_nextNode(node));
return 0;
}
/**
* @brief Parses the escorts from the escort node.
*
* @param parent "escorts" node to parse.
* @return 0 on success.
*/
static int player_parseEscorts( xmlNodePtr parent )
{
xmlNodePtr node;
char *buf, *ship;
EscortType_t type;
node = parent->xmlChildrenNode;
do {
if (xml_isNode(node,"escort")) {
xmlr_attr( node, "type", buf );
if (strcmp(buf,"bay")==0)
type = ESCORT_TYPE_BAY;
else {
WARN("Escort has invalid type '%s'.", buf);
type = ESCORT_TYPE_NULL;
}
free(buf);
ship = xml_get(node);
/* Add escort to the list. */
escort_addList( player.p, ship, type, 0 );
}
} while (xml_nextNode(node));
return 0;
}
/**
* @brief Adds outfit to pilot if it can.
*/
static void player_addOutfitToPilot( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s )
{
int ret;
if (!outfit_fitsSlot( outfit, &s->sslot->slot )) {
DEBUG( "Outfit '%s' does not fit designated slot on player's pilot '%s', adding to stock.",
outfit->name, pilot->name );
player_addOutfit( outfit, 1 );
return;
}
ret = pilot_addOutfitRaw( pilot, outfit, s );
if (ret != 0) {
DEBUG("Outfit '%s' does not fit on player's pilot '%s', adding to stock.",
outfit->name, pilot->name);
player_addOutfit( outfit, 1 );
return;
}
/* Update stats. */
pilot_calcStats( pilot );
}
/**
* @brief Parses a ship outfit slot.
*/
static void player_parseShipSlot( xmlNodePtr node, Pilot *ship, PilotOutfitSlot *slot )
{
Outfit *o, *ammo;
char *buf;
int q;
char *name = xml_get(node);
if (name == NULL) {
WARN("Empty ship slot node found, skipping.");
return;
}
/* Add the outfit. */
o = outfit_get( name );
if (o==NULL)
return;
player_addOutfitToPilot( ship, o, slot );
/* Doesn't have ammo. */
if (outfit_ammo(o)==NULL)
return;
/* See if has ammo. */
xmlr_attr(node,"ammo",buf);
if (buf == NULL)
return;
/* Get the ammo. */
ammo = outfit_get(buf);
free(buf);
if (ammo==NULL)
return;
/* See if has quantity. */
xmlr_attr(node,"quantity",buf);
if (buf == NULL)
return;
/* Get quantity. */
q = atoi(buf);
free(buf);
/* Add ammo. */
pilot_addAmmo( ship, slot, ammo, q );
}
/**
* @brief Parses a player's ship.
*
* @param parent Node of the ship.
* @param is_player Is it the ship the player is currently in?
* @param planet Default planet in case ship location not found.
* @return 0 on success.
*/
static int player_parseShip( xmlNodePtr parent, int is_player, char *planet )
{
char *name, *model, *loc, *q, *id;
int i, n;
double fuel;
Ship *ship_parsed;
Pilot* ship;
xmlNodePtr node, cur, ccur;
int quantity;
Outfit *o;
int ret;
/*const char *str;*/
Commodity *com;
PilotFlags flags;
unsigned int pid;
int autoweap, level, weapid, active_set;
xmlr_attr(parent,"name",name);
xmlr_attr(parent,"model",model);
/* Sane defaults. */
loc = NULL;
pilot_clearFlagsRaw( flags );
pilot_setFlagRaw( flags, PILOT_PLAYER );
pilot_setFlagRaw( flags, PILOT_NO_OUTFITS );
/* Get the ship. */
ship_parsed = ship_get(model);
if (ship_parsed == NULL) {
WARN("Player ship '%s' not found!", model);
/* Clean up. */
free(name);
free(model);
return -1;
}
/* Add GUI if applicable. */
player_guiAdd( ship_parsed->gui );
/* player is currently on this ship */
if (is_player != 0) {
pid = pilot_create( ship_parsed, name, faction_get("Player"), "player", 0., NULL, NULL, flags, -1 );
ship = player.p;
cam_setTargetPilot( pid, 0 );
}
else
ship = pilot_createEmpty( ship_parsed, name, faction_get("Player"), "player", flags );
/* Ship should not have default outfits. */
for (i=0; i<ship->noutfits; i++)
pilot_rmOutfitRaw( ship, ship->outfits[i] );
/* Clean up. */
free(name);
free(model);
/* Defaults. */
fuel = -1;
autoweap = 1;
/* Start parsing. */
node = parent->xmlChildrenNode;
do {
/* Get location. */
if (is_player == 0)
xmlr_str(node,"location",loc);
/* get fuel */
xmlr_float(node,"fuel",fuel);
/* New outfit loading. */
if (xml_isNode(node,"outfits_structure") || xml_isNode(node,"outfits_low")) { /** @todo remove legacy layer for 0.6.0 */
cur = node->xmlChildrenNode;
do { /* load each outfit */
if (xml_isNode(cur,"outfit")) {
xmlr_attr(cur,"slot",q);
n = -1;
if (q != NULL) {
n = atoi(q);
free(q);
}
if ((n<0) || (n >= ship->outfit_nstructure)) {
WARN("Outfit slot out of range, not adding.");
continue;
}
player_parseShipSlot( cur, ship, &ship->outfit_structure[n] );
}
} while (xml_nextNode(cur));
}
else if (xml_isNode(node,"outfits_utility") || xml_isNode(node,"outfits_medium")) { /** @todo remove legacy layer for 0.6.0 */
cur = node->xmlChildrenNode;
do { /* load each outfit */
if (xml_isNode(cur,"outfit")) {
xmlr_attr(cur,"slot",q);
n = -1;
if (q != NULL) {
n = atoi(q);
free(q);
}
if ((n<0) || (n >= ship->outfit_nutility)) {
WARN("Outfit slot out of range, not adding.");
continue;
}
player_parseShipSlot( cur, ship, &ship->outfit_utility[n] );
}
} while (xml_nextNode(cur));
}
else if (xml_isNode(node,"outfits_weapon") || xml_isNode(node,"outfits_high")) { /** @todo remove legacy layer for 0.6.0 */
cur = node->xmlChildrenNode;
do { /* load each outfit */
if (xml_isNode(cur,"outfit")) {
xmlr_attr(cur,"slot",q);
n = -1;
if (q != NULL) {
n = atoi(q);
free(q);
}
if ((n<0) || (n >= ship->outfit_nweapon)) {
WARN("Outfit slot out of range, not adding.");
continue;
}
player_parseShipSlot( cur, ship, &ship->outfit_weapon[n] );
}
} while (xml_nextNode(cur));
}
else if (xml_isNode(node,"commodities")) {
cur = node->xmlChildrenNode;
do {
if (xml_isNode(cur,"commodity")) {
xmlr_attr(cur,"quantity",q);
xmlr_attr(cur,"id",id);
quantity = atoi(q);
i = (id==NULL) ? 0 : atoi(id);
free(q);
if (id != NULL)
free(id);
/* Get the commodity. */
com = commodity_get(xml_get(cur));
if (com == NULL) {
WARN("Unknown commodity '%s' detected, removing.", xml_get(cur));
continue;
}
/* actually add the cargo with id hack
* Note that the player's cargo_free is ignored here.
*/
pilot_cargoAddRaw( ship, com, quantity, 0 );
if (i != 0)
ship->commodities[ ship->ncommodities-1 ].id = i;
}
} while (xml_nextNode(cur));
}
} while (xml_nextNode(node));
/* Update stats. */
pilot_calcStats( ship );
/* Test for sanity. */
if (fuel >= 0)
ship->fuel = MIN(ship->fuel_max, fuel);
if ((is_player == 0) && (planet_get(loc)==NULL))
loc = planet;
/* ships can now be non-spaceworthy on save
* str = pilot_checkSpaceworthy( ship ); */
if (!pilot_slotsCheckSanity( ship )) {
DEBUG("Player ship '%s' failed slot sanity check , removing all outfits and adding to stock.",
ship->name );
/* Remove all outfits. */
for (i=0; i<ship->noutfits; i++) {
o = ship->outfits[i]->outfit;
ret = pilot_rmOutfitRaw( ship, ship->outfits[i] );
if (ret==0)
player_addOutfit( o, 1 );
}
pilot_calcStats( ship );
}
/* add it to the stack if it's not what the player is in */
if (is_player == 0) {
player_stack = realloc(player_stack, sizeof(PlayerShip_t)*(player_nstack+1));
player_stack[player_nstack].p = ship;
player_stack[player_nstack].loc = (loc!=NULL) ? strdup(loc) : strdup("Uknown");
player_nstack++;
}
/* Sets inrange by default if weapon sets are missing. */
for (i=0; i<PILOT_WEAPON_SETS; i++)
pilot_weapSetInrange( ship, i, WEAPSET_INRANGE_PLAYER_DEF );
/* Second pass for weapon sets. */
node = parent->xmlChildrenNode;
do {
if (!xml_isNode(node,"weaponsets"))
continue;
/* Check for autoweap. */
xmlr_attr(node,"autoweap",id);
if (id != NULL) {
autoweap = atoi(id);
free(id);
}
/* Load the last weaponset the player used on this ship. */
xmlr_attr(node,"active_set",id);
if (id != NULL) {
active_set = atoi(id);
free(id);
}
else {
/* set active_set to invalid. will be dealt with later */
active_set = -1;
}
/* Parse weapon sets. */
cur = node->xmlChildrenNode;
do { /* Load each weapon set. */
xml_onlyNodes(cur);
if (!xml_isNode(cur,"weaponset")) {
WARN("Player ship '%s' has unknown node '%s' in 'weaponsets' (expected 'weaponset').",
ship->name, cur->name);
continue;
}
/* Get id. */
xmlr_attr(cur,"id",id);
if (id == NULL) {
WARN("Player ship '%s' missing 'id' tag for weapon set.",ship->name);
continue;
}
i = atoi(id);
free(id);
if ((i < 0) || (i >= PILOT_WEAPON_SETS)) {
WARN("Player ship '%s' has invalid weapon set id '%d' [max %d].",
ship->name, i, PILOT_WEAPON_SETS-1 );
continue;
}
/* Set inrange mode. */
xmlr_attr(cur,"inrange",id);
if (id == NULL)
pilot_weapSetInrange( ship, i, WEAPSET_INRANGE_PLAYER_DEF );
else {
pilot_weapSetInrange( ship, i, atoi(id) );
free(id);
}
if (autoweap) /* Autoweap handles everything except inrange. */
continue;
/* Set type mode. */
xmlr_attr(cur,"type",id);
if (id == NULL) {
WARN("Player ship '%s' missing 'type' tag for weapon set.",ship->name);
continue;
}
pilot_weapSetType( ship, i, atoi(id) );
free(id);
/* Parse individual weapons. */
ccur = cur->xmlChildrenNode;
do {
/* Only nodes. */
xml_onlyNodes(ccur);
/* Only weapon nodes. */
if (!xml_isNode(ccur,"weapon")) {
WARN("Player ship '%s' has unknown 'weaponset' child node '%s' (expected 'weapon').",
ship->name, ccur->name );
continue;
}
/* Get level. */
xmlr_attr(ccur,"level",id);
if (id == NULL) {
WARN("Player ship '%s' missing 'level' tag for weapon set weapon.", ship->name);
continue;
}
level = atoi(id);
free(id);
weapid = xml_getInt(ccur);
if ((weapid < 0) || (weapid >= ship->noutfits)) {
WARN("Player ship '%s' has invalid weapon id %d [max %d].",
ship->name, weapid, ship->noutfits-1 );
continue;
}
/* Add the weapon set. */
pilot_weapSetAdd( ship, i, ship->outfits[weapid], level );
} while (xml_nextNode(ccur));
} while (xml_nextNode(cur));
} while (xml_nextNode(node));
/* Set up autoweap if necessary. */
ship->autoweap = autoweap;
if (autoweap)
pilot_weaponAuto( ship );
pilot_weaponSane( ship );
if (active_set >= 0 && active_set < PILOT_WEAPON_SETS)
ship->active_set = active_set;
else
pilot_weaponSetDefault( ship );
return 0;
}