1 /*
2  * nlarn.c
3  * Copyright (C) 2009-2020 Joachim de Groot <jdegroot@web.de>
4  *
5  * This program is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /* setres[gu]id() from unistd.h are only defined when this is set
20    *before* including it. Another header in the list seems to do so
21    before we include it here.*/
22 #ifdef __linux__
23 # ifndef _GNU_SOURCE
24 #  define _GNU_SOURCE
25 # endif
26 #endif
27 
28 #include <ctype.h>
29 #include <stdlib.h>
30 #include <glib/gstdio.h>
31 
32 #ifdef __unix
33 # include <signal.h>
34 # include <unistd.h>
35 # include <sys/file.h>
36 # include <sys/stat.h>
37 #endif
38 
39 #include "config.h"
40 #include "container.h"
41 #include "display.h"
42 #include "game.h"
43 #include "nlarn.h"
44 #include "pathfinding.h"
45 #include "player.h"
46 #include "scoreboard.h"
47 #include "sobjects.h"
48 #include "traps.h"
49 #include "extdefs.h"
50 
51 /* see https://stackoverflow.com/q/36764885/1519878 */
52 #define _STR(x) #x
53 #define STR(x) _STR(x)
54 
55 /* version string */
56 const char *nlarn_version = STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) GITREV;
57 
58 /* empty scoreboard description */
59 const char *room_for_improvement = "\n...room for improvement...\n";
60 
61 /* path and file name constants*/
62 static const char *default_lib_dir = "/usr/share/nlarn";
63 #if ((defined (__unix) || defined (__unix__)) && defined (SETGID))
64 static const char *default_var_dir = "/var/games/nlarn";
65 #endif
66 static const char *mesgfile = "nlarn.msg";
67 static const char *helpfile = "nlarn.hlp";
68 static const char *mazefile = "maze";
69 static const char *fortunes = "fortune";
70 static const char *highscores = "highscores";
71 static const char *config_file = "nlarn.ini";
72 static const char *save_file = "nlarn.sav";
73 
74 /* global game object */
75 game *nlarn = NULL;
76 
77 /* the game settings */
78 static struct game_config config = {};
79 
80 /* death jump buffer - used to return to the main loop when the player has died */
81 jmp_buf nlarn_death_jump;
82 
83 static gboolean adjacent_corridor(position pos, char mv);
84 
85 #ifdef __unix
86 static void nlarn_signal_handler(int signo);
87 #endif
88 
nlarn_userdir()89 static const gchar *nlarn_userdir()
90 {
91     static gchar *userdir = NULL;
92 
93     if (userdir == NULL)
94     {
95         if (config.userdir)
96         {
97             if (!g_file_test(config.userdir, G_FILE_TEST_IS_DIR))
98             {
99                 g_printerr("Supplied user directory \"%s\" does not exist.",
100                         config.userdir);
101 
102                 exit(EXIT_FAILURE);
103             }
104             else
105             {
106                 userdir = g_strdup(config.userdir);
107             }
108         }
109         else
110         {
111 #ifdef G_OS_WIN32
112             userdir = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(),
113                     "nlarn", NULL);
114 #else
115             userdir = g_build_path(G_DIR_SEPARATOR_S, g_get_home_dir(),
116                     ".nlarn", NULL);
117 #endif
118         }
119     }
120 
121     return userdir;
122 }
123 
124 /* initialize runtime environment */
nlarn_init(int argc,char * argv[])125 static void nlarn_init(int argc, char *argv[])
126 {
127     /* determine paths and file names */
128     /* base directory for a local install */
129     g_autofree char *basedir = g_path_get_dirname(argv[0]);
130 
131     /* try to use the directory below the binary's location first */
132     nlarn_libdir = g_build_path(G_DIR_SEPARATOR_S, basedir, "lib", NULL);
133 
134     if (!g_file_test(nlarn_libdir, G_FILE_TEST_IS_DIR))
135     {
136         /* local lib directory could not be found, try the system wide directory. */
137 #ifdef __APPLE__
138         char *rellibdir = g_build_path(G_DIR_SEPARATOR_S, basedir,
139                                        "../Resources", NULL);
140 #endif
141         if (g_file_test(default_lib_dir, G_FILE_TEST_IS_DIR))
142         {
143             /* system-wide data directory exists */
144             /* string has to be dup'd as it is feed in the end */
145             nlarn_libdir = g_strdup((char *)default_lib_dir);
146         }
147 #ifdef __APPLE__
148         else if (g_file_test(rellibdir, G_FILE_TEST_IS_DIR))
149         {
150             /* program seems to be installed relocatable */
151             nlarn_libdir = g_strdup(rellibdir);
152         }
153 #endif
154         else
155         {
156             g_printerr("Could not find game library directory.\n\n"
157                        "Paths I've tried:\n"
158                        " * %s\n"
159 #ifdef __APPLE__
160                        " * %s\n"
161 #endif
162                        " * %s\n\n"
163                        "Please reinstall the game.\n",
164                        nlarn_libdir,
165 #ifdef __APPLE__
166                        rellibdir,
167 #endif
168                        default_lib_dir);
169 
170             exit(EXIT_FAILURE);
171         }
172     }
173 
174     nlarn_mesgfile = g_build_filename(nlarn_libdir, mesgfile, NULL);
175     nlarn_helpfile = g_build_filename(nlarn_libdir, helpfile, NULL);
176     nlarn_mazefile = g_build_filename(nlarn_libdir, mazefile, NULL);
177     nlarn_fortunes = g_build_filename(nlarn_libdir, fortunes, NULL);
178 
179 #if ((defined (__unix) || defined (__unix__)) && defined (SETGID))
180     /* highscore file handling for SETGID builds */
181     gid_t realgid;
182     uid_t realuid;
183 
184     /* assemble the scoreboard filename */
185     nlarn_highscores = g_build_path(G_DIR_SEPARATOR_S, default_var_dir,
186                                               highscores, NULL);
187 
188     /* Open the scoreboard file. */
189     if ((scoreboard_fd = open(nlarn_highscores, O_RDWR)) == -1)
190     {
191         perror("Could not open scoreboard file");
192         exit(EXIT_FAILURE);
193     }
194 
195     /* Figure out who we really are. */
196     realgid = getgid();
197     realuid = getuid();
198 
199     /* This is where we drop our setuid/setgid privileges. */
200 #ifdef __linux__
201     if (setresgid(-1, realgid, realgid) != 0) {
202 #else
203     if (setregid(-1, realgid) != 0) {
204 #endif
205         perror("Could not drop setgid privileges");
206         exit(EXIT_FAILURE);
207     }
208 
209 #ifdef __linux__
210     if (setresuid(-1, realuid, realuid) != 0) {
211 #else
212     if (setreuid(-1, realuid) != 0) {
213 #endif
214         perror("Could not drop setuid privileges");
215         exit(EXIT_FAILURE);
216     }
217 
218 #else
219     /* highscore file handling for non-SETGID builds -
220        store high scores in the same directory as the configuation */
221     nlarn_highscores = g_build_filename(nlarn_userdir(), highscores, NULL);
222 #endif
223 
224     /* parse the command line options */
225     parse_commandline(argc, argv, &config);
226 
227     /* show version information */
228     if (config.show_version) {
229         g_printf("NLarn version %s, built on %s.\n\n", nlarn_version, __DATE__);
230         g_printf("Game lib directory:\t%s\n", nlarn_libdir);
231         g_printf("Game savefile version:\t%d\n", SAVEFILE_VERSION);
232 
233         exit(EXIT_SUCCESS);
234     }
235 
236     /* show highscores */
237     if (config.show_scores) {
238         GList *scores = scores_load();
239         g_autofree char *s = scores_to_string(scores, NULL);
240 
241         g_printf("NLarn Hall of Fame\n==================\n%s",
242                 scores ? s : room_for_improvement);
243         scores_destroy(scores);
244 
245         exit(EXIT_SUCCESS);
246     }
247 
248     /* verify that user directory exists */
249     if (!g_file_test(nlarn_userdir(), G_FILE_TEST_IS_DIR))
250     {
251         /* directory is missing -> create it */
252         int ret = g_mkdir(nlarn_userdir(), 0755);
253 
254         if (ret == -1)
255         {
256             /* creating the directory failed */
257             g_printerr("Failed to create directory %s.", nlarn_userdir());
258             exit(EXIT_FAILURE);
259         }
260     }
261 
262     /* try loading settings from the default configuration file */
263     nlarn_inifile = g_build_path(G_DIR_SEPARATOR_S, nlarn_userdir(),
264             config_file, NULL);
265 
266     /* write a default configuration file, if none exists */
267     if (!g_file_test(nlarn_inifile, G_FILE_TEST_IS_REGULAR))
268     {
269         write_ini_file(nlarn_inifile, NULL);
270     }
271 
272     /* try to load settings from the configuration file */
273     parse_ini_file(nlarn_inifile, &config);
274 
275 #ifdef SDLPDCURSES
276     /* If a font size was defined, export it to the environment
277      * before initialising PDCurses. */
278     if (config.font_size)
279     {
280         gchar size[4];
281         g_snprintf(size, 3, "%d", config.font_size);
282         g_setenv("PDC_FONT_SIZE", size, TRUE);
283     }
284 #endif
285     /* initialise the display - must not happen before this point
286        otherwise displaying the command line help fails */
287     display_init();
288 
289     /* call display_shutdown when terminating the game */
290     atexit(display_shutdown);
291 
292     /* assemble the save file name */
293     nlarn_savefile = g_build_path(G_DIR_SEPARATOR_S, nlarn_userdir(),
294             save_file, NULL);
295 
296     /* set the console shutdown handler */
297 #ifdef __unix
298     signal(SIGTERM, nlarn_signal_handler);
299     signal(SIGHUP, nlarn_signal_handler);
300 #endif
301 }
302 
303 static void mainloop()
304 {
305     /* count of moves used by last action */
306     int moves_count = 0;
307 
308     /* used to read in e.g. the help file */
309     gchar *strbuf;
310 
311     /* position to examine / to travel to */
312     position pos = pos_invalid;
313 
314     /* position chosen for auto travel, allowing to continue travel */
315     position cpos = pos_invalid;
316 
317     char run_cmd = 0;
318     int ch = 0;
319     gboolean adj_corr = FALSE;
320     guint end_resting = 0;
321 
322     /* main event loop
323        keep running until the game object was destroyed */
324     while (nlarn)
325     {
326         /* repaint screen */
327         display_paint_screen(nlarn->p);
328 
329         if (pos_valid(pos))
330         {
331             /* travel mode */
332 
333             /* check if travel mode shall be aborted:
334                attacked or fell through trap door */
335             if (nlarn->p->attacked || player_adjacent_monster(nlarn->p, FALSE)
336                 || Z(pos) != Z(nlarn->p->pos))
337             {
338                 pos = pos_invalid;
339             }
340             else if (pos_adjacent(nlarn->p->pos, pos))
341             {
342                 /* the target has almost been reached. This is the last move. */
343                 moves_count = player_move(nlarn->p, pos_dir(nlarn->p->pos, pos), TRUE);
344                 /* reset the target position */
345                 pos = cpos = pos_invalid;
346             }
347             else
348             {
349                 /* find a path to the destination */
350                 path *path = path_find(game_map(nlarn, Z(nlarn->p->pos)),
351                                        nlarn->p->pos, pos, LE_GROUND);
352 
353                 if (path && !g_queue_is_empty(path->path))
354                 {
355                     /* Path found. Move the player. */
356                     path_element *el = g_queue_pop_head(path->path);
357                     moves_count = player_move(nlarn->p, pos_dir(nlarn->p->pos, el->pos), TRUE);
358 
359                     if (moves_count == 0)
360                     {
361                         /* for some reason movement is impossible, therefore
362                            stop auto travel. */
363                         pos = pos_invalid;
364                     }
365                 }
366                 else
367                 {
368                     /* No path found. Stop traveling */
369                     pos = pos_invalid;
370                 }
371 
372                 /* clean up */
373                 if (path) path_destroy(path);
374             }
375         }
376         else if (run_cmd != 0)
377         {
378             /* run mode */
379             ch = run_cmd;
380             // Check if we're in open surroundings.
381             adj_corr = adjacent_corridor(nlarn->p->pos, ch);
382         }
383         else
384         {
385             /* not running or travelling, get a key and handle it */
386             ch = display_getch(NULL);
387 
388             if (ch == '/' || ch == 'g')
389             {
390                 /* fast movement: get direction of movement */
391                 ch = display_getch(NULL);
392                 switch (ch)
393                 {
394                 case 'b':
395                 case KEY_END:
396                 case KEY_C1:
397                 case '1':
398                     ch = 'B';
399                     break;
400 
401                 case 'j':
402                 case KEY_DOWN:
403 #ifdef KEY_C2
404                 case KEY_C2:
405 #endif
406                 case '2':
407                     ch = 'J';
408                     break;
409 
410                 case 'n':
411                 case KEY_NPAGE:
412                 case KEY_C3:
413                 case '3':
414                     ch = 'N';
415                     break;
416 
417                 case 'h':
418                 case KEY_LEFT:
419 #ifdef KEY_B1
420                 case KEY_B1:
421 #endif
422                 case '4':
423                     ch = 'H';
424                     break;
425 
426                 case '5':
427                 case KEY_B2:
428                     ch = 'w';
429                     break;
430 
431                 case 'l':
432                 case KEY_RIGHT:
433 #ifdef KEY_B3
434                 case KEY_B3:
435 #endif
436                 case '6':
437                     ch = 'L';
438                     break;
439 
440                 case 'y':
441                 case KEY_HOME:
442                 case KEY_A1:
443                 case '7':
444                     ch = 'Y';
445                     break;
446 
447                 case 'k':
448                 case KEY_UP:
449 #ifdef KEY_A2
450                 case KEY_A2:
451 #endif
452                 case '8':
453                     ch = 'K';
454                     break;
455 
456                 case 'u':
457                 case KEY_PPAGE:
458                 case KEY_A3:
459                 case '9':
460                     ch = 'U';
461                     break;
462                 }
463             }
464 
465             switch (ch)
466             {
467             case 'H':
468             case 'J':
469             case 'K':
470             case 'L':
471             case 'Y':
472             case 'U':
473             case 'B':
474             case 'N':
475                 ch = tolower(ch);
476                 run_cmd = ch;
477                 adj_corr = adjacent_corridor(nlarn->p->pos, ch);
478                 break;
479             case 'w': /* rest up to 1 mobul */
480                 ch = '.';
481                 run_cmd = ch;
482                 end_resting = game_turn(nlarn) + 100;
483                 break;
484             }
485         }
486 
487         /* get key and analyse it */
488         switch (ch)
489         {
490             /* *** MOVEMENT *** */
491         case 'h':
492         case '4':
493         case KEY_LEFT:
494 #ifdef KEY_B1
495         case KEY_B1:
496 #endif
497             moves_count = player_move(nlarn->p, GD_WEST, run_cmd == 0);
498             break;
499 
500         case 'y':
501         case '7':
502         case KEY_HOME:
503         case KEY_A1:
504             moves_count = player_move(nlarn->p, GD_NW, run_cmd == 0);
505             break;
506 
507         case 'l':
508         case '6':
509         case KEY_RIGHT:
510 #ifdef KEY_B3
511         case KEY_B3:
512 #endif
513             moves_count = player_move(nlarn->p, GD_EAST, run_cmd == 0);
514             break;
515 
516         case 'n':
517         case '3':
518         case KEY_NPAGE:
519         case KEY_C3:
520             moves_count = player_move(nlarn->p, GD_SE, run_cmd == 0);
521             break;
522 
523         case 'k':
524         case '8':
525         case KEY_UP:
526 #ifdef KEY_A2
527         case KEY_A2:
528 #endif
529             moves_count = player_move(nlarn->p, GD_NORTH, run_cmd == 0);
530             break;
531 
532         case 'u':
533         case '9':
534         case KEY_PPAGE:
535         case KEY_A3:
536             moves_count = player_move(nlarn->p, GD_NE, run_cmd == 0);
537             break;
538 
539         case 'j':
540         case '2':
541         case KEY_DOWN:
542 #ifdef KEY_C2
543         case KEY_C2:
544 #endif
545             moves_count = player_move(nlarn->p, GD_SOUTH, run_cmd == 0);
546             break;
547 
548         case 'b':
549         case '1':
550         case KEY_END:
551         case KEY_C1:
552             moves_count = player_move(nlarn->p, GD_SW, run_cmd == 0);
553             break;
554 
555             /* look at current position */
556         case ':':
557             strbuf = map_pos_examine(nlarn->p->pos);
558             log_add_entry(nlarn->log, strbuf);
559             g_free(strbuf);
560             break;
561 
562             /* look at different position */
563         case ';':
564             if (!player_effect(nlarn->p, ET_BLINDNESS))
565                 (void)display_get_new_position(nlarn->p, nlarn->p->pos,
566                         "Choose a position to examine", FALSE, FALSE,
567                         FALSE, 0, FALSE, TRUE);
568             else
569                 log_add_entry(nlarn->log, "You can't look around "
570                         "while blinded!");
571             break;
572 
573             /* pick up */
574         case ',':
575             player_pickup(nlarn->p);
576             break;
577 
578             /* sit and wait */
579         case '5':
580         case '.':
581         case KEY_B2:
582             moves_count = 1;
583             break;
584 
585             /* help */
586         case KEY_F(1):
587         case '?':
588             if (g_file_get_contents(nlarn_helpfile, &strbuf, NULL, NULL))
589             {
590                 display_show_message("Help for the game of NLarn", strbuf, 1);
591                 g_free(strbuf);
592             }
593             else
594             {
595                 display_show_message("Error",
596                                      "\n The help file could not be found. \n", 0);
597             }
598             break;
599 
600             /* go down stairs / enter a building */
601         case '>':
602             if (!(moves_count = player_stairs_down(nlarn->p)))
603                 moves_count = player_building_enter(nlarn->p);
604             break;
605 
606             /* go up stairs */
607         case '<':
608             moves_count = player_stairs_up(nlarn->p);
609             break;
610 
611             /* bank account information */
612         case '$':
613             log_add_entry(nlarn->log, "There %s %s gold on your bank account.",
614                           is_are(nlarn->p->bank_account),
615                           int2str(nlarn->p->bank_account));
616             break;
617 
618         case '\\':
619             if ((strbuf = player_item_identified_list(nlarn->p)))
620             {
621                 display_show_message("Identified items", strbuf, 0);
622                 g_free(strbuf);
623             }
624             else
625             {
626                 log_add_entry(nlarn->log, "You have not discovered any item yet.");
627             }
628             break;
629 
630             /* desecrate altar */
631         case 'A':
632             moves_count = player_altar_desecrate(nlarn->p);
633             break;
634 
635             /* continue auto travel */
636         case 'C':
637             /* delete last auto travel target if it was on another map */
638             if (Z(cpos) != Z(nlarn->p->pos))
639             {
640                 cpos = pos_invalid;
641             }
642 
643             if (pos_valid(cpos))
644             {
645                 /* restore last known auto travel position */
646                 pos = cpos;
647                 /* reset keyboard input */
648                 ch = 0;
649             }
650             else
651                 log_add_entry(nlarn->log, "No travel destination known.");
652             break;
653 
654             /* close door */
655         case 'c':
656             moves_count = player_door_close(nlarn->p);
657             break;
658 
659             /* disarm a trapped container or a trap */
660         case 'D':
661             if (!container_untrap(nlarn->p))
662                 moves_count = trap_disarm(nlarn->p);
663             break;
664 
665             /* drop something */
666         case 'd':
667             player_drop(nlarn->p);
668             break;
669 
670             /* wash at fountain */
671         case 'F':
672             moves_count = player_fountain_wash(nlarn->p);
673             break;
674 
675             /* fire a ranged weapon */
676         case 'f':
677             moves_count = weapon_fire(nlarn->p);
678             break;
679 
680             /* display inventory */
681         case 'i':
682             player_inv_display(nlarn->p);
683             break;
684 
685             /* work magic */
686         case 'm':
687             moves_count = spell_cast_new(nlarn->p);
688             break;
689 
690             /* recast previous spell */
691         case 'M':
692             moves_count = spell_cast_previous(nlarn->p);
693             break;
694 
695             /* open door / container */
696         case 'o':
697             if (inv_length_filtered(*map_ilist_at(game_map(nlarn, Z(nlarn->p->pos)),
698                                                   nlarn->p->pos),
699                                     &item_filter_container) > 0)
700             {
701                 container_open(nlarn->p, NULL, NULL);
702             }
703             else
704             {
705                 moves_count = player_door_open(nlarn->p, GD_NONE);
706             }
707             break;
708 
709             /* pray at altar */
710         case 'p':
711             moves_count = player_altar_pray(nlarn->p);
712             break;
713 
714         case 'P':
715             if (nlarn->p->outstanding_taxes)
716             {
717                 log_add_entry(nlarn->log, "You presently owe %d gold in taxes.",
718                               nlarn->p->outstanding_taxes);
719             }
720             else
721                 log_add_entry(nlarn->log, "You do not owe any taxes.");
722             break;
723 
724             /* drink a potion or from a fountain */
725         case 'q':
726         {
727             sobject_t ms = map_sobject_at(game_map(nlarn, Z(nlarn->p->pos)),
728                                               nlarn->p->pos);
729 
730             if ((ms == LS_FOUNTAIN || ms == LS_DEADFOUNTAIN)
731                     && display_get_yesno("There is a fountain here, drink from it?",
732                                          NULL, NULL, NULL))
733             {
734                 moves_count = player_fountain_drink(nlarn->p);
735             }
736             else
737             {
738                 player_quaff(nlarn->p);
739             }
740         }
741         break;
742 
743             /* remove gems from throne */
744         case 'R':
745             moves_count = player_throne_pillage(nlarn->p);
746             break;
747 
748             /* read something */
749         case 'r':
750             player_read(nlarn->p);
751             break;
752 
753             /* sit on throne */
754         case 'S':
755             moves_count = player_throne_sit(nlarn->p);
756             break;
757 
758             /* search */
759         case 's':
760             player_search(nlarn->p);
761             break;
762 
763             /* take off something */
764         case 'T':
765             player_take_off(nlarn->p);
766             break;
767 
768             /* throw a potion */
769         case 't':
770             moves_count = potion_throw(nlarn->p);
771             break;
772 
773             /* voyage (travel) */
774         case 'V':
775             pos = display_get_new_position(nlarn->p, cpos,
776                                            "Choose a destination to travel to.",
777                                            FALSE, FALSE, TRUE, 0, TRUE, FALSE);
778 
779             if (pos_valid(pos))
780             {
781                 /* empty key input buffer to avoid recurring position queries */
782                 ch = 0;
783                 /* store position for resuming travel */
784                 cpos = pos;
785             }
786             else
787             {
788                 log_add_entry(nlarn->log, "Aborted.");
789             }
790             break;
791 
792         case 'v':
793             log_add_entry(nlarn->log, "NLarn version %s, built on %s.", nlarn_version, __DATE__);
794             break;
795 
796             /* wear/wield something */
797         case 'W':
798             player_equip(nlarn->p);
799             break;
800 
801             /* swap weapons */
802         case 'x':
803             weapon_swap(nlarn->p);
804             break;
805 
806             /* configure auto-pickup */
807         case 1: /* ^A */
808             {
809                 display_config_autopickup(nlarn->p->settings.auto_pickup);
810                 char *settings = verbose_autopickup_settings(nlarn->p->settings.auto_pickup);
811 
812                 if (!settings)
813                 {
814                     log_add_entry(nlarn->log, "Auto-pickup is not enabled.");
815                 }
816                 else
817                 {
818                     log_add_entry(nlarn->log, "Auto-pickup is enabled for %s.", settings);
819                     g_free(settings);
820                 }
821             }
822             break;
823 
824             /* show stationary object memory */
825         case 4: /* ^D */
826             player_list_sobjmem(nlarn->p);
827             break;
828 
829             /* "paper doll" */
830         case KEY_TAB:
831             player_paperdoll(nlarn->p);
832             break;
833 
834             /* redraw screen */
835         case 12: /* ^L */
836 #ifdef SDLPDCURSES
837         case KEY_RESIZE: /* SDL window size event */
838 #endif
839             clear();
840             display_draw();
841             break;
842 
843             /* configure defaults */
844         case 16: /* ^P */
845             configure_defaults(nlarn_inifile);
846             break;
847 
848             /* quit */
849         case 17: /* ^Q */
850             if (display_get_yesno("Are you sure you want to quit?", NULL, NULL, NULL))
851                 player_die(nlarn->p, PD_QUIT, 0);
852             break;
853 
854             /* message log browser */
855         case 18: /* ^R */
856             display_show_history(nlarn->log, "Message history");
857             break;
858 
859             /* save */
860         case 19: /* ^S */
861             if (game_save(nlarn))
862             {
863                 /* only terminate the game if saving was successful */
864                 nlarn = game_destroy(nlarn);
865 
866                 /* return control to the main function and
867                    indicate our desire to quit the game */
868                 longjmp(nlarn_death_jump, PD_QUIT);
869             }
870             break;
871 
872             /* enable wizard mode */
873         case 23: /* ^W */
874             if (!game_wizardmode(nlarn))
875             {
876                 if (display_get_yesno("Are you sure you want to switch to Wizard mode?\n" \
877                                       "You will not be able to switch back to normal " \
878                                       "gameplay and your score will not be counted.", NULL, NULL, NULL))
879                 {
880                     game_wizardmode(nlarn) = TRUE;
881                     log_add_entry(nlarn->log, "Wizard mode has been activated.");
882                 }
883             }
884             else
885             {
886                 log_add_entry(nlarn->log, "Wizard mode is already enabled.");
887             }
888             break;
889 
890             /* *** DEBUGGING SUPPORT *** */
891 
892             /* toggle visibility of entire map in wizard mode */
893         case 22: /* ^V */
894             if (game_wizardmode(nlarn))
895                 game_fullvis(nlarn) = (!game_fullvis(nlarn));
896             break;
897 
898         case '*':
899             if (game_wizardmode(nlarn)) nlarn->p->bank_account += 1000;
900             break;
901 
902         case '+': /* map up */
903             if (game_wizardmode(nlarn) && (Z(nlarn->p->pos) > 0))
904             {
905                 moves_count = player_map_enter(nlarn->p, game_map(nlarn, Z(nlarn->p->pos) - 1),
906                                                Z(nlarn->p->pos) == MAP_CMAX);
907             }
908             break;
909 
910         case '-': /* map down */
911             if (game_wizardmode(nlarn) && (Z(nlarn->p->pos) < (MAP_MAX - 1)))
912             {
913                 moves_count = player_map_enter(nlarn->p, game_map(nlarn, Z(nlarn->p->pos) + 1),
914                                                Z(nlarn->p->pos) == MAP_CMAX - 1);
915             }
916             break;
917 
918         case 20: /* (^T) intra-level teleport */
919             if (game_wizardmode(nlarn))
920             {
921                 pos = display_get_new_position(nlarn->p, nlarn->p->pos,
922                                                "Choose a position to teleport to.",
923                                                FALSE, FALSE, TRUE, 0, TRUE, FALSE);
924 
925                 if (pos_valid(pos))
926                 {
927                     effect *e;
928                     if ((e = player_effect_get(nlarn->p, ET_TRAPPED)))
929                         player_effect_del(nlarn->p, e);
930 
931                     nlarn->p->pos = pos;
932 
933                     /* reset pos, otherwise auto travel would be enabled */
934                     pos = pos_invalid;
935                 }
936             }
937             break;
938 
939             /* gain experience level */
940         case 24:  /* ^X */
941             if (game_wizardmode(nlarn))
942                 player_level_gain(nlarn->p, 1);
943 
944             break;
945 
946         case '&': /* instaheal */
947             if (game_wizardmode(nlarn))
948             {
949                 nlarn->p->hp = nlarn->p->hp_max;
950                 nlarn->p->mp = nlarn->p->mp_max;
951 
952                 /* clear some nasty effects */
953                 effect *e;
954                 if ((e = player_effect_get(nlarn->p, ET_PARALYSIS)))
955                     player_effect_del(nlarn->p, e);
956                 if ((e = player_effect_get(nlarn->p, ET_CONFUSION)))
957                     player_effect_del(nlarn->p, e);
958                 if ((e = player_effect_get(nlarn->p, ET_BLINDNESS)))
959                     player_effect_del(nlarn->p, e);
960                 if ((e = player_effect_get(nlarn->p, ET_POISON)))
961                     player_effect_del(nlarn->p, e);
962                 if ((e = player_effect_get(nlarn->p, ET_TRAPPED)))
963                     player_effect_del(nlarn->p, e);
964             }
965             break;
966 
967         case 6: /* ^F */
968             if (game_wizardmode(nlarn))
969                 calc_fighting_stats(nlarn->p);
970             break;
971         }
972 
973         gboolean no_move = (moves_count == 0);
974         gboolean was_attacked = FALSE;
975 
976         /* manipulate game time */
977         if (moves_count)
978         {
979             player_make_move(nlarn->p, moves_count, FALSE, NULL);
980             was_attacked = nlarn->p->attacked;
981             nlarn->p->attacked = FALSE;
982             moves_count = 0;
983         }
984 
985         /* recalculate FOV */
986         player_update_fov(nlarn->p);
987 
988         if (run_cmd != 0)
989         {
990             // Interrupt running AND resting if:
991             // * last action cost no turns (we ran into a wall)
992             // * we took damage (trap, poison, or invisible monster)
993             // * a monster has moved adjacent to us
994             if (no_move || was_attacked
995                     || player_adjacent_monster(nlarn->p, run_cmd == '.'))
996             {
997                 run_cmd = 0;
998             }
999             // Interrupt resting if we've rested for 100 turns OR
1000             // * hp is full,
1001             // * mp is full,
1002             // * we are not confused,
1003             // * we are not blinded, AND
1004             // * we are not paralysed
1005             else if (run_cmd == '.')
1006             {
1007                 if (game_turn(nlarn) >= end_resting
1008                         || (nlarn->p->hp == (gint)nlarn->p->hp_max
1009                             && nlarn->p->mp == (gint)nlarn->p->mp_max
1010                             && !player_effect_get(nlarn->p, ET_CONFUSION)
1011                             && !player_effect_get(nlarn->p, ET_PARALYSIS)
1012                             && !player_effect_get(nlarn->p, ET_DIZZINESS)
1013                             && !player_effect_get(nlarn->p, ET_BLINDNESS)))
1014                 {
1015                     run_cmd = 0;
1016                 }
1017             }
1018             // Interrupt running if:
1019             // * became confused (umber hulk)
1020             // * there's a fork in the path ahead
1021             else if (player_effect_get(nlarn->p, ET_CONFUSION)
1022                      || (!adj_corr && adjacent_corridor(nlarn->p->pos, run_cmd)))
1023             {
1024                 run_cmd = 0;
1025             }
1026         }
1027     }
1028 }
1029 
1030 gboolean main_menu()
1031 {
1032     const char *main_menu_tpl =
1033         "\n"
1034         "      `lightgreen`a`end`) %s Game\n"
1035         "      `lightgreen`b`end`) Configure Settings\n"
1036         "      `lightgreen`c`end`) Visit the Hall of Fame\n"
1037         "\n"
1038         "      `lightgreen`q`end`) Quit Game\n"
1039         "\n"
1040         "    You have reached difficulty level %d\n";
1041 
1042 
1043     g_autofree char *title = g_strdup_printf("NLarn %s", nlarn_version);
1044     g_autofree char *main_menu = g_strdup_printf(main_menu_tpl,
1045         (game_turn(nlarn) == 1) ? "New" : "Continue saved", game_difficulty(nlarn));
1046 
1047     char input = 0;
1048 
1049     while (input != 'q' && input != KEY_ESC)
1050     {
1051         input = display_show_message(title, main_menu, 0);
1052 
1053         switch (input)
1054         {
1055         case 'a':
1056             return TRUE;
1057             break;
1058 
1059         case 'b':
1060             configure_defaults(nlarn_inifile);
1061             break;
1062 
1063         case 'c':
1064         {
1065             GList *hs = scores_load();
1066             char *rendered_highscores = scores_to_string(hs, NULL);
1067 
1068             display_show_message("NLarn Hall of Fame",
1069                     highscores ? rendered_highscores : room_for_improvement, 0);
1070 
1071             if (hs)
1072             {
1073                 scores_destroy(hs);
1074                 g_free(rendered_highscores);
1075             }
1076         }
1077             break;
1078         }
1079     }
1080 
1081     return FALSE;
1082 }
1083 
1084 int main(int argc, char *argv[])
1085 {
1086     /* initialisation */
1087     nlarn_init(argc, argv);
1088 
1089     /* check if the message file exists */
1090     gchar *message_file = NULL;
1091     if (!g_file_get_contents(nlarn_mesgfile, &message_file, NULL, NULL))
1092     {
1093         nlarn = game_destroy(nlarn);
1094         display_shutdown();
1095         g_printerr("Error: Cannot find the message file.\n");
1096 
1097         return EXIT_FAILURE;
1098     }
1099 
1100     /* show message file */
1101     display_show_message("Welcome to the game of NLarn!", message_file, 0);
1102     g_free(message_file);
1103 
1104     /* Create the jump target for player death. Death will destroy the game
1105        object, thus control will be returned to the line after this one, i.e
1106        the game will be created again and the main menu will be shown. To
1107        ensure that the game quits when quitting from inside the game, return
1108        the cause of death from player_die().
1109     */
1110     player_cod cod = setjmp(nlarn_death_jump);
1111 
1112     /* clear the screen to wipe remains from the previous game */
1113     clear();
1114 
1115     /* can be broken by quitting in the game, or with q or ESC in main menu */
1116     while (cod != PD_QUIT)
1117     {
1118         /* initialise the game */
1119         game_init(&config);
1120 
1121         /* present main menu - */
1122         if (FALSE == main_menu()) {
1123             break;
1124         }
1125 
1126         /* ask for a character name if none has been supplied */
1127         while (nlarn->p->name == NULL)
1128         {
1129             nlarn->p->name = display_get_string("Choose your name",
1130                     "By what name shall you be called?", NULL, 45);
1131         }
1132 
1133         /* ask for character's gender if it is not known yet */
1134         if (nlarn->p->sex == PS_NONE)
1135         {
1136             int res = display_get_yesno("Are you male or female?", NULL, "Female", "Male");
1137 
1138             /* display_get_yesno() returns 0 or one */
1139             nlarn->p->sex = (res == TRUE) ? PS_FEMALE : PS_MALE;
1140         }
1141 
1142         while (!nlarn->player_stats_set)
1143         {
1144             /* assign the player's stats */
1145             char selection = player_select_bonus_stats();
1146             nlarn->player_stats_set = player_assign_bonus_stats(nlarn->p, selection);
1147         }
1148 
1149         /* automatic save point (not when restoring a save) */
1150         if ((game_turn(nlarn) == 1) && game_autosave(nlarn))
1151         {
1152             game_save(nlarn);
1153         }
1154 
1155         /* main event loop */
1156         mainloop();
1157     }
1158 
1159     free_config(config);
1160 
1161     return EXIT_SUCCESS;
1162 }
1163 
1164 static gboolean adjacent_corridor(position pos, char mv)
1165 {
1166     position p1 = pos, p2 = pos;
1167     switch (mv)
1168     {
1169     case 'h': // left
1170         X(p1) -= 1;
1171         Y(p1) -= 1;
1172         X(p2) -= 1;
1173         Y(p2) += 1;
1174         break;
1175     case 'j': // down
1176         X(p1) -= 1;
1177         Y(p1) += 1;
1178         X(p2) += 1;
1179         Y(p2) += 1;
1180         break;
1181     case 'k': // up
1182         X(p1) -= 1;
1183         Y(p1) -= 1;
1184         X(p2) += 1;
1185         Y(p2) -= 1;
1186         break;
1187     case 'l': // right
1188         X(p1) += 1;
1189         Y(p1) -= 1;
1190         X(p2) += 1;
1191         Y(p2) += 1;
1192         break;
1193     case 'y': // up left
1194         Y(p1) -= 1;
1195         X(p2) -= 1;
1196         break;
1197     case 'u': // up right
1198         Y(p1) -= 1;
1199         X(p2) += 1;
1200         break;
1201     case 'b': // down left
1202         X(p1) -= 1;
1203         Y(p2) += 1;
1204         break;
1205     case 'n': // down right
1206         X(p1) += 1;
1207         Y(p2) += 1;
1208         break;
1209     }
1210 
1211     if (X(p1) < MAP_MAX_X && Y(p1) < MAP_MAX_Y
1212             && mt_is_passable(map_tiletype_at(game_map(nlarn, Z(nlarn->p->pos)), p1)))
1213     {
1214         return TRUE;
1215     }
1216     if (X(p2) < MAP_MAX_X && Y(p2) < MAP_MAX_Y
1217             && mt_is_passable(map_tiletype_at(game_map(nlarn, Z(nlarn->p->pos)), p2)))
1218     {
1219         return TRUE;
1220     }
1221 
1222     return FALSE;
1223 }
1224 
1225 #ifdef __unix
1226 static void nlarn_signal_handler(int signo)
1227 {
1228     /* restore the display down before emitting messages */
1229     display_shutdown();
1230 
1231     /* attempt to save and clear the game, when initialized */
1232     if (nlarn)
1233     {
1234             game_save(nlarn);
1235 
1236         if (signo != SIGHUP)
1237         {
1238             g_printf("Terminated. Your progress has been saved.\n");
1239         }
1240 
1241         nlarn = game_destroy(nlarn);
1242     }
1243 
1244     exit(EXIT_SUCCESS);
1245 }
1246 #endif
1247