white space changes
[rocksndiamonds.git] / src / game_bd / bd_bdcff.c
1 /*
2  * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #include <errno.h>
18
19 #include "main_bd.h"
20
21
22 #define BDCFF_VERSION "0.5"
23
24 /* these are used for bdcff loading, storing the sizes of caves */
25 static int cavesize[6], intermissionsize[6];
26
27 static boolean replay_store_from_bdcff(GdReplay *replay, const char *str)
28 {
29   GdDirection dir;
30   boolean up, down, left, right;
31   boolean fire, suicide;
32   const char *num = NULL;
33   int count, i;
34
35   fire = suicide = up = down = left = right = FALSE;
36
37   for (i = 0; str[i] != 0; i++)
38   {
39     switch (str[i])
40     {
41       case 'U':
42         fire = TRUE;
43       case 'u':
44         up = TRUE;
45         break;
46
47       case 'D':
48         fire = TRUE;
49       case 'd':
50         down = TRUE;
51         break;
52
53       case 'L':
54         fire = TRUE;
55       case 'l':
56         left = TRUE;
57         break;
58
59       case 'R':
60         fire = TRUE;
61       case 'r':
62         right = TRUE;
63         break;
64
65       case 'F':
66         fire = TRUE;
67         break;
68       case 'k':
69         suicide = TRUE;
70         break;
71
72       case '.':
73         /* do nothing, as all other movements are false */
74         break;
75
76       case 'c':
77       case 'C':
78         /* bdcff 'combined' flags. do nothing. */
79         break;
80
81       default:
82         if (str[i] >= '0' && str[i] <= '9')
83         {
84           if (!num)
85             num = str + i;
86         }
87     }
88   }
89
90   dir = gd_direction_from_keypress(up, down, left, right);
91   count = 1;
92
93   if (num)
94     sscanf(num, "%d", &count);
95
96   for (i = 0; i < count; i++)
97     gd_replay_store_movement(replay, dir, fire, suicide);
98
99   return TRUE;
100 }
101
102 static boolean attrib_is_valid_for_cave(const char *attrib)
103 {
104   int i;
105
106   /* bdcff engine flag............ */
107   if (strcasecmp(attrib, "Engine") == 0)
108     return TRUE;
109
110   /* old flags - for compatibility */
111   if (strcasecmp(attrib, "BD1Scheduling") == 0)
112     return TRUE;
113
114   if (strcasecmp(attrib, "SnapExplosions") == 0)
115     return TRUE;
116
117   if (strcasecmp(attrib, "AmoebaProperties") == 0)
118     return TRUE;
119
120   /* search in property database */
121   for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
122     if (strcasecmp(gd_cave_properties[i].identifier, attrib) == 0)
123       return TRUE;
124
125   return FALSE;
126 }
127
128 static boolean attrib_is_valid_for_caveset(const char *attrib)
129 {
130   int i;
131
132   /* search in property database */
133   for (i = 0; gd_caveset_properties[i].identifier != NULL; i++)
134     if (strcasecmp(gd_caveset_properties[i].identifier, attrib) == 0)
135       return TRUE;
136
137   return FALSE;
138 }
139
140 static boolean struct_set_property(void *str, const GdStructDescriptor *prop_desc,
141                                    const char *attrib, const char *param, int ratio)
142 {
143   char **params;
144   int paramcount;
145   boolean identifier_found;
146   int paramindex = 0;
147   int i;
148   boolean was_string;
149
150   params = getSplitStringArray(param, " ", -1);
151   paramcount = getStringArrayLength(params);
152   identifier_found = FALSE;
153
154   /* check all known tags. do not exit this loop if identifier_found == true...
155      as there are more lines in the array which have the same identifier. */
156   was_string = FALSE;
157
158   for (i = 0; prop_desc[i].identifier != NULL; i++)
159   {
160     if (strcasecmp(prop_desc[i].identifier, attrib) == 0)
161     {
162       /* found the identifier */
163       void *value = STRUCT_MEMBER_P(str, prop_desc[i].offset);
164
165       /* these point to the same, but to avoid the awkward cast syntax */
166       int *ivalue = value;
167       GdElement *evalue = value;
168       GdDirection *dvalue = value;
169       GdScheduling *svalue = value;
170       boolean *bvalue = value;
171       int j, k;
172
173       identifier_found = TRUE;
174
175       if (prop_desc[i].type == GD_TYPE_STRING)
176       {
177         /* strings are treated different, as occupy the whole length of the line */
178         gd_strcpy(value, param);
179
180         /* remember this to skip checking the number of parameters at the end of the function */
181         was_string = TRUE;
182
183         continue;
184       }
185
186       if (prop_desc[i].type == GD_TYPE_LONGSTRING)
187       {
188         char **str = (char **)value;
189
190         checked_free(*str);
191         *str = getUnescapedString(param);
192
193         /* remember this to skip checking the number of parameters at the end of the function */
194         was_string = TRUE;
195
196         continue;
197       }
198
199       /* not a string, so use scanf calls */
200       /* ALSO, if no more parameters to process, exit loop */
201       for (j = 0; j < prop_desc[i].count && params[paramindex] != NULL; j++)
202       {
203         boolean success = FALSE;
204         double res;
205
206         switch (prop_desc[i].type)
207         {
208           case GD_TYPE_LONGSTRING:
209           case GD_TYPE_STRING:
210             /* handled above */
211           case GD_TAB:
212           case GD_LABEL:
213             /* do nothing */
214             break;
215
216           case GD_TYPE_BOOLEAN:
217             success = sscanf(params[paramindex], "%d", &bvalue[j]) == 1;
218             if (!success)
219             {
220               if (strcasecmp(params[paramindex], "true") == 0 ||
221                   strcasecmp(params[paramindex], "on") == 0 ||
222                   strcasecmp(params[paramindex], "yes") == 0)
223               {
224                 bvalue[j] = TRUE;
225                 success = TRUE;
226               }
227               else if (strcasecmp(params[paramindex], "false") == 0 ||
228                        strcasecmp(params[paramindex], "off") == 0 ||
229                        strcasecmp(params[paramindex], "no") == 0)
230               {
231                 bvalue[j] = FALSE;
232                 success = TRUE;
233               }
234             }
235
236             /* if we are processing an array, fill other values with these.
237                if there are other values specified, those will be overwritten. */
238             if (success)
239               for (k = j + 1; k < prop_desc[i].count; k++)
240                 bvalue[k] = bvalue[j];
241
242             break;
243
244           case GD_TYPE_INT:
245             success = sscanf(params[paramindex], "%d", &ivalue[j]) == 1;
246             if (success)
247               /* copy to other if array */
248               for (k = j + 1; k < prop_desc[i].count; k++)
249                 ivalue[k] = ivalue[j];
250
251             break;
252
253           case GD_TYPE_PROBABILITY:
254             errno = 0;  /* must be reset before calling strtod() to detect overflow/underflow */
255             res = strtod(params[paramindex], NULL);
256             if (errno == 0 && res >= 0 && res <= 1)
257             {
258               /* fill all remaining items in array - may be only one */
259               for (k = j; k < prop_desc[i].count; k++)
260                 /* probabilities are stored inside as ppm (1E6) */
261                 ivalue[k] = res * 1E6 + 0.5;
262
263               success = TRUE;
264             }
265
266             break;
267
268           case GD_TYPE_RATIO:
269             errno = 0;  /* must be reset before calling strtod() to detect overflow/underflow */
270             res = strtod (params[paramindex], NULL);
271             if (errno == 0 && res >= 0 && res <= 1)
272             {
273               for (k = j; k < prop_desc[i].count; k++)
274                 ivalue[k] = (int)(res * ratio + 0.5);
275
276               success = TRUE;
277             }
278
279             break;
280
281           case GD_TYPE_ELEMENT:
282             evalue[j] = gd_get_element_from_string(params[paramindex]);
283
284             /* copy to all remaining elements in array */
285             for (k = j + 1; k < prop_desc[i].count; k++)
286               evalue[k] = evalue[j];
287
288             /* this shows error message on its own, do treat as always succeeded */
289             success = TRUE;
290             break;
291
292           case GD_TYPE_DIRECTION:
293             dvalue[j] = gd_direction_from_string(params[paramindex]);
294             /* copy to all remaining items in array */
295             for (k = j + 1; k < prop_desc[i].count; k++)
296               dvalue[k] = dvalue[j];
297
298             success = TRUE;
299             break;
300
301           case GD_TYPE_SCHEDULING:
302             svalue[j] = gd_scheduling_from_string(params[paramindex]);
303             /* copy to all remaining items in array */
304             for (k = j + 1; k < prop_desc[i].count; k++)
305               svalue[k] = svalue[j];
306
307             /* if there was an error, already reported by gd_scheduling_from_string */
308             success = TRUE;
309             break;
310
311           case GD_TYPE_COLOR:
312           case GD_TYPE_EFFECT:
313             /* shoud have handled this elsewhere */
314             break;
315         }
316
317         if (success)
318           paramindex++;    /* go to next parameter to process */
319         else
320           Warn("invalid parameter '%s' for attribute %s", params[paramindex], attrib);
321       }
322     }
323   }
324
325   /* if we found the identifier, but still could not process all parameters... */
326   /* of course, not for strings, as the whole line is the string */
327   if (identifier_found && !was_string && paramindex < paramcount)
328     Warn("excess parameters for attribute '%s': '%s'", attrib, params[paramindex]);
329
330   freeStringArray(params);
331
332   return identifier_found;
333 }
334
335 /********************************************************************************
336  *
337  * BDCFF LOADING
338  *
339  */
340
341 static boolean replay_store_more_from_bdcff(GdReplay *replay, const char *param)
342 {
343   char **split;
344   int i;
345   boolean result = TRUE;
346
347   split = getSplitStringArray(param, " ", -1);
348
349   for (i = 0; split[i] != 0; i++)
350     result = result && replay_store_from_bdcff(replay, split[i]);
351
352   freeStringArray(split);
353
354   return result;
355 }
356
357 /* report all remaining tags; called after the above function. */
358 static void replay_report_unknown_tags_func(const char *attrib, const char *param, void *data)
359 {
360   Warn("unknown replay tag '%s'", attrib);
361 }
362
363 /* a GHashTable foreach func.
364    keys are attribs; values are params;
365    the user data is the cave the hash table belongs to. */
366 static boolean replay_process_tags_func(const char *attrib, const char *param, GdReplay *replay)
367 {
368   boolean identifier_found = FALSE;
369
370   /* movements */
371   if (strcasecmp(attrib, "Movements") == 0)
372   {
373     identifier_found = TRUE;
374     replay_store_more_from_bdcff(replay, param);
375   }
376   else
377   {
378     /* any other tag */
379     /* 0: for ratio types; not used */
380     identifier_found = struct_set_property(replay, gd_replay_properties,
381                                            attrib, param, 0);
382   }
383
384   /* a ghrfunc should return true if the identifier is to be removed */
385   return identifier_found;
386 }
387
388 /* ... */
389 static void replay_process_tags(GdReplay *replay, HashTable *tags)
390 {
391   /* process all tags */
392   hashtable_foreach_remove(tags, (hashtable_remove_fn)replay_process_tags_func, replay);
393 }
394
395 /* a GHashTable foreach func.
396    keys are attribs; values are params;
397    the user data is the cave the hash table belongs to. */
398 static boolean cave_process_tags_func(const char *attrib, const char *param, GdCave *cave)
399 {
400   char **params;
401   boolean identifier_found;
402
403   params = getSplitStringArray(param, " ", -1);
404   identifier_found = FALSE;
405
406   if (strcasecmp(attrib, "SnapExplosions") == 0)
407   {
408     /* handle compatibility with old snapexplosions flag */
409
410     identifier_found = TRUE;
411
412     if (strcasecmp(param, "true") == 0)
413     {
414       cave->snap_element = O_EXPLODE_1;
415     }
416     else if (strcasecmp(param, "false") == 0)
417     {
418       cave->snap_element = O_SPACE;
419     }
420     else
421     {
422       Warn("invalid param for '%s': '%s'", attrib, param);
423     }
424   }
425   else if (strcasecmp(attrib, "BD1Scheduling") == 0)
426   {
427     /* handle compatibility with old bd1scheduling flag */
428
429     identifier_found = TRUE;
430
431     if (strcasecmp(param, "true") == 0)
432     {
433       if (cave->scheduling == GD_SCHEDULING_PLCK)
434         cave->scheduling = GD_SCHEDULING_BD1;
435     }
436   }
437   else if (strcasecmp(attrib, "Engine") == 0)
438   {
439     /* handle bdcff engine flag */
440
441     identifier_found = TRUE;
442
443     GdEngine engine = gd_cave_get_engine_from_string(param);
444
445     if (engine == GD_ENGINE_INVALID)
446       Warn(_("invalid parameter \"%s\" for attribute %s"), param, attrib);
447     else
448       gd_cave_set_engine_defaults(cave, engine);
449   }
450   else if (strcasecmp(attrib, "AmoebaProperties") == 0)
451   {
452     /* handle compatibility with old AmoebaProperties flag */
453
454     GdElement elem1 = O_STONE, elem2 = O_DIAMOND;
455
456     identifier_found = TRUE;
457     elem1 = gd_get_element_from_string(params[0]);
458     elem2 = gd_get_element_from_string(params[1]);
459     cave->amoeba_too_big_effect = elem1;
460     cave->amoeba_enclosed_effect = elem2;
461   }
462   else if (strcasecmp(attrib, "Colors") == 0)
463   {
464     /* colors attribute is a mess, have to process explicitly */
465
466     /* Colors = [border background] foreground1 foreground2 foreground3 [amoeba slime] */
467     identifier_found = TRUE;
468
469     cave->colorb = GD_GDASH_BLACK;    /* border - black */
470     cave->color0 = GD_GDASH_BLACK;    /* background - black */
471     cave->color1 = GD_GDASH_RED;
472     cave->color2 = GD_GDASH_PURPLE;
473     cave->color3 = GD_GDASH_YELLOW;
474     cave->color4 = cave->color3;    /* amoeba */
475     cave->color5 = cave->color1;    /* slime */
476   }
477   else
478   {
479     identifier_found = struct_set_property(cave, gd_cave_properties, attrib, param, cave->w * cave->h);
480   }
481
482   freeStringArray(params);
483
484   /* a ghrfunc should return true if the identifier is to be removed */
485   return identifier_found;
486 }
487
488 /* report all remaining tags; called after the above function. */
489 static void cave_report_and_copy_unknown_tags_func(char *attrib, char *param, void *data)
490 {
491   GdCave *cave = (GdCave *)data;
492
493   Warn("unknown tag '%s'", attrib);
494
495   hashtable_insert(cave->tags, getStringCopy(attrib), getStringCopy(param));
496 }
497
498 /* having read all strings belonging to the cave, process it. */
499 static void cave_process_tags(GdCave *cave, HashTable *tags, List *maplines)
500 {
501   char *value;
502
503   /* first check cave name, so we can report errors correctly (saying that GdCave xy: error foobar) */
504   value = hashtable_search(tags, "Name");
505   if (value)
506     cave_process_tags_func("Name", value, cave);
507
508   /* process lame engine tag first so its settings may be overwritten later */
509   value = hashtable_search(tags, "Engine");
510   if (value)
511   {
512     cave_process_tags_func("Engine", value, cave);
513     hashtable_remove(tags, "Engine");
514   }
515
516   /* check if this is an intermission, so we can set to cavesize or intermissionsize */
517   value = hashtable_search(tags, "Intermission");
518   if (value)
519   {
520     cave_process_tags_func("Intermission", value, cave);
521     hashtable_remove(tags, "Intermission");
522   }
523
524   if (cave->intermission)
525   {
526     /* set to IntermissionSize */
527     cave->w  = intermissionsize[0];
528     cave->h  = intermissionsize[1];
529     cave->x1 = intermissionsize[2];
530     cave->y1 = intermissionsize[3];
531     cave->x2 = intermissionsize[4];
532     cave->y2 = intermissionsize[5];
533   }
534   else
535   {
536     /* set to CaveSize */
537     cave->w = cavesize[0];
538     cave->h = cavesize[1];
539     cave->x1 = cavesize[2];
540     cave->y1 = cavesize[3];
541     cave->x2 = cavesize[4];
542     cave->y2 = cavesize[5];
543   }
544
545   /* process size at the beginning... as ratio types depend on this. */
546   value = hashtable_search(tags, "Size");
547   if (value)
548   {
549     cave_process_tags_func("Size", value, cave);
550     hashtable_remove(tags, "Size");
551   }
552
553   /* these are read from the hash table, but also have some implications */
554   /* we do not delete them from the hash table here; as _their values will be processed later_. */
555   /* here we only set their implicite meanings. */
556   /* these also set predictability */
557   if (hashtable_search(tags, "SlimePermeability"))
558     cave->slime_predictable = FALSE;
559
560   if (hashtable_search(tags, "SlimePermeabilityC64"))
561     cave->slime_predictable = TRUE;
562
563   /* these set scheduling type. framedelay takes precedence, if there are both; so we check it later. */
564   if (hashtable_search(tags, "CaveDelay"))
565   {
566     /* only set scheduling type, when it is not the gdash-default. */
567     /* this allows settings cavescheduling = bd1 in the [game] section, for example. */
568     /* in that case, this one will not overwrite it. */
569     if (cave->scheduling == GD_SCHEDULING_MILLISECONDS)
570       cave->scheduling = GD_SCHEDULING_PLCK;
571   }
572
573   if (hashtable_search(tags, "FrameTime"))
574     /* but if the cave has a frametime setting, always switch to milliseconds. */
575     cave->scheduling = GD_SCHEDULING_MILLISECONDS;
576
577   /* process all tags */
578   hashtable_foreach_remove(tags, (hashtable_remove_fn)cave_process_tags_func, cave);
579
580   /* and at the end, when read all tags (especially the size= tag) */
581   /* process map, if any. */
582   /* only report if map read is bigger than size= specified. */
583   /* some old bdcff files use smaller intermissions than the one specified. */
584   if (maplines)
585   {
586     int x, y, length = list_length(maplines);
587     List *iter;
588
589     /* create map and fill with initial border, in case that map strings are shorter or somewhat */
590     cave->map = gd_cave_map_new(cave, GdElement);
591
592     for (y = 0; y < cave->h; y++)
593       for (x = 0; x < cave->w; x++)
594         cave->map[y][x] = cave->initial_border;
595
596     if (length != cave->h && length != (cave->y2-cave->y1 + 1))
597       Warn("map error: cave height = %d (%d visible), map height = %d",
598            cave->h, cave->y2 - cave->y1 + 1, length);
599
600     for (iter = maplines, y = 0; y < length && iter != NULL; iter = iter->next, y++)
601     {
602       const char *line = iter->data;
603       int slen = strlen(line);
604
605       if (slen != cave->w && slen != (cave->x2 - cave->x1 + 1))
606         Warn("map error in row %d: cave width = %d (%d visible), map width = %d",
607              y, cave->w, cave->x2 - cave->x1 + 1, slen);
608
609       /* use number of cells from cave or string, whichever is smaller.
610          so will not overwrite array! */
611       for (x = 0; x < MIN(cave->w, slen); x++)
612         cave->map[y][x] = gd_get_element_from_character (line[x]);
613     }
614   }
615 }
616
617 /* sets the cavesize array to default values */
618 static void set_cavesize_defaults(void)
619 {
620   cavesize[0] = 40;
621   cavesize[1] = 22;
622   cavesize[2] = 0;
623   cavesize[3] = 0;
624   cavesize[4] = 39;
625   cavesize[5] = 21;
626 }
627
628 /* sets the cavesize array to default values */
629 static void set_intermissionsize_defaults(void)
630 {
631   intermissionsize[0] = 40;
632   intermissionsize[1] = 22;
633   intermissionsize[2] = 0;
634   intermissionsize[3] = 0;
635   intermissionsize[4] = 19;
636   intermissionsize[5] = 11;
637 }
638
639 boolean gd_caveset_load_from_bdcff(const char *contents)
640 {
641   char **lines;
642   int lineno;
643   GdCave *cave;
644   List *iter;
645   boolean reading_replay = FALSE;
646   boolean reading_map = FALSE;
647   boolean reading_mapcodes = FALSE;
648   boolean reading_highscore = FALSE;
649   boolean reading_objects = FALSE;
650   boolean reading_bdcff_demo = FALSE;
651   /* assume version to be 0.32, also when the file does not specify it explicitly */
652   GdString version_read = "0.32";
653   List *mapstrings = NULL;
654   int linenum;
655   HashTable *tags, *replay_tags;
656   GdObjectLevels levels = GD_OBJECT_LEVEL_ALL;
657   GdCave *default_cave;
658
659   gd_caveset_clear();
660
661   set_cavesize_defaults();
662   set_intermissionsize_defaults();
663   gd_create_char_to_element_table();
664
665   tags        = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
666   replay_tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
667
668   /* split into lines */
669   lines = getSplitStringArray (contents, "\n", 0);
670
671   /* attributes read will be set in cave. if no [cave]; they are stored
672      in the default cave; like in a [game] */
673   default_cave = gd_cave_new();
674   cave = default_cave;
675
676   linenum = getStringArrayLength(lines);
677
678   for (lineno = 0; lineno < linenum; lineno++)
679   {
680     char *line = lines[lineno];
681     char *r;
682
683     /* remove windows-nightmare \r-s */
684     while((r = strchr(line, '\r')))
685       strcpy(r, r + 1);
686
687     if (strlen (line) == 0)
688       continue;            /* skip empty lines */
689
690     /* just skip comments. be aware that map lines may start with a semicolon... */
691     if (!reading_map && line[0] == ';')
692       continue;
693
694     /* STARTING WITH A BRACKET [ IS A SECTION */
695     if (line[0] == '[')
696     {
697       if (strcasecmp(line, "[cave]") == 0)
698       {
699         /* new cave */
700         if (mapstrings)
701         {
702           Warn("incorrect file format: new [cave] section, but already read some map lines");
703           list_free(mapstrings);
704           mapstrings = NULL;
705         }
706
707         /* process any pending tags for game ... */
708         cave_process_tags(default_cave, tags, NULL);
709
710         /* ... to be able to create a copy for a new cave. */
711         cave = gd_cave_new_from_cave(default_cave);
712         gd_caveset = list_append (gd_caveset, cave);
713       }
714       else if (strcasecmp(line, "[/cave]") == 0)
715       {
716         cave_process_tags(cave, tags, mapstrings);
717         list_free(mapstrings);
718         mapstrings = NULL;
719
720         hashtable_foreach(tags, (hashtable_fn)cave_report_and_copy_unknown_tags_func, cave);
721         hashtable_remove_all(tags);
722
723         /* set this to point the pseudo-cave which holds default values */
724         cave = default_cave;
725       }
726       else if (strcasecmp(line, "[map]") == 0)
727       {
728         reading_map = TRUE;
729         if (mapstrings != NULL)
730         {
731           Warn("incorrect file format: new [map] section, but already read some map lines");
732           list_free(mapstrings);
733           mapstrings = NULL;
734         }
735       }
736       else if (strcasecmp(line, "[/map]") == 0)
737       {
738         reading_map = FALSE;
739       }
740       else if (strcasecmp(line, "[mapcodes]") == 0)
741       {
742         reading_mapcodes = TRUE;
743       }
744       else if (strcasecmp(line, "[/mapcodes]") == 0)
745       {
746         reading_mapcodes = FALSE;
747       }
748       else if (strcasecmp(line, "[highscore]") == 0)
749       {
750         reading_highscore = TRUE;
751       }
752       else if (strcasecmp(line, "[/highscore]") == 0)
753       {
754         reading_highscore = FALSE;
755       }
756       else if (strcasecmp(line, "[objects]") == 0)
757       {
758         reading_objects = TRUE;
759       }
760       else if (strcasecmp(line, "[/objects]") == 0)
761       {
762         reading_objects = FALSE;
763       }
764       else if (strcasecmp(line, "[demo]") == 0)
765       {
766         GdReplay *replay;
767
768         reading_bdcff_demo = TRUE;
769
770         if (cave != default_cave)
771         {
772           replay = gd_replay_new();
773           replay->saved = TRUE;
774           replay->success = TRUE;   /* we think that it is a successful demo */
775           cave->replays = list_append(cave->replays, replay);
776           gd_strcpy(replay->player_name, "???");    /* name not saved */
777         }
778         else
779         {
780           Warn("[demo] section must be in [cave] section!");
781         }
782       }
783       else if (strcasecmp(line, "[/demo]") == 0)
784       {
785         reading_bdcff_demo = FALSE;
786       }
787       else if (strcasecmp(line, "[replay]") == 0)
788       {
789         reading_replay = TRUE;
790       }
791       else if (strcasecmp(line, "[/replay]") == 0)
792       {
793         GdReplay *replay;
794
795         reading_replay = FALSE;
796         replay = gd_replay_new();
797
798         /* set "saved" flag, so this replay will be written when the caveset is saved again */
799         replay->saved = TRUE;
800         replay_process_tags(replay, replay_tags);
801
802 #if 1
803         /* BDCFF numbers levels from 1 to 5, but internally we number levels from 0 to 4 */
804         if (replay->level > 0)
805           replay->level--;
806 #endif
807
808         /* report any remaining unknown tags */
809         hashtable_foreach(replay_tags, (hashtable_fn)replay_report_unknown_tags_func, NULL);
810         hashtable_remove_all(replay_tags);
811
812         if (replay->movements->len != 0)
813         {
814           cave->replays = list_append(cave->replays, replay);
815         }
816         else
817         {
818           Warn("no movements in replay!");
819           gd_replay_free(replay);
820         }
821       }
822       /* GOSH i hate bdcff */
823       else if (strncasecmp(line, "[level=", strlen("[level=")) == 0)
824       {
825         int l[5];
826         int num;
827         char *nums;
828
829         /* there IS an equal sign, and we also skip that, so this points to the numbers */
830         nums = strchr(line, '=') + 1;
831         num = sscanf(nums, "%d,%d,%d,%d,%d", l + 0, l + 1, l + 2, l + 3, l + 4);
832         levels = 0;
833
834         if (num == 0)
835         {
836           Warn("invalid Levels tag: %s", line);
837           levels = GD_OBJECT_LEVEL_ALL;
838         }
839         else
840         {
841           int n;
842
843           for (n = 0; n < num; n++)
844           {
845             if (l[n] <= 5 && l[n] >= 1)
846               levels |= gd_levels_mask[l[n] - 1];
847             else
848               Warn("invalid level number %d", l[n]);
849           }
850         }
851       }
852       else if (strcasecmp(line, "[/level]") == 0)
853       {
854         levels = GD_OBJECT_LEVEL_ALL;
855       }
856       else if (strcasecmp(line, "[game]") == 0)
857       {
858       }
859       else if (strcasecmp(line, "[/game]") == 0)
860       {
861       }
862       else if (strcasecmp(line, "[BDCFF]") == 0)
863       {
864       }
865       else if (strcasecmp(line, "[/BDCFF]") == 0)
866       {
867       }
868       else
869       {
870         Warn("unknown section: \"%s\"", line);
871       }
872
873       continue;
874     }
875
876     if (reading_map)
877     {
878       /* just append to the mapstrings list. we will process it later */
879       mapstrings = list_append(mapstrings, line);
880
881       continue;
882     }
883
884     /* strip leading and trailing spaces AFTER checking if we are reading a map.
885        map lines might begin or end with spaces */
886     stripString(line);
887
888     if (reading_highscore)
889     {
890       int score;
891
892       if (sscanf(line, "%d", &score) != 1 || strchr(line, ' ') == NULL)
893       {    /* first word is the score */
894         Warn("highscore format incorrect");
895       }
896       else
897       {
898         if (cave == default_cave)
899           /* if we are reading the [game], add highscore to that one. */
900           /* from first space: the name */
901           gd_add_highscore(gd_caveset_data->highscore, strchr(line, ' ') + 1, score);
902         else
903           /* if a cave, add highscore to that. */
904           gd_add_highscore(cave->highscore, strchr(line, ' ') + 1, score);
905       }
906
907       continue;
908     }
909
910     /* read bdcff-style [demo], similar to a complete replay but cannot store like anything */
911     if (reading_bdcff_demo)
912     {
913       GdReplay *replay;
914       List *iter;
915
916       /* demo must be in [cave] section. we already showed an error message for this. */
917       if (cave == default_cave)
918         continue;
919
920       iter = list_last(cave->replays);
921
922       replay = (GdReplay *)iter->data;
923       replay_store_more_from_bdcff(replay, line);
924
925       continue;
926     }
927
928     if (reading_objects)
929     {
930       GdObject *new_object;
931
932       new_object = gd_object_new_from_string(line);
933       if (new_object)
934       {
935         new_object->levels = levels;    /* apply levels to new object */
936         cave->objects = list_append(cave->objects, new_object);
937       }
938       else
939       {
940         Error("invalid object specification: %s", line);
941       }
942
943       continue;
944     }
945
946     /* has an equal sign ->  some_attrib = parameters  type line. */
947     if (strchr (line, '=') != NULL)
948     {
949       char *attrib, *param;
950
951       attrib = line;                   /* attrib is from the first char */
952       param = strchr(line, '=') + 1;   /* param is after equal sign */
953       *strchr (line, '=') = 0;         /* delete equal sign - line is therefore splitted */
954
955       /* own tag: not too much thinking :P */
956       if (reading_replay)
957       {
958         hashtable_insert(replay_tags, getStringCopy(attrib), getStringCopy(param));
959       }
960       else if (reading_mapcodes)
961       {
962         if (strcasecmp("Length", attrib) == 0)
963         {
964           /* we do not support map code width != 1 */
965           if (strcmp(param, "1") != 0)
966             Warn(_("Only one-character map codes are currently supported!"));
967         }
968         else
969         {
970           /* the first character of the attribute is the element code itself */
971           gd_char_to_element[(int)attrib[0]] = gd_get_element_from_string(param);
972         }
973       }
974       /* BDCFF version */
975       else if (strcasecmp("Version", attrib) == 0)
976       {
977         gd_strcpy(version_read, param);
978       }
979       /* CAVES = x */
980       else if (strcasecmp(attrib, "Caves") == 0)
981       {
982         /* BDCFF files sometimes state how many caves they have */
983         /* we ignore this field. */
984       }
985       /* LEVELS = x */
986       else if (strcasecmp(attrib, "Levels") == 0)
987       {
988         /* BDCFF files sometimes state how many levels they have */
989         /* we ignore this field. */
990       }
991       else if (strcasecmp(attrib, "CaveSize") == 0)
992       {
993         int i;
994
995         i = sscanf(param, "%d %d %d %d %d %d",
996                    cavesize + 0,
997                    cavesize + 1,
998                    cavesize + 2,
999                    cavesize + 3,
1000                    cavesize + 4,
1001                    cavesize + 5);
1002
1003         /* allowed: 2 or 6 numbers */
1004         if (i == 2)
1005         {
1006           cavesize[2] = 0;
1007           cavesize[3] = 0;
1008           cavesize[4] = cavesize[0]-1;
1009           cavesize[5] = cavesize[1]-1;
1010         }
1011         else if (i != 6)
1012         {
1013           set_cavesize_defaults();
1014           Warn("invalid CaveSize tag: %s", line);
1015         }
1016       }
1017       else if (strcasecmp(attrib, "IntermissionSize") == 0)
1018       {
1019         int i;
1020
1021         i = sscanf(param, "%d %d %d %d %d %d",
1022                    intermissionsize + 0,
1023                    intermissionsize + 1,
1024                    intermissionsize + 2,
1025                    intermissionsize + 3,
1026                    intermissionsize + 4,
1027                    intermissionsize + 5);
1028
1029         /* allowed: 2 or 6 numbers */
1030         if (i == 2)
1031         {
1032           intermissionsize[2] = 0;
1033           intermissionsize[3] = 0;
1034           intermissionsize[4] = intermissionsize[0]-1;
1035           intermissionsize[5] = intermissionsize[1]-1;
1036         }
1037         else if (i != 6)
1038         {
1039           set_intermissionsize_defaults();
1040           Warn("invalid IntermissionSize tag: '%s'", line);
1041         }
1042       }
1043       else if (strcasecmp(attrib, "Effect") == 0)
1044       {
1045         /* CHECK IF IT IS AN EFFECT */
1046         char **params;
1047
1048         params = getSplitStringArray(param, " ", -1);
1049
1050         /* an effect command has two parameters */
1051         if (getStringArrayLength(params) == 2)
1052         {
1053           int i;
1054
1055           for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
1056           {
1057             /* we have to search for this effect */
1058             if (gd_cave_properties[i].type == GD_TYPE_EFFECT &&
1059                 strcasecmp(params[0], gd_cave_properties[i].identifier) == 0)
1060             {
1061               /* found identifier */
1062               void *value = STRUCT_MEMBER_P (cave, gd_cave_properties[i].offset);
1063
1064               *((GdElement *) value) = gd_get_element_from_string (params[1]);
1065               break;
1066             }
1067           }
1068
1069           /* if we didn't find first element name */
1070           if (gd_cave_properties[i].identifier == NULL)
1071           {
1072             /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
1073             if (strcasecmp(params[0], "BOUNCING_BOULDER") == 0)
1074               cave->stone_bouncing_effect = gd_get_element_from_string (params[1]);
1075             else if (strcasecmp(params[0], "EXPLOSION3S") == 0)
1076               cave->explosion_effect = gd_get_element_from_string(params[1]);
1077             /* falling with one l... */
1078             else if (strcasecmp(params[0], "STARTING_FALING_DIAMOND") == 0)
1079               cave->diamond_falling_effect = gd_get_element_from_string (params[1]);
1080             /* dirt lookslike */
1081             else if (strcasecmp(params[0], "DIRT") == 0)
1082               cave->dirt_looks_like = gd_get_element_from_string (params[1]);
1083             else if (strcasecmp(params[0], "HEXPANDING_WALL") == 0 && strcasecmp(params[1], "STEEL_HEXPANDING_WALL") == 0)
1084             {
1085               cave->expanding_wall_looks_like = O_STEEL;
1086             }
1087             else
1088               /* didn't find at all */
1089               Warn("invalid effect name '%s'", params[0]);
1090           }
1091         }
1092         else
1093           Warn("invalid effect specification '%s'", param);
1094
1095         freeStringArray(params);
1096       }
1097       else
1098       {
1099         /* no special handling: this is a normal attribute. */
1100
1101         if (cave == default_cave)
1102         {
1103           /* we are reading the [game] */
1104           if (attrib_is_valid_for_caveset(attrib))
1105           {
1106             /* if it is a caveset attrib, process it for the caveset. */
1107             struct_set_property(gd_caveset_data, gd_caveset_properties, attrib, param, 0);
1108           }
1109           else if (attrib_is_valid_for_cave(attrib))
1110           {
1111             /* it must be a default setting for all caves. is it a valid identifier? */
1112             /* yes, it is. add to the hash table, which will be copied for all caves. */
1113             hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param));
1114           }
1115           else
1116           {
1117             /* unknown setting - report. */
1118             Warn("invalid attribute for [game] '%s'", attrib);
1119           }
1120         }
1121         else
1122         {
1123           /* we are reading a [cave] */
1124           /* cave settings are immediately added to cave hash table. */
1125           /* if it is unknown, we have to remember it, and save it again. */
1126           hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param));
1127         }
1128       }
1129
1130       continue;
1131     }
1132
1133     Error("cannot parse line: %s", line);
1134   }
1135
1136   if (mapstrings)
1137   {
1138     Warn("incorrect file format: end of file, but still have some map lines read");
1139     list_free(mapstrings);
1140     mapstrings = NULL;
1141   }
1142
1143   /* the [game] section had some values which are default if not specified in [cave] sections. */
1144   /* these are used only for loading, so forget them now */
1145   if (default_cave->map)
1146     Warn(_("Invalid BDCFF: [game] section has a map"));
1147   if (default_cave->objects)
1148     Warn(_("Invalid BDCFF: [game] section has drawing objects defined"));
1149
1150   /* cleanup */
1151   freeStringArray(lines);
1152   hashtable_destroy(tags);
1153   hashtable_destroy(replay_tags);
1154   gd_cave_free(default_cave);
1155
1156   /* old bdcff files hack. explanation follows. */
1157   /* there were 40x22 caves in c64 bd, intermissions were also 40x22, but the visible */
1158   /* part was the upper left corner, 20x12. 40x22 caves are needed, as 20x12 caves would */
1159   /* look different (random cave elements needs the correct size.) */
1160   /* also, in older bdcff files, there is no size= tag. caves default to 40x22 and 20x12. */
1161   /* even the explicit drawrect and other drawing instructions, which did set up intermissions */
1162   /* to be 20x12, are deleted. very very bad decision. */
1163   /* here we try to detect and correct this. */
1164
1165   if (strEqual(version_read, "0.32"))
1166   {
1167     List *iter;
1168
1169     Warn("No BDCFF version, or 0.32. Using unspecified-intermission-size hack.");
1170
1171     for (iter = gd_caveset; iter != NULL; iter = iter->next)
1172     {
1173       GdCave *cave = (GdCave *)iter->data;
1174
1175       /* only applies to intermissions */
1176       /* not applied to mapped caves, as maps are filled with initial border, if the map read is smaller */
1177       if (cave->intermission && !cave->map)
1178       {
1179         /* we do not set the cave to 20x12, rather to 40x22 with 20x12 visible. */
1180         GdObject object;
1181
1182         cave->w = 40;
1183         cave->h = 22;
1184         cave->x1 = 0;
1185         cave->y1 = 0;
1186         cave->x2 = 19;
1187         cave->y2 = 11;
1188
1189         /* and cover the invisible area */
1190         object.type = GD_FILLED_RECTANGLE;
1191         object.x1 = 0;
1192         object.y1 = 11;    /* 11, because this will also be the border */
1193         object.x2 = 39;
1194         object.y2 = 21;
1195         object.element = cave->initial_border;
1196         object.fill_element = cave->initial_border;
1197
1198         cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object)));
1199
1200         object.x1 = 19;
1201         object.y1 = 0;    /* 19, as it is also the border */
1202
1203         cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object)));    /* another */
1204       }
1205     }
1206   }
1207
1208   if (!strEqual(version_read, BDCFF_VERSION))
1209     Warn("BDCFF version %s, loaded caveset may have errors.", version_read);
1210
1211   /* check for replays which are problematic */
1212   for (iter = gd_caveset; iter != NULL; iter = iter->next)
1213     gd_cave_check_replays((GdCave *)iter->data, TRUE, FALSE, FALSE);
1214
1215   /* if there was some error message - return fail XXX */
1216   return TRUE;
1217 }