1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 /**
6  * @file player.c
7  *
8  * @brief Contains all the player related stuff.
9  */
10 
11 
12 #include "player.h"
13 
14 #include "naev.h"
15 
16 #include <stdlib.h>
17 
18 #include "nxml.h"
19 #include "pilot.h"
20 #include "log.h"
21 #include "opengl.h"
22 #include "font.h"
23 #include "ndata.h"
24 #include "space.h"
25 #include "rng.h"
26 #include "land.h"
27 #include "land_outfits.h"
28 #include "sound.h"
29 #include "economy.h"
30 #include "pause.h"
31 #include "menu.h"
32 #include "toolkit.h"
33 #include "dialogue.h"
34 #include "mission.h"
35 #include "nlua_misn.h"
36 #include "ntime.h"
37 #include "hook.h"
38 #include "map.h"
39 #include "map_overlay.h"
40 #include "nfile.h"
41 #include "spfx.h"
42 #include "unidiff.h"
43 #include "comm.h"
44 #include "intro.h"
45 #include "perlin.h"
46 #include "ai.h"
47 #include "music.h"
48 #include "gui.h"
49 #include "gui_omsg.h"
50 #include "nlua_var.h"
51 #include "escort.h"
52 #include "event.h"
53 #include "conf.h"
54 #include "nebula.h"
55 #include "equipment.h"
56 #include "camera.h"
57 #include "claim.h"
58 #include "player_gui.h"
59 #include "start.h"
60 #include "input.h"
61 #include "news.h"
62 #include "nstring.h"
63 
64 
65 /*
66  * player stuff
67  */
68 Player_t player; /**< Local player. */
69 static Ship* player_ship      = NULL; /**< Temporary ship to hold when naming it */
70 static credits_t player_creds = 0; /**< Temporary hack for when creating. */
71 static const char *player_message_noland = NULL; /**< No landing message (when PLAYER_NOLAND is set). */
72 
73 /*
74  * Licenses.
75  */
76 static char **player_licenses = NULL; /**< Licenses player has. */
77 static int player_nlicenses   = 0; /**< Number of licenses player has. */
78 
79 
80 /*
81  * player sounds.
82  */
83 static int player_engine_group = -1; /**< Player engine sound group. */
84 static int player_hyper_group = -1; /**< Player hyperspace sound group. */
85 static int player_gui_group   = -1; /**< Player GUI sound group. */
86 int snd_target                = -1; /**< Sound when targeting. */
87 int snd_jump                  = -1; /**< Sound when can jump. */
88 int snd_nav                   = -1; /**< Sound when changing nav computer. */
89 int snd_hail                  = -1; /**< Sound when being hailed. */
90 /* Hyperspace sounds. */
91 int snd_hypPowUp              = -1; /**< Hyperspace power up sound. */
92 int snd_hypEng                = -1; /**< Hyperspace engine sound. */
93 int snd_hypPowDown            = -1; /**< Hyperspace power down sound. */
94 int snd_hypPowUpJump          = -1; /**< Hyperspace Power up to jump sound. */
95 int snd_hypJump               = -1; /**< Hyperspace jump sound. */
96 static int player_lastEngineSound = -1; /**< Last engine sound. */
97 static int player_hailCounter = 0; /**< Number of times to play the hail. */
98 static double player_hailTimer = 0.; /**< Timer for hailing. */
99 
100 
101 /*
102  * player pilot stack - ships he has
103  */
104 static PlayerShip_t* player_stack   = NULL;  /**< Stack of ships player has. */
105 static int player_nstack            = 0;     /**< Number of ships player has. */
106 
107 
108 /*
109  * player outfit stack - outfits he has
110  */
111 static PlayerOutfit_t *player_outfits  = NULL;  /**< Outfits player has. */
112 static int player_noutfits             = 0;     /**< Number of outfits player has. */
113 static int player_moutfits             = 0;     /**< Current allocated memory. */
114 #define OUTFIT_CHUNKSIZE               32       /**< Allocation chunk size. */
115 
116 
117 /*
118  * player global properties
119  */
120 /* used in input.c */
121 double player_left         = 0.; /**< Player left turn velocity from input. */
122 double player_right        = 0.; /**< Player right turn velocity from input. */
123 double player_acc          = 0.; /**< Accel velocity from input. */
124 /* for death and such */
125 static double player_timer = 0.; /**< For death and such. */
126 
127 
128 /*
129  * unique mission stack.
130  */
131 static int* missions_done  = NULL; /**< Saves position of completed missions. */
132 static int missions_mdone  = 0; /**< Memory size of completed missions. */
133 static int missions_ndone  = 0; /**< Number of completed missions. */
134 
135 
136 /*
137  * unique event stack.
138  */
139 static int* events_done  = NULL; /**< Saves position of completed events. */
140 static int events_mdone  = 0; /**< Memory size of completed events. */
141 static int events_ndone  = 0; /**< Number of completed events. */
142 
143 
144 /*
145  * Extern stuff for player ships.
146  */
147 extern Pilot** pilot_stack;
148 extern int pilot_nstack;
149 
150 
151 /*
152  * map stuff for autonav
153  */
154 extern int map_npath;
155 
156 
157 /*
158  * prototypes
159  */
160 /*
161  * internal
162  */
163 static void player_checkHail (void);
164 /* creation */
165 static void player_newSetup( int tutorial );
166 static int player_newMake (void);
167 static Pilot* player_newShipMake( const char* name );
168 /* sound */
169 static void player_initSound (void);
170 /* save/load */
171 static int player_saveEscorts( xmlTextWriterPtr writer );
172 static int player_saveShipSlot( xmlTextWriterPtr writer, PilotOutfitSlot *slot, int i );
173 static int player_saveShip( xmlTextWriterPtr writer,
174       Pilot* ship, char* loc );
175 static Planet* player_parse( xmlNodePtr parent );
176 static int player_parseDoneMissions( xmlNodePtr parent );
177 static int player_parseDoneEvents( xmlNodePtr parent );
178 static int player_parseLicenses( xmlNodePtr parent );
179 static void player_parseShipSlot( xmlNodePtr node, Pilot *ship, PilotOutfitSlot *slot );
180 static int player_parseShip( xmlNodePtr parent, int is_player, char *planet );
181 static int player_parseEscorts( xmlNodePtr parent );
182 static void player_addOutfitToPilot( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s );
183 /* Misc. */
184 static int player_filterSuitablePlanet( Planet *p );
185 static void player_planetOutOfRangeMsg (void);
186 static int player_outfitCompare( const void *arg1, const void *arg2 );
187 static int player_thinkMouseFly(void);
188 static int preemption = 0; /* Hyperspace target/untarget preemption. */
189 /*
190  * externed
191  */
192 int player_save( xmlTextWriterPtr writer ); /* save.c */
193 Planet* player_load( xmlNodePtr parent ); /* save.c */
194 
195 
196 /**
197  * @brief Initializes player stuff.
198  */
player_init(void)199 int player_init (void)
200 {
201    player_initSound();
202    return 0;
203 }
204 
205 /**
206  * @brief Sets up a new player.
207  */
player_newSetup(int tutorial)208 static void player_newSetup( int tutorial )
209 {
210    double x, y;
211 
212    /* Set up GUI. */
213    gui_setDefaults();
214 
215    /* Setup sound */
216    player_initSound();
217 
218    /* Clean up player stuff if we'll be recreating. */
219    player_cleanup();
220 
221    /* For pretty background. */
222    pilots_cleanAll();
223    if (!tutorial) {
224       space_init( start_system() );
225       start_position( &x, &y );
226    }
227    else
228       start_tutPosition( &x, &y );
229 
230    cam_setTargetPos( x, y, 0 );
231    cam_setZoom( conf.zoom_far );
232 
233    /* Clear the init message for new game. */
234    gui_clearMessages();
235 }
236 
237 
238 /**
239  * @brief Creates a new tutorial player.
240  *
241  *   - Cleans up after old players.
242  *   - Prompts for name.
243  */
player_newTutorial(void)244 void player_newTutorial (void)
245 {
246    int ret;
247    double x, y;
248 
249    /* Set up new player. */
250    player_newSetup( 1 );
251 
252    /* New name. */
253    player.name = strdup( "John Doe" );
254 
255    /* Time. */
256    ntime_set( start_date() );
257 
258    /* Welcome message - must be before space_init. */
259    player_message( "\egWelcome to "APPNAME" Tutorial!" );
260    player_message( "\eg v%s", naev_version(0) );
261 
262    /* Try to create the pilot, if fails reask for player name. */
263    player_ship = ship_get( start_ship() );
264    if (player_ship==NULL) {
265       WARN("Ship not properly set by module.");
266       return;
267    }
268    player_creds   = 0;
269    player_newShipMake( "Star Voyager" );
270    start_tutPosition( &x, &y );
271    vect_cset( &player.p->solid->pos, x, y );
272    vectnull( &player.p->solid->vel );
273    player.p->solid->dir = RNGF() * 2.*M_PI;
274    space_init( start_tutSystem() );
275 
276    /* Monies. */
277    player.p->credits = start_credits();
278 
279    /* clear the map */
280    map_clear();
281 
282    /* Start the economy. */
283    economy_init();
284 
285    /* Start the news */
286    news_init();
287 
288    /* Play music. */
289    music_choose( "ambient" );
290 
291    /* Load the GUI. */
292    gui_load( gui_pick() );
293 
294    /* It's the tutorial. */
295    player_setFlag( PLAYER_TUTORIAL );
296 
297    /* Add the mission if found. */
298    if (start_tutMission() != NULL) {
299       ret = mission_start(start_tutMission(), NULL);
300       if (ret < 0)
301          WARN("Failed to run start tutorial mission '%s'.", start_tutMission());
302    }
303 
304    /* Add the event if found. */
305    if (!menu_isOpen(MENU_MAIN) && (start_tutEvent() != NULL)) {
306       if (event_start( start_tutEvent(), NULL ))
307          WARN("Failed to run start event '%s'.", start_tutEvent());
308    }
309 }
310 
311 
312 /**
313  * @brief Creates a new player.
314  *
315  *   - Cleans up after old players.
316  *   - Prompts for name.
317  *
318  * @sa player_newMake
319  */
player_new(void)320 void player_new (void)
321 {
322    int r;
323 
324    /* Set up new player. */
325    player_newSetup( 0 );
326 
327    /* Get the name. */
328    player.name = dialogue_input( "Player Name", 2, 20,
329          "Please write your name:" );
330 
331    /* Player cancelled dialogue. */
332    if (player.name == NULL) {
333       menu_main();
334       return;
335    }
336 
337    if (nfile_fileExists("%ssaves/%s.ns", nfile_dataPath(), player.name)) {
338       r = dialogue_YesNo("Overwrite",
339             "You already have a pilot named %s. Overwrite?",player.name);
340       if (r==0) { /* no */
341          player_new();
342          return;
343       }
344    }
345 
346    if (player_newMake())
347       return;
348 
349    /* Display the intro. */
350    intro_display( "dat/intro", "intro" );
351 
352    /* Play music. */
353    music_choose( "ambient" );
354 
355    /* Add the mission if found. */
356    if (start_mission() != NULL) {
357       if (mission_start(start_mission(), NULL) < 0)
358          WARN("Failed to run start mission '%s'.", start_mission());
359    }
360 
361    /* Add the event if found. */
362    if (start_event() != NULL) {
363       if (event_start( start_event(), NULL ))
364          WARN("Failed to run start event '%s'.", start_event());
365    }
366 
367    /* Run the load event trigger. */
368    events_trigger( EVENT_TRIGGER_LOAD );
369 
370    /* Load the GUI. */
371    gui_load( gui_pick() );
372 }
373 
374 
375 /**
376  * @brief Actually creates a new player.
377  *
378  *    @return 0 on success.
379  */
player_newMake(void)380 static int player_newMake (void)
381 {
382    Ship *ship;
383    const char *shipname;
384    double x,y;
385 
386    /* Time. */
387    ntime_set( start_date() );
388 
389    /* Welcome message - must be before space_init. */
390    player_message( "\egWelcome to "APPNAME"!" );
391    player_message( "\eg v%s", naev_version(0) );
392 
393    /* Try to create the pilot, if fails reask for player name. */
394    ship = ship_get( start_ship() );
395    shipname = start_shipname();
396    if (ship==NULL) {
397       WARN("Ship not properly set by module.");
398       return -1;
399    }
400    /* Setting a default name in the XML prevents naming prompt. */
401    if (player_newShip( ship, shipname, 0, (shipname==NULL) ? 0 : 1 ) == NULL) {
402       player_new();
403       return -1;
404    }
405    start_position( &x, &y );
406    vect_cset( &player.p->solid->pos, x, y );
407    vectnull( &player.p->solid->vel );
408    player.p->solid->dir = RNGF() * 2.*M_PI;
409    space_init( start_system() );
410 
411    /* Monies. */
412    player.p->credits = start_credits();
413 
414    /* clear the map */
415    map_clear();
416 
417    /* Start the economy. */
418    economy_init();
419 
420    /* Start the news */
421    news_init();
422 
423    return 0;
424 }
425 
426 
427 /**
428  * @brief Creates a new ship for player.
429  *
430  *    @param ship New ship to get.
431  *    @param def_name Default name to give it if cancelled.
432  *    @param trade Whether or not to trade player's current ship with the new ship.
433  *    @param noname Whether or not to let the player name it.
434  *    @return Newly created pilot on success or NULL if dialogue was cancelled.
435  *
436  * @sa player_newShipMake
437  */
player_newShip(Ship * ship,const char * def_name,int trade,int noname)438 Pilot* player_newShip( Ship* ship, const char *def_name,
439       int trade, int noname )
440 {
441    char *ship_name, *old_name;
442    int i, len, w;
443    Pilot *new_ship;
444 
445    /* temporary values while player doesn't exist */
446    player_creds = (player.p != NULL) ? player.p->credits : 0;
447    player_ship    = ship;
448    if (!noname)
449       ship_name      = dialogue_input( "Ship Name", 3, 20,
450             "Please name your new ship:" );
451    else
452       ship_name      = NULL;
453 
454    /* Dialogue cancelled. */
455    if (ship_name == NULL) {
456       /* No default name, fail. */
457       if (def_name == NULL)
458          return NULL;
459 
460       /* Add default name. */
461       i = 2;
462       len = strlen(def_name)+10;
463       ship_name = malloc( len );
464       strncpy( ship_name, def_name, len );
465       while (player_hasShip(ship_name)) {
466          nsnprintf( ship_name, len, "%s %d", def_name, i );
467          i++;
468       }
469    }
470 
471    /* Must not have same name. */
472    if (player_hasShip(ship_name)) {
473       dialogue_msg( "Name collision",
474             "Please do not give the ship the same name as another of your ships.");
475       return NULL;
476    }
477 
478    new_ship = player_newShipMake( ship_name );
479 
480    /* Player is trading ship in. */
481    if (trade) {
482       if (player.p == NULL)
483          ERR("Player ship isn't valid... This shouldn't happen!");
484       old_name = player.p->name;
485       player_swapShip( ship_name ); /* Move to the new ship. */
486       player_rmShip( old_name );
487    }
488 
489    free(ship_name);
490 
491    /* Update ship list if landed. */
492    if (landed) {
493       w = land_getWid( LAND_WINDOW_EQUIPMENT );
494       equipment_regenLists( w, 0, 1 );
495    }
496 
497    return new_ship;
498 }
499 
500 /**
501  * @brief Actually creates the new ship.
502  */
player_newShipMake(const char * name)503 static Pilot* player_newShipMake( const char* name )
504 {
505    Vector2d vp, vv;
506    PilotFlags flags;
507    PlayerShip_t *ship;
508    Pilot *new_pilot;
509    double px, py, dir;
510    unsigned int id;
511 
512    /* store the current ship if it exists */
513    pilot_clearFlagsRaw( flags );
514    pilot_setFlagRaw( flags, PILOT_PLAYER );
515 
516    /* in case we're respawning */
517    player_rmFlag( PLAYER_CREATING );
518 
519    /* create the player */
520    if (player.p == NULL) {
521       /* Set position to defaults. */
522       if (player.p != NULL) {
523          px    = player.p->solid->pos.x;
524          py    = player.p->solid->pos.y;
525          dir   = player.p->solid->dir;
526       }
527       else {
528          px    = 0.;
529          py    = 0.;
530          dir   = 0.;
531       }
532       vect_cset( &vp, px, py );
533       vect_cset( &vv, 0., 0. );
534 
535       /* Create the player. */
536       id = pilot_create( player_ship, name, faction_get("Player"), "player",
537             dir, &vp, &vv, flags );
538       cam_setTargetPilot( id, 0 );
539       new_pilot = pilot_get( id );
540    }
541    else {
542       /* Grow memory. */
543       player_stack = realloc(player_stack, sizeof(PlayerShip_t)*(player_nstack+1));
544       ship        = &player_stack[player_nstack];
545       /* Create the ship. */
546       ship->p     = pilot_createEmpty( player_ship, name, faction_get("Player"), "player", flags );
547       ship->loc   = strdup( land_planet->name );
548       player_nstack++;
549       new_pilot   = ship->p;
550    }
551 
552    if (player.p == NULL)
553       ERR("Something seriously wonky went on, newly created player does not exist, bailing!");
554 
555    /* Add GUI. */
556    player_guiAdd( player_ship->gui );
557 
558    /* money. */
559    player.p->credits = player_creds;
560    player_creds = 0;
561 
562    return new_pilot;
563 }
564 
565 
566 /**
567  * @brief Swaps player's current ship with their ship named shipname.
568  *
569  *    @param shipname Ship to change to.
570  */
player_swapShip(char * shipname)571 void player_swapShip( char* shipname )
572 {
573    int i, j;
574    Pilot* ship;
575    Vector2d v;
576    double dir;
577 
578    for (i=0; i<player_nstack; i++) {
579       if (strcmp(shipname,player_stack[i].p->name)!=0)
580          continue;
581 
582       /* swap player and ship */
583       ship = player_stack[i].p;
584 
585       /* move credits over */
586       ship->credits = player.p->credits;
587 
588       /* move cargo over */
589       pilot_cargoMove( ship, player.p );
590 
591       /* Store position. */
592       v = player.p->solid->pos;
593       dir = player.p->solid->dir;
594 
595       /* extra pass to calculate stats */
596       pilot_calcStats( ship );
597       pilot_calcStats( player.p );
598 
599       /* now swap the players */
600       player_stack[i].p = player.p;
601       for (j=0; j<pilot_nstack; j++) /* find pilot in stack to swap */
602          if (pilot_stack[j] == player.p) {
603             player.p         = ship;
604             pilot_stack[j] = ship;
605             break;
606          }
607 
608       /* Copy position back. */
609       player.p->solid->pos = v;
610       player.p->solid->dir = dir;
611 
612       /* Fill the tank. */
613       if (landed)
614          land_refuel();
615 
616       /* Set some gui stuff. */
617       gui_load( gui_pick() );
618 
619       /* Bind camera. */
620       cam_setTargetPilot( player.p->id, 0 );
621       return;
622    }
623    WARN( "Unable to swap player.p with ship '%s': ship does not exist!", shipname );
624 }
625 
626 
627 /**
628  * @brief Calculates the price of one of the player's ships.
629  *
630  *    @param shipname Name of the ship.
631  *    @return The price of the ship in credits.
632  */
player_shipPrice(char * shipname)633 credits_t player_shipPrice( char* shipname )
634 {
635    int i;
636    Pilot *ship = NULL;
637 
638    if (strcmp(shipname,player.p->name)==0)
639       ship = player.p;
640    else {
641       /* Find the ship. */
642       for (i=0; i<player_nstack; i++) {
643          if (strcmp(shipname,player_stack[i].p->name)==0) {
644             ship = player_stack[i].p;
645             break;
646          }
647       }
648    }
649 
650    /* Not found. */
651    if (ship == NULL) {
652       WARN( "Unable to find price for player's ship '%s': ship does not exist!", shipname );
653       return -1;
654    }
655 
656    return pilot_worth( ship );
657 }
658 
659 
660 /**
661  * @brief Removes one of the player's ships.
662  *
663  *    @param shipname Name of the ship to remove.
664  */
player_rmShip(char * shipname)665 void player_rmShip( char* shipname )
666 {
667    int i, w;
668 
669    for (i=0; i<player_nstack; i++) {
670       /* Not the ship we are looking for. */
671       if (strcmp(shipname,player_stack[i].p->name)!=0)
672          continue;
673 
674       /* Free player ship and location. */
675       pilot_free(player_stack[i].p);
676       free(player_stack[i].loc);
677 
678       /* Move memory to make adjacent. */
679       memmove( player_stack+i, player_stack+i+1,
680             sizeof(PlayerShip_t) * (player_nstack-i-1) );
681       player_nstack--; /* Shrink stack. */
682       /* Realloc memory to smaller size. */
683       player_stack = realloc( player_stack,
684             sizeof(PlayerShip_t) * (player_nstack) );
685    }
686 
687    /* Update ship list if landed. */
688    if (landed) {
689       w = land_getWid( LAND_WINDOW_EQUIPMENT );
690       equipment_regenLists( w, 0, 1 );
691    }
692 }
693 
694 
695 /**
696  * @brief Cleans up player stuff like player_stack.
697  */
player_cleanup(void)698 void player_cleanup (void)
699 {
700    int i;
701 
702    /* Enable all input. */
703    input_enableAll();
704 
705    /* Clean up other stuff. */
706    diff_clear();
707    var_cleanup();
708    missions_cleanup();
709    events_cleanup();
710    space_clearKnown();
711    land_cleanup();
712    map_cleanup();
713 
714    /* Reset controls. */
715    player_accelOver();
716    player_left  = 0.;
717    player_right = 0.;
718 
719    /* Clear player. */
720    player_clear();
721 
722    /* Clear hail timer. */
723    player_hailCounter   = 0;
724    player_hailTimer     = 0.;
725 
726    /* Clear messages. */
727    gui_clearMessages();
728 
729    /* Reset factions. */
730    factions_reset();
731 
732    /* clean up name */
733    if (player.name != NULL) {
734       free(player.name);
735       player.name = NULL;
736    }
737 
738    /* Clean up gui. */
739    gui_cleanup();
740    player_guiCleanup();
741    ovr_setOpen(0);
742 
743    /* clean up the stack */
744    for (i=0; i<player_nstack; i++) {
745       pilot_free(player_stack[i].p);
746       free(player_stack[i].loc);
747    }
748    if (player_stack != NULL)
749       free(player_stack);
750    player_stack = NULL;
751    /* nothing left */
752    player_nstack = 0;
753 
754    /* Free outfits. */
755    if (player_outfits != NULL)
756       free(player_outfits);
757    player_outfits  = NULL;
758    player_noutfits = 0;
759    player_moutfits = 0;
760 
761    /* Clean up missions */
762    if (missions_done != NULL)
763       free(missions_done);
764    missions_done = NULL;
765    missions_ndone = 0;
766    missions_mdone = 0;
767 
768    /* Clean up events. */
769    if (events_done != NULL)
770       free(events_done);
771    events_done = NULL;
772    events_ndone = 0;
773    events_mdone = 0;
774 
775    /* Clean up licenses. */
776    if (player_nlicenses > 0) {
777       for (i=0; i<player_nlicenses; i++)
778          free(player_licenses[i]);
779       free(player_licenses);
780       player_licenses = NULL;
781       player_nlicenses = 0;
782    }
783 
784    /* Clear claims. */
785    claim_clear();
786 
787    /* just in case purge the pilot stack */
788    pilots_cleanAll();
789 
790    /* Reset some player stuff. */
791    player_creds   = 0;
792    player.crating = 0;
793    if (player.gui != NULL)
794       free( player.gui );
795    player.gui = NULL;
796 
797    /* Clear omsg. */
798    omsg_cleanup();
799 
800    /* Stop the sounds. */
801    sound_stopAll();
802 
803    /* Reset time compression. */
804    pause_setSpeed( 1.0 );
805 
806    /* Clean up. */
807    memset( &player, 0, sizeof(Player_t) );
808    player_setFlag(PLAYER_CREATING);
809 }
810 
811 
812 static int player_soundReserved = 0; /**< Has the player already reserved sound? */
813 /**
814  * @brief Initializes the player sounds.
815  */
player_initSound(void)816 static void player_initSound (void)
817 {
818    if (player_soundReserved)
819       return;
820 
821    /* Allocate channels. */
822    player_engine_group  = sound_createGroup(1); /* Channel for engine noises. */
823    player_gui_group     = sound_createGroup(4);
824    player_hyper_group   = sound_createGroup(4);
825    sound_speedGroup( player_gui_group, 0 ); /* Disable pitch shift. */
826    player_soundReserved = 1;
827 
828    /* Get sounds. */
829    snd_target           = sound_get("target");
830    snd_jump             = sound_get("jump");
831    snd_nav              = sound_get("nav");
832    snd_hail             = sound_get("hail");
833    snd_hypPowUp         = sound_get("hyperspace_powerup");
834    snd_hypEng           = sound_get("hyperspace_engine");
835    snd_hypPowDown       = sound_get("hyperspace_powerdown");
836    snd_hypPowUpJump     = sound_get("hyperspace_powerupjump");
837    snd_hypJump          = sound_get("hyperspace_jump");
838 }
839 
840 
841 /**
842  * @brief Plays a GUI sound (unaffected by time accel).
843  *
844  *    @param sound ID of the sound to play.
845  *    @param once Play only once?
846  */
player_soundPlayGUI(int sound,int once)847 void player_soundPlayGUI( int sound, int once )
848 {
849    sound_playGroup( player_gui_group, sound, once );
850 }
851 
852 
853 /**
854  * @brief Plays a sound at the player.
855  *
856  *    @param sound ID of the sound to play.
857  *    @param once Play only once?
858  */
player_soundPlay(int sound,int once)859 void player_soundPlay( int sound, int once )
860 {
861    sound_playGroup( player_hyper_group, sound, once );
862 }
863 
864 
865 /**
866  * @brief Stops playing player sounds.
867  */
player_soundStop(void)868 void player_soundStop (void)
869 {
870    if (player_gui_group >= 0)
871       sound_stopGroup( player_gui_group );
872    if (player_engine_group >= 0)
873       sound_stopGroup( player_engine_group );
874    if (player_hyper_group >= 0)
875       sound_stopGroup( player_hyper_group );
876 
877    /* No last engine sound. */
878    player_lastEngineSound = -1;
879 }
880 
881 
882 /**
883  * @brief Pauses the ship's sounds.
884  */
player_soundPause(void)885 void player_soundPause (void)
886 {
887    if (player_engine_group >= 0)
888       sound_pauseGroup(player_engine_group);
889    if (player_hyper_group >= 0)
890       sound_pauseGroup(player_hyper_group);
891 }
892 
893 
894 /**
895  * @brief Resumes the ship's sounds.
896  */
player_soundResume(void)897 void player_soundResume (void)
898 {
899    if (player_engine_group >= 0)
900       sound_resumeGroup(player_engine_group);
901    if (player_hyper_group >= 0)
902       sound_resumeGroup(player_hyper_group);
903 }
904 
905 
906 /**
907  * @brief Warps the player to the new position
908  *
909  *    @param x X value of the position to warp to.
910  *    @param y Y value of the position to warp to.
911  */
player_warp(const double x,const double y)912 void player_warp( const double x, const double y )
913 {
914    vect_cset( &player.p->solid->pos, x, y );
915 }
916 
917 
918 /**
919  * @brief Clears the targets.
920  */
player_clear(void)921 void player_clear (void)
922 {
923    if (player.p != NULL) {
924       pilot_setTarget( player.p, player.p->id );
925       gui_setTarget();
926    }
927 
928    /* Clear the noland flag. */
929    player_rmFlag( PLAYER_NOLAND );
930 }
931 
932 
933 static char* player_ratings[] = {
934       "Harmless",
935       "Mostly Harmless",
936       "Smallfry",
937       "Average",
938       "Above Average",
939       "Major",
940       "Intimidating",
941       "Fearsome",
942       "Terrifying",
943       "Unstoppable",
944       "Godlike"
945 }; /**< Combat ratings. */
946 /**
947  * @brief Gets the player's combat rating in a human-readable string.
948  *
949  *    @return The player's combat rating in a human readable string.
950  */
player_rating(void)951 const char* player_rating (void)
952 {
953    if (player.crating == 0.) return player_ratings[0];
954    else if (player.crating < 25.) return player_ratings[1];
955    else if (player.crating < 50.) return player_ratings[2];
956    else if (player.crating < 100.) return player_ratings[3];
957    else if (player.crating < 200.) return player_ratings[4];
958    else if (player.crating < 500.) return player_ratings[5];
959    else if (player.crating < 1000.) return player_ratings[6];
960    else if (player.crating < 2000.) return player_ratings[7];
961    else if (player.crating < 5000.) return player_ratings[8];
962    else if (player.crating < 10000.) return player_ratings[9];
963    else return player_ratings[10];
964 }
965 
966 
967 /**
968  * @brief Checks to see if the player has enough credits.
969  *
970  *    @param amount Amount of credits to check to see if the player has.
971  *    @return 1 if the player has enough credits.
972  */
player_hasCredits(credits_t amount)973 int player_hasCredits( credits_t amount )
974 {
975    return pilot_hasCredits( player.p, amount );
976 }
977 
978 
979 /**
980  * @brief Modifies the amount of credits the player has.
981  *
982  *    @param amount Quantity to modify player's credits by.
983  *    @return Amount of credits the player has.
984  */
player_modCredits(credits_t amount)985 credits_t player_modCredits( credits_t amount )
986 {
987    return pilot_modCredits( player.p, amount );
988 }
989 
990 
991 /**
992  * @brief Renders the player
993  */
player_render(double dt)994 void player_render( double dt )
995 {
996    /*
997     * Check to see if the death menu should pop up.
998     */
999    if (player_isFlag(PLAYER_DESTROYED)) {
1000       player_timer -= dt;
1001       if (!toolkit_isOpen() && !player_isFlag(PLAYER_CREATING) &&
1002             (player_timer < 0.))
1003          menu_death();
1004    }
1005 
1006    /*
1007     * Render the player.
1008     */
1009    if ((player.p != NULL) && !player_isFlag(PLAYER_CREATING) &&
1010          !pilot_isFlag( player.p, PILOT_INVISIBLE))
1011       pilot_render(player.p, dt);
1012 }
1013 
1014 
1015 /**
1016  * @brief Basically uses keyboard input instead of AI input. Used in pilot.c.
1017  *
1018  *    @param pplayer Player to think.
1019  */
player_think(Pilot * pplayer,const double dt)1020 void player_think( Pilot* pplayer, const double dt )
1021 {
1022    (void) dt;
1023    Pilot *target;
1024    double turn;
1025    int facing, fired;
1026 
1027    /* last i heard, the dead don't think */
1028    if (pilot_isFlag(pplayer,PILOT_DEAD)) {
1029       /* no sense in accelerating or turning */
1030       pilot_setThrust( pplayer, 0. );
1031       pilot_setTurn( pplayer, 0. );
1032       return;
1033    }
1034 
1035    ai_think( pplayer, dt );
1036 
1037    /* Under manual control is special. */
1038    if (pilot_isFlag( pplayer, PILOT_MANUAL_CONTROL )) {
1039       return;
1040    }
1041 
1042    /* Not facing anything yet. */
1043    facing = 0;
1044 
1045    /* Autonav takes over normal controls. */
1046    if (player_isFlag(PLAYER_AUTONAV)) {
1047       player_thinkAutonav( pplayer, dt );
1048 
1049       /* Disable turning. */
1050       facing = 1;
1051    }
1052 
1053    /* Mouse-flying is enabled. */
1054    if (!facing && player_isFlag(PLAYER_MFLY))
1055       facing = player_thinkMouseFly();
1056 
1057    /* turning taken over by PLAYER_FACE */
1058    if (!facing && player_isFlag(PLAYER_FACE)) {
1059       /* Try to face pilot target. */
1060       if (player.p->target != PLAYER_ID) {
1061          target = pilot_get(player.p->target);
1062          if (target != NULL) {
1063             pilot_face( pplayer,
1064                   vect_angle( &player.p->solid->pos, &target->solid->pos ));
1065 
1066             /* Disable turning. */
1067             facing = 1;
1068          }
1069       }
1070       /* If not try to face planet target. */
1071       else if ((player.p->nav_planet != -1) && ((preemption == 0) || (player.p->nav_hyperspace == -1))) {
1072          pilot_face( pplayer,
1073                vect_angle( &player.p->solid->pos,
1074                   &cur_system->planets[ player.p->nav_planet ]->pos ));
1075          /* Disable turning. */
1076          facing = 1;
1077       }
1078       else if (player.p->nav_hyperspace != -1) {
1079          pilot_face( pplayer,
1080                vect_angle( &player.p->solid->pos,
1081                   &cur_system->jumps[ player.p->nav_hyperspace ].pos ));
1082          /* Disable turning. */
1083          facing = 1;
1084       }
1085    }
1086 
1087    /* turning taken over by PLAYER_REVERSE */
1088    if (player_isFlag(PLAYER_REVERSE)) {
1089 
1090       /* Check to see if already stopped. */
1091       /*
1092       if (VMOD(pplayer->solid->vel) < MIN_VEL_ERR)
1093          player_accel( 0. );
1094 
1095       else {
1096          d = pilot_face( pplayer, VANGLE(player.p->solid->vel) + M_PI );
1097          if ((player_acc < 1.) && (d < MAX_DIR_ERR))
1098             player_accel( 1. );
1099       }
1100       */
1101 
1102       /*
1103        * If the player has reverse thrusters, fire those.
1104        */
1105       if (player.p->stats.misc_reverse_thrust)
1106          player_accel( -PILOT_REVERSE_THRUST );
1107       else if (!facing){
1108          pilot_face( pplayer, VANGLE(player.p->solid->vel) + M_PI );
1109          /* Disable turning. */
1110          facing = 1;
1111       }
1112    }
1113 
1114    /* normal turning scheme */
1115    if (!facing) {
1116       turn = 0;
1117       if (player_isFlag(PLAYER_TURN_LEFT))
1118          turn -= player_left;
1119       if (player_isFlag(PLAYER_TURN_RIGHT))
1120          turn += player_right;
1121       turn = CLAMP( -1., 1., turn );
1122       pilot_setTurn( pplayer, -turn );
1123    }
1124 
1125    /*
1126     * Weapon shooting stuff
1127     */
1128    fired = 0;
1129 
1130    /* Primary weapon. */
1131    if (player_isFlag(PLAYER_PRIMARY)) {
1132       fired |= pilot_shoot( pplayer, 0 );
1133       player_setFlag(PLAYER_PRIMARY_L);
1134    }
1135    else if (player_isFlag(PLAYER_PRIMARY_L)) {
1136       pilot_shootStop( pplayer, 0 );
1137       player_rmFlag(PLAYER_PRIMARY_L);
1138    }
1139    /* Secondary weapon - we use PLAYER_SECONDARY_L to track last frame. */
1140    if (player_isFlag(PLAYER_SECONDARY)) { /* needs target */
1141       fired |= pilot_shoot( pplayer, 1 );
1142       player_setFlag(PLAYER_SECONDARY_L);
1143    }
1144    else if (player_isFlag(PLAYER_SECONDARY_L)) {
1145       pilot_shootStop( pplayer, 1 );
1146       player_rmFlag(PLAYER_SECONDARY_L);
1147    }
1148 
1149    if (fired) {
1150       player.autonav_timer = MAX( player.autonav_timer, 1. );
1151       player_autonavResetSpeed();
1152    }
1153 
1154    pilot_setThrust( pplayer, player_acc );
1155 }
1156 
1157 
1158 /**
1159  * @brief Player update function.
1160  *
1161  *    @param pplayer Player to update.
1162  *    @param dt Current deltatick.
1163  */
player_update(Pilot * pplayer,const double dt)1164 void player_update( Pilot *pplayer, const double dt )
1165 {
1166    /* Update normally. */
1167    pilot_update( pplayer, dt );
1168 
1169    /* Update player.p specific stuff. */
1170    if (!player_isFlag(PLAYER_DESTROYED))
1171       player_updateSpecific( pplayer, dt );
1172 }
1173 
1174 
1175 /**
1176  * @brief Does a player specific update.
1177  *
1178  *    @param pplayer Player to update.
1179  *    @param dt Current deltatick.
1180  */
player_updateSpecific(Pilot * pplayer,const double dt)1181 void player_updateSpecific( Pilot *pplayer, const double dt )
1182 {
1183    int engsound;
1184 
1185    /* Calculate engine sound to use. */
1186    if (pilot_isFlag(pplayer, PILOT_AFTERBURNER))
1187       engsound = pplayer->afterburner->outfit->u.afb.sound;
1188    else if ((pplayer->solid->thrust > 1e-3) || (pplayer->solid->thrust < -1e-3)) {
1189       /* See if is in hyperspace. */
1190       if (pilot_isFlag(pplayer, PILOT_HYPERSPACE))
1191          engsound = snd_hypEng;
1192       else
1193          engsound = pplayer->ship->sound;
1194    }
1195    else
1196       engsound = -1;
1197    /* See if sound must change. */
1198    if (player_lastEngineSound != engsound) {
1199       sound_stopGroup( player_engine_group );
1200       if (engsound >= 0)
1201          sound_playGroup( player_engine_group, engsound, 0 );
1202    }
1203    player_lastEngineSound = engsound;
1204 
1205    /* Sound. */
1206    /*
1207     * Sound is now camera-specific and thus not player specific. A bit sad really.
1208    sound_updateListener( pplayer->solid->dir,
1209          pplayer->solid->pos.x, pplayer->solid->pos.y,
1210          pplayer->solid->vel.x, pplayer->solid->vel.y );
1211    */
1212 
1213    /* See if must play hail sound. */
1214    if (player_hailCounter > 0) {
1215       player_hailTimer -= dt;
1216       if (player_hailTimer < 0.) {
1217          player_soundPlayGUI( snd_hail, 1 );
1218          player_hailCounter--;
1219          player_hailTimer = 3.;
1220       }
1221    }
1222 }
1223 
1224 
1225 /*
1226  *    For use in keybindings
1227  */
1228 /**
1229  * @brief Activates a player's weapon set.
1230  */
player_weapSetPress(int id,int type,int repeat)1231 void player_weapSetPress( int id, int type, int repeat )
1232 {
1233    if (repeat)
1234       return;
1235 
1236    if ((type > 0) && ((player.p == NULL) || toolkit_isOpen()))
1237       return;
1238 
1239    if (player.p == NULL)
1240       return;
1241 
1242    if (pilot_isFlag(player.p, PILOT_HYP_PREP) ||
1243          pilot_isFlag(player.p, PILOT_HYPERSPACE) ||
1244          pilot_isFlag(player.p, PILOT_LANDING) ||
1245          pilot_isFlag(player.p, PILOT_TAKEOFF))
1246       return;
1247 
1248    pilot_weapSetPress( player.p, id, type );
1249 }
1250 
1251 
1252 /**
1253  * @brief Aborts autonav and other states that take control of the ship.
1254  *
1255  *    @param reason Reason for aborting (see player.h)
1256  *    @param str String accompanying the reason.
1257  */
player_restoreControl(int reason,char * str)1258 void player_restoreControl( int reason, char *str )
1259 {
1260    if (player.p==NULL)
1261       return;
1262 
1263    if (reason != PINPUT_AUTONAV) {
1264       /* Autonav should be harder to abort when paused. */
1265       if (!paused || reason != PINPUT_MOVEMENT)
1266          player_autonavAbort(str);
1267    }
1268 
1269    if (reason != PINPUT_BRAKING) {
1270       pilot_rmFlag(player.p, PILOT_BRAKING);
1271       pilot_rmFlag(player.p, PILOT_COOLDOWN_BRAKE);
1272       if (pilot_isFlag(player.p, PILOT_COOLDOWN))
1273          pilot_cooldownEnd(player.p, str);
1274    }
1275 }
1276 
1277 
1278 /**
1279  * @brief Sets the player's target planet.
1280  *
1281  *    @param id Target planet or -1 if none should be selected.
1282  */
player_targetPlanetSet(int id)1283 void player_targetPlanetSet( int id )
1284 {
1285    int old;
1286 
1287    if (id >= cur_system->nplanets) {
1288       WARN("Trying to set player's planet target to invalid ID '%d'", id);
1289       return;
1290    }
1291 
1292    if ((player.p == NULL) || pilot_isFlag( player.p, PILOT_LANDING ))
1293       return;
1294 
1295    old = player.p->nav_planet;
1296    player.p->nav_planet = id;
1297    player_hyperspacePreempt((id < 0) ? 1 : 0);
1298    if (old != id) {
1299       player_rmFlag(PLAYER_LANDACK);
1300       if (id >= 0)
1301          player_soundPlayGUI(snd_nav, 1);
1302    }
1303    gui_forceBlink();
1304    gui_setNav();
1305 }
1306 
1307 
1308 /**
1309  * @brief Cycle through planet targets.
1310  */
player_targetPlanet(void)1311 void player_targetPlanet (void)
1312 {
1313    int id, i;
1314 
1315    /* Not under manual control. */
1316    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
1317       return;
1318 
1319    /* Find next planet target. */
1320    for (id=player.p->nav_planet+1; id<cur_system->nplanets; id++)
1321       if (planet_isKnown( cur_system->planets[id] ))
1322          break;
1323 
1324    /* Try to select the lowest-indexed valid planet. */
1325    if (id >= cur_system->nplanets ) {
1326       id = -1;
1327       for (i=0; i<cur_system->nplanets; i++)
1328          if (planet_isKnown( cur_system->planets[i] )) {
1329             id = i;
1330             break;
1331          }
1332    }
1333 
1334    /* Untarget if out of range. */
1335    player_targetPlanetSet( id );
1336 }
1337 
1338 
1339 /**
1340  * @brief Try to land or target closest planet if no land target.
1341  */
player_land(void)1342 void player_land (void)
1343 {
1344    int i;
1345    int tp, silent;
1346    double td, d;
1347    Planet *planet;
1348 
1349    silent = 0; /* Whether to suppress the land ack noise. */
1350 
1351    if (landed) { /* player is already landed */
1352       takeoff(1);
1353       return;
1354    }
1355 
1356    /* Not under manual control or disabled. */
1357    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
1358          pilot_isDisabled(player.p))
1359       return;
1360 
1361    /* Already landing. */
1362    if ((pilot_isFlag( player.p, PILOT_LANDING) ||
1363          pilot_isFlag( player.p, PILOT_TAKEOFF)))
1364       return;
1365 
1366    /* Check if there are planets to land on. */
1367    if (cur_system->nplanets == 0) {
1368       player_messageRaw( "\erThere are no planets to land on." );
1369       return;
1370    }
1371 
1372    if (player.p->nav_planet == -1) { /* get nearest planet target */
1373       td = -1; /* temporary distance */
1374       tp = -1; /* temporary planet */
1375       for (i=0; i<cur_system->nplanets; i++) {
1376          planet = cur_system->planets[i];
1377          d = vect_dist(&player.p->solid->pos,&planet->pos);
1378          if (pilot_inRangePlanet( player.p, i ) &&
1379                planet_hasService(planet,PLANET_SERVICE_LAND) &&
1380                ((tp==-1) || ((td == -1) || (td > d)))) {
1381             tp = i;
1382             td = d;
1383          }
1384       }
1385       player_targetPlanetSet( tp );
1386       player_hyperspacePreempt(0);
1387 
1388       /* no landable planet */
1389       if (player.p->nav_planet < 0)
1390          return;
1391 
1392       silent = 1; /* Suppress further targeting noises. */
1393    }
1394    /*check if planet is in range*/
1395    else if (!pilot_inRangePlanet( player.p, player.p->nav_planet)) {
1396       player_planetOutOfRangeMsg();
1397       return;
1398    }
1399 
1400    if (player_isFlag(PLAYER_NOLAND)) {
1401       player_message( "\er%s", player_message_noland );
1402       return;
1403    }
1404    else if (pilot_isFlag( player.p, PILOT_NOLAND)) {
1405       player_message( "\erDocking stabilizers malfunctioning, cannot land." );
1406       return;
1407    }
1408 
1409    /* attempt to land at selected planet */
1410    planet = cur_system->planets[player.p->nav_planet];
1411    if (!planet_hasService(planet, PLANET_SERVICE_LAND)) {
1412       player_messageRaw( "\erYou can't land here." );
1413       return;
1414    }
1415    else if (!player_isFlag(PLAYER_LANDACK)) { /* no landing authorization */
1416       if (planet_hasService(planet,PLANET_SERVICE_INHABITED)) { /* Basic services */
1417          if (planet->can_land || (planet->land_override > 0))
1418             player_message( "\e%c%s>\e0 %s", planet_getColourChar(planet),
1419                   planet->name, planet->land_msg );
1420          else if (planet->bribed && (planet->land_override >= 0))
1421             player_message( "\e%c%s>\e0 %s", planet_getColourChar(planet),
1422                   planet->name, planet->bribe_ack_msg );
1423          else { /* Hostile */
1424             player_message( "\e%c%s>\e0 %s", planet_getColourChar(planet),
1425                   planet->name, planet->land_msg );
1426             return;
1427          }
1428       }
1429       else /* No shoes, no shirt, no lifeforms, no service. */
1430          player_message( "\epReady to land on %s.", planet->name );
1431 
1432       player_setFlag(PLAYER_LANDACK);
1433       if (!silent)
1434          player_soundPlayGUI(snd_nav, 1);
1435 
1436       return;
1437    }
1438    else if (vect_dist2(&player.p->solid->pos,&planet->pos) > pow2(planet->radius)) {
1439       player_message("\erYou are too far away to land on %s.", planet->name);
1440       return;
1441    }
1442    else if ((pow2(VX(player.p->solid->vel)) + pow2(VY(player.p->solid->vel))) >
1443          (double)pow2(MAX_HYPERSPACE_VEL)) {
1444       player_message("\erYou are going too fast to land on %s.", planet->name);
1445       return;
1446    }
1447 
1448    /* Abort autonav. */
1449    player_restoreControl(0, NULL);
1450 
1451    /* Stop afterburning. */
1452    pilot_afterburnOver( player.p );
1453    /* Stop accelerating. */
1454    player_accelOver();
1455 
1456    /* Stop all on outfits. */
1457    if (pilot_outfitOffAll( player.p ) > 0)
1458       pilot_calcStats( player.p );
1459 
1460    /* Start landing. */
1461    player_soundPause();
1462    player.p->ptimer = PILOT_LANDING_DELAY;
1463    pilot_setFlag( player.p, PILOT_LANDING );
1464    pilot_setThrust( player.p, 0. );
1465    pilot_setTurn( player.p, 0. );
1466 }
1467 
1468 
1469 /**
1470  * @brief Revokes landing authorization if the player's reputation is too low.
1471  */
player_checkLandAck(void)1472 void player_checkLandAck( void )
1473 {
1474    Planet *p;
1475 
1476    /* No authorization to revoke. */
1477    if ((player.p == NULL) || !player_isFlag(PLAYER_LANDACK))
1478       return;
1479 
1480    /* Avoid a potential crash if PLAYER_LANDACK is set inappropriately. */
1481    if (player.p->nav_planet < 0) {
1482       WARN("Player has landing permission, but no valid planet targeted.");
1483       return;
1484    }
1485 
1486    p = cur_system->planets[ player.p->nav_planet ];
1487 
1488    /* Player can still land. */
1489    if (p->can_land || (p->land_override > 0) || p->bribed)
1490       return;
1491 
1492    player_rmFlag(PLAYER_LANDACK);
1493    player_message( "\e%c%s>\e0 Landing permission revoked.",
1494          planet_getColourChar(p), p->name );
1495 }
1496 
1497 
1498 /**
1499  * @brief Checks whether the player's ship is able to takeoff.
1500  */
player_canTakeoff(void)1501 int player_canTakeoff(void)
1502 {
1503    return !pilot_checkSpaceworthy(player.p);
1504 }
1505 
1506 
1507 /**
1508  * @brief Sets the no land message.
1509  *
1510  *    @brief str Message to set when the player is not allowed to land temporarily.
1511  */
player_nolandMsg(const char * str)1512 void player_nolandMsg( const char *str )
1513 {
1514    if (str != NULL)
1515       player_message_noland = str;
1516    else
1517       player_message_noland = "You are not allowed to land at this moment.";
1518 }
1519 
1520 
1521 /**
1522  * @brief Sets the player's hyperspace target.
1523  *
1524  *    @param id ID of the hyperspace target.
1525  */
player_targetHyperspaceSet(int id)1526 void player_targetHyperspaceSet( int id )
1527 {
1528    int old;
1529 
1530    if (id >= cur_system->njumps) {
1531       WARN("Trying to set player's hyperspace target to invalid ID '%d'", id);
1532       return;
1533    }
1534 
1535    if (pilot_isFlag(player.p, PILOT_HYP_PREP) ||
1536          pilot_isFlag(player.p, PILOT_HYP_BEGIN) ||
1537          pilot_isFlag(player.p, PILOT_HYPERSPACE))
1538       return;
1539 
1540 
1541    old = player.p->nav_hyperspace;
1542    player.p->nav_hyperspace = id;
1543    player_hyperspacePreempt((id < 0) ? 0 : 1);
1544    if ((old != id) && (id >= 0))
1545       player_soundPlayGUI(snd_nav,1);
1546    gui_setNav();
1547 }
1548 
1549 
1550 /**
1551  * @brief Gets a hyperspace target.
1552  */
player_targetHyperspace(void)1553 void player_targetHyperspace (void)
1554 {
1555    int id, i;
1556 
1557    /* Not under manual control. */
1558    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
1559       return;
1560 
1561    map_clear(); /* clear the current map path */
1562 
1563    for (id=player.p->nav_hyperspace+1; id<cur_system->njumps; id++)
1564       if (jp_isKnown( &cur_system->jumps[id]))
1565          break;
1566 
1567    /* Try to find the lowest-indexed valid jump. */
1568    if (id >= cur_system->njumps) {
1569       id = -1;
1570       for (i=0; i<cur_system->njumps; i++)
1571          if (jp_isUsable( &cur_system->jumps[i])) {
1572             id = i;
1573             break;
1574          }
1575    }
1576 
1577    player_targetHyperspaceSet( id );
1578 
1579    /* Map gets special treatment if open. */
1580    if (id == -1)
1581       map_select( NULL , 0);
1582    else
1583       map_select( cur_system->jumps[ id ].target, 0 );
1584 }
1585 
1586 
1587 /**
1588  * @brief Enables or disables jump points preempting planets in autoface and target clearing.
1589  *
1590  *    @param preempt Boolean; 1 preempts planet target.
1591  */
player_hyperspacePreempt(int preempt)1592 void player_hyperspacePreempt( int preempt )
1593 {
1594    preemption = preempt;
1595 }
1596 
1597 
1598 /**
1599  * @brief Returns whether the jump point target should preempt the planet target.
1600  *
1601  *    @return Boolean; 1 preempts planet target.
1602  */
player_getHypPreempt(void)1603 int player_getHypPreempt(void)
1604 {
1605    return preemption;
1606 }
1607 
1608 
1609 /**
1610  * @brief Starts the hail sounds and aborts autoNav
1611  */
player_hailStart(void)1612 void player_hailStart (void)
1613 {
1614    player_hailCounter = 5;
1615 
1616    /* Abort autonav. */
1617    player_messageRaw("\erReceiving hail!");
1618    player_autonavResetSpeed();
1619    player.autonav_timer = MAX( player.autonav_timer, 10. );
1620 }
1621 
1622 
1623 /**
1624  * @brief Actually attempts to jump in hyperspace.
1625  *
1626  *    @return 1 if actually started a jump, 0 otherwise.
1627  */
player_jump(void)1628 int player_jump (void)
1629 {
1630    int i, j;
1631    double dist, mindist;
1632 
1633    /* Must have a jump target and not be already jumping. */
1634    if (pilot_isFlag(player.p, PILOT_HYPERSPACE))
1635       return 0;
1636 
1637    /* Not under manual control or disabled. */
1638    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
1639          pilot_isDisabled(player.p))
1640       return 0;
1641 
1642    /* Select nearest jump if not target. */
1643    if (player.p->nav_hyperspace == -1) {
1644       j        = -1;
1645       mindist  = INFINITY;
1646       for (i=0; i<cur_system->njumps; i++) {
1647          dist = vect_dist2( &player.p->solid->pos, &cur_system->jumps[i].pos );
1648          if (dist < mindist && jp_isUsable(&cur_system->jumps[i])) {
1649             mindist  = dist;
1650             j        = i;
1651          }
1652       }
1653       if (j  < 0)
1654          return 0;
1655 
1656       player.p->nav_hyperspace = j;
1657       player_soundPlayGUI(snd_nav,1);
1658       map_select( cur_system->jumps[player.p->nav_hyperspace].target, 0 );
1659       gui_setNav();
1660 
1661       /* Only follow through if within range. */
1662       if (mindist > pow2( cur_system->jumps[j].radius ))
1663          return 0;
1664    }
1665 
1666    /* Already jumping, so we break jump. */
1667    if (pilot_isFlag(player.p, PILOT_HYP_PREP)) {
1668       pilot_hyperspaceAbort(player.p);
1669       player_message("\erAborting hyperspace sequence.");
1670       return 0;
1671    }
1672 
1673    /* Try to hyperspace. */
1674    i = space_hyperspace(player.p);
1675    if (i == -1)
1676       player_message("\erYou are too far from a jump point to initiate hyperspace.");
1677    else if (i == -2)
1678       player_message("\erHyperspace drive is offline.");
1679    else if (i == -3)
1680       player_message("\erYou do not have enough fuel to hyperspace jump.");
1681    else {
1682       player_message("\epPreparing for hyperspace.");
1683       /* Stop acceleration noise. */
1684       player_accelOver();
1685       /* Stop possible shooting. */
1686       pilot_shootStop( player.p, 0 );
1687       pilot_shootStop( player.p, 1 );
1688 
1689       /* Order escorts to jump; just for aesthetics (for now) */
1690       escorts_jump( player.p );
1691 
1692       return 1;
1693    }
1694    return 0;
1695 }
1696 
1697 /**
1698  * @brief Player actually broke hyperspace (entering new system).
1699  */
player_brokeHyperspace(void)1700 void player_brokeHyperspace (void)
1701 {
1702    ntime_t t;
1703    StarSystem *sys;
1704    JumpPoint *jp;
1705 
1706    /* First run jump hook. */
1707    hooks_run( "jumpout" );
1708 
1709    /* Prevent targeted planet # from carrying over. */
1710    gui_setNav();
1711    gui_setTarget();
1712    player_targetPlanetSet( -1 );
1713 
1714    /* calculates the time it takes, call before space_init */
1715    t  = pilot_hyperspaceDelay( player.p );
1716    ntime_inc( t );
1717 
1718    /* Save old system. */
1719    sys = cur_system;
1720 
1721    /* Free old graphics. */
1722    space_gfxUnload( sys );
1723 
1724    /* enter the new system */
1725    jp = &cur_system->jumps[player.p->nav_hyperspace];
1726    space_init( jp->target->name );
1727 
1728    /* set position, the pilot_update will handle lowering vel */
1729    space_calcJumpInPos( cur_system, sys, &player.p->solid->pos, &player.p->solid->vel, &player.p->solid->dir );
1730    cam_setTargetPilot( player.p->id, 0 );
1731 
1732    /* reduce fuel */
1733    player.p->fuel -= player.p->fuel_consumption;
1734 
1735    /* stop hyperspace */
1736    pilot_rmFlag( player.p, PILOT_HYPERSPACE );
1737    pilot_rmFlag( player.p, PILOT_HYP_BEGIN );
1738    pilot_rmFlag( player.p, PILOT_HYP_BRAKE );
1739    pilot_rmFlag( player.p, PILOT_HYP_PREP );
1740 
1741    /* Update the map */
1742    map_jump();
1743 
1744    /* Add the escorts. */
1745    player_addEscorts();
1746 
1747    /* Disable autonavigation if arrived. */
1748    if (player_isFlag(PLAYER_AUTONAV)) {
1749       if (player.p->nav_hyperspace == -1) {
1750          player_message( "\epAutonav arrived at the %s system.", cur_system->name);
1751          player_autonavEnd();
1752       }
1753       else {
1754          player_message( "\epAutonav continuing until destination (%d jump%s left).",
1755                map_npath, (map_npath==1) ? "" : "s" );
1756       }
1757    }
1758 
1759    /* Safe since this is run in the player hook section. */
1760    hooks_run( "jumpin" );
1761    hooks_run( "enter" );
1762    events_trigger( EVENT_TRIGGER_ENTER );
1763    missions_run( MIS_AVAIL_SPACE, -1, NULL, NULL );
1764 
1765    /* Player sound. */
1766    player_soundPlay( snd_hypJump, 1 );
1767 }
1768 
1769 
1770 /**
1771  * @brief Start accelerating.
1772  *
1773  *    @param acc How much thrust should be applied of maximum (0 - 1).
1774  */
player_accel(double acc)1775 void player_accel( double acc )
1776 {
1777    if ((player.p == NULL) || pilot_isFlag(player.p, PILOT_HYP_PREP) ||
1778          pilot_isFlag(player.p, PILOT_HYPERSPACE))
1779       return;
1780 
1781 
1782    player_acc = acc;
1783    if (toolkit_isOpen() || paused)
1784       player_soundPause();
1785 }
1786 
1787 
1788 /**
1789  * @brief Done accelerating.
1790  */
player_accelOver(void)1791 void player_accelOver (void)
1792 {
1793    player_acc = 0.;
1794 }
1795 
1796 
1797 /**
1798  * @brief Sets the player's target.
1799  *
1800  *    @param id Target to set for the player.
1801  */
player_targetSet(unsigned int id)1802 void player_targetSet( unsigned int id )
1803 {
1804    unsigned int old;
1805    old = player.p->target;
1806    pilot_setTarget( player.p, id );
1807    if ((old != id) && (player.p->target != PLAYER_ID)) {
1808       gui_forceBlink();
1809       player_soundPlayGUI( snd_target, 1 );
1810    }
1811    gui_setTarget();
1812 }
1813 
1814 
1815 /**
1816  * @brief Targets the nearest hostile enemy to the player.
1817  *
1818  * @note This function largely duplicates pilot_getNearestEnemy, because the
1819  *       player's hostility with AIs is more nuanced than AI vs AI.
1820  */
player_targetHostile(void)1821 void player_targetHostile (void)
1822 {
1823    unsigned int tp;
1824    int i;
1825    double d, td;
1826 
1827    tp = PLAYER_ID;
1828    d  = 0;
1829    for (i=0; i<pilot_nstack; i++) {
1830       /* Don't get if is bribed. */
1831       if (pilot_isFlag(pilot_stack[i],PILOT_BRIBED))
1832          continue;
1833 
1834       /* Shouldn't be disabled. */
1835       if (pilot_isDisabled(pilot_stack[i]))
1836          continue;
1837 
1838       /* Must be a valid target. */
1839       if (!pilot_validTarget( player.p, pilot_stack[i] ))
1840          continue;
1841 
1842       /* Normal unbribed check. */
1843       if (pilot_isHostile(pilot_stack[i])) {
1844          td = vect_dist2(&pilot_stack[i]->solid->pos, &player.p->solid->pos);
1845          if ((tp==PLAYER_ID) || (td < d)) {
1846             d  = td;
1847             tp = pilot_stack[i]->id;
1848          }
1849       }
1850    }
1851 
1852    player_targetSet( tp );
1853 }
1854 
1855 
1856 /**
1857  * @brief Cycles to next target.
1858  *
1859  *    @param mode Mode to target. 0 is normal, 1 is hostiles.
1860  */
player_targetNext(int mode)1861 void player_targetNext( int mode )
1862 {
1863    player_targetSet( pilot_getNextID(player.p->target, mode) );
1864 }
1865 
1866 
1867 /**
1868  * @brief Cycles to previous target.
1869  *
1870  *    @param mode Mode to target. 0 is normal, 1 is hostiles.
1871  */
player_targetPrev(int mode)1872 void player_targetPrev( int mode )
1873 {
1874    player_targetSet( pilot_getPrevID(player.p->target, mode) );
1875 }
1876 
1877 
1878 /**
1879  * @brief Clears the player's ship, planet or hyperspace target, in that order.
1880  */
player_targetClear(void)1881 void player_targetClear (void)
1882 {
1883    gui_forceBlink();
1884    if (player.p->target == PLAYER_ID && (preemption == 1 || player.p->nav_planet == -1)
1885          && !pilot_isFlag(player.p, PILOT_HYP_PREP)) {
1886       player.p->nav_hyperspace = -1;
1887       player_hyperspacePreempt(0);
1888       map_clear();
1889    }
1890    else if (player.p->target == PLAYER_ID)
1891       player_targetPlanetSet( -1 );
1892    else
1893       player_targetSet( PLAYER_ID );
1894    gui_setNav();
1895 }
1896 
1897 
1898 /**
1899  * @brief Targets the pilot.
1900  *
1901  *    @param prev 1 if is cycling backwards.
1902  */
player_targetEscort(int prev)1903 void player_targetEscort( int prev )
1904 {
1905    int i;
1906 
1907    /* Check if current target is an escort. */
1908    for (i=0; i<player.p->nescorts; i++) {
1909       if (player.p->target == player.p->escorts[i].id) {
1910 
1911          /* Cycle targets. */
1912          if (prev)
1913             pilot_setTarget( player.p, (i > 0) ?
1914                   player.p->escorts[i-1].id : player.p->id );
1915          else
1916             pilot_setTarget( player.p, (i < player.p->nescorts-1) ?
1917                   player.p->escorts[i+1].id : player.p->id );
1918 
1919          break;
1920       }
1921    }
1922 
1923    /* Not found in loop. */
1924    if (i >= player.p->nescorts) {
1925 
1926       /* Check to see if he actually has escorts. */
1927       if (player.p->nescorts > 0) {
1928 
1929          /* Cycle forward or backwards. */
1930          if (prev)
1931             pilot_setTarget( player.p, player.p->escorts[player.p->nescorts-1].id );
1932          else
1933             pilot_setTarget( player.p, player.p->escorts[0].id );
1934       }
1935       else
1936          pilot_setTarget( player.p, player.p->id );
1937    }
1938 
1939 
1940    if (player.p->target != PLAYER_ID) {
1941       gui_forceBlink();
1942       player_soundPlayGUI( snd_target, 1 );
1943    }
1944    gui_setTarget();
1945 }
1946 
1947 
1948 
1949 /**
1950  * @brief Player targets nearest pilot.
1951  */
player_targetNearest(void)1952 void player_targetNearest (void)
1953 {
1954    unsigned int t, dt;
1955    double d;
1956 
1957    d = pilot_getNearestPos( player.p, &dt, player.p->solid->pos.x,
1958          player.p->solid->pos.y, 1 );
1959    t = dt;
1960 
1961    /* Disabled ships are typically only valid if within 500 px of the player. */
1962    if ((d > 250000) && (pilot_isDisabled( pilot_get(dt) ))) {
1963       t = pilot_getNearestPilot(player.p);
1964       /* Try to target a disabled ship if there are no active ships in range. */
1965       if (t == PLAYER_ID)
1966          t = dt;
1967    }
1968 
1969    player_targetSet( t );
1970 }
1971 
1972 
1973 static int screenshot_cur = 0; /**< Current screenshot at. */
1974 /**
1975  * @brief Takes a screenshot.
1976  */
player_screenshot(void)1977 void player_screenshot (void)
1978 {
1979    char filename[PATH_MAX];
1980 
1981    if (nfile_dirMakeExist("%s", nfile_dataPath()) < 0 || nfile_dirMakeExist("%sscreenshots", nfile_dataPath()) < 0) {
1982       WARN("Aborting screenshot");
1983       return;
1984    }
1985 
1986    /* Try to find current screenshots. */
1987    for ( ; screenshot_cur < 1000; screenshot_cur++) {
1988       nsnprintf( filename, PATH_MAX, "%sscreenshots/screenshot%03d.png",
1989             nfile_dataPath(), screenshot_cur );
1990       if (!nfile_fileExists( filename ))
1991          break;
1992    }
1993 
1994    if (screenshot_cur >= 999) { /* in case the crap system breaks :) */
1995       WARN("You have reached the maximum amount of screenshots [999]");
1996       return;
1997    }
1998 
1999    /* now proceed to take the screenshot */
2000    DEBUG( "Taking screenshot [%03d]...", screenshot_cur );
2001    gl_screenshot(filename);
2002 }
2003 
2004 
2005 /**
2006  * @brief Checks to see if player is still being hailed and clears hail counters
2007  *        if he isn't.
2008  */
player_checkHail(void)2009 static void player_checkHail (void)
2010 {
2011    int i;
2012    Pilot *p;
2013 
2014    /* See if a pilot is hailing. */
2015    for (i=0; i<pilot_nstack; i++) {
2016       p = pilot_stack[i];
2017 
2018       /* Must be hailing. */
2019       if (pilot_isFlag(p, PILOT_HAILING))
2020          return;
2021    }
2022 
2023    /* Clear hail timer. */
2024    player_hailCounter   = 0;
2025    player_hailTimer     = 0.;
2026 }
2027 
2028 
2029 /**
2030  * @brief Displays an out of range message for the player's currently selected planet.
2031  */
player_planetOutOfRangeMsg(void)2032 static void player_planetOutOfRangeMsg (void)
2033 {
2034    player_message( "\er%s is out of comm range, unable to contact.",
2035          cur_system->planets[player.p->nav_planet]->name );
2036 }
2037 
2038 
2039 /**
2040  * @brief Opens communication with the player's target.
2041  */
player_hail(void)2042 void player_hail (void)
2043 {
2044    /* Not under manual control or disabled. */
2045    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
2046          pilot_isDisabled(player.p))
2047       return;
2048 
2049    if (player.p->target != player.p->id)
2050       comm_openPilot(player.p->target);
2051    else if(player.p->nav_planet != -1) {
2052       if (pilot_inRangePlanet( player.p, player.p->nav_planet ))
2053          comm_openPlanet( cur_system->planets[ player.p->nav_planet ] );
2054       else
2055          player_planetOutOfRangeMsg();
2056    }
2057    else
2058       player_message("\erNo target selected to hail.");
2059 
2060    /* Clear hails if none found. */
2061    player_checkHail();
2062 }
2063 
2064 
2065 /**
2066  * @brief Opens communication with the player's planet target.
2067  */
player_hailPlanet(void)2068 void player_hailPlanet (void)
2069 {
2070    /* Not under manual control. */
2071    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ))
2072       return;
2073 
2074    if (player.p->nav_planet != -1) {
2075       if (pilot_inRangePlanet( player.p, player.p->nav_planet ))
2076          comm_openPlanet( cur_system->planets[ player.p->nav_planet ] );
2077       else
2078          player_planetOutOfRangeMsg();
2079    }
2080    else
2081       player_message("\erNo target selected to hail.");
2082 }
2083 
2084 
2085 /**
2086  * @brief Automatically tries to hail a pilot that hailed the player.
2087  */
player_autohail(void)2088 void player_autohail (void)
2089 {
2090    int i;
2091    Pilot *p;
2092 
2093    /* Not under manual control or disabled. */
2094    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
2095          pilot_isDisabled(player.p))
2096       return;
2097 
2098    /* Find pilot to autohail. */
2099    for (i=0; i<pilot_nstack; i++) {
2100       p = pilot_stack[i];
2101 
2102       /* Must be hailing. */
2103       if (pilot_isFlag(p, PILOT_HAILING))
2104          break;
2105    }
2106 
2107    /* Not found any. */
2108    if (i >= pilot_nstack) {
2109       player_message("\erYou haven't been hailed by any pilots.");
2110       return;
2111    }
2112 
2113    /* Try to hail. */
2114    pilot_setTarget( player.p, p->id );
2115    gui_setTarget();
2116    player_hail();
2117 
2118    /* Clear hails if none found. */
2119    player_checkHail();
2120 }
2121 
2122 
2123 /**
2124  * @brief Toggles mouse flying.
2125  */
player_toggleMouseFly(void)2126 void player_toggleMouseFly(void)
2127 {
2128    if (!player_isFlag(PLAYER_MFLY)) {
2129       input_mouseShow();
2130       player_message("\epMouse flying enabled.");
2131       player_setFlag(PLAYER_MFLY);
2132    }
2133    else {
2134       input_mouseHide();
2135       player_rmFlag(PLAYER_MFLY);
2136       player_message("\erMouse flying disabled.");
2137 
2138       if (conf.mouse_thrust)
2139          player_accelOver();
2140    }
2141 }
2142 
2143 
2144 /**
2145  * @brief Starts braking or active cooldown.
2146  */
player_brake(void)2147 void player_brake(void)
2148 {
2149    int stopped;
2150 
2151    if (pilot_isFlag(player.p, PILOT_TAKEOFF))
2152       return;
2153 
2154    /* Not under manual control or disabled. */
2155    if (pilot_isFlag( player.p, PILOT_MANUAL_CONTROL ) ||
2156          pilot_isDisabled(player.p))
2157       return;
2158 
2159    stopped = pilot_isStopped(player.p);
2160    if (stopped && !pilot_isFlag(player.p, PILOT_COOLDOWN))
2161       pilot_cooldown(player.p);
2162    else if (pilot_isFlag(player.p, PILOT_BRAKING))
2163       pilot_setFlag(player.p, PILOT_COOLDOWN_BRAKE);
2164    else if (!stopped)
2165       pilot_setFlag(player.p, PILOT_BRAKING);
2166 }
2167 
2168 
2169 /**
2170  * @brief Handles mouse flying based on cursor position.
2171  *
2172  *    @return 1 if cursor is outside the dead zone, 0 if it isn't.
2173  */
player_thinkMouseFly(void)2174 static int player_thinkMouseFly(void)
2175 {
2176    double px, py, r, x, y, acc;
2177 
2178    px = player.p->solid->pos.x;
2179    py = player.p->solid->pos.y;
2180    gl_screenToGameCoords( &x, &y, player.mousex, player.mousey );
2181    r = sqrt(pow2(x-px) + pow2(y-py));
2182    if (r > 50.) { /* Ignore mouse input within a 50 px radius of the centre. */
2183       pilot_face(player.p, atan2( y - py, x - px));
2184       if (conf.mouse_thrust) { /* Only alter thrust if option is enabled. */
2185          acc = CLAMP(0., 1., (r - 100) / 200.);
2186          acc = 3 * pow2(acc) - 2 * pow(acc, 3);
2187          /* Only accelerate when within 180 degrees of the intended direction. */
2188          if (ABS(angle_diff(atan2( y - py, x - px), player.p->solid->dir)) < M_PI_2 )
2189             player_accel(acc);
2190          else
2191             player_accel(0.);
2192       }
2193       return 1;
2194    }
2195    else
2196       return 0;
2197 }
2198 
2199 
2200 /**
2201  * @brief Player got pwned.
2202  */
player_dead(void)2203 void player_dead (void)
2204 {
2205    /* Explode at normal speed. */
2206    pause_setSpeed(1.);
2207 
2208    gui_cleanup();
2209 
2210    /* Close the overlay. */
2211    ovr_setOpen(0);
2212 }
2213 
2214 
2215 /**
2216  * @brief Player blew up in a fireball.
2217  */
player_destroyed(void)2218 void player_destroyed (void)
2219 {
2220    if (player_isFlag(PLAYER_DESTROYED))
2221       return;
2222 
2223    /* Mark as destroyed. */
2224    player_setFlag(PLAYER_DESTROYED);
2225 
2226    /* Set timer for death menu. */
2227    player_timer = 5.;
2228 
2229    /* Stop sounds. */
2230    player_soundStop();
2231 
2232    /* Stop autonav */
2233    player_autonavEnd();
2234 
2235    /* Reset time compression when player dies. */
2236    pause_setSpeed( 1. );
2237 }
2238 
2239 
2240 /**
2241  * @brief PlayerShip_t compare function for qsort().
2242  */
player_shipsCompare(const void * arg1,const void * arg2)2243 static int player_shipsCompare( const void *arg1, const void *arg2 )
2244 {
2245    PlayerShip_t *ps1, *ps2;
2246    credits_t p1, p2;
2247 
2248    /* Get the arguments. */
2249    ps1 = (PlayerShip_t*) arg1;
2250    ps2 = (PlayerShip_t*) arg2;
2251 
2252    /* Get prices. */
2253    p1 = pilot_worth( ps1->p );
2254    p2 = pilot_worth( ps2->p );
2255 
2256    /* Compare price INVERSELY */
2257    if (p1 < p2)
2258       return +1;
2259    else if (p1 > p2)
2260       return -1;
2261 
2262    /* In case of tie sort by name so they don't flip or something. */
2263    return strcmp( ps1->p->name, ps2->p->name );
2264 }
2265 
2266 
2267 /**
2268  * @brief Returns a buffer with all the player's ships names.
2269  *
2270  *    @param sships Fills sships with player_nships ship names.
2271  *    @param tships Fills sships with player_nships ship target textures.
2272  *    @return Freshly allocated array with allocated ship names.
2273  *    @return The number of ships the player has.
2274  */
player_ships(char ** sships,glTexture ** tships)2275 int player_ships( char** sships, glTexture** tships )
2276 {
2277    int i;
2278 
2279    if (player_nstack == 0)
2280       return 0;
2281 
2282    /* Sort. */
2283    qsort( player_stack, player_nstack, sizeof(PlayerShip_t), player_shipsCompare );
2284 
2285    /* Create the struct. */
2286    for (i=0; i < player_nstack; i++) {
2287       sships[i] = strdup(player_stack[i].p->name);
2288       tships[i] = player_stack[i].p->ship->gfx_store;
2289    }
2290 
2291    return player_nstack;
2292 }
2293 
2294 
2295 /**
2296  * @brief Gets all of the player's ships.
2297  *
2298  *    @param[out] Number of star systems gotten.
2299  *    @return The player's ships.
2300  */
player_getShipStack(int * n)2301 const PlayerShip_t* player_getShipStack( int *n )
2302 {
2303    *n = player_nstack;
2304    return player_stack;
2305 }
2306 
2307 
2308 /**
2309  * @brief Gets the amount of ships player has in storage.
2310  *
2311  *    @return The number of ships the player has.
2312  */
player_nships(void)2313 int player_nships (void)
2314 {
2315    return player_nstack;
2316 }
2317 
2318 
2319 /**
2320  * @brief Sees if player has a ship of a name.
2321  *
2322  *    @param shipname Nome of the ship to get.
2323  *    @return 1 if ship exists.
2324  */
player_hasShip(char * shipname)2325 int player_hasShip( char* shipname )
2326 {
2327    int i;
2328 
2329    /* Check current ship. */
2330    if ((player.p != NULL) && (strcmp(player.p->name,shipname)==0))
2331       return 1;
2332 
2333    /* Check stocked ships. */
2334    for (i=0; i < player_nstack; i++)
2335       if (strcmp(player_stack[i].p->name, shipname)==0)
2336          return 1;
2337    return 0;
2338 }
2339 
2340 
2341 /**
2342  * @brief Gets a specific ship.
2343  *
2344  *    @param shipname Nome of the ship to get.
2345  *    @return The ship matching name.
2346  */
player_getShip(char * shipname)2347 Pilot* player_getShip( char* shipname )
2348 {
2349    int i;
2350 
2351    if ((player.p != NULL) && (strcmp(shipname,player.p->name)==0))
2352       return player.p;
2353 
2354    for (i=0; i < player_nstack; i++)
2355       if (strcmp(player_stack[i].p->name, shipname)==0)
2356          return player_stack[i].p;
2357 
2358    WARN("Player ship '%s' not found in stack", shipname);
2359    return NULL;
2360 }
2361 
2362 
2363 /**
2364  * @brief Gets where a specific ship is.
2365  *
2366  *    @param shipname Ship to check where it is.
2367  *    @return The location of the ship.
2368  */
player_getLoc(char * shipname)2369 char* player_getLoc( char* shipname )
2370 {
2371    int i;
2372 
2373    if (strcmp(player.p->name,shipname)==0)
2374       return land_planet->name;
2375 
2376    for (i=0; i < player_nstack; i++)
2377       if (strcmp(player_stack[i].p->name, shipname)==0)
2378          return player_stack[i].loc;
2379 
2380    WARN("Player ship '%s' not found in stack", shipname);
2381    return NULL;
2382 }
2383 
2384 
2385 /**
2386  * @brief Sets the location of a specific ship.
2387  *
2388  *    @param shipname Name of the ship to change location of.
2389  *    @param loc Location of the ship to change to.
2390  */
player_setLoc(char * shipname,char * loc)2391 void player_setLoc( char* shipname, char* loc )
2392 {
2393    int i;
2394 
2395    for (i=0; i < player_nstack; i++) {
2396       if (strcmp(player_stack[i].p->name, shipname)==0) {
2397          free(player_stack[i].loc);
2398          player_stack[i].loc = strdup(loc);
2399          return;
2400       }
2401    }
2402 
2403    WARN("Player ship '%s' not found in stack", shipname);
2404 }
2405 
2406 
2407 /**
2408  * @brief Gets how many of the outfit the player owns.
2409  *
2410  *    @param outfitname Outfit to check how many the player owns.
2411  *    @return The number of outfits matching outfitname owned.
2412  */
player_outfitOwned(const Outfit * o)2413 int player_outfitOwned( const Outfit* o )
2414 {
2415    int i;
2416 
2417    /* Special case map. */
2418    if ((outfit_isMap(o) && map_isMapped(o)) ||
2419          (outfit_isLocalMap(o) && localmap_isMapped(o)))
2420       return 1;
2421 
2422    /* Special case license. */
2423    if (outfit_isLicense(o) &&
2424          player_hasLicense(o->name))
2425       return 1;
2426 
2427    /* Special case GUI. */
2428    if (outfit_isGUI(o) &&
2429          player_guiCheck(o->u.gui.gui))
2430       return 1;
2431 
2432    /* Try to find it. */
2433    for (i=0; i<player_noutfits; i++)
2434       if (player_outfits[i].o == o)
2435          return player_outfits[i].q;
2436 
2437    return 0;
2438 }
2439 
2440 
2441 /**
2442  * @brief qsort() compare function for PlayerOutfit_t sorting.
2443  */
player_outfitCompare(const void * arg1,const void * arg2)2444 static int player_outfitCompare( const void *arg1, const void *arg2 )
2445 {
2446    PlayerOutfit_t *po1, *po2;
2447 
2448    /* Get type. */
2449    po1 = (PlayerOutfit_t*) arg1;
2450    po2 = (PlayerOutfit_t*) arg2;
2451 
2452    /* Compare. */
2453    return outfit_compareTech( &po1->o, &po2->o );
2454 }
2455 
2456 
2457 /**
2458  * @brief Returns the player's outfits.
2459  *
2460  *    @param[out] n Number of distinct outfits (not total quantity).
2461  *    @return Outfits the player owns.
2462  */
player_getOutfits(int * n)2463 const PlayerOutfit_t* player_getOutfits( int *n )
2464 {
2465    *n = player_noutfits;
2466    return (const PlayerOutfit_t*) player_outfits;
2467 }
2468 
2469 
2470 /**
2471  * @brief Prepares two arrays for displaying in an image array.
2472  *
2473  *    @param[out] outfits Outfits the player owns.
2474  *    @param[out] toutfits Optional store textures for the image array.
2475  *    @param[in] filter Function to filter which outfits to get.
2476  *    @param[in] name Name fragment that each outfit must contain.
2477  *    @return Number of outfits.
2478  */
player_getOutfitsFiltered(Outfit ** outfits,glTexture ** toutfits,int (* filter)(const Outfit * o),char * name)2479 int player_getOutfitsFiltered( Outfit **outfits, glTexture** toutfits,
2480       int(*filter)( const Outfit *o ), char *name )
2481 {
2482    int i;
2483 
2484    if (player_noutfits == 0)
2485       return 0;
2486 
2487    /* We'll sort. */
2488    qsort( player_outfits, player_noutfits,
2489          sizeof(PlayerOutfit_t), player_outfitCompare );
2490 
2491    for (i=0; i<player_noutfits; i++)
2492       outfits[i] = (Outfit*)player_outfits[i].o;
2493 
2494    return outfits_filter( outfits, toutfits, player_noutfits, filter, name );
2495 }
2496 
2497 
2498 /**
2499  * @brief Gets the amount of different outfits in the player outfit stack.
2500  *
2501  *    @return Amount of different outfits.
2502  */
player_numOutfits(void)2503 int player_numOutfits (void)
2504 {
2505    return player_noutfits;
2506 }
2507 
2508 
2509 /**
2510  * @brief Adds an outfit to the player outfit stack.
2511  *
2512  *    @param o Outfit to add.
2513  *    @param quantity Amount to add.
2514  *    @return Amount added.
2515  */
player_addOutfit(const Outfit * o,int quantity)2516 int player_addOutfit( const Outfit *o, int quantity )
2517 {
2518    int i;
2519 
2520    /* Sanity check. */
2521    if (quantity == 0)
2522       return 0;
2523 
2524    /* special case if it's a map */
2525    if (outfit_isMap(o)) {
2526       map_map(o);
2527       return 1; /* Success. */
2528    }
2529    else if (outfit_isLocalMap(o)) {
2530       localmap_map(o);
2531       return 1;
2532    }
2533    /* special case if it's an outfit */
2534    else if (outfit_isGUI(o)) {
2535       player_guiAdd(o->u.gui.gui);
2536       return 1; /* Success. */
2537    }
2538    /* special case if it's a license. */
2539    else if (outfit_isLicense(o)) {
2540       player_addLicense(o->name);
2541       return 1; /* Success. */
2542    }
2543 
2544    /* Try to find it. */
2545    for (i=0; i<player_noutfits; i++) {
2546       if (player_outfits[i].o == o) {
2547          player_outfits[i].q  += quantity;
2548          return quantity;
2549       }
2550    }
2551 
2552    /* Allocate if needed. */
2553    player_noutfits++;
2554    if (player_noutfits > player_moutfits) {
2555       if (player_moutfits == 0)
2556          player_moutfits = OUTFIT_CHUNKSIZE;
2557       else
2558          player_moutfits *= 2;
2559       player_outfits   = realloc( player_outfits,
2560             sizeof(PlayerOutfit_t) * player_moutfits );
2561    }
2562 
2563    /* Add the outfit. */
2564    player_outfits[player_noutfits-1].o = o;
2565    player_outfits[player_noutfits-1].q = quantity;
2566    return quantity;
2567 }
2568 
2569 
2570 /**
2571  * @brief Remove an outfit from the player's outfit stack.
2572  *
2573  *    @param o Outfit to remove.
2574  *    @param quantity Amount to remove.
2575  *    @return Amount removed.
2576  */
player_rmOutfit(const Outfit * o,int quantity)2577 int player_rmOutfit( const Outfit *o, int quantity )
2578 {
2579    int i, q;
2580 
2581    /* Try to find it. */
2582    for (i=0; i<player_noutfits; i++) {
2583       if (player_outfits[i].o == o) {
2584          /* See how many to remove. */
2585          q = MIN( player_outfits[i].q, quantity );
2586          player_outfits[i].q -= q;
2587 
2588          /* See if must remove element. */
2589          if (player_outfits[i].q <= 0) {
2590             player_noutfits--;
2591             memmove( &player_outfits[i], &player_outfits[i+1],
2592                   sizeof(PlayerOutfit_t) * (player_noutfits-i) );
2593          }
2594 
2595          /* Return removed outfits. */
2596          return q;
2597       }
2598    }
2599 
2600    /* Nothing removed. */
2601    return 0;
2602 }
2603 
2604 
2605 /**
2606  * @brief Marks a mission as completed.
2607  *
2608  *    @param id ID of the mission to mark as completed.
2609  */
player_missionFinished(int id)2610 void player_missionFinished( int id )
2611 {
2612    /* Make sure not already marked. */
2613    if (player_missionAlreadyDone(id))
2614       return;
2615 
2616    /* Mark as done. */
2617    missions_ndone++;
2618    if (missions_ndone > missions_mdone) { /* need to grow */
2619       missions_mdone += 25;
2620       missions_done = realloc( missions_done, sizeof(int) * missions_mdone);
2621    }
2622    missions_done[ missions_ndone-1 ] = id;
2623 }
2624 
2625 
2626 /**
2627  * @brief Checks to see if player has already completed a mission.
2628  *
2629  *    @param id ID of the mission to see if player has completed.
2630  *    @return 1 if player has completed the mission, 0 otherwise.
2631  */
player_missionAlreadyDone(int id)2632 int player_missionAlreadyDone( int id )
2633 {
2634    int i;
2635    for (i=0; i<missions_ndone; i++)
2636       if (missions_done[i] == id)
2637          return 1;
2638    return 0;
2639 }
2640 
2641 
2642 /**
2643  * @brief Marks a event as completed.
2644  *
2645  *    @param id ID of the event to mark as completed.
2646  */
player_eventFinished(int id)2647 void player_eventFinished( int id )
2648 {
2649    /* Make sure not already done. */
2650    if (player_eventAlreadyDone(id))
2651       return;
2652 
2653    /* Add to done. */
2654    events_ndone++;
2655    if (events_ndone > events_mdone) { /* need to grow */
2656       events_mdone += 25;
2657       events_done = realloc( events_done, sizeof(int) * events_mdone);
2658    }
2659    events_done[ events_ndone-1 ] = id;
2660 }
2661 
2662 
2663 /**
2664  * @brief Checks to see if player has already completed a event.
2665  *
2666  *    @param id ID of the event to see if player has completed.
2667  *    @return 1 if player has completed the event, 0 otherwise.
2668  */
player_eventAlreadyDone(int id)2669 int player_eventAlreadyDone( int id )
2670 {
2671    int i;
2672    for (i=0; i<events_ndone; i++)
2673       if (events_done[i] == id)
2674          return 1;
2675    return 0;
2676 }
2677 
2678 
2679 /**
2680  * @brief Checks to see if player has license.
2681  *
2682  *    @param license License to check to see if the player has.
2683  *    @return 1 if has license (or none needed), 0 if doesn't.
2684  */
player_hasLicense(char * license)2685 int player_hasLicense( char *license )
2686 {
2687    int i;
2688    if (!license) /* Null input. */
2689       return 1;
2690 
2691    for (i=0; i<player_nlicenses; i++)
2692       if (strcmp(license, player_licenses[i])==0)
2693          return 1;
2694 
2695    return 0;
2696 }
2697 
2698 
2699 /**
2700  * @brief Gives the player a license.
2701  *
2702  *    @brief license License to give the player.
2703  */
player_addLicense(char * license)2704 void player_addLicense( char *license )
2705 {
2706    /* Player already has license. */
2707    if (player_hasLicense(license))
2708       return;
2709 
2710    /* Add the license. */
2711    player_nlicenses++;
2712    player_licenses = realloc( player_licenses, sizeof(char*)*player_nlicenses );
2713    player_licenses[player_nlicenses-1] = strdup(license);
2714 }
2715 
2716 
2717 /**
2718  * @brief Gets the player's licenses.
2719  *
2720  *    @param nlicenses Amount of licenses the player has.
2721  *    @return Name of the licenses he has.
2722  */
player_getLicenses(int * nlicenses)2723 char **player_getLicenses( int *nlicenses )
2724 {
2725    *nlicenses = player_nlicenses;
2726    return player_licenses;
2727 }
2728 
2729 
2730 /**
2731  * @brief Runs hooks for the player.
2732  */
player_runHooks(void)2733 void player_runHooks (void)
2734 {
2735    if (player_isFlag( PLAYER_HOOK_HYPER )) {
2736       player_brokeHyperspace();
2737       player_rmFlag( PLAYER_HOOK_HYPER );
2738    }
2739    if (player_isFlag( PLAYER_HOOK_JUMPIN)) {
2740       hooks_run( "jumpin" );
2741       hooks_run( "enter" );
2742       events_trigger( EVENT_TRIGGER_ENTER );
2743       missions_run( MIS_AVAIL_SPACE, -1, NULL, NULL );
2744       player_rmFlag( PLAYER_HOOK_JUMPIN );
2745    }
2746    if (player_isFlag( PLAYER_HOOK_LAND )) {
2747       land( cur_system->planets[ player.p->nav_planet ], 0 );
2748       player_rmFlag( PLAYER_HOOK_LAND );
2749    }
2750 }
2751 
2752 
2753 /**
2754  * @brief Clears escorts to make sure deployment is sane.
2755  */
player_clearEscorts(void)2756 void player_clearEscorts (void)
2757 {
2758    int i;
2759 
2760    for (i=0; i<player.p->noutfits; i++) {
2761       if (player.p->outfits[i]->outfit == NULL)
2762          continue;
2763 
2764       if (outfit_isFighterBay(player.p->outfits[i]->outfit))
2765          player.p->outfits[i]->u.ammo.deployed = 0;
2766    }
2767 }
2768 
2769 
2770 /**
2771  * @brief Adds the player's escorts.
2772  *
2773  *    @return 0 on success.
2774  */
player_addEscorts(void)2775 int player_addEscorts (void)
2776 {
2777    int i, j;
2778    double a;
2779    Vector2d v;
2780    unsigned int e;
2781    Outfit *o;
2782    int q;
2783 
2784    /* Clear escorts first. */
2785    player_clearEscorts();
2786 
2787    for (i=0; i<player.p->nescorts; i++) {
2788       if (!player.p->escorts[i].persist) {
2789          escort_rmList(player.p, player.p->escorts[i].id);
2790          i--;
2791          continue;
2792       }
2793 
2794       a = RNGF() * 2 * M_PI;
2795       vect_cset( &v, player.p->solid->pos.x + 50.*cos(a),
2796             player.p->solid->pos.y + 50.*sin(a) );
2797       e = escort_create( player.p, player.p->escorts[i].ship,
2798             &v, &player.p->solid->vel, player.p->solid->dir,
2799             player.p->escorts[i].type, 0 );
2800       player.p->escorts[i].id = e; /* Important to update ID. */
2801 
2802       /* Update outfit if needed. */
2803       if (player.p->escorts[i].type != ESCORT_TYPE_BAY)
2804          continue;
2805 
2806       for (j=0; j<player.p->noutfits; j++) {
2807          /* Must have outfit. */
2808          if (player.p->outfits[j]->outfit == NULL)
2809             continue;
2810 
2811          /* Must be fighter bay. */
2812          if (!outfit_isFighterBay(player.p->outfits[j]->outfit))
2813             continue;
2814 
2815          /* Ship must match. */
2816          o = outfit_ammo(player.p->outfits[j]->outfit);
2817          if (!outfit_isFighter(o) ||
2818                (strcmp(player.p->escorts[i].ship,o->u.fig.ship)!=0))
2819             continue;
2820 
2821          /* Must not have all deployed. */
2822          q = player.p->outfits[j]->u.ammo.deployed + player.p->outfits[j]->u.ammo.quantity;
2823          if (q >= outfit_amount(player.p->outfits[j]->outfit))
2824             continue;
2825 
2826          /* Mark as deployed. */
2827          player.p->outfits[j]->u.ammo.deployed += 1;
2828          break;
2829       }
2830       if (j >= player.p->noutfits)
2831          WARN("Unable to mark escort as deployed");
2832    }
2833 
2834    return 0;
2835 }
2836 
2837 
2838 /**
2839  * @brief Saves the player's escorts.
2840  */
player_saveEscorts(xmlTextWriterPtr writer)2841 static int player_saveEscorts( xmlTextWriterPtr writer )
2842 {
2843    int i;
2844 
2845    for (i=0; i<player.p->nescorts; i++) {
2846       if (player.p->escorts[i].persist) {
2847          xmlw_startElem(writer, "escort");
2848          xmlw_attr(writer,"type","bay"); /**< @todo other types. */
2849          xmlw_str(writer, "%s", player.p->escorts[i].ship);
2850          xmlw_endElem(writer); /* "escort" */
2851       }
2852    }
2853 
2854    return 0;
2855 }
2856 
2857 
2858 /**
2859  * @brief Save the freaking player in a freaking xmlfile.
2860  *
2861  *    @param writer xml Writer to use.
2862  *    @return 0 on success.
2863  */
player_save(xmlTextWriterPtr writer)2864 int player_save( xmlTextWriterPtr writer )
2865 {
2866    char **guis;
2867    int i, n;
2868    MissionData *m;
2869    const char *ev;
2870    int scu, stp, stu;
2871    double rem;
2872 
2873    xmlw_startElem(writer,"player");
2874 
2875    /* Standard player details. */
2876    xmlw_attr(writer,"name","%s",player.name);
2877    xmlw_elem(writer,"rating","%f",player.crating);
2878    xmlw_elem(writer,"credits","%"CREDITS_PRI,player.p->credits);
2879    if (player.gui != NULL)
2880       xmlw_elem(writer,"gui","%s",player.gui);
2881    xmlw_elem(writer,"guiOverride","%d",player.guiOverride);
2882    xmlw_elem(writer,"mapOverlay","%d",ovr_isOpen());
2883 
2884    /* Time. */
2885    xmlw_startElem(writer,"time");
2886    ntime_getR( &scu, &stp, &stu, &rem );
2887    xmlw_elem(writer,"SCU","%d", scu);
2888    xmlw_elem(writer,"STP","%d", stp);
2889    xmlw_elem(writer,"STU","%d", stu);
2890    xmlw_elem(writer,"Remainder","%lf", rem);
2891    xmlw_endElem(writer); /* "time" */
2892 
2893    /* Current ship. */
2894    xmlw_elem(writer,"location","%s",land_planet->name);
2895    player_saveShip( writer, player.p, NULL ); /* current ship */
2896 
2897    /* Ships. */
2898    xmlw_startElem(writer,"ships");
2899    for (i=0; i<player_nstack; i++)
2900       player_saveShip( writer, player_stack[i].p, player_stack[i].loc );
2901    xmlw_endElem(writer); /* "ships" */
2902 
2903    /* GUIs. */
2904    xmlw_startElem(writer,"guis");
2905    guis = player_guiList( &n );
2906    for (i=0; i<n; i++)
2907       xmlw_elem(writer,"gui","%s",guis[i]);
2908    xmlw_endElem(writer); /* "guis" */
2909 
2910    /* Outfits. */
2911    xmlw_startElem(writer,"outfits");
2912    for (i=0; i<player_noutfits; i++) {
2913       xmlw_startElem(writer,"outfit");
2914       xmlw_attr(writer,"quantity","%d",player_outfits[i].q);
2915       xmlw_str(writer,"%s",player_outfits[i].o->name);
2916       xmlw_endElem(writer); /* "outfit" */
2917    }
2918    xmlw_endElem(writer); /* "outfits" */
2919 
2920    /* Licenses. */
2921    xmlw_startElem(writer,"licenses");
2922    for (i=0; i<player_nlicenses; i++)
2923       xmlw_elem(writer,"license","%s",player_licenses[i]);
2924    xmlw_endElem(writer); /* "licenses" */
2925 
2926    xmlw_endElem(writer); /* "player" */
2927 
2928    /* Mission the player has done. */
2929    xmlw_startElem(writer,"missions_done");
2930    for (i=0; i<missions_ndone; i++) {
2931       m = mission_get(missions_done[i]);
2932       if (m != NULL) /* In case mission name changes between versions */
2933          xmlw_elem(writer,"done","%s",m->name);
2934    }
2935    xmlw_endElem(writer); /* "missions_done" */
2936 
2937    /* Events the player has done. */
2938    xmlw_startElem(writer,"events_done");
2939    for (i=0; i<events_ndone; i++) {
2940       ev = event_dataName(events_done[i]);
2941       if (ev != NULL) /* In case mission name changes between versions */
2942          xmlw_elem(writer,"done","%s",ev);
2943    }
2944    xmlw_endElem(writer); /* "events_done" */
2945 
2946    /* Escorts. */
2947    xmlw_startElem(writer, "escorts");
2948    player_saveEscorts(writer);
2949    xmlw_endElem(writer); /* "escorts" */
2950 
2951    return 0;
2952 }
2953 
2954 /**
2955  * @brief Saves an outfit slot.
2956  */
player_saveShipSlot(xmlTextWriterPtr writer,PilotOutfitSlot * slot,int i)2957 static int player_saveShipSlot( xmlTextWriterPtr writer, PilotOutfitSlot *slot, int i )
2958 {
2959    Outfit *o;
2960    o = slot->outfit;
2961    xmlw_startElem(writer,"outfit");
2962    xmlw_attr(writer,"slot","%d",i);
2963    if ((outfit_ammo(o) != NULL) &&
2964          (slot->u.ammo.outfit != NULL)) {
2965       xmlw_attr(writer,"ammo","%s",slot->u.ammo.outfit->name);
2966       xmlw_attr(writer,"quantity","%d", slot->u.ammo.quantity);
2967    }
2968    xmlw_str(writer,"%s",o->name);
2969    xmlw_endElem(writer); /* "outfit" */
2970 
2971    return 0;
2972 }
2973 
2974 
2975 /**
2976  * @brief Saves a ship.
2977  *
2978  *    @param writer XML writer.
2979  *    @param ship Ship to save.
2980  *    @param loc Location of the ship.
2981  *    @return 0 on success.
2982  */
player_saveShip(xmlTextWriterPtr writer,Pilot * ship,char * loc)2983 static int player_saveShip( xmlTextWriterPtr writer,
2984       Pilot* ship, char* loc )
2985 {
2986    int i, j, k, n;
2987    int found;
2988    const char *name;
2989    PilotWeaponSetOutfit *weaps;
2990 
2991    xmlw_startElem(writer,"ship");
2992    xmlw_attr(writer,"name","%s",ship->name);
2993    xmlw_attr(writer,"model","%s",ship->ship->name);
2994 
2995    if (loc != NULL)
2996       xmlw_elem(writer,"location","%s",loc);
2997 
2998    /* save the fuel */
2999    xmlw_elem(writer,"fuel","%f",ship->fuel);
3000 
3001    /* save the outfits */
3002    xmlw_startElem(writer,"outfits_structure");
3003    for (i=0; i<ship->outfit_nstructure; i++) {
3004       if (ship->outfit_structure[i].outfit==NULL)
3005          continue;
3006       player_saveShipSlot( writer, &ship->outfit_structure[i], i );
3007    }
3008    xmlw_endElem(writer); /* "outfits_structure" */
3009    xmlw_startElem(writer,"outfits_utility");
3010    for (i=0; i<ship->outfit_nutility; i++) {
3011       if (ship->outfit_utility[i].outfit==NULL)
3012          continue;
3013       player_saveShipSlot( writer, &ship->outfit_utility[i], i );
3014    }
3015    xmlw_endElem(writer); /* "outfits_utility" */
3016    xmlw_startElem(writer,"outfits_weapon");
3017    for (i=0; i<ship->outfit_nweapon; i++) {
3018       if (ship->outfit_weapon[i].outfit==NULL)
3019          continue;
3020       player_saveShipSlot( writer, &ship->outfit_weapon[i], i );
3021    }
3022    xmlw_endElem(writer); /* "outfits_weapon" */
3023 
3024    /* save the commodities */
3025    xmlw_startElem(writer,"commodities");
3026    for (i=0; i<ship->ncommodities; i++) {
3027       /* Remove cargo with id and no mission. */
3028       if (ship->commodities[i].id > 0) {
3029          found = 0;
3030          for (j=0; j<MISSION_MAX; j++) {
3031             /* Only check active missions. */
3032             if (player_missions[j]->id > 0) {
3033                /* Now check if it's in the cargo list. */
3034                for (k=0; k<player_missions[j]->ncargo; k++) {
3035                   /* See if it matches a cargo. */
3036                   if (player_missions[j]->cargo[k] == ship->commodities[i].id) {
3037                      found = 1;
3038                      break;
3039                   }
3040                }
3041             }
3042             if (found)
3043                break;
3044          }
3045 
3046          if (!found) {
3047             WARN("Found mission cargo without associated mission.");
3048             WARN("Please reload save game to remove the dead cargo.");
3049             continue;
3050          }
3051       }
3052 
3053       xmlw_startElem(writer,"commodity");
3054 
3055       xmlw_attr(writer,"quantity","%d",ship->commodities[i].quantity);
3056       if (ship->commodities[i].id > 0)
3057          xmlw_attr(writer,"id","%d",ship->commodities[i].id);
3058       xmlw_str(writer,"%s",ship->commodities[i].commodity->name);
3059 
3060       xmlw_endElem(writer); /* commodity */
3061    }
3062    xmlw_endElem(writer); /* "commodities" */
3063 
3064    xmlw_startElem(writer,"weaponsets");
3065    xmlw_attr(writer,"autoweap","%d",ship->autoweap);
3066    xmlw_attr(writer,"active_set","%d",ship->active_set);
3067    for (i=0; i<PILOT_WEAPON_SETS; i++) {
3068       weaps = pilot_weapSetList( ship, i, &n );
3069       xmlw_startElem(writer,"weaponset");
3070       /* Inrange isn't handled by autoweap for the player. */
3071       xmlw_attr(writer,"inrange","%d",pilot_weapSetInrangeCheck(ship,i));
3072       xmlw_attr(writer,"id","%d",i);
3073       if (!ship->autoweap) {
3074          name = pilot_weapSetName(ship,i);
3075          if (name != NULL)
3076             xmlw_attr(writer,"name","%s",name);
3077          xmlw_attr(writer,"type","%d",pilot_weapSetTypeCheck(ship,i));
3078          for (j=0; j<n;j++) {
3079             xmlw_startElem(writer,"weapon");
3080             xmlw_attr(writer,"level","%d",weaps[j].level);
3081             xmlw_str(writer,"%d",weaps[j].slot->id);
3082             xmlw_endElem(writer); /* "weapon" */
3083          }
3084       }
3085       xmlw_endElem(writer); /* "weaponset" */
3086    }
3087    xmlw_endElem(writer); /* "weaponsets" */
3088 
3089    xmlw_endElem(writer); /* "ship" */
3090 
3091    return 0;
3092 }
3093 
3094 /**
3095  * @brief Loads the player stuff.
3096  *
3097  *    @param parent Node where the player stuff is to be found.
3098  *    @return 0 on success.
3099  */
player_load(xmlNodePtr parent)3100 Planet* player_load( xmlNodePtr parent )
3101 {
3102    xmlNodePtr node;
3103    Planet *pnt;
3104 
3105    /* some cleaning up */
3106    memset( &player, 0, sizeof(Player_t) );
3107    pnt = NULL;
3108    map_cleanup();
3109 
3110    node = parent->xmlChildrenNode;
3111    do {
3112       if (xml_isNode(node,"player"))
3113          pnt = player_parse( node );
3114       else if (xml_isNode(node,"missions_done"))
3115          player_parseDoneMissions( node );
3116       else if (xml_isNode(node,"events_done"))
3117          player_parseDoneEvents( node );
3118       else if (xml_isNode(node,"escorts"))
3119          player_parseEscorts(node);
3120    } while (xml_nextNode(node));
3121 
3122    return pnt;
3123 }
3124 
3125 
3126 /**
3127  * @brief Parses the player node.
3128  *
3129  *    @param parent The player node.
3130  *    @return Planet to start on on success.
3131  */
player_parse(xmlNodePtr parent)3132 static Planet* player_parse( xmlNodePtr parent )
3133 {
3134    char *planet, *found, *str;
3135    unsigned int services;
3136    Planet *pnt;
3137    xmlNodePtr node, cur;
3138    int q;
3139    Outfit *o;
3140    int i, map_overlay;
3141    StarSystem *sys;
3142    double a, r;
3143    Pilot *old_ship;
3144    PilotFlags flags;
3145    int scu, stp, stu, time_set;
3146    double rem;
3147 
3148    xmlr_attr(parent,"name",player.name);
3149 
3150    /* Make sure player.p is NULL. */
3151    player.p = NULL;
3152    pnt = NULL;
3153 
3154    /* Sane defaults. */
3155    planet      = NULL;
3156    time_set    = 0;
3157    map_overlay = 0;
3158 
3159    /* Must get planet first. */
3160    node = parent->xmlChildrenNode;
3161    do {
3162       xmlr_str(node,"location",planet);
3163    } while (xml_nextNode(node));
3164 
3165    /* Parse rest. */
3166    node = parent->xmlChildrenNode;
3167    do {
3168 
3169       /* global stuff */
3170       xmlr_float(node,"rating",player.crating);
3171       xmlr_ulong(node,"credits",player_creds);
3172       xmlr_strd(node,"gui",player.gui);
3173       xmlr_int(node,"guiOverride",player.guiOverride);
3174       xmlr_int(node,"mapOverlay",map_overlay);
3175       ovr_setOpen(map_overlay);
3176 
3177       /* Time. */
3178       if (xml_isNode(node,"time")) {
3179          cur = node->xmlChildrenNode;
3180          scu = stp = stu = -1;
3181          rem = -1.;
3182          do {
3183             xmlr_int(cur,"SCU",scu);
3184             xmlr_int(cur,"STP",stp);
3185             xmlr_int(cur,"STU",stu);
3186             xmlr_float(cur,"Remainder",rem);
3187          } while (xml_nextNode(cur));
3188          if ((scu < 0) || (stp < 0) || (stu < 0) || (rem<0.))
3189             WARN("Malformed time in save game!");
3190          ntime_setR( scu, stp, stu, rem );
3191          if ((scu >= 0) || (stp >= 0) || (stu >= 0))
3192             time_set = 1;
3193       }
3194 
3195       if (xml_isNode(node,"ship"))
3196          player_parseShip(node, 1, planet);
3197 
3198       /* Parse ships. */
3199       else if (xml_isNode(node,"ships")) {
3200          cur = node->xmlChildrenNode;
3201          do {
3202             if (xml_isNode(cur,"ship"))
3203                player_parseShip(cur, 0, planet);
3204          } while (xml_nextNode(cur));
3205       }
3206 
3207       /* Parse GUIs. */
3208       else if (xml_isNode(node,"guis")) {
3209          cur = node->xmlChildrenNode;
3210          do {
3211             if (xml_isNode(cur,"gui"))
3212                player_guiAdd( xml_get(cur) );
3213          } while (xml_nextNode(cur));
3214       }
3215 
3216       /* Parse outfits. */
3217       else if (xml_isNode(node,"outfits")) {
3218          cur = node->xmlChildrenNode;
3219          do {
3220             if (xml_isNode(cur,"outfit")) {
3221                o = outfit_get( xml_get(cur) );
3222                if (o == NULL) {
3223                   WARN("Outfit '%s' was saved but does not exist!", xml_get(cur));
3224                   continue;
3225                }
3226 
3227                xmlr_attr( cur, "quantity", str );
3228                if (str != NULL) {
3229                   q = atof(str);
3230                   free(str);
3231                }
3232                else {
3233                   WARN("Outfit '%s' was saved without quantity!", o->name);
3234                   continue;
3235                }
3236 
3237                player_addOutfit( o, q );
3238             }
3239          } while (xml_nextNode(cur));
3240       }
3241 
3242       /* Parse licenses. */
3243       else if (xml_isNode(node,"licenses"))
3244          player_parseLicenses(node);
3245 
3246    } while (xml_nextNode(node));
3247 
3248    /* Handle cases where ship is missing. */
3249    if (player.p == NULL) {
3250       pilot_clearFlagsRaw( flags );
3251       pilot_setFlagRaw( flags, PILOT_PLAYER );
3252       pilot_setFlagRaw( flags, PILOT_NO_OUTFITS );
3253       WARN("Player ship does not exist!");
3254 
3255       if (player_nstack == 0) {
3256          WARN("Player has no other ships, giving starting ship.");
3257          pilot_create( ship_get(start_ship()), "MIA",
3258                faction_get("Player"), "player", 0., NULL, NULL, flags );
3259       }
3260       else {
3261 
3262          /* Just give player.p a random ship in the stack. */
3263          old_ship = player_stack[player_nstack-1].p;
3264          pilot_create( old_ship->ship, old_ship->name,
3265                faction_get("Player"), "player", 0., NULL, NULL, flags );
3266          player_rmShip( old_ship->name );
3267          WARN("Giving player ship '%s'.", player.p->name );
3268       }
3269    }
3270 
3271    /* Check. */
3272    if (player.p == NULL) {
3273       ERR("Something went horribly wrong, player does not exist after load...");
3274       return NULL;
3275    }
3276 
3277    /* set global thingies */
3278    player.p->credits = player_creds;
3279    if (!time_set) {
3280       WARN("Save has no time information, setting to start information.");
3281       ntime_set( start_date() );
3282    }
3283 
3284    /* set player in system */
3285    pnt = planet_get( planet );
3286    /* Get random planet if it's NULL. */
3287    if ((pnt == NULL) || (planet_getSystem(planet) == NULL) ||
3288          !planet_hasService(pnt, PLANET_SERVICE_LAND)) {
3289       WARN("Player starts out in non-existent or invalid planet '%s',"
3290             "trying to find a suitable one instead.",
3291             planet );
3292 
3293       /* Find a landable, inhabited planet that's in a system, offers refueling
3294        * and meets the following additional criteria:
3295        *
3296        *    0: Shipyard, outfitter, non-hostile
3297        *    1: Outfitter, non-hostile
3298        *    2: None
3299        *
3300        * If no planet meeting the current criteria can be found, the next
3301        * set of criteria is tried until none remain.
3302        */
3303       found = NULL;
3304       for (i=0; i<3; i++) {
3305          services = PLANET_SERVICE_LAND | PLANET_SERVICE_INHABITED |
3306                PLANET_SERVICE_REFUEL;
3307 
3308          if (i == 0)
3309             services |= PLANET_SERVICE_SHIPYARD;
3310 
3311          if (i != 2)
3312             services |= PLANET_SERVICE_OUTFITS;
3313 
3314          found = space_getRndPlanet( 1, services,
3315                (i != 2) ? player_filterSuitablePlanet : NULL );
3316          if (found != NULL)
3317             break;
3318 
3319          WARN("Could not find a planet satisfying criteria %d.", i);
3320       }
3321 
3322       if (found == NULL) {
3323          WARN("Could not find a suitable planet. Choosing a random planet.");
3324          found = space_getRndPlanet(0, 0, NULL); /* This should never, ever fail. */
3325       }
3326       pnt = planet_get( found );
3327    }
3328    sys = system_get( planet_getSystem( pnt->name ) );
3329    space_gfxLoad( sys );
3330    a = RNGF() * 2.*M_PI;
3331    r = RNGF() * pnt->radius * 0.8;
3332    player_warp( pnt->pos.x + r*cos(a), pnt->pos.y + r*sin(a) );
3333    player.p->solid->dir = RNG(0,359) * M_PI/180.;
3334 
3335    /* initialize the system */
3336    space_init( sys->name );
3337    map_clear(); /* sets the map up */
3338 
3339    /* initialize the sound */
3340    player_initSound();
3341 
3342    return pnt;
3343 }
3344 
3345 
3346 /**
3347  * @brief Filter function for space_getRndPlanet
3348  *
3349  *    @param p Planet.
3350  *    @return Whether the planet is suitable for teleporting to.
3351  */
player_filterSuitablePlanet(Planet * p)3352 static int player_filterSuitablePlanet( Planet *p )
3353 {
3354    return !areEnemies(p->faction, FACTION_PLAYER);
3355 }
3356 
3357 
3358 /**
3359  * @brief Parses player's done missions.
3360  *
3361  *    @param parent Node of the missions.
3362  *    @return 0 on success.
3363  */
player_parseDoneMissions(xmlNodePtr parent)3364 static int player_parseDoneMissions( xmlNodePtr parent )
3365 {
3366    xmlNodePtr node;
3367    int id;
3368 
3369    node = parent->xmlChildrenNode;
3370 
3371    do {
3372       if (xml_isNode(node,"done")) {
3373          id = mission_getID( xml_get(node) );
3374          if (id < 0)
3375             DEBUG("Mission '%s' doesn't seem to exist anymore, removing from save.",
3376                   xml_get(node));
3377          else
3378             player_missionFinished( id );
3379       }
3380    } while (xml_nextNode(node));
3381 
3382    return 0;
3383 }
3384 
3385 
3386 /**
3387  * @brief Parses player's done missions.
3388  *
3389  *    @param parent Node of the missions.
3390  *    @return 0 on success.
3391  */
player_parseDoneEvents(xmlNodePtr parent)3392 static int player_parseDoneEvents( xmlNodePtr parent )
3393 {
3394    xmlNodePtr node;
3395    int id;
3396 
3397    node = parent->xmlChildrenNode;
3398 
3399    do {
3400       if (xml_isNode(node,"done")) {
3401          id = event_dataID( xml_get(node) );
3402          if (id < 0)
3403             DEBUG("Event '%s' doesn't seem to exist anymore, removing from save.",
3404                   xml_get(node));
3405          else
3406             player_eventFinished( id );
3407       }
3408    } while (xml_nextNode(node));
3409 
3410    return 0;
3411 }
3412 
3413 
3414 /**
3415  * @brief Parses player's licenses.
3416  *
3417  *    @param parent Node of the licenses.
3418  *    @return 0 on success.
3419  */
player_parseLicenses(xmlNodePtr parent)3420 static int player_parseLicenses( xmlNodePtr parent )
3421 {
3422    xmlNodePtr node;
3423 
3424    node = parent->xmlChildrenNode;
3425 
3426    do {
3427       if (xml_isNode(node,"license"))
3428          player_addLicense( xml_get(node) );
3429    } while (xml_nextNode(node));
3430 
3431    return 0;
3432 }
3433 
3434 
3435 /**
3436  * @brief Parses the escorts from the escort node.
3437  *
3438  *    @param parent "escorts" node to parse.
3439  *    @return 0 on success.
3440  */
player_parseEscorts(xmlNodePtr parent)3441 static int player_parseEscorts( xmlNodePtr parent )
3442 {
3443    xmlNodePtr node;
3444    char *buf, *ship;
3445    EscortType_t type;
3446 
3447    node = parent->xmlChildrenNode;
3448 
3449    do {
3450       if (xml_isNode(node,"escort")) {
3451          xmlr_attr( node, "type", buf );
3452          if (strcmp(buf,"bay")==0)
3453             type = ESCORT_TYPE_BAY;
3454          else {
3455             WARN("Escort has invalid type '%s'.", buf);
3456             type = ESCORT_TYPE_NULL;
3457          }
3458          free(buf);
3459 
3460          ship = xml_get(node);
3461 
3462          /* Add escort to the list. */
3463          escort_addList( player.p, ship, type, 0, 1 );
3464       }
3465    } while (xml_nextNode(node));
3466 
3467    return 0;
3468 }
3469 
3470 
3471 /**
3472  * @brief Adds outfit to pilot if it can.
3473  */
player_addOutfitToPilot(Pilot * pilot,Outfit * outfit,PilotOutfitSlot * s)3474 static void player_addOutfitToPilot( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s )
3475 {
3476    int ret;
3477 
3478    if (!outfit_fitsSlot( outfit, &s->sslot->slot )) {
3479       DEBUG( "Outfit '%s' does not fit designated slot on player's pilot '%s', adding to stock.",
3480             outfit->name, pilot->name );
3481       player_addOutfit( outfit, 1 );
3482       return;
3483    }
3484 
3485    ret = pilot_addOutfitRaw( pilot, outfit, s );
3486    if (ret != 0) {
3487       DEBUG("Outfit '%s' does not fit on player's pilot '%s', adding to stock.",
3488             outfit->name, pilot->name);
3489       player_addOutfit( outfit, 1 );
3490       return;
3491    }
3492 
3493    /* Update stats. */
3494    pilot_calcStats( pilot );
3495 }
3496 
3497 
3498 /**
3499  * @brief Parses a ship outfit slot.
3500  */
player_parseShipSlot(xmlNodePtr node,Pilot * ship,PilotOutfitSlot * slot)3501 static void player_parseShipSlot( xmlNodePtr node, Pilot *ship, PilotOutfitSlot *slot )
3502 {
3503    Outfit *o, *ammo;
3504    char *buf;
3505    int q;
3506 
3507    char *name = xml_get(node);
3508    if (name == NULL) {
3509       WARN("Empty ship slot node found, skipping.");
3510       return;
3511    }
3512 
3513    /* Add the outfit. */
3514    o = outfit_get( name );
3515    if (o==NULL)
3516       return;
3517    player_addOutfitToPilot( ship, o, slot );
3518 
3519    /* Doesn't have ammo. */
3520    if (outfit_ammo(o)==NULL)
3521       return;
3522 
3523    /* See if has ammo. */
3524    xmlr_attr(node,"ammo",buf);
3525    if (buf == NULL)
3526       return;
3527 
3528    /* Get the ammo. */
3529    ammo = outfit_get(buf);
3530    free(buf);
3531    if (ammo==NULL)
3532       return;
3533 
3534    /* See if has quantity. */
3535    xmlr_attr(node,"quantity",buf);
3536    if (buf == NULL)
3537       return;
3538 
3539    /* Get quantity. */
3540    q = atoi(buf);
3541    free(buf);
3542 
3543    /* Add ammo. */
3544    pilot_addAmmo( ship, slot, ammo, q );
3545 }
3546 
3547 
3548 /**
3549  * @brief Parses a player's ship.
3550  *
3551  *    @param parent Node of the ship.
3552  *    @param is_player Is it the ship the player is currently in?
3553  *    @param planet Default planet in case ship location not found.
3554  *    @return 0 on success.
3555  */
player_parseShip(xmlNodePtr parent,int is_player,char * planet)3556 static int player_parseShip( xmlNodePtr parent, int is_player, char *planet )
3557 {
3558    char *name, *model, *loc, *q, *id;
3559    int i, n;
3560    double fuel;
3561    Ship *ship_parsed;
3562    Pilot* ship;
3563    xmlNodePtr node, cur, ccur;
3564    int quantity;
3565    Outfit *o;
3566    int ret;
3567    /*const char *str;*/
3568    Commodity *com;
3569    PilotFlags flags;
3570    unsigned int pid;
3571    int autoweap, level, weapid, active_set;
3572 
3573    xmlr_attr(parent,"name",name);
3574    xmlr_attr(parent,"model",model);
3575 
3576    /* Sane defaults. */
3577    loc = NULL;
3578    pilot_clearFlagsRaw( flags );
3579    pilot_setFlagRaw( flags, PILOT_PLAYER );
3580    pilot_setFlagRaw( flags, PILOT_NO_OUTFITS );
3581 
3582    /* Get the ship. */
3583    ship_parsed = ship_get(model);
3584    if (ship_parsed == NULL) {
3585       WARN("Player ship '%s' not found!", model);
3586 
3587       /* Clean up. */
3588       free(name);
3589       free(model);
3590 
3591       return -1;
3592    }
3593 
3594    /* Add GUI if applicable. */
3595    player_guiAdd( ship_parsed->gui );
3596 
3597    /* player is currently on this ship */
3598    if (is_player != 0) {
3599       pid = pilot_create( ship_parsed, name, faction_get("Player"), "player", 0., NULL, NULL, flags );
3600       ship = player.p;
3601       cam_setTargetPilot( pid, 0 );
3602    }
3603    else
3604       ship = pilot_createEmpty( ship_parsed, name, faction_get("Player"), "player", flags );
3605 
3606    /* Ship should not have default outfits. */
3607    for (i=0; i<ship->noutfits; i++)
3608       pilot_rmOutfitRaw( ship, ship->outfits[i] );
3609 
3610    /* Clean up. */
3611    free(name);
3612    free(model);
3613 
3614    /* Defaults. */
3615    fuel     = -1;
3616    autoweap = 1;
3617 
3618    /* Start parsing. */
3619    node = parent->xmlChildrenNode;
3620    do {
3621       /* Get location. */
3622       if (is_player == 0)
3623          xmlr_str(node,"location",loc);
3624 
3625       /* get fuel */
3626       xmlr_float(node,"fuel",fuel);
3627 
3628       /* New outfit loading. */
3629       if (xml_isNode(node,"outfits_structure") || xml_isNode(node,"outfits_low")) { /** @todo remove legacy layer for 0.6.0 */
3630          cur = node->xmlChildrenNode;
3631          do { /* load each outfit */
3632             if (xml_isNode(cur,"outfit")) {
3633                xmlr_attr(cur,"slot",q);
3634                n = -1;
3635                if (q != NULL) {
3636                   n = atoi(q);
3637                   free(q);
3638                }
3639                if ((n<0) || (n >= ship->outfit_nstructure)) {
3640                   WARN("Outfit slot out of range, not adding.");
3641                   continue;
3642                }
3643                player_parseShipSlot( cur, ship, &ship->outfit_structure[n] );
3644             }
3645          } while (xml_nextNode(cur));
3646       }
3647       else if (xml_isNode(node,"outfits_utility") || xml_isNode(node,"outfits_medium")) { /** @todo remove legacy layer for 0.6.0 */
3648          cur = node->xmlChildrenNode;
3649          do { /* load each outfit */
3650             if (xml_isNode(cur,"outfit")) {
3651                xmlr_attr(cur,"slot",q);
3652                n = -1;
3653                if (q != NULL) {
3654                   n = atoi(q);
3655                   free(q);
3656                }
3657                if ((n<0) || (n >= ship->outfit_nutility)) {
3658                   WARN("Outfit slot out of range, not adding.");
3659                   continue;
3660                }
3661                player_parseShipSlot( cur, ship, &ship->outfit_utility[n] );
3662             }
3663          } while (xml_nextNode(cur));
3664       }
3665       else if (xml_isNode(node,"outfits_weapon") || xml_isNode(node,"outfits_high")) { /** @todo remove legacy layer for 0.6.0 */
3666          cur = node->xmlChildrenNode;
3667          do { /* load each outfit */
3668             if (xml_isNode(cur,"outfit")) {
3669                xmlr_attr(cur,"slot",q);
3670                n = -1;
3671                if (q != NULL) {
3672                   n = atoi(q);
3673                   free(q);
3674                }
3675                if ((n<0) || (n >= ship->outfit_nweapon)) {
3676                   WARN("Outfit slot out of range, not adding.");
3677                   continue;
3678                }
3679                player_parseShipSlot( cur, ship, &ship->outfit_weapon[n] );
3680             }
3681          } while (xml_nextNode(cur));
3682       }
3683       else if (xml_isNode(node,"commodities")) {
3684          cur = node->xmlChildrenNode;
3685          do {
3686             if (xml_isNode(cur,"commodity")) {
3687                xmlr_attr(cur,"quantity",q);
3688                xmlr_attr(cur,"id",id);
3689                quantity = atoi(q);
3690                i = (id==NULL) ? 0 : atoi(id);
3691                free(q);
3692                if (id != NULL)
3693                   free(id);
3694 
3695                /* Get the commodity. */
3696                com = commodity_get(xml_get(cur));
3697                if (com == NULL) {
3698                   WARN("Unknown commodity '%s' detected, removing.", xml_get(cur));
3699                   continue;
3700                }
3701 
3702                /* actually add the cargo with id hack
3703                 * Note that the player's cargo_free is ignored here.
3704                 */
3705                pilot_cargoAddRaw( ship, com, quantity, 0 );
3706                if (i != 0)
3707                   ship->commodities[ ship->ncommodities-1 ].id = i;
3708             }
3709          } while (xml_nextNode(cur));
3710       }
3711    } while (xml_nextNode(node));
3712 
3713    /* Update stats. */
3714    pilot_calcStats( ship );
3715 
3716    /* Test for sanity. */
3717    if (fuel >= 0)
3718       ship->fuel = MIN(ship->fuel_max, fuel);
3719    if ((is_player == 0) && (planet_get(loc)==NULL))
3720       loc = planet;
3721    /* ships can now be non-spaceworthy on save
3722     * str = pilot_checkSpaceworthy( ship ); */
3723    if (!pilot_slotsCheckSanity( ship )) {
3724       DEBUG("Player ship '%s' failed slot sanity check , removing all outfits and adding to stock.",
3725             ship->name );
3726       /* Remove all outfits. */
3727       for (i=0; i<ship->noutfits; i++) {
3728          o = ship->outfits[i]->outfit;
3729          ret = pilot_rmOutfitRaw( ship, ship->outfits[i] );
3730          if (ret==0)
3731             player_addOutfit( o, 1 );
3732       }
3733       pilot_calcStats( ship );
3734    }
3735 
3736    /* add it to the stack if it's not what the player is in */
3737    if (is_player == 0) {
3738       player_stack = realloc(player_stack, sizeof(PlayerShip_t)*(player_nstack+1));
3739       player_stack[player_nstack].p    = ship;
3740       player_stack[player_nstack].loc  = (loc!=NULL) ? strdup(loc) : strdup("Uknown");
3741       player_nstack++;
3742    }
3743 
3744    /* Sets inrange by default if weapon sets are missing. */
3745    for (i=0; i<PILOT_WEAPON_SETS; i++)
3746       pilot_weapSetInrange( ship, i, WEAPSET_INRANGE_PLAYER_DEF );
3747 
3748    /* Second pass for weapon sets. */
3749    node = parent->xmlChildrenNode;
3750    do {
3751       if (!xml_isNode(node,"weaponsets"))
3752          continue;
3753 
3754       /* Check for autoweap. */
3755       xmlr_attr(node,"autoweap",id);
3756       if (id != NULL) {
3757          autoweap = atoi(id);
3758          free(id);
3759       }
3760 
3761       /* Load the last weaponset the player used on this ship. */
3762       xmlr_attr(node,"active_set",id);
3763       if (id != NULL) {
3764          active_set = atoi(id);
3765          free(id);
3766       }
3767       else {
3768          /* set active_set to invalid. will be dealt with later */
3769          active_set = -1;
3770       }
3771 
3772       /* Parse weapon sets. */
3773       cur = node->xmlChildrenNode;
3774       do { /* Load each weapon set. */
3775          xml_onlyNodes(cur);
3776          if (!xml_isNode(cur,"weaponset")) {
3777             WARN("Player ship '%s' has unknown node '%s' in 'weaponsets' (expected 'weaponset').",
3778                   ship->name, cur->name);
3779             continue;
3780          }
3781 
3782          /* Get id. */
3783          xmlr_attr(cur,"id",id);
3784          if (id == NULL) {
3785             WARN("Player ship '%s' missing 'id' tag for weapon set.",ship->name);
3786             continue;
3787          }
3788          i = atoi(id);
3789          free(id);
3790          if ((i < 0) || (i >= PILOT_WEAPON_SETS)) {
3791             WARN("Player ship '%s' has invalid weapon set id '%d' [max %d].",
3792                   ship->name, i, PILOT_WEAPON_SETS-1 );
3793             continue;
3794          }
3795 
3796          /* Set inrange mode. */
3797          xmlr_attr(cur,"inrange",id);
3798          if (id == NULL)
3799             pilot_weapSetInrange( ship, i, WEAPSET_INRANGE_PLAYER_DEF );
3800          else {
3801             pilot_weapSetInrange( ship, i, atoi(id) );
3802             free(id);
3803          }
3804 
3805          if (autoweap) /* Autoweap handles everything except inrange. */
3806             continue;
3807 
3808          /* Set type mode. */
3809          xmlr_attr(cur,"type",id);
3810          if (id == NULL) {
3811             WARN("Player ship '%s' missing 'type' tag for weapon set.",ship->name);
3812             continue;
3813          }
3814          pilot_weapSetType( ship, i, atoi(id) );
3815          free(id);
3816 
3817          /* Parse individual weapons. */
3818          ccur = cur->xmlChildrenNode;
3819          do {
3820             /* Only nodes. */
3821             xml_onlyNodes(ccur);
3822 
3823             /* Only weapon nodes. */
3824             if (!xml_isNode(ccur,"weapon")) {
3825                WARN("Player ship '%s' has unknown 'weaponset' child node '%s' (expected 'weapon').",
3826                      ship->name, ccur->name );
3827                continue;
3828             }
3829 
3830             /* Get level. */
3831             xmlr_attr(ccur,"level",id);
3832             if (id == NULL) {
3833                WARN("Player ship '%s' missing 'level' tag for weapon set weapon.", ship->name);
3834                continue;
3835             }
3836             level = atoi(id);
3837             free(id);
3838             weapid = xml_getInt(ccur);
3839             if ((weapid < 0) || (weapid >= ship->noutfits)) {
3840                WARN("Player ship '%s' has invalid weapon id %d [max %d].",
3841                      ship->name, weapid, ship->noutfits-1 );
3842                continue;
3843             }
3844 
3845             /* Add the weapon set. */
3846             pilot_weapSetAdd( ship, i, ship->outfits[weapid], level );
3847 
3848          } while (xml_nextNode(ccur));
3849       } while (xml_nextNode(cur));
3850    } while (xml_nextNode(node));
3851 
3852    /* Set up autoweap if necessary. */
3853    ship->autoweap = autoweap;
3854    if (autoweap)
3855       pilot_weaponAuto( ship );
3856    pilot_weaponSane( ship );
3857    if (active_set >= 0 && active_set < PILOT_WEAPON_SETS)
3858       ship->active_set = active_set;
3859    else
3860       pilot_weaponSetDefault( ship );
3861 
3862    return 0;
3863 }
3864 
3865 
3866