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.
22 #define BDCFF_VERSION "0.5"
24 /* these are used for bdcff loading, storing the sizes of caves */
25 static int cavesize[6], intermissionsize[6];
27 static boolean replay_store_from_bdcff(GdReplay *replay, const char *str)
30 boolean up, down, left, right;
31 boolean fire, suicide;
32 const char *num = NULL;
35 fire = suicide = up = down = left = right = FALSE;
37 for (i = 0; str[i] != 0; i++)
73 /* do nothing, as all other movements are false */
78 /* bdcff 'combined' flags. do nothing. */
82 if (str[i] >= '0' && str[i] <= '9')
90 dir = gd_direction_from_keypress(up, down, left, right);
94 sscanf(num, "%d", &count);
96 for (i = 0; i < count; i++)
97 gd_replay_store_movement(replay, dir, fire, suicide);
102 static boolean attrib_is_valid_for_cave(const char *attrib)
106 /* bdcff engine flag............ */
107 if (strcasecmp(attrib, "Engine") == 0)
110 /* old flags - for compatibility */
111 if (strcasecmp(attrib, "BD1Scheduling") == 0)
114 if (strcasecmp(attrib, "SnapExplosions") == 0)
117 if (strcasecmp(attrib, "AmoebaProperties") == 0)
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)
128 static boolean attrib_is_valid_for_caveset(const char *attrib)
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)
140 static boolean struct_set_property(void *str, const GdStructDescriptor *prop_desc,
141 const char *attrib, const char *param, int ratio)
145 boolean identifier_found;
150 params = getSplitStringArray(param, " ", -1);
151 paramcount = getStringArrayLength(params);
152 identifier_found = FALSE;
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. */
158 for (i = 0; prop_desc[i].identifier != NULL; i++)
160 if (strcasecmp(prop_desc[i].identifier, attrib) == 0)
162 /* found the identifier */
163 void *value = STRUCT_MEMBER_P(str, prop_desc[i].offset);
165 /* these point to the same, but to avoid the awkward cast syntax */
167 GdElement *evalue = value;
168 GdDirection *dvalue = value;
169 GdScheduling *svalue = value;
170 boolean *bvalue = value;
173 identifier_found = TRUE;
175 if (prop_desc[i].type == GD_TYPE_STRING)
177 /* strings are treated different, as occupy the whole length of the line */
178 gd_strcpy(value, param);
180 /* remember this to skip checking the number of parameters at the end of the function */
186 if (prop_desc[i].type == GD_TYPE_LONGSTRING)
188 char **str = (char **)value;
191 *str = getUnescapedString(param);
193 /* remember this to skip checking the number of parameters at the end of the function */
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++)
203 boolean success = FALSE;
206 switch (prop_desc[i].type)
208 case GD_TYPE_LONGSTRING:
216 case GD_TYPE_BOOLEAN:
217 success = sscanf(params[paramindex], "%d", &bvalue[j]) == 1;
220 if (strcasecmp(params[paramindex], "true") == 0 ||
221 strcasecmp(params[paramindex], "on") == 0 ||
222 strcasecmp(params[paramindex], "yes") == 0)
227 else if (strcasecmp(params[paramindex], "false") == 0 ||
228 strcasecmp(params[paramindex], "off") == 0 ||
229 strcasecmp(params[paramindex], "no") == 0)
236 /* if we are processing an array, fill other values with these.
237 if there are other values specified, those will be overwritten. */
239 for (k = j + 1; k < prop_desc[i].count; k++)
240 bvalue[k] = bvalue[j];
245 success = sscanf(params[paramindex], "%d", &ivalue[j]) == 1;
247 /* copy to other if array */
248 for (k = j + 1; k < prop_desc[i].count; k++)
249 ivalue[k] = ivalue[j];
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)
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;
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)
273 for (k = j; k < prop_desc[i].count; k++)
274 ivalue[k] = (int)(res * ratio + 0.5);
281 case GD_TYPE_ELEMENT:
282 evalue[j] = gd_get_element_from_string(params[paramindex]);
284 /* copy to all remaining elements in array */
285 for (k = j + 1; k < prop_desc[i].count; k++)
286 evalue[k] = evalue[j];
288 /* this shows error message on its own, do treat as always succeeded */
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];
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];
307 /* if there was an error, already reported by gd_scheduling_from_string */
313 /* shoud have handled this elsewhere */
318 paramindex++; /* go to next parameter to process */
320 Warn("invalid parameter '%s' for attribute %s", params[paramindex], attrib);
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]);
330 freeStringArray(params);
332 return identifier_found;
335 /********************************************************************************
341 static boolean replay_store_more_from_bdcff(GdReplay *replay, const char *param)
345 boolean result = TRUE;
347 split = getSplitStringArray(param, " ", -1);
349 for (i = 0; split[i] != 0; i++)
350 result = result && replay_store_from_bdcff(replay, split[i]);
352 freeStringArray(split);
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)
360 Warn("unknown replay tag '%s'", attrib);
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)
368 boolean identifier_found = FALSE;
371 if (strcasecmp(attrib, "Movements") == 0)
373 identifier_found = TRUE;
374 replay_store_more_from_bdcff(replay, param);
379 /* 0: for ratio types; not used */
380 identifier_found = struct_set_property(replay, gd_replay_properties,
384 /* a ghrfunc should return true if the identifier is to be removed */
385 return identifier_found;
389 static void replay_process_tags(GdReplay *replay, HashTable *tags)
391 /* process all tags */
392 hashtable_foreach_remove(tags, (hashtable_remove_fn)replay_process_tags_func, replay);
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)
401 boolean identifier_found;
403 params = getSplitStringArray(param, " ", -1);
404 identifier_found = FALSE;
406 if (strcasecmp(attrib, "SnapExplosions") == 0)
408 /* handle compatibility with old snapexplosions flag */
410 identifier_found = TRUE;
412 if (strcasecmp(param, "true") == 0)
414 cave->snap_element = O_EXPLODE_1;
416 else if (strcasecmp(param, "false") == 0)
418 cave->snap_element = O_SPACE;
422 Warn("invalid param for '%s': '%s'", attrib, param);
425 else if (strcasecmp(attrib, "BD1Scheduling") == 0)
427 /* handle compatibility with old bd1scheduling flag */
429 identifier_found = TRUE;
431 if (strcasecmp(param, "true") == 0)
433 if (cave->scheduling == GD_SCHEDULING_PLCK)
434 cave->scheduling = GD_SCHEDULING_BD1;
437 else if (strcasecmp(attrib, "Engine") == 0)
439 /* handle bdcff engine flag */
441 identifier_found = TRUE;
443 GdEngine engine = gd_cave_get_engine_from_string(param);
445 if (engine == GD_ENGINE_INVALID)
446 Warn(_("invalid parameter \"%s\" for attribute %s"), param, attrib);
448 gd_cave_set_engine_defaults(cave, engine);
450 else if (strcasecmp(attrib, "AmoebaProperties") == 0)
452 /* handle compatibility with old AmoebaProperties flag */
454 GdElement elem1 = O_STONE, elem2 = O_DIAMOND;
456 identifier_found = TRUE;
457 elem1 = gd_get_element_from_string(params[0]);
458 elem2 = gd_get_element_from_string(params[1]);
459 cave->amoeba_too_big_effect = elem1;
460 cave->amoeba_enclosed_effect = elem2;
462 else if (strcasecmp(attrib, "Colors") == 0)
464 /* colors attribute is a mess, have to process explicitly */
466 /* Colors = [border background] foreground1 foreground2 foreground3 [amoeba slime] */
467 identifier_found = TRUE;
469 cave->colorb = GD_GDASH_BLACK; /* border - black */
470 cave->color0 = GD_GDASH_BLACK; /* background - black */
471 cave->color1 = GD_GDASH_RED;
472 cave->color2 = GD_GDASH_PURPLE;
473 cave->color3 = GD_GDASH_YELLOW;
474 cave->color4 = cave->color3; /* amoeba */
475 cave->color5 = cave->color1; /* slime */
479 identifier_found = struct_set_property(cave, gd_cave_properties, attrib, param, cave->w * cave->h);
482 freeStringArray(params);
484 /* a ghrfunc should return true if the identifier is to be removed */
485 return identifier_found;
488 /* report all remaining tags; called after the above function. */
489 static void cave_report_and_copy_unknown_tags_func(char *attrib, char *param, void *data)
491 GdCave *cave = (GdCave *)data;
493 Warn("unknown tag '%s'", attrib);
495 hashtable_insert(cave->tags, getStringCopy(attrib), getStringCopy(param));
498 /* having read all strings belonging to the cave, process it. */
499 static void cave_process_tags(GdCave *cave, HashTable *tags, List *maplines)
503 /* first check cave name, so we can report errors correctly (saying that GdCave xy: error foobar) */
504 value = hashtable_search(tags, "Name");
506 cave_process_tags_func("Name", value, cave);
508 /* process lame engine tag first so its settings may be overwritten later */
509 value = hashtable_search(tags, "Engine");
512 cave_process_tags_func("Engine", value, cave);
513 hashtable_remove(tags, "Engine");
516 /* check if this is an intermission, so we can set to cavesize or intermissionsize */
517 value = hashtable_search(tags, "Intermission");
520 cave_process_tags_func("Intermission", value, cave);
521 hashtable_remove(tags, "Intermission");
524 if (cave->intermission)
526 /* set to IntermissionSize */
527 cave->w = intermissionsize[0];
528 cave->h = intermissionsize[1];
529 cave->x1 = intermissionsize[2];
530 cave->y1 = intermissionsize[3];
531 cave->x2 = intermissionsize[4];
532 cave->y2 = intermissionsize[5];
536 /* set to CaveSize */
537 cave->w = cavesize[0];
538 cave->h = cavesize[1];
539 cave->x1 = cavesize[2];
540 cave->y1 = cavesize[3];
541 cave->x2 = cavesize[4];
542 cave->y2 = cavesize[5];
545 /* process size at the beginning... as ratio types depend on this. */
546 value = hashtable_search(tags, "Size");
549 cave_process_tags_func("Size", value, cave);
550 hashtable_remove(tags, "Size");
553 /* these are read from the hash table, but also have some implications */
554 /* we do not delete them from the hash table here; as _their values will be processed later_. */
555 /* here we only set their implicite meanings. */
556 /* these also set predictability */
557 if (hashtable_search(tags, "SlimePermeability"))
558 cave->slime_predictable = FALSE;
560 if (hashtable_search(tags, "SlimePermeabilityC64"))
561 cave->slime_predictable = TRUE;
563 /* these set scheduling type. framedelay takes precedence, if there are both; so we check it later. */
564 if (hashtable_search(tags, "CaveDelay"))
566 /* only set scheduling type, when it is not the gdash-default. */
567 /* this allows settings cavescheduling = bd1 in the [game] section, for example. */
568 /* in that case, this one will not overwrite it. */
569 if (cave->scheduling == GD_SCHEDULING_MILLISECONDS)
570 cave->scheduling = GD_SCHEDULING_PLCK;
573 if (hashtable_search(tags, "FrameTime"))
574 /* but if the cave has a frametime setting, always switch to milliseconds. */
575 cave->scheduling = GD_SCHEDULING_MILLISECONDS;
577 /* process all tags */
578 hashtable_foreach_remove(tags, (hashtable_remove_fn)cave_process_tags_func, cave);
580 /* and at the end, when read all tags (especially the size= tag) */
581 /* process map, if any. */
582 /* only report if map read is bigger than size= specified. */
583 /* some old bdcff files use smaller intermissions than the one specified. */
586 int x, y, length = list_length(maplines);
589 /* create map and fill with initial border, in case that map strings are shorter or somewhat */
590 cave->map = gd_cave_map_new(cave, GdElement);
592 for (y = 0; y < cave->h; y++)
593 for (x = 0; x < cave->w; x++)
594 cave->map[y][x] = cave->initial_border;
596 if (length != cave->h && length != (cave->y2-cave->y1 + 1))
597 Warn("map error: cave height = %d (%d visible), map height = %d",
598 cave->h, cave->y2 - cave->y1 + 1, length);
600 for (iter = maplines, y = 0; y < length && iter != NULL; iter = iter->next, y++)
602 const char *line = iter->data;
603 int slen = strlen(line);
605 if (slen != cave->w && slen != (cave->x2 - cave->x1 + 1))
606 Warn("map error in row %d: cave width = %d (%d visible), map width = %d",
607 y, cave->w, cave->x2 - cave->x1 + 1, slen);
609 /* use number of cells from cave or string, whichever is smaller.
610 so will not overwrite array! */
611 for (x = 0; x < MIN(cave->w, slen); x++)
612 cave->map[y][x] = gd_get_element_from_character (line[x]);
617 /* sets the cavesize array to default values */
618 static void set_cavesize_defaults(void)
628 /* sets the cavesize array to default values */
629 static void set_intermissionsize_defaults(void)
631 intermissionsize[0] = 40;
632 intermissionsize[1] = 22;
633 intermissionsize[2] = 0;
634 intermissionsize[3] = 0;
635 intermissionsize[4] = 19;
636 intermissionsize[5] = 11;
639 boolean gd_caveset_load_from_bdcff(const char *contents)
645 boolean reading_replay = FALSE;
646 boolean reading_map = FALSE;
647 boolean reading_mapcodes = FALSE;
648 boolean reading_highscore = FALSE;
649 boolean reading_objects = FALSE;
650 boolean reading_bdcff_demo = FALSE;
651 /* assume version to be 0.32, also when the file does not specify it explicitly */
652 GdString version_read = "0.32";
653 List *mapstrings = NULL;
655 HashTable *tags, *replay_tags;
656 GdObjectLevels levels = GD_OBJECT_LEVEL_ALL;
657 GdCave *default_cave;
661 set_cavesize_defaults();
662 set_intermissionsize_defaults();
663 gd_create_char_to_element_table();
665 tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
666 replay_tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
668 /* split into lines */
669 lines = getSplitStringArray (contents, "\n", 0);
671 /* attributes read will be set in cave. if no [cave]; they are stored
672 in the default cave; like in a [game] */
673 default_cave = gd_cave_new();
676 linenum = getStringArrayLength(lines);
678 for (lineno = 0; lineno < linenum; lineno++)
680 char *line = lines[lineno];
683 /* remove windows-nightmare \r-s */
684 while((r = strchr(line, '\r')))
687 if (strlen (line) == 0)
688 continue; /* skip empty lines */
690 /* just skip comments. be aware that map lines may start with a semicolon... */
691 if (!reading_map && line[0] == ';')
694 /* STARTING WITH A BRACKET [ IS A SECTION */
697 if (strcasecmp(line, "[cave]") == 0)
702 Warn("incorrect file format: new [cave] section, but already read some map lines");
703 list_free(mapstrings);
707 /* process any pending tags for game ... */
708 cave_process_tags(default_cave, tags, NULL);
710 /* ... to be able to create a copy for a new cave. */
711 cave = gd_cave_new_from_cave(default_cave);
712 gd_caveset = list_append (gd_caveset, cave);
714 else if (strcasecmp(line, "[/cave]") == 0)
716 cave_process_tags(cave, tags, mapstrings);
717 list_free(mapstrings);
720 hashtable_foreach(tags, (hashtable_fn)cave_report_and_copy_unknown_tags_func, cave);
721 hashtable_remove_all(tags);
723 /* set this to point the pseudo-cave which holds default values */
726 else if (strcasecmp(line, "[map]") == 0)
729 if (mapstrings != NULL)
731 Warn("incorrect file format: new [map] section, but already read some map lines");
732 list_free(mapstrings);
736 else if (strcasecmp(line, "[/map]") == 0)
740 else if (strcasecmp(line, "[mapcodes]") == 0)
742 reading_mapcodes = TRUE;
744 else if (strcasecmp(line, "[/mapcodes]") == 0)
746 reading_mapcodes = FALSE;
748 else if (strcasecmp(line, "[highscore]") == 0)
750 reading_highscore = TRUE;
752 else if (strcasecmp(line, "[/highscore]") == 0)
754 reading_highscore = FALSE;
756 else if (strcasecmp(line, "[objects]") == 0)
758 reading_objects = TRUE;
760 else if (strcasecmp(line, "[/objects]") == 0)
762 reading_objects = FALSE;
764 else if (strcasecmp(line, "[demo]") == 0)
768 reading_bdcff_demo = TRUE;
770 if (cave != default_cave)
772 replay = gd_replay_new();
773 replay->saved = TRUE;
774 replay->success = TRUE; /* we think that it is a successful demo */
775 cave->replays = list_append(cave->replays, replay);
776 gd_strcpy(replay->player_name, "???"); /* name not saved */
780 Warn("[demo] section must be in [cave] section!");
783 else if (strcasecmp(line, "[/demo]") == 0)
785 reading_bdcff_demo = FALSE;
787 else if (strcasecmp(line, "[replay]") == 0)
789 reading_replay = TRUE;
791 else if (strcasecmp(line, "[/replay]") == 0)
795 reading_replay = FALSE;
796 replay = gd_replay_new();
798 /* set "saved" flag, so this replay will be written when the caveset is saved again */
799 replay->saved = TRUE;
800 replay_process_tags(replay, replay_tags);
803 /* BDCFF numbers levels from 1 to 5, but internally we number levels from 0 to 4 */
804 if (replay->level > 0)
808 /* report any remaining unknown tags */
809 hashtable_foreach(replay_tags, (hashtable_fn)replay_report_unknown_tags_func, NULL);
810 hashtable_remove_all(replay_tags);
812 if (replay->movements->len != 0)
814 cave->replays = list_append(cave->replays, replay);
818 Warn("no movements in replay!");
819 gd_replay_free(replay);
822 /* GOSH i hate bdcff */
823 else if (strncasecmp(line, "[level=", strlen("[level=")) == 0)
829 /* there IS an equal sign, and we also skip that, so this points to the numbers */
830 nums = strchr(line, '=') + 1;
831 num = sscanf(nums, "%d,%d,%d,%d,%d", l + 0, l + 1, l + 2, l + 3, l + 4);
836 Warn("invalid Levels tag: %s", line);
837 levels = GD_OBJECT_LEVEL_ALL;
843 for (n = 0; n < num; n++)
845 if (l[n] <= 5 && l[n] >= 1)
846 levels |= gd_levels_mask[l[n] - 1];
848 Warn("invalid level number %d", l[n]);
852 else if (strcasecmp(line, "[/level]") == 0)
854 levels = GD_OBJECT_LEVEL_ALL;
856 else if (strcasecmp(line, "[game]") == 0)
859 else if (strcasecmp(line, "[/game]") == 0)
862 else if (strcasecmp(line, "[BDCFF]") == 0)
865 else if (strcasecmp(line, "[/BDCFF]") == 0)
870 Warn("unknown section: \"%s\"", line);
878 /* just append to the mapstrings list. we will process it later */
879 mapstrings = list_append(mapstrings, line);
884 /* strip leading and trailing spaces AFTER checking if we are reading a map.
885 map lines might begin or end with spaces */
888 if (reading_highscore)
892 if (sscanf(line, "%d", &score) != 1 || strchr(line, ' ') == NULL)
893 { /* first word is the score */
894 Warn("highscore format incorrect");
898 if (cave == default_cave)
899 /* if we are reading the [game], add highscore to that one. */
900 /* from first space: the name */
901 gd_add_highscore(gd_caveset_data->highscore, strchr(line, ' ') + 1, score);
903 /* if a cave, add highscore to that. */
904 gd_add_highscore(cave->highscore, strchr(line, ' ') + 1, score);
910 /* read bdcff-style [demo], similar to a complete replay but cannot store like anything */
911 if (reading_bdcff_demo)
916 /* demo must be in [cave] section. we already showed an error message for this. */
917 if (cave == default_cave)
920 iter = list_last(cave->replays);
922 replay = (GdReplay *)iter->data;
923 replay_store_more_from_bdcff(replay, line);
930 GdObject *new_object;
932 new_object = gd_object_new_from_string(line);
935 new_object->levels = levels; /* apply levels to new object */
936 cave->objects = list_append(cave->objects, new_object);
940 Error("invalid object specification: %s", line);
946 /* has an equal sign -> some_attrib = parameters type line. */
947 if (strchr (line, '=') != NULL)
949 char *attrib, *param;
951 attrib = line; /* attrib is from the first char */
952 param = strchr(line, '=') + 1; /* param is after equal sign */
953 *strchr (line, '=') = 0; /* delete equal sign - line is therefore splitted */
955 /* own tag: not too much thinking :P */
958 hashtable_insert(replay_tags, getStringCopy(attrib), getStringCopy(param));
960 else if (reading_mapcodes)
962 if (strcasecmp("Length", attrib) == 0)
964 /* we do not support map code width != 1 */
965 if (strcmp(param, "1") != 0)
966 Warn(_("Only one-character map codes are currently supported!"));
970 /* the first character of the attribute is the element code itself */
971 gd_char_to_element[(int)attrib[0]] = gd_get_element_from_string(param);
975 else if (strcasecmp("Version", attrib) == 0)
977 gd_strcpy(version_read, param);
980 else if (strcasecmp(attrib, "Caves") == 0)
982 /* BDCFF files sometimes state how many caves they have */
983 /* we ignore this field. */
986 else if (strcasecmp(attrib, "Levels") == 0)
988 /* BDCFF files sometimes state how many levels they have */
989 /* we ignore this field. */
991 else if (strcasecmp(attrib, "CaveSize") == 0)
995 i = sscanf(param, "%d %d %d %d %d %d",
1003 /* allowed: 2 or 6 numbers */
1008 cavesize[4] = cavesize[0]-1;
1009 cavesize[5] = cavesize[1]-1;
1013 set_cavesize_defaults();
1014 Warn("invalid CaveSize tag: %s", line);
1017 else if (strcasecmp(attrib, "IntermissionSize") == 0)
1021 i = sscanf(param, "%d %d %d %d %d %d",
1022 intermissionsize + 0,
1023 intermissionsize + 1,
1024 intermissionsize + 2,
1025 intermissionsize + 3,
1026 intermissionsize + 4,
1027 intermissionsize + 5);
1029 /* allowed: 2 or 6 numbers */
1032 intermissionsize[2] = 0;
1033 intermissionsize[3] = 0;
1034 intermissionsize[4] = intermissionsize[0]-1;
1035 intermissionsize[5] = intermissionsize[1]-1;
1039 set_intermissionsize_defaults();
1040 Warn("invalid IntermissionSize tag: '%s'", line);
1043 else if (strcasecmp(attrib, "Effect") == 0)
1045 /* CHECK IF IT IS AN EFFECT */
1048 params = getSplitStringArray(param, " ", -1);
1050 /* an effect command has two parameters */
1051 if (getStringArrayLength(params) == 2)
1055 for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
1057 /* we have to search for this effect */
1058 if (gd_cave_properties[i].type == GD_TYPE_EFFECT &&
1059 strcasecmp(params[0], gd_cave_properties[i].identifier) == 0)
1061 /* found identifier */
1062 void *value = STRUCT_MEMBER_P (cave, gd_cave_properties[i].offset);
1064 *((GdElement *) value) = gd_get_element_from_string (params[1]);
1069 /* if we didn't find first element name */
1070 if (gd_cave_properties[i].identifier == NULL)
1072 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
1073 if (strcasecmp(params[0], "BOUNCING_BOULDER") == 0)
1074 cave->stone_bouncing_effect = gd_get_element_from_string (params[1]);
1075 else if (strcasecmp(params[0], "EXPLOSION3S") == 0)
1076 cave->explosion_effect = gd_get_element_from_string(params[1]);
1077 /* falling with one l... */
1078 else if (strcasecmp(params[0], "STARTING_FALING_DIAMOND") == 0)
1079 cave->diamond_falling_effect = gd_get_element_from_string (params[1]);
1080 /* dirt lookslike */
1081 else if (strcasecmp(params[0], "DIRT") == 0)
1082 cave->dirt_looks_like = gd_get_element_from_string (params[1]);
1083 else if (strcasecmp(params[0], "HEXPANDING_WALL") == 0 && strcasecmp(params[1], "STEEL_HEXPANDING_WALL") == 0)
1085 cave->expanding_wall_looks_like = O_STEEL;
1088 /* didn't find at all */
1089 Warn("invalid effect name '%s'", params[0]);
1093 Warn("invalid effect specification '%s'", param);
1095 freeStringArray(params);
1099 /* no special handling: this is a normal attribute. */
1101 if (cave == default_cave)
1103 /* we are reading the [game] */
1104 if (attrib_is_valid_for_caveset(attrib))
1106 /* if it is a caveset attrib, process it for the caveset. */
1107 struct_set_property(gd_caveset_data, gd_caveset_properties, attrib, param, 0);
1109 else if (attrib_is_valid_for_cave(attrib))
1111 /* it must be a default setting for all caves. is it a valid identifier? */
1112 /* yes, it is. add to the hash table, which will be copied for all caves. */
1113 hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param));
1117 /* unknown setting - report. */
1118 Warn("invalid attribute for [game] '%s'", attrib);
1123 /* we are reading a [cave] */
1124 /* cave settings are immediately added to cave hash table. */
1125 /* if it is unknown, we have to remember it, and save it again. */
1126 hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param));
1133 Error("cannot parse line: %s", line);
1138 Warn("incorrect file format: end of file, but still have some map lines read");
1139 list_free(mapstrings);
1143 /* the [game] section had some values which are default if not specified in [cave] sections. */
1144 /* these are used only for loading, so forget them now */
1145 if (default_cave->map)
1146 Warn(_("Invalid BDCFF: [game] section has a map"));
1147 if (default_cave->objects)
1148 Warn(_("Invalid BDCFF: [game] section has drawing objects defined"));
1151 freeStringArray(lines);
1152 hashtable_destroy(tags);
1153 hashtable_destroy(replay_tags);
1154 gd_cave_free(default_cave);
1156 /* old bdcff files hack. explanation follows. */
1157 /* there were 40x22 caves in c64 bd, intermissions were also 40x22, but the visible */
1158 /* part was the upper left corner, 20x12. 40x22 caves are needed, as 20x12 caves would */
1159 /* look different (random cave elements needs the correct size.) */
1160 /* also, in older bdcff files, there is no size= tag. caves default to 40x22 and 20x12. */
1161 /* even the explicit drawrect and other drawing instructions, which did set up intermissions */
1162 /* to be 20x12, are deleted. very very bad decision. */
1163 /* here we try to detect and correct this. */
1165 if (strEqual(version_read, "0.32"))
1169 Warn("No BDCFF version, or 0.32. Using unspecified-intermission-size hack.");
1171 for (iter = gd_caveset; iter != NULL; iter = iter->next)
1173 GdCave *cave = (GdCave *)iter->data;
1175 /* only applies to intermissions */
1176 /* not applied to mapped caves, as maps are filled with initial border, if the map read is smaller */
1177 if (cave->intermission && !cave->map)
1179 /* we do not set the cave to 20x12, rather to 40x22 with 20x12 visible. */
1189 /* and cover the invisible area */
1190 object.type = GD_FILLED_RECTANGLE;
1192 object.y1 = 11; /* 11, because this will also be the border */
1195 object.element = cave->initial_border;
1196 object.fill_element = cave->initial_border;
1198 cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object)));
1201 object.y1 = 0; /* 19, as it is also the border */
1203 cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object))); /* another */
1208 if (!strEqual(version_read, BDCFF_VERSION))
1209 Warn("BDCFF version %s, loaded caveset may have errors.", version_read);
1211 /* check for replays which are problematic */
1212 for (iter = gd_caveset; iter != NULL; iter = iter->next)
1213 gd_cave_check_replays((GdCave *)iter->data, TRUE, FALSE, FALSE);
1215 /* if there was some error message - return fail XXX */