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 (str[i] >= '0' && str[i] <= '9')
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 = getSplitStringArray(param, " ", -1);
154 paramcount = getStringArrayLength(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 char **str = (char **)value;
194 *str = getUnescapedString(param);
196 /* remember this to skip checking the number of parameters at the end of the function */
202 /* not a string, so use scanf calls */
203 /* ALSO, if no more parameters to process, exit loop */
204 for (j = 0; j < prop_desc[i].count && params[paramindex] != NULL; j++)
206 boolean success = FALSE;
209 switch (prop_desc[i].type)
211 case GD_TYPE_LONGSTRING:
219 case GD_TYPE_BOOLEAN:
220 success = sscanf(params[paramindex], "%d", &bvalue[j]) == 1;
223 if (strcasecmp(params[paramindex], "true") == 0 ||
224 strcasecmp(params[paramindex], "on") == 0 ||
225 strcasecmp(params[paramindex], "yes") == 0)
230 else if (strcasecmp(params[paramindex], "false") == 0 ||
231 strcasecmp(params[paramindex], "off") == 0 ||
232 strcasecmp(params[paramindex], "no") == 0)
239 /* if we are processing an array, fill other values with these.
240 if there are other values specified, those will be overwritten. */
242 for (k = j + 1; k < prop_desc[i].count; k++)
243 bvalue[k] = bvalue[j];
248 success = sscanf(params[paramindex], "%d", &ivalue[j]) == 1;
250 /* copy to other if array */
251 for (k = j + 1; k < prop_desc[i].count; k++)
252 ivalue[k] = ivalue[j];
256 case GD_TYPE_PROBABILITY:
257 res = g_ascii_strtod(params[paramindex], NULL);
258 if (errno == 0 && res >= 0 && res <= 1)
260 /* fill all remaining items in array - may be only one */
261 for (k = j; k < prop_desc[i].count; k++)
262 /* probabilities are stored inside as ppm (1E6) */
263 ivalue[k] = res * 1E6 + 0.5;
271 res = g_ascii_strtod (params[paramindex], NULL);
272 if (errno == 0 && res >= 0 && res <= 1)
274 for (k = j; k < prop_desc[i].count; k++)
275 ivalue[k] = (int)(res * ratio + 0.5);
282 case GD_TYPE_ELEMENT:
283 evalue[j] = gd_get_element_from_string(params[paramindex]);
285 /* copy to all remaining elements in array */
286 for (k = j + 1; k < prop_desc[i].count; k++)
287 evalue[k] = evalue[j];
289 /* this shows error message on its own, do treat as always succeeded */
293 case GD_TYPE_DIRECTION:
294 dvalue[j] = gd_direction_from_string(params[paramindex]);
295 /* copy to all remaining items in array */
296 for (k = j + 1; k < prop_desc[i].count; k++)
297 dvalue[k] = dvalue[j];
302 case GD_TYPE_SCHEDULING:
303 svalue[j] = gd_scheduling_from_string(params[paramindex]);
304 /* copy to all remaining items in array */
305 for (k = j + 1; k < prop_desc[i].count; k++)
306 svalue[k] = svalue[j];
308 /* if there was an error, already reported by gd_scheduling_from_string */
314 /* shoud have handled this elsewhere */
319 paramindex++; /* go to next parameter to process */
321 Warn("invalid parameter '%s' for attribute %s", params[paramindex], attrib);
326 /* if we found the identifier, but still could not process all parameters... */
327 /* of course, not for strings, as the whole line is the string */
328 if (identifier_found && !was_string && paramindex < paramcount)
329 Warn("excess parameters for attribute '%s': '%s'", attrib, params[paramindex]);
331 freeStringArray(params);
333 return identifier_found;
336 /********************************************************************************
342 static boolean replay_store_more_from_bdcff(GdReplay *replay, const char *param)
346 boolean result = TRUE;
348 split = getSplitStringArray(param, " ", -1);
350 for (i = 0; split[i] != 0; i++)
351 result = result && replay_store_from_bdcff(replay, split[i]);
353 freeStringArray(split);
358 /* report all remaining tags; called after the above function. */
359 static void replay_report_unknown_tags_func(const char *attrib, const char *param, gpointer data)
361 Warn("unknown replay tag '%s'", attrib);
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)
369 boolean identifier_found = FALSE;
372 if (strcasecmp(attrib, "Movements") == 0)
374 identifier_found = TRUE;
375 replay_store_more_from_bdcff(replay, param);
380 /* 0: for ratio types; not used */
381 identifier_found = struct_set_property(replay, gd_replay_properties,
385 /* a ghrfunc should return true if the identifier is to be removed */
386 return identifier_found;
390 static void replay_process_tags(GdReplay *replay, GHashTable *tags)
392 /* process all tags */
393 g_hash_table_foreach_remove(tags, (GHRFunc) replay_process_tags_func, replay);
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)
402 boolean identifier_found;
404 params = getSplitStringArray(param, " ", -1);
405 identifier_found = FALSE;
407 if (strcasecmp(attrib, "SnapExplosions") == 0)
409 /* handle compatibility with old snapexplosions flag */
411 identifier_found = TRUE;
413 if (strcasecmp(param, "true") == 0)
415 cave->snap_element = O_EXPLODE_1;
417 else if (strcasecmp(param, "false") == 0)
419 cave->snap_element = O_SPACE;
423 Warn("invalid param for '%s': '%s'", attrib, param);
426 else if (strcasecmp(attrib, "BD1Scheduling") == 0)
428 /* handle compatibility with old bd1scheduling flag */
430 identifier_found = TRUE;
432 if (strcasecmp(param, "true") == 0)
434 if (cave->scheduling == GD_SCHEDULING_PLCK)
435 cave->scheduling = GD_SCHEDULING_BD1;
438 else if (strcasecmp(attrib, "Engine") == 0)
440 /* handle bdcff engine flag */
442 identifier_found = TRUE;
444 GdEngine engine = gd_cave_get_engine_from_string(param);
446 if (engine == GD_ENGINE_INVALID)
447 Warn(_("invalid parameter \"%s\" for attribute %s"), param, attrib);
449 gd_cave_set_engine_defaults(cave, engine);
451 else if (strcasecmp(attrib, "AmoebaProperties") == 0)
453 /* handle compatibility with old AmoebaProperties flag */
455 GdElement elem1 = O_STONE, elem2 = O_DIAMOND;
457 identifier_found = TRUE;
458 elem1 = gd_get_element_from_string(params[0]);
459 elem2 = gd_get_element_from_string(params[1]);
460 cave->amoeba_too_big_effect = elem1;
461 cave->amoeba_enclosed_effect = elem2;
463 else if (strcasecmp(attrib, "Colors") == 0)
465 /* colors attribute is a mess, have to process explicitly */
467 /* Colors = [border background] foreground1 foreground2 foreground3 [amoeba slime] */
468 identifier_found = TRUE;
470 cave->colorb = GD_GDASH_BLACK; /* border - black */
471 cave->color0 = GD_GDASH_BLACK; /* background - black */
472 cave->color1 = GD_GDASH_RED;
473 cave->color2 = GD_GDASH_PURPLE;
474 cave->color3 = GD_GDASH_YELLOW;
475 cave->color4 = cave->color3; /* amoeba */
476 cave->color5 = cave->color1; /* slime */
480 identifier_found = struct_set_property(cave, gd_cave_properties, attrib, param, cave->w * cave->h);
483 freeStringArray(params);
485 /* a ghrfunc should return true if the identifier is to be removed */
486 return identifier_found;
489 /* report all remaining tags; called after the above function. */
490 static void cave_report_and_copy_unknown_tags_func(char *attrib, char *param, gpointer data)
492 GdCave *cave = (GdCave *)data;
494 Warn("unknown tag '%s'", attrib);
496 g_hash_table_insert(cave->tags, g_strdup(attrib), g_strdup(param));
499 /* having read all strings belonging to the cave, process it. */
500 static void cave_process_tags(GdCave *cave, GHashTable *tags, GList *maplines)
504 /* first check cave name, so we can report errors correctly (saying that GdCave xy: error foobar) */
505 value = g_hash_table_lookup(tags, "Name");
507 cave_process_tags_func("Name", value, cave);
509 /* process lame engine tag first so its settings may be overwritten later */
510 value = g_hash_table_lookup(tags, "Engine");
513 cave_process_tags_func("Engine", value, cave);
514 g_hash_table_remove(tags, "Engine");
517 /* check if this is an intermission, so we can set to cavesize or intermissionsize */
518 value = g_hash_table_lookup(tags, "Intermission");
521 cave_process_tags_func("Intermission", value, cave);
522 g_hash_table_remove(tags, "Intermission");
525 if (cave->intermission)
527 /* set to IntermissionSize */
528 cave->w = intermissionsize[0];
529 cave->h = intermissionsize[1];
530 cave->x1 = intermissionsize[2];
531 cave->y1 = intermissionsize[3];
532 cave->x2 = intermissionsize[4];
533 cave->y2 = intermissionsize[5];
537 /* set to CaveSize */
538 cave->w = cavesize[0];
539 cave->h = cavesize[1];
540 cave->x1 = cavesize[2];
541 cave->y1 = cavesize[3];
542 cave->x2 = cavesize[4];
543 cave->y2 = cavesize[5];
546 /* process size at the beginning... as ratio types depend on this. */
547 value = g_hash_table_lookup(tags, "Size");
550 cave_process_tags_func("Size", value, cave);
551 g_hash_table_remove(tags, "Size");
554 /* these are read from the hash table, but also have some implications */
555 /* we do not delete them from the hash table here; as _their values will be processed later_. */
556 /* here we only set their implicite meanings. */
557 /* these also set predictability */
558 if (g_hash_table_lookup(tags, "SlimePermeability"))
559 cave->slime_predictable = FALSE;
561 if (g_hash_table_lookup(tags, "SlimePermeabilityC64"))
562 cave->slime_predictable = TRUE;
564 /* these set scheduling type. framedelay takes precedence, if there are both; so we check it later. */
565 if (g_hash_table_lookup(tags, "CaveDelay"))
567 /* only set scheduling type, when it is not the gdash-default. */
568 /* this allows settings cavescheduling = bd1 in the [game] section, for example. */
569 /* in that case, this one will not overwrite it. */
570 if (cave->scheduling == GD_SCHEDULING_MILLISECONDS)
571 cave->scheduling = GD_SCHEDULING_PLCK;
574 if (g_hash_table_lookup(tags, "FrameTime"))
575 /* but if the cave has a frametime setting, always switch to milliseconds. */
576 cave->scheduling = GD_SCHEDULING_MILLISECONDS;
578 /* process all tags */
579 g_hash_table_foreach_remove(tags, (GHRFunc) cave_process_tags_func, cave);
581 /* and at the end, when read all tags (especially the size= tag) */
582 /* process map, if any. */
583 /* only report if map read is bigger than size= specified. */
584 /* some old bdcff files use smaller intermissions than the one specified. */
587 int x, y, length = g_list_length(maplines);
590 /* create map and fill with initial border, in case that map strings are shorter or somewhat */
591 cave->map = gd_cave_map_new(cave, GdElement);
593 for (y = 0; y < cave->h; y++)
594 for (x = 0; x < cave->w; x++)
595 cave->map[y][x] = cave->initial_border;
597 if (length != cave->h && length != (cave->y2-cave->y1 + 1))
598 Warn("map error: cave height = %d (%d visible), map height = %d",
599 cave->h, cave->y2 - cave->y1 + 1, length);
601 for (iter = maplines, y = 0; y < length && iter != NULL; iter = iter->next, y++)
603 const char *line = iter->data;
604 int slen = strlen(line);
606 if (slen != cave->w && slen != (cave->x2 - cave->x1 + 1))
607 Warn("map error in row %d: cave width = %d (%d visible), map width = %d",
608 y, cave->w, cave->x2 - cave->x1 + 1, slen);
610 /* use number of cells from cave or string, whichever is smaller.
611 so will not overwrite array! */
612 for (x = 0; x < MIN(cave->w, slen); x++)
613 cave->map[y][x] = gd_get_element_from_character (line[x]);
618 /* sets the cavesize array to default values */
619 static void set_cavesize_defaults(void)
629 /* sets the cavesize array to default values */
630 static void set_intermissionsize_defaults(void)
632 intermissionsize[0] = 40;
633 intermissionsize[1] = 22;
634 intermissionsize[2] = 0;
635 intermissionsize[3] = 0;
636 intermissionsize[4] = 19;
637 intermissionsize[5] = 11;
640 boolean gd_caveset_load_from_bdcff(const char *contents)
646 boolean reading_replay = FALSE;
647 boolean reading_map = FALSE;
648 boolean reading_mapcodes = FALSE;
649 boolean reading_highscore = FALSE;
650 boolean reading_objects = FALSE;
651 boolean reading_bdcff_demo = FALSE;
652 /* assume version to be 0.32, also when the file does not specify it explicitly */
653 GdString version_read = "0.32";
654 GList *mapstrings = NULL;
656 GHashTable *tags, *replay_tags;
657 GdObjectLevels levels = GD_OBJECT_LEVEL_ALL;
658 GdCave *default_cave;
662 set_cavesize_defaults();
663 set_intermissionsize_defaults();
664 gd_create_char_to_element_table();
666 tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free);
667 replay_tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free);
669 /* split into lines */
670 lines = getSplitStringArray (contents, "\n", 0);
672 /* attributes read will be set in cave. if no [cave]; they are stored
673 in the default cave; like in a [game] */
674 default_cave = gd_cave_new();
677 linenum = getStringArrayLength(lines);
679 for (lineno = 0; lineno < linenum; lineno++)
681 char *line = lines[lineno];
684 /* remove windows-nightmare \r-s */
685 while((r = strchr(line, '\r')))
688 if (strlen (line) == 0)
689 continue; /* skip empty lines */
691 /* just skip comments. be aware that map lines may start with a semicolon... */
692 if (!reading_map && line[0] == ';')
695 /* STARTING WITH A BRACKET [ IS A SECTION */
698 if (strcasecmp(line, "[cave]") == 0)
703 Warn("incorrect file format: new [cave] section, but already read some map lines");
704 g_list_free(mapstrings);
708 /* process any pending tags for game ... */
709 cave_process_tags(default_cave, tags, NULL);
711 /* ... to be able to create a copy for a new cave. */
712 cave = gd_cave_new_from_cave(default_cave);
713 gd_caveset = g_list_append (gd_caveset, cave);
715 else if (strcasecmp(line, "[/cave]") == 0)
717 cave_process_tags(cave, tags, mapstrings);
718 g_list_free(mapstrings);
721 if (g_hash_table_size(tags) != 0)
722 g_hash_table_foreach(tags, (GHFunc) cave_report_and_copy_unknown_tags_func, cave);
723 g_hash_table_remove_all(tags);
724 /* set this to point the pseudo-cave which holds default values */
727 else if (strcasecmp(line, "[map]") == 0)
730 if (mapstrings != NULL)
732 Warn("incorrect file format: new [map] section, but already read some map lines");
733 g_list_free(mapstrings);
737 else if (strcasecmp(line, "[/map]") == 0)
741 else if (strcasecmp(line, "[mapcodes]") == 0)
743 reading_mapcodes = TRUE;
745 else if (strcasecmp(line, "[/mapcodes]") == 0)
747 reading_mapcodes = FALSE;
749 else if (strcasecmp(line, "[highscore]") == 0)
751 reading_highscore = TRUE;
753 else if (strcasecmp(line, "[/highscore]") == 0)
755 reading_highscore = FALSE;
757 else if (strcasecmp(line, "[objects]") == 0)
759 reading_objects = TRUE;
761 else if (strcasecmp(line, "[/objects]") == 0)
763 reading_objects = FALSE;
765 else if (strcasecmp(line, "[demo]") == 0)
769 reading_bdcff_demo = TRUE;
771 if (cave != default_cave)
773 replay = gd_replay_new();
774 replay->saved = TRUE;
775 replay->success = TRUE; /* we think that it is a successful demo */
776 cave->replays = g_list_append(cave->replays, replay);
777 gd_strcpy(replay->player_name, "???"); /* name not saved */
781 Warn("[demo] section must be in [cave] section!");
784 else if (strcasecmp(line, "[/demo]") == 0)
786 reading_bdcff_demo = FALSE;
788 else if (strcasecmp(line, "[replay]") == 0)
790 reading_replay = TRUE;
792 else if (strcasecmp(line, "[/replay]") == 0)
796 reading_replay = FALSE;
797 replay = gd_replay_new();
799 /* set "saved" flag, so this replay will be written when the caveset is saved again */
800 replay->saved = TRUE;
801 replay_process_tags(replay, replay_tags);
804 /* BDCFF numbers levels from 1 to 5, but internally we number levels from 0 to 4 */
805 if (replay->level > 0)
809 /* report any remaining unknown tags */
810 g_hash_table_foreach(replay_tags, (GHFunc) replay_report_unknown_tags_func, NULL);
811 g_hash_table_remove_all(replay_tags);
813 if (replay->movements->len != 0)
815 cave->replays = g_list_append(cave->replays, replay);
819 Warn("no movements in replay!");
820 gd_replay_free(replay);
823 /* GOSH i hate bdcff */
824 else if (strncasecmp(line, "[level=", strlen("[level=")) == 0)
830 /* there IS an equal sign, and we also skip that, so this points to the numbers */
831 nums = strchr(line, '=') + 1;
832 num = sscanf(nums, "%d,%d,%d,%d,%d", l + 0, l + 1, l + 2, l + 3, l + 4);
837 Warn("invalid Levels tag: %s", line);
838 levels = GD_OBJECT_LEVEL_ALL;
844 for (n = 0; n < num; n++)
846 if (l[n] <= 5 && l[n] >= 1)
847 levels |= gd_levels_mask[l[n] - 1];
849 Warn("invalid level number %d", l[n]);
853 else if (strcasecmp(line, "[/level]") == 0)
855 levels = GD_OBJECT_LEVEL_ALL;
857 else if (strcasecmp(line, "[game]") == 0)
860 else if (strcasecmp(line, "[/game]") == 0)
863 else if (strcasecmp(line, "[BDCFF]") == 0)
866 else if (strcasecmp(line, "[/BDCFF]") == 0)
871 Warn("unknown section: \"%s\"", line);
879 /* just append to the mapstrings list. we will process it later */
880 mapstrings = g_list_append(mapstrings, line);
885 /* strip leading and trailing spaces AFTER checking if we are reading a map.
886 map lines might begin or end with spaces */
889 if (reading_highscore)
893 if (sscanf(line, "%d", &score) != 1 || strchr(line, ' ') == NULL)
894 { /* first word is the score */
895 Warn("highscore format incorrect");
899 if (cave == default_cave)
900 /* if we are reading the [game], add highscore to that one. */
901 /* from first space: the name */
902 gd_add_highscore(gd_caveset_data->highscore, strchr(line, ' ') + 1, score);
904 /* if a cave, add highscore to that. */
905 gd_add_highscore(cave->highscore, strchr(line, ' ') + 1, score);
911 /* read bdcff-style [demo], similar to a complete replay but cannot store like anything */
912 if (reading_bdcff_demo)
917 /* demo must be in [cave] section. we already showed an error message for this. */
918 if (cave == default_cave)
921 iter = g_list_last(cave->replays);
923 replay = (GdReplay *)iter->data;
924 replay_store_more_from_bdcff(replay, line);
931 GdObject *new_object;
933 new_object = gd_object_new_from_string(line);
936 new_object->levels = levels; /* apply levels to new object */
937 cave->objects = g_list_append(cave->objects, new_object);
941 Error("invalid object specification: %s", line);
947 /* has an equal sign -> some_attrib = parameters type line. */
948 if (strchr (line, '=') != NULL)
950 char *attrib, *param;
952 attrib = line; /* attrib is from the first char */
953 param = strchr(line, '=') + 1; /* param is after equal sign */
954 *strchr (line, '=') = 0; /* delete equal sign - line is therefore splitted */
956 /* own tag: not too much thinking :P */
959 g_hash_table_insert(replay_tags, g_strdup(attrib), g_strdup(param));
961 else if (reading_mapcodes)
963 if (strcasecmp("Length", attrib) == 0)
965 /* we do not support map code width != 1 */
966 if (strcmp(param, "1") != 0)
967 Warn(_("Only one-character map codes are currently supported!"));
971 /* the first character of the attribute is the element code itself */
972 gd_char_to_element[(int)attrib[0]] = gd_get_element_from_string(param);
976 else if (strcasecmp("Version", attrib) == 0)
978 gd_strcpy(version_read, param);
981 else if (strcasecmp(attrib, "Caves") == 0)
983 /* BDCFF files sometimes state how many caves they have */
984 /* we ignore this field. */
987 else if (strcasecmp(attrib, "Levels") == 0)
989 /* BDCFF files sometimes state how many levels they have */
990 /* we ignore this field. */
992 else if (strcasecmp(attrib, "CaveSize") == 0)
996 i = sscanf(param, "%d %d %d %d %d %d",
1004 /* allowed: 2 or 6 numbers */
1009 cavesize[4] = cavesize[0]-1;
1010 cavesize[5] = cavesize[1]-1;
1014 set_cavesize_defaults();
1015 Warn("invalid CaveSize tag: %s", line);
1018 else if (strcasecmp(attrib, "IntermissionSize") == 0)
1022 i = sscanf(param, "%d %d %d %d %d %d",
1023 intermissionsize + 0,
1024 intermissionsize + 1,
1025 intermissionsize + 2,
1026 intermissionsize + 3,
1027 intermissionsize + 4,
1028 intermissionsize + 5);
1030 /* allowed: 2 or 6 numbers */
1033 intermissionsize[2] = 0;
1034 intermissionsize[3] = 0;
1035 intermissionsize[4] = intermissionsize[0]-1;
1036 intermissionsize[5] = intermissionsize[1]-1;
1040 set_intermissionsize_defaults();
1041 Warn("invalid IntermissionSize tag: '%s'", line);
1044 else if (strcasecmp(attrib, "Effect") == 0)
1046 /* CHECK IF IT IS AN EFFECT */
1049 params = getSplitStringArray(param, " ", -1);
1051 /* an effect command has two parameters */
1052 if (getStringArrayLength(params) == 2)
1056 for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
1058 /* we have to search for this effect */
1059 if (gd_cave_properties[i].type == GD_TYPE_EFFECT &&
1060 strcasecmp(params[0], gd_cave_properties[i].identifier) == 0)
1062 /* found identifier */
1063 gpointer value = G_STRUCT_MEMBER_P (cave, gd_cave_properties[i].offset);
1065 *((GdElement *) value) = gd_get_element_from_string (params[1]);
1070 /* if we didn't find first element name */
1071 if (gd_cave_properties[i].identifier == NULL)
1073 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
1074 if (strcasecmp(params[0], "BOUNCING_BOULDER") == 0)
1075 cave->stone_bouncing_effect = gd_get_element_from_string (params[1]);
1076 else if (strcasecmp(params[0], "EXPLOSION3S") == 0)
1077 cave->explosion_effect = gd_get_element_from_string(params[1]);
1078 /* falling with one l... */
1079 else if (strcasecmp(params[0], "STARTING_FALING_DIAMOND") == 0)
1080 cave->diamond_falling_effect = gd_get_element_from_string (params[1]);
1081 /* dirt lookslike */
1082 else if (strcasecmp(params[0], "DIRT") == 0)
1083 cave->dirt_looks_like = gd_get_element_from_string (params[1]);
1084 else if (strcasecmp(params[0], "HEXPANDING_WALL") == 0 && strcasecmp(params[1], "STEEL_HEXPANDING_WALL") == 0)
1086 cave->expanding_wall_looks_like = O_STEEL;
1089 /* didn't find at all */
1090 Warn("invalid effect name '%s'", params[0]);
1094 Warn("invalid effect specification '%s'", param);
1096 freeStringArray(params);
1100 /* no special handling: this is a normal attribute. */
1102 if (cave == default_cave)
1104 /* we are reading the [game] */
1105 if (attrib_is_valid_for_caveset(attrib))
1107 /* if it is a caveset attrib, process it for the caveset. */
1108 struct_set_property(gd_caveset_data, gd_caveset_properties, attrib, param, 0);
1110 else if (attrib_is_valid_for_cave(attrib))
1112 /* it must be a default setting for all caves. is it a valid identifier? */
1113 /* yes, it is. add to the hash table, which will be copied for all caves. */
1114 g_hash_table_insert(tags, g_strdup(attrib), g_strdup(param));
1117 /* unknown setting - report. */
1118 Warn("invalid attribute for [game] '%s'", attrib);
1122 /* we are reading a [cave] */
1123 /* cave settings are immediately added to cave hash table. */
1124 /* if it is unknown, we have to remember it, and save it again. */
1125 g_hash_table_insert(tags, g_strdup(attrib), g_strdup(param));
1132 Error("cannot parse line: %s", line);
1137 Warn("incorrect file format: end of file, but still have some map lines read");
1138 g_list_free(mapstrings);
1142 /* the [game] section had some values which are default if not specified in [cave] sections. */
1143 /* these are used only for loading, so forget them now */
1144 if (default_cave->map)
1145 Warn(_("Invalid BDCFF: [game] section has a map"));
1146 if (default_cave->objects)
1147 Warn(_("Invalid BDCFF: [game] section has drawing objects defined"));
1150 freeStringArray(lines);
1151 g_hash_table_destroy(tags);
1152 g_hash_table_destroy(replay_tags);
1153 gd_cave_free(default_cave);
1155 /* old bdcff files hack. explanation follows. */
1156 /* there were 40x22 caves in c64 bd, intermissions were also 40x22, but the visible */
1157 /* part was the upper left corner, 20x12. 40x22 caves are needed, as 20x12 caves would */
1158 /* look different (random cave elements needs the correct size.) */
1159 /* also, in older bdcff files, there is no size= tag. caves default to 40x22 and 20x12. */
1160 /* even the explicit drawrect and other drawing instructions, which did set up intermissions */
1161 /* to be 20x12, are deleted. very very bad decision. */
1162 /* here we try to detect and correct this. */
1164 if (strEqual(version_read, "0.32"))
1168 Warn("No BDCFF version, or 0.32. Using unspecified-intermission-size hack.");
1170 for (iter = gd_caveset; iter != NULL; iter = iter->next)
1172 GdCave *cave = (GdCave *)iter->data;
1174 /* only applies to intermissions */
1175 /* not applied to mapped caves, as maps are filled with initial border, if the map read is smaller */
1176 if (cave->intermission && !cave->map)
1178 /* we do not set the cave to 20x12, rather to 40x22 with 20x12 visible. */
1188 /* and cover the invisible area */
1189 object.type = GD_FILLED_RECTANGLE;
1191 object.y1 = 11; /* 11, because this will also be the border */
1194 object.element = cave->initial_border;
1195 object.fill_element = cave->initial_border;
1197 cave->objects = g_list_prepend(cave->objects, g_memdup(&object, sizeof(object)));
1200 object.y1 = 0; /* 19, as it is also the border */
1202 cave->objects = g_list_prepend(cave->objects, g_memdup(&object, sizeof(object))); /* another */
1207 if (!strEqual(version_read, BDCFF_VERSION))
1208 Warn("BDCFF version %s, loaded caveset may have errors.", version_read);
1210 /* check for replays which are problematic */
1211 for (iter = gd_caveset; iter != NULL; iter = iter->next)
1212 gd_cave_check_replays((GdCave *)iter->data, TRUE, FALSE, FALSE);
1214 /* if there was some error message - return fail XXX */