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