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