31fab6e814144d2b5a88cf19e200dbcaea7fd50a
[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   int paramcount;
402   boolean identifier_found;
403
404   params = getSplitStringArray(param, " ", -1);
405   paramcount = getStringArrayLength(params);
406   identifier_found = FALSE;
407
408   if (strcasecmp(attrib, "SnapExplosions") == 0)
409   {
410     /* handle compatibility with old snapexplosions flag */
411
412     identifier_found = TRUE;
413
414     if (strcasecmp(param, "true") == 0)
415     {
416       cave->snap_element = O_EXPLODE_1;
417     }
418     else if (strcasecmp(param, "false") == 0)
419     {
420       cave->snap_element = O_SPACE;
421     }
422     else
423     {
424       Warn("invalid param for '%s': '%s'", attrib, param);
425     }
426   }
427   else if (strcasecmp(attrib, "BD1Scheduling") == 0)
428   {
429     /* handle compatibility with old bd1scheduling flag */
430
431     identifier_found = TRUE;
432
433     if (strcasecmp(param, "true") == 0)
434     {
435       if (cave->scheduling == GD_SCHEDULING_PLCK)
436         cave->scheduling = GD_SCHEDULING_BD1;
437     }
438   }
439   else if (strcasecmp(attrib, "Engine") == 0)
440   {
441     /* handle bdcff engine flag */
442
443     identifier_found = TRUE;
444
445     GdEngine engine = gd_cave_get_engine_from_string(param);
446
447     if (engine == GD_ENGINE_INVALID)
448       Warn(_("invalid parameter \"%s\" for attribute %s"), param, attrib);
449     else
450       gd_cave_set_engine_defaults(cave, engine);
451   }
452   else if (strcasecmp(attrib, "AmoebaProperties") == 0)
453   {
454     /* handle compatibility with old AmoebaProperties flag */
455
456     GdElement elem1 = O_STONE, elem2 = O_DIAMOND;
457
458     identifier_found = TRUE;
459     elem1 = gd_get_element_from_string(params[0]);
460     elem2 = gd_get_element_from_string(params[1]);
461     cave->amoeba_too_big_effect = elem1;
462     cave->amoeba_enclosed_effect = elem2;
463   }
464   else if (strcasecmp(attrib, "Colors") == 0)
465   {
466     /* colors attribute is a mess, have to process explicitly */
467     boolean ok = TRUE;
468
469     /* Colors = [border background] foreground1 foreground2 foreground3 [amoeba slime] */
470     identifier_found = TRUE;
471
472     if (paramcount == 3)
473     {
474       /* only color1,2,3 */
475       cave->colorb = gd_c64_color(0);    /* border - black */
476       cave->color0 = gd_c64_color(0);    /* background - black */
477       cave->color1 = gd_color_get_from_string(params[0]);
478       cave->color2 = gd_color_get_from_string(params[1]);
479       cave->color3 = gd_color_get_from_string(params[2]);
480       cave->color4 = cave->color3;    /* amoeba */
481       cave->color5 = cave->color1;    /* slime */
482     }
483     else if (paramcount == 5)
484     {
485       /* bg,color0,1,2,3 */
486       cave->colorb = gd_color_get_from_string(params[0]);
487       cave->color0 = gd_color_get_from_string(params[1]);
488       cave->color1 = gd_color_get_from_string(params[2]);
489       cave->color2 = gd_color_get_from_string(params[3]);
490       cave->color3 = gd_color_get_from_string(params[4]);
491       cave->color4 = cave->color3;    /* amoeba */
492       cave->color5 = cave->color1;    /* slime */
493     }
494     else if (paramcount == 7)
495     {
496       /* bg,color0,1,2,3,amoeba,slime */
497       cave->colorb = gd_color_get_from_string(params[0]);
498       cave->color0 = gd_color_get_from_string(params[1]);
499       cave->color1 = gd_color_get_from_string(params[2]);
500       cave->color2 = gd_color_get_from_string(params[3]);
501       cave->color3 = gd_color_get_from_string(params[4]);
502       cave->color4 = gd_color_get_from_string(params[5]);    /* amoeba */
503       cave->color5 = gd_color_get_from_string(params[6]);    /* slime */
504     }
505     else
506     {
507       Warn("invalid number of color strings: %s", param);
508
509       ok = FALSE;
510     }
511
512     /* now check and maybe make up some new. */
513     if (!ok ||
514         gd_color_is_unknown(cave->colorb) ||
515         gd_color_is_unknown(cave->color0) ||
516         gd_color_is_unknown(cave->color1) ||
517         gd_color_is_unknown(cave->color2) ||
518         gd_color_is_unknown(cave->color3) ||
519         gd_color_is_unknown(cave->color4) ||
520         gd_color_is_unknown(cave->color5))
521     {
522       Warn("created a new C64 color scheme.");
523
524       gd_cave_set_random_c64_colors(cave);    /* just create some random */
525     }
526   }
527   else
528   {
529     identifier_found = struct_set_property(cave, gd_cave_properties, attrib, param, cave->w * cave->h);
530   }
531
532   freeStringArray(params);
533
534   /* a ghrfunc should return true if the identifier is to be removed */
535   return identifier_found;
536 }
537
538 /* report all remaining tags; called after the above function. */
539 static void cave_report_and_copy_unknown_tags_func(char *attrib, char *param, void *data)
540 {
541   GdCave *cave = (GdCave *)data;
542
543   Warn("unknown tag '%s'", attrib);
544
545   hashtable_insert(cave->tags, getStringCopy(attrib), getStringCopy(param));
546 }
547
548 /* having read all strings belonging to the cave, process it. */
549 static void cave_process_tags(GdCave *cave, HashTable *tags, List *maplines)
550 {
551   char *value;
552
553   /* first check cave name, so we can report errors correctly (saying that GdCave xy: error foobar) */
554   value = hashtable_search(tags, "Name");
555   if (value)
556     cave_process_tags_func("Name", value, cave);
557
558   /* process lame engine tag first so its settings may be overwritten later */
559   value = hashtable_search(tags, "Engine");
560   if (value)
561   {
562     cave_process_tags_func("Engine", value, cave);
563     hashtable_remove(tags, "Engine");
564   }
565
566   /* check if this is an intermission, so we can set to cavesize or intermissionsize */
567   value = hashtable_search(tags, "Intermission");
568   if (value)
569   {
570     cave_process_tags_func("Intermission", value, cave);
571     hashtable_remove(tags, "Intermission");
572   }
573
574   if (cave->intermission)
575   {
576     /* set to IntermissionSize */
577     cave->w  = intermissionsize[0];
578     cave->h  = intermissionsize[1];
579     cave->x1 = intermissionsize[2];
580     cave->y1 = intermissionsize[3];
581     cave->x2 = intermissionsize[4];
582     cave->y2 = intermissionsize[5];
583   }
584   else
585   {
586     /* set to CaveSize */
587     cave->w = cavesize[0];
588     cave->h = cavesize[1];
589     cave->x1 = cavesize[2];
590     cave->y1 = cavesize[3];
591     cave->x2 = cavesize[4];
592     cave->y2 = cavesize[5];
593   }
594
595   /* process size at the beginning... as ratio types depend on this. */
596   value = hashtable_search(tags, "Size");
597   if (value)
598   {
599     cave_process_tags_func("Size", value, cave);
600     hashtable_remove(tags, "Size");
601   }
602
603   /* these are read from the hash table, but also have some implications */
604   /* we do not delete them from the hash table here; as _their values will be processed later_. */
605   /* here we only set their implicite meanings. */
606   /* these also set predictability */
607   if (hashtable_search(tags, "SlimePermeability"))
608     cave->slime_predictable = FALSE;
609
610   if (hashtable_search(tags, "SlimePermeabilityC64"))
611     cave->slime_predictable = TRUE;
612
613   /* these set scheduling type. framedelay takes precedence, if there are both; so we check it later. */
614   if (hashtable_search(tags, "CaveDelay"))
615   {
616     /* only set scheduling type, when it is not the gdash-default. */
617     /* this allows settings cavescheduling = bd1 in the [game] section, for example. */
618     /* in that case, this one will not overwrite it. */
619     if (cave->scheduling == GD_SCHEDULING_MILLISECONDS)
620       cave->scheduling = GD_SCHEDULING_PLCK;
621   }
622
623   if (hashtable_search(tags, "FrameTime"))
624     /* but if the cave has a frametime setting, always switch to milliseconds. */
625     cave->scheduling = GD_SCHEDULING_MILLISECONDS;
626
627   /* process all tags */
628   hashtable_foreach_remove(tags, (hashtable_remove_fn)cave_process_tags_func, cave);
629
630   /* and at the end, when read all tags (especially the size= tag) */
631   /* process map, if any. */
632   /* only report if map read is bigger than size= specified. */
633   /* some old bdcff files use smaller intermissions than the one specified. */
634   if (maplines)
635   {
636     int x, y, length = list_length(maplines);
637     List *iter;
638
639     /* create map and fill with initial border, in case that map strings are shorter or somewhat */
640     cave->map = gd_cave_map_new(cave, GdElement);
641
642     for (y = 0; y < cave->h; y++)
643       for (x = 0; x < cave->w; x++)
644         cave->map[y][x] = cave->initial_border;
645
646     if (length != cave->h && length != (cave->y2-cave->y1 + 1))
647       Warn("map error: cave height = %d (%d visible), map height = %d",
648            cave->h, cave->y2 - cave->y1 + 1, length);
649
650     for (iter = maplines, y = 0; y < length && iter != NULL; iter = iter->next, y++)
651     {
652       const char *line = iter->data;
653       int slen = strlen(line);
654
655       if (slen != cave->w && slen != (cave->x2 - cave->x1 + 1))
656         Warn("map error in row %d: cave width = %d (%d visible), map width = %d",
657              y, cave->w, cave->x2 - cave->x1 + 1, slen);
658
659       /* use number of cells from cave or string, whichever is smaller.
660          so will not overwrite array! */
661       for (x = 0; x < MIN(cave->w, slen); x++)
662         cave->map[y][x] = gd_get_element_from_character (line[x]);
663     }
664   }
665 }
666
667 /* sets the cavesize array to default values */
668 static void set_cavesize_defaults(void)
669 {
670   cavesize[0] = 40;
671   cavesize[1] = 22;
672   cavesize[2] = 0;
673   cavesize[3] = 0;
674   cavesize[4] = 39;
675   cavesize[5] = 21;
676 }
677
678 /* sets the cavesize array to default values */
679 static void set_intermissionsize_defaults(void)
680 {
681   intermissionsize[0] = 40;
682   intermissionsize[1] = 22;
683   intermissionsize[2] = 0;
684   intermissionsize[3] = 0;
685   intermissionsize[4] = 19;
686   intermissionsize[5] = 11;
687 }
688
689 boolean gd_caveset_load_from_bdcff(const char *contents)
690 {
691   char **lines;
692   int lineno;
693   GdCave *cave;
694   List *iter;
695   boolean reading_replay = FALSE;
696   boolean reading_map = FALSE;
697   boolean reading_mapcodes = FALSE;
698   boolean reading_highscore = FALSE;
699   boolean reading_objects = FALSE;
700   boolean reading_bdcff_demo = FALSE;
701   /* assume version to be 0.32, also when the file does not specify it explicitly */
702   GdString version_read = "0.32";
703   List *mapstrings = NULL;
704   int linenum;
705   HashTable *tags, *replay_tags;
706   GdObjectLevels levels = GD_OBJECT_LEVEL_ALL;
707   GdCave *default_cave;
708
709   gd_caveset_clear();
710
711   set_cavesize_defaults();
712   set_intermissionsize_defaults();
713   gd_create_char_to_element_table();
714
715   tags        = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
716   replay_tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
717
718   /* split into lines */
719   lines = getSplitStringArray (contents, "\n", 0);
720
721   /* attributes read will be set in cave. if no [cave]; they are stored
722      in the default cave; like in a [game] */
723   default_cave = gd_cave_new();
724   cave = default_cave;
725
726   linenum = getStringArrayLength(lines);
727
728   for (lineno = 0; lineno < linenum; lineno++)
729   {
730     char *line = lines[lineno];
731     char *r;
732
733     /* remove windows-nightmare \r-s */
734     while((r = strchr(line, '\r')))
735       strcpy(r, r + 1);
736
737     if (strlen (line) == 0)
738       continue;            /* skip empty lines */
739
740     /* just skip comments. be aware that map lines may start with a semicolon... */
741     if (!reading_map && line[0] == ';')
742       continue;
743
744     /* STARTING WITH A BRACKET [ IS A SECTION */
745     if (line[0] == '[')
746     {
747       if (strcasecmp(line, "[cave]") == 0)
748       {
749         /* new cave */
750         if (mapstrings)
751         {
752           Warn("incorrect file format: new [cave] section, but already read some map lines");
753           list_free(mapstrings);
754           mapstrings = NULL;
755         }
756
757         /* process any pending tags for game ... */
758         cave_process_tags(default_cave, tags, NULL);
759
760         /* ... to be able to create a copy for a new cave. */
761         cave = gd_cave_new_from_cave(default_cave);
762         gd_caveset = list_append (gd_caveset, cave);
763       }
764       else if (strcasecmp(line, "[/cave]") == 0)
765       {
766         cave_process_tags(cave, tags, mapstrings);
767         list_free(mapstrings);
768         mapstrings = NULL;
769
770         hashtable_foreach(tags, (hashtable_fn)cave_report_and_copy_unknown_tags_func, cave);
771         hashtable_remove_all(tags);
772
773         /* set this to point the pseudo-cave which holds default values */
774         cave = default_cave;
775       }
776       else if (strcasecmp(line, "[map]") == 0)
777       {
778         reading_map = TRUE;
779         if (mapstrings != NULL)
780         {
781           Warn("incorrect file format: new [map] section, but already read some map lines");
782           list_free(mapstrings);
783           mapstrings = NULL;
784         }
785       }
786       else if (strcasecmp(line, "[/map]") == 0)
787       {
788         reading_map = FALSE;
789       }
790       else if (strcasecmp(line, "[mapcodes]") == 0)
791       {
792         reading_mapcodes = TRUE;
793       }
794       else if (strcasecmp(line, "[/mapcodes]") == 0)
795       {
796         reading_mapcodes = FALSE;
797       }
798       else if (strcasecmp(line, "[highscore]") == 0)
799       {
800         reading_highscore = TRUE;
801       }
802       else if (strcasecmp(line, "[/highscore]") == 0)
803       {
804         reading_highscore = FALSE;
805       }
806       else if (strcasecmp(line, "[objects]") == 0)
807       {
808         reading_objects = TRUE;
809       }
810       else if (strcasecmp(line, "[/objects]") == 0)
811       {
812         reading_objects = FALSE;
813       }
814       else if (strcasecmp(line, "[demo]") == 0)
815       {
816         GdReplay *replay;
817
818         reading_bdcff_demo = TRUE;
819
820         if (cave != default_cave)
821         {
822           replay = gd_replay_new();
823           replay->saved = TRUE;
824           replay->success = TRUE;   /* we think that it is a successful demo */
825           cave->replays = list_append(cave->replays, replay);
826           gd_strcpy(replay->player_name, "???");    /* name not saved */
827         }
828         else
829         {
830           Warn("[demo] section must be in [cave] section!");
831         }
832       }
833       else if (strcasecmp(line, "[/demo]") == 0)
834       {
835         reading_bdcff_demo = FALSE;
836       }
837       else if (strcasecmp(line, "[replay]") == 0)
838       {
839         reading_replay = TRUE;
840       }
841       else if (strcasecmp(line, "[/replay]") == 0)
842       {
843         GdReplay *replay;
844
845         reading_replay = FALSE;
846         replay = gd_replay_new();
847
848         /* set "saved" flag, so this replay will be written when the caveset is saved again */
849         replay->saved = TRUE;
850         replay_process_tags(replay, replay_tags);
851
852 #if 1
853         /* BDCFF numbers levels from 1 to 5, but internally we number levels from 0 to 4 */
854         if (replay->level > 0)
855           replay->level--;
856 #endif
857
858         /* report any remaining unknown tags */
859         hashtable_foreach(replay_tags, (hashtable_fn)replay_report_unknown_tags_func, NULL);
860         hashtable_remove_all(replay_tags);
861
862         if (replay->movements->len != 0)
863         {
864           cave->replays = list_append(cave->replays, replay);
865         }
866         else
867         {
868           Warn("no movements in replay!");
869           gd_replay_free(replay);
870         }
871       }
872       /* GOSH i hate bdcff */
873       else if (strncasecmp(line, "[level=", strlen("[level=")) == 0)
874       {
875         int l[5];
876         int num;
877         char *nums;
878
879         /* there IS an equal sign, and we also skip that, so this points to the numbers */
880         nums = strchr(line, '=') + 1;
881         num = sscanf(nums, "%d,%d,%d,%d,%d", l + 0, l + 1, l + 2, l + 3, l + 4);
882         levels = 0;
883
884         if (num == 0)
885         {
886           Warn("invalid Levels tag: %s", line);
887           levels = GD_OBJECT_LEVEL_ALL;
888         }
889         else
890         {
891           int n;
892
893           for (n = 0; n < num; n++)
894           {
895             if (l[n] <= 5 && l[n] >= 1)
896               levels |= gd_levels_mask[l[n] - 1];
897             else
898               Warn("invalid level number %d", l[n]);
899           }
900         }
901       }
902       else if (strcasecmp(line, "[/level]") == 0)
903       {
904         levels = GD_OBJECT_LEVEL_ALL;
905       }
906       else if (strcasecmp(line, "[game]") == 0)
907       {
908       }
909       else if (strcasecmp(line, "[/game]") == 0)
910       {
911       }
912       else if (strcasecmp(line, "[BDCFF]") == 0)
913       {
914       }
915       else if (strcasecmp(line, "[/BDCFF]") == 0)
916       {
917       }
918       else
919       {
920         Warn("unknown section: \"%s\"", line);
921       }
922
923       continue;
924     }
925
926     if (reading_map)
927     {
928       /* just append to the mapstrings list. we will process it later */
929       mapstrings = list_append(mapstrings, line);
930
931       continue;
932     }
933
934     /* strip leading and trailing spaces AFTER checking if we are reading a map.
935        map lines might begin or end with spaces */
936     stripString(line);
937
938     if (reading_highscore)
939     {
940       int score;
941
942       if (sscanf(line, "%d", &score) != 1 || strchr(line, ' ') == NULL)
943       {    /* first word is the score */
944         Warn("highscore format incorrect");
945       }
946       else
947       {
948         if (cave == default_cave)
949           /* if we are reading the [game], add highscore to that one. */
950           /* from first space: the name */
951           gd_add_highscore(gd_caveset_data->highscore, strchr(line, ' ') + 1, score);
952         else
953           /* if a cave, add highscore to that. */
954           gd_add_highscore(cave->highscore, strchr(line, ' ') + 1, score);
955       }
956
957       continue;
958     }
959
960     /* read bdcff-style [demo], similar to a complete replay but cannot store like anything */
961     if (reading_bdcff_demo)
962     {
963       GdReplay *replay;
964       List *iter;
965
966       /* demo must be in [cave] section. we already showed an error message for this. */
967       if (cave == default_cave)
968         continue;
969
970       iter = list_last(cave->replays);
971
972       replay = (GdReplay *)iter->data;
973       replay_store_more_from_bdcff(replay, line);
974
975       continue;
976     }
977
978     if (reading_objects)
979     {
980       GdObject *new_object;
981
982       new_object = gd_object_new_from_string(line);
983       if (new_object)
984       {
985         new_object->levels = levels;    /* apply levels to new object */
986         cave->objects = list_append(cave->objects, new_object);
987       }
988       else
989       {
990         Error("invalid object specification: %s", line);
991       }
992
993       continue;
994     }
995
996     /* has an equal sign ->  some_attrib = parameters  type line. */
997     if (strchr (line, '=') != NULL)
998     {
999       char *attrib, *param;
1000
1001       attrib = line;                   /* attrib is from the first char */
1002       param = strchr(line, '=') + 1;   /* param is after equal sign */
1003       *strchr (line, '=') = 0;         /* delete equal sign - line is therefore splitted */
1004
1005       /* own tag: not too much thinking :P */
1006       if (reading_replay)
1007       {
1008         hashtable_insert(replay_tags, getStringCopy(attrib), getStringCopy(param));
1009       }
1010       else if (reading_mapcodes)
1011       {
1012         if (strcasecmp("Length", attrib) == 0)
1013         {
1014           /* we do not support map code width != 1 */
1015           if (strcmp(param, "1") != 0)
1016             Warn(_("Only one-character map codes are currently supported!"));
1017         }
1018         else
1019         {
1020           /* the first character of the attribute is the element code itself */
1021           gd_char_to_element[(int)attrib[0]] = gd_get_element_from_string(param);
1022         }
1023       }
1024       /* BDCFF version */
1025       else if (strcasecmp("Version", attrib) == 0)
1026       {
1027         gd_strcpy(version_read, param);
1028       }
1029       /* CAVES = x */
1030       else if (strcasecmp(attrib, "Caves") == 0)
1031       {
1032         /* BDCFF files sometimes state how many caves they have */
1033         /* we ignore this field. */
1034       }
1035       /* LEVELS = x */
1036       else if (strcasecmp(attrib, "Levels") == 0)
1037       {
1038         /* BDCFF files sometimes state how many levels they have */
1039         /* we ignore this field. */
1040       }
1041       else if (strcasecmp(attrib, "CaveSize") == 0)
1042       {
1043         int i;
1044
1045         i = sscanf(param, "%d %d %d %d %d %d",
1046                    cavesize + 0,
1047                    cavesize + 1,
1048                    cavesize + 2,
1049                    cavesize + 3,
1050                    cavesize + 4,
1051                    cavesize + 5);
1052
1053         /* allowed: 2 or 6 numbers */
1054         if (i == 2)
1055         {
1056           cavesize[2] = 0;
1057           cavesize[3] = 0;
1058           cavesize[4] = cavesize[0]-1;
1059           cavesize[5] = cavesize[1]-1;
1060         }
1061         else if (i != 6)
1062         {
1063           set_cavesize_defaults();
1064           Warn("invalid CaveSize tag: %s", line);
1065         }
1066       }
1067       else if (strcasecmp(attrib, "IntermissionSize") == 0)
1068       {
1069         int i;
1070
1071         i = sscanf(param, "%d %d %d %d %d %d",
1072                    intermissionsize + 0,
1073                    intermissionsize + 1,
1074                    intermissionsize + 2,
1075                    intermissionsize + 3,
1076                    intermissionsize + 4,
1077                    intermissionsize + 5);
1078
1079         /* allowed: 2 or 6 numbers */
1080         if (i == 2)
1081         {
1082           intermissionsize[2] = 0;
1083           intermissionsize[3] = 0;
1084           intermissionsize[4] = intermissionsize[0]-1;
1085           intermissionsize[5] = intermissionsize[1]-1;
1086         }
1087         else if (i != 6)
1088         {
1089           set_intermissionsize_defaults();
1090           Warn("invalid IntermissionSize tag: '%s'", line);
1091         }
1092       }
1093       else if (strcasecmp(attrib, "Effect") == 0)
1094       {
1095         /* CHECK IF IT IS AN EFFECT */
1096         char **params;
1097
1098         params = getSplitStringArray(param, " ", -1);
1099
1100         /* an effect command has two parameters */
1101         if (getStringArrayLength(params) == 2)
1102         {
1103           int i;
1104
1105           for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
1106           {
1107             /* we have to search for this effect */
1108             if (gd_cave_properties[i].type == GD_TYPE_EFFECT &&
1109                 strcasecmp(params[0], gd_cave_properties[i].identifier) == 0)
1110             {
1111               /* found identifier */
1112               void *value = STRUCT_MEMBER_P (cave, gd_cave_properties[i].offset);
1113
1114               *((GdElement *) value) = gd_get_element_from_string (params[1]);
1115               break;
1116             }
1117           }
1118
1119           /* if we didn't find first element name */
1120           if (gd_cave_properties[i].identifier == NULL)
1121           {
1122             /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
1123             if (strcasecmp(params[0], "BOUNCING_BOULDER") == 0)
1124               cave->stone_bouncing_effect = gd_get_element_from_string (params[1]);
1125             else if (strcasecmp(params[0], "EXPLOSION3S") == 0)
1126               cave->explosion_effect = gd_get_element_from_string(params[1]);
1127             /* falling with one l... */
1128             else if (strcasecmp(params[0], "STARTING_FALING_DIAMOND") == 0)
1129               cave->diamond_falling_effect = gd_get_element_from_string (params[1]);
1130             /* dirt lookslike */
1131             else if (strcasecmp(params[0], "DIRT") == 0)
1132               cave->dirt_looks_like = gd_get_element_from_string (params[1]);
1133             else if (strcasecmp(params[0], "HEXPANDING_WALL") == 0 && strcasecmp(params[1], "STEEL_HEXPANDING_WALL") == 0)
1134             {
1135               cave->expanding_wall_looks_like = O_STEEL;
1136             }
1137             else
1138               /* didn't find at all */
1139               Warn("invalid effect name '%s'", params[0]);
1140           }
1141         }
1142         else
1143           Warn("invalid effect specification '%s'", param);
1144
1145         freeStringArray(params);
1146       }
1147       else
1148       {
1149         /* no special handling: this is a normal attribute. */
1150
1151         if (cave == default_cave)
1152         {
1153           /* we are reading the [game] */
1154           if (attrib_is_valid_for_caveset(attrib))
1155           {
1156             /* if it is a caveset attrib, process it for the caveset. */
1157             struct_set_property(gd_caveset_data, gd_caveset_properties, attrib, param, 0);
1158           }
1159           else if (attrib_is_valid_for_cave(attrib))
1160           {
1161             /* it must be a default setting for all caves. is it a valid identifier? */
1162             /* yes, it is. add to the hash table, which will be copied for all caves. */
1163             hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param));
1164           }
1165           else
1166           {
1167             /* unknown setting - report. */
1168             Warn("invalid attribute for [game] '%s'", attrib);
1169           }
1170         }
1171         else
1172         {
1173           /* we are reading a [cave] */
1174           /* cave settings are immediately added to cave hash table. */
1175           /* if it is unknown, we have to remember it, and save it again. */
1176           hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param));
1177         }
1178       }
1179
1180       continue;
1181     }
1182
1183     Error("cannot parse line: %s", line);
1184   }
1185
1186   if (mapstrings)
1187   {
1188     Warn("incorrect file format: end of file, but still have some map lines read");
1189     list_free(mapstrings);
1190     mapstrings = NULL;
1191   }
1192
1193   /* the [game] section had some values which are default if not specified in [cave] sections. */
1194   /* these are used only for loading, so forget them now */
1195   if (default_cave->map)
1196     Warn(_("Invalid BDCFF: [game] section has a map"));
1197   if (default_cave->objects)
1198     Warn(_("Invalid BDCFF: [game] section has drawing objects defined"));
1199
1200   /* cleanup */
1201   freeStringArray(lines);
1202   hashtable_destroy(tags);
1203   hashtable_destroy(replay_tags);
1204   gd_cave_free(default_cave);
1205
1206   /* old bdcff files hack. explanation follows. */
1207   /* there were 40x22 caves in c64 bd, intermissions were also 40x22, but the visible */
1208   /* part was the upper left corner, 20x12. 40x22 caves are needed, as 20x12 caves would */
1209   /* look different (random cave elements needs the correct size.) */
1210   /* also, in older bdcff files, there is no size= tag. caves default to 40x22 and 20x12. */
1211   /* even the explicit drawrect and other drawing instructions, which did set up intermissions */
1212   /* to be 20x12, are deleted. very very bad decision. */
1213   /* here we try to detect and correct this. */
1214
1215   if (strEqual(version_read, "0.32"))
1216   {
1217     List *iter;
1218
1219     Warn("No BDCFF version, or 0.32. Using unspecified-intermission-size hack.");
1220
1221     for (iter = gd_caveset; iter != NULL; iter = iter->next)
1222     {
1223       GdCave *cave = (GdCave *)iter->data;
1224
1225       /* only applies to intermissions */
1226       /* not applied to mapped caves, as maps are filled with initial border, if the map read is smaller */
1227       if (cave->intermission && !cave->map)
1228       {
1229         /* we do not set the cave to 20x12, rather to 40x22 with 20x12 visible. */
1230         GdObject object;
1231
1232         cave->w = 40;
1233         cave->h = 22;
1234         cave->x1 = 0;
1235         cave->y1 = 0;
1236         cave->x2 = 19;
1237         cave->y2 = 11;
1238
1239         /* and cover the invisible area */
1240         object.type = GD_FILLED_RECTANGLE;
1241         object.x1 = 0;
1242         object.y1 = 11;    /* 11, because this will also be the border */
1243         object.x2 = 39;
1244         object.y2 = 21;
1245         object.element = cave->initial_border;
1246         object.fill_element = cave->initial_border;
1247
1248         cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object)));
1249
1250         object.x1 = 19;
1251         object.y1 = 0;    /* 19, as it is also the border */
1252
1253         cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object)));    /* another */
1254       }
1255     }
1256   }
1257
1258   if (!strEqual(version_read, BDCFF_VERSION))
1259     Warn("BDCFF version %s, loaded caveset may have errors.", version_read);
1260
1261   /* check for replays which are problematic */
1262   for (iter = gd_caveset; iter != NULL; iter = iter->next)
1263     gd_cave_check_replays((GdCave *)iter->data, TRUE, FALSE, FALSE);
1264
1265   /* if there was some error message - return fail XXX */
1266   return TRUE;
1267 }
1268
1269 /********************************************************************************
1270  *
1271  * BDCFF saving
1272  *
1273  */
1274
1275 #define GD_PTR_ARRAY_MINIMUM_INITIAL_SIZE       64
1276
1277 GdPtrArray *gd_ptr_array_sized_new(unsigned int size)
1278 {
1279   GdPtrArray *array = checked_calloc(sizeof(GdPtrArray));
1280
1281   array->data = checked_calloc(size * sizeof(void *));
1282   array->size_allocated = size;
1283   array->size_initial = size;
1284   array->size = 0;
1285
1286   return array;
1287 }
1288
1289 GdPtrArray *gd_ptr_array_new(void)
1290 {
1291   return gd_ptr_array_sized_new(GD_PTR_ARRAY_MINIMUM_INITIAL_SIZE);
1292 }
1293
1294 void gd_ptr_array_add(GdPtrArray *array, void *data)
1295 {
1296   if (array->size == array->size_allocated)
1297   {
1298     array->size_allocated += array->size_initial;
1299     array->data = checked_realloc(array->data, array->size_allocated * sizeof(void *));
1300   }
1301
1302   array->data[array->size++] = data;
1303 }
1304
1305 boolean gd_ptr_array_remove(GdPtrArray *array, void *data)
1306 {
1307   int i, j;
1308
1309   for (i = 0; i < array->size; i++)
1310   {
1311     if (array->data[i] == data)
1312     {
1313       checked_free(array->data[i]);
1314
1315       for (j = i; j < array->size - 1; j++)
1316         array->data[j] = array->data[j + 1];
1317
1318       array->size--;
1319
1320       return TRUE;
1321     }
1322   }
1323
1324   return FALSE;
1325 }
1326
1327 void gd_ptr_array_free(GdPtrArray *array, boolean free_data)
1328 {
1329   int i;
1330
1331   if (free_data)
1332   {
1333     for (i = 0; i < array->size; i++)
1334       checked_free(array->data[i]);
1335   }
1336
1337   checked_free(array);
1338 }
1339
1340 /* ratio: max cave size for GD_TYPE_RATIO. should be set to cave->w*cave->h when calling */
1341 static void save_properties(GdPtrArray *out, void *str, void *str_def,
1342                             const GdStructDescriptor *prop_desc, int ratio)
1343 {
1344   int i, j;
1345   boolean parameter_written = FALSE, should_write = FALSE;
1346   char *line = NULL;
1347   const char *identifier = NULL;
1348
1349   for (i = 0; prop_desc[i].identifier != NULL; i++)
1350   {
1351     void *value, *default_value;
1352
1353     if (prop_desc[i].type == GD_TAB || prop_desc[i].type == GD_LABEL)
1354       /* used only by the gui */
1355       continue;
1356
1357     /* these are handled explicitly */
1358     if (prop_desc[i].flags & GD_DONT_SAVE)
1359       continue;
1360
1361     /* string data */
1362     /* write together with identifier, as one string per line. */
1363     if (prop_desc[i].type == GD_TYPE_STRING)
1364     {
1365       /* treat strings as special - do not even write identifier if no string. */
1366       char *text = STRUCT_MEMBER_P(str, prop_desc[i].offset);
1367
1368       if (strlen(text) > 0)
1369         gd_ptr_array_add(out, getStringPrint("%s=%s", prop_desc[i].identifier, text));
1370
1371       continue;
1372     }
1373
1374     /* dynamic string: need to escape newlines */
1375     if (prop_desc[i].type == GD_TYPE_LONGSTRING)
1376     {
1377       char *string = STRUCT_MEMBER(char *, str, prop_desc[i].offset);
1378
1379       if (string != NULL && strlen(string) > 0)
1380       {
1381         char *escaped = getEscapedString(string);
1382
1383         gd_ptr_array_add(out, getStringPrint("%s=%s", prop_desc[i].identifier, escaped));
1384
1385         checked_free(escaped);
1386       }
1387
1388       continue;
1389     }
1390
1391     /* if identifier differs from the previous, write out the line collected, and start a new one */
1392     if (identifier == NULL || !strEqual(prop_desc[i].identifier, identifier))
1393     {
1394       if (should_write)
1395       {
1396         /* write lines only which carry information other than the default settings */
1397         gd_ptr_array_add(out, getStringCopy(line));
1398       }
1399
1400       if (prop_desc[i].type == GD_TYPE_EFFECT)
1401         setStringPrint(&line, "Effect=");
1402       else
1403         setStringPrint(&line, "%s=", prop_desc[i].identifier);
1404
1405       parameter_written = FALSE;    /* no value written yet */
1406       should_write = FALSE;
1407
1408       /* remember identifier */
1409       identifier = prop_desc[i].identifier;
1410     }
1411
1412     /* if we always save this identifier, remember now */
1413     if (prop_desc[i].flags & GD_ALWAYS_SAVE)
1414       should_write = TRUE;
1415
1416     value         = STRUCT_MEMBER_P(str,     prop_desc[i].offset);
1417     default_value = STRUCT_MEMBER_P(str_def, prop_desc[i].offset);
1418
1419     for (j = 0; j < prop_desc[i].count; j++)
1420     {
1421       /* separate values by spaces. of course no space required for the first one */
1422       if (parameter_written)
1423         appendStringPrint(&line, " ");
1424
1425       parameter_written = TRUE;    /* at least one value written, so write space the next time */
1426
1427       switch (prop_desc[i].type)
1428       {
1429         case GD_TYPE_BOOLEAN:
1430           appendStringPrint(&line, "%s", ((boolean *) value)[j] ? "true" : "false");
1431           if (((boolean *) value)[j] != ((boolean *) default_value)[j])
1432             should_write = TRUE;
1433           break;
1434
1435         case GD_TYPE_INT:
1436           appendStringPrint(&line, "%d", ((int *) value)[j]);
1437           if (((int *) value)[j] != ((int *) default_value)[j])
1438             should_write = TRUE;
1439           break;
1440
1441         case GD_TYPE_RATIO:
1442           appendStringPrint(&line, "%6.5f", ((int *) value)[j] / (double)ratio);
1443           if (((int *) value)[j] != ((int *) default_value)[j])
1444             should_write = TRUE;
1445           break;
1446
1447         case GD_TYPE_PROBABILITY:
1448           appendStringPrint(&line, "%6.5f", ((int *) value)[j] / 1E6);   /* probabilities are stored as *1E6 */
1449
1450           if (((double *) value)[j] != ((double *) default_value)[j])
1451             should_write = TRUE;
1452           break;
1453
1454         case GD_TYPE_ELEMENT:
1455           appendStringPrint(&line, "%s", gd_elements[((GdElement *) value)[j]].filename);
1456           if (((GdElement *) value)[j] != ((GdElement *) default_value)[j])
1457             should_write = TRUE;
1458           break;
1459
1460         case GD_TYPE_EFFECT:
1461           /* for effects, the property identifier is the effect name. "Effect=" is hardcoded; see above. */
1462           appendStringPrint(&line, "%s %s", prop_desc[i].identifier, gd_elements[((GdElement *) value)[j]].filename);
1463           if (((GdElement *) value)[j] != ((GdElement *) default_value)[j])
1464             should_write = TRUE;
1465           break;
1466
1467         case GD_TYPE_COLOR:
1468           appendStringPrint(&line, "%s", gd_color_get_string(((GdColor *) value)[j]));
1469           should_write = TRUE;
1470           break;
1471
1472         case GD_TYPE_DIRECTION:
1473           appendStringPrint(&line, "%s", gd_direction_get_filename(((GdDirection *) value)[j]));
1474           if (((GdDirection *) value)[j] != ((GdDirection *) default_value)[j])
1475             should_write = TRUE;
1476           break;
1477
1478         case GD_TYPE_SCHEDULING:
1479           appendStringPrint(&line, "%s", gd_scheduling_get_filename(((GdScheduling *) value)[j]));
1480           if (((GdScheduling *) value)[j] != ((GdScheduling *) default_value)[j])
1481             should_write = TRUE;
1482           break;
1483
1484         case GD_TAB:
1485         case GD_LABEL:
1486           /* used by the editor ui */
1487           break;
1488
1489         case GD_TYPE_STRING:
1490         case GD_TYPE_LONGSTRING:
1491           /* never reached */
1492           break;
1493       }
1494     }
1495   }
1496
1497   /* write remaining data */
1498   if (should_write)
1499     gd_ptr_array_add(out, getStringCopy(line));
1500
1501   checked_free(line);
1502 }
1503
1504 /* remove a line from the list of strings. */
1505 /* the prefix should be a property; add an equal sign! so properties which have names like
1506    "slime" and "slimeproperties" won't match each other. */
1507 static void cave_properties_remove(GdPtrArray *out, const char *prefix)
1508 {
1509   int i;
1510
1511   if (!strSuffix(prefix, "="))
1512     Warn("string '%s' should have suffix '='", prefix);
1513
1514   /* search for strings which match, and set them to NULL. */
1515   /* also free them. */
1516   for (i = 0; i < out->size; i++)
1517   {
1518     if (strPrefix(gd_ptr_array_index(out, i), prefix))
1519     {
1520       checked_free(gd_ptr_array_index(out, i));
1521       gd_ptr_array_index(out, i) = NULL;
1522     }
1523   }
1524
1525   /* remove all "null" occurrences */
1526   while (gd_ptr_array_remove(out, NULL))
1527     ;
1528 }
1529
1530 /* output properties of a structure to a file. */
1531 /* list_foreach func, so "out" is the last parameter! */
1532 static void caveset_save_cave_func(GdCave *cave, GdPtrArray *out)
1533 {
1534   GdCave *default_cave;
1535   GdPtrArray *this_out;
1536   char *line = NULL;    /* used for various purposes */
1537   int i;
1538
1539   gd_ptr_array_add(out, getStringCopy(""));
1540   gd_ptr_array_add(out, getStringCopy("[cave]"));
1541
1542   /* first add the properties to a local ptr array. */
1543   /* later, some are deleted (slime permeability, for example) - this is needed because of the inconsistencies of the bdcff. */
1544   /* finally, remaining will be added to the normal "out" array. */
1545   this_out = gd_ptr_array_new();
1546
1547   default_cave = gd_cave_new();
1548   save_properties(this_out, cave, default_cave, gd_cave_properties, cave->w * cave->h);
1549   gd_cave_free(default_cave);
1550
1551   /* properties which are handled explicitly. these cannot be handled easily above,
1552      as they have some special meaning. for example, slime_permeability=x sets permeability to
1553      x, and sets predictable to false. bdcff format is simply inconsistent in these aspects. */
1554
1555   /* slime permeability is always set explicitly, as it also sets predictability. */
1556   if (cave->slime_predictable)
1557     /* if slime is predictable, remove permeab. flag, as that would imply unpredictable slime. */
1558     cave_properties_remove(this_out, "SlimePermeability=");
1559   else
1560     /* if slime is UNpredictable, remove permeabc64 flag, as that would imply predictable slime. */
1561     cave_properties_remove(this_out, "SlimePermeabilityC64=");
1562
1563   /* add tags to output, and free local array */
1564   for (i = 0; i < this_out->size; i++)
1565     gd_ptr_array_add(out, gd_ptr_array_index(this_out, i));
1566
1567   /* do not free data pointers, which were just added to array "out" */
1568   gd_ptr_array_free(this_out, FALSE);
1569
1570 #if 0
1571   /* save unknown tags as they are */
1572   if (cave->tags)
1573   {
1574     List *hashkeys;
1575     List *iter;
1576
1577     hashkeys = g_hash_table_get_keys(cave->tags);
1578     for (iter = hashkeys; iter != NULL; iter = iter->next)
1579     {
1580       gchar *key = (gchar *)iter->data;
1581
1582       gd_ptr_array_add(out, getStringPrint("%s=%s", key, (const char *) g_hash_table_lookup(cave->tags, key)));
1583     }
1584
1585     list_free(hashkeys);
1586   }
1587 #endif
1588
1589   /* map */
1590   if (cave->map)
1591   {
1592     int x, y;
1593
1594     gd_ptr_array_add(out, getStringCopy(""));
1595     gd_ptr_array_add(out, getStringCopy("[map]"));
1596
1597     line = checked_calloc(cave->w + 1);
1598
1599     /* save map */
1600     for (y = 0; y < cave->h; y++)
1601     {
1602       for (x = 0; x < cave->w; x++)
1603       {
1604         /* check if character is non-zero; the ...save() should have assigned a character to every element */
1605         if (gd_elements[cave->map[y][x]].character_new == 0)
1606           Warn("gd_elements[cave->map[y][x]].character_new should be non-zero");
1607
1608         line[x] = gd_elements[cave->map[y][x]].character_new;
1609       }
1610
1611       gd_ptr_array_add(out, getStringCopy(line));
1612     }
1613
1614     gd_ptr_array_add(out, getStringCopy("[/map]"));
1615   }
1616
1617   /* save drawing objects */
1618   if (cave->objects)
1619   {
1620     List *listiter;
1621
1622     gd_ptr_array_add(out, getStringCopy(""));
1623     gd_ptr_array_add(out, getStringCopy("[objects]"));
1624
1625     for (listiter = cave->objects; listiter; listiter = list_next(listiter))
1626     {
1627       GdObject *object = listiter->data;
1628       char *text;
1629
1630       /* not for all levels? */
1631       if (object->levels != GD_OBJECT_LEVEL_ALL)
1632       {
1633         int i;
1634         boolean once;    /* true if already written one number */
1635
1636         setStringPrint(&line, "[Level=");
1637         once = FALSE;
1638
1639         for (i = 0; i < 5; i++)
1640         {
1641           if (object->levels & gd_levels_mask[i])
1642           {
1643             if (once)    /* if written at least one number so far, we need a comma */
1644               appendStringPrint(&line, ",");
1645
1646             appendStringPrint(&line, "%d", i+1);
1647             once = TRUE;
1648           }
1649         }
1650
1651         appendStringPrint(&line, "]");
1652         gd_ptr_array_add(out, getStringCopy(line));
1653       }
1654
1655       text = gd_object_get_bdcff(object);
1656       gd_ptr_array_add(out, getStringCopy(text));
1657       checked_free(text);
1658
1659       if (object->levels != GD_OBJECT_LEVEL_ALL)
1660         gd_ptr_array_add(out, getStringCopy("[/Level]"));
1661     }
1662
1663     gd_ptr_array_add(out, getStringCopy("[/objects]"));
1664   }
1665
1666   gd_ptr_array_add(out, getStringCopy("[/cave]"));
1667
1668   checked_free(line);
1669 }
1670
1671 /* save cave in bdcff format. */
1672 /* "out" will be added lines of bdcff description. */
1673 GdPtrArray *gd_caveset_save_to_bdcff(void)
1674 {
1675   GdPtrArray *out = gd_ptr_array_sized_new(512);
1676   GdCavesetData *default_caveset;
1677   boolean write_mapcodes = FALSE;
1678   List *iter;
1679   int i;
1680
1681   /* check if we need an own mapcode table ------ */
1682   /* copy original characters to character_new fields; new elements will be added to that one */
1683   for (i = 0; i < O_MAX; i++)
1684     gd_elements[i].character_new = gd_elements[i].character;
1685
1686   /* also regenerate this table as we use it */
1687   gd_create_char_to_element_table();
1688
1689   /* check all caves */
1690   for (iter = gd_caveset; iter != NULL; iter = iter->next)
1691   {
1692     GdCave *cave = (GdCave *)iter->data;
1693
1694     /* if they have a map (random elements+object based maps do not need characters) */
1695     if (cave->map)
1696     {
1697       int x, y;
1698
1699       /* check every element of map */
1700       for(y = 0; y < cave->h; y++)
1701         for (x = 0; x < cave->w; x++)
1702         {
1703           GdElement e = cave->map[y][x];
1704
1705           /* if no character assigned */
1706           if (gd_elements[e].character_new == 0)
1707           {
1708             int j;
1709
1710             /* we found at least one, so later we have to write the mapcodes */
1711             write_mapcodes = TRUE;
1712
1713             /* find a character which is not yet used for any element */
1714             for (j = 32; j < 128; j++)
1715             {
1716               /* the string contains the characters which should not be used. */
1717               if (strchr("<>&[]/=\\", j) == NULL && gd_char_to_element[j] == O_UNKNOWN)
1718                 break;
1719             }
1720
1721             /* if no more space... XXX we should rather report to the user */
1722             if (j == 128)
1723               Warn("variable j should be != 128");
1724
1725             gd_elements[e].character_new = j;
1726
1727             /* we also record to this table, as we use it ^^ a few lines above */
1728             gd_char_to_element[j] = e;
1729           }
1730         }
1731     }
1732   }
1733
1734   gd_ptr_array_add(out, getStringCopy("[BDCFF]"));
1735   gd_ptr_array_add(out, getStringPrint("Version=%s", BDCFF_VERSION));
1736
1737   /* this flag was set above if we need to write mapcodes */
1738   if (write_mapcodes)
1739   {
1740     int i;
1741
1742     gd_ptr_array_add(out, getStringCopy("[mapcodes]"));
1743     gd_ptr_array_add(out, getStringCopy("Length=1"));
1744
1745     for (i = 0; i < O_MAX; i++)
1746     {
1747       /* if no character assigned by specification BUT (AND) we assigned one */
1748       if (gd_elements[i].character == 0 && gd_elements[i].character_new != 0)
1749         gd_ptr_array_add(out, getStringPrint("%c=%s", gd_elements[i].character_new, gd_elements[i].filename));
1750     }
1751
1752     gd_ptr_array_add(out, getStringCopy("[/mapcodes]"));
1753   }
1754
1755   gd_ptr_array_add(out, getStringCopy("[game]"));
1756
1757   default_caveset = gd_caveset_data_new();
1758   save_properties(out, gd_caveset_data, default_caveset, gd_caveset_properties, 0);
1759   gd_caveset_data_free(default_caveset);
1760   gd_ptr_array_add(out, getStringCopy("Levels=5"));
1761
1762   list_foreach(gd_caveset, (list_fn)caveset_save_cave_func, out);
1763
1764   gd_ptr_array_add(out, getStringCopy("[/game]"));
1765   gd_ptr_array_add(out, getStringCopy("[/BDCFF]"));
1766
1767   /* saved to ptrarray */
1768   return out;
1769 }