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