1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 /**
6  * @file load.c
7  *
8  * @brief Contains stuff to load a pilot or look up information about it.
9  */
10 
11 
12 #include "load.h"
13 
14 #include "naev.h"
15 
16 #include "nxml.h"
17 #include "log.h"
18 #include "player.h"
19 #include "nfile.h"
20 #include "array.h"
21 #include "space.h"
22 #include "toolkit.h"
23 #include "menu.h"
24 #include "dialogue.h"
25 #include "event.h"
26 #include "news.h"
27 #include "mission.h"
28 #include "faction.h"
29 #include "gui.h"
30 #include "unidiff.h"
31 #include "nlua_var.h"
32 #include "land.h"
33 #include "hook.h"
34 #include "nstring.h"
35 #include "outfit.h"
36 
37 
38 #define LOAD_WIDTH      600 /**< Load window width. */
39 #define LOAD_HEIGHT     500 /**< Load window height. */
40 
41 #define BUTTON_WIDTH    80 /**< Button width. */
42 #define BUTTON_HEIGHT   30 /**< Button height. */
43 
44 
45 static nsave_t *load_saves = NULL; /**< Array of save.s */
46 
47 
48 extern int save_loaded; /**< From save.c */
49 
50 
51 /*
52  * Prototypes.
53  */
54 /* externs */
55 /* player.c */
56 extern Planet* player_load( xmlNodePtr parent ); /**< Loads player related stuff. */
57 /* mission.c */
58 extern int missions_loadActive( xmlNodePtr parent ); /**< Loads active missions. */
59 /* event.c */
60 extern int events_loadActive( xmlNodePtr parent );
61 /* news.c */
62 extern int news_loadArticles( xmlNodePtr parent );
63 /* nlua_var.c */
64 extern int var_load( xmlNodePtr parent ); /**< Loads mission variables. */
65 /* faction.c */
66 extern int pfaction_load( xmlNodePtr parent ); /**< Loads faction data. */
67 /* hook.c */
68 extern int hook_load( xmlNodePtr parent ); /**< Loads hooks. */
69 /* space.c */
70 extern int space_sysLoad( xmlNodePtr parent ); /**< Loads the space stuff. */
71 /* unidiff.c */
72 extern int diff_load( xmlNodePtr parent ); /**< Loads the universe diffs. */
73 /* static */
74 static void load_menu_update( unsigned int wid, char *str );
75 static void load_menu_close( unsigned int wdw, char *str );
76 static void load_menu_load( unsigned int wdw, char *str );
77 static void load_menu_delete( unsigned int wdw, char *str );
78 static int load_load( nsave_t *save, const char *path );
79 
80 
81 /**
82  * @brief Loads an individual save.
83  */
load_load(nsave_t * save,const char * path)84 static int load_load( nsave_t *save, const char *path )
85 {
86    xmlDocPtr doc;
87    xmlNodePtr root, parent, node, cur;
88    int scu, stp, stu;
89    char *version = NULL;
90 
91    memset( save, 0, sizeof(nsave_t) );
92 
93    /* Load the XML. */
94    doc   = xmlParseFile(path);
95    if (doc == NULL) {
96       WARN("Unable to parse save path '%s'.", path);
97       return -1;
98    }
99    root = doc->xmlChildrenNode; /* base node */
100    if (root == NULL) {
101       WARN("Unable to get child node of save '%s'.",path);
102       xmlFreeDoc(doc);
103       return -1;
104    }
105 
106    /* Save path. */
107    save->path = strdup(path);
108 
109    /* Iterate inside the naev_save. */
110    parent = root->xmlChildrenNode;
111    do {
112       xml_onlyNodes(parent);
113 
114       /* Info. */
115       if (xml_isNode(parent,"version")) {
116          node = parent->xmlChildrenNode;
117          do {
118             xmlr_strd(node,"naev",version);
119             xmlr_strd(node,"data",save->data);
120          } while (xml_nextNode(node));
121          continue;
122       }
123 
124       if (xml_isNode(parent,"player")) {
125          /* Get name. */
126          xmlr_attr(parent,"name",save->name);
127          /* Parse rest. */
128          node = parent->xmlChildrenNode;
129          do {
130             xml_onlyNodes(node);
131 
132             /* Player info. */
133             xmlr_strd(node,"location",save->planet);
134             xmlr_ulong(node,"credits",save->credits);
135 
136             /* Time. */
137             if (xml_isNode(node,"time")) {
138                cur = node->xmlChildrenNode;
139                scu = stp = stu = 0;
140                do {
141                   xmlr_int(cur,"SCU",scu);
142                   xmlr_int(cur,"STP",stp);
143                   xmlr_int(cur,"STU",stu);
144                } while (xml_nextNode(cur));
145                save->date = ntime_create( scu, stp, stu );
146                continue;
147             }
148 
149             /* Ship info. */
150             if (xml_isNode(node,"ship")) {
151                xmlr_attr(node,"name",save->shipname);
152                xmlr_attr(node,"model",save->shipmodel);
153                continue;
154             }
155          } while (xml_nextNode(node));
156          continue;
157       }
158    } while (xml_nextNode(parent));
159 
160    /* Handle version. */
161    if (version != NULL) {
162       naev_versionParse( save->version, version, strlen(version) );
163       free(version);
164    }
165 
166    /* Clean up. */
167    xmlFreeDoc(doc);
168 
169    return 0;
170 }
171 
172 
173 /**
174  * @brief Loads or refreshes saved games.
175  */
load_refresh(void)176 int load_refresh (void)
177 {
178    char **files, buf[PATH_MAX], *tmp;
179    int nfiles, i, len;
180    int ok;
181    nsave_t *ns;
182 
183    if (load_saves != NULL)
184       load_free();
185    load_saves = array_create( nsave_t );
186 
187    /* load the saves */
188    files = nfile_readDir( &nfiles, "%ssaves", nfile_dataPath() );
189    for (i=0; i<nfiles; i++) {
190       len = strlen(files[i]);
191 
192       /* no save or backup save extension */
193       if (((len < 5) || strcmp(&files[i][len-3],".ns")) &&
194             ((len < 12) || strcmp(&files[i][len-10],".ns.backup"))) {
195          free(files[i]);
196          memmove( &files[i], &files[i+1], sizeof(char*) * (nfiles-i-1) );
197          nfiles--;
198          i--;
199       }
200    }
201 
202    /* Make sure files are none. */
203    if (files == NULL)
204       return 0;
205 
206    /* Make sure backups are after saves. */
207    for (i=0; i<nfiles-1; i++) {
208       len = strlen( files[i] );
209 
210       /* Only interested in swapping backup with file after it if it's not backup. */
211       if ((len < 12) || strcmp( &files[i][len-10],".ns.backup" ))
212          continue;
213 
214       /* Don't match. */
215       if (strncmp( files[i], files[i+1], (len-10) ))
216          continue;
217 
218       /* Swap around. */
219       tmp         = files[i];
220       files[i]    = files[i+1];
221       files[i+1]  = tmp;
222    }
223 
224    /* Allocate and parse. */
225    ok = 0;
226    ns = NULL;
227    for (i=0; i<nfiles; i++) {
228       if (!ok)
229          ns = &array_grow( &load_saves );
230       nsnprintf( buf, sizeof(buf), "%ssaves/%s", nfile_dataPath(), files[i] );
231       ok = load_load( ns, buf );
232    }
233 
234    /* If the save was invalid, array is 1 member too large. */
235    if (ok)
236       array_resize( &load_saves, array_size(load_saves)-1 );
237 
238    /* Clean up memory. */
239    for (i=0; i<nfiles; i++)
240       free(files[i]);
241    free(files);
242 
243    return 0;
244 }
245 
246 
247 /**
248  * @brief Frees loaded save stuff.
249  */
load_free(void)250 void load_free (void)
251 {
252    int i;
253    nsave_t *ns;
254 
255    if (load_saves != NULL) {
256       for (i=0; i<array_size(load_saves); i++) {
257          ns = &load_saves[i];
258          free(ns->path);
259          if (ns->name != NULL)
260             free(ns->name);
261 
262          if (ns->data != NULL)
263             free(ns->data);
264 
265          if (ns->planet != NULL)
266             free(ns->planet);
267 
268          if (ns->shipname != NULL)
269             free(ns->shipname);
270          if (ns->shipmodel != NULL)
271             free(ns->shipmodel);
272       }
273       array_free( load_saves );
274    }
275    load_saves = NULL;
276 }
277 
278 
279 /**
280  * @brief Gets the list of loaded saves.
281  */
load_getList(int * n)282 nsave_t *load_getList( int *n )
283 {
284    if (load_saves == NULL) {
285       *n = 0;
286       return NULL;
287    }
288 
289    *n = array_size( load_saves );
290    return load_saves;
291 }
292 
293 /**
294  * @brief Opens the load game menu.
295  */
load_loadGameMenu(void)296 void load_loadGameMenu (void)
297 {
298    unsigned int wid;
299    char **names, buf[PATH_MAX];
300    nsave_t *nslist, *ns;
301    int i, n, len;
302 
303    /* window */
304    wid = window_create( "Load Game", -1, -1, LOAD_WIDTH, LOAD_HEIGHT );
305    window_setAccept( wid, load_menu_load );
306    window_setCancel( wid, load_menu_close );
307 
308    /* Load loads. */
309    load_refresh();
310 
311    /* load the saves */
312    nslist = load_getList( &n );
313    if (n > 0) {
314       names = malloc( sizeof(char*)*n );
315       for (i=0; i<n; i++) {
316          ns       = &nslist[i];
317          len      = strlen(ns->path);
318          if (strcmp(&ns->path[len-10],".ns.backup")==0) {
319             nsnprintf( buf, sizeof(buf), "%s \er(Backup)\e0", ns->name );
320             names[i] = strdup(buf);
321          }
322          else
323             names[i] = strdup( ns->name );
324       }
325    }
326    /* case there are no files */
327    else {
328       names = malloc(sizeof(char*));
329       names[0] = strdup("None");
330       n     = 1;
331    }
332 
333    /* Player text. */
334    window_addText( wid, -20, -40, 200, LOAD_HEIGHT-40-20-2*(BUTTON_HEIGHT+20),
335          0, "txtPilot", NULL, &cBlack, NULL );
336 
337    window_addList( wid, 20, -50,
338          LOAD_WIDTH-200-60, LOAD_HEIGHT-110,
339          "lstSaves", names, n, 0, load_menu_update );
340 
341    /* Buttons */
342    window_addButtonKey( wid, -20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
343          "btnBack", "Back", load_menu_close, SDLK_b );
344    window_addButtonKey( wid, -20, 20 + BUTTON_HEIGHT+20, BUTTON_WIDTH, BUTTON_HEIGHT,
345          "btnLoad", "Load", load_menu_load, SDLK_l );
346    window_addButton( wid, 20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
347          "btnDelete", "Del", load_menu_delete );
348 }
349 /**
350  * @brief Closes the load game menu.
351  *    @param wdw Window triggering function.
352  *    @param str Unused.
353  */
load_menu_close(unsigned int wdw,char * str)354 static void load_menu_close( unsigned int wdw, char *str )
355 {
356    (void)str;
357    window_destroy( wdw );
358 }
359 /**
360  * @brief Updates the load menu.
361  *    @param wdw Window triggering function.
362  *    @param str Unused.
363  */
load_menu_update(unsigned int wid,char * str)364 static void load_menu_update( unsigned int wid, char *str )
365 {
366    (void) str;
367    int pos;
368    nsave_t *ns;
369    int n;
370    char *save;
371    char buf[256], credits[ECON_CRED_STRLEN], date[64], version[256];
372 
373    /* Make sure list is ok. */
374    save = toolkit_getList( wid, "lstSaves" );
375    if (strcmp(save,"None") == 0)
376       return;
377 
378    /* Get position. */
379    pos = toolkit_getListPos( wid, "lstSaves" );
380    ns  = load_getList( &n );
381    ns  = &ns[pos];
382 
383    /* Display text. */
384    credits2str( credits, ns->credits, 2 );
385    ntime_prettyBuf( date, sizeof(date), ns->date, 2 );
386    naev_versionString( version, sizeof(version), ns->version[0], ns->version[1], ns->version[2] );
387    nsnprintf( buf, sizeof(buf),
388          "\eDName:\n"
389          "\e0   %s\n"
390          "\eDVersion:\n"
391          "\e0   %s\n"
392          "\eDDate:\n"
393          "\e0   %s\n"
394          "\eDPlanet:\n"
395          "\e0   %s\n"
396          "\eDCredits:\n"
397          "\e0   %s\n"
398          "\eDShip Name:\n"
399          "\e0   %s\n"
400          "\eDShip Model:\n"
401          "\e0   %s",
402          ns->name, version, date, ns->planet,
403          credits, ns->shipname, ns->shipmodel );
404    window_modifyText( wid, "txtPilot", buf );
405 }
406 /**
407  * @brief Loads a new game.
408  *    @param wdw Window triggering function.
409  *    @param str Unused.
410  */
load_menu_load(unsigned int wdw,char * str)411 static void load_menu_load( unsigned int wdw, char *str )
412 {
413    (void)str;
414    char *save;
415    int wid, pos;
416    nsave_t *ns;
417    int n;
418    char version[64];
419    int diff;
420 
421    wid = window_get( "Load Game" );
422    save = toolkit_getList( wid, "lstSaves" );
423 
424    if (strcmp(save,"None") == 0)
425       return;
426 
427    pos = toolkit_getListPos( wid, "lstSaves" );
428    ns  = load_getList( &n );
429 
430    /* Check version. */
431    diff = naev_versionCompare( ns[pos].version );
432    if (ABS(diff) >= 2) {
433       naev_versionString( version, sizeof(version), ns[pos].version[0],
434             ns[pos].version[1], ns[pos].version[2] );
435       if (!dialogue_YesNo( "Save game version mismatch",
436             "Save game '%s' version does not match Naev version:\n"
437             "   Save version: \er%s\e0\n"
438             "   Naev version: \eD%s\e0\n"
439             "Are you sure you want to load this game? It may lose data.",
440             save, version, naev_version(0) ))
441          return;
442    }
443 
444    /* Close menus before loading for proper rendering. */
445    load_menu_close(wdw, NULL);
446 
447    /* Close the main menu. */
448    menu_main_close();
449 
450    /* Try to load the game. */
451    if (load_game( ns[pos].path, diff )) {
452       /* Failed so reopen both. */
453       menu_main();
454       load_loadGameMenu();
455    }
456 }
457 /**
458  * @brief Deletes an old game.
459  *    @param wdw Window to delete.
460  *    @param str Unused.
461  */
load_menu_delete(unsigned int wdw,char * str)462 static void load_menu_delete( unsigned int wdw, char *str )
463 {
464    (void)str;
465    char *save;
466    int wid, pos;
467    nsave_t *ns;
468    int n;
469 
470    wid = window_get( "Load Game" );
471    save = toolkit_getList( wid, "lstSaves" );
472 
473    if (strcmp(save,"None") == 0)
474       return;
475 
476    if (dialogue_YesNo( "Permanently Delete?",
477       "Are you sure you want to permanently delete '%s'?", save) == 0)
478       return;
479 
480    /* Remove it. */
481    pos = toolkit_getListPos( wid, "lstSaves" );
482    ns  = load_getList( &n );
483    remove( ns[pos].path ); /* remove is portable and will call unlink on linux. */
484 
485    /* need to reload the menu */
486    load_menu_close(wdw, NULL);
487    load_loadGameMenu();
488 }
489 
490 
load_compatSlots(void)491 static void load_compatSlots (void)
492 {
493    /* Vars for loading old saves. */
494    int i,j;
495    char **sships;
496    glTexture **tships;
497    int nships;
498    Pilot *ship;
499    ShipOutfitSlot *sslot;
500 
501    nships = player_nships();
502    sships = malloc(nships * sizeof(char*));
503    tships = malloc(nships * sizeof(glTexture*));
504    nships = player_ships( sships, tships );
505    ship   = player.p;
506    for (i=-1; i<nships; i++) {
507       if (i >= 0)
508          ship = player_getShip( sships[i] );
509       /* Remove all outfits. */
510       for (j=0; j<ship->noutfits; j++) {
511          if (ship->outfits[j]->outfit != NULL) {
512             player_addOutfit( ship->outfits[j]->outfit, 1 );
513             pilot_rmOutfitRaw( ship, ship->outfits[j] );
514          }
515 
516          /* Add default outfit. */
517          sslot = ship->outfits[j]->sslot;
518          if (sslot->data != NULL)
519             pilot_addOutfitRaw( ship, sslot->data, ship->outfits[j] );
520       }
521 
522       pilot_calcStats( ship );
523    }
524 
525    /* Clean up. */
526    for (i=0; i<nships; i++)
527       free(sships[i]);
528    free(sships);
529    free(tships);
530 }
531 
532 
533 /**
534  * @brief Actually loads a new game based on file.
535  *
536  *    @param file File that contains the new game.
537  *    @return 0 on success.
538  */
load_game(const char * file,int version_diff)539 int load_game( const char* file, int version_diff )
540 {
541    xmlNodePtr node;
542    xmlDocPtr doc;
543    Planet *pnt;
544 
545    /* Make sure it exists. */
546    if (!nfile_fileExists(file)) {
547       dialogue_alert("Savegame file seems to have been deleted.");
548       return -1;
549    }
550 
551    /* Load the XML. */
552    doc   = xmlParseFile(file);
553    if (doc == NULL)
554       goto err;
555    node  = doc->xmlChildrenNode; /* base node */
556    if (node == NULL)
557       goto err_doc;
558 
559    /* Clean up possible stuff that should be cleaned. */
560    player_cleanup();
561 
562    /* Welcome message - must be before space_init. */
563    player_message( "\egWelcome to "APPNAME"!" );
564    player_message( "\eg v%s", naev_version(0) );
565 
566    /* Now begin to load. */
567    diff_load(node); /* Must load first to work properly. */
568    pfaction_load(node); /* Must be loaded before player so the messages show up properly. */
569    pnt = player_load(node);
570 
571    /* Sanitize for new version. */
572    if (version_diff <= -2) {
573       WARN("Old version detected. Sanitizing ships for slots");
574       load_compatSlots();
575    }
576 
577    /* Load more stuff. */
578    var_load(node);
579    missions_loadActive(node);
580    events_loadActive(node);
581    news_loadArticles( node );
582    hook_load(node);
583    space_sysLoad(node);
584 
585    /* Initialize the economy. */
586    economy_init();
587 
588    /* Check sanity. */
589    event_checkSanity();
590 
591    /* Run the load event trigger. */
592    events_trigger( EVENT_TRIGGER_LOAD );
593 
594    /* Create escorts in space. */
595    player_addEscorts();
596 
597    /* Land the player. */
598    land( pnt, 1 );
599 
600    /* Load the GUI. */
601    if (gui_load( gui_pick() )) {
602       if (player.p->ship->gui != NULL)
603          gui_load( player.p->ship->gui );
604    }
605 
606    /* Sanitize the GUI. */
607    gui_setCargo();
608    gui_setShip();
609 
610    xmlFreeDoc(doc);
611 
612    /* Set loaded. */
613    save_loaded = 1;
614 
615    return 0;
616 
617 err_doc:
618    xmlFreeDoc(doc);
619 err:
620    WARN("Savegame '%s' invalid!", file);
621    return -1;
622 }
623 
624 
625