2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
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.
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.
18 #include <glib/gi18n.h>
25 #define BDCFF_VERSION "0.5"
27 /* these are used for bdcff loading, storing the sizes of caves */
28 static int cavesize[6], intermissionsize[6];
30 static boolean replay_store_from_bdcff(GdReplay *replay, const char *str)
33 boolean up, down, left, right;
34 boolean fire, suicide;
35 const char *num = NULL;
38 fire = suicide = up = down = left = right = FALSE;
40 for (i = 0; str[i] != 0; i++)
76 /* do nothing, as all other movements are false */
81 /* bdcff 'combined' flags. do nothing. */
85 if (g_ascii_isdigit(str[i]))
93 dir = gd_direction_from_keypress(up, down, left, right);
97 sscanf(num, "%d", &count);
99 for (i = 0; i < count; i++)
100 gd_replay_store_movement(replay, dir, fire, suicide);
105 static boolean attrib_is_valid_for_cave(const char *attrib)
109 /* bdcff engine flag............ */
110 if (strcasecmp(attrib, "Engine")==0)
113 /* old flags - for compatibility */
114 if (strcasecmp(attrib, "BD1Scheduling")==0)
117 if (strcasecmp(attrib, "SnapExplosions")==0)
120 if (strcasecmp(attrib, "AmoebaProperties")==0)
123 /* search in property database */
124 for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
125 if (strcasecmp(gd_cave_properties[i].identifier, attrib) == 0)
131 static boolean attrib_is_valid_for_caveset(const char *attrib)
135 /* search in property database */
136 for (i = 0; gd_caveset_properties[i].identifier != NULL; i++)
137 if (strcasecmp(gd_caveset_properties[i].identifier, attrib) == 0)
143 static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_desc,
144 const char *attrib, const char *param, int ratio)
148 boolean identifier_found;
153 params = g_strsplit_set(param, " ", -1);
154 paramcount = g_strv_length(params);
155 identifier_found = FALSE;
157 /* check all known tags. do not exit this loop if identifier_found == true...
158 as there are more lines in the array which have the same identifier. */
161 for (i = 0; prop_desc[i].identifier != NULL; i++)
163 if (strcasecmp(prop_desc[i].identifier, attrib) == 0)
165 /* found the identifier */
166 gpointer value = G_STRUCT_MEMBER_P(str, prop_desc[i].offset);
168 /* these point to the same, but to avoid the awkward cast syntax */
170 GdElement *evalue = value;
171 GdDirection *dvalue = value;
172 GdScheduling *svalue = value;
173 boolean *bvalue = value;
176 identifier_found = TRUE;
178 if (prop_desc[i].type == GD_TYPE_STRING)
180 /* strings are treated different, as occupy the whole length of the line */
181 gd_strcpy(value, param);
183 /* remember this to skip checking the number of parameters at the end of the function */
189 if (prop_desc[i].type == GD_TYPE_LONGSTRING)
191 GString *str = *(GString **)value;
194 compressed = g_strcompress(param);
195 g_string_assign(str, compressed);
198 /* remember this to skip checking the number of parameters at the end of the function */
204 /* not a string, so use scanf calls */
205 /* ALSO, if no more parameters to process, exit loop */
206 for (j = 0; j < prop_desc[i].count && params[paramindex] != NULL; j++)
208 boolean success = FALSE;
211 switch (prop_desc[i].type)
213 case GD_TYPE_LONGSTRING:
221 case GD_TYPE_BOOLEAN:
222 success = sscanf(params[paramindex], "%d", &bvalue[j]) == 1;
225 if (strcasecmp(params[paramindex], "true") == 0 ||
226 strcasecmp(params[paramindex], "on") == 0 ||
227 strcasecmp(params[paramindex], "yes") == 0)
232 else if (strcasecmp(params[paramindex], "false") == 0 ||
233 strcasecmp(params[paramindex], "off") == 0 ||
234 strcasecmp(params[paramindex], "no") == 0)
241 /* if we are processing an array, fill other values with these.
242 if there are other values specified, those will be overwritten. */
244 for (k = j + 1; k < prop_desc[i].count; k++)
245 bvalue[k] = bvalue[j];
250 success = sscanf(params[paramindex], "%d", &ivalue[j]) == 1;
252 /* copy to other if array */
253 for (k = j + 1; k < prop_desc[i].count; k++)
254 ivalue[k] = ivalue[j];
258 case GD_TYPE_PROBABILITY:
259 res = g_ascii_strtod(params[paramindex], NULL);
260 if (errno == 0 && res >= 0 && res <= 1)
262 /* fill all remaining items in array - may be only one */
263 for (k = j; k < prop_desc[i].count; k++)
264 /* probabilities are stored inside as ppm (1E6) */
265 ivalue[k] = res * 1E6 + 0.5;
273 res = g_ascii_strtod (params[paramindex], NULL);
274 if (errno == 0 && res >= 0 && res <= 1)
276 for (k = j; k < prop_desc[i].count; k++)
277 ivalue[k] = (int)(res * ratio + 0.5);
284 case GD_TYPE_ELEMENT:
285 evalue[j] = gd_get_element_from_string(params[paramindex]);
287 /* copy to all remaining elements in array */
288 for (k = j + 1; k < prop_desc[i].count; k++)
289 evalue[k] = evalue[j];
291 /* this shows error message on its own, do treat as always succeeded */
295 case GD_TYPE_DIRECTION:
296 dvalue[j] = gd_direction_from_string(params[paramindex]);
297 /* copy to all remaining items in array */
298 for (k = j + 1; k < prop_desc[i].count; k++)
299 dvalue[k] = dvalue[j];
304 case GD_TYPE_SCHEDULING:
305 svalue[j] = gd_scheduling_from_string(params[paramindex]);
306 /* copy to all remaining items in array */
307 for (k = j + 1; k < prop_desc[i].count; k++)
308 svalue[k] = svalue[j];
310 /* if there was an error, already reported by gd_scheduling_from_string */
316 /* shoud have handled this elsewhere */
321 paramindex++; /* go to next parameter to process */
323 Warn("invalid parameter '%s' for attribute %s", params[paramindex], attrib);
328 /* if we found the identifier, but still could not process all parameters... */
329 /* of course, not for strings, as the whole line is the string */
330 if (identifier_found && !was_string && paramindex < paramcount)
331 Warn("excess parameters for attribute '%s': '%s'", attrib, params[paramindex]);
335 return identifier_found;
338 /********************************************************************************
344 static boolean replay_store_more_from_bdcff(GdReplay *replay, const char *param)
348 boolean result = TRUE;
350 split = g_strsplit_set(param, " ", -1);
352 for (i = 0; split[i] != 0; i++)
353 result = result && replay_store_from_bdcff(replay, split[i]);
360 /* report all remaining tags; called after the above function. */
361 static void replay_report_unknown_tags_func(const char *attrib, const char *param, gpointer data)
363 Warn("unknown replay tag '%s'", attrib);
366 /* a GHashTable foreach func.
367 keys are attribs; values are params;
368 the user data is the cave the hash table belongs to. */
369 static boolean replay_process_tags_func(const char *attrib, const char *param, GdReplay *replay)
371 boolean identifier_found = FALSE;
374 if (strcasecmp(attrib, "Movements") == 0)
376 identifier_found = TRUE;
377 replay_store_more_from_bdcff(replay, param);
382 /* 0: for ratio types; not used */
383 identifier_found = struct_set_property(replay, gd_replay_properties,
387 /* a ghrfunc should return true if the identifier is to be removed */
388 return identifier_found;
392 static void replay_process_tags(GdReplay *replay, GHashTable *tags)
394 /* process all tags */
395 g_hash_table_foreach_remove(tags, (GHRFunc) replay_process_tags_func, replay);
398 /* a GHashTable foreach func.
399 keys are attribs; values are params;
400 the user data is the cave the hash table belongs to. */
401 static boolean cave_process_tags_func(const char *attrib, const char *param, GdCave *cave)
404 boolean identifier_found;
406 params = g_strsplit_set(param, " ", -1);
407 identifier_found = FALSE;
409 if (strcasecmp(attrib, "SnapExplosions") == 0)
411 /* handle compatibility with old snapexplosions flag */
413 identifier_found = TRUE;
415 if (strcasecmp(param, "true") == 0)
417 cave->snap_element = O_EXPLODE_1;
419 else if (strcasecmp(param, "false") == 0)
421 cave->snap_element = O_SPACE;
425 Warn("invalid param for '%s': '%s'", attrib, param);
428 else if (strcasecmp(attrib, "BD1Scheduling") == 0)
430 /* handle compatibility with old bd1scheduling flag */
432 identifier_found = TRUE;
434 if (strcasecmp(param, "true") == 0)
436 if (cave->scheduling == GD_SCHEDULING_PLCK)
437 cave->scheduling = GD_SCHEDULING_BD1;
440 else if (strcasecmp(attrib, "Engine") == 0)
442 /* handle bdcff engine flag */
444 identifier_found = TRUE;
446 GdEngine engine = gd_cave_get_engine_from_string(param);
448 if (engine == GD_ENGINE_INVALID)
449 Warn(_("invalid parameter \"%s\" for attribute %s"), param, attrib);
451 gd_cave_set_engine_defaults(cave, engine);
453 else if (strcasecmp(attrib, "AmoebaProperties") == 0)
455 /* handle compatibility with old AmoebaProperties flag */
457 GdElement elem1 = O_STONE, elem2 = O_DIAMOND;
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;
465 else if (strcasecmp(attrib, "Colors") == 0)
467 /* colors attribute is a mess, have to process explicitly */
469 /* Colors = [border background] foreground1 foreground2 foreground3 [amoeba slime] */
470 identifier_found = TRUE;
472 cave->colorb = GD_GDASH_BLACK; /* border - black */
473 cave->color0 = GD_GDASH_BLACK; /* background - black */
474 cave->color1 = GD_GDASH_RED;
475 cave->color2 = GD_GDASH_PURPLE;
476 cave->color3 = GD_GDASH_YELLOW;
477 cave->color4 = cave->color3; /* amoeba */
478 cave->color5 = cave->color1; /* slime */
482 identifier_found = struct_set_property(cave, gd_cave_properties, attrib, param, cave->w * cave->h);
487 /* a ghrfunc should return true if the identifier is to be removed */
488 return identifier_found;
491 /* report all remaining tags; called after the above function. */
492 static void cave_report_and_copy_unknown_tags_func(char *attrib, char *param, gpointer data)
494 GdCave *cave = (GdCave *)data;
496 Warn("unknown tag '%s'", attrib);
498 g_hash_table_insert(cave->tags, g_strdup(attrib), g_strdup(param));
501 /* having read all strings belonging to the cave, process it. */
502 static void cave_process_tags(GdCave *cave, GHashTable *tags, GList *maplines)
506 /* first check cave name, so we can report errors correctly (saying that GdCave xy: error foobar) */
507 value = g_hash_table_lookup(tags, "Name");
509 cave_process_tags_func("Name", value, cave);
511 /* process lame engine tag first so its settings may be overwritten later */
512 value = g_hash_table_lookup(tags, "Engine");
515 cave_process_tags_func("Engine", value, cave);
516 g_hash_table_remove(tags, "Engine");
519 /* check if this is an intermission, so we can set to cavesize or intermissionsize */
520 value = g_hash_table_lookup(tags, "Intermission");
523 cave_process_tags_func("Intermission", value, cave);
524 g_hash_table_remove(tags, "Intermission");
527 if (cave->intermission)
529 /* set to IntermissionSize */
530 cave->w = intermissionsize[0];
531 cave->h = intermissionsize[1];
532 cave->x1 = intermissionsize[2];
533 cave->y1 = intermissionsize[3];
534 cave->x2 = intermissionsize[4];
535 cave->y2 = intermissionsize[5];
539 /* set to CaveSize */
540 cave->w = cavesize[0];
541 cave->h = cavesize[1];
542 cave->x1 = cavesize[2];
543 cave->y1 = cavesize[3];
544 cave->x2 = cavesize[4];
545 cave->y2 = cavesize[5];
548 /* process size at the beginning... as ratio types depend on this. */
549 value = g_hash_table_lookup(tags, "Size");
552 cave_process_tags_func("Size", value, cave);
553 g_hash_table_remove(tags, "Size");
556 /* these are read from the hash table, but also have some implications */
557 /* we do not delete them from the hash table here; as _their values will be processed later_. */
558 /* here we only set their implicite meanings. */
559 /* these also set predictability */
560 if (g_hash_table_lookup(tags, "SlimePermeability"))
561 cave->slime_predictable = FALSE;
563 if (g_hash_table_lookup(tags, "SlimePermeabilityC64"))
564 cave->slime_predictable = TRUE;
566 /* these set scheduling type. framedelay takes precedence, if there are both; so we check it later. */
567 if (g_hash_table_lookup(tags, "CaveDelay"))
569 /* only set scheduling type, when it is not the gdash-default. */
570 /* this allows settings cavescheduling = bd1 in the [game] section, for example. */
571 /* in that case, this one will not overwrite it. */
572 if (cave->scheduling == GD_SCHEDULING_MILLISECONDS)
573 cave->scheduling = GD_SCHEDULING_PLCK;
576 if (g_hash_table_lookup(tags, "FrameTime"))
577 /* but if the cave has a frametime setting, always switch to milliseconds. */
578 cave->scheduling = GD_SCHEDULING_MILLISECONDS;
580 /* process all tags */
581 g_hash_table_foreach_remove(tags, (GHRFunc) cave_process_tags_func, cave);
583 /* and at the end, when read all tags (especially the size= tag) */
584 /* process map, if any. */
585 /* only report if map read is bigger than size= specified. */
586 /* some old bdcff files use smaller intermissions than the one specified. */
589 int x, y, length = g_list_length(maplines);
592 /* create map and fill with initial border, in case that map strings are shorter or somewhat */
593 cave->map = gd_cave_map_new(cave, GdElement);
595 for (y = 0; y < cave->h; y++)
596 for (x = 0; x < cave->w; x++)
597 cave->map[y][x] = cave->initial_border;
599 if (length != cave->h && length != (cave->y2-cave->y1 + 1))
600 Warn("map error: cave height = %d (%d visible), map height = %d",
601 cave->h, cave->y2 - cave->y1 + 1, length);
603 for (iter = maplines, y = 0; y < length && iter != NULL; iter = iter->next, y++)
605 const char *line = iter->data;
606 int slen = strlen(line);
608 if (slen != cave->w && slen != (cave->x2 - cave->x1 + 1))
609 Warn("map error in row %d: cave width = %d (%d visible), map width = %d",
610 y, cave->w, cave->x2 - cave->x1 + 1, slen);
612 /* use number of cells from cave or string, whichever is smaller.
613 so will not overwrite array! */
614 for (x = 0; x < MIN(cave->w, slen); x++)
615 cave->map[y][x] = gd_get_element_from_character (line[x]);
620 /* sets the cavesize array to default values */
621 static void set_cavesize_defaults(void)
631 /* sets the cavesize array to default values */
632 static void set_intermissionsize_defaults(void)
634 intermissionsize[0] = 40;
635 intermissionsize[1] = 22;
636 intermissionsize[2] = 0;
637 intermissionsize[3] = 0;
638 intermissionsize[4] = 19;
639 intermissionsize[5] = 11;
642 boolean gd_caveset_load_from_bdcff(const char *contents)
648 boolean reading_replay = FALSE;
649 boolean reading_map = FALSE;
650 boolean reading_mapcodes = FALSE;
651 boolean reading_highscore = FALSE;
652 boolean reading_objects = FALSE;
653 boolean reading_bdcff_demo = FALSE;
654 /* assume version to be 0.32, also when the file does not specify it explicitly */
655 GdString version_read = "0.32";
656 GList *mapstrings = NULL;
658 GHashTable *tags, *replay_tags;
659 GdObjectLevels levels = GD_OBJECT_LEVEL_ALL;
660 GdCave *default_cave;
664 set_cavesize_defaults();
665 set_intermissionsize_defaults();
666 gd_create_char_to_element_table();
668 tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free);
669 replay_tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free);
671 /* split into lines */
672 lines = g_strsplit_set (contents, "\n", 0);
674 /* attributes read will be set in cave. if no [cave]; they are stored
675 in the default cave; like in a [game] */
676 default_cave = gd_cave_new();
679 linenum = g_strv_length(lines);
681 for (lineno = 0; lineno < linenum; lineno++)
683 char *line = lines[lineno];
686 /* remove windows-nightmare \r-s */
687 while((r = strchr(line, '\r')))
690 if (strlen (line) == 0)
691 continue; /* skip empty lines */
693 /* just skip comments. be aware that map lines may start with a semicolon... */
694 if (!reading_map && line[0] == ';')
697 /* STARTING WITH A BRACKET [ IS A SECTION */
700 if (strcasecmp(line, "[cave]") == 0)
705 Warn("incorrect file format: new [cave] section, but already read some map lines");
706 g_list_free(mapstrings);
710 /* process any pending tags for game ... */
711 cave_process_tags(default_cave, tags, NULL);
713 /* ... to be able to create a copy for a new cave. */
714 cave = gd_cave_new_from_cave(default_cave);
715 gd_caveset = g_list_append (gd_caveset, cave);
717 else if (strcasecmp(line, "[/cave]") == 0)
719 cave_process_tags(cave, tags, mapstrings);
720 g_list_free(mapstrings);
723 if (g_hash_table_size(tags) != 0)
724 g_hash_table_foreach(tags, (GHFunc) cave_report_and_copy_unknown_tags_func, cave);
725 g_hash_table_remove_all(tags);
726 /* set this to point the pseudo-cave which holds default values */
729 else if (strcasecmp(line, "[map]") == 0)
732 if (mapstrings != NULL)
734 Warn("incorrect file format: new [map] section, but already read some map lines");
735 g_list_free(mapstrings);
739 else if (strcasecmp(line, "[/map]") == 0)
743 else if (strcasecmp(line, "[mapcodes]") == 0)
745 reading_mapcodes = TRUE;
747 else if (strcasecmp(line, "[/mapcodes]") == 0)
749 reading_mapcodes = FALSE;
751 else if (strcasecmp(line, "[highscore]") == 0)
753 reading_highscore = TRUE;
755 else if (strcasecmp(line, "[/highscore]") == 0)
757 reading_highscore = FALSE;
759 else if (strcasecmp(line, "[objects]") == 0)
761 reading_objects = TRUE;
763 else if (strcasecmp(line, "[/objects]") == 0)
765 reading_objects = FALSE;
767 else if (strcasecmp(line, "[demo]") == 0)
771 reading_bdcff_demo = TRUE;
773 if (cave != default_cave)
775 replay = gd_replay_new();
776 replay->saved = TRUE;
777 replay->success = TRUE; /* we think that it is a successful demo */
778 cave->replays = g_list_append(cave->replays, replay);
779 gd_strcpy(replay->player_name, "???"); /* name not saved */
783 Warn("[demo] section must be in [cave] section!");
786 else if (strcasecmp(line, "[/demo]") == 0)
788 reading_bdcff_demo = FALSE;
790 else if (strcasecmp(line, "[replay]") == 0)
792 reading_replay = TRUE;
794 else if (strcasecmp(line, "[/replay]") == 0)
798 reading_replay = FALSE;
799 replay = gd_replay_new();
801 /* set "saved" flag, so this replay will be written when the caveset is saved again */
802 replay->saved = TRUE;
803 replay_process_tags(replay, replay_tags);
806 /* BDCFF numbers levels from 1 to 5, but internally we number levels from 0 to 4 */
807 if (replay->level > 0)
811 /* report any remaining unknown tags */
812 g_hash_table_foreach(replay_tags, (GHFunc) replay_report_unknown_tags_func, NULL);
813 g_hash_table_remove_all(replay_tags);
815 if (replay->movements->len != 0)
817 cave->replays = g_list_append(cave->replays, replay);
821 Warn("no movements in replay!");
822 gd_replay_free(replay);
825 /* GOSH i hate bdcff */
826 else if (strncasecmp(line, "[level=", strlen("[level=")) == 0)
832 /* there IS an equal sign, and we also skip that, so this points to the numbers */
833 nums = strchr(line, '=') + 1;
834 num = sscanf(nums, "%d,%d,%d,%d,%d", l + 0, l + 1, l + 2, l + 3, l + 4);
839 Warn("invalid Levels tag: %s", line);
840 levels = GD_OBJECT_LEVEL_ALL;
846 for (n = 0; n < num; n++)
848 if (l[n] <= 5 && l[n] >= 1)
849 levels |= gd_levels_mask[l[n] - 1];
851 Warn("invalid level number %d", l[n]);
855 else if (strcasecmp(line, "[/level]") == 0)
857 levels = GD_OBJECT_LEVEL_ALL;
859 else if (strcasecmp(line, "[game]") == 0)
862 else if (strcasecmp(line, "[/game]") == 0)
865 else if (strcasecmp(line, "[BDCFF]") == 0)
868 else if (strcasecmp(line, "[/BDCFF]") == 0)
873 Warn("unknown section: \"%s\"", line);
881 /* just append to the mapstrings list. we will process it later */
882 mapstrings = g_list_append(mapstrings, line);
887 /* strip leading and trailing spaces AFTER checking if we are reading a map.
888 map lines might begin or end with spaces */
891 if (reading_highscore)
895 if (sscanf(line, "%d", &score) != 1 || strchr(line, ' ') == NULL)
896 { /* first word is the score */
897 Warn("highscore format incorrect");
901 if (cave == default_cave)
902 /* if we are reading the [game], add highscore to that one. */
903 /* from first space: the name */
904 gd_add_highscore(gd_caveset_data->highscore, strchr(line, ' ') + 1, score);
906 /* if a cave, add highscore to that. */
907 gd_add_highscore(cave->highscore, strchr(line, ' ') + 1, score);
913 /* read bdcff-style [demo], similar to a complete replay but cannot store like anything */
914 if (reading_bdcff_demo)
919 /* demo must be in [cave] section. we already showed an error message for this. */
920 if (cave == default_cave)
923 iter = g_list_last(cave->replays);
925 replay = (GdReplay *)iter->data;
926 replay_store_more_from_bdcff(replay, line);
933 GdObject *new_object;
935 new_object = gd_object_new_from_string(line);
938 new_object->levels = levels; /* apply levels to new object */
939 cave->objects = g_list_append(cave->objects, new_object);
943 Error("invalid object specification: %s", line);
949 /* has an equal sign -> some_attrib = parameters type line. */
950 if (strchr (line, '=') != NULL)
952 char *attrib, *param;
954 attrib = line; /* attrib is from the first char */
955 param = strchr(line, '=') + 1; /* param is after equal sign */
956 *strchr (line, '=') = 0; /* delete equal sign - line is therefore splitted */
958 /* own tag: not too much thinking :P */
961 g_hash_table_insert(replay_tags, g_strdup(attrib), g_strdup(param));
963 else if (reading_mapcodes)
965 if (strcasecmp("Length", attrib) == 0)
967 /* we do not support map code width != 1 */
968 if (strcmp(param, "1") != 0)
969 Warn(_("Only one-character map codes are currently supported!"));
973 /* the first character of the attribute is the element code itself */
974 gd_char_to_element[(int)attrib[0]] = gd_get_element_from_string(param);
978 else if (strcasecmp("Version", attrib) == 0)
980 gd_strcpy(version_read, param);
983 else if (strcasecmp(attrib, "Caves") == 0)
985 /* BDCFF files sometimes state how many caves they have */
986 /* we ignore this field. */
989 else if (strcasecmp(attrib, "Levels") == 0)
991 /* BDCFF files sometimes state how many levels they have */
992 /* we ignore this field. */
994 else if (strcasecmp(attrib, "CaveSize") == 0)
998 i = sscanf(param, "%d %d %d %d %d %d",
1006 /* allowed: 2 or 6 numbers */
1011 cavesize[4] = cavesize[0]-1;
1012 cavesize[5] = cavesize[1]-1;
1016 set_cavesize_defaults();
1017 Warn("invalid CaveSize tag: %s", line);
1020 else if (strcasecmp(attrib, "IntermissionSize") == 0)
1024 i = sscanf(param, "%d %d %d %d %d %d",
1025 intermissionsize + 0,
1026 intermissionsize + 1,
1027 intermissionsize + 2,
1028 intermissionsize + 3,
1029 intermissionsize + 4,
1030 intermissionsize + 5);
1032 /* allowed: 2 or 6 numbers */
1035 intermissionsize[2] = 0;
1036 intermissionsize[3] = 0;
1037 intermissionsize[4] = intermissionsize[0]-1;
1038 intermissionsize[5] = intermissionsize[1]-1;
1042 set_intermissionsize_defaults();
1043 Warn("invalid IntermissionSize tag: '%s'", line);
1046 else if (strcasecmp(attrib, "Effect") == 0)
1048 /* CHECK IF IT IS AN EFFECT */
1051 params = g_strsplit_set(param, " ", -1);
1053 /* an effect command has two parameters */
1054 if (g_strv_length(params) == 2)
1058 for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
1060 /* we have to search for this effect */
1061 if (gd_cave_properties[i].type == GD_TYPE_EFFECT &&
1062 strcasecmp(params[0], gd_cave_properties[i].identifier) == 0)
1064 /* found identifier */
1065 gpointer value = G_STRUCT_MEMBER_P (cave, gd_cave_properties[i].offset);
1067 *((GdElement *) value) = gd_get_element_from_string (params[1]);
1072 /* if we didn't find first element name */
1073 if (gd_cave_properties[i].identifier == NULL)
1075 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
1076 if (strcasecmp(params[0], "BOUNCING_BOULDER") == 0)
1077 cave->stone_bouncing_effect = gd_get_element_from_string (params[1]);
1078 else if (strcasecmp(params[0], "EXPLOSION3S") == 0)
1079 cave->explosion_effect = gd_get_element_from_string(params[1]);
1080 /* falling with one l... */
1081 else if (strcasecmp(params[0], "STARTING_FALING_DIAMOND") == 0)
1082 cave->diamond_falling_effect = gd_get_element_from_string (params[1]);
1083 /* dirt lookslike */
1084 else if (strcasecmp(params[0], "DIRT") == 0)
1085 cave->dirt_looks_like = gd_get_element_from_string (params[1]);
1086 else if (strcasecmp(params[0], "HEXPANDING_WALL") == 0 && strcasecmp(params[1], "STEEL_HEXPANDING_WALL") == 0)
1088 cave->expanding_wall_looks_like = O_STEEL;
1091 /* didn't find at all */
1092 Warn("invalid effect name '%s'", params[0]);
1096 Warn("invalid effect specification '%s'", param);
1102 /* no special handling: this is a normal attribute. */
1104 if (cave == default_cave)
1106 /* we are reading the [game] */
1107 if (attrib_is_valid_for_caveset(attrib))
1109 /* if it is a caveset attrib, process it for the caveset. */
1110 struct_set_property(gd_caveset_data, gd_caveset_properties, attrib, param, 0);
1112 else if (attrib_is_valid_for_cave(attrib))
1114 /* it must be a default setting for all caves. is it a valid identifier? */
1115 /* yes, it is. add to the hash table, which will be copied for all caves. */
1116 g_hash_table_insert(tags, g_strdup(attrib), g_strdup(param));
1119 /* unknown setting - report. */
1120 Warn("invalid attribute for [game] '%s'", attrib);
1124 /* we are reading a [cave] */
1125 /* cave settings are immediately added to cave hash table. */
1126 /* if it is unknown, we have to remember it, and save it again. */
1127 g_hash_table_insert(tags, g_strdup(attrib), g_strdup(param));
1134 Error("cannot parse line: %s", line);
1139 Warn("incorrect file format: end of file, but still have some map lines read");
1140 g_list_free(mapstrings);
1144 /* the [game] section had some values which are default if not specified in [cave] sections. */
1145 /* these are used only for loading, so forget them now */
1146 if (default_cave->map)
1147 Warn(_("Invalid BDCFF: [game] section has a map"));
1148 if (default_cave->objects)
1149 Warn(_("Invalid BDCFF: [game] section has drawing objects defined"));
1153 g_hash_table_destroy(tags);
1154 g_hash_table_destroy(replay_tags);
1155 gd_cave_free(default_cave);
1157 /* old bdcff files hack. explanation follows. */
1158 /* there were 40x22 caves in c64 bd, intermissions were also 40x22, but the visible */
1159 /* part was the upper left corner, 20x12. 40x22 caves are needed, as 20x12 caves would */
1160 /* look different (random cave elements needs the correct size.) */
1161 /* also, in older bdcff files, there is no size= tag. caves default to 40x22 and 20x12. */
1162 /* even the explicit drawrect and other drawing instructions, which did set up intermissions */
1163 /* to be 20x12, are deleted. very very bad decision. */
1164 /* here we try to detect and correct this. */
1166 if (strEqual(version_read, "0.32"))
1170 Warn("No BDCFF version, or 0.32. Using unspecified-intermission-size hack.");
1172 for (iter = gd_caveset; iter != NULL; iter = iter->next)
1174 GdCave *cave = (GdCave *)iter->data;
1176 /* only applies to intermissions */
1177 /* not applied to mapped caves, as maps are filled with initial border, if the map read is smaller */
1178 if (cave->intermission && !cave->map)
1180 /* we do not set the cave to 20x12, rather to 40x22 with 20x12 visible. */
1190 /* and cover the invisible area */
1191 object.type = GD_FILLED_RECTANGLE;
1193 object.y1 = 11; /* 11, because this will also be the border */
1196 object.element = cave->initial_border;
1197 object.fill_element = cave->initial_border;
1199 cave->objects = g_list_prepend(cave->objects, g_memdup(&object, sizeof(object)));
1202 object.y1 = 0; /* 19, as it is also the border */
1204 cave->objects = g_list_prepend(cave->objects, g_memdup(&object, sizeof(object))); /* another */
1209 if (!strEqual(version_read, BDCFF_VERSION))
1210 Warn("BDCFF version %s, loaded caveset may have errors.", version_read);
1212 /* check for replays which are problematic */
1213 for (iter = gd_caveset; iter != NULL; iter = iter->next)
1214 gd_cave_check_replays((GdCave *)iter->data, TRUE, FALSE, FALSE);
1216 /* if there was some error message - return fail XXX */