Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
597 lines (499 sloc) 15.3 KB
/*
* See Licensing and Copyright notice in naev.h
*/
/**
* @file player_autonav.c
*
* @brief Contains all the player autonav related stuff.
*/
#include "player.h"
#include "naev.h"
#include "toolkit.h"
#include "pause.h"
#include "player.h"
#include "pilot.h"
#include "pilot_ew.h"
#include "space.h"
#include "conf.h"
#include <time.h>
extern double player_acc; /**< Player acceleration. */
extern int map_npath; /**< @todo remove */
static double tc_mod = 1.; /**< Time compression modifier. */
static double tc_base = 1.; /**< Base compression modifier. */
static double tc_down = 0.; /**< Rate of decrement. */
static int tc_rampdown = 0; /**< Ramping down time compression? */
static double lasts;
static double lasta;
/*
* Prototypes.
*/
static int player_autonavSetup (void);
static void player_autonav (void);
static int player_autonavApproach( const Vector2d *pos, double *dist2, int count_target );
static int player_autonavBrake (void);
/**
* @brief Resets the game speed.
*/
void player_autonavResetSpeed (void)
{
if (player_isFlag(PLAYER_DOUBLESPEED)) {
tc_mod = 2.;
pause_setSpeed( 2. );
} else {
tc_mod = 1.;
pause_setSpeed( 1. );
}
tc_rampdown = 0;
}
/**
* @brief Starts autonav.
*/
void player_autonavStart (void)
{
/* Not under manual control or disabled. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
pilot_isDisabled(player.p))
return;
if ((player.p->nav_hyperspace == -1) && (player.p->nav_planet== -1))
return;
else if ((player.p->nav_planet != -1) && !player_getHypPreempt()) {
player_autonavPnt( cur_system->planets[ player.p->nav_planet ]->name );
return;
}
if (player.p->fuel < player.p->fuel_consumption) {
player_message("\erNot enough fuel to jump for autonav.");
return;
}
if (pilot_isFlag( player.p, PILOT_NOJUMP)) {
player_message("\erHyperspace drive is offline.");
return;
}
if (!player_autonavSetup())
return;
player.autonav = AUTONAV_JUMP_APPROACH;
}
/**
* @brief Prepares the player to enter autonav.
*
* @return 1 on success, 0 on failure (disabled, etc.)
*/
static int player_autonavSetup (void)
{
/* Not under manual control or disabled. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
pilot_isDisabled(player.p))
return 0;
/* Autonav is mutually-exclusive with other autopilot methods. */
player_restoreControl( PINPUT_AUTONAV, NULL );
player_message("\epAutonav initialized.");
if (!player_isFlag(PLAYER_AUTONAV)) {
tc_base = player_isFlag(PLAYER_DOUBLESPEED) ? 2. : 1.;
tc_mod = tc_base;
if (conf.compression_mult >= 1.)
player.tc_max = MIN( conf.compression_velocity / solid_maxspeed(player.p->solid, player.p->speed, player.p->thrust), conf.compression_mult );
else
player.tc_max = conf.compression_velocity /
solid_maxspeed(player.p->solid, player.p->speed, player.p->thrust);
/* Safe cap. */
player.tc_max = MAX( 1., player.tc_max );
}
/* Sane values. */
tc_rampdown = 0;
tc_down = 0.;
lasts = player.p->shield / player.p->shield_max;
lasta = player.p->armour / player.p->armour_max;
/* Set flag and tc_mod just in case. */
player_setFlag(PLAYER_AUTONAV);
pause_setSpeed( tc_mod );
/* Make sure time acceleration starts immediately. */
player.autonav_timer = 0.;
return 1;
}
/**
* @brief Ends the autonav.
*/
void player_autonavEnd (void)
{
player_rmFlag(PLAYER_AUTONAV);
player_autonavResetSpeed();
}
/**
* @brief Starts autonav and closes the window.
*/
void player_autonavStartWindow( unsigned int wid, char *str)
{
(void) str;
player_autonavStart();
window_destroy( wid );
}
/**
* @brief Starts autonav with a local position destination.
*/
void player_autonavPos( double x, double y )
{
if (!player_autonavSetup())
return;
player.autonav = AUTONAV_POS_APPROACH;
player.autonavmsg = "position";
vect_cset( &player.autonav_pos, x, y );
}
/**
* @brief Starts autonav with a planet destination.
*/
void player_autonavPnt( char *name )
{
Planet *p;
p = planet_get( name );
if (!player_autonavSetup())
return;
player.autonav = AUTONAV_PNT_APPROACH;
player.autonavmsg = p->name;
vect_cset( &player.autonav_pos, p->pos.x, p->pos.y );
}
/**
* @brief Handles common time accel ramp-down for autonav to positions and planets.
*/
static void player_autonavRampdown( double d )
{
double t, tint;
double vel;
vel = MIN( 1.5*player.p->speed, VMOD(player.p->solid->vel) );
t = d / vel * (1. - 0.075 * tc_base);
tint = 3. + 0.5*(3.*(tc_mod-tc_base));
if (t < tint) {
tc_rampdown = 1;
tc_down = (tc_mod-tc_base) / 3.;
}
}
/**
* @brief Aborts regular interstellar autonav, but not in-system autonav.
*
* @param reason Human-readable string describing abort condition.
*/
void player_autonavAbortJump( const char *reason )
{
/* No point if player is beyond aborting. */
if ((player.p==NULL) || pilot_isFlag(player.p, PILOT_HYPERSPACE))
return;
if (!player_isFlag(PLAYER_AUTONAV) || ((player.autonav != AUTONAV_JUMP_APPROACH) &&
(player.autonav != AUTONAV_JUMP_BRAKE)))
return;
/* It's definitely not in-system autonav. */
player_autonavAbort(reason);
}
/**
* @brief Aborts autonav.
*
* @param reason Human-readable string describing abort condition.
*/
void player_autonavAbort( const char *reason )
{
/* No point if player is beyond aborting. */
if ((player.p==NULL) || pilot_isFlag(player.p, PILOT_HYPERSPACE))
return;
/* Cooldown (handled later) may be script-initiated and we don't
* want to make it player-abortable while under manual control. */
if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
return;
if (player_isFlag(PLAYER_AUTONAV)) {
if (reason != NULL)
player_message("\erAutonav aborted: %s!", reason);
else
player_message("\erAutonav aborted!");
player_rmFlag(PLAYER_AUTONAV);
/* Get rid of acceleration. */
player_accelOver();
/* Break possible hyperspacing. */
if (pilot_isFlag(player.p, PILOT_HYP_PREP)) {
pilot_hyperspaceAbort(player.p);
player_message("\epAborting hyperspace sequence.");
}
/* Reset time compression. */
player_autonavEnd();
}
}
/**
* @brief Handles the autonavigation process for the player.
*/
static void player_autonav (void)
{
JumpPoint *jp;
int ret;
double d, t, tint;
double vel;
switch (player.autonav) {
case AUTONAV_JUMP_APPROACH:
/* Target jump. */
jp = &cur_system->jumps[ player.p->nav_hyperspace ];
ret = player_autonavApproach( &jp->pos, &d, 0 );
if (ret)
player.autonav = AUTONAV_JUMP_BRAKE;
else if (!tc_rampdown && (map_npath<=1)) {
vel = MIN( 1.5*player.p->speed, VMOD(player.p->solid->vel) );
t = d / vel * (1.2 - .1 * tc_base);
/* tint is the integral of the time in per time units.
*
* tc_mod
* ^
* |
* |\
* | \
* | \___
* |
* +------> time
* 0 3
*
* We decompose integral in a rectangle (3*1) and a triangle (3*(tc_mod-1.))/2.
* This is the "elapsed time" when linearly decreasing the tc_mod. Which we can
* use to calculate the actual "game time" that'll pass when decreasing the
* tc_mod to 1 during 3 seconds. This can be used then to compare when we want to
* start decrementing.
*/
tint = 3. + 0.5*(3.*(tc_mod-tc_base));
if (t < tint) {
tc_rampdown = 1;
tc_down = (tc_mod-tc_base) / 3.;
}
}
break;
case AUTONAV_JUMP_BRAKE:
/* Target jump. */
jp = &cur_system->jumps[ player.p->nav_hyperspace ];
if (player.p->stats.misc_instant_jump) {
ret = pilot_interceptPos( player.p, jp->pos.x, jp->pos.y );
if (!ret && space_canHyperspace(player.p))
ret = 1;
player_acc = player.p->solid->thrust / player.p->thrust;
}
else
ret = player_autonavBrake();
/* Try to jump or see if braked. */
if (ret) {
if (space_canHyperspace(player.p))
player_jump();
player.autonav = AUTONAV_JUMP_APPROACH;
}
/* See if should ramp down. */
if (!tc_rampdown && (map_npath<=1)) {
tc_rampdown = 1;
tc_down = (tc_mod-tc_base) / 3.;
}
break;
case AUTONAV_POS_APPROACH:
ret = player_autonavApproach( &player.autonav_pos, &d, 1 );
if (ret) {
player_message( "\epAutonav arrived at position." );
player_autonavEnd();
}
else if (!tc_rampdown)
player_autonavRampdown(d);
break;
case AUTONAV_PNT_APPROACH:
ret = player_autonavApproach( &player.autonav_pos, &d, 1 );
if (ret) {
player_message( "\epAutonav arrived at \e%c%s\e\0.",
planet_getColourChar( planet_get(player.autonavmsg) ),
player.autonavmsg );
player_autonavEnd();
}
else if (!tc_rampdown)
player_autonavRampdown(d);
break;
}
}
/**
* @brief Handles approaching a position with autonav.
*
* @param[in] pos Position to go to.
* @param[out] dist2 Square distance left to target.
* @param count_target If 1 it subtracts the braking distance from dist2. Otherwise it returns the full distance.
* @return 1 on completion.
*/
static int player_autonavApproach( const Vector2d *pos, double *dist2, int count_target )
{
double d, t, vel, dist;
/* Only accelerate if facing move dir. */
d = pilot_face( player.p, vect_angle( &player.p->solid->pos, pos ) );
if (FABS(d) < MIN_DIR_ERR) {
if (player_acc < 1.)
player_accel( 1. );
}
else if (player_acc > 0.)
player_accelOver();
/* Get current time to reach target. */
t = MIN( 1.5*player.p->speed, VMOD(player.p->solid->vel) ) /
(player.p->thrust / player.p->solid->mass);
/* Get velocity. */
vel = MIN( player.p->speed, VMOD(player.p->solid->vel) );
/* Get distance. */
dist = vel*(t+1.1*M_PI/player.p->turn) -
0.5*(player.p->thrust/player.p->solid->mass)*t*t;
/* Output distance^2 */
d = vect_dist( pos, &player.p->solid->pos );
dist = d - dist;
if (count_target)
*dist2 = dist;
else
*dist2 = d;
/* See if should start braking. */
if (dist < 0.) {
player_accelOver();
return 1;
}
return 0;
}
/**
* @brief Handles the autonav braking.
*
* @return 1 on completion.
*/
static int player_autonavBrake (void)
{
int ret;
JumpPoint *jp;
Vector2d pos;
if ((player.autonav == AUTONAV_JUMP_BRAKE) && (player.p->nav_hyperspace != -1)) {
jp = &cur_system->jumps[ player.p->nav_hyperspace ];
pilot_brakeDist( player.p, &pos );
if (vect_dist2( &pos, &jp->pos ) > pow2(jp->radius))
ret = pilot_interceptPos( player.p, jp->pos.x, jp->pos.y );
else
ret = pilot_brake( player.p );
}
else
ret = pilot_brake(player.p);
player_acc = player.p->solid->thrust / player.p->thrust;
return ret;
}
/**
* @brief Checks whether the speed should be reset due to damage or missile locks.
*
* @return 1 if the speed should be reset.
*/
int player_autonavShouldResetSpeed (void)
{
double failpc, shield, armour;
int i, n;
Pilot **pstk;
int hostiles, will_reset;
if (!player_isFlag(PLAYER_AUTONAV))
return 0;
hostiles = 0;
will_reset = 0;
failpc = conf.autonav_reset_speed;
shield = player.p->shield / player.p->shield_max;
armour = player.p->armour / player.p->armour_max;
pstk = pilot_getAll( &n );
for (i=0; i<n; i++) {
if ((pstk[i]->id != PLAYER_ID) && pilot_isHostile( pstk[i] ) &&
pilot_inRangePilot( player.p, pstk[i] ) &&
!pilot_isDisabled( pstk[i] ) &&
!pilot_isFlag( pstk[i], PILOT_BRIBED )) {
hostiles = 1;
break;
}
}
if (hostiles) {
if (failpc > .995) {
will_reset = 1;
player.autonav_timer = MAX( player.autonav_timer, 0. );
}
else if ((shield < lasts && shield < failpc) || armour < lasta) {
will_reset = 1;
player.autonav_timer = MAX( player.autonav_timer, 2. );
}
}
lasts = shield;
lasta = armour;
if (will_reset || (player.autonav_timer > 0)) {
player_autonavResetSpeed();
return 1;
}
return 0;
}
/**
* @brief Handles autonav thinking.
*
* @param pplayer Player doing the thinking.
*/
void player_thinkAutonav( Pilot *pplayer, double dt )
{
if (player.autonav_timer > 0.)
player.autonav_timer -= dt;
player_autonavShouldResetSpeed();
if ((player.autonav == AUTONAV_JUMP_APPROACH) ||
(player.autonav == AUTONAV_JUMP_BRAKE)) {
/* If we're already at the target. */
if (player.p->nav_hyperspace == -1)
player_autonavAbort("Target changed to current system");
/* Need fuel. */
else if (pplayer->fuel < pplayer->fuel_consumption)
player_autonavAbort("Not enough fuel for autonav to continue");
else
player_autonav();
}
/* Keep on moving. */
else
player_autonav();
}
/**
* @brief Updates the player's autonav.
*
* @param dt Current delta tick (should be real delta tick, not game delta tick).
*/
void player_updateAutonav( double dt )
{
const double dis_dead = 5.0;
const double dis_mod = 0.5;
const double dis_max = 4.0;
const double dis_ramp = 6.0;
if (paused || (player.p==NULL) || pilot_isFlag(player.p, PILOT_DEAD))
return;
/* We handle disabling here. */
if (pilot_isFlag(player.p, PILOT_DISABLED)) {
/* It is somewhat like:
* /------------\ 4x
* / \
* -----/ \----- 1x
*
* <---><-><----------><-><--->
* 5 6 X 6 5 Real time
* 5 15 X 15 5 Game time
*
* For triangles we have to add the rectangle and triangle areas.
*/
/* 5 second deadtime. */
if (player.p->dtimer_accum < dis_dead)
tc_mod = tc_base;
else {
/* Ramp down. */
if (player.p->dtimer - player.p->dtimer_accum < dis_dead + (dis_max-tc_base)*dis_ramp/2 + tc_base*dis_ramp)
tc_mod = MAX( tc_base, tc_mod - dis_mod*dt );
/* Normal. */
else
tc_mod = MIN( dis_max, tc_mod + dis_mod*dt );
}
pause_setSpeed( tc_mod );
return;
}
/* Must be autonaving. */
if (!player_isFlag(PLAYER_AUTONAV))
return;
/* Ramping down. */
if (tc_rampdown) {
if (tc_mod != tc_base) {
tc_mod = MAX( tc_base, tc_mod-tc_down*dt );
pause_setSpeed( tc_mod );
}
return;
}
/* We'll update the time compression here. */
if (tc_mod == player.tc_max)
return;
else
tc_mod += 0.2 * dt * (player.tc_max-tc_base);
/* Avoid going over. */
if (tc_mod > player.tc_max)
tc_mod = player.tc_max;
pause_setSpeed( tc_mod );
}
Jump to Line
Something went wrong with that request. Please try again.