1 /*
2  * config.c
3  * Copyright (C) 2009-2020 Joachim de Groot <jdegroot@web.de>
4  *
5  * NLarn 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  * NLarn 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 #include <stdlib.h>
20 #include <string.h>
21 #include <glib.h>
22 
23 #include "config.h"
24 #include "display.h"
25 #include "extdefs.h"
26 #include "items.h"
27 #include "player.h"
28 
29 static const char *default_config_file =
30     "[nlarn]\n"
31     "# Difficulty (i.e. number of games won)\n"
32     "difficulty=0\n"
33     "\n"
34     "# Set character's name\n"
35     "name=\n"
36     "\n"
37     "# Choose the gender of your character (m/f)\n"
38     "gender=\n"
39     "\n"
40     "# Choose the stats of your character\n"
41     "# a Strong character\n"
42     "# b Agile character\n"
43     "# c Tough character\n"
44     "# d Smart character\n"
45     "# e Randomly pick one of the above\n"
46     "# f Stats assigned randomly\n"
47     "stats=\n"
48     "\n"
49     "# Item types to pick up automatically:\n"
50     "# \" amulets\n"
51     "# ' ammunition (ammunition once fired will be picked up anyway)\n"
52     "# [ armour\n"
53     "# + books\n"
54     "# ] containers\n"
55     "# * gems\n"
56     "# $ money\n"
57     "# ! potions\n"
58     "# = rings\n"
59     "# ? scrolls\n"
60     "# ( weapons\n"
61     "auto-pickup=\"+*$\n"
62     "\n"
63 #ifdef SDLPDCURSES
64     "# Font size for the game. Defaults to 18 when not defined.\n"
65     "font-size=18\n"
66     "\n"
67 #endif
68     "# Disable automatic saving when switching a level. Saving the game is\n"
69     "# enabled by default, disable when it's too slow on your computer\n"
70     "no-autosave=false\n";
71 
72 /* shared config cleanup helper */
free_config(const struct game_config config)73 void free_config(const struct game_config config)
74 {
75     if (config.name)        g_free(config.name);
76     if (config.gender)      g_free(config.gender);
77     if (config.stats)       g_free(config.stats);
78     if (config.auto_pickup) g_free(config.auto_pickup);
79 }
80 
81 /* parse the command line */
parse_commandline(int argc,char * argv[],struct game_config * config)82 void parse_commandline(int argc, char *argv[], struct game_config *config)
83 {
84     const GOptionEntry entries[] =
85     {
86         { "name",        'n', 0, G_OPTION_ARG_STRING, &config->name,         "Set character's name", NULL },
87         { "gender",      'g', 0, G_OPTION_ARG_STRING, &config->gender,       "Set character's gender (m/f)", NULL },
88         { "stats",       's', 0, G_OPTION_ARG_STRING, &config->stats,        "Set character's stats (a-f)", NULL },
89         { "auto-pickup", 'a', 0, G_OPTION_ARG_STRING, &config->auto_pickup,  "Item types to pick up automatically, e.g. '$*+'", NULL },
90         { "no-autosave", 'N', 0, G_OPTION_ARG_NONE,   &config->no_autosave,  "Disable autosave", NULL },
91         { "wizard",      'w', 0, G_OPTION_ARG_NONE,   &config->wizard,       "Enable wizard mode", NULL },
92 #ifdef SDLPDCURSES
93         { "font-size",   'S', 0, G_OPTION_ARG_INT,    &config->font_size,   "Set font size", NULL },
94 #endif
95         { "userdir",     'D', 0, G_OPTION_ARG_FILENAME, &config->userdir,    "Alternate directory for config file and saved games", NULL },
96         { "highscores",  'h', 0, G_OPTION_ARG_NONE,   &config->show_scores,  "Show highscores and exit", NULL },
97         { "version",     'v', 0, G_OPTION_ARG_NONE,   &config->show_version, "Show version information and exit", NULL },
98         { NULL, 0, 0, 0, NULL, NULL, NULL }
99     };
100 
101     GError *error = NULL;
102     GOptionContext *context = g_option_context_new(NULL);
103     g_option_context_add_main_entries(context, entries, NULL);
104 
105     if (!g_option_context_parse(context, &argc, &argv, &error))
106     {
107         g_printerr("option parsing failed: %s\n", error->message);
108 
109         exit (EXIT_FAILURE);
110     }
111     g_option_context_free(context);
112 }
113 
parse_ini_file(const char * filename,struct game_config * config)114 gboolean parse_ini_file(const char *filename, struct game_config *config)
115 {
116     /* ini file handling */
117     GKeyFile *ini_file = g_key_file_new();
118     GError *error = NULL;
119     gboolean success;
120 
121     /* config file defined on the command line precedes over the default */
122     g_key_file_load_from_file(ini_file, filename,
123             G_KEY_FILE_NONE, &error);
124 
125     if ((success = (!error)))
126     {
127         /* ini file has been found, get values */
128         /* clear error after each attempt as values need not to be defined */
129         int difficulty = g_key_file_get_integer(ini_file, "nlarn", "difficulty", &error);
130         if (!config->difficulty && !error) config->difficulty = difficulty;
131         g_clear_error(&error);
132 
133         gboolean no_autosave = g_key_file_get_boolean(ini_file, "nlarn", "no-autosave", &error);
134         if (!config->no_autosave && !error) config->no_autosave = no_autosave;
135         g_clear_error(&error);
136 
137         char *name = g_key_file_get_string(ini_file, "nlarn", "name", &error);
138         if (!config->name && !error) config->name = name;
139         g_clear_error(&error);
140 
141         char *gender = g_key_file_get_string(ini_file, "nlarn", "gender", &error);
142         if (!config->gender && !error) config->gender = gender;
143         g_clear_error(&error);
144 
145         char *auto_pickup = g_key_file_get_string(ini_file, "nlarn", "auto-pickup", &error);
146         if (!config->auto_pickup && !error) config->auto_pickup = auto_pickup;
147         g_clear_error(&error);
148 
149         char *stats = g_key_file_get_string(ini_file, "nlarn", "stats", &error);
150         if (!config->stats && !error) config->stats = stats;
151         g_clear_error(&error);
152 
153 #ifdef SDLPDCURSES
154         int font_size = g_key_file_get_integer(ini_file, "nlarn", "font-size", &error);
155         if (!config->font_size && !error) config->font_size = font_size;
156         g_clear_error(&error);
157 #endif
158     }
159     else
160     {
161         /* File not found. Never mind but clean up the mess */
162         if (nlarn && nlarn->log) {
163             log_add_entry(nlarn->log,
164                 "Unable to parse configuration file: %s\n",
165                 error->message);
166         }
167         g_clear_error(&error);
168     }
169 
170     /* clean-up */
171     g_key_file_free(ini_file);
172 
173     return success;
174 }
175 
write_ini_file(const char * filename,struct game_config * config)176 void write_ini_file(const char *filename, struct game_config *config)
177 {
178     /* create configuration file from defaults */
179     GKeyFile *kf = g_key_file_new();
180     g_key_file_load_from_data(kf, default_config_file,
181             strlen(default_config_file), G_KEY_FILE_KEEP_COMMENTS, NULL);
182 
183     if (config)
184     {
185         /* apply configuration to configuration file */
186         g_key_file_set_integer(kf, "nlarn", "difficulty",  config->difficulty);
187         g_key_file_set_string(kf,  "nlarn", "name",        config->name ? config->name : "");
188         g_key_file_set_value(kf,   "nlarn", "gender",      config->gender ? config->gender : "");
189         g_key_file_set_value(kf,   "nlarn", "stats",       config->stats ? config->stats : "");
190         g_key_file_set_value(kf,   "nlarn", "auto-pickup", config->auto_pickup ? config->auto_pickup : "");
191         g_key_file_set_boolean(kf, "nlarn", "no-autosave", config->no_autosave);
192 #ifdef SDLPDCURSES
193         g_key_file_set_integer(kf, "nlarn", "font-size", config->font_size);
194 #endif
195     }
196 
197     /* write config file contents to the give file */
198     g_key_file_save_to_file(kf, filename, NULL);
199 
200     /* clean up */
201     g_key_file_free(kf);
202 }
203 
parse_autopickup_settings(const char * settings,gboolean config[IT_MAX])204 void parse_autopickup_settings(const char *settings, gboolean config[IT_MAX])
205 {
206     /* reset configuration */
207     memset(config, 0, sizeof(gboolean) * IT_MAX);
208 
209     /* parsing config has failed, not settings string given */
210     if (!settings) return;
211 
212     for (guint idx = 0; idx < strlen(settings); idx++)
213     {
214         for (item_t it = IT_NONE; it < IT_MAX; it++)
215         {
216             if (settings[idx] == item_glyph(it))
217             {
218                 config[it] = TRUE;
219             }
220         }
221     }
222 }
223 
compose_autopickup_settings(const gboolean config[IT_MAX])224 char *compose_autopickup_settings(const gboolean config[IT_MAX])
225 {
226     char *settings = g_malloc0(IT_MAX);
227 
228     int idx = 0;
229     for (item_t it = IT_NONE; it < IT_MAX; it++)
230     {
231         if (config[it])
232         {
233             settings[idx] = item_glyph(it);
234             idx++;
235         }
236     }
237 
238     return settings;
239 }
240 
verbose_autopickup_settings(const gboolean config[IT_MAX])241 char *verbose_autopickup_settings(const gboolean config[IT_MAX])
242 {
243     GString *settings = g_string_new(NULL);
244     int count = 0;
245 
246     for (item_t it = IT_NONE; it < IT_MAX; it++)
247     {
248         if (config[it])
249         {
250             if (count)
251                 g_string_append(settings, ", ");
252 
253             g_string_append(settings, item_name_pl(it));
254             count++;
255         }
256     }
257 
258     return g_string_free(settings, settings->len == 0);
259 }
260 
parse_gender(const char gender)261 int parse_gender(const char gender)
262 {
263     char _gender = g_ascii_tolower(gender);
264 
265     switch (_gender)
266     {
267     case 'm':
268         return PS_MALE;
269         break;
270 
271     case 'f':
272         return PS_FEMALE;
273         break;
274 
275     default:
276         return PS_NONE;
277         break;
278     }
279 }
280 
compose_gender(const int gender)281 char compose_gender(const int gender)
282 {
283     switch(gender)
284     {
285         case PS_MALE:
286             return 'm';
287             break;
288         case PS_FEMALE:
289             return 'f';
290             break;
291         default:
292             return ' ';
293     }
294 }
295 
configure_defaults(const char * inifile)296 void configure_defaults(const char *inifile)
297 {
298     const char *undef = "not defined";
299     const char *menu =
300         "Configure game defaults\n"
301         "\n"
302         "  `lightgreen`a`end`) Character name         - %s\n"
303         "  `lightgreen`b`end`) Character gender       - %s\n"
304         "  `lightgreen`c`end`) Character stats        - %s\n"
305         "  `lightgreen`d`end`) Configure auto-pickup  - %s\n"
306         "  `lightgreen`e`end`) Autosave on map change - `yellow`%s`end`\n"
307 #ifdef SDLPDCURSES
308         "  `lightgreen`f`end`) Configure font size    - `yellow`%d`end`\n"
309 #endif
310         "\n"
311         "Clear values with `lightgreen`A`end`-`lightgreen`D`end`\n";
312 
313     struct game_config config = {};
314     parse_ini_file(inifile, &config);
315 
316     gboolean leaving = FALSE;
317 
318     while (!leaving)
319     {
320         /* name */
321         char *nbuf = (config.name && strlen(config.name) > 0)
322             ? g_strdup_printf("`yellow`%s`end`", config.name)
323             : NULL;
324         /* gender */
325         char *gbuf = config.gender
326             ? g_strdup_printf("`yellow`%s`end`",
327                     player_sex_str[parse_gender(config.gender[0])])
328             : NULL;
329         /* stats */
330         char *sbuf = (config.stats && strlen(config.stats) > 0)
331             ? g_strdup_printf("`yellow`%s`end`", player_bonus_stat_desc[config.stats[0] - 'a'])
332             : NULL;
333         /* auto-pickup */
334         gboolean autopickup[IT_MAX];
335         parse_autopickup_settings(config.auto_pickup, autopickup);
336         char *verboseap = verbose_autopickup_settings(autopickup);
337         char *abuf = config.auto_pickup
338             ? g_strdup_printf("`yellow`%s`end`", verboseap)
339             : NULL;
340         if (verboseap) g_free(verboseap);
341 
342         char *msg = g_strdup_printf(menu,
343                 nbuf ? nbuf : undef,
344                 gbuf ? gbuf : undef,
345                 sbuf ? sbuf : undef,
346                 abuf ? abuf: undef,
347                 config.no_autosave ? "no" : "yes"
348 #ifdef SDLPDCURSES
349                 , config.font_size
350 #endif
351                 );
352 
353         if (nbuf) g_free(nbuf);
354         if (gbuf) g_free(gbuf);
355         if (sbuf) g_free(sbuf);
356         if (abuf) g_free(abuf);
357 
358         display_window *cwin = display_popup(COLS / 2 - 34, LINES / 2 - 6, 68,
359                 "Configure defaults", msg, 30);
360         g_free(msg);
361 
362         int res = display_getch(cwin->window);
363         switch (res)
364         {
365             /* default name */
366             case 'a':
367             {
368                 char *name = display_get_string("Choose default name",
369                         "By what name shall all your charactes be called?",
370                         config.name, 45);
371                 if (name)
372                 {
373                     if (config.name) g_free(config.name);
374                     config.name = name;
375                 }
376                 break;
377             }
378 
379             /* clear name */
380             case 'A':
381                 if (config.name) g_free(config.name);
382                 config.name = NULL;
383                 break;
384 
385             /* default gender */
386             case 'b':
387             {
388                 int gender = (display_get_yesno("Shall your future characters be "
389                             "male or female?", "Choose default gender",
390                             "Female", "Male") == TRUE)
391                     ? PS_FEMALE : PS_MALE;
392 
393                 if (config.gender) g_free(config.gender);
394                 config.gender = g_strdup_printf("%c", compose_gender(gender));
395                 break;
396             }
397 
398             /* clear gender */
399             case 'B':
400                 if (config.gender) g_free(config.gender);
401                 config.gender = NULL;
402                 break;
403 
404             /* default stats */
405             case 'c':
406             {
407                 char stats = player_select_bonus_stats();
408 
409                 if (config.stats) free(config.stats);
410                 config.stats = g_strdup_printf("%c", stats);
411                 break;
412             }
413 
414             /* clear stats */
415             case 'C':
416                 if (config.stats) g_free(config.stats);
417                 config.stats = NULL;
418                 break;
419 
420             /* auto-pickup defaults */
421             case 'd':
422             {
423                 gboolean conf[IT_MAX] = {};
424                 if (config.auto_pickup)
425                 {
426                     parse_autopickup_settings(config.auto_pickup, conf);
427                     g_free(config.auto_pickup);
428                 }
429 
430                 display_config_autopickup(conf);
431                 config.auto_pickup = compose_autopickup_settings(conf);
432 
433                 break;
434             }
435 
436             /* clear auto-pickup */
437             case 'D':
438                 if (config.auto_pickup) g_free(config.auto_pickup);
439                 config.auto_pickup = NULL;
440                 break;
441 
442             /* autosave */
443             case 'e':
444                 config.no_autosave = !config.no_autosave;
445                 break;
446 
447             /* font size */
448 #ifdef SDLPDCURSES
449             case 'f':
450                 {
451                     char *cval = g_strdup_printf("%d", config.font_size);
452                     char *nval = display_get_string("Default font size",
453                         "Font size (6 - 48): ", cval, 2);
454                     g_free(cval);
455 
456                     if (nval)
457                     {
458                         int val = atoi(nval);
459                         g_free(nval);
460 
461                         if (val >= 6 && val <= 48)
462                         {
463                             config.font_size = val;
464                         }
465                         else
466                         {
467                             display_show_message("Error", "Invalid font size", 0);
468                         }
469                     }
470                 }
471                 break;
472 #endif
473 
474             case KEY_ESC:
475                 leaving = TRUE;
476                 break;
477 
478             default:
479                 /* ignore input */
480                 break;
481         }
482         display_window_destroy(cwin);
483     }
484 
485     /* write back modified config */
486     write_ini_file(inifile, &config);
487 
488     /* clean up */
489     free_config(config);
490 }
491 
config_increase_difficulty(const char * inifile,const int new_difficulty)492 void config_increase_difficulty(const char *inifile, const int new_difficulty)
493 {
494     struct game_config config = {};
495     parse_ini_file(inifile, &config);
496 
497     config.difficulty = new_difficulty;
498 
499     /* write back modified config */
500     write_ini_file(inifile, &config);
501 
502     /* clean up */
503     free_config(config);
504 }
505