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