1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 /**
6  * @file menu.h
7  *
8  * @brief Handles the important game menus.
9  */
10 
11 
12 #include "menu.h"
13 
14 #include "naev.h"
15 
16 #include "nstring.h"
17 
18 #include "SDL.h"
19 
20 #include "toolkit.h"
21 #include "tk/toolkit_priv.h" /* Needed for menu_main_resize */
22 #include "dialogue.h"
23 #include "log.h"
24 #include "pilot.h"
25 #include "space.h"
26 #include "player.h"
27 #include "mission.h"
28 #include "ntime.h"
29 #include "save.h"
30 #include "load.h"
31 #include "land.h"
32 #include "rng.h"
33 #include "nebula.h"
34 #include "pause.h"
35 #include "options.h"
36 #include "intro.h"
37 #include "music.h"
38 #include "map.h"
39 #include "nfile.h"
40 #include "info.h"
41 #include "comm.h"
42 #include "conf.h"
43 #include "dev_uniedit.h"
44 #include "gui.h"
45 #include "start.h"
46 #include "camera.h"
47 #include "board.h"
48 #include "ndata.h"
49 
50 
51 #define MAIN_WIDTH      130 /**< Main menu width. */
52 
53 #define MENU_WIDTH      130 /**< Escape menu width. */
54 #define MENU_HEIGHT     250 /**< Escape menu height. */
55 
56 
57 #define DEATH_WIDTH     130 /**< Death menu width. */
58 #define DEATH_HEIGHT    200 /**< Death menu height. */
59 
60 #define OPTIONS_WIDTH   360 /**< Options menu width. */
61 #define OPTIONS_HEIGHT  90  /**< Options menu height. */
62 
63 #define BUTTON_WIDTH    90 /**< Button width, standard across menus. */
64 #define BUTTON_HEIGHT   30 /**< Button height, standard across menus. */
65 
66 #define menu_Open(f)    (menu_open |= (f)) /**< Marks a menu as opened. */
67 #define menu_Close(f)   (menu_open &= ~(f)) /**< Marks a menu as closed. */
68 int menu_open = 0; /**< Stores the opened/closed menus. */
69 
70 
71 static glTexture *main_naevLogo = NULL; /**< Naev Logo texture. */
72 
73 
74 /*
75  * prototypes
76  */
77 /* Generic. */
78 static void menu_exit( unsigned int wid, char* str );
79 /* main menu */
80 static int menu_main_bkg_system (void);
81 static void main_menu_promptClose( unsigned int wid, char *unused );
82 static void menu_main_load( unsigned int wid, char* str );
83 static void menu_main_new( unsigned int wid, char* str );
84 static void menu_main_tutorial( unsigned int wid, char* str );
85 static void menu_main_credits( unsigned int wid, char* str );
86 static void menu_main_cleanBG( unsigned int wid, char* str );
87 /* small menu */
88 static void menu_small_close( unsigned int wid, char* str );
89 static void menu_small_info( unsigned int wid, char *str );
90 static void menu_small_exit( unsigned int wid, char* str );
91 static void exit_game (void);
92 /* death menu */
93 static void menu_death_continue( unsigned int wid, char* str );
94 static void menu_death_restart( unsigned int wid, char* str );
95 static void menu_death_main( unsigned int wid, char* str );
96 static void menu_death_close( unsigned int wid, char* str );
97 /* options button. */
98 static void menu_options_button( unsigned int wid, char *str );
99 
100 
menu_main_bkg_system(void)101 static int menu_main_bkg_system (void)
102 {
103    nsave_t *ns;
104    int n;
105    const char *sys;
106    Planet *pnt;
107    double cx, cy;
108 
109    /* Clean pilots. */
110    pilots_cleanAll();
111    sys = NULL;
112 
113    /* Refresh saves. */
114    load_refresh();
115 
116    /* Get start position. */
117    ns = load_getList( &n );
118    if ((n > 0) && (planet_exists( ns[0].planet ))) {
119       pnt = planet_get( ns[0].planet );
120       if (pnt != NULL) {
121          sys = planet_getSystem( ns[0].planet );
122          if (sys != NULL) {
123             cx = pnt->pos.x;
124             cy = pnt->pos.y;
125             cx += 300;
126             cy += 200;
127          }
128       }
129    }
130 
131    /* Fallback if necessary. */
132    if (sys == NULL) {
133       sys = start_system();
134       start_position( &cx, &cy );
135    }
136 
137    /* Initialize. */
138    space_init( sys );
139    cam_setTargetPos( cx, cy, 0 );
140    cam_setZoom( conf.zoom_far );
141    pause_setSpeed( 1. );
142 
143    return 0;
144 }
145 
146 
147 /**
148  * @brief Opens the main menu (titlescreen).
149  */
menu_main(void)150 void menu_main (void)
151 {
152    int offset_logo, offset_wdw, freespace;
153    unsigned int bwid, wid;
154    glTexture *tex;
155    int h, y;
156 
157    if (menu_isOpen(MENU_MAIN)) {
158       WARN("Menu main is already open.");
159       return;
160    }
161 
162    /* Clean up GUI - must be done before using SCREEN_W or SCREEN_H. */
163    gui_cleanup();
164    player_soundStop(); /* Stop sound. */
165 
166    /* Play load music. */
167    music_choose("load");
168 
169    /* Load background and friends. */
170    tex = gl_newImage( GFX_PATH"Naev.png", 0 );
171    main_naevLogo = tex;
172    menu_main_bkg_system();
173 
174    /* Set dimensions */
175    y  = 20 + (BUTTON_HEIGHT+20)*5;
176    h  = y + 80;
177    if (conf.devmode) {
178       h += BUTTON_HEIGHT + 20;
179       y += BUTTON_HEIGHT + 20;
180    }
181 
182    /* Calculate Logo and window offset. */
183    freespace = SCREEN_H - tex->sh - h;
184    if (freespace < 0) { /* Not enough freespace, this can get ugly. */
185       offset_logo = SCREEN_W - tex->sh;
186       offset_wdw  = 0;
187    }
188    /* Otherwise space evenly. */
189    else {
190       offset_logo = -freespace/4;
191       offset_wdw  = freespace/2;
192    }
193 
194    /* create background image window */
195    bwid = window_create( "BG", -1, -1, -1, -1 );
196    window_onClose( bwid, menu_main_cleanBG );
197    window_setBorder( bwid, 0 );
198    window_addImage( bwid, (SCREEN_W-tex->sw)/2., offset_logo, 0, 0, "imgLogo", tex, 0 );
199    window_addText( bwid, 0, 10, SCREEN_W, 30., 1, "txtBG", NULL,
200          &cWhite, naev_version(1) );
201 
202    /* create menu window */
203    wid = window_create( "Main Menu", -1, offset_wdw, MAIN_WIDTH, h );
204    window_setCancel( wid, main_menu_promptClose );
205 
206    /* Buttons. */
207    window_addButtonKey( wid, 20, y, BUTTON_WIDTH, BUTTON_HEIGHT,
208          "btnLoad", "Load Game", menu_main_load, SDLK_l );
209    y -= BUTTON_HEIGHT+20;
210    window_addButtonKey( wid, 20, y, BUTTON_WIDTH, BUTTON_HEIGHT,
211          "btnNew", "New Game", menu_main_new, SDLK_n );
212    y -= BUTTON_HEIGHT+20;
213    window_addButtonKey( wid, 20, y, BUTTON_WIDTH, BUTTON_HEIGHT,
214          "btnTutorial", "Tutorial", menu_main_tutorial, SDLK_t );
215    y -= BUTTON_HEIGHT+20;
216    if (conf.devmode) {
217       window_addButtonKey( wid, 20, y, BUTTON_WIDTH, BUTTON_HEIGHT,
218             "btnEditor", "Editor", uniedit_open, SDLK_e );
219       y -= BUTTON_HEIGHT+20;
220    }
221    window_addButtonKey( wid, 20, y, BUTTON_WIDTH, BUTTON_HEIGHT,
222          "btnOptions", "Options", menu_options_button, SDLK_o );
223    y -= BUTTON_HEIGHT+20;
224    window_addButtonKey( wid, 20, y, BUTTON_WIDTH, BUTTON_HEIGHT,
225          "btnCredits", "Credits", menu_main_credits, SDLK_c );
226    y -= BUTTON_HEIGHT+20;
227    window_addButtonKey( wid, 20, y, BUTTON_WIDTH, BUTTON_HEIGHT,
228          "btnExit", "Exit", menu_exit, SDLK_x );
229 
230    /* Disable load button if there are no saves. */
231    if (!save_hasSave())
232       window_disableButton( wid, "btnLoad" );
233 
234    /* Make the background window a child of the menu. */
235    window_setParent( bwid, wid );
236 
237    unpause_game();
238    menu_Open(MENU_MAIN);
239 }
240 
241 
242 /**
243  * @brief Resizes the main menu and its background.
244  *
245  * This is a one-off function that ensures the main menu's appearance
246  * is consistent regardless of window resizing.
247  */
menu_main_resize(void)248 void menu_main_resize (void)
249 {
250    int w, h, bgw, bgh, tw, th;
251    int offset_logo, offset_wdw, freespace;
252    int menu_id, bg_id;
253    Widget *wgt;
254 
255    if (!menu_isOpen(MENU_MAIN))
256       return;
257 
258    menu_id = window_get("Main Menu");
259    bg_id   = window_get("BG");
260 
261    window_dimWindow( menu_id, &w, &h );
262    window_dimWindow( bg_id, &bgw, &bgh );
263 
264    freespace = SCREEN_H - main_naevLogo->sh - h;
265    if (freespace < 0) {
266       offset_logo = SCREEN_H - main_naevLogo->sh;
267       offset_wdw  = 0;
268    }
269    else {
270       offset_logo = -freespace/4;
271       offset_wdw  = freespace/2;
272    }
273 
274    window_moveWidget( bg_id, "imgLogo",
275          (bgw - main_naevLogo->sw)/2., offset_logo );
276 
277    window_dimWidget( bg_id, "txtBG", &tw, &th );
278 
279    if (tw > SCREEN_W) {
280       /* RIP abstractions. X must be set manually because window_moveWidget
281        * transforms negative coordinates. */
282       wgt = window_getwgt( bg_id, "txtBG" );
283       if (wgt)
284          wgt->x = (SCREEN_W - tw) / 2;
285    }
286    else
287       window_moveWidget( bg_id, "txtBG", (SCREEN_W - tw)/2, 10. );
288 
289    window_move( menu_id, -1, offset_wdw );
290 }
291 
292 
293 /**
294  * @brief Main menu closing prompt.
295  */
main_menu_promptClose(unsigned int wid,char * unused)296 static void main_menu_promptClose( unsigned int wid, char *unused )
297 {
298    (void) wid;
299    (void) unused;
300    exit_game();
301 }
302 
303 
304 /**
305  * @brief Closes the main menu.
306  */
menu_main_close(void)307 void menu_main_close (void)
308 {
309    if (window_exists("Main Menu"))
310       window_destroy( window_get("Main Menu") );
311    else
312       WARN("Main menu does not exist.");
313 
314    menu_Close(MENU_MAIN);
315    pause_game();
316 }
317 /**
318  * @brief Function to active the load game menu.
319  *    @param str Unused.
320  */
menu_main_load(unsigned int wid,char * str)321 static void menu_main_load( unsigned int wid, char* str )
322 {
323    (void) str;
324    (void) wid;
325    load_loadGameMenu();
326 }
327 /**
328  * @brief Function to active the new game menu.
329  *    @param str Unused.
330  */
menu_main_new(unsigned int wid,char * str)331 static void menu_main_new( unsigned int wid, char* str )
332 {
333    (void) str;
334    (void) wid;
335 
336    /* Closes the main menu window. */
337    window_destroy( wid );
338    menu_Close(MENU_MAIN);
339    pause_game();
340 
341    /* Start the new player. */
342    player_new();
343 }
344 /**
345  * @brief Function to active the new game menu.
346  *    @param str Unused.
347  */
menu_main_tutorial(unsigned int wid,char * str)348 static void menu_main_tutorial( unsigned int wid, char* str )
349 {
350    (void) str;
351    (void) wid;
352    window_destroy( wid );
353    menu_Close(MENU_MAIN);
354    pause_game();
355    player_newTutorial();
356 }
357 /**
358  * @brief Function to exit the main menu and game.
359  *    @param str Unused.
360  */
menu_main_credits(unsigned int wid,char * str)361 static void menu_main_credits( unsigned int wid, char* str )
362 {
363    (void) str;
364    (void) wid;
365    intro_display( "AUTHORS", "credits" );
366    /* We'll need to start music again. */
367    music_choose("load");
368 }
369 /**
370  * @brief Function to exit the main menu and game.
371  *    @param str Unused.
372  */
menu_exit(unsigned int wid,char * str)373 static void menu_exit( unsigned int wid, char* str )
374 {
375    (void) str;
376    (void) wid;
377 
378    naev_quit();
379 }
380 /**
381  * @brief Function to clean up the background window.
382  *    @param wid Window to clean.
383  *    @param str Unused.
384  */
menu_main_cleanBG(unsigned int wid,char * str)385 static void menu_main_cleanBG( unsigned int wid, char* str )
386 {
387    (void) str;
388 
389    /*
390     * Ugly hack to prevent player.c from segfaulting due to the fact
391     * that game will attempt to render while waiting for the quit event
392     * pushed by exit_game() to be handled without actually having a player
393     * nor anything of the likes (nor toolkit to stop rendering) while
394     * not leaking any texture.
395     */
396    if (main_naevLogo != NULL)
397       gl_freeTexture(main_naevLogo);
398    main_naevLogo = NULL;
399    window_modifyImage( wid, "imgLogo", NULL, 0, 0 );
400 }
401 
402 
403 /*
404  *
405  * ingame menu
406  *
407  */
408 /**
409  * @brief Opens the small ingame menu.
410  */
menu_small(void)411 void menu_small (void)
412 {
413    unsigned int wid;
414 
415    /* Check if menu should be openable. */
416    if ((player.p == NULL) || player_isFlag(PLAYER_DESTROYED) ||
417          pilot_isFlag(player.p,PILOT_DEAD) ||
418          comm_isOpen() ||
419          dialogue_isOpen() || /* Shouldn't open over dialogues. */
420          (menu_isOpen(MENU_MAIN) ||
421             menu_isOpen(MENU_SMALL) ||
422             menu_isOpen(MENU_DEATH) ))
423       return;
424 
425    wid = window_create( "Menu", -1, -1, MENU_WIDTH, MENU_HEIGHT );
426 
427    window_setCancel( wid, menu_small_close );
428 
429    window_addButtonKey( wid, 20, 20 + BUTTON_HEIGHT*3 + 20*3,
430          BUTTON_WIDTH, BUTTON_HEIGHT,
431          "btnResume", "Resume", menu_small_close, SDLK_r );
432    window_addButtonKey( wid, 20, 20 + BUTTON_HEIGHT*2 + 20*2,
433          BUTTON_WIDTH, BUTTON_HEIGHT,
434          "btnInfo", "Info", menu_small_info, SDLK_i );
435    window_addButtonKey( wid, 20, 20 + BUTTON_HEIGHT + 20,
436          BUTTON_WIDTH, BUTTON_HEIGHT,
437          "btnOptions", "Options", menu_options_button, SDLK_o );
438    window_addButtonKey( wid, 20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
439          "btnExit", "Exit", menu_small_exit, SDLK_x );
440 
441    menu_Open(MENU_SMALL);
442 }
443 
444 
445 /**
446  * @brief Closes the small ingame menu.
447  *    @param str Unused.
448  */
menu_small_close(unsigned int wid,char * str)449 static void menu_small_close( unsigned int wid, char* str )
450 {
451    (void)str;
452    window_destroy( wid );
453    menu_Close(MENU_SMALL);
454 }
455 
456 
457 /**
458  * @brief Opens the info window.
459  *    @param wid Unused.
460  *    @param str Unused.
461  */
menu_small_info(unsigned int wid,char * str)462 static void menu_small_info( unsigned int wid, char *str )
463 {
464    (void) str;
465    (void) wid;
466 
467    menu_info( INFO_MAIN );
468 }
469 
470 /**
471  * @brief Closes the small ingame menu and goes back to the main menu.
472  *    @param str Unused.
473  */
menu_small_exit(unsigned int wid,char * str)474 static void menu_small_exit( unsigned int wid, char* str )
475 {
476    (void) str;
477    unsigned int info_wid, board_wid;
478 
479    /* if landed we must save anyways */
480    if (landed) {
481       save_all();
482       land_cleanup();
483    }
484 
485    /* Close info menu if open. */
486    if (menu_isOpen(MENU_INFO)) {
487       info_wid = window_get("Info");
488       window_destroy( info_wid );
489       menu_Close(MENU_INFO);
490    }
491 
492    /* Force unboard. */
493    if (player_isBoarded()) {
494       board_wid = window_get("Boarding");
495       board_exit(board_wid, NULL);
496    }
497 
498    /* Stop player sounds because sometimes they hang. */
499    player_restoreControl( 0, "Exited game." );
500    player_soundStop();
501 
502    /* Clean up. */
503    window_destroy( wid );
504    menu_Close(MENU_SMALL);
505    menu_main();
506 }
507 
508 
509 /**
510  * @brief Exits the game.
511  */
exit_game(void)512 static void exit_game (void)
513 {
514    /* if landed we must save anyways */
515    if (landed) {
516       save_all();
517       land_cleanup();
518    }
519    SDL_Event quit;
520    quit.type = SDL_QUIT;
521    SDL_PushEvent(&quit);
522 }
523 
524 
525 /**
526  * @brief Reload the current savegame, when player want to continue after death
527  */
menu_death_continue(unsigned int wid,char * str)528 static void menu_death_continue( unsigned int wid, char* str )
529 {
530    (void) str;
531 
532    window_destroy( wid );
533    menu_Close(MENU_DEATH);
534 
535    save_reload();
536 }
537 
538 /**
539  * @brief Restart the game, when player want to continue after death but without a savegame
540  */
menu_death_restart(unsigned int wid,char * str)541 static void menu_death_restart( unsigned int wid, char* str )
542 {
543    (void) str;
544 
545    window_destroy( wid );
546    menu_Close(MENU_DEATH);
547 
548    if (player_isTut())
549       player_newTutorial();
550    else
551       player_new();
552 }
553 
554 /**
555  * @brief Player death menu, appears when player got creamed.
556  */
menu_death(void)557 void menu_death (void)
558 {
559    unsigned int wid;
560    char path[PATH_MAX];
561 
562    wid = window_create( "Death", -1, -1, DEATH_WIDTH, DEATH_HEIGHT );
563    window_onClose( wid, menu_death_close );
564 
565    /* Allow the player to continue if the savegame exists, if not, propose to restart */
566    nsnprintf(path, PATH_MAX, "%ssaves/%s.ns", nfile_dataPath(), player.name);
567    if (!player_isTut() && nfile_fileExists(path))
568       window_addButtonKey( wid, 20, 20 + BUTTON_HEIGHT*2 + 20*2, BUTTON_WIDTH, BUTTON_HEIGHT,
569          "btnContinue", "Continue", menu_death_continue, SDLK_c );
570    else
571       window_addButtonKey( wid, 20, 20 + BUTTON_HEIGHT*2 + 20*2, BUTTON_WIDTH, BUTTON_HEIGHT,
572          "btnRestart", "Restart", menu_death_restart, SDLK_r );
573 
574    window_addButtonKey( wid, 20, 20 + (BUTTON_HEIGHT+20),
575          BUTTON_WIDTH, BUTTON_HEIGHT,
576          "btnMain", "Main Menu", menu_death_main, SDLK_m );
577    window_addButtonKey( wid, 20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
578          "btnExit", "Exit Game", menu_exit, SDLK_x );
579    menu_Open(MENU_DEATH);
580 
581    /* Makes it all look cooler since everything still goes on. */
582    unpause_game();
583 }
584 /**
585  * @brief Closes the player death menu.
586  *    @param str Unused.
587  */
menu_death_main(unsigned int wid,char * str)588 static void menu_death_main( unsigned int wid, char* str )
589 {
590    (void) str;
591 
592    window_destroy( wid );
593    menu_Close(MENU_DEATH);
594 
595    /* Game will repause now since toolkit closes and reopens. */
596    menu_main();
597 }
598 /**
599  * @brief Hack to get around the fact the death menu unpauses the game.
600  */
menu_death_close(unsigned int wid,char * str)601 static void menu_death_close( unsigned int wid, char* str )
602 {
603    (void) wid;
604    (void) str;
605    pause_game(); /* Repause the game. */
606 }
607 
608 
609 /**
610  * @brief Opens the menu options from a button.
611  */
menu_options_button(unsigned int wid,char * str)612 static void menu_options_button( unsigned int wid, char *str )
613 {
614    (void) wid;
615    (void) str;
616    opt_menu();
617 }
618 
619 
620 /**
621  * @brief Menu to ask if player really wants to quit.
622  */
menu_askQuit(void)623 int menu_askQuit (void)
624 {
625    /* Asked twice, quit. */
626    if (menu_isOpen( MENU_ASKQUIT )) {
627       exit_game();
628       return 1;
629    }
630 
631    /* Ask if should quit. */
632    menu_Open( MENU_ASKQUIT );
633    if (dialogue_YesNoRaw( "Quit Naev", "Are you sure you want to quit Naev?" )) {
634       exit_game();
635       return 1;
636    }
637    menu_Close( MENU_ASKQUIT );
638 
639    return 0;
640 }
641 
642