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