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