1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 /**
6  * @file player_autonav.c
7  *
8  * @brief Contains all the player autonav related stuff.
9  */
10 
11 
12 #include "player.h"
13 
14 #include "naev.h"
15 
16 #include "toolkit.h"
17 #include "pause.h"
18 #include "player.h"
19 #include "pilot.h"
20 #include "pilot_ew.h"
21 #include "space.h"
22 #include "conf.h"
23 #include <time.h>
24 
25 
26 extern double player_acc; /**< Player acceleration. */
27 extern int map_npath; /**< @todo remove */
28 
29 static double tc_mod    = 1.; /**< Time compression modifier. */
30 static double tc_base   = 1.; /**< Base compression modifier. */
31 static double tc_down   = 0.; /**< Rate of decrement. */
32 static int tc_rampdown  = 0; /**< Ramping down time compression? */
33 static double lasts;
34 static double lasta;
35 
36 /*
37  * Prototypes.
38  */
39 static int player_autonavSetup (void);
40 static void player_autonav (void);
41 static int player_autonavApproach( const Vector2d *pos, double *dist2, int count_target );
42 static int player_autonavBrake (void);
43 
44 
45 /**
46  * @brief Resets the game speed.
47  */
player_autonavResetSpeed(void)48 void player_autonavResetSpeed (void)
49 {
50    if (player_isFlag(PLAYER_DOUBLESPEED)) {
51      tc_mod         = 2.;
52      pause_setSpeed( 2. );
53    } else {
54      tc_mod         = 1.;
55      pause_setSpeed( 1. );
56    }
57    tc_rampdown = 0;
58 }
59 
60 
61 /**
62  * @brief Starts autonav.
63  */
player_autonavStart(void)64 void player_autonavStart (void)
65 {
66    /* Not under manual control or disabled. */
67    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
68          pilot_isDisabled(player.p))
69       return;
70 
71    if ((player.p->nav_hyperspace == -1) && (player.p->nav_planet== -1))
72       return;
73    else if ((player.p->nav_planet != -1) && !player_getHypPreempt()) {
74       player_autonavPnt( cur_system->planets[ player.p->nav_planet ]->name );
75       return;
76    }
77 
78    if (player.p->fuel < player.p->fuel_consumption) {
79       player_message("\erNot enough fuel to jump for autonav.");
80       return;
81    }
82 
83    if (pilot_isFlag( player.p, PILOT_NOJUMP)) {
84       player_message("\erHyperspace drive is offline.");
85       return;
86    }
87 
88    if (!player_autonavSetup())
89       return;
90 
91    player.autonav = AUTONAV_JUMP_APPROACH;
92 }
93 
94 
95 /**
96  * @brief Prepares the player to enter autonav.
97  *
98  *    @return 1 on success, 0 on failure (disabled, etc.)
99  */
player_autonavSetup(void)100 static int player_autonavSetup (void)
101 {
102    /* Not under manual control or disabled. */
103    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
104          pilot_isDisabled(player.p))
105       return 0;
106 
107    /* Autonav is mutually-exclusive with other autopilot methods. */
108    player_restoreControl( PINPUT_AUTONAV, NULL );
109 
110    player_message("\epAutonav initialized.");
111    if (!player_isFlag(PLAYER_AUTONAV)) {
112 
113       tc_base   = player_isFlag(PLAYER_DOUBLESPEED) ? 2. : 1.;
114       tc_mod    = tc_base;
115       if (conf.compression_mult >= 1.)
116          player.tc_max = MIN( conf.compression_velocity / solid_maxspeed(player.p->solid, player.p->speed, player.p->thrust), conf.compression_mult );
117       else
118          player.tc_max = conf.compression_velocity /
119                solid_maxspeed(player.p->solid, player.p->speed, player.p->thrust);
120 
121       /* Safe cap. */
122       player.tc_max = MAX( 1., player.tc_max );
123    }
124 
125    /* Sane values. */
126    tc_rampdown  = 0;
127    tc_down      = 0.;
128    lasts        = player.p->shield / player.p->shield_max;
129    lasta        = player.p->armour / player.p->armour_max;
130 
131    /* Set flag and tc_mod just in case. */
132    player_setFlag(PLAYER_AUTONAV);
133    pause_setSpeed( tc_mod );
134 
135    /* Make sure time acceleration starts immediately. */
136    player.autonav_timer = 0.;
137 
138    return 1;
139 }
140 
141 
142 /**
143  * @brief Ends the autonav.
144  */
player_autonavEnd(void)145 void player_autonavEnd (void)
146 {
147    player_rmFlag(PLAYER_AUTONAV);
148    player_autonavResetSpeed();
149 }
150 
151 
152 /**
153  * @brief Starts autonav and closes the window.
154  */
player_autonavStartWindow(unsigned int wid,char * str)155 void player_autonavStartWindow( unsigned int wid, char *str)
156 {
157    (void) str;
158    player_autonavStart();
159    window_destroy( wid );
160 }
161 
162 
163 /**
164  * @brief Starts autonav with a local position destination.
165  */
player_autonavPos(double x,double y)166 void player_autonavPos( double x, double y )
167 {
168    if (!player_autonavSetup())
169       return;
170 
171    player.autonav    = AUTONAV_POS_APPROACH;
172    player.autonavmsg = "position";
173    vect_cset( &player.autonav_pos, x, y );
174 }
175 
176 
177 /**
178  * @brief Starts autonav with a planet destination.
179  */
player_autonavPnt(char * name)180 void player_autonavPnt( char *name )
181 {
182    Planet *p;
183 
184    p = planet_get( name );
185    if (!player_autonavSetup())
186       return;
187 
188    player.autonav    = AUTONAV_PNT_APPROACH;
189    player.autonavmsg = p->name;
190    vect_cset( &player.autonav_pos, p->pos.x, p->pos.y );
191 }
192 
193 
194 /**
195  * @brief Handles common time accel ramp-down for autonav to positions and planets.
196  */
player_autonavRampdown(double d)197 static void player_autonavRampdown( double d )
198 {
199    double t, tint;
200    double vel;
201 
202    vel   = MIN( 1.5*player.p->speed, VMOD(player.p->solid->vel) );
203    t     = d / vel * (1. - 0.075 * tc_base);
204    tint  = 3. + 0.5*(3.*(tc_mod-tc_base));
205    if (t < tint) {
206       tc_rampdown = 1;
207       tc_down     = (tc_mod-tc_base) / 3.;
208    }
209 }
210 
211 
212 /**
213  * @brief Aborts regular interstellar autonav, but not in-system autonav.
214  *
215  *    @param reason Human-readable string describing abort condition.
216  */
player_autonavAbortJump(const char * reason)217 void player_autonavAbortJump( const char *reason )
218 {
219    /* No point if player is beyond aborting. */
220    if ((player.p==NULL) || pilot_isFlag(player.p, PILOT_HYPERSPACE))
221       return;
222 
223    if (!player_isFlag(PLAYER_AUTONAV) || ((player.autonav != AUTONAV_JUMP_APPROACH) &&
224          (player.autonav != AUTONAV_JUMP_BRAKE)))
225       return;
226 
227    /* It's definitely not in-system autonav. */
228    player_autonavAbort(reason);
229 }
230 
231 
232 /**
233  * @brief Aborts autonav.
234  *
235  *    @param reason Human-readable string describing abort condition.
236  */
player_autonavAbort(const char * reason)237 void player_autonavAbort( const char *reason )
238 {
239    /* No point if player is beyond aborting. */
240    if ((player.p==NULL) || pilot_isFlag(player.p, PILOT_HYPERSPACE))
241       return;
242 
243    /* Cooldown (handled later) may be script-initiated and we don't
244     * want to make it player-abortable while under manual control. */
245    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
246       return;
247 
248    if (player_isFlag(PLAYER_AUTONAV)) {
249       if (reason != NULL)
250          player_message("\erAutonav aborted: %s!", reason);
251       else
252          player_message("\erAutonav aborted!");
253       player_rmFlag(PLAYER_AUTONAV);
254 
255       /* Get rid of acceleration. */
256       player_accelOver();
257 
258       /* Break possible hyperspacing. */
259       if (pilot_isFlag(player.p, PILOT_HYP_PREP)) {
260          pilot_hyperspaceAbort(player.p);
261          player_message("\epAborting hyperspace sequence.");
262       }
263 
264       /* Reset time compression. */
265       player_autonavEnd();
266    }
267 }
268 
269 
270 /**
271  * @brief Handles the autonavigation process for the player.
272  */
player_autonav(void)273 static void player_autonav (void)
274 {
275    JumpPoint *jp;
276    int ret;
277    double d, t, tint;
278    double vel;
279 
280    switch (player.autonav) {
281       case AUTONAV_JUMP_APPROACH:
282          /* Target jump. */
283          jp    = &cur_system->jumps[ player.p->nav_hyperspace ];
284          ret   = player_autonavApproach( &jp->pos, &d, 0 );
285          if (ret)
286             player.autonav = AUTONAV_JUMP_BRAKE;
287          else if (!tc_rampdown && (map_npath<=1)) {
288             vel   = MIN( 1.5*player.p->speed, VMOD(player.p->solid->vel) );
289             t     = d / vel * (1.2 - .1 * tc_base);
290             /* tint is the integral of the time in per time units.
291              *
292              * tc_mod
293              *    ^
294              *    |
295              *    |\
296              *    | \
297              *    |  \___
298              *    |
299              *    +------> time
300              *    0   3
301              *
302              * We decompose integral in a rectangle (3*1) and a triangle (3*(tc_mod-1.))/2.
303              *  This is the "elapsed time" when linearly decreasing the tc_mod. Which we can
304              *  use to calculate the actual "game time" that'll pass when decreasing the
305              *  tc_mod to 1 during 3 seconds. This can be used then to compare when we want to
306              *  start decrementing.
307              */
308             tint  = 3. + 0.5*(3.*(tc_mod-tc_base));
309             if (t < tint) {
310                tc_rampdown = 1;
311                tc_down     = (tc_mod-tc_base) / 3.;
312             }
313          }
314          break;
315 
316       case AUTONAV_JUMP_BRAKE:
317          /* Target jump. */
318          jp    = &cur_system->jumps[ player.p->nav_hyperspace ];
319          if (player.p->stats.misc_instant_jump) {
320             ret = pilot_interceptPos( player.p, jp->pos.x, jp->pos.y );
321             if (!ret && space_canHyperspace(player.p))
322                ret = 1;
323             player_acc = player.p->solid->thrust / player.p->thrust;
324          }
325          else
326             ret = player_autonavBrake();
327 
328          /* Try to jump or see if braked. */
329          if (ret) {
330             if (space_canHyperspace(player.p))
331                player_jump();
332             player.autonav = AUTONAV_JUMP_APPROACH;
333          }
334 
335          /* See if should ramp down. */
336          if (!tc_rampdown && (map_npath<=1)) {
337             tc_rampdown = 1;
338             tc_down     = (tc_mod-tc_base) / 3.;
339          }
340          break;
341 
342       case AUTONAV_POS_APPROACH:
343          ret = player_autonavApproach( &player.autonav_pos, &d, 1 );
344          if (ret) {
345             player_message( "\epAutonav arrived at position." );
346             player_autonavEnd();
347          }
348          else if (!tc_rampdown)
349             player_autonavRampdown(d);
350          break;
351       case AUTONAV_PNT_APPROACH:
352          ret = player_autonavApproach( &player.autonav_pos, &d, 1 );
353          if (ret) {
354             player_message( "\epAutonav arrived at \e%c%s\e\0.",
355                   planet_getColourChar( planet_get(player.autonavmsg) ),
356                   player.autonavmsg );
357             player_autonavEnd();
358          }
359          else if (!tc_rampdown)
360             player_autonavRampdown(d);
361          break;
362    }
363 }
364 
365 
366 /**
367  * @brief Handles approaching a position with autonav.
368  *
369  *    @param[in] pos Position to go to.
370  *    @param[out] dist2 Square distance left to target.
371  *    @param count_target If 1 it subtracts the braking distance from dist2. Otherwise it returns the full distance.
372  *    @return 1 on completion.
373  */
player_autonavApproach(const Vector2d * pos,double * dist2,int count_target)374 static int player_autonavApproach( const Vector2d *pos, double *dist2, int count_target )
375 {
376    double d, t, vel, dist;
377 
378    /* Only accelerate if facing move dir. */
379    d = pilot_face( player.p, vect_angle( &player.p->solid->pos, pos ) );
380    if (FABS(d) < MIN_DIR_ERR) {
381       if (player_acc < 1.)
382          player_accel( 1. );
383    }
384    else if (player_acc > 0.)
385       player_accelOver();
386 
387    /* Get current time to reach target. */
388    t  = MIN( 1.5*player.p->speed, VMOD(player.p->solid->vel) ) /
389       (player.p->thrust / player.p->solid->mass);
390 
391    /* Get velocity. */
392    vel   = MIN( player.p->speed, VMOD(player.p->solid->vel) );
393 
394    /* Get distance. */
395    dist  = vel*(t+1.1*M_PI/player.p->turn) -
396       0.5*(player.p->thrust/player.p->solid->mass)*t*t;
397 
398    /* Output distance^2 */
399    d        = vect_dist( pos, &player.p->solid->pos );
400    dist     = d - dist;
401    if (count_target)
402       *dist2   = dist;
403    else
404       *dist2   = d;
405 
406    /* See if should start braking. */
407    if (dist < 0.) {
408       player_accelOver();
409       return 1;
410    }
411    return 0;
412 }
413 
414 
415 /**
416  * @brief Handles the autonav braking.
417  *
418  *    @return 1 on completion.
419  */
player_autonavBrake(void)420 static int player_autonavBrake (void)
421 {
422    int ret;
423    JumpPoint *jp;
424    Vector2d pos;
425 
426    if ((player.autonav == AUTONAV_JUMP_BRAKE) && (player.p->nav_hyperspace != -1)) {
427       jp  = &cur_system->jumps[ player.p->nav_hyperspace ];
428 
429       pilot_brakeDist( player.p, &pos );
430       if (vect_dist2( &pos, &jp->pos ) > pow2(jp->radius))
431          ret = pilot_interceptPos( player.p, jp->pos.x, jp->pos.y );
432       else
433          ret = pilot_brake( player.p );
434    }
435    else
436       ret = pilot_brake(player.p);
437 
438    player_acc = player.p->solid->thrust / player.p->thrust;
439 
440    return ret;
441 }
442 
443 /**
444  * @brief Checks whether the speed should be reset due to damage or missile locks.
445  *
446  *    @return 1 if the speed should be reset.
447  */
player_autonavShouldResetSpeed(void)448 int player_autonavShouldResetSpeed (void)
449 {
450    double failpc, shield, armour;
451    int i, n;
452    Pilot **pstk;
453    int hostiles, will_reset;
454 
455    if (!player_isFlag(PLAYER_AUTONAV))
456       return 0;
457 
458    hostiles   = 0;
459    will_reset = 0;
460 
461    failpc = conf.autonav_reset_speed;
462    shield = player.p->shield / player.p->shield_max;
463    armour = player.p->armour / player.p->armour_max;
464 
465    pstk = pilot_getAll( &n );
466    for (i=0; i<n; i++) {
467       if ((pstk[i]->id != PLAYER_ID) && pilot_isHostile( pstk[i] ) &&
468             pilot_inRangePilot( player.p, pstk[i] ) &&
469             !pilot_isDisabled( pstk[i] ) &&
470             !pilot_isFlag( pstk[i], PILOT_BRIBED )) {
471          hostiles = 1;
472          break;
473       }
474    }
475 
476    if (hostiles) {
477       if (failpc > .995) {
478          will_reset = 1;
479          player.autonav_timer = MAX( player.autonav_timer, 0. );
480       }
481       else if ((shield < lasts && shield < failpc) || armour < lasta) {
482          will_reset = 1;
483          player.autonav_timer = MAX( player.autonav_timer, 2. );
484       }
485    }
486 
487    lasts = shield;
488    lasta = armour;
489 
490    if (will_reset || (player.autonav_timer > 0)) {
491       player_autonavResetSpeed();
492       return 1;
493    }
494    return 0;
495 }
496 
497 
498 /**
499  * @brief Handles autonav thinking.
500  *
501  *    @param pplayer Player doing the thinking.
502  */
player_thinkAutonav(Pilot * pplayer,double dt)503 void player_thinkAutonav( Pilot *pplayer, double dt )
504 {
505    if (player.autonav_timer > 0.)
506       player.autonav_timer -= dt;
507 
508    player_autonavShouldResetSpeed();
509    if ((player.autonav == AUTONAV_JUMP_APPROACH) ||
510          (player.autonav == AUTONAV_JUMP_BRAKE)) {
511       /* If we're already at the target. */
512       if (player.p->nav_hyperspace == -1)
513          player_autonavAbort("Target changed to current system");
514 
515       /* Need fuel. */
516       else if (pplayer->fuel < pplayer->fuel_consumption)
517          player_autonavAbort("Not enough fuel for autonav to continue");
518 
519       else
520          player_autonav();
521    }
522 
523    /* Keep on moving. */
524    else
525       player_autonav();
526 }
527 
528 
529 /**
530  * @brief Updates the player's autonav.
531  *
532  *    @param dt Current delta tick (should be real delta tick, not game delta tick).
533  */
player_updateAutonav(double dt)534 void player_updateAutonav( double dt )
535 {
536    const double dis_dead = 5.0;
537    const double dis_mod  = 0.5;
538    const double dis_max  = 4.0;
539    const double dis_ramp = 6.0;
540 
541    if (paused || (player.p==NULL) || pilot_isFlag(player.p, PILOT_DEAD))
542       return;
543 
544    /* We handle disabling here. */
545    if (pilot_isFlag(player.p, PILOT_DISABLED)) {
546       /* It is somewhat like:
547        *        /------------\        4x
548        *       /              \
549        * -----/                \----- 1x
550        *
551        * <---><-><----------><-><--->
552        *   5   6     X        6   5    Real time
553        *   5   15    X        15  5    Game time
554        *
555        * For triangles we have to add the rectangle and triangle areas.
556        */
557       /* 5 second deadtime. */
558       if (player.p->dtimer_accum < dis_dead)
559          tc_mod = tc_base;
560       else {
561          /* Ramp down. */
562          if (player.p->dtimer - player.p->dtimer_accum < dis_dead + (dis_max-tc_base)*dis_ramp/2 + tc_base*dis_ramp)
563             tc_mod = MAX( tc_base, tc_mod - dis_mod*dt );
564          /* Normal. */
565          else
566             tc_mod = MIN( dis_max, tc_mod + dis_mod*dt );
567       }
568       pause_setSpeed( tc_mod );
569       return;
570    }
571 
572    /* Must be autonaving. */
573    if (!player_isFlag(PLAYER_AUTONAV))
574       return;
575 
576    /* Ramping down. */
577    if (tc_rampdown) {
578       if (tc_mod != tc_base) {
579          tc_mod = MAX( tc_base, tc_mod-tc_down*dt );
580          pause_setSpeed( tc_mod );
581       }
582       return;
583    }
584 
585    /* We'll update the time compression here. */
586    if (tc_mod == player.tc_max)
587       return;
588    else
589       tc_mod += 0.2 * dt * (player.tc_max-tc_base);
590    /* Avoid going over. */
591    if (tc_mod > player.tc_max)
592       tc_mod = player.tc_max;
593    pause_setSpeed( tc_mod );
594 }
595 
596 
597