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