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