rnd-19981108-2
[rocksndiamonds.git] / src / files.c
1 /***********************************************************
2 *  Rocks'n'Diamonds -- McDuffin Strikes Back!              *
3 *----------------------------------------------------------*
4 *  (c) 1995-98 Artsoft Entertainment                       *
5 *              Holger Schemel                              *
6 *              Oststrasse 11a                              *
7 *              33604 Bielefeld                             *
8 *              phone: ++49 +521 290471                     *
9 *              email: aeglos@valinor.owl.de                *
10 *----------------------------------------------------------*
11 *  files.h                                                 *
12 ***********************************************************/
13
14 #include <ctype.h>
15
16 #include "files.h"
17 #include "tools.h"
18 #include "misc.h"
19 #include "tape.h"
20 #include "joystick.h"
21
22 #define MAX_LINE_LEN                    1000
23
24 static char *getGlobalDataDir()
25 {
26   return GAME_DIR;
27 }
28
29 static char *getUserDataDir()
30 {
31   static char *userdata_dir = NULL;
32
33   if (!userdata_dir)
34   {
35     char *home_dir = getHomeDir();
36     char *data_dir = USERDATA_DIRECTORY;
37
38     userdata_dir = checked_malloc(strlen(home_dir) + strlen(data_dir) + 2);
39     sprintf(userdata_dir, "%s/%s", home_dir, data_dir);
40   }
41
42   return userdata_dir;
43 }
44
45 static char *getSetupDir()
46 {
47   return getUserDataDir();
48 }
49
50 static char *getTapeDir(char *level_subdir)
51 {
52   static char *tape_dir = NULL;
53   char *data_dir = getUserDataDir();
54   char *tape_subdir = TAPES_DIRECTORY;
55
56   if (tape_dir)
57     free(tape_dir);
58
59   tape_dir = checked_malloc(strlen(data_dir) + strlen(tape_subdir) +
60                             strlen(level_subdir) + 3);
61   sprintf(tape_dir, "%s/%s%s%s", data_dir, tape_subdir,
62           (strlen(level_subdir) > 0 ? "/" : ""), level_subdir);
63
64   return tape_dir;
65 }
66
67 static char *getScoreDir(char *level_subdir)
68 {
69   static char *score_dir = NULL;
70   char *data_dir = getGlobalDataDir();
71   char *score_subdir = SCORES_DIRECTORY;
72
73   if (score_dir)
74     free(score_dir);
75
76   score_dir = checked_malloc(strlen(data_dir) + strlen(score_subdir) +
77                              strlen(level_subdir) + 3);
78   sprintf(score_dir, "%s/%s%s%s", data_dir, score_subdir,
79           (strlen(level_subdir) > 0 ? "/" : ""), level_subdir);
80
81   return score_dir;
82 }
83
84 static void createDirectory(char *dir, char *text)
85 {
86   if (access(dir, F_OK) != 0)
87     if (mkdir(dir, USERDATA_DIR_MODE) != 0)
88       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
89 }
90
91 static void InitUserDataDirectory()
92 {
93   createDirectory(getUserDataDir(), "user data");
94 }
95
96 static void InitTapeDirectory(char *level_subdir)
97 {
98   createDirectory(getTapeDir(""), "main tape");
99   createDirectory(getTapeDir(level_subdir), "level tape");
100 }
101
102 static void InitScoreDirectory(char *level_subdir)
103 {
104   createDirectory(getScoreDir(""), "main score");
105   createDirectory(getScoreDir(level_subdir), "level score");
106 }
107
108 boolean LoadLevelInfo()
109 {
110   int i;
111   char filename[MAX_FILENAME_LEN];
112   char cookie[MAX_FILENAME_LEN];
113   FILE *file;
114
115   sprintf(filename, "%s/%s", options.level_directory, LEVDIR_FILENAME);
116
117   if (!(file = fopen(filename, "r")))
118   {
119     Error(ERR_WARN, "cannot read level info '%s'", filename);
120     return(FALSE);
121   }
122
123   fscanf(file, "%s\n", cookie);
124   if (strcmp(cookie, LEVELDIR_COOKIE))
125   {
126     Error(ERR_WARN, "wrong format of level info file");
127     fclose(file);
128     return(FALSE);
129   }
130
131   num_leveldirs = 0;
132   leveldir_nr = 0;
133   for(i=0; i<MAX_LEVDIR_ENTRIES; i++)
134   {
135     fscanf(file, "%s", leveldir[i].filename);
136     fscanf(file, "%s", leveldir[i].name);
137     fscanf(file, "%d", &leveldir[i].levels);
138     fscanf(file, "%d", &leveldir[i].readonly);
139     if (feof(file))
140       break;
141
142     num_leveldirs++;
143   }
144
145   if (!num_leveldirs)
146   {
147     Error(ERR_WARN, "empty level info '%s'", filename);
148     return(FALSE);
149   }
150
151   return(TRUE);
152 }
153
154 void LoadLevel(int level_nr)
155 {
156   int i, x, y;
157   char filename[MAX_FILENAME_LEN];
158   char cookie[MAX_FILENAME_LEN];
159   FILE *file;
160
161   sprintf(filename, "%s/%s/%d",
162           options.level_directory, leveldir[leveldir_nr].filename, level_nr);
163
164   if (!(file = fopen(filename, "r")))
165     Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
166   else
167   {
168     fgets(cookie, LEVEL_COOKIE_LEN, file);
169     fgetc(file);
170
171     if (strcmp(cookie,LEVEL_COOKIE))
172     {
173       Error(ERR_WARN, "wrong format of level file '%s'", filename);
174       fclose(file);
175       file = NULL;
176     }
177   }
178
179   if (file)
180   {
181     lev_fieldx = level.fieldx = fgetc(file);
182     lev_fieldy = level.fieldy = fgetc(file);
183
184     level.time          = (fgetc(file)<<8) | fgetc(file);
185     level.edelsteine    = (fgetc(file)<<8) | fgetc(file);
186     for(i=0; i<MAX_LEVNAMLEN; i++)
187       level.name[i]     = fgetc(file);
188     level.name[MAX_LEVNAMLEN-1] = 0;
189     for(i=0; i<MAX_LEVSCORE_ENTRIES; i++)
190       level.score[i]    = fgetc(file);
191     for(i=0; i<4; i++)
192       for(y=0; y<3; y++)
193         for(x=0; x<3; x++)
194           level.mampfer_inhalt[i][x][y] = fgetc(file);
195     level.tempo_amoebe  = fgetc(file);
196     level.dauer_sieb    = fgetc(file);
197     level.dauer_ablenk  = fgetc(file);
198     level.amoebe_inhalt = fgetc(file);
199
200     for(i=0; i<NUM_FREE_LVHD_BYTES; i++) /* Rest frei / Headergröße 80 Bytes */
201       fgetc(file);
202
203     for(y=0; y<MAX_LEV_FIELDY; y++) 
204       for(x=0; x<MAX_LEV_FIELDX; x++) 
205         Feld[x][y] = Ur[x][y] = EL_ERDREICH;
206
207     for(y=0; y<lev_fieldy; y++) 
208       for(x=0; x<lev_fieldx; x++) 
209         Feld[x][y] = Ur[x][y] = fgetc(file);
210
211     fclose(file);
212
213     if (level.time <= 10)       /* Mindestspieldauer */
214       level.time = 10;
215   }
216   else
217   {
218     lev_fieldx = level.fieldx = STD_LEV_FIELDX;
219     lev_fieldy = level.fieldy = STD_LEV_FIELDY;
220
221     level.time          = 100;
222     level.edelsteine    = 0;
223     strcpy(level.name, "Nameless Level");
224     for(i=0; i<MAX_LEVSCORE_ENTRIES; i++)
225       level.score[i]    = 10;
226     for(i=0; i<4; i++)
227       for(y=0; y<3; y++)
228         for(x=0; x<3; x++)
229           level.mampfer_inhalt[i][x][y] = EL_FELSBROCKEN;
230     level.tempo_amoebe  = 10;
231     level.dauer_sieb    = 10;
232     level.dauer_ablenk  = 10;
233     level.amoebe_inhalt = EL_DIAMANT;
234
235     for(y=0; y<STD_LEV_FIELDY; y++) 
236       for(x=0; x<STD_LEV_FIELDX; x++) 
237         Feld[x][y] = Ur[x][y] = EL_ERDREICH;
238     Feld[0][0] = Ur[0][0] = EL_SPIELFIGUR;
239     Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
240       Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_AUSGANG_ZU;
241   }
242 }
243
244 void SaveLevel(int level_nr)
245 {
246   int i, x, y;
247   char filename[MAX_FILENAME_LEN];
248   FILE *file;
249
250   sprintf(filename, "%s/%s/%d",
251           options.level_directory, leveldir[leveldir_nr].filename, level_nr);
252
253   if (!(file = fopen(filename, "w")))
254   {
255     Error(ERR_WARN, "cannot save level file '%s'", filename);
256     return;
257   }
258
259   fputs(LEVEL_COOKIE,file);             /* Formatkennung */
260   fputc(0x0a, file);
261
262   fputc(level.fieldx, file);
263   fputc(level.fieldy, file);
264   fputc(level.time / 256, file);
265   fputc(level.time % 256, file);
266   fputc(level.edelsteine / 256, file);
267   fputc(level.edelsteine % 256, file);
268
269   for(i=0; i<MAX_LEVNAMLEN; i++)
270     fputc(level.name[i], file);
271   for(i=0; i<MAX_LEVSCORE_ENTRIES; i++)
272     fputc(level.score[i], file);
273   for(i=0; i<4; i++)
274     for(y=0; y<3; y++)
275       for(x=0; x<3; x++)
276         fputc(level.mampfer_inhalt[i][x][y], file);
277   fputc(level.tempo_amoebe, file);
278   fputc(level.dauer_sieb, file);
279   fputc(level.dauer_ablenk, file);
280   fputc(level.amoebe_inhalt, file);
281
282   for(i=0; i<NUM_FREE_LVHD_BYTES; i++)  /* Rest frei / Headergröße 80 Bytes */
283     fputc(0, file);
284
285   for(y=0; y<lev_fieldy; y++) 
286     for(x=0; x<lev_fieldx; x++) 
287       fputc(Ur[x][y], file);
288
289   fclose(file);
290
291   chmod(filename, LEVEL_PERMS);
292 }
293
294 void LoadTape(int level_nr)
295 {
296   int i;
297   char filename[MAX_FILENAME_LEN];
298   char cookie[MAX_FILENAME_LEN];
299   FILE *file;
300   boolean levelrec_10 = FALSE;
301
302   sprintf(filename, "%s/%d.%s",
303           getTapeDir(leveldir[leveldir_nr].filename),
304           level_nr, TAPEFILE_EXTENSION);
305
306   if ((file = fopen(filename, "r")))
307   {
308     fgets(cookie, LEVELREC_COOKIE_LEN, file);
309     fgetc(file);
310     if (!strcmp(cookie, LEVELREC_COOKIE_10))    /* old 1.0 tape format */
311       levelrec_10 = TRUE;
312     else if (strcmp(cookie, LEVELREC_COOKIE))   /* unknown tape format */
313     {
314       Error(ERR_WARN, "wrong format of level recording file '%s'", filename);
315       fclose(file);
316       file = NULL;
317     }
318   }
319
320   if (!file)
321     return;
322
323   tape.random_seed =
324     (fgetc(file)<<24) | (fgetc(file)<<16) | (fgetc(file)<<8) | fgetc(file);
325   tape.date =
326     (fgetc(file)<<24) | (fgetc(file)<<16) | (fgetc(file)<<8) | fgetc(file);
327   tape.length =
328     (fgetc(file)<<24) | (fgetc(file)<<16) | (fgetc(file)<<8) | fgetc(file);
329
330   tape.level_nr = level_nr;
331   tape.counter = 0;
332   tape.changed = FALSE;
333
334   tape.recording = FALSE;
335   tape.playing = FALSE;
336   tape.pausing = FALSE;
337
338   for(i=0; i<tape.length; i++)
339   {
340     int j;
341
342     if (i >= MAX_TAPELEN)
343       break;
344
345     for(j=0; j<MAX_PLAYERS; j++)
346     {
347       if (levelrec_10 && j > 0)
348       {
349         tape.pos[i].action[j] = MV_NO_MOVING;
350         continue;
351       }
352       tape.pos[i].action[j] = fgetc(file);
353     }
354
355     tape.pos[i].delay = fgetc(file);
356
357     if (levelrec_10)
358     {
359       /* eliminate possible diagonal moves in old tapes */
360       /* this is only for backward compatibility */
361
362       byte joy_dir[4] = { JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN };
363       byte action = tape.pos[i].action[0];
364       int k, num_moves = 0;
365
366       for (k=0; k<4; k++)
367       {
368         if (action & joy_dir[k])
369         {
370           tape.pos[i + num_moves].action[0] = joy_dir[k];
371           if (num_moves > 0)
372             tape.pos[i + num_moves].delay = 0;
373           num_moves++;
374         }
375       }
376
377       if (num_moves > 1)
378       {
379         num_moves--;
380         i += num_moves;
381         tape.length += num_moves;
382       }
383     }
384
385     if (feof(file))
386       break;
387   }
388
389   fclose(file);
390
391   if (i != tape.length)
392     Error(ERR_WARN, "level recording file '%s' corrupted", filename);
393
394   tape.length_seconds = GetTapeLength();
395 }
396
397 void SaveTape(int level_nr)
398 {
399   int i;
400   char filename[MAX_FILENAME_LEN];
401   FILE *file;
402   boolean new_tape = TRUE;
403
404   sprintf(filename, "%s/%d.%s",
405           getTapeDir(leveldir[leveldir_nr].filename),
406           level_nr, TAPEFILE_EXTENSION);
407
408   InitTapeDirectory(leveldir[leveldir_nr].filename);
409
410   /* Testen, ob bereits eine Aufnahme existiert */
411   if ((file = fopen(filename, "r")))
412   {
413     new_tape = FALSE;
414     fclose(file);
415
416     if (!Request("Replace old tape ?", REQ_ASK))
417       return;
418   }
419
420   if (!(file = fopen(filename, "w")))
421   {
422     Error(ERR_WARN, "cannot save level recording file '%s'", filename);
423     return;
424   }
425
426   fputs(LEVELREC_COOKIE, file);         /* Formatkennung */
427   fputc(0x0a, file);
428
429   fputc((tape.random_seed >> 24) & 0xff,file);
430   fputc((tape.random_seed >> 16) & 0xff,file);
431   fputc((tape.random_seed >>  8) & 0xff,file);
432   fputc((tape.random_seed >>  0) & 0xff,file);
433
434   fputc((tape.date >>  24) & 0xff,file);
435   fputc((tape.date >>  16) & 0xff,file);
436   fputc((tape.date >>   8) & 0xff,file);
437   fputc((tape.date >>   0) & 0xff,file);
438
439   fputc((tape.length >>  24) & 0xff,file);
440   fputc((tape.length >>  16) & 0xff,file);
441   fputc((tape.length >>   8) & 0xff,file);
442   fputc((tape.length >>   0) & 0xff,file);
443
444   for(i=0; i<tape.length; i++)
445   {
446     int j;
447
448     for(j=0; j<MAX_PLAYERS; j++)
449       fputc(tape.pos[i].action[j], file);
450
451     fputc(tape.pos[i].delay, file);
452   }
453
454   fclose(file);
455
456   chmod(filename, LEVREC_PERMS);
457
458   tape.changed = FALSE;
459
460   if (new_tape)
461     Request("tape saved !",REQ_CONFIRM);
462 }
463
464 void LoadScore(int level_nr)
465 {
466   int i;
467   char filename[MAX_FILENAME_LEN];
468   char cookie[MAX_FILENAME_LEN];
469   char line[MAX_LINE_LEN];
470   char *line_ptr;
471   FILE *file;
472
473   /* start with empty score table */
474   for(i=0; i<MAX_SCORE_ENTRIES; i++)
475   {
476     strcpy(highscore[i].Name, EMPTY_ALIAS);
477     highscore[i].Score = 0;
478   }
479
480   sprintf(filename, "%s/%d.%s",
481           getScoreDir(leveldir[leveldir_nr].filename),
482           level_nr, SCOREFILE_EXTENSION);
483
484   if (!(file = fopen(filename, "r")))
485     return;
486
487   fgets(cookie, SCORE_COOKIE_LEN, file);
488
489   if (strcmp(cookie, SCORE_COOKIE) != 0)
490   {
491     Error(ERR_WARN, "wrong format of score file '%s'", filename);
492     fclose(file);
493     return;
494   }
495
496   for(i=0; i<MAX_SCORE_ENTRIES; i++)
497   {
498     int position_nr;
499
500     fscanf(file, "%d", &position_nr);
501     fscanf(file, "%d", &highscore[i].Score);
502     fgets(line, MAX_LINE_LEN, file);
503
504     if (line[strlen(line)-1] == '\n')
505       line[strlen(line)-1] = '\0';
506
507     for (line_ptr = line; *line_ptr; line_ptr++)
508     {
509       if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
510       {
511         strncpy(highscore[i].Name, line_ptr, MAX_NAMELEN - 1);
512         highscore[i].Name[MAX_NAMELEN - 1] = '\0';
513         break;
514       }
515     }
516   }
517
518   fclose(file);
519 }
520
521 void SaveScore(int level_nr)
522 {
523   int i;
524   char filename[MAX_FILENAME_LEN];
525   FILE *file;
526
527   sprintf(filename, "%s/%d.%s",
528           getScoreDir(leveldir[leveldir_nr].filename),
529           level_nr, SCOREFILE_EXTENSION);
530
531   InitScoreDirectory(leveldir[leveldir_nr].filename);
532
533   if (!(file = fopen(filename, "w")))
534   {
535     Error(ERR_WARN, "cannot save score for level %d", level_nr);
536     return;
537   }
538
539   fprintf(file, "%s\n\n", SCORE_COOKIE);
540
541   for(i=0; i<MAX_SCORE_ENTRIES; i++)
542     fprintf(file, "%d %d %s\n", i+1, highscore[i].Score, highscore[i].Name);
543
544   fclose(file);
545
546   chmod(filename, SCORE_PERMS);
547 }
548
549 #define TOKEN_STR_FILE_IDENTIFIER       "file_identifier"
550 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
551 #define TOKEN_STR_PLAYER_PREFIX         "player_"
552
553 #define TOKEN_VALUE_POSITION            30
554
555 #define SETUP_TOKEN_SOUND               0
556 #define SETUP_TOKEN_SOUND_LOOPS         1
557 #define SETUP_TOKEN_SOUND_MUSIC         2
558 #define SETUP_TOKEN_SOUND_SIMPLE        3
559 #define SETUP_TOKEN_TOONS               4
560 #define SETUP_TOKEN_DOUBLE_BUFFERING    5
561 #define SETUP_TOKEN_SCROLL_DELAY        6
562 #define SETUP_TOKEN_SOFT_SCROLLING      7
563 #define SETUP_TOKEN_FADING              8
564 #define SETUP_TOKEN_AUTORECORD          9
565 #define SETUP_TOKEN_QUICK_DOORS         10
566 #define SETUP_TOKEN_TEAM_MODE           11
567 #define SETUP_TOKEN_ALIAS_NAME          12
568
569 #define SETUP_TOKEN_USE_JOYSTICK        13
570 #define SETUP_TOKEN_JOY_DEVICE_NAME     14
571 #define SETUP_TOKEN_JOY_XLEFT           15
572 #define SETUP_TOKEN_JOY_XMIDDLE         16
573 #define SETUP_TOKEN_JOY_XRIGHT          17
574 #define SETUP_TOKEN_JOY_YUPPER          18
575 #define SETUP_TOKEN_JOY_YMIDDLE         19
576 #define SETUP_TOKEN_JOY_YLOWER          20
577 #define SETUP_TOKEN_JOY_SNAP            21
578 #define SETUP_TOKEN_JOY_BOMB            22
579 #define SETUP_TOKEN_KEY_LEFT            23
580 #define SETUP_TOKEN_KEY_RIGHT           24
581 #define SETUP_TOKEN_KEY_UP              25
582 #define SETUP_TOKEN_KEY_DOWN            26
583 #define SETUP_TOKEN_KEY_SNAP            27
584 #define SETUP_TOKEN_KEY_BOMB            28
585
586 #define NUM_SETUP_TOKENS                29
587
588 #define FIRST_GLOBAL_SETUP_TOKEN        SETUP_TOKEN_SOUND
589 #define LAST_GLOBAL_SETUP_TOKEN         SETUP_TOKEN_ALIAS_NAME
590
591 #define FIRST_PLAYER_SETUP_TOKEN        SETUP_TOKEN_USE_JOYSTICK
592 #define LAST_PLAYER_SETUP_TOKEN         SETUP_TOKEN_KEY_BOMB
593
594 #define TYPE_BOOLEAN                    1
595 #define TYPE_SWITCH                     2
596 #define TYPE_KEYSYM                     3
597 #define TYPE_INTEGER                    4
598 #define TYPE_STRING                     5
599
600 static struct SetupInfo si;
601 static struct SetupInputInfo sii;
602 static struct
603 {
604   int type;
605   void *value;
606   char *text;
607 } token_info[] =
608 {
609   { TYPE_SWITCH,  &si.sound,            "sound"                         },
610   { TYPE_SWITCH,  &si.sound_loops,      "repeating_sound_loops"         },
611   { TYPE_SWITCH,  &si.sound_music,      "background_music"              },
612   { TYPE_SWITCH,  &si.sound_simple,     "simple_sound_effects"          },
613   { TYPE_SWITCH,  &si.toons,            "toons"                         },
614   { TYPE_SWITCH,  &si.double_buffering, "double_buffering"              },
615   { TYPE_SWITCH,  &si.scroll_delay,     "scroll_delay"                  },
616   { TYPE_SWITCH,  &si.soft_scrolling,   "soft_scrolling"                },
617   { TYPE_SWITCH,  &si.fading,           "screen_fading"                 },
618   { TYPE_SWITCH,  &si.autorecord,       "automatic_tape_recording"      },
619   { TYPE_SWITCH,  &si.quick_doors,      "quick_doors"                   },
620   { TYPE_SWITCH,  &si.team_mode,        "team_mode"                     },
621   { TYPE_STRING,  &si.alias_name,       "alias_name"                    },
622
623   /* for each player: */
624   { TYPE_BOOLEAN, &sii.use_joystick,    ".use_joystick"                 },
625   { TYPE_STRING,  &sii.joy.device_name, ".joy.device_name"              },
626   { TYPE_INTEGER, &sii.joy.xleft,       ".joy.xleft"                    },
627   { TYPE_INTEGER, &sii.joy.xmiddle,     ".joy.xmiddle"                  },
628   { TYPE_INTEGER, &sii.joy.xright,      ".joy.xright"                   },
629   { TYPE_INTEGER, &sii.joy.yupper,      ".joy.yupper"                   },
630   { TYPE_INTEGER, &sii.joy.ymiddle,     ".joy.ymiddle"                  },
631   { TYPE_INTEGER, &sii.joy.ylower,      ".joy.ylower"                   },
632   { TYPE_INTEGER, &sii.joy.snap,        ".joy.snap_field"               },
633   { TYPE_INTEGER, &sii.joy.bomb,        ".joy.place_bomb"               },
634   { TYPE_KEYSYM,  &sii.key.left,        ".key.move_left"                },
635   { TYPE_KEYSYM,  &sii.key.right,       ".key.move_right"               },
636   { TYPE_KEYSYM,  &sii.key.up,          ".key.move_up"                  },
637   { TYPE_KEYSYM,  &sii.key.down,        ".key.move_down"                },
638   { TYPE_KEYSYM,  &sii.key.snap,        ".key.snap_field"               },
639   { TYPE_KEYSYM,  &sii.key.bomb,        ".key.place_bomb"               }
640 };
641
642 static char *string_tolower(char *s)
643 {
644   static char s_lower[100];
645   int i;
646
647   if (strlen(s) >= 100)
648     return s;
649
650   strcpy(s_lower, s);
651
652   for (i=0; i<strlen(s_lower); i++)
653     s_lower[i] = tolower(s_lower[i]);
654
655   return s_lower;
656 }
657
658 static int get_string_integer_value(char *s)
659 {
660   static char *number_text[][3] =
661   {
662     { "0", "zero", "null", },
663     { "1", "one", "first" },
664     { "2", "two", "second" },
665     { "3", "three", "third" },
666     { "4", "four", "fourth" },
667     { "5", "five", "fifth" },
668     { "6", "six", "sixth" },
669     { "7", "seven", "seventh" },
670     { "8", "eight", "eighth" },
671     { "9", "nine", "ninth" },
672     { "10", "ten", "tenth" },
673     { "11", "eleven", "eleventh" },
674     { "12", "twelve", "twelfth" },
675   };
676
677   int i, j;
678
679   for (i=0; i<13; i++)
680     for (j=0; j<3; j++)
681       if (strcmp(string_tolower(s), number_text[i][j]) == 0)
682         return i;
683
684   return atoi(s);
685 }
686
687 static boolean get_string_boolean_value(char *s)
688 {
689   if (strcmp(string_tolower(s), "true") == 0 ||
690       strcmp(string_tolower(s), "yes") == 0 ||
691       strcmp(string_tolower(s), "on") == 0 ||
692       get_string_integer_value(s) == 1)
693     return TRUE;
694   else
695     return FALSE;
696 }
697
698 static char *getFormattedSetupEntry(char *token, char *value)
699 {
700   int i;
701   static char entry[MAX_LINE_LEN];
702
703   sprintf(entry, "%s:", token);
704   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
705     entry[i] = ' ';
706   entry[i] = '\0';
707
708   strcat(entry, value);
709
710   return entry;
711 }
712
713 static void freeSetupFileList(struct SetupFileList *setup_file_list)
714 {
715   if (!setup_file_list)
716     return;
717
718   if (setup_file_list->token)
719     free(setup_file_list->token);
720   if (setup_file_list->value)
721     free(setup_file_list->value);
722   if (setup_file_list->next)
723     freeSetupFileList(setup_file_list->next);
724   free(setup_file_list);
725 }
726
727 static struct SetupFileList *newSetupFileList(char *token, char *value)
728 {
729   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
730
731   new->token = checked_malloc(strlen(token) + 1);
732   strcpy(new->token, token);
733
734   new->value = checked_malloc(strlen(value) + 1);
735   strcpy(new->value, value);
736
737   new->next = NULL;
738
739   return new;
740 }
741
742 static char *getTokenValue(struct SetupFileList *setup_file_list,
743                            char *token)
744 {
745   if (!setup_file_list)
746     return NULL;
747
748   if (strcmp(setup_file_list->token, token) == 0)
749     return setup_file_list->value;
750   else
751     return getTokenValue(setup_file_list->next, token);
752 }
753
754 static void setTokenValue(struct SetupFileList *setup_file_list,
755                           char *token, char *value)
756 {
757   if (!setup_file_list)
758     return;
759
760   if (strcmp(setup_file_list->token, token) == 0)
761   {
762     free(setup_file_list->value);
763     setup_file_list->value = checked_malloc(strlen(value) + 1);
764     strcpy(setup_file_list->value, value);
765   }
766   else if (setup_file_list->next == NULL)
767     setup_file_list->next = newSetupFileList(token, value);
768   else
769     setTokenValue(setup_file_list->next, token, value);
770 }
771
772 #ifdef DEBUG
773 static void printSetupFileList(struct SetupFileList *setup_file_list)
774 {
775   if (!setup_file_list)
776     return;
777
778   printf("token: '%s'\n", setup_file_list->token);
779   printf("value: '%s'\n", setup_file_list->value);
780
781   printSetupFileList(setup_file_list->next);
782 }
783 #endif
784
785 static struct SetupFileList *loadSetupFileList(char *filename)
786 {
787   int line_len;
788   char line[MAX_LINE_LEN];
789   char *token, *value, *line_ptr;
790   struct SetupFileList *setup_file_list = newSetupFileList("", "");
791   struct SetupFileList *first_valid_list_entry;
792
793   FILE *file;
794
795   if (!(file = fopen(filename, "r")))
796   {
797     Error(ERR_WARN, "cannot open setup file '%s'", filename);
798     return NULL;
799   }
800
801   while(!feof(file))
802   {
803     /* read next line of input file */
804     if (!fgets(line, MAX_LINE_LEN, file))
805       break;
806
807     /* cut trailing comment or whitespace from input line */
808     for (line_ptr = line; *line_ptr; line_ptr++)
809     {
810       if (*line_ptr == '#' || *line_ptr == '\n')
811       {
812         *line_ptr = '\0';
813         break;
814       }
815     }
816
817     /* cut trailing whitespaces from input line */
818     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
819       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
820         *line_ptr = '\0';
821
822     /* ignore empty lines */
823     if (*line == '\0')
824       continue;
825
826     line_len = strlen(line);
827
828     /* cut leading whitespaces from token */
829     for (token = line; *token; token++)
830       if (*token != ' ' && *token != '\t')
831         break;
832
833     /* find end of token */
834     for (line_ptr = token; *line_ptr; line_ptr++)
835     {
836       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
837       {
838         *line_ptr = '\0';
839         break;
840       }
841     }
842
843     if (line_ptr < line + line_len)
844       value = line_ptr + 1;
845     else
846       value = "\0";
847
848     /* cut leading whitespaces from value */
849     for (; *value; value++)
850       if (*value != ' ' && *value != '\t')
851         break;
852
853     if (*token && *value)
854       setTokenValue(setup_file_list, token, value);
855   }
856
857   fclose(file);
858
859   first_valid_list_entry = setup_file_list->next;
860
861   /* free empty list header */
862   setup_file_list->next = NULL;
863   freeSetupFileList(setup_file_list);
864
865   if (!first_valid_list_entry)
866     Error(ERR_WARN, "setup file is empty");
867
868   return first_valid_list_entry;
869 }
870
871 static void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
872                                          char *identifier)
873 {
874   if (!setup_file_list)
875     return;
876
877   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
878   {
879     if (strcmp(setup_file_list->value, identifier) != 0)
880     {
881       Error(ERR_WARN, "setup file has wrong version");
882       return;
883     }
884     else
885       return;
886   }
887
888   if (setup_file_list->next)
889     checkSetupFileListIdentifier(setup_file_list->next, identifier);
890   else
891   {
892     Error(ERR_WARN, "setup file has no version information");
893     return;
894   }
895 }
896
897 static void setSetupInfoToDefaults(struct SetupInfo *si)
898 {
899   int i;
900
901   si->sound = TRUE;
902   si->sound_loops = FALSE;
903   si->sound_music = FALSE;
904   si->sound_simple = FALSE;
905   si->toons = TRUE;
906   si->double_buffering = TRUE;
907   si->direct_draw = !si->double_buffering;
908   si->scroll_delay = FALSE;
909   si->soft_scrolling = TRUE;
910   si->fading = FALSE;
911   si->autorecord = FALSE;
912   si->quick_doors = FALSE;
913
914   strncpy(si->login_name, getLoginName(), MAX_NAMELEN-1);
915   si->login_name[MAX_NAMELEN-1] = '\0';
916   strncpy(si->alias_name, getLoginName(), MAX_NAMELEN-1);
917   si->alias_name[MAX_NAMELEN-1] = '\0';
918
919   for (i=0; i<MAX_PLAYERS; i++)
920   {
921     si->input[i].use_joystick = FALSE;
922     strcpy(si->input[i].joy.device_name, joystick_device_name[i]);
923     si->input[i].joy.xleft   = JOYSTICK_XLEFT;
924     si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
925     si->input[i].joy.xright  = JOYSTICK_XRIGHT;
926     si->input[i].joy.yupper  = JOYSTICK_YUPPER;
927     si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
928     si->input[i].joy.ylower  = JOYSTICK_YLOWER;
929     si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
930     si->input[i].joy.bomb  = (i == 0 ? JOY_BUTTON_2 : 0);
931     si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KEY_UNDEFINDED);
932     si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KEY_UNDEFINDED);
933     si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KEY_UNDEFINDED);
934     si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KEY_UNDEFINDED);
935     si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KEY_UNDEFINDED);
936     si->input[i].key.bomb  = (i == 0 ? DEFAULT_KEY_BOMB  : KEY_UNDEFINDED);
937   }
938 }
939
940 static void setSetupInfo(int token_nr, char *token_value)
941 {
942   int token_type = token_info[token_nr].type;
943   void *setup_value = token_info[token_nr].value;
944
945   if (token_value == NULL)
946     return;
947
948   /* set setup field to corresponding token value */
949   switch (token_type)
950   {
951     case TYPE_BOOLEAN:
952     case TYPE_SWITCH:
953       *(boolean *)setup_value = get_string_boolean_value(token_value);
954       break;
955
956     case TYPE_KEYSYM:
957       *(KeySym *)setup_value = getKeySymFromX11KeyName(token_value);
958       break;
959
960     case TYPE_INTEGER:
961       *(int *)setup_value = get_string_integer_value(token_value);
962       break;
963
964     case TYPE_STRING:
965       strcpy((char *)setup_value, token_value);
966       break;
967
968     default:
969       break;
970   }
971 }
972
973 static void decodeSetupFileList(struct SetupFileList *setup_file_list)
974 {
975   int i, pnr;
976
977   if (!setup_file_list)
978     return;
979
980   /* handle global setup values */
981   si = setup;
982   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
983     setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
984   setup = si;
985
986   /* handle player specific setup values */
987   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
988   {
989     char prefix[30];
990
991     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
992
993     sii = setup.input[pnr];
994     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
995     {
996       char full_token[100];
997
998       sprintf(full_token, "%s%s", prefix, token_info[i].text);
999       setSetupInfo(i, getTokenValue(setup_file_list, full_token));
1000     }
1001     setup.input[pnr] = sii;
1002   }
1003 }
1004
1005 int getLevelSeriesNrFromLevelSeriesName(char *level_series_name)
1006 {
1007   int i;
1008
1009   if (!level_series_name)
1010     return 0;
1011
1012   for (i=0; i<num_leveldirs; i++)
1013     if (strcmp(level_series_name, leveldir[i].filename) == 0)
1014       return i;
1015
1016   return 0;
1017 }
1018
1019 int getLastPlayedLevelOfLevelSeries(char *level_series_name)
1020 {
1021   char *token_value;
1022   int level_series_nr = getLevelSeriesNrFromLevelSeriesName(level_series_name);
1023   int last_level_nr = 0;
1024
1025   if (!level_series_name)
1026     return 0;
1027
1028   token_value = getTokenValue(level_setup_list, level_series_name);
1029
1030   if (token_value)
1031   {
1032     int highest_level_nr = leveldir[level_series_nr].levels - 1;
1033
1034     last_level_nr = atoi(token_value);
1035
1036     if (last_level_nr < 0)
1037       last_level_nr = 0;
1038     if (last_level_nr > highest_level_nr)
1039       last_level_nr = highest_level_nr;
1040   }
1041
1042   return last_level_nr;
1043 }
1044
1045 void LoadSetup()
1046 {
1047   char filename[MAX_FILENAME_LEN];
1048   struct SetupFileList *setup_file_list = NULL;
1049
1050   /* always start with reliable default setup values */
1051   setSetupInfoToDefaults(&setup);
1052
1053   sprintf(filename, "%s/%s", getSetupDir(), SETUP_FILENAME);
1054
1055   setup_file_list = loadSetupFileList(filename);
1056
1057   if (setup_file_list)
1058   {
1059     checkSetupFileListIdentifier(setup_file_list, SETUP_COOKIE);
1060     decodeSetupFileList(setup_file_list);
1061
1062     setup.direct_draw = !setup.double_buffering;
1063
1064     freeSetupFileList(setup_file_list);
1065   }
1066   else
1067     Error(ERR_WARN, "using default setup values");
1068 }
1069
1070 static char *getSetupLine(char *prefix, int token_nr)
1071 {
1072   int i;
1073   static char entry[MAX_LINE_LEN];
1074   int token_type = token_info[token_nr].type;
1075   void *setup_value = token_info[token_nr].value;
1076   char *token_text = token_info[token_nr].text;
1077
1078   /* start with the prefix, token and some spaces to format output line */
1079   sprintf(entry, "%s%s:", prefix, token_text);
1080   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1081     strcat(entry, " ");
1082
1083   /* continue with the token's value (which can have different types) */
1084   switch (token_type)
1085   {
1086     case TYPE_BOOLEAN:
1087       strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1088       break;
1089
1090     case TYPE_SWITCH:
1091       strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1092       break;
1093
1094     case TYPE_KEYSYM:
1095       {
1096         KeySym keysym = *(KeySym *)setup_value;
1097         char *keyname = getKeyNameFromKeySym(keysym);
1098
1099         strcat(entry, getX11KeyNameFromKeySym(keysym));
1100         for (i=strlen(entry); i<50; i++)
1101           strcat(entry, " ");
1102
1103         /* add comment, if useful */
1104         if (strcmp(keyname, "(undefined)") != 0 &&
1105             strcmp(keyname, "(unknown)") != 0)
1106         {
1107           strcat(entry, "# ");
1108           strcat(entry, keyname);
1109         }
1110       }
1111       break;
1112
1113     case TYPE_INTEGER:
1114       {
1115         char buffer[MAX_LINE_LEN];
1116
1117         sprintf(buffer, "%d", *(int *)setup_value);
1118         strcat(entry, buffer);
1119       }
1120       break;
1121
1122     case TYPE_STRING:
1123       strcat(entry, (char *)setup_value);
1124       break;
1125
1126     default:
1127       break;
1128   }
1129
1130   return entry;
1131 }
1132
1133 void SaveSetup()
1134 {
1135   int i, pnr;
1136   char filename[MAX_FILENAME_LEN];
1137   FILE *file;
1138
1139   sprintf(filename, "%s/%s", getSetupDir(), SETUP_FILENAME);
1140
1141   InitUserDataDirectory();
1142
1143   if (!(file = fopen(filename, "w")))
1144   {
1145     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1146     return;
1147   }
1148
1149   fprintf(file, "%s\n",
1150           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, SETUP_COOKIE));
1151   fprintf(file, "\n");
1152
1153   /* handle global setup values */
1154   si = setup;
1155   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1156   {
1157     /* just to make things nicer :) */
1158     if (i == SETUP_TOKEN_ALIAS_NAME)
1159       fprintf(file, "\n");
1160
1161     fprintf(file, "%s\n", getSetupLine("", i));
1162   }
1163
1164   /* handle player specific setup values */
1165   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1166   {
1167     char prefix[30];
1168
1169     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1170     fprintf(file, "\n");
1171
1172     sii = setup.input[pnr];
1173     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1174       fprintf(file, "%s\n", getSetupLine(prefix, i));
1175   }
1176
1177   fclose(file);
1178
1179   chmod(filename, SETUP_PERMS);
1180 }
1181
1182 void LoadLevelSetup()
1183 {
1184   char filename[MAX_FILENAME_LEN];
1185
1186   /* always start with reliable default setup values */
1187
1188   leveldir_nr = 0;
1189   level_nr = 0;
1190
1191   sprintf(filename, "%s/%s", getSetupDir(), LEVELSETUP_FILENAME);
1192
1193   if (level_setup_list)
1194     freeSetupFileList(level_setup_list);
1195
1196   level_setup_list = loadSetupFileList(filename);
1197
1198   if (level_setup_list)
1199   {
1200     char *last_level_series =
1201       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1202
1203     leveldir_nr = getLevelSeriesNrFromLevelSeriesName(last_level_series);
1204     level_nr = getLastPlayedLevelOfLevelSeries(last_level_series);
1205
1206     checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
1207   }
1208   else
1209     Error(ERR_WARN, "using default setup values");
1210 }
1211
1212 void SaveLevelSetup()
1213 {
1214   char filename[MAX_FILENAME_LEN];
1215   struct SetupFileList *list_entry = level_setup_list;
1216   FILE *file;
1217
1218   setTokenValue(level_setup_list,
1219                 TOKEN_STR_LAST_LEVEL_SERIES, leveldir[leveldir_nr].filename);
1220
1221   setTokenValue(level_setup_list,
1222                 leveldir[leveldir_nr].filename, int2str(level_nr, 0));
1223
1224   sprintf(filename, "%s/%s", getSetupDir(), LEVELSETUP_FILENAME);
1225
1226   InitUserDataDirectory();
1227
1228   if (!(file = fopen(filename, "w")))
1229   {
1230     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1231     return;
1232   }
1233
1234   fprintf(file, "%s:              %s\n\n",
1235           TOKEN_STR_FILE_IDENTIFIER, LEVELSETUP_COOKIE);
1236
1237   while (list_entry)
1238   {
1239     if (strcmp(list_entry->token, TOKEN_STR_FILE_IDENTIFIER) != 0)
1240       fprintf(file, "%s\n",
1241               getFormattedSetupEntry(list_entry->token, list_entry->value));
1242
1243     /* just to make things nicer :) */
1244     if (strcmp(list_entry->token, TOKEN_STR_LAST_LEVEL_SERIES) == 0)
1245       fprintf(file, "\n");
1246
1247     list_entry = list_entry->next;
1248   }
1249
1250   fclose(file);
1251
1252   chmod(filename, SETUP_PERMS);
1253 }