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